diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000000..f2a2ef2248a4 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,222 @@ +// eslint does not properly load plugins loaded by presets +// this fixes that +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + root: true, + extends: [ + '@ephys/eslint-config-typescript', + '@ephys/eslint-config-typescript/node', + '@ephys/eslint-config-typescript/commonjs', + ], + plugins: ['mocha', 'jsdoc'], + rules: { + 'jsdoc/check-param-names': 'error', + 'jsdoc/check-tag-names': 'error', + 'jsdoc/check-types': 'off', + 'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }], + 'jsdoc/no-undefined-types': 'off', + 'jsdoc/require-description-complete-sentence': 'off', + 'jsdoc/require-example': 'off', + 'jsdoc/require-hyphen-before-param-description': 'off', + 'jsdoc/require-param': 'error', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'error', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-returns-description': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/valid-types': 'error', + 'jsdoc/no-types': 'error', + + // TODO: enable in follow-up PR. Requires the utils package. + 'no-restricted-syntax': 'off', + 'no-restricted-imports': 'off', + '@typescript-eslint/ban-types': 'off', + // TODO: enable in follow-up PR. Requires enabling TSC's noUncheckedIndexedAccess + '@typescript-eslint/no-unnecessary-condition': 'off', + // TODO: enable in follow-up PR. Requires manual code changes. + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/member-ordering': 'off', + 'unicorn/no-object-as-default-parameter': 'off', + '@typescript-eslint/prefer-optional-chain': 'off', + 'logical-assignment-operators': 'off', + }, + overrides: [ + { + files: ['**/*.{js,mjs,cjs}'], + rules: { + 'jsdoc/no-types': 'off', + 'jsdoc/require-param-type': 'error', + 'jsdoc/check-types': 'error', + 'jsdoc/require-returns-type': 'error', + }, + }, + { + files: ['**/*.js'], + rules: { + // These rules have been disabled in .js files to ease adoption. + // They'll be fixed during the TS migration. + // Remove these once most files have been migrated to TS. + + // This will catch a lot of bugs with early-returns + 'consistent-return': 'off', + + // code smells that should be resolved + 'no-restricted-syntax': 'off', + 'no-await-in-loop': 'off', + 'default-case': 'off', + 'no-loop-func': 'off', + 'no-shadow': 'off', + 'default-param-last': 'off', + 'no-fallthrough': 'off', + 'prefer-rest-params': 'off', + 'no-loss-of-precision': 'off', + + // optimisation + 'unicorn/consistent-function-scoping': 'off', + + // array.reduce is difficult to reason about and can almost always + // be replaced by a more explicit method + 'unicorn/no-array-reduce': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/prefer-spread': 'off', + + // makes code clearer + 'unicorn/prefer-default-parameters': 'off', + 'max-statements-per-line': 'off', + + // makes debug easier + 'func-names': 'off', + + // multi-assigns can be difficult to understand + // https://eslint.org/docs/rules/no-multi-assign + 'no-multi-assign': 'off', + + // GitHub's display length is 125 chars. + // This enforces that length. + 'max-len': 'off', + 'max-depth': 'off', + + // Reduce diff noise. + 'import/order': 'off', + + // consistency + 'unicorn/filename-case': 'off', + + // Passing a function reference to an array callback can accidentally introduce bug + // due to array methods passing more than one parameter. + 'unicorn/no-array-callback-reference': 'off', + }, + }, + { + // most tests are written in old JS style + // let's disable the most problematic rules for now. + // they're only disabled for .js files. + // .ts files will need to migrate. + files: ['packages/*/test/**/*.js'], + rules: { + 'func-names': 'off', + 'import/order': 'off', + + 'consistent-this': 'off', + 'no-invalid-this': 'off', + 'unicorn/no-this-assignment': 'off', + 'no-unused-expressions': 'off', + camelcase: 'off', + 'no-console': 'off', + 'no-prototype-builtins': 'off', + 'no-multi-spaces': 'off', + 'unicorn/error-message': 'off', + }, + }, + { + // Disable slow rules that are not important in tests (perf) + files: ['packages/*/test/**/*', '*.test.{ts,js}'], + rules: { + 'import/no-extraneous-dependencies': 'off', + // no need to check jsdoc in tests & docs + 'jsdoc/check-types': 'off', + 'jsdoc/valid-types': 'off', + 'jsdoc/tag-lines': 'off', + 'jsdoc/check-tag-names': 'off', + + // Enable test-specific rules (perf) + 'mocha/no-exclusive-tests': 'error', + 'mocha/no-skipped-tests': 'warn', + + // it's fine if we're not very efficient in tests. + 'no-inner-declarations': 'off', + 'unicorn/no-unsafe-regex': 'off', + + // because of Chai + '@typescript-eslint/no-unused-expressions': 'off', + }, + env: { + mocha: true, + }, + }, + { + files: ['packages/*/test/types/**/*'], + rules: { + // This code is never executed, it's typing only, so these rules make no sense: + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-floating-promises': 'off', + 'no-console': 'off', + }, + }, + { + files: ['**/tsconfig.json'], + rules: { + 'json/*': ['error', { allowComments: true }], + }, + }, + { + files: ['sscce.ts'], + rules: { + 'no-console': 'off', + }, + }, + ], + settings: { + jsdoc: { + tagNamePreference: { + augments: 'extends', + }, + structuredTags: { + typeParam: { + type: false, + required: ['name'], + }, + category: { + type: false, + required: ['name'], + }, + internal: { + type: false, + }, + hidden: { + type: false, + }, + }, + }, + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + ignorePatterns: [ + 'packages/*/lib/**/*', + 'packages/*/types/**/*', + 'packages/**/skeletons/**/*', + '.typedoc-build', + 'packages/cli/migrations/**/*', + 'packages/cli/seeds/**/*', + ], + env: { + node: true, + mocha: true, + es6: true, + es2020: true, + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 38d78cf4ea61..000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "extends": "eslint:recommended", - "rules": { - "mocha/no-exclusive-tests": "error", - "mocha/no-skipped-tests": "warn", - - "jsdoc/check-param-names": "error", - "jsdoc/check-tag-names": "error", - "jsdoc/check-types": "error", - "jsdoc/newline-after-description": "error", - "jsdoc/no-undefined-types": "off", - "jsdoc/require-description-complete-sentence": "off", - "jsdoc/require-example": "off", - "jsdoc/require-hyphen-before-param-description": "off", - "jsdoc/require-param": "error", - "jsdoc/require-param-description": "off", - "jsdoc/require-param-name": "error", - "jsdoc/require-param-type": "error", - "jsdoc/require-returns-description": "off", - "jsdoc/require-returns-type": "error", - "jsdoc/valid-types": "error", - - // style - "array-bracket-spacing": "error", - "comma-spacing": "error", - "key-spacing": "error", - "keyword-spacing": "error", - "object-curly-spacing": ["error", "always"], - "func-call-spacing": ["error", "never"], - "new-cap": [ - "error", - { - "properties": false - } - ], - "semi": ["error", "always"], - "space-before-function-paren": ["error", { - "named": "never", - "anonymous": "never", - "asyncArrow": "always" - }], - "space-before-blocks": "error", - "space-infix-ops": "error", - "no-multi-spaces": "error", - "arrow-parens": ["error", "as-needed"], - "comma-style": ["error", "last"], - "indent": [ - "error", - 2, - { - "SwitchCase": 1 - } - ], - "no-extra-parens": "error", - "curly": "off", - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "linebreak-style": "error", - "camelcase": "error", - "comma-dangle": "error", - - // functional - "valid-jsdoc": "off", - "strict": ["error", "global"], - "no-var": "error", - "prefer-const": "error", - "prefer-arrow-callback": "error", - "no-extend-native": "error", - "wrap-iife": ["error", "any"], - "no-use-before-define": "off", - "no-caller": "error", - "no-irregular-whitespace": "error", - "max-depth": ["error", 6], - "no-loop-func": "warn", - "object-shorthand": "error", - "one-var-declaration-per-line": "error", - "no-shadow": "warn", - "prefer-template": "error", - "no-else-return": ["error", { "allowElseIf": false }], - "no-lonely-if": "error", - "no-throw-literal": "error", - "prefer-promise-reject-errors": "error", - "no-invalid-this": "error", - "radix": "error", - "no-with": "error", - "no-useless-concat": "error", - "no-useless-catch": "error", - "no-useless-call": "error", - "no-unused-expressions": "error", - "no-sequences": "error", - "no-self-compare": "error", - "no-case-declarations": "off", - "prefer-object-spread": "error" - }, - "settings": { - "jsdoc": { - "tagNamePreference": { - "augments": "extends" - } - } - }, - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "script" - }, - "plugins": ["mocha", "jsdoc"], - "env": { - "node": true, - "mocha": 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/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..a86b051e0a45 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @sequelize/code-reviewers diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000000..c30a9c2cd86d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: sequelize +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 9be60467005d..b8432654f69f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,109 +1,87 @@ --- -name: Bug report +name: 🐛 Bug report about: Create a bug report to help us improve title: '' -labels: '' +labels: ['pending-approval'] +type: Bug assignees: '' - --- ## Issue Creation Checklist -[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) +- [ ] I understand that my issue will be automatically closed if I don't fill in the requested information +- [ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) ## Bug Description -### SSCCE + + +### Reproducible Example -**Here is the link to the SSCCE for this issue:** LINK-HERE +Here is the link to the SSCCE for this issue: -```js -// You can delete this code block if you have included a link to your SSCCE above! -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? -_I wanted Foo!_ - ### What is actually happening? - - -_The output was Bar!_ - -``` -Output here -``` - -### Additional context - -Add any other context and details here. + ### Environment -- Sequelize version: XXX -- Node.js version: XXX -- If TypeScript related: TypeScript version: XXX + - +- Sequelize version: +- Node.js version: +- If TypeScript related: TypeScript version: +- Database & Version: +- Connector library & Version: -### How does this problem relate to dialects? +## Would you be willing to resolve this issue by submitting a Pull Request? - + -- [ ] I think this problem happens regardless of the dialect. -- [ ] I think this problem happens only for the following dialect(s): -- [ ] I don't know, I was using PUT-YOUR-DIALECT-HERE, with connector library version XXX and database version XXX +- [ ] Yes, I have the time and I know how to start. +- [ ] Yes, I have the time but I will need guidance. +- [ ] No, I don't have the time, but my company or I are [supporting Sequelize through donations on OpenCollective](https://opencollective.com/sequelize). +- [ ] No, I don't have the time, and I understand that I will need to wait until someone from the community or maintainers is interested in resolving my issue. -### 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. +_Indicate your interest in the resolution of this issue by adding the 👍 reaction. Comments such as "+1" will be removed._ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..a410cc485271 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 Help & Questions + url: https://github.com/sequelize/sequelize/discussions + about: The issue tracker is for bug reports and feature requests. Use GitHub Discussions for question and help requests. + - name: 🔧 Having issues with Sequelize CLI? + url: https://github.com/sequelize/cli/issues + about: Please file an issue in the Sequelize CLI repository. + - name: 📗 Found an issue in our documentation? + url: https://github.com/sequelize/website/issues + about: Please file an issue in the Sequelize website repository. diff --git a/.github/ISSUE_TEMPLATE/docs_issue.md b/.github/ISSUE_TEMPLATE/docs_issue.md deleted file mode 100644 index c26ce93b8c62..000000000000 --- a/.github/ISSUE_TEMPLATE/docs_issue.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -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/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9b0b4062f53e..dd36a288ca4e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,62 +1,55 @@ --- -name: Feature request +name: 🚀 Feature request about: Suggest an idea for this project title: '' -labels: '' +labels: ['pending-approval'] +type: Feature assignees: '' - --- ## Issue Creation Checklist -[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) +- [ ] I understand that my issue will be automatically closed if I don't fill in the requested information +- [ ] 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 ... - -### 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 -// If applicable, add a code snippet showing how your feature would be used in a real use-case -``` - -### Why should this be in Sequelize +### Describe the feature you'd like to see implemented -Short explanation why this should be part of Sequelize rather than a separate package. + + -### Describe alternatives/workarounds you've considered +### Describe why you would like this feature to be added to Sequelize -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. - -## Feature Request Checklist - - + ### Is this feature dialect-specific? - [ ] No. This feature is relevant to Sequelize as a whole. -- [ ] Yes. This feature only applies to the following dialect(s): XXX, YYY, ZZZ +- [ ] Yes. This feature only applies to the following dialect(s): -### Would you be willing to implement this feature by submitting a Pull Request? +### 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. +- [ ] Yes, I have the time but I will need guidance. +- [ ] No, I don't have the time, but my company or I are [supporting Sequelize through donations on OpenCollective](https://opencollective.com/sequelize). +- [ ] No, I don't have the time, and I understand that I will need to wait until someone from the community or maintainers is interested in implementing my feature. + +--- + + + +_Indicate your interest in the addition of this feature by adding the 👍 reaction. Comments such as "+1" will be removed._ diff --git a/.github/ISSUE_TEMPLATE/other_issue.md b/.github/ISSUE_TEMPLATE/other_issue.md deleted file mode 100644 index 8b29a78cad13..000000000000 --- a/.github/ISSUE_TEMPLATE/other_issue.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: Other -about: Open an issue that does not fall directly into the other categories -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. - -If applicable, you can add some code. In this case, an SSCCE/MCVE/reprex is much better than just an isolated code snippet. - - - -### 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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 91d8c314aaa4..1cf0421cbafa 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,23 +2,24 @@ Thanks for wanting to fix something on Sequelize - we already love you! Please fill in the template below. If unsure about something, just do as best as you're able. - -If your PR only contains changes to documentation, you may skip the template below. --> -### 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? -- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? +- [ ] If a documentation update is necessary, have you opened a PR to [the documentation repository](https://github.com/sequelize/website/)? - [ ] 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/main/CONTRIBUTING.md)? +- [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? +- [ ] Does the name of your PR follow [our conventions](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#6-commit-your-modifications)? -### Description of change +## Description of Changes + +## List of Breaking Changes + + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..daae73f988c2 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,189 @@ +# Sequelize AI Development Guide + +## Architecture Overview + +Sequelize is a monorepo ORM for Node.js supporting 9+ databases. Key architectural patterns: + +- **Packages**: Core ORM (`@sequelize/core`) + dialect-specific packages (`@sequelize/postgres`, `@sequelize/mysql`, etc.) +- **Dialect Pattern**: Each database has its own package with `dialect.ts`, `query-generator.js`, `query-interface.js`, and `connection-manager.ts` +- **Model System**: Legacy models use `model.js` with TypeScript definitions, but **all new code should be written in TypeScript** +- **Build System**: Uses esbuild via `build-packages.mjs` to compile TypeScript to both CommonJS and ESM +- **TypeScript Migration**: Project is actively migrating from JavaScript to TypeScript - prefer `.ts` files for all new implementations + +## Development Workflow + +### Essential Commands + +```bash +# Build specific package +node build-packages.mjs core +node build-packages.mjs postgres + +# Run tests by dialect +yarn test-integration-postgres +yarn test-unit + +# Start local DBs for testing +yarn start-postgres-latest +yarn reset-postgres + +# SSCCE (debugging minimal reproductions) +yarn sscce-postgres +``` + +### Database Testing Setup + +- Docker containers in `dev/{dialect}/{latest|oldest}/` with start/stop/reset scripts +- Test config in `packages/core/test/config/config.ts` with environment-based settings +- Integration tests use `Support.createMultiTransactionalTestSequelizeInstance()` for isolation + +## Code Patterns + +### Dialect Implementation + +When adding dialect features: + +1. Update `packages/{dialect}/src/dialect.ts` for feature support flags +2. Implement in `query-generator.js` (legacy) or `query-generator.ts` (preferred for new features) +3. Add to `query-interface.js` (legacy) or `query-interface.ts` (preferred) for schema operations +4. **Write all new implementations in TypeScript** - avoid creating new `.js` files +5. TypeScript definitions are co-located with implementation files + +### Model Definition Pattern + +```javascript +// Core pattern in tests and examples +this.User = sequelize.define('User', { + field: DataTypes.STRING, + uniqueField: { type: DataTypes.STRING, unique: true }, +}); +``` + +### Testing Conventions + +- Use `Support.getTestDialectTeaser()` for dialect-specific test descriptions +- `beforeEach`/`afterEach` with `customSequelize.sync({ force: true })` and `.close()` +- Test files: `.test.js` for integration, `.test.ts` for unit tests +- Chai assertions with custom extensions in `packages/core/test/chai-extensions.d.ts` + +### Writing Integration Tests for New Features + +```typescript +import type { CreationOptional, InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { beforeAll2, sequelize, setResetMode } from '../support'; + +describe('Model#newFeature', () => { + // Skip entire suite if feature is not supported + if (!sequelize.dialect.supports.newFeature) { + return; + } + + setResetMode('destroy'); + + const vars = beforeAll2(async () => { + class User extends Model, InferCreationAttributes> { + declare id: CreationOptional; + + @Attribute(DataTypes.INTEGER) + @NotNull + declare someField: number; + + @Attribute(DataTypes.STRING) + declare name: string | null; + } + + sequelize.addModels([User]); + await sequelize.sync({ force: true }); + + return { User }; + }); + + beforeEach(async () => { + await vars.User.create({ someField: 1, name: 'test' }); + }); + + it('works with basic functionality', async () => { + const user = await vars.User.findByPk(1, { rejectOnEmpty: true }); + await user.newFeature(); + expect(user.someField).to.equal(2); + }); + + it('handles edge cases properly', async () => { + const user = await vars.User.findByPk(1, { rejectOnEmpty: true }); + await expect(user.newFeature({ invalid: 'option' })).to.be.rejected; + }); + + // Test dialect-specific behavior + if (sequelize.dialect.name === 'postgres') { + it('supports postgres-specific functionality', async () => { + const user = await vars.User.findByPk(1, { rejectOnEmpty: true }); + await user.newFeature({ postgresOption: true }); + expect(user.someField).to.equal(3); + }); + } + + // Test feature variations + if (sequelize.dialect.supports.newFeature?.advanced) { + it('supports advanced newFeature options', async () => { + const user = await vars.User.findByPk(1, { rejectOnEmpty: true }); + await user.newFeature({ mode: 'advanced' }); + expect(user.someField).to.equal(5); + }); + } +}); +``` + +### Testing Database Compatibility + +- Use `dialect.supports.featureName` checks to skip unsupported tests +- Test against both oldest and latest database versions via Docker containers +- Integration tests should use `Support.createMultiTransactionalTestSequelizeInstance()` for proper isolation +- Unit tests for pure logic, integration tests for database interactions + +## File Structure Conventions + +### Package Structure + +``` +packages/{dialect}/ +├── src/ +│ ├── dialect.ts # Feature flags & dialect config +│ ├── query-generator.js # SQL generation logic +│ ├── query-interface.js # Schema operations +│ ├── connection-manager.ts # Connection pooling +│ └── index.ts # Package exports +└── test/ # Package-specific tests +``` + +### Core Package Critical Files + +- `src/model.js` - Main Model class implementation +- `src/sequelize.js` - Core Sequelize class +- `src/associations/` - Relationship definitions +- `src/data-types.ts` - Type system +- `test/support.ts` - Test utilities and setup + +## Integration Points + +### Cross-Package Dependencies + +- Core package is database-agnostic, dialects extend abstract classes +- `@sequelize/utils` provides shared utilities across packages +- Test support utilities in `packages/core/test/support.ts` used across dialect tests + +### Build & Export System + +- Dual CommonJS/ESM exports via `exports` field in package.json +- TypeScript compiled to `lib/` directory with both `.d.ts` and `.d.mts` files +- Legacy decorators exported separately as `@sequelize/core/decorators-legacy` + +## Common Gotchas + +- Always use `dialect.supports.xyz` checks before implementing dialect-specific features +- Integration tests require database containers - use dev scripts to start them +- Model methods use `.js` files with separate TypeScript definitions +- When modifying query generation, update both the base class and dialect-specific implementations +- **Prefer TypeScript for all new code** - only modify existing `.js` files when necessary 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/authors.yml b/.github/workflows/authors.yml new file mode 100644 index 000000000000..546cd4ab570d --- /dev/null +++ b/.github/workflows/authors.yml @@ -0,0 +1,37 @@ +name: 'authors update' +on: + schedule: + # Run once a week at 00:05 AM UTC on Sunday. + - cron: '5 0 * * 0' + + workflow_dispatch: + +permissions: + contents: read + +jobs: + authors_update: + permissions: + contents: write # for gr2m/create-or-update-pull-request-action to push local changes + pull-requests: write # for gr2m/create-or-update-pull-request-action to create a PR + if: github.repository == 'sequelize/sequelize' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: '0' # This is required to actually get all the authors + persist-credentials: false + - run: 'dev/update-authors.js' # Run the AUTHORS tool + - uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # Create a PR or update the Action's existing PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + author: SequelizeJS + body: > + Here are some new additions to the AUTHORS file. + This is an automatically generated PR by the + `authors.yml` GitHub Action, which runs `dev/update-authors.js`. + branch: 'actions/authors-update' # Custom branch *just* for this Action. + commit-message: 'meta: update sequelize AUTHORS' + labels: meta + title: 'meta: update sequelize AUTHORS' diff --git a/.github/workflows/auto-add-pending-approval.yml b/.github/workflows/auto-add-pending-approval.yml new file mode 100644 index 000000000000..a2de741ae76b --- /dev/null +++ b/.github/workflows/auto-add-pending-approval.yml @@ -0,0 +1,23 @@ +name: Label new issues with pending-approval +on: + issues: + types: + - opened +permissions: + contents: read + +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["pending-approval"] + }) diff --git a/.github/workflows/auto-remove-awaiting-response-label.yml b/.github/workflows/auto-remove-awaiting-response-label.yml index 1af396f8c67b..8ae362ab5eaf 100644 --- a/.github/workflows/auto-remove-awaiting-response-label.yml +++ b/.github/workflows/auto-remove-awaiting-response-label.yml @@ -6,10 +6,11 @@ on: jobs: auto-remove-awaiting-response-label: name: Run + if: "${{ contains(github.event.issue.labels.*.name, 'status: awaiting response') }}" runs-on: ubuntu-latest env: # Case insensitive. Replace spaces with `%20`. - LABEL_TO_REMOVE: "status:%20awaiting%20response" + LABEL_TO_REMOVE: 'status:%20awaiting%20response' steps: - name: Run run: |- diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 000000000000..16467928c07d --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,57 @@ +name: auto-update PRs & label conflicts +on: + push: + branches: + - main + # can also be used with the pull_request event + pull_request_target: + types: + - synchronize + # allow the workflow to correct any label incorrectly added or removed by a user. + - labeled + - unlabeled + # update the PR as soon as the auto-merge is enabled. + - auto_merge_enabled + # process the PR once they appear in the search filters + - ready_for_review + - opened + - reopened +jobs: + autoupdate: + runs-on: ubuntu-latest + steps: + # This step is used by the fork update feature, which uses git instead of the API + - name: Configure git + env: + UPDATE_FORK_USERNAME: '${{ vars.UPDATE_FORK_USERNAME }}' + UPDATE_FORK_EMAIL: '${{ vars.UPDATE_FORK_EMAIL }}' + run: | + # The username of the "UPDATE_FORK_PAT" owner + git config --global user.name "${UPDATE_FORK_USERNAME}" + # The email of the "UPDATE_FORK_PAT" owner + git config --global user.email "${UPDATE_FORK_EMAIL}" + - name: Generate Sequelize Bot Token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: '${{ secrets.SEQUELIZE_BOT_APP_ID }}' + private-key: '${{ secrets.SEQUELIZE_BOT_PRIVATE_KEY }}' + - uses: sequelize/pr-auto-update-and-handle-conflicts@74929c430b8843e691e7b83d229d8d13d78d89e3 # 2.0.0 + with: + conflict-label: 'conflicted' + conflict-requires-ready-state: 'ready_for_review' + conflict-excluded-authors: 'bot/renovate' + update-pr-branches: true + update-requires-auto-merge: true + update-requires-ready-state: 'ready_for_review' + update-excluded-authors: 'bot/renovate' + update-excluded-labels: 'no-autoupdate' + env: + # We need to use a GitHub App PAT to update PRs + # or workflows will not be triggered by the update + GITHUB_TOKEN: '${{ steps.generate-token.outputs.token }}' + + # updating forks require a user PAT instead of an app PAT, + # as the permissions of the app PAT do not apply to forks. + UPDATE_FORK_PAT: '${{ secrets.UPDATE_FORK_PAT }}' + UPDATE_FORK_USERNAME: '${{ vars.UPDATE_FORK_USERNAME }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 941c0ed80a0e..2aa8599fc7c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,193 +1,360 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - renovate/** + pull_request: + merge_group: + workflow_dispatch: -env: - SEQ_DB: sequelize_test - SEQ_USER: sequelize_test - SEQ_PW: sequelize_test +# workflow_ref includes the branch ref & the file name +# which means a unique run per branch for push & pull_request +# pull_request_target would use the ref of the target branch, which would not be suitable +concurrency: + group: '${{ github.workflow_ref }}' + cancel-in-progress: true jobs: + install-and-build: + # We already run the CI on "push" for renovate branches + if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + strategy: + fail-fast: false + matrix: + node-version: [18, 20] + name: Upload install and build artifact (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + - name: Install dependencies + run: yarn install --immutable + - name: Build sequelize + run: yarn build + - name: Reset NX cache + run: yarn nx reset + - name: Compress artifact + run: tar -cf install-build-node-${{ matrix.node-version }}.tar ./packages/*/lib ./node_modules ./packages/*/node_modules + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + path: install-build-node-${{ matrix.node-version }}.tar + retention-days: 1 lint: - name: Lint code and docs + name: Lint code runs-on: ubuntu-latest + needs: install-and-build steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: 20.19.6 + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: - node-version: 12.x - - run: npm install - - run: npm run lint - - run: npm run lint-docs + name: install-build-artifact-node-20 + - name: Extract artifact + run: tar -xf install-build-node-20.tar + - run: yarn test:format + unit-test: + strategy: + fail-fast: false + matrix: + node-version: [18, 20] + name: Unit test all dialects (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + - name: Extract artifact + run: tar -xf install-build-node-${{ matrix.node-version }}.tar + - name: ESM / CJS export equivalence + run: yarn test-unit-esm + - name: Unit tests (validator.js) + run: yarn lerna run test-unit --scope=@sequelize/validator.js + - name: Unit tests (cli) + run: yarn lerna run test-unit --scope=@sequelize/cli + - name: Unit tests (utils) + run: yarn lerna run test-unit --scope=@sequelize/utils + - name: Unit tests (core - mariadb) + run: yarn lerna run test-unit-mariadb --scope=@sequelize/core + - name: Unit tests (mariadb package) + run: yarn lerna run test-unit --scope=@sequelize/mariadb + - name: Unit tests (core - mysql) + run: yarn lerna run test-unit-mysql --scope=@sequelize/core + - name: Unit tests (mysql package) + run: yarn lerna run test-unit --scope=@sequelize/mysql + - name: Unit tests (core - postgres) + run: yarn lerna run test-unit-postgres --scope=@sequelize/core + - name: Unit tests (postgres package) + run: yarn lerna run test-unit --scope=@sequelize/postgres + - name: Unit tests (core - sqlite3) + run: yarn lerna run test-unit-sqlite3 --scope=@sequelize/core + - name: Unit tests (core - mssql) + run: yarn lerna run test-unit-mssql --scope=@sequelize/core + - name: Unit tests (mssql package) + run: yarn lerna run test-unit --scope=@sequelize/mssql + - name: Unit tests (core - db2) + run: yarn lerna run test-unit-db2 --scope=@sequelize/core + - name: Unit tests (core - ibmi) + run: yarn lerna run test-unit-ibmi --scope=@sequelize/core + - name: Unit tests (core - snowflake) + run: yarn lerna run test-unit-snowflake --scope=@sequelize/core + - name: SQLite SSCCE + run: yarn sscce-sqlite3 + test-win: + strategy: + fail-fast: false + matrix: + node-version: [18, 20] + name: Build and test on Windows (Node ${{ matrix.node-version }}) + runs-on: windows-latest + needs: lint # don't bother running if lint tests fail + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + - name: Install dependencies + run: yarn install --immutable + - name: Build sequelize + run: yarn build + - name: Unit tests + run: yarn lerna run test-unit + - name: SSCCE + run: yarn sscce-sqlite3 + - name: Integration tests (sqlite3) + run: yarn lerna run test-integration-sqlite3 --scope=@sequelize/core + docs: + name: Generate TypeDoc + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: 20.19.6 + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-20 + - name: Extract artifact + run: tar -xf install-build-node-20.tar + - run: yarn docs test-typings: strategy: fail-fast: false matrix: - ts-version: ['3.9', '4.0', '4.1'] + ts-version: ['5.5', '5.6', '5.7', '5.8'] name: TS Typings (${{ matrix.ts-version }}) runs-on: ubuntu-latest + needs: lint steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12.x - - run: npm install - - run: npm install --save-dev typescript@~${{ matrix.ts-version }} - - run: npm run test-typings - test-sqlite: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: 20.19.6 + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-20 + - name: Extract artifact + run: tar -xf install-build-node-20.tar + - name: Install TypeScript + run: yarn add typescript@~${{ matrix.ts-version }} + - name: Typing Tests + run: yarn test-typings + test-sqlite3: strategy: fail-fast: false matrix: - node-version: [10, 12] - name: SQLite (Node ${{ matrix.node-version }}) + node-version: [18, 20] + name: sqlite3 (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest + needs: [unit-test, test-typings] env: - DIALECT: sqlite + DIALECT: sqlite3 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version: ${{ matrix.node-version }} - - run: npm install - - name: Unit Tests - run: npm run test-unit + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + - name: Extract artifact + run: tar -xf install-build-node-${{ matrix.node-version }}.tar - name: Integration Tests - run: npm run test-integration + run: yarn lerna run test-integration --scope=@sequelize/core test-postgres: strategy: fail-fast: false matrix: - node-version: [10, 12] - postgres-version: [9.5, 10] # Does not work with 12 + node-version: [18, 20] + postgres-version: [oldest, latest] minify-aliases: [true, false] native: [true, false] - name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} (Node ${{ matrix.node-version }})${{ matrix.minify-aliases && ' (minified aliases)' || '' }} + name: postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} (Node ${{ matrix.node-version }})${{ matrix.minify-aliases && ' (minified aliases)' || '' }} runs-on: ubuntu-latest - services: - postgres: - 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 + needs: [unit-test, test-typings] 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 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm install pg-native + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + - name: Extract artifact + run: tar -xf install-build-node-${{ matrix.node-version }}.tar + - name: Install pg-native + run: yarn workspace @sequelize/core add pg-native if: matrix.native - - name: Unit Tests - run: npm run test-unit - if: ${{ !matrix.minify-aliases }} + - run: yarn start-postgres-${{ matrix.postgres-version }} - name: Integration Tests - run: npm run test-integration - test-mysql-mariadb: + run: yarn lerna run test-integration --scope=@sequelize/core + test-oldest-latest: 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: 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: ${{ matrix.name }} (Node ${{ matrix.node-version }}) + node-version: [18, 20] + database-version: [oldest, latest] + dialect: [mysql, mariadb, db2] + name: ${{ matrix.dialect }} ${{ matrix.database-version }} (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 + needs: [unit-test, test-typings] 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 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version: ${{ matrix.node-version }} - - run: npm install - - name: Unit Tests - run: npm run test-unit + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + - name: Extract artifact + run: tar -xf install-build-node-${{ matrix.node-version }}.tar + - run: yarn start-${{ matrix.dialect }}-${{ matrix.database-version }} - name: Integration Tests - run: npm run test-integration - test-mssql: + run: yarn lerna run test-integration --scope=@sequelize/core + test-mssql-latest: strategy: fail-fast: false matrix: - node-version: [10, 12] - mssql-version: [2017, 2019] - name: MSSQL ${{ matrix.mssql-version }} (Node ${{ matrix.node-version }}) + node-version: [18, 20] + name: mssql latest (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 + needs: [unit-test, test-typings] 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 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version: ${{ matrix.node-version }} - - run: npm install - - name: Unit Tests - run: npm run test-unit + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-${{ matrix.node-version }} + - name: Extract artifact + run: tar -xf install-build-node-${{ matrix.node-version }}.tar + - run: yarn start-mssql-latest - name: Integration Tests - run: npm run test-integration + run: yarn lerna run test-integration --scope=@sequelize/core + # TODO: the following CI job is disabled due to mssql 2017 Docker image not working with ubuntu-22.04 or later. See https://github.com/sequelize/sequelize/pull/17772 + # test-mssql-oldest: + # strategy: + # fail-fast: false + # matrix: + # node-version: [18, 20] + # name: mssql oldest (Node ${{ matrix.node-version }}) + # runs-on: ubuntu-20.04 + # needs: [unit-test, test-typings] + # env: + # DIALECT: mssql + # steps: + # - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + # with: + # node-version: ${{ matrix.node-version }} + # cache: yarn + # - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + # with: + # name: install-build-artifact-node-${{ matrix.node-version }} + # - name: Extract artifact + # run: tar -xf install-build-node-${{ matrix.node-version }}.tar + # - run: yarn start-mssql-oldest + # - name: Integration Tests + # run: yarn lerna run test-integration --scope=@sequelize/core 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' + needs: + # TODO: add test-mssql-oldest back here when it's uncommented + [docs, test-sqlite3, test-postgres, test-oldest-latest, test-mssql-latest] + if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: '${{ secrets.NPM_TOKEN }}' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - name: Generate Sequelize Bot Token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: '${{ secrets.SEQUELIZE_BOT_APP_ID }}' + private-key: '${{ secrets.SEQUELIZE_BOT_PRIVATE_KEY }}' + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + # Number of commits to fetch. 0 indicates all history for all branches and tags. + # We need the entire history to generate the changelog properly + fetch-depth: 0 + # The credentials used for checkout are persisted + # Lerna will use the same credentials later for "git push". + # This must be done using the Sequelize bot. + token: '${{ steps.generate-token.outputs.token }}' + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + node-version: 20.19.6 + cache: yarn + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: install-build-artifact-node-20 + - name: Extract artifact + run: tar -xf install-build-node-20.tar + - name: Configure git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "bot@sequelize.org" + - name: Set npm auth token + run: npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" + - run: yarn publish-all + env: + GITHUB_TOKEN: '${{ steps.generate-token.outputs.token }}' + GH_TOKEN: '${{ steps.generate-token.outputs.token }}' + - id: sequelize + uses: sdepold/github-action-get-latest-release@aa12fcb2943e8899cbcc29ff6f73409b32b48fa1 # master with: - node-version: 12.x - - run: npm install - - run: npx semantic-release + repository: sequelize/sequelize + - name: Notify channels + run: | + curl -XPOST -u "sdepold:${{ secrets.GH_TOKEN }}" -H "Accept: application/vnd.github.v3+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/sequelize/dispatches --data '{"event_type":"Release notifier","client_payload":{"release-id": ${{ steps.sequelize.outputs.id }}}}' + - name: Notify docs repo + run: | + curl -XPOST -u "sdepold:${{ secrets.GH_TOKEN }}" -H "Accept: application/vnd.github.v3+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/website/dispatches --data '{"event_type":"Build website"}' diff --git a/.github/workflows/notify.yml b/.github/workflows/notify.yml new file mode 100644 index 000000000000..7e3268c8e422 --- /dev/null +++ b/.github/workflows/notify.yml @@ -0,0 +1,37 @@ +# Get releases: +# curl -XGET -u "username:access-token" -H "Accept: application/vnd.github.everest-preview+json" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/sequelize/releases + +# Trigger manually: +# curl -XPOST -u "username:access-token" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/sequelize/dispatches --data '{"event_type":"Release notifier","client_payload":{"release-id": release-id}}' + +name: Notify release channels +on: repository_dispatch +jobs: + tweet: + name: Tweet release + runs-on: ubuntu-latest + steps: + - uses: cardinalby/git-get-release-action@319798e20e923b75a49d335f1afdaf6f18422118 # v1.1 + id: releaseInfo + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + releaseId: ${{ github.event.client_payload.release-id }} + - uses: ethomson/send-tweet-action@288f9339e0412e3038dce350e0da5ecdf12133a6 # v1.0.0 + with: + status: 'We have just released ${{ steps.releaseInfo.outputs.name }} of Sequelize. https://github.com/sequelize/sequelize/releases/tag/${{ steps.releaseInfo.outputs.name }}' + consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }} + consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} + access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} + access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + notify-opencollective: + name: Notify OpenCollective + runs-on: ubuntu-latest + steps: + - uses: sequelize/proxy-release-to-open-collective@d8edaf56f12f51518fb97829b699a2e2e6d8166e # main + with: + releaseId: ${{ github.event.client_payload.release-id }} + projectSlug: sequelize/sequelize + ocSlug: sequelize + ocApiKey: ${{ secrets.OPEN_COLLECTIVE_KEY }} + githubToken: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 000000000000..225ea3f3f502 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,45 @@ +name: 'Lint PR' +on: + pull_request: + types: + - opened + - edited + - synchronize +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + main: + permissions: + pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs + statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR + name: PR has semantic title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + fix + feat + docs + meta + scopes: | + cli + core + db2 + ibmi + mariadb + mssql + mysql + oracle + postgres + snowflake + sqlite3 + utils + validator.js + ignoreLabels: | + ignore-semantic-pull-request diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 000000000000..9756ade75e33 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +on: + workflow_dispatch: {} + pull_request: {} + push: + branches: + - main + - master + paths: + - .github/workflows/semgrep.yml + schedule: + # random HH:MM to avoid a load spike on GitHub Actions at 00:00 + - cron: 35 14 * * * +name: Semgrep +jobs: + semgrep: + name: Scan + runs-on: ubuntu-22.04 + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + container: + image: returntocorp/semgrep + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - run: semgrep ci diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..3f4b499dfc39 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,61 @@ +name: 'Stale issue handler' +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + stale: + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs + runs-on: ubuntu-latest + steps: + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + id: stale-bug + with: + stale-issue-label: 'stale' + stale-issue-message: 'This bug report has not been verified by maintainers yet, and 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, consider commenting with more information to help us verify it.' + days-before-issue-stale: -1 + days-before-issue-close: -1 + operations-per-run: 1000 + days-before-pr-stale: -1 + days-before-pr-close: -1 + only-issue-labels: 'type: bug' + any-of-issue-labels: 'pending-approval' + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + id: stale-feature + with: + stale-issue-label: 'stale' + stale-issue-message: 'This feature request has not been approved by maintainers yet, and has been open for 14 days without activity. It will be closed as "not planned" if no further activity occurs within the next 14 days.' + stale-pr-message: 'This pull request has been marked as maybe-abandoned and has not seen any activity for 14 days. It will be closed if no further activity occurs within the next 30 days. If you would still like to work on this, please comment to let us know and the label will be removed.' + days-before-issue-stale: -1 + days-before-issue-close: -1 + operations-per-run: 1000 + days-before-pr-stale: 14 + days-before-pr-close: 30 + only-issue-labels: 'type: feature' + any-of-issue-labels: 'pending-approval' + any-of-pr-labels: 'maybe-abandoned' + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + id: invalid + with: + stale-issue-label: 'stale' + stale-issue-message: 'This issue has been marked as invalid ([see our contributing guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md)) and has not been active in 2 weeks. It will be closed if no further activity occurs within the next 2 weeks.' + stale-pr-message: 'This pull request has been marked as invalid ([see our contributing guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md)) and has not been active in 2 weeks. It will be closed if no further activity occurs within the next 2 weeks.' + days-before-issue-stale: 14 + days-before-issue-close: 14 + operations-per-run: 1000 + days-before-pr-stale: 14 + days-before-pr-close: 14 + any-of-issue-labels: 'invalid' + any-of-pr-labels: 'invalid' + - name: Print outputs (stale-bug) + run: echo ${{ join(steps.stale-bug.outputs.*, ',') }} + - name: Print outputs (stale-feature) + run: echo ${{ join(steps.stale-feature.outputs.*, ',') }} + - name: Print outputs (invalid) + run: echo ${{ join(steps.invalid.outputs.*, ',') }} diff --git a/.gitignore b/.gitignore index b47ee9c01b3e..0778fe949cec 100644 --- a/.gitignore +++ b/.gitignore @@ -3,15 +3,28 @@ .DS_STORE npm-debug.log* *~ -test.sqlite *.sublime* -yarn.lock +package-lock.json +pnpm-lock.yaml .nyc_output coverage-* coverage -test/tmp/* -test/binary/tmp/* .vscode/ -esdoc +.typedoc-build node_modules +*.log +/packages/*/lib +/packages/*/types +/packages/core/test/sqlite-databases +/.nx + +lib + +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000000..3ed33d81de5a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +yarn lint-staged --concurrent false +yarn delete-changelog diff --git a/.markdownlint.json b/.markdownlint.json index 677590cbaca5..c7db0ba13e16 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -35,4 +35,4 @@ "fenced-code-language": true, "no-empty-links": true, "no-alt-text": true -} \ No newline at end of file +} diff --git a/.mocharc.jsonc b/.mocharc.jsonc index 1a10e465d7ff..967bfbc2bf3b 100644 --- a/.mocharc.jsonc +++ b/.mocharc.jsonc @@ -1,15 +1,47 @@ { - // 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, + "require": "ts-node/register", + "loader": "ts-node/esm", + "extensions": ["js", "ts"], + "watch-files": ["src"], "check-leaks": true, "timeout": 30000, - "reporter": "spec" + "reporter": "spec", + "exit": true, + "inline-diffs": true, + "full-trace": true, + // Globals added as per https://github.com/sequelize/sequelize/pull/17206#discussion_r1538541573 + "globals": [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__esDecorate", + "__runInitializers", + "__propKey", + "__setFunctionName", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__spreadArray", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet", + "__classPrivateFieldIn", + "__addDisposableResource", + "__disposeResources", + "__rewriteRelativeImportExtension" + ] } diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e719a5a..000000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..b5485c3c03a1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +.yarn +/packages/cli/migrations +/packages/cli/seeds +*.tar diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000000..50cbe2463191 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,15 @@ +{ + "plugins": ["prettier-plugin-organize-imports"], + "bracketSameLine": true, + "printWidth": 100, + "singleQuote": true, + "arrowParens": "avoid", + "overrides": [ + { + "files": [".mocharc.jsonc"], + "options": { + "trailingComma": "none" + } + } + ] +} diff --git a/.yarn/releases/yarn-4.12.0.cjs b/.yarn/releases/yarn-4.12.0.cjs new file mode 100755 index 000000000000..f979d768e2b3 --- /dev/null +++ b/.yarn/releases/yarn-4.12.0.cjs @@ -0,0 +1,942 @@ +#!/usr/bin/env node +/* eslint-disable */ +//prettier-ignore +(()=>{var xGe=Object.create;var mU=Object.defineProperty;var kGe=Object.getOwnPropertyDescriptor;var QGe=Object.getOwnPropertyNames;var TGe=Object.getPrototypeOf,RGe=Object.prototype.hasOwnProperty;var Ie=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var Xe=(t,e)=>()=>(t&&(e=t(t=0)),e);var _=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Vt=(t,e)=>{for(var r in e)mU(t,r,{get:e[r],enumerable:!0})},FGe=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of QGe(e))!RGe.call(t,a)&&a!==r&&mU(t,a,{get:()=>e[a],enumerable:!(s=kGe(e,a))||s.enumerable});return t};var ut=(t,e,r)=>(r=t!=null?xGe(TGe(t)):{},FGe(e||!t||!t.__esModule?mU(r,"default",{value:t,enumerable:!0}):r,t));var fi={};Vt(fi,{SAFE_TIME:()=>WZ,S_IFDIR:()=>JP,S_IFLNK:()=>KP,S_IFMT:()=>Mf,S_IFREG:()=>N2});var Mf,JP,N2,KP,WZ,YZ=Xe(()=>{Mf=61440,JP=16384,N2=32768,KP=40960,WZ=456789e3});var or={};Vt(or,{EBADF:()=>Mo,EBUSY:()=>NGe,EEXIST:()=>HGe,EINVAL:()=>LGe,EISDIR:()=>_Ge,ENOENT:()=>MGe,ENOSYS:()=>OGe,ENOTDIR:()=>UGe,ENOTEMPTY:()=>GGe,EOPNOTSUPP:()=>qGe,EROFS:()=>jGe,ERR_DIR_CLOSED:()=>yU});function Cc(t,e){return Object.assign(new Error(`${t}: ${e}`),{code:t})}function NGe(t){return Cc("EBUSY",t)}function OGe(t,e){return Cc("ENOSYS",`${t}, ${e}`)}function LGe(t){return Cc("EINVAL",`invalid argument, ${t}`)}function Mo(t){return Cc("EBADF",`bad file descriptor, ${t}`)}function MGe(t){return Cc("ENOENT",`no such file or directory, ${t}`)}function UGe(t){return Cc("ENOTDIR",`not a directory, ${t}`)}function _Ge(t){return Cc("EISDIR",`illegal operation on a directory, ${t}`)}function HGe(t){return Cc("EEXIST",`file already exists, ${t}`)}function jGe(t){return Cc("EROFS",`read-only filesystem, ${t}`)}function GGe(t){return Cc("ENOTEMPTY",`directory not empty, ${t}`)}function qGe(t){return Cc("EOPNOTSUPP",`operation not supported, ${t}`)}function yU(){return Cc("ERR_DIR_CLOSED","Directory handle was closed")}var zP=Xe(()=>{});var $a={};Vt($a,{BigIntStatsEntry:()=>iE,DEFAULT_MODE:()=>CU,DirEntry:()=>EU,StatEntry:()=>nE,areStatsEqual:()=>wU,clearStats:()=>XP,convertToBigIntStats:()=>YGe,makeDefaultStats:()=>VZ,makeEmptyStats:()=>WGe});function VZ(){return new nE}function WGe(){return XP(VZ())}function XP(t){for(let e in t)if(Object.hasOwn(t,e)){let r=t[e];typeof r=="number"?t[e]=0:typeof r=="bigint"?t[e]=BigInt(0):IU.types.isDate(r)&&(t[e]=new Date(0))}return t}function YGe(t){let e=new iE;for(let r in t)if(Object.hasOwn(t,r)){let s=t[r];typeof s=="number"?e[r]=BigInt(s):IU.types.isDate(s)&&(e[r]=new Date(s))}return e.atimeNs=e.atimeMs*BigInt(1e6),e.mtimeNs=e.mtimeMs*BigInt(1e6),e.ctimeNs=e.ctimeMs*BigInt(1e6),e.birthtimeNs=e.birthtimeMs*BigInt(1e6),e}function wU(t,e){if(t.atimeMs!==e.atimeMs||t.birthtimeMs!==e.birthtimeMs||t.blksize!==e.blksize||t.blocks!==e.blocks||t.ctimeMs!==e.ctimeMs||t.dev!==e.dev||t.gid!==e.gid||t.ino!==e.ino||t.isBlockDevice()!==e.isBlockDevice()||t.isCharacterDevice()!==e.isCharacterDevice()||t.isDirectory()!==e.isDirectory()||t.isFIFO()!==e.isFIFO()||t.isFile()!==e.isFile()||t.isSocket()!==e.isSocket()||t.isSymbolicLink()!==e.isSymbolicLink()||t.mode!==e.mode||t.mtimeMs!==e.mtimeMs||t.nlink!==e.nlink||t.rdev!==e.rdev||t.size!==e.size||t.uid!==e.uid)return!1;let r=t,s=e;return!(r.atimeNs!==s.atimeNs||r.mtimeNs!==s.mtimeNs||r.ctimeNs!==s.ctimeNs||r.birthtimeNs!==s.birthtimeNs)}var IU,CU,EU,nE,iE,BU=Xe(()=>{IU=ut(Ie("util")),CU=33188,EU=class{constructor(){this.name="";this.path="";this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},nE=class{constructor(){this.uid=0;this.gid=0;this.size=0;this.blksize=0;this.atimeMs=0;this.mtimeMs=0;this.ctimeMs=0;this.birthtimeMs=0;this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=0;this.ino=0;this.mode=CU;this.nlink=1;this.rdev=0;this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},iE=class{constructor(){this.uid=BigInt(0);this.gid=BigInt(0);this.size=BigInt(0);this.blksize=BigInt(0);this.atimeMs=BigInt(0);this.mtimeMs=BigInt(0);this.ctimeMs=BigInt(0);this.birthtimeMs=BigInt(0);this.atimeNs=BigInt(0);this.mtimeNs=BigInt(0);this.ctimeNs=BigInt(0);this.birthtimeNs=BigInt(0);this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=BigInt(0);this.ino=BigInt(0);this.mode=BigInt(CU);this.nlink=BigInt(1);this.rdev=BigInt(0);this.blocks=BigInt(1)}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&BigInt(61440))===BigInt(16384)}isFIFO(){return!1}isFile(){return(this.mode&BigInt(61440))===BigInt(32768)}isSocket(){return!1}isSymbolicLink(){return(this.mode&BigInt(61440))===BigInt(40960)}}});function XGe(t){let e,r;if(e=t.match(KGe))t=e[1];else if(r=t.match(zGe))t=`\\\\${r[1]?".\\":""}${r[2]}`;else return t;return t.replace(/\//g,"\\")}function ZGe(t){t=t.replace(/\\/g,"/");let e,r;return(e=t.match(VGe))?t=`/${e[1]}`:(r=t.match(JGe))&&(t=`/unc/${r[1]?".dot/":""}${r[2]}`),t}function ZP(t,e){return t===fe?KZ(e):vU(e)}var O2,vt,Er,fe,J,JZ,VGe,JGe,KGe,zGe,vU,KZ,el=Xe(()=>{O2=ut(Ie("path")),vt={root:"/",dot:".",parent:".."},Er={home:"~",nodeModules:"node_modules",manifest:"package.json",lockfile:"yarn.lock",virtual:"__virtual__",pnpJs:".pnp.js",pnpCjs:".pnp.cjs",pnpData:".pnp.data.json",pnpEsmLoader:".pnp.loader.mjs",rc:".yarnrc.yml",env:".env"},fe=Object.create(O2.default),J=Object.create(O2.default.posix);fe.cwd=()=>process.cwd();J.cwd=process.platform==="win32"?()=>vU(process.cwd()):process.cwd;process.platform==="win32"&&(J.resolve=(...t)=>t.length>0&&J.isAbsolute(t[0])?O2.default.posix.resolve(...t):O2.default.posix.resolve(J.cwd(),...t));JZ=function(t,e,r){return e=t.normalize(e),r=t.normalize(r),e===r?".":(e.endsWith(t.sep)||(e=e+t.sep),r.startsWith(e)?r.slice(e.length):null)};fe.contains=(t,e)=>JZ(fe,t,e);J.contains=(t,e)=>JZ(J,t,e);VGe=/^([a-zA-Z]:.*)$/,JGe=/^\/\/(\.\/)?(.*)$/,KGe=/^\/([a-zA-Z]:.*)$/,zGe=/^\/unc\/(\.dot\/)?(.*)$/;vU=process.platform==="win32"?ZGe:t=>t,KZ=process.platform==="win32"?XGe:t=>t;fe.fromPortablePath=KZ;fe.toPortablePath=vU});async function $P(t,e){let r="0123456789abcdef";await t.mkdirPromise(e.indexPath,{recursive:!0});let s=[];for(let a of r)for(let n of r)s.push(t.mkdirPromise(t.pathUtils.join(e.indexPath,`${a}${n}`),{recursive:!0}));return await Promise.all(s),e.indexPath}async function zZ(t,e,r,s,a){let n=t.pathUtils.normalize(e),c=r.pathUtils.normalize(s),f=[],p=[],{atime:h,mtime:E}=a.stableTime?{atime:dd,mtime:dd}:await r.lstatPromise(c);await t.mkdirpPromise(t.pathUtils.dirname(e),{utimes:[h,E]}),await SU(f,p,t,n,r,c,{...a,didParentExist:!0});for(let C of f)await C();await Promise.all(p.map(C=>C()))}async function SU(t,e,r,s,a,n,c){let f=c.didParentExist?await XZ(r,s):null,p=await a.lstatPromise(n),{atime:h,mtime:E}=c.stableTime?{atime:dd,mtime:dd}:p,C;switch(!0){case p.isDirectory():C=await e5e(t,e,r,s,f,a,n,p,c);break;case p.isFile():C=await n5e(t,e,r,s,f,a,n,p,c);break;case p.isSymbolicLink():C=await i5e(t,e,r,s,f,a,n,p,c);break;default:throw new Error(`Unsupported file type (${p.mode})`)}return(c.linkStrategy?.type!=="HardlinkFromIndex"||!p.isFile())&&((C||f?.mtime?.getTime()!==E.getTime()||f?.atime?.getTime()!==h.getTime())&&(e.push(()=>r.lutimesPromise(s,h,E)),C=!0),(f===null||(f.mode&511)!==(p.mode&511))&&(e.push(()=>r.chmodPromise(s,p.mode&511)),C=!0)),C}async function XZ(t,e){try{return await t.lstatPromise(e)}catch{return null}}async function e5e(t,e,r,s,a,n,c,f,p){if(a!==null&&!a.isDirectory())if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;let h=!1;a===null&&(t.push(async()=>{try{await r.mkdirPromise(s,{mode:f.mode})}catch(S){if(S.code!=="EEXIST")throw S}}),h=!0);let E=await n.readdirPromise(c),C=p.didParentExist&&!a?{...p,didParentExist:!1}:p;if(p.stableSort)for(let S of E.sort())await SU(t,e,r,r.pathUtils.join(s,S),n,n.pathUtils.join(c,S),C)&&(h=!0);else(await Promise.all(E.map(async P=>{await SU(t,e,r,r.pathUtils.join(s,P),n,n.pathUtils.join(c,P),C)}))).some(P=>P)&&(h=!0);return h}async function t5e(t,e,r,s,a,n,c,f,p,h){let E=await n.checksumFilePromise(c,{algorithm:"sha1"}),C=420,S=f.mode&511,P=`${E}${S!==C?S.toString(8):""}`,I=r.pathUtils.join(h.indexPath,E.slice(0,2),`${P}.dat`),R;(le=>(le[le.Lock=0]="Lock",le[le.Rename=1]="Rename"))(R||={});let N=1,U=await XZ(r,I);if(a){let ie=U&&a.dev===U.dev&&a.ino===U.ino,ue=U?.mtimeMs!==$Ge;if(ie&&ue&&h.autoRepair&&(N=0,U=null),!ie)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1}let W=!U&&N===1?`${I}.${Math.floor(Math.random()*4294967296).toString(16).padStart(8,"0")}`:null,ee=!1;return t.push(async()=>{if(!U&&(N===0&&await r.lockPromise(I,async()=>{let ie=await n.readFilePromise(c);await r.writeFilePromise(I,ie)}),N===1&&W)){let ie=await n.readFilePromise(c);await r.writeFilePromise(W,ie);try{await r.linkPromise(W,I)}catch(ue){if(ue.code==="EEXIST")ee=!0,await r.unlinkPromise(W);else throw ue}}a||await r.linkPromise(I,s)}),e.push(async()=>{U||(await r.lutimesPromise(I,dd,dd),S!==C&&await r.chmodPromise(I,S)),W&&!ee&&await r.unlinkPromise(W)}),!1}async function r5e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{let h=await n.readFilePromise(c);await r.writeFilePromise(s,h)}),!0}async function n5e(t,e,r,s,a,n,c,f,p){return p.linkStrategy?.type==="HardlinkFromIndex"?t5e(t,e,r,s,a,n,c,f,p,p.linkStrategy):r5e(t,e,r,s,a,n,c,f,p)}async function i5e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{await r.symlinkPromise(ZP(r.pathUtils,await n.readlinkPromise(c)),s)}),!0}var dd,$Ge,DU=Xe(()=>{el();dd=new Date(456789e3*1e3),$Ge=dd.getTime()});function ex(t,e,r,s){let a=()=>{let n=r.shift();if(typeof n>"u")return null;let c=t.pathUtils.join(e,n);return Object.assign(t.statSync(c),{name:n,path:void 0})};return new L2(e,a,s)}var L2,ZZ=Xe(()=>{zP();L2=class{constructor(e,r,s={}){this.path=e;this.nextDirent=r;this.opts=s;this.closed=!1}throwIfClosed(){if(this.closed)throw yU()}async*[Symbol.asyncIterator](){try{let e;for(;(e=await this.read())!==null;)yield e}finally{await this.close()}}read(e){let r=this.readSync();return typeof e<"u"?e(null,r):Promise.resolve(r)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),typeof e<"u"?e(null):Promise.resolve()}closeSync(){this.throwIfClosed(),this.opts.onClose?.(),this.closed=!0}}});function $Z(t,e){if(t!==e)throw new Error(`Invalid StatWatcher status: expected '${e}', got '${t}'`)}var e$,tx,t$=Xe(()=>{e$=Ie("events");BU();tx=class t extends e$.EventEmitter{constructor(r,s,{bigint:a=!1}={}){super();this.status="ready";this.changeListeners=new Map;this.startTimeout=null;this.fakeFs=r,this.path=s,this.bigint=a,this.lastStats=this.stat()}static create(r,s,a){let n=new t(r,s,a);return n.start(),n}start(){$Z(this.status,"ready"),this.status="running",this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit("change",this.lastStats,this.lastStats)},3)}stop(){$Z(this.status,"running"),this.status="stopped",this.startTimeout!==null&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit("stop")}stat(){try{return this.fakeFs.statSync(this.path,{bigint:this.bigint})}catch{let r=this.bigint?new iE:new nE;return XP(r)}}makeInterval(r){let s=setInterval(()=>{let a=this.stat(),n=this.lastStats;wU(a,n)||(this.lastStats=a,this.emit("change",a,n))},r.interval);return r.persistent?s:s.unref()}registerChangeListener(r,s){this.addListener("change",r),this.changeListeners.set(r,this.makeInterval(s))}unregisterChangeListener(r){this.removeListener("change",r);let s=this.changeListeners.get(r);typeof s<"u"&&clearInterval(s),this.changeListeners.delete(r)}unregisterAllChangeListeners(){for(let r of this.changeListeners.keys())this.unregisterChangeListener(r)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(let r of this.changeListeners.values())r.ref();return this}unref(){for(let r of this.changeListeners.values())r.unref();return this}}});function sE(t,e,r,s){let a,n,c,f;switch(typeof r){case"function":a=!1,n=!0,c=5007,f=r;break;default:({bigint:a=!1,persistent:n=!0,interval:c=5007}=r),f=s;break}let p=rx.get(t);typeof p>"u"&&rx.set(t,p=new Map);let h=p.get(e);return typeof h>"u"&&(h=tx.create(t,e,{bigint:a}),p.set(e,h)),h.registerChangeListener(f,{persistent:n,interval:c}),h}function md(t,e,r){let s=rx.get(t);if(typeof s>"u")return;let a=s.get(e);typeof a>"u"||(typeof r>"u"?a.unregisterAllChangeListeners():a.unregisterChangeListener(r),a.hasChangeListeners()||(a.stop(),s.delete(e)))}function yd(t){let e=rx.get(t);if(!(typeof e>"u"))for(let r of e.keys())md(t,r)}var rx,bU=Xe(()=>{t$();rx=new WeakMap});function s5e(t){let e=t.match(/\r?\n/g);if(e===null)return n$.EOL;let r=e.filter(a=>a===`\r +`).length,s=e.length-r;return r>s?`\r +`:` +`}function Ed(t,e){return e.replace(/\r?\n/g,s5e(t))}var r$,n$,mp,Uf,Id=Xe(()=>{r$=Ie("crypto"),n$=Ie("os");DU();el();mp=class{constructor(e){this.pathUtils=e}async*genTraversePromise(e,{stableSort:r=!1}={}){let s=[e];for(;s.length>0;){let a=s.shift();if((await this.lstatPromise(a)).isDirectory()){let c=await this.readdirPromise(a);if(r)for(let f of c.sort())s.push(this.pathUtils.join(a,f));else throw new Error("Not supported")}else yield a}}async checksumFilePromise(e,{algorithm:r="sha512"}={}){let s=await this.openPromise(e,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,r$.createHash)(r),f=0;for(;(f=await this.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await this.closePromise(s)}}async removePromise(e,{recursive:r=!0,maxRetries:s=5}={}){let a;try{a=await this.lstatPromise(e)}catch(n){if(n.code==="ENOENT")return;throw n}if(a.isDirectory()){if(r){let n=await this.readdirPromise(e);await Promise.all(n.map(c=>this.removePromise(this.pathUtils.resolve(e,c))))}for(let n=0;n<=s;n++)try{await this.rmdirPromise(e);break}catch(c){if(c.code!=="EBUSY"&&c.code!=="ENOTEMPTY")throw c;nsetTimeout(f,n*100))}}else await this.unlinkPromise(e)}removeSync(e,{recursive:r=!0}={}){let s;try{s=this.lstatSync(e)}catch(a){if(a.code==="ENOENT")return;throw a}if(s.isDirectory()){if(r)for(let a of this.readdirSync(e))this.removeSync(this.pathUtils.resolve(e,a));this.rmdirSync(e)}else this.unlinkSync(e)}async mkdirpPromise(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{await this.mkdirPromise(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&await this.chmodPromise(f,r),s!=null)await this.utimesPromise(f,s[0],s[1]);else{let p=await this.statPromise(this.pathUtils.dirname(f));await this.utimesPromise(f,p.atime,p.mtime)}}}return n}mkdirpSync(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{this.mkdirSync(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&this.chmodSync(f,r),s!=null)this.utimesSync(f,s[0],s[1]);else{let p=this.statSync(this.pathUtils.dirname(f));this.utimesSync(f,p.atime,p.mtime)}}}return n}async copyPromise(e,r,{baseFs:s=this,overwrite:a=!0,stableSort:n=!1,stableTime:c=!1,linkStrategy:f=null}={}){return await zZ(this,e,s,r,{overwrite:a,stableSort:n,stableTime:c,linkStrategy:f})}copySync(e,r,{baseFs:s=this,overwrite:a=!0}={}){let n=s.lstatSync(r),c=this.existsSync(e);if(n.isDirectory()){this.mkdirpSync(e);let p=s.readdirSync(r);for(let h of p)this.copySync(this.pathUtils.join(e,h),s.pathUtils.join(r,h),{baseFs:s,overwrite:a})}else if(n.isFile()){if(!c||a){c&&this.removeSync(e);let p=s.readFileSync(r);this.writeFileSync(e,p)}}else if(n.isSymbolicLink()){if(!c||a){c&&this.removeSync(e);let p=s.readlinkSync(r);this.symlinkSync(ZP(this.pathUtils,p),e)}}else throw new Error(`Unsupported file type (file: ${r}, mode: 0o${n.mode.toString(8).padStart(6,"0")})`);let f=n.mode&511;this.chmodSync(e,f)}async changeFilePromise(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferPromise(e,r,s):this.changeFileTextPromise(e,r,s)}async changeFileBufferPromise(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=await this.readFilePromise(e)}catch{}Buffer.compare(a,r)!==0&&await this.writeFilePromise(e,r,{mode:s})}async changeFileTextPromise(e,r,{automaticNewlines:s,mode:a}={}){let n="";try{n=await this.readFilePromise(e,"utf8")}catch{}let c=s?Ed(n,r):r;n!==c&&await this.writeFilePromise(e,c,{mode:a})}changeFileSync(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferSync(e,r,s):this.changeFileTextSync(e,r,s)}changeFileBufferSync(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=this.readFileSync(e)}catch{}Buffer.compare(a,r)!==0&&this.writeFileSync(e,r,{mode:s})}changeFileTextSync(e,r,{automaticNewlines:s=!1,mode:a}={}){let n="";try{n=this.readFileSync(e,"utf8")}catch{}let c=s?Ed(n,r):r;n!==c&&this.writeFileSync(e,c,{mode:a})}async movePromise(e,r){try{await this.renamePromise(e,r)}catch(s){if(s.code==="EXDEV")await this.copyPromise(r,e),await this.removePromise(e);else throw s}}moveSync(e,r){try{this.renameSync(e,r)}catch(s){if(s.code==="EXDEV")this.copySync(r,e),this.removeSync(e);else throw s}}async lockPromise(e,r){let s=`${e}.flock`,a=1e3/60,n=Date.now(),c=null,f=async()=>{let p;try{[p]=await this.readJsonPromise(s)}catch{return Date.now()-n<500}try{return process.kill(p,0),!0}catch{return!1}};for(;c===null;)try{c=await this.openPromise(s,"wx")}catch(p){if(p.code==="EEXIST"){if(!await f())try{await this.unlinkPromise(s);continue}catch{}if(Date.now()-n<60*1e3)await new Promise(h=>setTimeout(h,a));else throw new Error(`Couldn't acquire a lock in a reasonable time (via ${s})`)}else throw p}await this.writePromise(c,JSON.stringify([process.pid]));try{return await r()}finally{try{await this.closePromise(c),await this.unlinkPromise(s)}catch{}}}async readJsonPromise(e){let r=await this.readFilePromise(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}readJsonSync(e){let r=this.readFileSync(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}async writeJsonPromise(e,r,{compact:s=!1}={}){let a=s?0:2;return await this.writeFilePromise(e,`${JSON.stringify(r,null,a)} +`)}writeJsonSync(e,r,{compact:s=!1}={}){let a=s?0:2;return this.writeFileSync(e,`${JSON.stringify(r,null,a)} +`)}async preserveTimePromise(e,r){let s=await this.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await this.lutimesPromise(e,s.atime,s.mtime)}async preserveTimeSync(e,r){let s=this.lstatSync(e),a=r();typeof a<"u"&&(e=a),this.lutimesSync(e,s.atime,s.mtime)}},Uf=class extends mp{constructor(){super(J)}}});var _s,yp=Xe(()=>{Id();_s=class extends mp{getExtractHint(e){return this.baseFs.getExtractHint(e)}resolve(e){return this.mapFromBase(this.baseFs.resolve(this.mapToBase(e)))}getRealPath(){return this.mapFromBase(this.baseFs.getRealPath())}async openPromise(e,r,s){return this.baseFs.openPromise(this.mapToBase(e),r,s)}openSync(e,r,s){return this.baseFs.openSync(this.mapToBase(e),r,s)}async opendirPromise(e,r){return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(e),r),{path:e})}opendirSync(e,r){return Object.assign(this.baseFs.opendirSync(this.mapToBase(e),r),{path:e})}async readPromise(e,r,s,a,n){return await this.baseFs.readPromise(e,r,s,a,n)}readSync(e,r,s,a,n){return this.baseFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return typeof r=="string"?await this.baseFs.writePromise(e,r,s):await this.baseFs.writePromise(e,r,s,a,n)}writeSync(e,r,s,a,n){return typeof r=="string"?this.baseFs.writeSync(e,r,s):this.baseFs.writeSync(e,r,s,a,n)}async closePromise(e){return this.baseFs.closePromise(e)}closeSync(e){this.baseFs.closeSync(e)}createReadStream(e,r){return this.baseFs.createReadStream(e!==null?this.mapToBase(e):e,r)}createWriteStream(e,r){return this.baseFs.createWriteStream(e!==null?this.mapToBase(e):e,r)}async realpathPromise(e){return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(e)))}realpathSync(e){return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(e)))}async existsPromise(e){return this.baseFs.existsPromise(this.mapToBase(e))}existsSync(e){return this.baseFs.existsSync(this.mapToBase(e))}accessSync(e,r){return this.baseFs.accessSync(this.mapToBase(e),r)}async accessPromise(e,r){return this.baseFs.accessPromise(this.mapToBase(e),r)}async statPromise(e,r){return this.baseFs.statPromise(this.mapToBase(e),r)}statSync(e,r){return this.baseFs.statSync(this.mapToBase(e),r)}async fstatPromise(e,r){return this.baseFs.fstatPromise(e,r)}fstatSync(e,r){return this.baseFs.fstatSync(e,r)}lstatPromise(e,r){return this.baseFs.lstatPromise(this.mapToBase(e),r)}lstatSync(e,r){return this.baseFs.lstatSync(this.mapToBase(e),r)}async fchmodPromise(e,r){return this.baseFs.fchmodPromise(e,r)}fchmodSync(e,r){return this.baseFs.fchmodSync(e,r)}async chmodPromise(e,r){return this.baseFs.chmodPromise(this.mapToBase(e),r)}chmodSync(e,r){return this.baseFs.chmodSync(this.mapToBase(e),r)}async fchownPromise(e,r,s){return this.baseFs.fchownPromise(e,r,s)}fchownSync(e,r,s){return this.baseFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return this.baseFs.chownPromise(this.mapToBase(e),r,s)}chownSync(e,r,s){return this.baseFs.chownSync(this.mapToBase(e),r,s)}async renamePromise(e,r){return this.baseFs.renamePromise(this.mapToBase(e),this.mapToBase(r))}renameSync(e,r){return this.baseFs.renameSync(this.mapToBase(e),this.mapToBase(r))}async copyFilePromise(e,r,s=0){return this.baseFs.copyFilePromise(this.mapToBase(e),this.mapToBase(r),s)}copyFileSync(e,r,s=0){return this.baseFs.copyFileSync(this.mapToBase(e),this.mapToBase(r),s)}async appendFilePromise(e,r,s){return this.baseFs.appendFilePromise(this.fsMapToBase(e),r,s)}appendFileSync(e,r,s){return this.baseFs.appendFileSync(this.fsMapToBase(e),r,s)}async writeFilePromise(e,r,s){return this.baseFs.writeFilePromise(this.fsMapToBase(e),r,s)}writeFileSync(e,r,s){return this.baseFs.writeFileSync(this.fsMapToBase(e),r,s)}async unlinkPromise(e){return this.baseFs.unlinkPromise(this.mapToBase(e))}unlinkSync(e){return this.baseFs.unlinkSync(this.mapToBase(e))}async utimesPromise(e,r,s){return this.baseFs.utimesPromise(this.mapToBase(e),r,s)}utimesSync(e,r,s){return this.baseFs.utimesSync(this.mapToBase(e),r,s)}async lutimesPromise(e,r,s){return this.baseFs.lutimesPromise(this.mapToBase(e),r,s)}lutimesSync(e,r,s){return this.baseFs.lutimesSync(this.mapToBase(e),r,s)}async mkdirPromise(e,r){return this.baseFs.mkdirPromise(this.mapToBase(e),r)}mkdirSync(e,r){return this.baseFs.mkdirSync(this.mapToBase(e),r)}async rmdirPromise(e,r){return this.baseFs.rmdirPromise(this.mapToBase(e),r)}rmdirSync(e,r){return this.baseFs.rmdirSync(this.mapToBase(e),r)}async rmPromise(e,r){return this.baseFs.rmPromise(this.mapToBase(e),r)}rmSync(e,r){return this.baseFs.rmSync(this.mapToBase(e),r)}async linkPromise(e,r){return this.baseFs.linkPromise(this.mapToBase(e),this.mapToBase(r))}linkSync(e,r){return this.baseFs.linkSync(this.mapToBase(e),this.mapToBase(r))}async symlinkPromise(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkPromise(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkPromise(c,a,s)}symlinkSync(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkSync(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkSync(c,a,s)}async readFilePromise(e,r){return this.baseFs.readFilePromise(this.fsMapToBase(e),r)}readFileSync(e,r){return this.baseFs.readFileSync(this.fsMapToBase(e),r)}readdirPromise(e,r){return this.baseFs.readdirPromise(this.mapToBase(e),r)}readdirSync(e,r){return this.baseFs.readdirSync(this.mapToBase(e),r)}async readlinkPromise(e){return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(e)))}readlinkSync(e){return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(e)))}async truncatePromise(e,r){return this.baseFs.truncatePromise(this.mapToBase(e),r)}truncateSync(e,r){return this.baseFs.truncateSync(this.mapToBase(e),r)}async ftruncatePromise(e,r){return this.baseFs.ftruncatePromise(e,r)}ftruncateSync(e,r){return this.baseFs.ftruncateSync(e,r)}watch(e,r,s){return this.baseFs.watch(this.mapToBase(e),r,s)}watchFile(e,r,s){return this.baseFs.watchFile(this.mapToBase(e),r,s)}unwatchFile(e,r){return this.baseFs.unwatchFile(this.mapToBase(e),r)}fsMapToBase(e){return typeof e=="number"?e:this.mapToBase(e)}}});var _f,i$=Xe(()=>{yp();_f=class extends _s{constructor(e,{baseFs:r,pathUtils:s}){super(s),this.target=e,this.baseFs=r}getRealPath(){return this.target}getBaseFs(){return this.baseFs}mapFromBase(e){return e}mapToBase(e){return e}}});function s$(t){let e=t;return typeof t.path=="string"&&(e.path=fe.toPortablePath(t.path)),e}var o$,Yn,Cd=Xe(()=>{o$=ut(Ie("fs"));Id();el();Yn=class extends Uf{constructor(e=o$.default){super(),this.realFs=e}getExtractHint(){return!1}getRealPath(){return vt.root}resolve(e){return J.resolve(e)}async openPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.open(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}openSync(e,r,s){return this.realFs.openSync(fe.fromPortablePath(e),r,s)}async opendirPromise(e,r){return await new Promise((s,a)=>{typeof r<"u"?this.realFs.opendir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.opendir(fe.fromPortablePath(e),this.makeCallback(s,a))}).then(s=>{let a=s;return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a})}opendirSync(e,r){let a=typeof r<"u"?this.realFs.opendirSync(fe.fromPortablePath(e),r):this.realFs.opendirSync(fe.fromPortablePath(e));return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a}async readPromise(e,r,s=0,a=0,n=-1){return await new Promise((c,f)=>{this.realFs.read(e,r,s,a,n,(p,h)=>{p?f(p):c(h)})})}readSync(e,r,s,a,n){return this.realFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return await new Promise((c,f)=>typeof r=="string"?this.realFs.write(e,r,s,this.makeCallback(c,f)):this.realFs.write(e,r,s,a,n,this.makeCallback(c,f)))}writeSync(e,r,s,a,n){return typeof r=="string"?this.realFs.writeSync(e,r,s):this.realFs.writeSync(e,r,s,a,n)}async closePromise(e){await new Promise((r,s)=>{this.realFs.close(e,this.makeCallback(r,s))})}closeSync(e){this.realFs.closeSync(e)}createReadStream(e,r){let s=e!==null?fe.fromPortablePath(e):e;return this.realFs.createReadStream(s,r)}createWriteStream(e,r){let s=e!==null?fe.fromPortablePath(e):e;return this.realFs.createWriteStream(s,r)}async realpathPromise(e){return await new Promise((r,s)=>{this.realFs.realpath(fe.fromPortablePath(e),{},this.makeCallback(r,s))}).then(r=>fe.toPortablePath(r))}realpathSync(e){return fe.toPortablePath(this.realFs.realpathSync(fe.fromPortablePath(e),{}))}async existsPromise(e){return await new Promise(r=>{this.realFs.exists(fe.fromPortablePath(e),r)})}accessSync(e,r){return this.realFs.accessSync(fe.fromPortablePath(e),r)}async accessPromise(e,r){return await new Promise((s,a)=>{this.realFs.access(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}existsSync(e){return this.realFs.existsSync(fe.fromPortablePath(e))}async statPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.stat(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.stat(fe.fromPortablePath(e),this.makeCallback(s,a))})}statSync(e,r){return r?this.realFs.statSync(fe.fromPortablePath(e),r):this.realFs.statSync(fe.fromPortablePath(e))}async fstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.fstat(e,r,this.makeCallback(s,a)):this.realFs.fstat(e,this.makeCallback(s,a))})}fstatSync(e,r){return r?this.realFs.fstatSync(e,r):this.realFs.fstatSync(e)}async lstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.lstat(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.lstat(fe.fromPortablePath(e),this.makeCallback(s,a))})}lstatSync(e,r){return r?this.realFs.lstatSync(fe.fromPortablePath(e),r):this.realFs.lstatSync(fe.fromPortablePath(e))}async fchmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.fchmod(e,r,this.makeCallback(s,a))})}fchmodSync(e,r){return this.realFs.fchmodSync(e,r)}async chmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.chmod(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}chmodSync(e,r){return this.realFs.chmodSync(fe.fromPortablePath(e),r)}async fchownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.fchown(e,r,s,this.makeCallback(a,n))})}fchownSync(e,r,s){return this.realFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.chown(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}chownSync(e,r,s){return this.realFs.chownSync(fe.fromPortablePath(e),r,s)}async renamePromise(e,r){return await new Promise((s,a)=>{this.realFs.rename(fe.fromPortablePath(e),fe.fromPortablePath(r),this.makeCallback(s,a))})}renameSync(e,r){return this.realFs.renameSync(fe.fromPortablePath(e),fe.fromPortablePath(r))}async copyFilePromise(e,r,s=0){return await new Promise((a,n)=>{this.realFs.copyFile(fe.fromPortablePath(e),fe.fromPortablePath(r),s,this.makeCallback(a,n))})}copyFileSync(e,r,s=0){return this.realFs.copyFileSync(fe.fromPortablePath(e),fe.fromPortablePath(r),s)}async appendFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.appendFile(c,r,s,this.makeCallback(a,n)):this.realFs.appendFile(c,r,this.makeCallback(a,n))})}appendFileSync(e,r,s){let a=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.appendFileSync(a,r,s):this.realFs.appendFileSync(a,r)}async writeFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.writeFile(c,r,s,this.makeCallback(a,n)):this.realFs.writeFile(c,r,this.makeCallback(a,n))})}writeFileSync(e,r,s){let a=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.writeFileSync(a,r,s):this.realFs.writeFileSync(a,r)}async unlinkPromise(e){return await new Promise((r,s)=>{this.realFs.unlink(fe.fromPortablePath(e),this.makeCallback(r,s))})}unlinkSync(e){return this.realFs.unlinkSync(fe.fromPortablePath(e))}async utimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.utimes(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}utimesSync(e,r,s){this.realFs.utimesSync(fe.fromPortablePath(e),r,s)}async lutimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.lutimes(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}lutimesSync(e,r,s){this.realFs.lutimesSync(fe.fromPortablePath(e),r,s)}async mkdirPromise(e,r){return await new Promise((s,a)=>{this.realFs.mkdir(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}mkdirSync(e,r){return this.realFs.mkdirSync(fe.fromPortablePath(e),r)}async rmdirPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rmdir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rmdir(fe.fromPortablePath(e),this.makeCallback(s,a))})}rmdirSync(e,r){return this.realFs.rmdirSync(fe.fromPortablePath(e),r)}async rmPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rm(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rm(fe.fromPortablePath(e),this.makeCallback(s,a))})}rmSync(e,r){return this.realFs.rmSync(fe.fromPortablePath(e),r)}async linkPromise(e,r){return await new Promise((s,a)=>{this.realFs.link(fe.fromPortablePath(e),fe.fromPortablePath(r),this.makeCallback(s,a))})}linkSync(e,r){return this.realFs.linkSync(fe.fromPortablePath(e),fe.fromPortablePath(r))}async symlinkPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.symlink(fe.fromPortablePath(e.replace(/\/+$/,"")),fe.fromPortablePath(r),s,this.makeCallback(a,n))})}symlinkSync(e,r,s){return this.realFs.symlinkSync(fe.fromPortablePath(e.replace(/\/+$/,"")),fe.fromPortablePath(r),s)}async readFilePromise(e,r){return await new Promise((s,a)=>{let n=typeof e=="string"?fe.fromPortablePath(e):e;this.realFs.readFile(n,r,this.makeCallback(s,a))})}readFileSync(e,r){let s=typeof e=="string"?fe.fromPortablePath(e):e;return this.realFs.readFileSync(s,r)}async readdirPromise(e,r){return await new Promise((s,a)=>{r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(s$)),a)):this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(fe.toPortablePath)),a)):this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.readdir(fe.fromPortablePath(e),this.makeCallback(s,a))})}readdirSync(e,r){return r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdirSync(fe.fromPortablePath(e),r).map(s$):this.realFs.readdirSync(fe.fromPortablePath(e),r).map(fe.toPortablePath):this.realFs.readdirSync(fe.fromPortablePath(e),r):this.realFs.readdirSync(fe.fromPortablePath(e))}async readlinkPromise(e){return await new Promise((r,s)=>{this.realFs.readlink(fe.fromPortablePath(e),this.makeCallback(r,s))}).then(r=>fe.toPortablePath(r))}readlinkSync(e){return fe.toPortablePath(this.realFs.readlinkSync(fe.fromPortablePath(e)))}async truncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.truncate(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}truncateSync(e,r){return this.realFs.truncateSync(fe.fromPortablePath(e),r)}async ftruncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.ftruncate(e,r,this.makeCallback(s,a))})}ftruncateSync(e,r){return this.realFs.ftruncateSync(e,r)}watch(e,r,s){return this.realFs.watch(fe.fromPortablePath(e),r,s)}watchFile(e,r,s){return this.realFs.watchFile(fe.fromPortablePath(e),r,s)}unwatchFile(e,r){return this.realFs.unwatchFile(fe.fromPortablePath(e),r)}makeCallback(e,r){return(s,a)=>{s?r(s):e(a)}}}});var Sn,a$=Xe(()=>{Cd();yp();el();Sn=class extends _s{constructor(e,{baseFs:r=new Yn}={}){super(J),this.target=this.pathUtils.normalize(e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.target)}resolve(e){return this.pathUtils.isAbsolute(e)?J.normalize(e):this.baseFs.resolve(J.join(this.target,e))}mapFromBase(e){return e}mapToBase(e){return this.pathUtils.isAbsolute(e)?e:this.pathUtils.join(this.target,e)}}});var l$,Hf,c$=Xe(()=>{Cd();yp();el();l$=vt.root,Hf=class extends _s{constructor(e,{baseFs:r=new Yn}={}){super(J),this.target=this.pathUtils.resolve(vt.root,e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.pathUtils.relative(vt.root,this.target))}getTarget(){return this.target}getBaseFs(){return this.baseFs}mapToBase(e){let r=this.pathUtils.normalize(e);if(this.pathUtils.isAbsolute(e))return this.pathUtils.resolve(this.target,this.pathUtils.relative(l$,e));if(r.match(/^\.\.\/?/))throw new Error(`Resolving this path (${e}) would escape the jail`);return this.pathUtils.resolve(this.target,e)}mapFromBase(e){return this.pathUtils.resolve(l$,this.pathUtils.relative(this.target,e))}}});var oE,u$=Xe(()=>{yp();oE=class extends _s{constructor(r,s){super(s);this.instance=null;this.factory=r}get baseFs(){return this.instance||(this.instance=this.factory()),this.instance}set baseFs(r){this.instance=r}mapFromBase(r){return r}mapToBase(r){return r}}});var wd,tl,e0,f$=Xe(()=>{wd=Ie("fs");Id();Cd();bU();zP();el();tl=4278190080,e0=class extends Uf{constructor({baseFs:r=new Yn,filter:s=null,magicByte:a=42,maxOpenFiles:n=1/0,useCache:c=!0,maxAge:f=5e3,typeCheck:p=wd.constants.S_IFREG,getMountPoint:h,factoryPromise:E,factorySync:C}){if(Math.floor(a)!==a||!(a>1&&a<=127))throw new Error("The magic byte must be set to a round value between 1 and 127 included");super();this.fdMap=new Map;this.nextFd=3;this.isMount=new Set;this.notMount=new Set;this.realPaths=new Map;this.limitOpenFilesTimeout=null;this.baseFs=r,this.mountInstances=c?new Map:null,this.factoryPromise=E,this.factorySync=C,this.filter=s,this.getMountPoint=h,this.magic=a<<24,this.maxAge=f,this.maxOpenFiles=n,this.typeCheck=p}getExtractHint(r){return this.baseFs.getExtractHint(r)}getRealPath(){return this.baseFs.getRealPath()}saveAndClose(){if(yd(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.saveAndClose?.(),this.mountInstances.delete(r)}discardAndClose(){if(yd(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.discardAndClose?.(),this.mountInstances.delete(r)}resolve(r){return this.baseFs.resolve(r)}remapFd(r,s){let a=this.nextFd++|this.magic;return this.fdMap.set(a,[r,s]),a}async openPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.openPromise(r,s,a),async(n,{subPath:c})=>this.remapFd(n,await n.openPromise(c,s,a)))}openSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.openSync(r,s,a),(n,{subPath:c})=>this.remapFd(n,n.openSync(c,s,a)))}async opendirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.opendirPromise(r,s),async(a,{subPath:n})=>await a.opendirPromise(n,s),{requireSubpath:!1})}opendirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.opendirSync(r,s),(a,{subPath:n})=>a.opendirSync(n,s),{requireSubpath:!1})}async readPromise(r,s,a,n,c){if((r&tl)!==this.magic)return await this.baseFs.readPromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("read");let[p,h]=f;return await p.readPromise(h,s,a,n,c)}readSync(r,s,a,n,c){if((r&tl)!==this.magic)return this.baseFs.readSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("readSync");let[p,h]=f;return p.readSync(h,s,a,n,c)}async writePromise(r,s,a,n,c){if((r&tl)!==this.magic)return typeof s=="string"?await this.baseFs.writePromise(r,s,a):await this.baseFs.writePromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("write");let[p,h]=f;return typeof s=="string"?await p.writePromise(h,s,a):await p.writePromise(h,s,a,n,c)}writeSync(r,s,a,n,c){if((r&tl)!==this.magic)return typeof s=="string"?this.baseFs.writeSync(r,s,a):this.baseFs.writeSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("writeSync");let[p,h]=f;return typeof s=="string"?p.writeSync(h,s,a):p.writeSync(h,s,a,n,c)}async closePromise(r){if((r&tl)!==this.magic)return await this.baseFs.closePromise(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Mo("close");this.fdMap.delete(r);let[a,n]=s;return await a.closePromise(n)}closeSync(r){if((r&tl)!==this.magic)return this.baseFs.closeSync(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Mo("closeSync");this.fdMap.delete(r);let[a,n]=s;return a.closeSync(n)}createReadStream(r,s){return r===null?this.baseFs.createReadStream(r,s):this.makeCallSync(r,()=>this.baseFs.createReadStream(r,s),(a,{archivePath:n,subPath:c})=>{let f=a.createReadStream(c,s);return f.path=fe.fromPortablePath(this.pathUtils.join(n,c)),f})}createWriteStream(r,s){return r===null?this.baseFs.createWriteStream(r,s):this.makeCallSync(r,()=>this.baseFs.createWriteStream(r,s),(a,{subPath:n})=>a.createWriteStream(n,s))}async realpathPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.realpathPromise(r),async(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=await this.baseFs.realpathPromise(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,await s.realpathPromise(n)))})}realpathSync(r){return this.makeCallSync(r,()=>this.baseFs.realpathSync(r),(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=this.baseFs.realpathSync(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,s.realpathSync(n)))})}async existsPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.existsPromise(r),async(s,{subPath:a})=>await s.existsPromise(a))}existsSync(r){return this.makeCallSync(r,()=>this.baseFs.existsSync(r),(s,{subPath:a})=>s.existsSync(a))}async accessPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.accessPromise(r,s),async(a,{subPath:n})=>await a.accessPromise(n,s))}accessSync(r,s){return this.makeCallSync(r,()=>this.baseFs.accessSync(r,s),(a,{subPath:n})=>a.accessSync(n,s))}async statPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.statPromise(r,s),async(a,{subPath:n})=>await a.statPromise(n,s))}statSync(r,s){return this.makeCallSync(r,()=>this.baseFs.statSync(r,s),(a,{subPath:n})=>a.statSync(n,s))}async fstatPromise(r,s){if((r&tl)!==this.magic)return this.baseFs.fstatPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fstat");let[n,c]=a;return n.fstatPromise(c,s)}fstatSync(r,s){if((r&tl)!==this.magic)return this.baseFs.fstatSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fstatSync");let[n,c]=a;return n.fstatSync(c,s)}async lstatPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.lstatPromise(r,s),async(a,{subPath:n})=>await a.lstatPromise(n,s))}lstatSync(r,s){return this.makeCallSync(r,()=>this.baseFs.lstatSync(r,s),(a,{subPath:n})=>a.lstatSync(n,s))}async fchmodPromise(r,s){if((r&tl)!==this.magic)return this.baseFs.fchmodPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fchmod");let[n,c]=a;return n.fchmodPromise(c,s)}fchmodSync(r,s){if((r&tl)!==this.magic)return this.baseFs.fchmodSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fchmodSync");let[n,c]=a;return n.fchmodSync(c,s)}async chmodPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.chmodPromise(r,s),async(a,{subPath:n})=>await a.chmodPromise(n,s))}chmodSync(r,s){return this.makeCallSync(r,()=>this.baseFs.chmodSync(r,s),(a,{subPath:n})=>a.chmodSync(n,s))}async fchownPromise(r,s,a){if((r&tl)!==this.magic)return this.baseFs.fchownPromise(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Mo("fchown");let[c,f]=n;return c.fchownPromise(f,s,a)}fchownSync(r,s,a){if((r&tl)!==this.magic)return this.baseFs.fchownSync(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Mo("fchownSync");let[c,f]=n;return c.fchownSync(f,s,a)}async chownPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.chownPromise(r,s,a),async(n,{subPath:c})=>await n.chownPromise(c,s,a))}chownSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.chownSync(r,s,a),(n,{subPath:c})=>n.chownSync(c,s,a))}async renamePromise(r,s){return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.renamePromise(r,s),async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),async(a,{subPath:n})=>await this.makeCallPromise(s,async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},async(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return await a.renamePromise(n,f)}))}renameSync(r,s){return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.renameSync(r,s),()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),(a,{subPath:n})=>this.makeCallSync(s,()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return a.renameSync(n,f)}))}async copyFilePromise(r,s,a=0){let n=async(c,f,p,h)=>{if(a&wd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&wd.constants.COPYFILE_EXCL&&await this.existsPromise(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=await c.readFilePromise(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}await p.writeFilePromise(h,E)};return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.copyFilePromise(r,s,a),async(c,{subPath:f})=>await n(this.baseFs,r,c,f)),async(c,{subPath:f})=>await this.makeCallPromise(s,async()=>await n(c,f,this.baseFs,s),async(p,{subPath:h})=>c!==p?await n(c,f,p,h):await c.copyFilePromise(f,h,a)))}copyFileSync(r,s,a=0){let n=(c,f,p,h)=>{if(a&wd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&wd.constants.COPYFILE_EXCL&&this.existsSync(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=c.readFileSync(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}p.writeFileSync(h,E)};return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.copyFileSync(r,s,a),(c,{subPath:f})=>n(this.baseFs,r,c,f)),(c,{subPath:f})=>this.makeCallSync(s,()=>n(c,f,this.baseFs,s),(p,{subPath:h})=>c!==p?n(c,f,p,h):c.copyFileSync(f,h,a)))}async appendFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.appendFilePromise(r,s,a),async(n,{subPath:c})=>await n.appendFilePromise(c,s,a))}appendFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.appendFileSync(r,s,a),(n,{subPath:c})=>n.appendFileSync(c,s,a))}async writeFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.writeFilePromise(r,s,a),async(n,{subPath:c})=>await n.writeFilePromise(c,s,a))}writeFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.writeFileSync(r,s,a),(n,{subPath:c})=>n.writeFileSync(c,s,a))}async unlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.unlinkPromise(r),async(s,{subPath:a})=>await s.unlinkPromise(a))}unlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.unlinkSync(r),(s,{subPath:a})=>s.unlinkSync(a))}async utimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.utimesPromise(r,s,a),async(n,{subPath:c})=>await n.utimesPromise(c,s,a))}utimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.utimesSync(r,s,a),(n,{subPath:c})=>n.utimesSync(c,s,a))}async lutimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.lutimesPromise(r,s,a),async(n,{subPath:c})=>await n.lutimesPromise(c,s,a))}lutimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.lutimesSync(r,s,a),(n,{subPath:c})=>n.lutimesSync(c,s,a))}async mkdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.mkdirPromise(r,s),async(a,{subPath:n})=>await a.mkdirPromise(n,s))}mkdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.mkdirSync(r,s),(a,{subPath:n})=>a.mkdirSync(n,s))}async rmdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmdirPromise(r,s),async(a,{subPath:n})=>await a.rmdirPromise(n,s))}rmdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmdirSync(r,s),(a,{subPath:n})=>a.rmdirSync(n,s))}async rmPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmPromise(r,s),async(a,{subPath:n})=>await a.rmPromise(n,s))}rmSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmSync(r,s),(a,{subPath:n})=>a.rmSync(n,s))}async linkPromise(r,s){return await this.makeCallPromise(s,async()=>await this.baseFs.linkPromise(r,s),async(a,{subPath:n})=>await a.linkPromise(r,n))}linkSync(r,s){return this.makeCallSync(s,()=>this.baseFs.linkSync(r,s),(a,{subPath:n})=>a.linkSync(r,n))}async symlinkPromise(r,s,a){return await this.makeCallPromise(s,async()=>await this.baseFs.symlinkPromise(r,s,a),async(n,{subPath:c})=>await n.symlinkPromise(r,c))}symlinkSync(r,s,a){return this.makeCallSync(s,()=>this.baseFs.symlinkSync(r,s,a),(n,{subPath:c})=>n.symlinkSync(r,c))}async readFilePromise(r,s){return this.makeCallPromise(r,async()=>await this.baseFs.readFilePromise(r,s),async(a,{subPath:n})=>await a.readFilePromise(n,s))}readFileSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readFileSync(r,s),(a,{subPath:n})=>a.readFileSync(n,s))}async readdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.readdirPromise(r,s),async(a,{subPath:n})=>await a.readdirPromise(n,s),{requireSubpath:!1})}readdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readdirSync(r,s),(a,{subPath:n})=>a.readdirSync(n,s),{requireSubpath:!1})}async readlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.readlinkPromise(r),async(s,{subPath:a})=>await s.readlinkPromise(a))}readlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.readlinkSync(r),(s,{subPath:a})=>s.readlinkSync(a))}async truncatePromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.truncatePromise(r,s),async(a,{subPath:n})=>await a.truncatePromise(n,s))}truncateSync(r,s){return this.makeCallSync(r,()=>this.baseFs.truncateSync(r,s),(a,{subPath:n})=>a.truncateSync(n,s))}async ftruncatePromise(r,s){if((r&tl)!==this.magic)return this.baseFs.ftruncatePromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("ftruncate");let[n,c]=a;return n.ftruncatePromise(c,s)}ftruncateSync(r,s){if((r&tl)!==this.magic)return this.baseFs.ftruncateSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("ftruncateSync");let[n,c]=a;return n.ftruncateSync(c,s)}watch(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watch(r,s,a),(n,{subPath:c})=>n.watch(c,s,a))}watchFile(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watchFile(r,s,a),()=>sE(this,r,s,a))}unwatchFile(r,s){return this.makeCallSync(r,()=>this.baseFs.unwatchFile(r,s),()=>md(this,r,s))}async makeCallPromise(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return await s();let c=this.resolve(r),f=this.findMount(c);return f?n&&f.subPath==="/"?await s():await this.getMountPromise(f.archivePath,async p=>await a(p,f)):await s()}makeCallSync(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return s();let c=this.resolve(r),f=this.findMount(c);return!f||n&&f.subPath==="/"?s():this.getMountSync(f.archivePath,p=>a(p,f))}findMount(r){if(this.filter&&!this.filter.test(r))return null;let s="";for(;;){let a=r.substring(s.length),n=this.getMountPoint(a,s);if(!n)return null;if(s=this.pathUtils.join(s,n),!this.isMount.has(s)){if(this.notMount.has(s))continue;try{if(this.typeCheck!==null&&(this.baseFs.statSync(s).mode&wd.constants.S_IFMT)!==this.typeCheck){this.notMount.add(s);continue}}catch{return null}this.isMount.add(s)}return{archivePath:s,subPath:this.pathUtils.join(vt.root,r.substring(s.length))}}}limitOpenFiles(r){if(this.mountInstances===null)return;let s=Date.now(),a=s+this.maxAge,n=r===null?0:this.mountInstances.size-r;for(let[c,{childFs:f,expiresAt:p,refCount:h}]of this.mountInstances.entries())if(!(h!==0||f.hasOpenFileHandles?.())){if(s>=p){f.saveAndClose?.(),this.mountInstances.delete(c),n-=1;continue}else if(r===null||n<=0){a=p;break}f.saveAndClose?.(),this.mountInstances.delete(c),n-=1}this.limitOpenFilesTimeout===null&&(r===null&&this.mountInstances.size>0||r!==null)&&isFinite(a)&&(this.limitOpenFilesTimeout=setTimeout(()=>{this.limitOpenFilesTimeout=null,this.limitOpenFiles(null)},a-s).unref())}async getMountPromise(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);if(!a){let n=await this.factoryPromise(this.baseFs,r);a=this.mountInstances.get(r),a||(a={childFs:n(),expiresAt:0,refCount:0})}this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,a.refCount+=1;try{return await s(a.childFs)}finally{a.refCount-=1}}else{let a=(await this.factoryPromise(this.baseFs,r))();try{return await s(a)}finally{a.saveAndClose?.()}}}getMountSync(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);return a||(a={childFs:this.factorySync(this.baseFs,r),expiresAt:0,refCount:0}),this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,s(a.childFs)}else{let a=this.factorySync(this.baseFs,r);try{return s(a)}finally{a.saveAndClose?.()}}}}});var er,nx,A$=Xe(()=>{Id();el();er=()=>Object.assign(new Error("ENOSYS: unsupported filesystem access"),{code:"ENOSYS"}),nx=class t extends mp{static{this.instance=new t}constructor(){super(J)}getExtractHint(){throw er()}getRealPath(){throw er()}resolve(){throw er()}async openPromise(){throw er()}openSync(){throw er()}async opendirPromise(){throw er()}opendirSync(){throw er()}async readPromise(){throw er()}readSync(){throw er()}async writePromise(){throw er()}writeSync(){throw er()}async closePromise(){throw er()}closeSync(){throw er()}createWriteStream(){throw er()}createReadStream(){throw er()}async realpathPromise(){throw er()}realpathSync(){throw er()}async readdirPromise(){throw er()}readdirSync(){throw er()}async existsPromise(e){throw er()}existsSync(e){throw er()}async accessPromise(){throw er()}accessSync(){throw er()}async statPromise(){throw er()}statSync(){throw er()}async fstatPromise(e){throw er()}fstatSync(e){throw er()}async lstatPromise(e){throw er()}lstatSync(e){throw er()}async fchmodPromise(){throw er()}fchmodSync(){throw er()}async chmodPromise(){throw er()}chmodSync(){throw er()}async fchownPromise(){throw er()}fchownSync(){throw er()}async chownPromise(){throw er()}chownSync(){throw er()}async mkdirPromise(){throw er()}mkdirSync(){throw er()}async rmdirPromise(){throw er()}rmdirSync(){throw er()}async rmPromise(){throw er()}rmSync(){throw er()}async linkPromise(){throw er()}linkSync(){throw er()}async symlinkPromise(){throw er()}symlinkSync(){throw er()}async renamePromise(){throw er()}renameSync(){throw er()}async copyFilePromise(){throw er()}copyFileSync(){throw er()}async appendFilePromise(){throw er()}appendFileSync(){throw er()}async writeFilePromise(){throw er()}writeFileSync(){throw er()}async unlinkPromise(){throw er()}unlinkSync(){throw er()}async utimesPromise(){throw er()}utimesSync(){throw er()}async lutimesPromise(){throw er()}lutimesSync(){throw er()}async readFilePromise(){throw er()}readFileSync(){throw er()}async readlinkPromise(){throw er()}readlinkSync(){throw er()}async truncatePromise(){throw er()}truncateSync(){throw er()}async ftruncatePromise(e,r){throw er()}ftruncateSync(e,r){throw er()}watch(){throw er()}watchFile(){throw er()}unwatchFile(){throw er()}}});var t0,p$=Xe(()=>{yp();el();t0=class extends _s{constructor(e){super(fe),this.baseFs=e}mapFromBase(e){return fe.fromPortablePath(e)}mapToBase(e){return fe.toPortablePath(e)}}});var o5e,PU,a5e,uo,h$=Xe(()=>{Cd();yp();el();o5e=/^[0-9]+$/,PU=/^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/,a5e=/^([^/]+-)?[a-f0-9]+$/,uo=class t extends _s{static makeVirtualPath(e,r,s){if(J.basename(e)!=="__virtual__")throw new Error('Assertion failed: Virtual folders must be named "__virtual__"');if(!J.basename(r).match(a5e))throw new Error("Assertion failed: Virtual components must be ended by an hexadecimal hash");let n=J.relative(J.dirname(e),s).split("/"),c=0;for(;c{xU=ut(Ie("buffer")),g$=Ie("url"),d$=Ie("util");yp();el();ix=class extends _s{constructor(e){super(fe),this.baseFs=e}mapFromBase(e){return e}mapToBase(e){if(typeof e=="string")return e;if(e instanceof URL)return(0,g$.fileURLToPath)(e);if(Buffer.isBuffer(e)){let r=e.toString();if(!l5e(e,r))throw new Error("Non-utf8 buffers are not supported at the moment. Please upvote the following issue if you encounter this error: https://github.com/yarnpkg/berry/issues/4942");return r}throw new Error(`Unsupported path type: ${(0,d$.inspect)(e)}`)}}});var w$,Uo,Ep,r0,sx,ox,aE,Ru,Fu,y$,E$,I$,C$,M2,B$=Xe(()=>{w$=Ie("readline"),Uo=Symbol("kBaseFs"),Ep=Symbol("kFd"),r0=Symbol("kClosePromise"),sx=Symbol("kCloseResolve"),ox=Symbol("kCloseReject"),aE=Symbol("kRefs"),Ru=Symbol("kRef"),Fu=Symbol("kUnref"),M2=class{constructor(e,r){this[C$]=1;this[I$]=void 0;this[E$]=void 0;this[y$]=void 0;this[Uo]=r,this[Ep]=e}get fd(){return this[Ep]}async appendFile(e,r){try{this[Ru](this.appendFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;return await this[Uo].appendFilePromise(this.fd,e,s?{encoding:s}:void 0)}finally{this[Fu]()}}async chown(e,r){try{return this[Ru](this.chown),await this[Uo].fchownPromise(this.fd,e,r)}finally{this[Fu]()}}async chmod(e){try{return this[Ru](this.chmod),await this[Uo].fchmodPromise(this.fd,e)}finally{this[Fu]()}}createReadStream(e){return this[Uo].createReadStream(null,{...e,fd:this.fd})}createWriteStream(e){return this[Uo].createWriteStream(null,{...e,fd:this.fd})}datasync(){throw new Error("Method not implemented.")}sync(){throw new Error("Method not implemented.")}async read(e,r,s,a){try{this[Ru](this.read);let n,c;return ArrayBuffer.isView(e)?typeof r=="object"&&r!==null?(n=e,c=r?.offset??0,s=r?.length??n.byteLength-c,a=r?.position??null):(n=e,c=r??0,s??=0):(n=e?.buffer??Buffer.alloc(16384),c=e?.offset??0,s=e?.length??n.byteLength-c,a=e?.position??null),s===0?{bytesRead:s,buffer:n}:{bytesRead:await this[Uo].readPromise(this.fd,Buffer.isBuffer(n)?n:Buffer.from(n.buffer,n.byteOffset,n.byteLength),c,s,a),buffer:n}}finally{this[Fu]()}}async readFile(e){try{this[Ru](this.readFile);let r=(typeof e=="string"?e:e?.encoding)??void 0;return await this[Uo].readFilePromise(this.fd,r)}finally{this[Fu]()}}readLines(e){return(0,w$.createInterface)({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){try{return this[Ru](this.stat),await this[Uo].fstatPromise(this.fd,e)}finally{this[Fu]()}}async truncate(e){try{return this[Ru](this.truncate),await this[Uo].ftruncatePromise(this.fd,e)}finally{this[Fu]()}}utimes(e,r){throw new Error("Method not implemented.")}async writeFile(e,r){try{this[Ru](this.writeFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;await this[Uo].writeFilePromise(this.fd,e,s)}finally{this[Fu]()}}async write(...e){try{if(this[Ru](this.write),ArrayBuffer.isView(e[0])){let[r,s,a,n]=e;return{bytesWritten:await this[Uo].writePromise(this.fd,r,s??void 0,a??void 0,n??void 0),buffer:r}}else{let[r,s,a]=e;return{bytesWritten:await this[Uo].writePromise(this.fd,r,s,a),buffer:r}}}finally{this[Fu]()}}async writev(e,r){try{this[Ru](this.writev);let s=0;if(typeof r<"u")for(let a of e){let n=await this.write(a,void 0,void 0,r);s+=n.bytesWritten,r+=n.bytesWritten}else for(let a of e){let n=await this.write(a);s+=n.bytesWritten}return{buffers:e,bytesWritten:s}}finally{this[Fu]()}}readv(e,r){throw new Error("Method not implemented.")}close(){if(this[Ep]===-1)return Promise.resolve();if(this[r0])return this[r0];if(this[aE]--,this[aE]===0){let e=this[Ep];this[Ep]=-1,this[r0]=this[Uo].closePromise(e).finally(()=>{this[r0]=void 0})}else this[r0]=new Promise((e,r)=>{this[sx]=e,this[ox]=r}).finally(()=>{this[r0]=void 0,this[ox]=void 0,this[sx]=void 0});return this[r0]}[(Uo,Ep,C$=aE,I$=r0,E$=sx,y$=ox,Ru)](e){if(this[Ep]===-1){let r=new Error("file closed");throw r.code="EBADF",r.syscall=e.name,r}this[aE]++}[Fu](){if(this[aE]--,this[aE]===0){let e=this[Ep];this[Ep]=-1,this[Uo].closePromise(e).then(this[sx],this[ox])}}}});function U2(t,e){e=new ix(e);let r=(s,a,n)=>{let c=s[a];s[a]=n,typeof c?.[lE.promisify.custom]<"u"&&(n[lE.promisify.custom]=c[lE.promisify.custom])};{r(t,"exists",(s,...a)=>{let c=typeof a[a.length-1]=="function"?a.pop():()=>{};process.nextTick(()=>{e.existsPromise(s).then(f=>{c(f)},()=>{c(!1)})})}),r(t,"read",(...s)=>{let[a,n,c,f,p,h]=s;if(s.length<=3){let E={};s.length<3?h=s[1]:(E=s[1],h=s[2]),{buffer:n=Buffer.alloc(16384),offset:c=0,length:f=n.byteLength,position:p}=E}if(c==null&&(c=0),f|=0,f===0){process.nextTick(()=>{h(null,0,n)});return}p==null&&(p=-1),process.nextTick(()=>{e.readPromise(a,n,c,f,p).then(E=>{h(null,E,n)},E=>{h(E,0,n)})})});for(let s of v$){let a=s.replace(/Promise$/,"");if(typeof t[a]>"u")continue;let n=e[s];if(typeof n>"u")continue;r(t,a,(...f)=>{let h=typeof f[f.length-1]=="function"?f.pop():()=>{};process.nextTick(()=>{n.apply(e,f).then(E=>{h(null,E)},E=>{h(E)})})})}t.realpath.native=t.realpath}{r(t,"existsSync",s=>{try{return e.existsSync(s)}catch{return!1}}),r(t,"readSync",(...s)=>{let[a,n,c,f,p]=s;return s.length<=3&&({offset:c=0,length:f=n.byteLength,position:p}=s[2]||{}),c==null&&(c=0),f|=0,f===0?0:(p==null&&(p=-1),e.readSync(a,n,c,f,p))});for(let s of c5e){let a=s;if(typeof t[a]>"u")continue;let n=e[s];typeof n>"u"||r(t,a,n.bind(e))}t.realpathSync.native=t.realpathSync}{let s=t.promises;for(let a of v$){let n=a.replace(/Promise$/,"");if(typeof s[n]>"u")continue;let c=e[a];typeof c>"u"||a!=="open"&&r(s,n,(f,...p)=>f instanceof M2?f[n].apply(f,p):c.call(e,f,...p))}r(s,"open",async(...a)=>{let n=await e.openPromise(...a);return new M2(n,e)})}t.read[lE.promisify.custom]=async(s,a,...n)=>({bytesRead:await e.readPromise(s,a,...n),buffer:a}),t.write[lE.promisify.custom]=async(s,a,...n)=>({bytesWritten:await e.writePromise(s,a,...n),buffer:a})}function ax(t,e){let r=Object.create(t);return U2(r,e),r}var lE,c5e,v$,S$=Xe(()=>{lE=Ie("util");m$();B$();c5e=new Set(["accessSync","appendFileSync","createReadStream","createWriteStream","chmodSync","fchmodSync","chownSync","fchownSync","closeSync","copyFileSync","linkSync","lstatSync","fstatSync","lutimesSync","mkdirSync","openSync","opendirSync","readlinkSync","readFileSync","readdirSync","readlinkSync","realpathSync","renameSync","rmdirSync","rmSync","statSync","symlinkSync","truncateSync","ftruncateSync","unlinkSync","unwatchFile","utimesSync","watch","watchFile","writeFileSync","writeSync"]),v$=new Set(["accessPromise","appendFilePromise","fchmodPromise","chmodPromise","fchownPromise","chownPromise","closePromise","copyFilePromise","linkPromise","fstatPromise","lstatPromise","lutimesPromise","mkdirPromise","openPromise","opendirPromise","readdirPromise","realpathPromise","readFilePromise","readdirPromise","readlinkPromise","renamePromise","rmdirPromise","rmPromise","statPromise","symlinkPromise","truncatePromise","ftruncatePromise","unlinkPromise","utimesPromise","writeFilePromise","writeSync"])});function D$(t){let e=Math.ceil(Math.random()*4294967296).toString(16).padStart(8,"0");return`${t}${e}`}function b$(){if(kU)return kU;let t=fe.toPortablePath(P$.default.tmpdir()),e=ce.realpathSync(t);return process.once("exit",()=>{ce.rmtempSync()}),kU={tmpdir:t,realTmpdir:e}}var P$,Nu,kU,ce,x$=Xe(()=>{P$=ut(Ie("os"));Cd();el();Nu=new Set,kU=null;ce=Object.assign(new Yn,{detachTemp(t){Nu.delete(t)},mktempSync(t){let{tmpdir:e,realTmpdir:r}=b$();for(;;){let s=D$("xfs-");try{this.mkdirSync(J.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=J.join(r,s);if(Nu.add(a),typeof t>"u")return a;try{return t(a)}finally{if(Nu.has(a)){Nu.delete(a);try{this.removeSync(a)}catch{}}}}},async mktempPromise(t){let{tmpdir:e,realTmpdir:r}=b$();for(;;){let s=D$("xfs-");try{await this.mkdirPromise(J.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=J.join(r,s);if(Nu.add(a),typeof t>"u")return a;try{return await t(a)}finally{if(Nu.has(a)){Nu.delete(a);try{await this.removePromise(a)}catch{}}}}},async rmtempPromise(){await Promise.all(Array.from(Nu.values()).map(async t=>{try{await ce.removePromise(t,{maxRetries:0}),Nu.delete(t)}catch{}}))},rmtempSync(){for(let t of Nu)try{ce.removeSync(t),Nu.delete(t)}catch{}}})});var _2={};Vt(_2,{AliasFS:()=>_f,BasePortableFakeFS:()=>Uf,CustomDir:()=>L2,CwdFS:()=>Sn,FakeFS:()=>mp,Filename:()=>Er,JailFS:()=>Hf,LazyFS:()=>oE,MountFS:()=>e0,NoFS:()=>nx,NodeFS:()=>Yn,PortablePath:()=>vt,PosixFS:()=>t0,ProxiedFS:()=>_s,VirtualFS:()=>uo,constants:()=>fi,errors:()=>or,extendFs:()=>ax,normalizeLineEndings:()=>Ed,npath:()=>fe,opendir:()=>ex,patchFs:()=>U2,ppath:()=>J,setupCopyIndex:()=>$P,statUtils:()=>$a,unwatchAllFiles:()=>yd,unwatchFile:()=>md,watchFile:()=>sE,xfs:()=>ce});var Dt=Xe(()=>{YZ();zP();BU();DU();ZZ();bU();Id();el();el();i$();Id();a$();c$();u$();f$();A$();Cd();p$();yp();h$();S$();x$()});var F$=_((Dkt,R$)=>{R$.exports=T$;T$.sync=f5e;var k$=Ie("fs");function u5e(t,e){var r=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!r||(r=r.split(";"),r.indexOf("")!==-1))return!0;for(var s=0;s{M$.exports=O$;O$.sync=A5e;var N$=Ie("fs");function O$(t,e,r){N$.stat(t,function(s,a){r(s,s?!1:L$(a,e))})}function A5e(t,e){return L$(N$.statSync(t),e)}function L$(t,e){return t.isFile()&&p5e(t,e)}function p5e(t,e){var r=t.mode,s=t.uid,a=t.gid,n=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),c=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),f=parseInt("100",8),p=parseInt("010",8),h=parseInt("001",8),E=f|p,C=r&h||r&p&&a===c||r&f&&s===n||r&E&&n===0;return C}});var H$=_((xkt,_$)=>{var Pkt=Ie("fs"),lx;process.platform==="win32"||global.TESTING_WINDOWS?lx=F$():lx=U$();_$.exports=QU;QU.sync=h5e;function QU(t,e,r){if(typeof e=="function"&&(r=e,e={}),!r){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(s,a){QU(t,e||{},function(n,c){n?a(n):s(c)})})}lx(t,e||{},function(s,a){s&&(s.code==="EACCES"||e&&e.ignoreErrors)&&(s=null,a=!1),r(s,a)})}function h5e(t,e){try{return lx.sync(t,e||{})}catch(r){if(e&&e.ignoreErrors||r.code==="EACCES")return!1;throw r}}});var J$=_((kkt,V$)=>{var cE=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",j$=Ie("path"),g5e=cE?";":":",G$=H$(),q$=t=>Object.assign(new Error(`not found: ${t}`),{code:"ENOENT"}),W$=(t,e)=>{let r=e.colon||g5e,s=t.match(/\//)||cE&&t.match(/\\/)?[""]:[...cE?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(r)],a=cE?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",n=cE?a.split(r):[""];return cE&&t.indexOf(".")!==-1&&n[0]!==""&&n.unshift(""),{pathEnv:s,pathExt:n,pathExtExe:a}},Y$=(t,e,r)=>{typeof e=="function"&&(r=e,e={}),e||(e={});let{pathEnv:s,pathExt:a,pathExtExe:n}=W$(t,e),c=[],f=h=>new Promise((E,C)=>{if(h===s.length)return e.all&&c.length?E(c):C(q$(t));let S=s[h],P=/^".*"$/.test(S)?S.slice(1,-1):S,I=j$.join(P,t),R=!P&&/^\.[\\\/]/.test(t)?t.slice(0,2)+I:I;E(p(R,h,0))}),p=(h,E,C)=>new Promise((S,P)=>{if(C===a.length)return S(f(E+1));let I=a[C];G$(h+I,{pathExt:n},(R,N)=>{if(!R&&N)if(e.all)c.push(h+I);else return S(h+I);return S(p(h,E,C+1))})});return r?f(0).then(h=>r(null,h),r):f(0)},d5e=(t,e)=>{e=e||{};let{pathEnv:r,pathExt:s,pathExtExe:a}=W$(t,e),n=[];for(let c=0;c{"use strict";var K$=(t={})=>{let e=t.env||process.env;return(t.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(s=>s.toUpperCase()==="PATH")||"Path"};TU.exports=K$;TU.exports.default=K$});var eee=_((Tkt,$$)=>{"use strict";var X$=Ie("path"),m5e=J$(),y5e=z$();function Z$(t,e){let r=t.options.env||process.env,s=process.cwd(),a=t.options.cwd!=null,n=a&&process.chdir!==void 0&&!process.chdir.disabled;if(n)try{process.chdir(t.options.cwd)}catch{}let c;try{c=m5e.sync(t.command,{path:r[y5e({env:r})],pathExt:e?X$.delimiter:void 0})}catch{}finally{n&&process.chdir(s)}return c&&(c=X$.resolve(a?t.options.cwd:"",c)),c}function E5e(t){return Z$(t)||Z$(t,!0)}$$.exports=E5e});var tee=_((Rkt,FU)=>{"use strict";var RU=/([()\][%!^"`<>&|;, *?])/g;function I5e(t){return t=t.replace(RU,"^$1"),t}function C5e(t,e){return t=`${t}`,t=t.replace(/(?=(\\+?)?)\1"/g,'$1$1\\"'),t=t.replace(/(?=(\\+?)?)\1$/,"$1$1"),t=`"${t}"`,t=t.replace(RU,"^$1"),e&&(t=t.replace(RU,"^$1")),t}FU.exports.command=I5e;FU.exports.argument=C5e});var nee=_((Fkt,ree)=>{"use strict";ree.exports=/^#!(.*)/});var see=_((Nkt,iee)=>{"use strict";var w5e=nee();iee.exports=(t="")=>{let e=t.match(w5e);if(!e)return null;let[r,s]=e[0].replace(/#! ?/,"").split(" "),a=r.split("/").pop();return a==="env"?s:s?`${a} ${s}`:a}});var aee=_((Okt,oee)=>{"use strict";var NU=Ie("fs"),B5e=see();function v5e(t){let r=Buffer.alloc(150),s;try{s=NU.openSync(t,"r"),NU.readSync(s,r,0,150,0),NU.closeSync(s)}catch{}return B5e(r.toString())}oee.exports=v5e});var fee=_((Lkt,uee)=>{"use strict";var S5e=Ie("path"),lee=eee(),cee=tee(),D5e=aee(),b5e=process.platform==="win32",P5e=/\.(?:com|exe)$/i,x5e=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function k5e(t){t.file=lee(t);let e=t.file&&D5e(t.file);return e?(t.args.unshift(t.file),t.command=e,lee(t)):t.file}function Q5e(t){if(!b5e)return t;let e=k5e(t),r=!P5e.test(e);if(t.options.forceShell||r){let s=x5e.test(e);t.command=S5e.normalize(t.command),t.command=cee.command(t.command),t.args=t.args.map(n=>cee.argument(n,s));let a=[t.command].concat(t.args).join(" ");t.args=["/d","/s","/c",`"${a}"`],t.command=process.env.comspec||"cmd.exe",t.options.windowsVerbatimArguments=!0}return t}function T5e(t,e,r){e&&!Array.isArray(e)&&(r=e,e=null),e=e?e.slice(0):[],r=Object.assign({},r);let s={command:t,args:e,options:r,file:void 0,original:{command:t,args:e}};return r.shell?s:Q5e(s)}uee.exports=T5e});var hee=_((Mkt,pee)=>{"use strict";var OU=process.platform==="win32";function LU(t,e){return Object.assign(new Error(`${e} ${t.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${t.command}`,path:t.command,spawnargs:t.args})}function R5e(t,e){if(!OU)return;let r=t.emit;t.emit=function(s,a){if(s==="exit"){let n=Aee(a,e);if(n)return r.call(t,"error",n)}return r.apply(t,arguments)}}function Aee(t,e){return OU&&t===1&&!e.file?LU(e.original,"spawn"):null}function F5e(t,e){return OU&&t===1&&!e.file?LU(e.original,"spawnSync"):null}pee.exports={hookChildProcess:R5e,verifyENOENT:Aee,verifyENOENTSync:F5e,notFoundError:LU}});var _U=_((Ukt,uE)=>{"use strict";var gee=Ie("child_process"),MU=fee(),UU=hee();function dee(t,e,r){let s=MU(t,e,r),a=gee.spawn(s.command,s.args,s.options);return UU.hookChildProcess(a,s),a}function N5e(t,e,r){let s=MU(t,e,r),a=gee.spawnSync(s.command,s.args,s.options);return a.error=a.error||UU.verifyENOENTSync(a.status,s),a}uE.exports=dee;uE.exports.spawn=dee;uE.exports.sync=N5e;uE.exports._parse=MU;uE.exports._enoent=UU});var yee=_((_kt,mee)=>{"use strict";function O5e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Bd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Bd)}O5e(Bd,Error);Bd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C>",b=ur(">>",!1),y=">&",F=ur(">&",!1),z=">",X=ur(">",!1),$="<<<",oe=ur("<<<",!1),xe="<&",Te=ur("<&",!1),lt="<",Ct=ur("<",!1),qt=function(O){return{type:"argument",segments:[].concat(...O)}},ir=function(O){return O},Pt="$'",gn=ur("$'",!1),Pr="'",Ir=ur("'",!1),Or=function(O){return[{type:"text",text:O}]},on='""',ai=ur('""',!1),Io=function(){return{type:"text",text:""}},rs='"',$s=ur('"',!1),Co=function(O){return O},ji=function(O){return{type:"arithmetic",arithmetic:O,quoted:!0}},eo=function(O){return{type:"shell",shell:O,quoted:!0}},wo=function(O){return{type:"variable",...O,quoted:!0}},QA=function(O){return{type:"text",text:O}},Af=function(O){return{type:"arithmetic",arithmetic:O,quoted:!1}},dh=function(O){return{type:"shell",shell:O,quoted:!1}},mh=function(O){return{type:"variable",...O,quoted:!1}},to=function(O){return{type:"glob",pattern:O}},jn=/^[^']/,Ts=zi(["'"],!0,!1),ro=function(O){return O.join("")},ou=/^[^$"]/,au=zi(["$",'"'],!0,!1),lu=`\\ +`,TA=ur(`\\ +`,!1),RA=function(){return""},oa="\\",aa=ur("\\",!1),FA=/^[\\$"`]/,gr=zi(["\\","$",'"',"`"],!1,!1),Bo=function(O){return O},Me="\\a",cu=ur("\\a",!1),Cr=function(){return"a"},pf="\\b",NA=ur("\\b",!1),OA=function(){return"\b"},uu=/^[Ee]/,fu=zi(["E","e"],!1,!1),oc=function(){return"\x1B"},ve="\\f",Nt=ur("\\f",!1),ac=function(){return"\f"},Oi="\\n",no=ur("\\n",!1),Rt=function(){return` +`},xn="\\r",la=ur("\\r",!1),Gi=function(){return"\r"},Li="\\t",Na=ur("\\t",!1),dn=function(){return" "},Kn="\\v",Au=ur("\\v",!1),yh=function(){return"\v"},Oa=/^[\\'"?]/,La=zi(["\\","'",'"',"?"],!1,!1),Ma=function(O){return String.fromCharCode(parseInt(O,16))},$e="\\x",Ua=ur("\\x",!1),hf="\\u",lc=ur("\\u",!1),wn="\\U",ca=ur("\\U",!1),LA=function(O){return String.fromCodePoint(parseInt(O,16))},MA=/^[0-7]/,ua=zi([["0","7"]],!1,!1),Bl=/^[0-9a-fA-f]/,Mt=zi([["0","9"],["a","f"],["A","f"]],!1,!1),kn=yf(),fa="{}",Ha=ur("{}",!1),ns=function(){return"{}"},cc="-",pu=ur("-",!1),uc="+",ja=ur("+",!1),Mi=".",Is=ur(".",!1),vl=function(O,K,re){return{type:"number",value:(O==="-"?-1:1)*parseFloat(K.join("")+"."+re.join(""))}},gf=function(O,K){return{type:"number",value:(O==="-"?-1:1)*parseInt(K.join(""))}},fc=function(O){return{type:"variable",...O}},wi=function(O){return{type:"variable",name:O}},Qn=function(O){return O},Ac="*",Ke=ur("*",!1),st="/",St=ur("/",!1),lr=function(O,K,re){return{type:K==="*"?"multiplication":"division",right:re}},te=function(O,K){return K.reduce((re,de)=>({left:re,...de}),O)},Ee=function(O,K,re){return{type:K==="+"?"addition":"subtraction",right:re}},Oe="$((",dt=ur("$((",!1),Et="))",bt=ur("))",!1),tr=function(O){return O},An="$(",li=ur("$(",!1),qi=function(O){return O},Tn="${",Ga=ur("${",!1),my=":-",Z1=ur(":-",!1),vo=function(O,K){return{name:O,defaultValue:K}},yy=":-}",Eh=ur(":-}",!1),$1=function(O){return{name:O,defaultValue:[]}},So=":+",Ih=ur(":+",!1),Ch=function(O,K){return{name:O,alternativeValue:K}},hu=":+}",wh=ur(":+}",!1),Fg=function(O){return{name:O,alternativeValue:[]}},Ng=function(O){return{name:O}},Og="$",Ey=ur("$",!1),df=function(O){return e.isGlobPattern(O)},Do=function(O){return O},Sl=/^[a-zA-Z0-9_]/,Bh=zi([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),Lg=function(){return By()},Dl=/^[$@*?#a-zA-Z0-9_\-]/,bl=zi(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),Iy=/^[()}<>$|&; \t"']/,UA=zi(["(",")","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),Cy=/^[<>&; \t"']/,wy=zi(["<",">","&",";"," "," ",'"',"'"],!1,!1),_A=/^[ \t]/,HA=zi([" "," "],!1,!1),Y=0,xt=0,jA=[{line:1,column:1}],bo=0,mf=[],yt=0,gu;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function By(){return t.substring(xt,Y)}function Mg(){return Ef(xt,Y)}function e2(O,K){throw K=K!==void 0?K:Ef(xt,Y),GA([Ug(O)],t.substring(xt,Y),K)}function vh(O,K){throw K=K!==void 0?K:Ef(xt,Y),di(O,K)}function ur(O,K){return{type:"literal",text:O,ignoreCase:K}}function zi(O,K,re){return{type:"class",parts:O,inverted:K,ignoreCase:re}}function yf(){return{type:"any"}}function qa(){return{type:"end"}}function Ug(O){return{type:"other",description:O}}function du(O){var K=jA[O],re;if(K)return K;for(re=O-1;!jA[re];)re--;for(K=jA[re],K={line:K.line,column:K.column};rebo&&(bo=Y,mf=[]),mf.push(O))}function di(O,K){return new Bd(O,null,null,K)}function GA(O,K,re){return new Bd(Bd.buildMessage(O,K),O,K,re)}function Wa(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(re=Aa(),re===r&&(re=null),re!==r?(xt=O,K=n(re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Aa(){var O,K,re,de,Je;if(O=Y,K=Sh(),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de!==r?(Je=Ya(),Je===r&&(Je=null),Je!==r?(xt=O,K=c(K,de,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;if(O===r)if(O=Y,K=Sh(),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de===r&&(de=null),de!==r?(xt=O,K=f(K,de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function Ya(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=Aa(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=p(re),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function _g(){var O;return t.charCodeAt(Y)===59?(O=h,Y++):(O=r,yt===0&&wt(E)),O===r&&(t.charCodeAt(Y)===38?(O=C,Y++):(O=r,yt===0&&wt(S))),O}function Sh(){var O,K,re;return O=Y,K=qA(),K!==r?(re=Hg(),re===r&&(re=null),re!==r?(xt=O,K=P(K,re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Hg(){var O,K,re,de,Je,At,dr;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=vy(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Sh(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=I(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function vy(){var O;return t.substr(Y,2)===R?(O=R,Y+=2):(O=r,yt===0&&wt(N)),O===r&&(t.substr(Y,2)===U?(O=U,Y+=2):(O=r,yt===0&&wt(W))),O}function qA(){var O,K,re;return O=Y,K=If(),K!==r?(re=jg(),re===r&&(re=null),re!==r?(xt=O,K=ee(K,re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function jg(){var O,K,re,de,Je,At,dr;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=mu(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=qA(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=ie(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function mu(){var O;return t.substr(Y,2)===ue?(O=ue,Y+=2):(O=r,yt===0&&wt(le)),O===r&&(t.charCodeAt(Y)===124?(O=me,Y++):(O=r,yt===0&&wt(pe))),O}function yu(){var O,K,re,de,Je,At;if(O=Y,K=Ph(),K!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,yt===0&&wt(Ce)),re!==r)if(de=WA(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(xt=O,K=g(K,de),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;else Y=O,O=r;if(O===r)if(O=Y,K=Ph(),K!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,yt===0&&wt(Ce)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=we(K),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function If(){var O,K,re,de,Je,At,dr,vr,Un,mi,Cs;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(t.charCodeAt(Y)===40?(re=ye,Y++):(re=r,yt===0&&wt(Ae)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Aa(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();if(At!==r)if(t.charCodeAt(Y)===41?(dr=se,Y++):(dr=r,yt===0&&wt(Z)),dr!==r){for(vr=[],Un=kt();Un!==r;)vr.push(Un),Un=kt();if(vr!==r){for(Un=[],mi=Gn();mi!==r;)Un.push(mi),mi=Gn();if(Un!==r){for(mi=[],Cs=kt();Cs!==r;)mi.push(Cs),Cs=kt();mi!==r?(xt=O,K=De(Je,Un),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(t.charCodeAt(Y)===123?(re=Re,Y++):(re=r,yt===0&&wt(mt)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Aa(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();if(At!==r)if(t.charCodeAt(Y)===125?(dr=j,Y++):(dr=r,yt===0&&wt(rt)),dr!==r){for(vr=[],Un=kt();Un!==r;)vr.push(Un),Un=kt();if(vr!==r){for(Un=[],mi=Gn();mi!==r;)Un.push(mi),mi=Gn();if(Un!==r){for(mi=[],Cs=kt();Cs!==r;)mi.push(Cs),Cs=kt();mi!==r?(xt=O,K=Fe(Je,Un),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){for(re=[],de=yu();de!==r;)re.push(de),de=yu();if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r){if(Je=[],At=Eu(),At!==r)for(;At!==r;)Je.push(At),At=Eu();else Je=r;if(Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=Ne(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){if(re=[],de=yu(),de!==r)for(;de!==r;)re.push(de),de=yu();else re=r;if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=Pe(re),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}}}return O}function Rs(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){if(re=[],de=Pi(),de!==r)for(;de!==r;)re.push(de),de=Pi();else re=r;if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=Ve(re),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r;return O}function Eu(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r?(re=Gn(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();K!==r?(re=Pi(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r)}return O}function Gn(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(it.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ue)),re===r&&(re=null),re!==r?(de=is(),de!==r?(Je=Pi(),Je!==r?(xt=O,K=x(re,de,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function is(){var O;return t.substr(Y,2)===w?(O=w,Y+=2):(O=r,yt===0&&wt(b)),O===r&&(t.substr(Y,2)===y?(O=y,Y+=2):(O=r,yt===0&&wt(F)),O===r&&(t.charCodeAt(Y)===62?(O=z,Y++):(O=r,yt===0&&wt(X)),O===r&&(t.substr(Y,3)===$?(O=$,Y+=3):(O=r,yt===0&&wt(oe)),O===r&&(t.substr(Y,2)===xe?(O=xe,Y+=2):(O=r,yt===0&&wt(Te)),O===r&&(t.charCodeAt(Y)===60?(O=lt,Y++):(O=r,yt===0&&wt(Ct))))))),O}function Pi(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(re=WA(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function WA(){var O,K,re;if(O=Y,K=[],re=Cf(),re!==r)for(;re!==r;)K.push(re),re=Cf();else K=r;return K!==r&&(xt=O,K=qt(K)),O=K,O}function Cf(){var O,K;return O=Y,K=mn(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=Gg(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=qg(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=ss(),K!==r&&(xt=O,K=ir(K)),O=K))),O}function mn(){var O,K,re,de;return O=Y,t.substr(Y,2)===Pt?(K=Pt,Y+=2):(K=r,yt===0&&wt(gn)),K!==r?(re=yn(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,yt===0&&wt(Ir)),de!==r?(xt=O,K=Or(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function Gg(){var O,K,re,de;return O=Y,t.charCodeAt(Y)===39?(K=Pr,Y++):(K=r,yt===0&&wt(Ir)),K!==r?(re=wf(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,yt===0&&wt(Ir)),de!==r?(xt=O,K=Or(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function qg(){var O,K,re,de;if(O=Y,t.substr(Y,2)===on?(K=on,Y+=2):(K=r,yt===0&&wt(ai)),K!==r&&(xt=O,K=Io()),O=K,O===r)if(O=Y,t.charCodeAt(Y)===34?(K=rs,Y++):(K=r,yt===0&&wt($s)),K!==r){for(re=[],de=Pl();de!==r;)re.push(de),de=Pl();re!==r?(t.charCodeAt(Y)===34?(de=rs,Y++):(de=r,yt===0&&wt($s)),de!==r?(xt=O,K=Co(re),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function ss(){var O,K,re;if(O=Y,K=[],re=Po(),re!==r)for(;re!==r;)K.push(re),re=Po();else K=r;return K!==r&&(xt=O,K=Co(K)),O=K,O}function Pl(){var O,K;return O=Y,K=Zr(),K!==r&&(xt=O,K=ji(K)),O=K,O===r&&(O=Y,K=bh(),K!==r&&(xt=O,K=eo(K)),O=K,O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=wo(K)),O=K,O===r&&(O=Y,K=Bf(),K!==r&&(xt=O,K=QA(K)),O=K))),O}function Po(){var O,K;return O=Y,K=Zr(),K!==r&&(xt=O,K=Af(K)),O=K,O===r&&(O=Y,K=bh(),K!==r&&(xt=O,K=dh(K)),O=K,O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=mh(K)),O=K,O===r&&(O=Y,K=Sy(),K!==r&&(xt=O,K=to(K)),O=K,O===r&&(O=Y,K=Dh(),K!==r&&(xt=O,K=QA(K)),O=K)))),O}function wf(){var O,K,re;for(O=Y,K=[],jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts));re!==r;)K.push(re),jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts));return K!==r&&(xt=O,K=ro(K)),O=K,O}function Bf(){var O,K,re;if(O=Y,K=[],re=xl(),re===r&&(ou.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(au))),re!==r)for(;re!==r;)K.push(re),re=xl(),re===r&&(ou.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(au)));else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function xl(){var O,K,re;return O=Y,t.substr(Y,2)===lu?(K=lu,Y+=2):(K=r,yt===0&&wt(TA)),K!==r&&(xt=O,K=RA()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(FA.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(gr)),re!==r?(xt=O,K=Bo(re),O=K):(Y=O,O=r)):(Y=O,O=r)),O}function yn(){var O,K,re;for(O=Y,K=[],re=xo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts)));re!==r;)K.push(re),re=xo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts)));return K!==r&&(xt=O,K=ro(K)),O=K,O}function xo(){var O,K,re;return O=Y,t.substr(Y,2)===Me?(K=Me,Y+=2):(K=r,yt===0&&wt(cu)),K!==r&&(xt=O,K=Cr()),O=K,O===r&&(O=Y,t.substr(Y,2)===pf?(K=pf,Y+=2):(K=r,yt===0&&wt(NA)),K!==r&&(xt=O,K=OA()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(uu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(fu)),re!==r?(xt=O,K=oc(),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===ve?(K=ve,Y+=2):(K=r,yt===0&&wt(Nt)),K!==r&&(xt=O,K=ac()),O=K,O===r&&(O=Y,t.substr(Y,2)===Oi?(K=Oi,Y+=2):(K=r,yt===0&&wt(no)),K!==r&&(xt=O,K=Rt()),O=K,O===r&&(O=Y,t.substr(Y,2)===xn?(K=xn,Y+=2):(K=r,yt===0&&wt(la)),K!==r&&(xt=O,K=Gi()),O=K,O===r&&(O=Y,t.substr(Y,2)===Li?(K=Li,Y+=2):(K=r,yt===0&&wt(Na)),K!==r&&(xt=O,K=dn()),O=K,O===r&&(O=Y,t.substr(Y,2)===Kn?(K=Kn,Y+=2):(K=r,yt===0&&wt(Au)),K!==r&&(xt=O,K=yh()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(Oa.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(La)),re!==r?(xt=O,K=Bo(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Iu()))))))))),O}function Iu(){var O,K,re,de,Je,At,dr,vr,Un,mi,Cs,JA;return O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(re=pa(),re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===$e?(K=$e,Y+=2):(K=r,yt===0&&wt(Ua)),K!==r?(re=Y,de=Y,Je=pa(),Je!==r?(At=Fs(),At!==r?(Je=[Je,At],de=Je):(Y=de,de=r)):(Y=de,de=r),de===r&&(de=pa()),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===hf?(K=hf,Y+=2):(K=r,yt===0&&wt(lc)),K!==r?(re=Y,de=Y,Je=Fs(),Je!==r?(At=Fs(),At!==r?(dr=Fs(),dr!==r?(vr=Fs(),vr!==r?(Je=[Je,At,dr,vr],de=Je):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===wn?(K=wn,Y+=2):(K=r,yt===0&&wt(ca)),K!==r?(re=Y,de=Y,Je=Fs(),Je!==r?(At=Fs(),At!==r?(dr=Fs(),dr!==r?(vr=Fs(),vr!==r?(Un=Fs(),Un!==r?(mi=Fs(),mi!==r?(Cs=Fs(),Cs!==r?(JA=Fs(),JA!==r?(Je=[Je,At,dr,vr,Un,mi,Cs,JA],de=Je):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=LA(re),O=K):(Y=O,O=r)):(Y=O,O=r)))),O}function pa(){var O;return MA.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(ua)),O}function Fs(){var O;return Bl.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(Mt)),O}function Dh(){var O,K,re,de,Je;if(O=Y,K=[],re=Y,t.charCodeAt(Y)===92?(de=oa,Y++):(de=r,yt===0&&wt(aa)),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===fa?(de=fa,Y+=2):(de=r,yt===0&&wt(Ha)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,yt++,Je=Dy(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r))),re!==r)for(;re!==r;)K.push(re),re=Y,t.charCodeAt(Y)===92?(de=oa,Y++):(de=r,yt===0&&wt(aa)),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===fa?(de=fa,Y+=2):(de=r,yt===0&&wt(Ha)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,yt++,Je=Dy(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r)));else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function YA(){var O,K,re,de,Je,At;if(O=Y,t.charCodeAt(Y)===45?(K=cc,Y++):(K=r,yt===0&&wt(pu)),K===r&&(t.charCodeAt(Y)===43?(K=uc,Y++):(K=r,yt===0&&wt(ja))),K===r&&(K=null),K!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue));else re=r;if(re!==r)if(t.charCodeAt(Y)===46?(de=Mi,Y++):(de=r,yt===0&&wt(Is)),de!==r){if(Je=[],it.test(t.charAt(Y))?(At=t.charAt(Y),Y++):(At=r,yt===0&&wt(Ue)),At!==r)for(;At!==r;)Je.push(At),it.test(t.charAt(Y))?(At=t.charAt(Y),Y++):(At=r,yt===0&&wt(Ue));else Je=r;Je!==r?(xt=O,K=vl(K,re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;if(O===r){if(O=Y,t.charCodeAt(Y)===45?(K=cc,Y++):(K=r,yt===0&&wt(pu)),K===r&&(t.charCodeAt(Y)===43?(K=uc,Y++):(K=r,yt===0&&wt(ja))),K===r&&(K=null),K!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue));else re=r;re!==r?(xt=O,K=gf(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;if(O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=fc(K)),O=K,O===r&&(O=Y,K=pc(),K!==r&&(xt=O,K=wi(K)),O=K,O===r)))if(O=Y,t.charCodeAt(Y)===40?(K=ye,Y++):(K=r,yt===0&&wt(Ae)),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=io(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(t.charCodeAt(Y)===41?(At=se,Y++):(At=r,yt===0&&wt(Z)),At!==r?(xt=O,K=Qn(de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r}return O}function vf(){var O,K,re,de,Je,At,dr,vr;if(O=Y,K=YA(),K!==r){for(re=[],de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===42?(At=Ac,Y++):(At=r,yt===0&&wt(Ke)),At===r&&(t.charCodeAt(Y)===47?(At=st,Y++):(At=r,yt===0&&wt(St))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=YA(),vr!==r?(xt=de,Je=lr(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===42?(At=Ac,Y++):(At=r,yt===0&&wt(Ke)),At===r&&(t.charCodeAt(Y)===47?(At=st,Y++):(At=r,yt===0&&wt(St))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=YA(),vr!==r?(xt=de,Je=lr(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,K=te(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;return O}function io(){var O,K,re,de,Je,At,dr,vr;if(O=Y,K=vf(),K!==r){for(re=[],de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===43?(At=uc,Y++):(At=r,yt===0&&wt(ja)),At===r&&(t.charCodeAt(Y)===45?(At=cc,Y++):(At=r,yt===0&&wt(pu))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=vf(),vr!==r?(xt=de,Je=Ee(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===43?(At=uc,Y++):(At=r,yt===0&&wt(ja)),At===r&&(t.charCodeAt(Y)===45?(At=cc,Y++):(At=r,yt===0&&wt(pu))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=vf(),vr!==r?(xt=de,Je=Ee(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,K=te(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;return O}function Zr(){var O,K,re,de,Je,At;if(O=Y,t.substr(Y,3)===Oe?(K=Oe,Y+=3):(K=r,yt===0&&wt(dt)),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=io(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(t.substr(Y,2)===Et?(At=Et,Y+=2):(At=r,yt===0&&wt(bt)),At!==r?(xt=O,K=tr(de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;return O}function bh(){var O,K,re,de;return O=Y,t.substr(Y,2)===An?(K=An,Y+=2):(K=r,yt===0&&wt(li)),K!==r?(re=Aa(),re!==r?(t.charCodeAt(Y)===41?(de=se,Y++):(de=r,yt===0&&wt(Z)),de!==r?(xt=O,K=qi(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function VA(){var O,K,re,de,Je,At;return O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,2)===my?(de=my,Y+=2):(de=r,yt===0&&wt(Z1)),de!==r?(Je=Rs(),Je!==r?(t.charCodeAt(Y)===125?(At=j,Y++):(At=r,yt===0&&wt(rt)),At!==r?(xt=O,K=vo(re,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,3)===yy?(de=yy,Y+=3):(de=r,yt===0&&wt(Eh)),de!==r?(xt=O,K=$1(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,2)===So?(de=So,Y+=2):(de=r,yt===0&&wt(Ih)),de!==r?(Je=Rs(),Je!==r?(t.charCodeAt(Y)===125?(At=j,Y++):(At=r,yt===0&&wt(rt)),At!==r?(xt=O,K=Ch(re,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,3)===hu?(de=hu,Y+=3):(de=r,yt===0&&wt(wh)),de!==r?(xt=O,K=Fg(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.charCodeAt(Y)===125?(de=j,Y++):(de=r,yt===0&&wt(rt)),de!==r?(xt=O,K=Ng(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.charCodeAt(Y)===36?(K=Og,Y++):(K=r,yt===0&&wt(Ey)),K!==r?(re=pc(),re!==r?(xt=O,K=Ng(re),O=K):(Y=O,O=r)):(Y=O,O=r)))))),O}function Sy(){var O,K,re;return O=Y,K=Wg(),K!==r?(xt=Y,re=df(K),re?re=void 0:re=r,re!==r?(xt=O,K=Do(K),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Wg(){var O,K,re,de,Je;if(O=Y,K=[],re=Y,de=Y,yt++,Je=xh(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re!==r)for(;re!==r;)K.push(re),re=Y,de=Y,yt++,Je=xh(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r);else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function Ph(){var O,K,re;if(O=Y,K=[],Sl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Bh)),re!==r)for(;re!==r;)K.push(re),Sl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Bh));else K=r;return K!==r&&(xt=O,K=Lg()),O=K,O}function pc(){var O,K,re;if(O=Y,K=[],Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(bl)),re!==r)for(;re!==r;)K.push(re),Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(bl));else K=r;return K!==r&&(xt=O,K=Lg()),O=K,O}function Dy(){var O;return Iy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(UA)),O}function xh(){var O;return Cy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(wy)),O}function kt(){var O,K;if(O=[],_A.test(t.charAt(Y))?(K=t.charAt(Y),Y++):(K=r,yt===0&&wt(HA)),K!==r)for(;K!==r;)O.push(K),_A.test(t.charAt(Y))?(K=t.charAt(Y),Y++):(K=r,yt===0&&wt(HA));else O=r;return O}if(gu=a(),gu!==r&&Y===t.length)return gu;throw gu!==r&&Y!1}){try{return(0,Eee.parse)(t,e)}catch(r){throw r.location&&(r.message=r.message.replace(/(\.)?$/,` (line ${r.location.start.line}, column ${r.location.start.column})$1`)),r}}function fE(t,{endSemicolon:e=!1}={}){return t.map(({command:r,type:s},a)=>`${fx(r)}${s===";"?a!==t.length-1||e?";":"":" &"}`).join(" ")}function fx(t){return`${AE(t.chain)}${t.then?` ${HU(t.then)}`:""}`}function HU(t){return`${t.type} ${fx(t.line)}`}function AE(t){return`${GU(t)}${t.then?` ${jU(t.then)}`:""}`}function jU(t){return`${t.type} ${AE(t.chain)}`}function GU(t){switch(t.type){case"command":return`${t.envs.length>0?`${t.envs.map(e=>cx(e)).join(" ")} `:""}${t.args.map(e=>qU(e)).join(" ")}`;case"subshell":return`(${fE(t.subshell)})${t.args.length>0?` ${t.args.map(e=>H2(e)).join(" ")}`:""}`;case"group":return`{ ${fE(t.group,{endSemicolon:!0})} }${t.args.length>0?` ${t.args.map(e=>H2(e)).join(" ")}`:""}`;case"envs":return t.envs.map(e=>cx(e)).join(" ");default:throw new Error(`Unsupported command type: "${t.type}"`)}}function cx(t){return`${t.name}=${t.args[0]?vd(t.args[0]):""}`}function qU(t){switch(t.type){case"redirection":return H2(t);case"argument":return vd(t);default:throw new Error(`Unsupported argument type: "${t.type}"`)}}function H2(t){return`${t.subtype} ${t.args.map(e=>vd(e)).join(" ")}`}function vd(t){return t.segments.map(e=>WU(e)).join("")}function WU(t){let e=(s,a)=>a?`"${s}"`:s,r=s=>s===""?"''":s.match(/[()}<>$|&;"'\n\t ]/)?s.match(/['\t\p{C}]/u)?s.match(/'/)?`"${s.replace(/["$\t\p{C}]/u,U5e)}"`:`$'${s.replace(/[\t\p{C}]/u,Cee)}'`:`'${s}'`:s;switch(t.type){case"text":return r(t.text);case"glob":return t.pattern;case"shell":return e(`$(${fE(t.shell)})`,t.quoted);case"variable":return e(typeof t.defaultValue>"u"?typeof t.alternativeValue>"u"?`\${${t.name}}`:t.alternativeValue.length===0?`\${${t.name}:+}`:`\${${t.name}:+${t.alternativeValue.map(s=>vd(s)).join(" ")}}`:t.defaultValue.length===0?`\${${t.name}:-}`:`\${${t.name}:-${t.defaultValue.map(s=>vd(s)).join(" ")}}`,t.quoted);case"arithmetic":return`$(( ${Ax(t.arithmetic)} ))`;default:throw new Error(`Unsupported argument segment type: "${t.type}"`)}}function Ax(t){let e=a=>{switch(a){case"addition":return"+";case"subtraction":return"-";case"multiplication":return"*";case"division":return"/";default:throw new Error(`Can't extract operator from arithmetic expression of type "${a}"`)}},r=(a,n)=>n?`( ${a} )`:a,s=a=>r(Ax(a),!["number","variable"].includes(a.type));switch(t.type){case"number":return String(t.value);case"variable":return t.name;default:return`${s(t.left)} ${e(t.type)} ${s(t.right)}`}}var Eee,Iee,M5e,Cee,U5e,wee=Xe(()=>{Eee=ut(yee());Iee=new Map([["\f","\\f"],[` +`,"\\n"],["\r","\\r"],[" ","\\t"],["\v","\\v"],["\0","\\0"]]),M5e=new Map([["\\","\\\\"],["$","\\$"],['"','\\"'],...Array.from(Iee,([t,e])=>[t,`"$'${e}'"`])]),Cee=t=>Iee.get(t)??`\\x${t.charCodeAt(0).toString(16).padStart(2,"0")}`,U5e=t=>M5e.get(t)??`"$'${Cee(t)}'"`});var vee=_((eQt,Bee)=>{"use strict";function _5e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Sd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Sd)}_5e(Sd,Error);Sd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;Cue&&(ue=W,le=[]),le.push(Ue))}function rt(Ue,x){return new Sd(Ue,null,null,x)}function Fe(Ue,x,w){return new Sd(Sd.buildMessage(Ue,x),Ue,x,w)}function Ne(){var Ue,x,w,b;return Ue=W,x=Pe(),x!==r?(t.charCodeAt(W)===47?(w=n,W++):(w=r,me===0&&j(c)),w!==r?(b=Pe(),b!==r?(ee=Ue,x=f(x,b),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=Pe(),x!==r&&(ee=Ue,x=p(x)),Ue=x),Ue}function Pe(){var Ue,x,w,b;return Ue=W,x=Ve(),x!==r?(t.charCodeAt(W)===64?(w=h,W++):(w=r,me===0&&j(E)),w!==r?(b=it(),b!==r?(ee=Ue,x=C(x,b),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=Ve(),x!==r&&(ee=Ue,x=S(x)),Ue=x),Ue}function Ve(){var Ue,x,w,b,y;return Ue=W,t.charCodeAt(W)===64?(x=h,W++):(x=r,me===0&&j(E)),x!==r?(w=ke(),w!==r?(t.charCodeAt(W)===47?(b=n,W++):(b=r,me===0&&j(c)),b!==r?(y=ke(),y!==r?(ee=Ue,x=P(),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=ke(),x!==r&&(ee=Ue,x=P()),Ue=x),Ue}function ke(){var Ue,x,w;if(Ue=W,x=[],I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R)),w!==r)for(;w!==r;)x.push(w),I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R));else x=r;return x!==r&&(ee=Ue,x=P()),Ue=x,Ue}function it(){var Ue,x,w;if(Ue=W,x=[],N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U)),w!==r)for(;w!==r;)x.push(w),N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U));else x=r;return x!==r&&(ee=Ue,x=P()),Ue=x,Ue}if(pe=a(),pe!==r&&W===t.length)return pe;throw pe!==r&&W{See=ut(vee())});var bd=_((rQt,Dd)=>{"use strict";function bee(t){return typeof t>"u"||t===null}function j5e(t){return typeof t=="object"&&t!==null}function G5e(t){return Array.isArray(t)?t:bee(t)?[]:[t]}function q5e(t,e){var r,s,a,n;if(e)for(n=Object.keys(e),r=0,s=n.length;r{"use strict";function j2(t,e){Error.call(this),this.name="YAMLException",this.reason=t,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}j2.prototype=Object.create(Error.prototype);j2.prototype.constructor=j2;j2.prototype.toString=function(e){var r=this.name+": ";return r+=this.reason||"(unknown reason)",!e&&this.mark&&(r+=" "+this.mark.toString()),r};Pee.exports=j2});var Qee=_((iQt,kee)=>{"use strict";var xee=bd();function YU(t,e,r,s,a){this.name=t,this.buffer=e,this.position=r,this.line=s,this.column=a}YU.prototype.getSnippet=function(e,r){var s,a,n,c,f;if(!this.buffer)return null;for(e=e||4,r=r||75,s="",a=this.position;a>0&&`\0\r +\x85\u2028\u2029`.indexOf(this.buffer.charAt(a-1))===-1;)if(a-=1,this.position-a>r/2-1){s=" ... ",a+=5;break}for(n="",c=this.position;cr/2-1){n=" ... ",c-=5;break}return f=this.buffer.slice(a,c),xee.repeat(" ",e)+s+f+n+` +`+xee.repeat(" ",e+this.position-a+s.length)+"^"};YU.prototype.toString=function(e){var r,s="";return this.name&&(s+='in "'+this.name+'" '),s+="at line "+(this.line+1)+", column "+(this.column+1),e||(r=this.getSnippet(),r&&(s+=`: +`+r)),s};kee.exports=YU});var Ss=_((sQt,Ree)=>{"use strict";var Tee=pE(),V5e=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],J5e=["scalar","sequence","mapping"];function K5e(t){var e={};return t!==null&&Object.keys(t).forEach(function(r){t[r].forEach(function(s){e[String(s)]=r})}),e}function z5e(t,e){if(e=e||{},Object.keys(e).forEach(function(r){if(V5e.indexOf(r)===-1)throw new Tee('Unknown option "'+r+'" is met in definition of "'+t+'" YAML type.')}),this.tag=t,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(r){return r},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=K5e(e.styleAliases||null),J5e.indexOf(this.kind)===-1)throw new Tee('Unknown kind "'+this.kind+'" is specified for "'+t+'" YAML type.')}Ree.exports=z5e});var Pd=_((oQt,Nee)=>{"use strict";var Fee=bd(),gx=pE(),X5e=Ss();function VU(t,e,r){var s=[];return t.include.forEach(function(a){r=VU(a,e,r)}),t[e].forEach(function(a){r.forEach(function(n,c){n.tag===a.tag&&n.kind===a.kind&&s.push(c)}),r.push(a)}),r.filter(function(a,n){return s.indexOf(n)===-1})}function Z5e(){var t={scalar:{},sequence:{},mapping:{},fallback:{}},e,r;function s(a){t[a.kind][a.tag]=t.fallback[a.tag]=a}for(e=0,r=arguments.length;e{"use strict";var $5e=Ss();Oee.exports=new $5e("tag:yaml.org,2002:str",{kind:"scalar",construct:function(t){return t!==null?t:""}})});var Uee=_((lQt,Mee)=>{"use strict";var eqe=Ss();Mee.exports=new eqe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(t){return t!==null?t:[]}})});var Hee=_((cQt,_ee)=>{"use strict";var tqe=Ss();_ee.exports=new tqe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(t){return t!==null?t:{}}})});var dx=_((uQt,jee)=>{"use strict";var rqe=Pd();jee.exports=new rqe({explicit:[Lee(),Uee(),Hee()]})});var qee=_((fQt,Gee)=>{"use strict";var nqe=Ss();function iqe(t){if(t===null)return!0;var e=t.length;return e===1&&t==="~"||e===4&&(t==="null"||t==="Null"||t==="NULL")}function sqe(){return null}function oqe(t){return t===null}Gee.exports=new nqe("tag:yaml.org,2002:null",{kind:"scalar",resolve:iqe,construct:sqe,predicate:oqe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var Yee=_((AQt,Wee)=>{"use strict";var aqe=Ss();function lqe(t){if(t===null)return!1;var e=t.length;return e===4&&(t==="true"||t==="True"||t==="TRUE")||e===5&&(t==="false"||t==="False"||t==="FALSE")}function cqe(t){return t==="true"||t==="True"||t==="TRUE"}function uqe(t){return Object.prototype.toString.call(t)==="[object Boolean]"}Wee.exports=new aqe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:lqe,construct:cqe,predicate:uqe,represent:{lowercase:function(t){return t?"true":"false"},uppercase:function(t){return t?"TRUE":"FALSE"},camelcase:function(t){return t?"True":"False"}},defaultStyle:"lowercase"})});var Jee=_((pQt,Vee)=>{"use strict";var fqe=bd(),Aqe=Ss();function pqe(t){return 48<=t&&t<=57||65<=t&&t<=70||97<=t&&t<=102}function hqe(t){return 48<=t&&t<=55}function gqe(t){return 48<=t&&t<=57}function dqe(t){if(t===null)return!1;var e=t.length,r=0,s=!1,a;if(!e)return!1;if(a=t[r],(a==="-"||a==="+")&&(a=t[++r]),a==="0"){if(r+1===e)return!0;if(a=t[++r],a==="b"){for(r++;r=0?"0b"+t.toString(2):"-0b"+t.toString(2).slice(1)},octal:function(t){return t>=0?"0"+t.toString(8):"-0"+t.toString(8).slice(1)},decimal:function(t){return t.toString(10)},hexadecimal:function(t){return t>=0?"0x"+t.toString(16).toUpperCase():"-0x"+t.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var Xee=_((hQt,zee)=>{"use strict";var Kee=bd(),Eqe=Ss(),Iqe=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function Cqe(t){return!(t===null||!Iqe.test(t)||t[t.length-1]==="_")}function wqe(t){var e,r,s,a;return e=t.replace(/_/g,"").toLowerCase(),r=e[0]==="-"?-1:1,a=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(n){a.unshift(parseFloat(n,10))}),e=0,s=1,a.forEach(function(n){e+=n*s,s*=60}),r*e):r*parseFloat(e,10)}var Bqe=/^[-+]?[0-9]+e/;function vqe(t,e){var r;if(isNaN(t))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===t)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===t)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(Kee.isNegativeZero(t))return"-0.0";return r=t.toString(10),Bqe.test(r)?r.replace("e",".e"):r}function Sqe(t){return Object.prototype.toString.call(t)==="[object Number]"&&(t%1!==0||Kee.isNegativeZero(t))}zee.exports=new Eqe("tag:yaml.org,2002:float",{kind:"scalar",resolve:Cqe,construct:wqe,predicate:Sqe,represent:vqe,defaultStyle:"lowercase"})});var JU=_((gQt,Zee)=>{"use strict";var Dqe=Pd();Zee.exports=new Dqe({include:[dx()],implicit:[qee(),Yee(),Jee(),Xee()]})});var KU=_((dQt,$ee)=>{"use strict";var bqe=Pd();$ee.exports=new bqe({include:[JU()]})});var nte=_((mQt,rte)=>{"use strict";var Pqe=Ss(),ete=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),tte=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function xqe(t){return t===null?!1:ete.exec(t)!==null||tte.exec(t)!==null}function kqe(t){var e,r,s,a,n,c,f,p=0,h=null,E,C,S;if(e=ete.exec(t),e===null&&(e=tte.exec(t)),e===null)throw new Error("Date resolve error");if(r=+e[1],s=+e[2]-1,a=+e[3],!e[4])return new Date(Date.UTC(r,s,a));if(n=+e[4],c=+e[5],f=+e[6],e[7]){for(p=e[7].slice(0,3);p.length<3;)p+="0";p=+p}return e[9]&&(E=+e[10],C=+(e[11]||0),h=(E*60+C)*6e4,e[9]==="-"&&(h=-h)),S=new Date(Date.UTC(r,s,a,n,c,f,p)),h&&S.setTime(S.getTime()-h),S}function Qqe(t){return t.toISOString()}rte.exports=new Pqe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:xqe,construct:kqe,instanceOf:Date,represent:Qqe})});var ste=_((yQt,ite)=>{"use strict";var Tqe=Ss();function Rqe(t){return t==="<<"||t===null}ite.exports=new Tqe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:Rqe})});var lte=_((EQt,ate)=>{"use strict";var xd;try{ote=Ie,xd=ote("buffer").Buffer}catch{}var ote,Fqe=Ss(),zU=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= +\r`;function Nqe(t){if(t===null)return!1;var e,r,s=0,a=t.length,n=zU;for(r=0;r64)){if(e<0)return!1;s+=6}return s%8===0}function Oqe(t){var e,r,s=t.replace(/[\r\n=]/g,""),a=s.length,n=zU,c=0,f=[];for(e=0;e>16&255),f.push(c>>8&255),f.push(c&255)),c=c<<6|n.indexOf(s.charAt(e));return r=a%4*6,r===0?(f.push(c>>16&255),f.push(c>>8&255),f.push(c&255)):r===18?(f.push(c>>10&255),f.push(c>>2&255)):r===12&&f.push(c>>4&255),xd?xd.from?xd.from(f):new xd(f):f}function Lqe(t){var e="",r=0,s,a,n=t.length,c=zU;for(s=0;s>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]),r=(r<<8)+t[s];return a=n%3,a===0?(e+=c[r>>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]):a===2?(e+=c[r>>10&63],e+=c[r>>4&63],e+=c[r<<2&63],e+=c[64]):a===1&&(e+=c[r>>2&63],e+=c[r<<4&63],e+=c[64],e+=c[64]),e}function Mqe(t){return xd&&xd.isBuffer(t)}ate.exports=new Fqe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:Nqe,construct:Oqe,predicate:Mqe,represent:Lqe})});var ute=_((CQt,cte)=>{"use strict";var Uqe=Ss(),_qe=Object.prototype.hasOwnProperty,Hqe=Object.prototype.toString;function jqe(t){if(t===null)return!0;var e=[],r,s,a,n,c,f=t;for(r=0,s=f.length;r{"use strict";var qqe=Ss(),Wqe=Object.prototype.toString;function Yqe(t){if(t===null)return!0;var e,r,s,a,n,c=t;for(n=new Array(c.length),e=0,r=c.length;e{"use strict";var Jqe=Ss(),Kqe=Object.prototype.hasOwnProperty;function zqe(t){if(t===null)return!0;var e,r=t;for(e in r)if(Kqe.call(r,e)&&r[e]!==null)return!1;return!0}function Xqe(t){return t!==null?t:{}}pte.exports=new Jqe("tag:yaml.org,2002:set",{kind:"mapping",resolve:zqe,construct:Xqe})});var gE=_((vQt,gte)=>{"use strict";var Zqe=Pd();gte.exports=new Zqe({include:[KU()],implicit:[nte(),ste()],explicit:[lte(),ute(),Ate(),hte()]})});var mte=_((SQt,dte)=>{"use strict";var $qe=Ss();function e9e(){return!0}function t9e(){}function r9e(){return""}function n9e(t){return typeof t>"u"}dte.exports=new $qe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:e9e,construct:t9e,predicate:n9e,represent:r9e})});var Ete=_((DQt,yte)=>{"use strict";var i9e=Ss();function s9e(t){if(t===null||t.length===0)return!1;var e=t,r=/\/([gim]*)$/.exec(t),s="";return!(e[0]==="/"&&(r&&(s=r[1]),s.length>3||e[e.length-s.length-1]!=="/"))}function o9e(t){var e=t,r=/\/([gim]*)$/.exec(t),s="";return e[0]==="/"&&(r&&(s=r[1]),e=e.slice(1,e.length-s.length-1)),new RegExp(e,s)}function a9e(t){var e="/"+t.source+"/";return t.global&&(e+="g"),t.multiline&&(e+="m"),t.ignoreCase&&(e+="i"),e}function l9e(t){return Object.prototype.toString.call(t)==="[object RegExp]"}yte.exports=new i9e("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:s9e,construct:o9e,predicate:l9e,represent:a9e})});var wte=_((bQt,Cte)=>{"use strict";var mx;try{Ite=Ie,mx=Ite("esprima")}catch{typeof window<"u"&&(mx=window.esprima)}var Ite,c9e=Ss();function u9e(t){if(t===null)return!1;try{var e="("+t+")",r=mx.parse(e,{range:!0});return!(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function f9e(t){var e="("+t+")",r=mx.parse(e,{range:!0}),s=[],a;if(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return r.body[0].expression.params.forEach(function(n){s.push(n.name)}),a=r.body[0].expression.body.range,r.body[0].expression.body.type==="BlockStatement"?new Function(s,e.slice(a[0]+1,a[1]-1)):new Function(s,"return "+e.slice(a[0],a[1]))}function A9e(t){return t.toString()}function p9e(t){return Object.prototype.toString.call(t)==="[object Function]"}Cte.exports=new c9e("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:u9e,construct:f9e,predicate:p9e,represent:A9e})});var G2=_((xQt,vte)=>{"use strict";var Bte=Pd();vte.exports=Bte.DEFAULT=new Bte({include:[gE()],explicit:[mte(),Ete(),wte()]})});var Gte=_((kQt,q2)=>{"use strict";var Ip=bd(),Qte=pE(),h9e=Qee(),Tte=gE(),g9e=G2(),i0=Object.prototype.hasOwnProperty,yx=1,Rte=2,Fte=3,Ex=4,XU=1,d9e=2,Ste=3,m9e=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,y9e=/[\x85\u2028\u2029]/,E9e=/[,\[\]\{\}]/,Nte=/^(?:!|!!|![a-z\-]+!)$/i,Ote=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function Dte(t){return Object.prototype.toString.call(t)}function jf(t){return t===10||t===13}function Qd(t){return t===9||t===32}function rl(t){return t===9||t===32||t===10||t===13}function dE(t){return t===44||t===91||t===93||t===123||t===125}function I9e(t){var e;return 48<=t&&t<=57?t-48:(e=t|32,97<=e&&e<=102?e-97+10:-1)}function C9e(t){return t===120?2:t===117?4:t===85?8:0}function w9e(t){return 48<=t&&t<=57?t-48:-1}function bte(t){return t===48?"\0":t===97?"\x07":t===98?"\b":t===116||t===9?" ":t===110?` +`:t===118?"\v":t===102?"\f":t===114?"\r":t===101?"\x1B":t===32?" ":t===34?'"':t===47?"/":t===92?"\\":t===78?"\x85":t===95?"\xA0":t===76?"\u2028":t===80?"\u2029":""}function B9e(t){return t<=65535?String.fromCharCode(t):String.fromCharCode((t-65536>>10)+55296,(t-65536&1023)+56320)}var Lte=new Array(256),Mte=new Array(256);for(kd=0;kd<256;kd++)Lte[kd]=bte(kd)?1:0,Mte[kd]=bte(kd);var kd;function v9e(t,e){this.input=t,this.filename=e.filename||null,this.schema=e.schema||g9e,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=t.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function Ute(t,e){return new Qte(e,new h9e(t.filename,t.input,t.position,t.line,t.position-t.lineStart))}function Rr(t,e){throw Ute(t,e)}function Ix(t,e){t.onWarning&&t.onWarning.call(null,Ute(t,e))}var Pte={YAML:function(e,r,s){var a,n,c;e.version!==null&&Rr(e,"duplication of %YAML directive"),s.length!==1&&Rr(e,"YAML directive accepts exactly one argument"),a=/^([0-9]+)\.([0-9]+)$/.exec(s[0]),a===null&&Rr(e,"ill-formed argument of the YAML directive"),n=parseInt(a[1],10),c=parseInt(a[2],10),n!==1&&Rr(e,"unacceptable YAML version of the document"),e.version=s[0],e.checkLineBreaks=c<2,c!==1&&c!==2&&Ix(e,"unsupported YAML version of the document")},TAG:function(e,r,s){var a,n;s.length!==2&&Rr(e,"TAG directive accepts exactly two arguments"),a=s[0],n=s[1],Nte.test(a)||Rr(e,"ill-formed tag handle (first argument) of the TAG directive"),i0.call(e.tagMap,a)&&Rr(e,'there is a previously declared suffix for "'+a+'" tag handle'),Ote.test(n)||Rr(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[a]=n}};function n0(t,e,r,s){var a,n,c,f;if(e1&&(t.result+=Ip.repeat(` +`,e-1))}function S9e(t,e,r){var s,a,n,c,f,p,h,E,C=t.kind,S=t.result,P;if(P=t.input.charCodeAt(t.position),rl(P)||dE(P)||P===35||P===38||P===42||P===33||P===124||P===62||P===39||P===34||P===37||P===64||P===96||(P===63||P===45)&&(a=t.input.charCodeAt(t.position+1),rl(a)||r&&dE(a)))return!1;for(t.kind="scalar",t.result="",n=c=t.position,f=!1;P!==0;){if(P===58){if(a=t.input.charCodeAt(t.position+1),rl(a)||r&&dE(a))break}else if(P===35){if(s=t.input.charCodeAt(t.position-1),rl(s))break}else{if(t.position===t.lineStart&&Cx(t)||r&&dE(P))break;if(jf(P))if(p=t.line,h=t.lineStart,E=t.lineIndent,as(t,!1,-1),t.lineIndent>=e){f=!0,P=t.input.charCodeAt(t.position);continue}else{t.position=c,t.line=p,t.lineStart=h,t.lineIndent=E;break}}f&&(n0(t,n,c,!1),$U(t,t.line-p),n=c=t.position,f=!1),Qd(P)||(c=t.position+1),P=t.input.charCodeAt(++t.position)}return n0(t,n,c,!1),t.result?!0:(t.kind=C,t.result=S,!1)}function D9e(t,e){var r,s,a;if(r=t.input.charCodeAt(t.position),r!==39)return!1;for(t.kind="scalar",t.result="",t.position++,s=a=t.position;(r=t.input.charCodeAt(t.position))!==0;)if(r===39)if(n0(t,s,t.position,!0),r=t.input.charCodeAt(++t.position),r===39)s=t.position,t.position++,a=t.position;else return!0;else jf(r)?(n0(t,s,a,!0),$U(t,as(t,!1,e)),s=a=t.position):t.position===t.lineStart&&Cx(t)?Rr(t,"unexpected end of the document within a single quoted scalar"):(t.position++,a=t.position);Rr(t,"unexpected end of the stream within a single quoted scalar")}function b9e(t,e){var r,s,a,n,c,f;if(f=t.input.charCodeAt(t.position),f!==34)return!1;for(t.kind="scalar",t.result="",t.position++,r=s=t.position;(f=t.input.charCodeAt(t.position))!==0;){if(f===34)return n0(t,r,t.position,!0),t.position++,!0;if(f===92){if(n0(t,r,t.position,!0),f=t.input.charCodeAt(++t.position),jf(f))as(t,!1,e);else if(f<256&&Lte[f])t.result+=Mte[f],t.position++;else if((c=C9e(f))>0){for(a=c,n=0;a>0;a--)f=t.input.charCodeAt(++t.position),(c=I9e(f))>=0?n=(n<<4)+c:Rr(t,"expected hexadecimal character");t.result+=B9e(n),t.position++}else Rr(t,"unknown escape sequence");r=s=t.position}else jf(f)?(n0(t,r,s,!0),$U(t,as(t,!1,e)),r=s=t.position):t.position===t.lineStart&&Cx(t)?Rr(t,"unexpected end of the document within a double quoted scalar"):(t.position++,s=t.position)}Rr(t,"unexpected end of the stream within a double quoted scalar")}function P9e(t,e){var r=!0,s,a=t.tag,n,c=t.anchor,f,p,h,E,C,S={},P,I,R,N;if(N=t.input.charCodeAt(t.position),N===91)p=93,C=!1,n=[];else if(N===123)p=125,C=!0,n={};else return!1;for(t.anchor!==null&&(t.anchorMap[t.anchor]=n),N=t.input.charCodeAt(++t.position);N!==0;){if(as(t,!0,e),N=t.input.charCodeAt(t.position),N===p)return t.position++,t.tag=a,t.anchor=c,t.kind=C?"mapping":"sequence",t.result=n,!0;r||Rr(t,"missed comma between flow collection entries"),I=P=R=null,h=E=!1,N===63&&(f=t.input.charCodeAt(t.position+1),rl(f)&&(h=E=!0,t.position++,as(t,!0,e))),s=t.line,yE(t,e,yx,!1,!0),I=t.tag,P=t.result,as(t,!0,e),N=t.input.charCodeAt(t.position),(E||t.line===s)&&N===58&&(h=!0,N=t.input.charCodeAt(++t.position),as(t,!0,e),yE(t,e,yx,!1,!0),R=t.result),C?mE(t,n,S,I,P,R):h?n.push(mE(t,null,S,I,P,R)):n.push(P),as(t,!0,e),N=t.input.charCodeAt(t.position),N===44?(r=!0,N=t.input.charCodeAt(++t.position)):r=!1}Rr(t,"unexpected end of the stream within a flow collection")}function x9e(t,e){var r,s,a=XU,n=!1,c=!1,f=e,p=0,h=!1,E,C;if(C=t.input.charCodeAt(t.position),C===124)s=!1;else if(C===62)s=!0;else return!1;for(t.kind="scalar",t.result="";C!==0;)if(C=t.input.charCodeAt(++t.position),C===43||C===45)XU===a?a=C===43?Ste:d9e:Rr(t,"repeat of a chomping mode identifier");else if((E=w9e(C))>=0)E===0?Rr(t,"bad explicit indentation width of a block scalar; it cannot be less than one"):c?Rr(t,"repeat of an indentation width identifier"):(f=e+E-1,c=!0);else break;if(Qd(C)){do C=t.input.charCodeAt(++t.position);while(Qd(C));if(C===35)do C=t.input.charCodeAt(++t.position);while(!jf(C)&&C!==0)}for(;C!==0;){for(ZU(t),t.lineIndent=0,C=t.input.charCodeAt(t.position);(!c||t.lineIndentf&&(f=t.lineIndent),jf(C)){p++;continue}if(t.lineIndente)&&p!==0)Rr(t,"bad indentation of a sequence entry");else if(t.lineIndente)&&(yE(t,e,Ex,!0,a)&&(I?S=t.result:P=t.result),I||(mE(t,h,E,C,S,P,n,c),C=S=P=null),as(t,!0,-1),N=t.input.charCodeAt(t.position)),t.lineIndent>e&&N!==0)Rr(t,"bad indentation of a mapping entry");else if(t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndent tag; it should be "scalar", not "'+t.kind+'"'),C=0,S=t.implicitTypes.length;C tag; it should be "'+P.kind+'", not "'+t.kind+'"'),P.resolve(t.result)?(t.result=P.construct(t.result),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):Rr(t,"cannot resolve a node with !<"+t.tag+"> explicit tag")):Rr(t,"unknown tag !<"+t.tag+">");return t.listener!==null&&t.listener("close",t),t.tag!==null||t.anchor!==null||E}function F9e(t){var e=t.position,r,s,a,n=!1,c;for(t.version=null,t.checkLineBreaks=t.legacy,t.tagMap={},t.anchorMap={};(c=t.input.charCodeAt(t.position))!==0&&(as(t,!0,-1),c=t.input.charCodeAt(t.position),!(t.lineIndent>0||c!==37));){for(n=!0,c=t.input.charCodeAt(++t.position),r=t.position;c!==0&&!rl(c);)c=t.input.charCodeAt(++t.position);for(s=t.input.slice(r,t.position),a=[],s.length<1&&Rr(t,"directive name must not be less than one character in length");c!==0;){for(;Qd(c);)c=t.input.charCodeAt(++t.position);if(c===35){do c=t.input.charCodeAt(++t.position);while(c!==0&&!jf(c));break}if(jf(c))break;for(r=t.position;c!==0&&!rl(c);)c=t.input.charCodeAt(++t.position);a.push(t.input.slice(r,t.position))}c!==0&&ZU(t),i0.call(Pte,s)?Pte[s](t,s,a):Ix(t,'unknown document directive "'+s+'"')}if(as(t,!0,-1),t.lineIndent===0&&t.input.charCodeAt(t.position)===45&&t.input.charCodeAt(t.position+1)===45&&t.input.charCodeAt(t.position+2)===45?(t.position+=3,as(t,!0,-1)):n&&Rr(t,"directives end mark is expected"),yE(t,t.lineIndent-1,Ex,!1,!0),as(t,!0,-1),t.checkLineBreaks&&y9e.test(t.input.slice(e,t.position))&&Ix(t,"non-ASCII line breaks are interpreted as content"),t.documents.push(t.result),t.position===t.lineStart&&Cx(t)){t.input.charCodeAt(t.position)===46&&(t.position+=3,as(t,!0,-1));return}if(t.position"u"&&(r=e,e=null);var s=_te(t,r);if(typeof e!="function")return s;for(var a=0,n=s.length;a"u"&&(r=e,e=null),Hte(t,e,Ip.extend({schema:Tte},r))}function O9e(t,e){return jte(t,Ip.extend({schema:Tte},e))}q2.exports.loadAll=Hte;q2.exports.load=jte;q2.exports.safeLoadAll=N9e;q2.exports.safeLoad=O9e});var Are=_((QQt,n_)=>{"use strict";var Y2=bd(),V2=pE(),L9e=G2(),M9e=gE(),Xte=Object.prototype.toString,Zte=Object.prototype.hasOwnProperty,U9e=9,W2=10,_9e=13,H9e=32,j9e=33,G9e=34,$te=35,q9e=37,W9e=38,Y9e=39,V9e=42,ere=44,J9e=45,tre=58,K9e=61,z9e=62,X9e=63,Z9e=64,rre=91,nre=93,$9e=96,ire=123,eWe=124,sre=125,_o={};_o[0]="\\0";_o[7]="\\a";_o[8]="\\b";_o[9]="\\t";_o[10]="\\n";_o[11]="\\v";_o[12]="\\f";_o[13]="\\r";_o[27]="\\e";_o[34]='\\"';_o[92]="\\\\";_o[133]="\\N";_o[160]="\\_";_o[8232]="\\L";_o[8233]="\\P";var tWe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function rWe(t,e){var r,s,a,n,c,f,p;if(e===null)return{};for(r={},s=Object.keys(e),a=0,n=s.length;a0?t.charCodeAt(n-1):null,S=S&&Yte(c,f)}else{for(n=0;ns&&t[C+1]!==" ",C=n);else if(!EE(c))return wx;f=n>0?t.charCodeAt(n-1):null,S=S&&Yte(c,f)}h=h||E&&n-C-1>s&&t[C+1]!==" "}return!p&&!h?S&&!a(t)?are:lre:r>9&&ore(t)?wx:h?ure:cre}function lWe(t,e,r,s){t.dump=function(){if(e.length===0)return"''";if(!t.noCompatMode&&tWe.indexOf(e)!==-1)return"'"+e+"'";var a=t.indent*Math.max(1,r),n=t.lineWidth===-1?-1:Math.max(Math.min(t.lineWidth,40),t.lineWidth-a),c=s||t.flowLevel>-1&&r>=t.flowLevel;function f(p){return iWe(t,p)}switch(aWe(e,c,t.indent,n,f)){case are:return e;case lre:return"'"+e.replace(/'/g,"''")+"'";case cre:return"|"+Vte(e,t.indent)+Jte(Wte(e,a));case ure:return">"+Vte(e,t.indent)+Jte(Wte(cWe(e,n),a));case wx:return'"'+uWe(e,n)+'"';default:throw new V2("impossible error: invalid scalar style")}}()}function Vte(t,e){var r=ore(t)?String(e):"",s=t[t.length-1]===` +`,a=s&&(t[t.length-2]===` +`||t===` +`),n=a?"+":s?"":"-";return r+n+` +`}function Jte(t){return t[t.length-1]===` +`?t.slice(0,-1):t}function cWe(t,e){for(var r=/(\n+)([^\n]*)/g,s=function(){var h=t.indexOf(` +`);return h=h!==-1?h:t.length,r.lastIndex=h,Kte(t.slice(0,h),e)}(),a=t[0]===` +`||t[0]===" ",n,c;c=r.exec(t);){var f=c[1],p=c[2];n=p[0]===" ",s+=f+(!a&&!n&&p!==""?` +`:"")+Kte(p,e),a=n}return s}function Kte(t,e){if(t===""||t[0]===" ")return t;for(var r=/ [^ ]/g,s,a=0,n,c=0,f=0,p="";s=r.exec(t);)f=s.index,f-a>e&&(n=c>a?c:f,p+=` +`+t.slice(a,n),a=n+1),c=f;return p+=` +`,t.length-a>e&&c>a?p+=t.slice(a,c)+` +`+t.slice(c+1):p+=t.slice(a),p.slice(1)}function uWe(t){for(var e="",r,s,a,n=0;n=55296&&r<=56319&&(s=t.charCodeAt(n+1),s>=56320&&s<=57343)){e+=qte((r-55296)*1024+s-56320+65536),n++;continue}a=_o[r],e+=!a&&EE(r)?t[n]:a||qte(r)}return e}function fWe(t,e,r){var s="",a=t.tag,n,c;for(n=0,c=r.length;n1024&&(E+="? "),E+=t.dump+(t.condenseFlow?'"':"")+":"+(t.condenseFlow?"":" "),Td(t,e,h,!1,!1)&&(E+=t.dump,s+=E));t.tag=a,t.dump="{"+s+"}"}function hWe(t,e,r,s){var a="",n=t.tag,c=Object.keys(r),f,p,h,E,C,S;if(t.sortKeys===!0)c.sort();else if(typeof t.sortKeys=="function")c.sort(t.sortKeys);else if(t.sortKeys)throw new V2("sortKeys must be a boolean or a function");for(f=0,p=c.length;f1024,C&&(t.dump&&W2===t.dump.charCodeAt(0)?S+="?":S+="? "),S+=t.dump,C&&(S+=e_(t,e)),Td(t,e+1,E,!0,C)&&(t.dump&&W2===t.dump.charCodeAt(0)?S+=":":S+=": ",S+=t.dump,a+=S));t.tag=n,t.dump=a||"{}"}function zte(t,e,r){var s,a,n,c,f,p;for(a=r?t.explicitTypes:t.implicitTypes,n=0,c=a.length;n tag resolver accepts not "'+p+'" style');t.dump=s}return!0}return!1}function Td(t,e,r,s,a,n){t.tag=null,t.dump=r,zte(t,r,!1)||zte(t,r,!0);var c=Xte.call(t.dump);s&&(s=t.flowLevel<0||t.flowLevel>e);var f=c==="[object Object]"||c==="[object Array]",p,h;if(f&&(p=t.duplicates.indexOf(r),h=p!==-1),(t.tag!==null&&t.tag!=="?"||h||t.indent!==2&&e>0)&&(a=!1),h&&t.usedDuplicates[p])t.dump="*ref_"+p;else{if(f&&h&&!t.usedDuplicates[p]&&(t.usedDuplicates[p]=!0),c==="[object Object]")s&&Object.keys(t.dump).length!==0?(hWe(t,e,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(pWe(t,e,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump));else if(c==="[object Array]"){var E=t.noArrayIndent&&e>0?e-1:e;s&&t.dump.length!==0?(AWe(t,E,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(fWe(t,E,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump))}else if(c==="[object String]")t.tag!=="?"&&lWe(t,t.dump,e,n);else{if(t.skipInvalid)return!1;throw new V2("unacceptable kind of an object to dump "+c)}t.tag!==null&&t.tag!=="?"&&(t.dump="!<"+t.tag+"> "+t.dump)}return!0}function gWe(t,e){var r=[],s=[],a,n;for(t_(t,r,s),a=0,n=s.length;a{"use strict";var Bx=Gte(),pre=Are();function vx(t){return function(){throw new Error("Function "+t+" is deprecated and cannot be used.")}}Wi.exports.Type=Ss();Wi.exports.Schema=Pd();Wi.exports.FAILSAFE_SCHEMA=dx();Wi.exports.JSON_SCHEMA=JU();Wi.exports.CORE_SCHEMA=KU();Wi.exports.DEFAULT_SAFE_SCHEMA=gE();Wi.exports.DEFAULT_FULL_SCHEMA=G2();Wi.exports.load=Bx.load;Wi.exports.loadAll=Bx.loadAll;Wi.exports.safeLoad=Bx.safeLoad;Wi.exports.safeLoadAll=Bx.safeLoadAll;Wi.exports.dump=pre.dump;Wi.exports.safeDump=pre.safeDump;Wi.exports.YAMLException=pE();Wi.exports.MINIMAL_SCHEMA=dx();Wi.exports.SAFE_SCHEMA=gE();Wi.exports.DEFAULT_SCHEMA=G2();Wi.exports.scan=vx("scan");Wi.exports.parse=vx("parse");Wi.exports.compose=vx("compose");Wi.exports.addConstructor=vx("addConstructor")});var dre=_((RQt,gre)=>{"use strict";var mWe=hre();gre.exports=mWe});var yre=_((FQt,mre)=>{"use strict";function yWe(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Rd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Rd)}yWe(Rd,Error);Rd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C({[dt]:Oe})))},ue=function(te){return te},le=function(te){return te},me=Oa("correct indentation"),pe=" ",Be=dn(" ",!1),Ce=function(te){return te.length===lr*St},g=function(te){return te.length===(lr+1)*St},we=function(){return lr++,!0},ye=function(){return lr--,!0},Ae=function(){return la()},se=Oa("pseudostring"),Z=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,De=Kn(["\r",` +`," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),Re=/^[^\r\n\t ,\][{}:#"']/,mt=Kn(["\r",` +`," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),j=function(){return la().replace(/^ *| *$/g,"")},rt="--",Fe=dn("--",!1),Ne=/^[a-zA-Z\/0-9]/,Pe=Kn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),Ve=/^[^\r\n\t :,]/,ke=Kn(["\r",` +`," "," ",":",","],!0,!1),it="null",Ue=dn("null",!1),x=function(){return null},w="true",b=dn("true",!1),y=function(){return!0},F="false",z=dn("false",!1),X=function(){return!1},$=Oa("string"),oe='"',xe=dn('"',!1),Te=function(){return""},lt=function(te){return te},Ct=function(te){return te.join("")},qt=/^[^"\\\0-\x1F\x7F]/,ir=Kn(['"',"\\",["\0",""],"\x7F"],!0,!1),Pt='\\"',gn=dn('\\"',!1),Pr=function(){return'"'},Ir="\\\\",Or=dn("\\\\",!1),on=function(){return"\\"},ai="\\/",Io=dn("\\/",!1),rs=function(){return"/"},$s="\\b",Co=dn("\\b",!1),ji=function(){return"\b"},eo="\\f",wo=dn("\\f",!1),QA=function(){return"\f"},Af="\\n",dh=dn("\\n",!1),mh=function(){return` +`},to="\\r",jn=dn("\\r",!1),Ts=function(){return"\r"},ro="\\t",ou=dn("\\t",!1),au=function(){return" "},lu="\\u",TA=dn("\\u",!1),RA=function(te,Ee,Oe,dt){return String.fromCharCode(parseInt(`0x${te}${Ee}${Oe}${dt}`))},oa=/^[0-9a-fA-F]/,aa=Kn([["0","9"],["a","f"],["A","F"]],!1,!1),FA=Oa("blank space"),gr=/^[ \t]/,Bo=Kn([" "," "],!1,!1),Me=Oa("white space"),cu=/^[ \t\n\r]/,Cr=Kn([" "," ",` +`,"\r"],!1,!1),pf=`\r +`,NA=dn(`\r +`,!1),OA=` +`,uu=dn(` +`,!1),fu="\r",oc=dn("\r",!1),ve=0,Nt=0,ac=[{line:1,column:1}],Oi=0,no=[],Rt=0,xn;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function la(){return t.substring(Nt,ve)}function Gi(){return Ma(Nt,ve)}function Li(te,Ee){throw Ee=Ee!==void 0?Ee:Ma(Nt,ve),hf([Oa(te)],t.substring(Nt,ve),Ee)}function Na(te,Ee){throw Ee=Ee!==void 0?Ee:Ma(Nt,ve),Ua(te,Ee)}function dn(te,Ee){return{type:"literal",text:te,ignoreCase:Ee}}function Kn(te,Ee,Oe){return{type:"class",parts:te,inverted:Ee,ignoreCase:Oe}}function Au(){return{type:"any"}}function yh(){return{type:"end"}}function Oa(te){return{type:"other",description:te}}function La(te){var Ee=ac[te],Oe;if(Ee)return Ee;for(Oe=te-1;!ac[Oe];)Oe--;for(Ee=ac[Oe],Ee={line:Ee.line,column:Ee.column};OeOi&&(Oi=ve,no=[]),no.push(te))}function Ua(te,Ee){return new Rd(te,null,null,Ee)}function hf(te,Ee,Oe){return new Rd(Rd.buildMessage(te,Ee),te,Ee,Oe)}function lc(){var te;return te=LA(),te}function wn(){var te,Ee,Oe;for(te=ve,Ee=[],Oe=ca();Oe!==r;)Ee.push(Oe),Oe=ca();return Ee!==r&&(Nt=te,Ee=n(Ee)),te=Ee,te}function ca(){var te,Ee,Oe,dt,Et;return te=ve,Ee=Bl(),Ee!==r?(t.charCodeAt(ve)===45?(Oe=c,ve++):(Oe=r,Rt===0&&$e(f)),Oe!==r?(dt=Qn(),dt!==r?(Et=ua(),Et!==r?(Nt=te,Ee=p(Et),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te}function LA(){var te,Ee,Oe;for(te=ve,Ee=[],Oe=MA();Oe!==r;)Ee.push(Oe),Oe=MA();return Ee!==r&&(Nt=te,Ee=h(Ee)),te=Ee,te}function MA(){var te,Ee,Oe,dt,Et,bt,tr,An,li;if(te=ve,Ee=Qn(),Ee===r&&(Ee=null),Ee!==r){if(Oe=ve,t.charCodeAt(ve)===35?(dt=E,ve++):(dt=r,Rt===0&&$e(C)),dt!==r){if(Et=[],bt=ve,tr=ve,Rt++,An=st(),Rt--,An===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(An=t.charAt(ve),ve++):(An=r,Rt===0&&$e(S)),An!==r?(tr=[tr,An],bt=tr):(ve=bt,bt=r)):(ve=bt,bt=r),bt!==r)for(;bt!==r;)Et.push(bt),bt=ve,tr=ve,Rt++,An=st(),Rt--,An===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(An=t.charAt(ve),ve++):(An=r,Rt===0&&$e(S)),An!==r?(tr=[tr,An],bt=tr):(ve=bt,bt=r)):(ve=bt,bt=r);else Et=r;Et!==r?(dt=[dt,Et],Oe=dt):(ve=Oe,Oe=r)}else ve=Oe,Oe=r;if(Oe===r&&(Oe=null),Oe!==r){if(dt=[],Et=Ke(),Et!==r)for(;Et!==r;)dt.push(Et),Et=Ke();else dt=r;dt!==r?(Nt=te,Ee=P(),te=Ee):(ve=te,te=r)}else ve=te,te=r}else ve=te,te=r;if(te===r&&(te=ve,Ee=Bl(),Ee!==r?(Oe=Ha(),Oe!==r?(dt=Qn(),dt===r&&(dt=null),dt!==r?(t.charCodeAt(ve)===58?(Et=I,ve++):(Et=r,Rt===0&&$e(R)),Et!==r?(bt=Qn(),bt===r&&(bt=null),bt!==r?(tr=ua(),tr!==r?(Nt=te,Ee=N(Oe,tr),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,Ee=Bl(),Ee!==r?(Oe=ns(),Oe!==r?(dt=Qn(),dt===r&&(dt=null),dt!==r?(t.charCodeAt(ve)===58?(Et=I,ve++):(Et=r,Rt===0&&$e(R)),Et!==r?(bt=Qn(),bt===r&&(bt=null),bt!==r?(tr=ua(),tr!==r?(Nt=te,Ee=N(Oe,tr),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r))){if(te=ve,Ee=Bl(),Ee!==r)if(Oe=ns(),Oe!==r)if(dt=Qn(),dt!==r)if(Et=pu(),Et!==r){if(bt=[],tr=Ke(),tr!==r)for(;tr!==r;)bt.push(tr),tr=Ke();else bt=r;bt!==r?(Nt=te,Ee=N(Oe,Et),te=Ee):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r;else ve=te,te=r;else ve=te,te=r;if(te===r)if(te=ve,Ee=Bl(),Ee!==r)if(Oe=ns(),Oe!==r){if(dt=[],Et=ve,bt=Qn(),bt===r&&(bt=null),bt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&$e(W)),tr!==r?(An=Qn(),An===r&&(An=null),An!==r?(li=ns(),li!==r?(Nt=Et,bt=ee(Oe,li),Et=bt):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r),Et!==r)for(;Et!==r;)dt.push(Et),Et=ve,bt=Qn(),bt===r&&(bt=null),bt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&$e(W)),tr!==r?(An=Qn(),An===r&&(An=null),An!==r?(li=ns(),li!==r?(Nt=Et,bt=ee(Oe,li),Et=bt):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r);else dt=r;dt!==r?(Et=Qn(),Et===r&&(Et=null),Et!==r?(t.charCodeAt(ve)===58?(bt=I,ve++):(bt=r,Rt===0&&$e(R)),bt!==r?(tr=Qn(),tr===r&&(tr=null),tr!==r?(An=ua(),An!==r?(Nt=te,Ee=ie(Oe,dt,An),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r}return te}function ua(){var te,Ee,Oe,dt,Et,bt,tr;if(te=ve,Ee=ve,Rt++,Oe=ve,dt=st(),dt!==r?(Et=Mt(),Et!==r?(t.charCodeAt(ve)===45?(bt=c,ve++):(bt=r,Rt===0&&$e(f)),bt!==r?(tr=Qn(),tr!==r?(dt=[dt,Et,bt,tr],Oe=dt):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r),Rt--,Oe!==r?(ve=Ee,Ee=void 0):Ee=r,Ee!==r?(Oe=Ke(),Oe!==r?(dt=kn(),dt!==r?(Et=wn(),Et!==r?(bt=fa(),bt!==r?(Nt=te,Ee=ue(Et),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,Ee=st(),Ee!==r?(Oe=kn(),Oe!==r?(dt=LA(),dt!==r?(Et=fa(),Et!==r?(Nt=te,Ee=ue(dt),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r))if(te=ve,Ee=cc(),Ee!==r){if(Oe=[],dt=Ke(),dt!==r)for(;dt!==r;)Oe.push(dt),dt=Ke();else Oe=r;Oe!==r?(Nt=te,Ee=le(Ee),te=Ee):(ve=te,te=r)}else ve=te,te=r;return te}function Bl(){var te,Ee,Oe;for(Rt++,te=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));return Ee!==r?(Nt=ve,Oe=Ce(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)):(ve=te,te=r),Rt--,te===r&&(Ee=r,Rt===0&&$e(me)),te}function Mt(){var te,Ee,Oe;for(te=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));return Ee!==r?(Nt=ve,Oe=g(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)):(ve=te,te=r),te}function kn(){var te;return Nt=ve,te=we(),te?te=void 0:te=r,te}function fa(){var te;return Nt=ve,te=ye(),te?te=void 0:te=r,te}function Ha(){var te;return te=vl(),te===r&&(te=uc()),te}function ns(){var te,Ee,Oe;if(te=vl(),te===r){if(te=ve,Ee=[],Oe=ja(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=ja();else Ee=r;Ee!==r&&(Nt=te,Ee=Ae()),te=Ee}return te}function cc(){var te;return te=Mi(),te===r&&(te=Is(),te===r&&(te=vl(),te===r&&(te=uc()))),te}function pu(){var te;return te=Mi(),te===r&&(te=vl(),te===r&&(te=ja())),te}function uc(){var te,Ee,Oe,dt,Et,bt;if(Rt++,te=ve,Z.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(De)),Ee!==r){for(Oe=[],dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(Re.test(t.charAt(ve))?(bt=t.charAt(ve),ve++):(bt=r,Rt===0&&$e(mt)),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);dt!==r;)Oe.push(dt),dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(Re.test(t.charAt(ve))?(bt=t.charAt(ve),ve++):(bt=r,Rt===0&&$e(mt)),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);Oe!==r?(Nt=te,Ee=j(),te=Ee):(ve=te,te=r)}else ve=te,te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(se)),te}function ja(){var te,Ee,Oe,dt,Et;if(te=ve,t.substr(ve,2)===rt?(Ee=rt,ve+=2):(Ee=r,Rt===0&&$e(Fe)),Ee===r&&(Ee=null),Ee!==r)if(Ne.test(t.charAt(ve))?(Oe=t.charAt(ve),ve++):(Oe=r,Rt===0&&$e(Pe)),Oe!==r){for(dt=[],Ve.test(t.charAt(ve))?(Et=t.charAt(ve),ve++):(Et=r,Rt===0&&$e(ke));Et!==r;)dt.push(Et),Ve.test(t.charAt(ve))?(Et=t.charAt(ve),ve++):(Et=r,Rt===0&&$e(ke));dt!==r?(Nt=te,Ee=j(),te=Ee):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r;return te}function Mi(){var te,Ee;return te=ve,t.substr(ve,4)===it?(Ee=it,ve+=4):(Ee=r,Rt===0&&$e(Ue)),Ee!==r&&(Nt=te,Ee=x()),te=Ee,te}function Is(){var te,Ee;return te=ve,t.substr(ve,4)===w?(Ee=w,ve+=4):(Ee=r,Rt===0&&$e(b)),Ee!==r&&(Nt=te,Ee=y()),te=Ee,te===r&&(te=ve,t.substr(ve,5)===F?(Ee=F,ve+=5):(Ee=r,Rt===0&&$e(z)),Ee!==r&&(Nt=te,Ee=X()),te=Ee),te}function vl(){var te,Ee,Oe,dt;return Rt++,te=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&$e(xe)),Ee!==r?(t.charCodeAt(ve)===34?(Oe=oe,ve++):(Oe=r,Rt===0&&$e(xe)),Oe!==r?(Nt=te,Ee=Te(),te=Ee):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&$e(xe)),Ee!==r?(Oe=gf(),Oe!==r?(t.charCodeAt(ve)===34?(dt=oe,ve++):(dt=r,Rt===0&&$e(xe)),dt!==r?(Nt=te,Ee=lt(Oe),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)),Rt--,te===r&&(Ee=r,Rt===0&&$e($)),te}function gf(){var te,Ee,Oe;if(te=ve,Ee=[],Oe=fc(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=fc();else Ee=r;return Ee!==r&&(Nt=te,Ee=Ct(Ee)),te=Ee,te}function fc(){var te,Ee,Oe,dt,Et,bt;return qt.test(t.charAt(ve))?(te=t.charAt(ve),ve++):(te=r,Rt===0&&$e(ir)),te===r&&(te=ve,t.substr(ve,2)===Pt?(Ee=Pt,ve+=2):(Ee=r,Rt===0&&$e(gn)),Ee!==r&&(Nt=te,Ee=Pr()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===Ir?(Ee=Ir,ve+=2):(Ee=r,Rt===0&&$e(Or)),Ee!==r&&(Nt=te,Ee=on()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===ai?(Ee=ai,ve+=2):(Ee=r,Rt===0&&$e(Io)),Ee!==r&&(Nt=te,Ee=rs()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===$s?(Ee=$s,ve+=2):(Ee=r,Rt===0&&$e(Co)),Ee!==r&&(Nt=te,Ee=ji()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===eo?(Ee=eo,ve+=2):(Ee=r,Rt===0&&$e(wo)),Ee!==r&&(Nt=te,Ee=QA()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===Af?(Ee=Af,ve+=2):(Ee=r,Rt===0&&$e(dh)),Ee!==r&&(Nt=te,Ee=mh()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===to?(Ee=to,ve+=2):(Ee=r,Rt===0&&$e(jn)),Ee!==r&&(Nt=te,Ee=Ts()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===ro?(Ee=ro,ve+=2):(Ee=r,Rt===0&&$e(ou)),Ee!==r&&(Nt=te,Ee=au()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===lu?(Ee=lu,ve+=2):(Ee=r,Rt===0&&$e(TA)),Ee!==r?(Oe=wi(),Oe!==r?(dt=wi(),dt!==r?(Et=wi(),Et!==r?(bt=wi(),bt!==r?(Nt=te,Ee=RA(Oe,dt,Et,bt),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)))))))))),te}function wi(){var te;return oa.test(t.charAt(ve))?(te=t.charAt(ve),ve++):(te=r,Rt===0&&$e(aa)),te}function Qn(){var te,Ee;if(Rt++,te=[],gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Bo)),Ee!==r)for(;Ee!==r;)te.push(Ee),gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Bo));else te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(FA)),te}function Ac(){var te,Ee;if(Rt++,te=[],cu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Cr)),Ee!==r)for(;Ee!==r;)te.push(Ee),cu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Cr));else te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(Me)),te}function Ke(){var te,Ee,Oe,dt,Et,bt;if(te=ve,Ee=st(),Ee!==r){for(Oe=[],dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(bt=st(),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);dt!==r;)Oe.push(dt),dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(bt=st(),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)}else ve=te,te=r;return te}function st(){var te;return t.substr(ve,2)===pf?(te=pf,ve+=2):(te=r,Rt===0&&$e(NA)),te===r&&(t.charCodeAt(ve)===10?(te=OA,ve++):(te=r,Rt===0&&$e(uu)),te===r&&(t.charCodeAt(ve)===13?(te=fu,ve++):(te=r,Rt===0&&$e(oc)))),te}let St=2,lr=0;if(xn=a(),xn!==r&&ve===t.length)return xn;throw xn!==r&&ve"u"?!0:typeof t=="object"&&t!==null&&!Array.isArray(t)?Object.keys(t).every(e=>wre(t[e])):!1}function i_(t,e,r){if(t===null)return`null +`;if(typeof t=="number"||typeof t=="boolean")return`${t.toString()} +`;if(typeof t=="string")return`${Ire(t)} +`;if(Array.isArray(t)){if(t.length===0)return`[] +`;let s=" ".repeat(e);return` +${t.map(n=>`${s}- ${i_(n,e+1,!1)}`).join("")}`}if(typeof t=="object"&&t){let[s,a]=t instanceof Sx?[t.data,!1]:[t,!0],n=" ".repeat(e),c=Object.keys(s);a&&c.sort((p,h)=>{let E=Ere.indexOf(p),C=Ere.indexOf(h);return E===-1&&C===-1?ph?1:0:E!==-1&&C===-1?-1:E===-1&&C!==-1?1:E-C});let f=c.filter(p=>!wre(s[p])).map((p,h)=>{let E=s[p],C=Ire(p),S=i_(E,e+1,!0),P=h>0||r?n:"",I=C.length>1024?`? ${C} +${P}:`:`${C}:`,R=S.startsWith(` +`)?S:` ${S}`;return`${P}${I}${R}`}).join(e===0?` +`:"")||` +`;return r?` +${f}`:`${f}`}throw new Error(`Unsupported value type (${t})`)}function nl(t){try{let e=i_(t,0,!1);return e!==` +`?e:""}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}function CWe(t){return t.endsWith(` +`)||(t+=` +`),(0,Cre.parse)(t)}function BWe(t){if(wWe.test(t))return CWe(t);let e=(0,Dx.safeLoad)(t,{schema:Dx.FAILSAFE_SCHEMA,json:!0});if(e==null)return{};if(typeof e!="object")throw new Error(`Expected an indexed object, got a ${typeof e} instead. Does your file follow Yaml's rules?`);if(Array.isArray(e))throw new Error("Expected an indexed object, got an array instead. Does your file follow Yaml's rules?");return e}function ls(t){return BWe(t)}var Dx,Cre,IWe,Ere,Sx,wWe,Bre=Xe(()=>{Dx=ut(dre()),Cre=ut(yre()),IWe=/^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/,Ere=["__metadata","version","resolution","dependencies","peerDependencies","dependenciesMeta","peerDependenciesMeta","binaries"],Sx=class{constructor(e){this.data=e}};nl.PreserveOrdering=Sx;wWe=/^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i});var J2={};Vt(J2,{parseResolution:()=>px,parseShell:()=>ux,parseSyml:()=>ls,stringifyArgument:()=>qU,stringifyArgumentSegment:()=>WU,stringifyArithmeticExpression:()=>Ax,stringifyCommand:()=>GU,stringifyCommandChain:()=>AE,stringifyCommandChainThen:()=>jU,stringifyCommandLine:()=>fx,stringifyCommandLineThen:()=>HU,stringifyEnvSegment:()=>cx,stringifyRedirectArgument:()=>H2,stringifyResolution:()=>hx,stringifyShell:()=>fE,stringifyShellLine:()=>fE,stringifySyml:()=>nl,stringifyValueArgument:()=>vd});var wc=Xe(()=>{wee();Dee();Bre()});var Sre=_((UQt,s_)=>{"use strict";var vWe=t=>{let e=!1,r=!1,s=!1;for(let a=0;a{if(!(typeof t=="string"||Array.isArray(t)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let r=a=>e.pascalCase?a.charAt(0).toUpperCase()+a.slice(1):a;return Array.isArray(t)?t=t.map(a=>a.trim()).filter(a=>a.length).join("-"):t=t.trim(),t.length===0?"":t.length===1?e.pascalCase?t.toUpperCase():t.toLowerCase():(t!==t.toLowerCase()&&(t=vWe(t)),t=t.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(a,n)=>n.toUpperCase()).replace(/\d+(\w|$)/g,a=>a.toUpperCase()),r(t))};s_.exports=vre;s_.exports.default=vre});var Dre=_((_Qt,SWe)=>{SWe.exports=[{name:"Agola CI",constant:"AGOLA",env:"AGOLA_GIT_REF",pr:"AGOLA_PULL_REQUEST_ID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"TF_BUILD",pr:{BUILD_REASON:"PullRequest"}},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codemagic",constant:"CODEMAGIC",env:"CM_BUILD_ID",pr:"CM_PULL_REQUEST"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"Earthly",constant:"EARTHLY",env:"EARTHLY_CI"},{name:"Expo Application Services",constant:"EAS",env:"EAS_BUILD"},{name:"Gerrit",constant:"GERRIT",env:"GERRIT_PROJECT"},{name:"Gitea Actions",constant:"GITEA_ACTIONS",env:"GITEA_ACTIONS"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Google Cloud Build",constant:"GOOGLE_CLOUD_BUILD",env:"BUILDER_OUTPUT"},{name:"Harness CI",constant:"HARNESS",env:"HARNESS_BUILD_ID"},{name:"Heroku",constant:"HEROKU",env:{env:"NODE",includes:"/app/.heroku/node/bin/node"}},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Prow",constant:"PROW",env:"PROW_JOB_ID"},{name:"ReleaseHub",constant:"RELEASEHUB",env:"RELEASE_BUILD_ID"},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Sourcehut",constant:"SOURCEHUT",env:{CI_NAME:"sourcehut"}},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vela",constant:"VELA",env:"VELA",pr:{VELA_PULL_REQUEST:"1"}},{name:"Vercel",constant:"VERCEL",env:{any:["NOW_BUILDER","VERCEL"]},pr:"VERCEL_GIT_PULL_REQUEST_ID"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"},{name:"Woodpecker",constant:"WOODPECKER",env:{CI:"woodpecker"},pr:{CI_BUILD_EVENT:"pull_request"}},{name:"Xcode Cloud",constant:"XCODE_CLOUD",env:"CI_XCODE_PROJECT",pr:"CI_PULL_REQUEST_NUMBER"},{name:"Xcode Server",constant:"XCODE_SERVER",env:"XCS"}]});var Fd=_(Ml=>{"use strict";var Pre=Dre(),Ds=process.env;Object.defineProperty(Ml,"_vendors",{value:Pre.map(function(t){return t.constant})});Ml.name=null;Ml.isPR=null;Pre.forEach(function(t){let r=(Array.isArray(t.env)?t.env:[t.env]).every(function(s){return bre(s)});if(Ml[t.constant]=r,!!r)switch(Ml.name=t.name,typeof t.pr){case"string":Ml.isPR=!!Ds[t.pr];break;case"object":"env"in t.pr?Ml.isPR=t.pr.env in Ds&&Ds[t.pr.env]!==t.pr.ne:"any"in t.pr?Ml.isPR=t.pr.any.some(function(s){return!!Ds[s]}):Ml.isPR=bre(t.pr);break;default:Ml.isPR=null}});Ml.isCI=!!(Ds.CI!=="false"&&(Ds.BUILD_ID||Ds.BUILD_NUMBER||Ds.CI||Ds.CI_APP_ID||Ds.CI_BUILD_ID||Ds.CI_BUILD_NUMBER||Ds.CI_NAME||Ds.CONTINUOUS_INTEGRATION||Ds.RUN_ID||Ml.name));function bre(t){return typeof t=="string"?!!Ds[t]:"env"in t?Ds[t.env]&&Ds[t.env].includes(t.includes):"any"in t?t.any.some(function(e){return!!Ds[e]}):Object.keys(t).every(function(e){return Ds[e]===t[e]})}});var ei,En,Nd,o_,bx,xre,a_,l_,Px=Xe(()=>{(function(t){t.StartOfInput="\0",t.EndOfInput="",t.EndOfPartialInput=""})(ei||(ei={}));(function(t){t[t.InitialNode=0]="InitialNode",t[t.SuccessNode=1]="SuccessNode",t[t.ErrorNode=2]="ErrorNode",t[t.CustomNode=3]="CustomNode"})(En||(En={}));Nd=-1,o_=/^(-h|--help)(?:=([0-9]+))?$/,bx=/^(--[a-z]+(?:-[a-z]+)*|-[a-zA-Z]+)$/,xre=/^-[a-zA-Z]{2,}$/,a_=/^([^=]+)=([\s\S]*)$/,l_=process.env.DEBUG_CLI==="1"});var nt,IE,xx,c_,kx=Xe(()=>{Px();nt=class extends Error{constructor(e){super(e),this.clipanion={type:"usage"},this.name="UsageError"}},IE=class extends Error{constructor(e,r){if(super(),this.input=e,this.candidates=r,this.clipanion={type:"none"},this.name="UnknownSyntaxError",this.candidates.length===0)this.message="Command not found, but we're not sure what's the alternative.";else if(this.candidates.every(s=>s.reason!==null&&s.reason===r[0].reason)){let[{reason:s}]=this.candidates;this.message=`${s} + +${this.candidates.map(({usage:a})=>`$ ${a}`).join(` +`)}`}else if(this.candidates.length===1){let[{usage:s}]=this.candidates;this.message=`Command not found; did you mean: + +$ ${s} +${c_(e)}`}else this.message=`Command not found; did you mean one of: + +${this.candidates.map(({usage:s},a)=>`${`${a}.`.padStart(4)} ${s}`).join(` +`)} + +${c_(e)}`}},xx=class extends Error{constructor(e,r){super(),this.input=e,this.usages=r,this.clipanion={type:"none"},this.name="AmbiguousSyntaxError",this.message=`Cannot find which to pick amongst the following alternatives: + +${this.usages.map((s,a)=>`${`${a}.`.padStart(4)} ${s}`).join(` +`)} + +${c_(e)}`}},c_=t=>`While running ${t.filter(e=>e!==ei.EndOfInput&&e!==ei.EndOfPartialInput).map(e=>{let r=JSON.stringify(e);return e.match(/\s/)||e.length===0||r!==`"${e}"`?r:e}).join(" ")}`});function DWe(t){let e=t.split(` +`),r=e.filter(a=>a.match(/\S/)),s=r.length>0?r.reduce((a,n)=>Math.min(a,n.length-n.trimStart().length),Number.MAX_VALUE):0;return e.map(a=>a.slice(s).trimRight()).join(` +`)}function Ho(t,{format:e,paragraphs:r}){return t=t.replace(/\r\n?/g,` +`),t=DWe(t),t=t.replace(/^\n+|\n+$/g,""),t=t.replace(/^(\s*)-([^\n]*?)\n+/gm,`$1-$2 + +`),t=t.replace(/\n(\n)?\n*/g,(s,a)=>a||" "),r&&(t=t.split(/\n/).map(s=>{let a=s.match(/^\s*[*-][\t ]+(.*)/);if(!a)return s.match(/(.{1,80})(?: |$)/g).join(` +`);let n=s.length-s.trimStart().length;return a[1].match(new RegExp(`(.{1,${78-n}})(?: |$)`,"g")).map((c,f)=>" ".repeat(n)+(f===0?"- ":" ")+c).join(` +`)}).join(` + +`)),t=t.replace(/(`+)((?:.|[\n])*?)\1/g,(s,a,n)=>e.code(a+n+a)),t=t.replace(/(\*\*)((?:.|[\n])*?)\1/g,(s,a,n)=>e.bold(a+n+a)),t?`${t} +`:""}var u_,kre,Qre,f_=Xe(()=>{u_=Array(80).fill("\u2501");for(let t=0;t<=24;++t)u_[u_.length-t]=`\x1B[38;5;${232+t}m\u2501`;kre={header:t=>`\x1B[1m\u2501\u2501\u2501 ${t}${t.length<75?` ${u_.slice(t.length+5).join("")}`:":"}\x1B[0m`,bold:t=>`\x1B[1m${t}\x1B[22m`,error:t=>`\x1B[31m\x1B[1m${t}\x1B[22m\x1B[39m`,code:t=>`\x1B[36m${t}\x1B[39m`},Qre={header:t=>t,bold:t=>t,error:t=>t,code:t=>t}});function ya(t){return{...t,[K2]:!0}}function Gf(t,e){return typeof t>"u"?[t,e]:typeof t=="object"&&t!==null&&!Array.isArray(t)?[void 0,t]:[t,e]}function Qx(t,{mergeName:e=!1}={}){let r=t.match(/^([^:]+): (.*)$/m);if(!r)return"validation failed";let[,s,a]=r;return e&&(a=a[0].toLowerCase()+a.slice(1)),a=s!=="."||!e?`${s.replace(/^\.(\[|$)/,"$1")}: ${a}`:`: ${a}`,a}function z2(t,e){return e.length===1?new nt(`${t}${Qx(e[0],{mergeName:!0})}`):new nt(`${t}: +${e.map(r=>` +- ${Qx(r)}`).join("")}`)}function Od(t,e,r){if(typeof r>"u")return e;let s=[],a=[],n=f=>{let p=e;return e=f,n.bind(null,p)};if(!r(e,{errors:s,coercions:a,coercion:n}))throw z2(`Invalid value for ${t}`,s);for(let[,f]of a)f();return e}var K2,Cp=Xe(()=>{kx();K2=Symbol("clipanion/isOption")});var Ea={};Vt(Ea,{KeyRelationship:()=>qf,TypeAssertionError:()=>o0,applyCascade:()=>$2,as:()=>WWe,assert:()=>jWe,assertWithErrors:()=>GWe,cascade:()=>Nx,fn:()=>YWe,hasAtLeastOneKey:()=>y_,hasExactLength:()=>Ore,hasForbiddenKeys:()=>fYe,hasKeyRelationship:()=>tB,hasMaxLength:()=>JWe,hasMinLength:()=>VWe,hasMutuallyExclusiveKeys:()=>AYe,hasRequiredKeys:()=>uYe,hasUniqueItems:()=>KWe,isArray:()=>Tx,isAtLeast:()=>d_,isAtMost:()=>ZWe,isBase64:()=>oYe,isBoolean:()=>FWe,isDate:()=>OWe,isDict:()=>UWe,isEnum:()=>fo,isHexColor:()=>sYe,isISO8601:()=>iYe,isInExclusiveRange:()=>eYe,isInInclusiveRange:()=>$We,isInstanceOf:()=>HWe,isInteger:()=>m_,isJSON:()=>aYe,isLiteral:()=>Rre,isLowerCase:()=>tYe,isMap:()=>MWe,isNegative:()=>zWe,isNullable:()=>cYe,isNumber:()=>h_,isObject:()=>Fre,isOneOf:()=>g_,isOptional:()=>lYe,isPartial:()=>_We,isPayload:()=>NWe,isPositive:()=>XWe,isRecord:()=>Fx,isSet:()=>LWe,isString:()=>wE,isTuple:()=>Rx,isUUID4:()=>nYe,isUnknown:()=>p_,isUpperCase:()=>rYe,makeTrait:()=>Nre,makeValidator:()=>Wr,matchesRegExp:()=>Z2,softAssert:()=>qWe});function ti(t){return t===null?"null":t===void 0?"undefined":t===""?"an empty string":typeof t=="symbol"?`<${t.toString()}>`:Array.isArray(t)?"an array":JSON.stringify(t)}function CE(t,e){if(t.length===0)return"nothing";if(t.length===1)return ti(t[0]);let r=t.slice(0,-1),s=t[t.length-1],a=t.length>2?`, ${e} `:` ${e} `;return`${r.map(n=>ti(n)).join(", ")}${a}${ti(s)}`}function s0(t,e){var r,s,a;return typeof e=="number"?`${(r=t?.p)!==null&&r!==void 0?r:"."}[${e}]`:bWe.test(e)?`${(s=t?.p)!==null&&s!==void 0?s:""}.${e}`:`${(a=t?.p)!==null&&a!==void 0?a:"."}[${JSON.stringify(e)}]`}function A_(t,e,r){return t===1?e:r}function mr({errors:t,p:e}={},r){return t?.push(`${e??"."}: ${r}`),!1}function TWe(t,e){return r=>{t[e]=r}}function Wf(t,e){return r=>{let s=t[e];return t[e]=r,Wf(t,e).bind(null,s)}}function X2(t,e,r){let s=()=>(t(r()),a),a=()=>(t(e),s);return s}function p_(){return Wr({test:(t,e)=>!0})}function Rre(t){return Wr({test:(e,r)=>e!==t?mr(r,`Expected ${ti(t)} (got ${ti(e)})`):!0})}function wE(){return Wr({test:(t,e)=>typeof t!="string"?mr(e,`Expected a string (got ${ti(t)})`):!0})}function fo(t){let e=Array.isArray(t)?t:Object.values(t),r=e.every(a=>typeof a=="string"||typeof a=="number"),s=new Set(e);return s.size===1?Rre([...s][0]):Wr({test:(a,n)=>s.has(a)?!0:r?mr(n,`Expected one of ${CE(e,"or")} (got ${ti(a)})`):mr(n,`Expected a valid enumeration value (got ${ti(a)})`)})}function FWe(){return Wr({test:(t,e)=>{var r;if(typeof t!="boolean"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s=RWe.get(t);if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a boolean (got ${ti(t)})`)}return!0}})}function h_(){return Wr({test:(t,e)=>{var r;if(typeof t!="number"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"){let a;try{a=JSON.parse(t)}catch{}if(typeof a=="number")if(JSON.stringify(a)===t)s=a;else return mr(e,`Received a number that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a number (got ${ti(t)})`)}return!0}})}function NWe(t){return Wr({test:(e,r)=>{var s;if(typeof r?.coercions>"u")return mr(r,"The isPayload predicate can only be used with coercion enabled");if(typeof r.coercion>"u")return mr(r,"Unbound coercion result");if(typeof e!="string")return mr(r,`Expected a string (got ${ti(e)})`);let a;try{a=JSON.parse(e)}catch{return mr(r,`Expected a JSON string (got ${ti(e)})`)}let n={value:a};return t(a,Object.assign(Object.assign({},r),{coercion:Wf(n,"value")}))?(r.coercions.push([(s=r.p)!==null&&s!==void 0?s:".",r.coercion.bind(null,n.value)]),!0):!1}})}function OWe(){return Wr({test:(t,e)=>{var r;if(!(t instanceof Date)){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"&&Tre.test(t))s=new Date(t);else{let a;if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{}typeof n=="number"&&(a=n)}else typeof t=="number"&&(a=t);if(typeof a<"u")if(Number.isSafeInteger(a)||!Number.isSafeInteger(a*1e3))s=new Date(a*1e3);else return mr(e,`Received a timestamp that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a date (got ${ti(t)})`)}return!0}})}function Tx(t,{delimiter:e}={}){return Wr({test:(r,s)=>{var a;let n=r;if(typeof r=="string"&&typeof e<"u"&&typeof s?.coercions<"u"){if(typeof s?.coercion>"u")return mr(s,"Unbound coercion result");r=r.split(e)}if(!Array.isArray(r))return mr(s,`Expected an array (got ${ti(r)})`);let c=!0;for(let f=0,p=r.length;f{var n,c;if(Object.getPrototypeOf(s).toString()==="[object Set]")if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f=[...s],p=[...s];if(!r(p,Object.assign(Object.assign({},a),{coercion:void 0})))return!1;let h=()=>p.some((E,C)=>E!==f[C])?new Set(p):s;return a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",X2(a.coercion,s,h)]),!0}else{let f=!0;for(let p of s)if(f=t(p,Object.assign({},a))&&f,!f&&a?.errors==null)break;return f}if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f={value:s};return r(s,Object.assign(Object.assign({},a),{coercion:Wf(f,"value")}))?(a.coercions.push([(c=a.p)!==null&&c!==void 0?c:".",X2(a.coercion,s,()=>new Set(f.value))]),!0):!1}return mr(a,`Expected a set (got ${ti(s)})`)}})}function MWe(t,e){let r=Tx(Rx([t,e])),s=Fx(e,{keys:t});return Wr({test:(a,n)=>{var c,f,p;if(Object.getPrototypeOf(a).toString()==="[object Map]")if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h=[...a],E=[...a];if(!r(E,Object.assign(Object.assign({},n),{coercion:void 0})))return!1;let C=()=>E.some((S,P)=>S[0]!==h[P][0]||S[1]!==h[P][1])?new Map(E):a;return n.coercions.push([(c=n.p)!==null&&c!==void 0?c:".",X2(n.coercion,a,C)]),!0}else{let h=!0;for(let[E,C]of a)if(h=t(E,Object.assign({},n))&&h,!h&&n?.errors==null||(h=e(C,Object.assign(Object.assign({},n),{p:s0(n,E)}))&&h,!h&&n?.errors==null))break;return h}if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h={value:a};return Array.isArray(a)?r(a,Object.assign(Object.assign({},n),{coercion:void 0}))?(n.coercions.push([(f=n.p)!==null&&f!==void 0?f:".",X2(n.coercion,a,()=>new Map(h.value))]),!0):!1:s(a,Object.assign(Object.assign({},n),{coercion:Wf(h,"value")}))?(n.coercions.push([(p=n.p)!==null&&p!==void 0?p:".",X2(n.coercion,a,()=>new Map(Object.entries(h.value)))]),!0):!1}return mr(n,`Expected a map (got ${ti(a)})`)}})}function Rx(t,{delimiter:e}={}){let r=Ore(t.length);return Wr({test:(s,a)=>{var n;if(typeof s=="string"&&typeof e<"u"&&typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");s=s.split(e),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)])}if(!Array.isArray(s))return mr(a,`Expected a tuple (got ${ti(s)})`);let c=r(s,Object.assign({},a));for(let f=0,p=s.length;f{var n;if(Array.isArray(s)&&typeof a?.coercions<"u")return typeof a?.coercion>"u"?mr(a,"Unbound coercion result"):r(s,Object.assign(Object.assign({},a),{coercion:void 0}))?(s=Object.fromEntries(s),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)]),!0):!1;if(typeof s!="object"||s===null)return mr(a,`Expected an object (got ${ti(s)})`);let c=Object.keys(s),f=!0;for(let p=0,h=c.length;p{if(typeof a!="object"||a===null)return mr(n,`Expected an object (got ${ti(a)})`);let c=new Set([...r,...Object.keys(a)]),f={},p=!0;for(let h of c){if(h==="constructor"||h==="__proto__")p=mr(Object.assign(Object.assign({},n),{p:s0(n,h)}),"Unsafe property name");else{let E=Object.prototype.hasOwnProperty.call(t,h)?t[h]:void 0,C=Object.prototype.hasOwnProperty.call(a,h)?a[h]:void 0;typeof E<"u"?p=E(C,Object.assign(Object.assign({},n),{p:s0(n,h),coercion:Wf(a,h)}))&&p:e===null?p=mr(Object.assign(Object.assign({},n),{p:s0(n,h)}),`Extraneous property (got ${ti(C)})`):Object.defineProperty(f,h,{enumerable:!0,get:()=>C,set:TWe(a,h)})}if(!p&&n?.errors==null)break}return e!==null&&(p||n?.errors!=null)&&(p=e(f,n)&&p),p}});return Object.assign(s,{properties:t})}function _We(t){return Fre(t,{extra:Fx(p_())})}function Nre(t){return()=>t}function Wr({test:t}){return Nre(t)()}function jWe(t,e){if(!e(t))throw new o0}function GWe(t,e){let r=[];if(!e(t,{errors:r}))throw new o0({errors:r})}function qWe(t,e){}function WWe(t,e,{coerce:r=!1,errors:s,throw:a}={}){let n=s?[]:void 0;if(!r){if(e(t,{errors:n}))return a?t:{value:t,errors:void 0};if(a)throw new o0({errors:n});return{value:void 0,errors:n??!0}}let c={value:t},f=Wf(c,"value"),p=[];if(!e(t,{errors:n,coercion:f,coercions:p})){if(a)throw new o0({errors:n});return{value:void 0,errors:n??!0}}for(let[,h]of p)h();return a?c.value:{value:c.value,errors:void 0}}function YWe(t,e){let r=Rx(t);return(...s)=>{if(!r(s))throw new o0;return e(...s)}}function VWe(t){return Wr({test:(e,r)=>e.length>=t?!0:mr(r,`Expected to have a length of at least ${t} elements (got ${e.length})`)})}function JWe(t){return Wr({test:(e,r)=>e.length<=t?!0:mr(r,`Expected to have a length of at most ${t} elements (got ${e.length})`)})}function Ore(t){return Wr({test:(e,r)=>e.length!==t?mr(r,`Expected to have a length of exactly ${t} elements (got ${e.length})`):!0})}function KWe({map:t}={}){return Wr({test:(e,r)=>{let s=new Set,a=new Set;for(let n=0,c=e.length;nt<=0?!0:mr(e,`Expected to be negative (got ${t})`)})}function XWe(){return Wr({test:(t,e)=>t>=0?!0:mr(e,`Expected to be positive (got ${t})`)})}function d_(t){return Wr({test:(e,r)=>e>=t?!0:mr(r,`Expected to be at least ${t} (got ${e})`)})}function ZWe(t){return Wr({test:(e,r)=>e<=t?!0:mr(r,`Expected to be at most ${t} (got ${e})`)})}function $We(t,e){return Wr({test:(r,s)=>r>=t&&r<=e?!0:mr(s,`Expected to be in the [${t}; ${e}] range (got ${r})`)})}function eYe(t,e){return Wr({test:(r,s)=>r>=t&&re!==Math.round(e)?mr(r,`Expected to be an integer (got ${e})`):!t&&!Number.isSafeInteger(e)?mr(r,`Expected to be a safe integer (got ${e})`):!0})}function Z2(t){return Wr({test:(e,r)=>t.test(e)?!0:mr(r,`Expected to match the pattern ${t.toString()} (got ${ti(e)})`)})}function tYe(){return Wr({test:(t,e)=>t!==t.toLowerCase()?mr(e,`Expected to be all-lowercase (got ${t})`):!0})}function rYe(){return Wr({test:(t,e)=>t!==t.toUpperCase()?mr(e,`Expected to be all-uppercase (got ${t})`):!0})}function nYe(){return Wr({test:(t,e)=>QWe.test(t)?!0:mr(e,`Expected to be a valid UUID v4 (got ${ti(t)})`)})}function iYe(){return Wr({test:(t,e)=>Tre.test(t)?!0:mr(e,`Expected to be a valid ISO 8601 date string (got ${ti(t)})`)})}function sYe({alpha:t=!1}){return Wr({test:(e,r)=>(t?PWe.test(e):xWe.test(e))?!0:mr(r,`Expected to be a valid hexadecimal color string (got ${ti(e)})`)})}function oYe(){return Wr({test:(t,e)=>kWe.test(t)?!0:mr(e,`Expected to be a valid base 64 string (got ${ti(t)})`)})}function aYe(t=p_()){return Wr({test:(e,r)=>{let s;try{s=JSON.parse(e)}catch{return mr(r,`Expected to be a valid JSON string (got ${ti(e)})`)}return t(s,r)}})}function Nx(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Wr({test:(s,a)=>{var n,c;let f={value:s},p=typeof a?.coercions<"u"?Wf(f,"value"):void 0,h=typeof a?.coercions<"u"?[]:void 0;if(!t(s,Object.assign(Object.assign({},a),{coercion:p,coercions:h})))return!1;let E=[];if(typeof h<"u")for(let[,C]of h)E.push(C());try{if(typeof a?.coercions<"u"){if(f.value!==s){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,f.value)])}(c=a?.coercions)===null||c===void 0||c.push(...h)}return r.every(C=>C(f.value,a))}finally{for(let C of E)C()}}})}function $2(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Nx(t,r)}function lYe(t){return Wr({test:(e,r)=>typeof e>"u"?!0:t(e,r)})}function cYe(t){return Wr({test:(e,r)=>e===null?!0:t(e,r)})}function uYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)||p.push(h);return p.length>0?mr(c,`Missing required ${A_(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function y_(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>Object.keys(n).some(h=>a(s,h,n))?!0:mr(c,`Missing at least one property from ${CE(Array.from(s),"or")}`)})}function fYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>0?mr(c,`Forbidden ${A_(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function AYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>1?mr(c,`Mutually exclusive properties ${CE(p,"and")}`):!0}})}function tB(t,e,r,s){var a,n;let c=new Set((a=s?.ignore)!==null&&a!==void 0?a:[]),f=eB[(n=s?.missingIf)!==null&&n!==void 0?n:"missing"],p=new Set(r),h=pYe[e],E=e===qf.Forbids?"or":"and";return Wr({test:(C,S)=>{let P=new Set(Object.keys(C));if(!f(P,t,C)||c.has(C[t]))return!0;let I=[];for(let R of p)(f(P,R,C)&&!c.has(C[R]))!==h.expect&&I.push(R);return I.length>=1?mr(S,`Property "${t}" ${h.message} ${A_(I.length,"property","properties")} ${CE(I,E)}`):!0}})}var bWe,PWe,xWe,kWe,QWe,Tre,RWe,HWe,g_,o0,eB,qf,pYe,Ul=Xe(()=>{bWe=/^[a-zA-Z_][a-zA-Z0-9_]*$/;PWe=/^#[0-9a-f]{6}$/i,xWe=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,kWe=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,QWe=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,Tre=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/;RWe=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]);HWe=t=>Wr({test:(e,r)=>e instanceof t?!0:mr(r,`Expected an instance of ${t.name} (got ${ti(e)})`)}),g_=(t,{exclusive:e=!1}={})=>Wr({test:(r,s)=>{var a,n,c;let f=[],p=typeof s?.errors<"u"?[]:void 0;for(let h=0,E=t.length;h1?mr(s,`Expected to match exactly a single predicate (matched ${f.join(", ")})`):(c=s?.errors)===null||c===void 0||c.push(...p),!1}});o0=class extends Error{constructor({errors:e}={}){let r="Type mismatch";if(e&&e.length>0){r+=` +`;for(let s of e)r+=` +- ${s}`}super(r)}};eB={missing:(t,e)=>t.has(e),undefined:(t,e,r)=>t.has(e)&&typeof r[e]<"u",nil:(t,e,r)=>t.has(e)&&r[e]!=null,falsy:(t,e,r)=>t.has(e)&&!!r[e]};(function(t){t.Forbids="Forbids",t.Requires="Requires"})(qf||(qf={}));pYe={[qf.Forbids]:{expect:!1,message:"forbids using"},[qf.Requires]:{expect:!0,message:"requires using"}}});var ot,a0=Xe(()=>{Cp();ot=class{constructor(){this.help=!1}static Usage(e){return e}async catch(e){throw e}async validateAndExecute(){let r=this.constructor.schema;if(Array.isArray(r)){let{isDict:a,isUnknown:n,applyCascade:c}=await Promise.resolve().then(()=>(Ul(),Ea)),f=c(a(n()),r),p=[],h=[];if(!f(this,{errors:p,coercions:h}))throw z2("Invalid option schema",p);for(let[,C]of h)C()}else if(r!=null)throw new Error("Invalid command schema");let s=await this.execute();return typeof s<"u"?s:0}};ot.isOption=K2;ot.Default=[]});function il(t){l_&&console.log(t)}function Mre(){let t={nodes:[]};for(let e=0;e{if(e.has(s))return;e.add(s);let a=t.nodes[s];for(let c of Object.values(a.statics))for(let{to:f}of c)r(f);for(let[,{to:c}]of a.dynamics)r(c);for(let{to:c}of a.shortcuts)r(c);let n=new Set(a.shortcuts.map(({to:c})=>c));for(;a.shortcuts.length>0;){let{to:c}=a.shortcuts.shift(),f=t.nodes[c];for(let[p,h]of Object.entries(f.statics)){let E=Object.prototype.hasOwnProperty.call(a.statics,p)?a.statics[p]:a.statics[p]=[];for(let C of h)E.some(({to:S})=>C.to===S)||E.push(C)}for(let[p,h]of f.dynamics)a.dynamics.some(([E,{to:C}])=>p===E&&h.to===C)||a.dynamics.push([p,h]);for(let p of f.shortcuts)n.has(p.to)||(a.shortcuts.push(p),n.add(p.to))}};r(En.InitialNode)}function dYe(t,{prefix:e=""}={}){if(l_){il(`${e}Nodes are:`);for(let r=0;rE!==En.ErrorNode).map(({state:E})=>({usage:E.candidateUsage,reason:null})));if(h.every(({node:E})=>E===En.ErrorNode))throw new IE(e,h.map(({state:E})=>({usage:E.candidateUsage,reason:E.errorMessage})));s=EYe(h)}if(s.length>0){il(" Results:");for(let n of s)il(` - ${n.node} -> ${JSON.stringify(n.state)}`)}else il(" No results");return s}function yYe(t,e,{endToken:r=ei.EndOfInput}={}){let s=mYe(t,[...e,r]);return IYe(e,s.map(({state:a})=>a))}function EYe(t){let e=0;for(let{state:r}of t)r.path.length>e&&(e=r.path.length);return t.filter(({state:r})=>r.path.length===e)}function IYe(t,e){let r=e.filter(S=>S.selectedIndex!==null),s=r.filter(S=>!S.partial);if(s.length>0&&(r=s),r.length===0)throw new Error;let a=r.filter(S=>S.selectedIndex===Nd||S.requiredOptions.every(P=>P.some(I=>S.options.find(R=>R.name===I))));if(a.length===0)throw new IE(t,r.map(S=>({usage:S.candidateUsage,reason:null})));let n=0;for(let S of a)S.path.length>n&&(n=S.path.length);let c=a.filter(S=>S.path.length===n),f=S=>S.positionals.filter(({extra:P})=>!P).length+S.options.length,p=c.map(S=>({state:S,positionalCount:f(S)})),h=0;for(let{positionalCount:S}of p)S>h&&(h=S);let E=p.filter(({positionalCount:S})=>S===h).map(({state:S})=>S),C=CYe(E);if(C.length>1)throw new xx(t,C.map(S=>S.candidateUsage));return C[0]}function CYe(t){let e=[],r=[];for(let s of t)s.selectedIndex===Nd?r.push(s):e.push(s);return r.length>0&&e.push({...Lre,path:Ure(...r.map(s=>s.path)),options:r.reduce((s,a)=>s.concat(a.options),[])}),e}function Ure(t,e,...r){return e===void 0?Array.from(t):Ure(t.filter((s,a)=>s===e[a]),...r)}function _l(){return{dynamics:[],shortcuts:[],statics:{}}}function _re(t){return t===En.SuccessNode||t===En.ErrorNode}function E_(t,e=0){return{to:_re(t.to)?t.to:t.to>=En.CustomNode?t.to+e-En.CustomNode+1:t.to+e,reducer:t.reducer}}function wYe(t,e=0){let r=_l();for(let[s,a]of t.dynamics)r.dynamics.push([s,E_(a,e)]);for(let s of t.shortcuts)r.shortcuts.push(E_(s,e));for(let[s,a]of Object.entries(t.statics))r.statics[s]=a.map(n=>E_(n,e));return r}function Hs(t,e,r,s,a){t.nodes[e].dynamics.push([r,{to:s,reducer:a}])}function BE(t,e,r,s){t.nodes[e].shortcuts.push({to:r,reducer:s})}function Ia(t,e,r,s,a){(Object.prototype.hasOwnProperty.call(t.nodes[e].statics,r)?t.nodes[e].statics[r]:t.nodes[e].statics[r]=[]).push({to:s,reducer:a})}function Ox(t,e,r,s,a){if(Array.isArray(e)){let[n,...c]=e;return t[n](r,s,a,...c)}else return t[e](r,s,a)}var Lre,BYe,I_,Hl,C_,Lx,Mx=Xe(()=>{Px();kx();Lre={candidateUsage:null,requiredOptions:[],errorMessage:null,ignoreOptions:!1,path:[],positionals:[],options:[],remainder:null,selectedIndex:Nd,partial:!1,tokens:[]};BYe={always:()=>!0,isOptionLike:(t,e)=>!t.ignoreOptions&&e!=="-"&&e.startsWith("-"),isNotOptionLike:(t,e)=>t.ignoreOptions||e==="-"||!e.startsWith("-"),isOption:(t,e,r,s)=>!t.ignoreOptions&&e===s,isBatchOption:(t,e,r,s)=>!t.ignoreOptions&&xre.test(e)&&[...e.slice(1)].every(a=>s.has(`-${a}`)),isBoundOption:(t,e,r,s,a)=>{let n=e.match(a_);return!t.ignoreOptions&&!!n&&bx.test(n[1])&&s.has(n[1])&&a.filter(c=>c.nameSet.includes(n[1])).every(c=>c.allowBinding)},isNegatedOption:(t,e,r,s)=>!t.ignoreOptions&&e===`--no-${s.slice(2)}`,isHelp:(t,e)=>!t.ignoreOptions&&o_.test(e),isUnsupportedOption:(t,e,r,s)=>!t.ignoreOptions&&e.startsWith("-")&&bx.test(e)&&!s.has(e),isInvalidOption:(t,e)=>!t.ignoreOptions&&e.startsWith("-")&&!bx.test(e)},I_={setCandidateState:(t,e,r,s)=>({...t,...s}),setSelectedIndex:(t,e,r,s)=>({...t,selectedIndex:s}),setPartialIndex:(t,e,r,s)=>({...t,selectedIndex:s,partial:!0}),pushBatch:(t,e,r,s)=>{let a=t.options.slice(),n=t.tokens.slice();for(let c=1;c{let[,s,a]=e.match(a_),n=t.options.concat({name:s,value:a}),c=t.tokens.concat([{segmentIndex:r,type:"option",slice:[0,s.length],option:s},{segmentIndex:r,type:"assign",slice:[s.length,s.length+1]},{segmentIndex:r,type:"value",slice:[s.length+1,s.length+a.length+1]}]);return{...t,options:n,tokens:c}},pushPath:(t,e,r)=>{let s=t.path.concat(e),a=t.tokens.concat({segmentIndex:r,type:"path"});return{...t,path:s,tokens:a}},pushPositional:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!1}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtra:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!0}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtraNoLimits:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:Hl}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushTrue:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushFalse:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!1}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushUndefined:(t,e,r,s)=>{let a=t.options.concat({name:e,value:void 0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:e});return{...t,options:a,tokens:n}},pushStringValue:(t,e,r)=>{var s;let a=t.options[t.options.length-1],n=t.options.slice(),c=t.tokens.concat({segmentIndex:r,type:"value"});return a.value=((s=a.value)!==null&&s!==void 0?s:[]).concat([e]),{...t,options:n,tokens:c}},setStringValue:(t,e,r)=>{let s=t.options[t.options.length-1],a=t.options.slice(),n=t.tokens.concat({segmentIndex:r,type:"value"});return s.value=e,{...t,options:a,tokens:n}},inhibateOptions:t=>({...t,ignoreOptions:!0}),useHelp:(t,e,r,s)=>{let[,,a]=e.match(o_);return typeof a<"u"?{...t,options:[{name:"-c",value:String(s)},{name:"-i",value:a}]}:{...t,options:[{name:"-c",value:String(s)}]}},setError:(t,e,r,s)=>e===ei.EndOfInput||e===ei.EndOfPartialInput?{...t,errorMessage:`${s}.`}:{...t,errorMessage:`${s} ("${e}").`},setOptionArityError:(t,e)=>{let r=t.options[t.options.length-1];return{...t,errorMessage:`Not enough arguments to option ${r.name}.`}}},Hl=Symbol(),C_=class{constructor(e,r){this.allOptionNames=new Map,this.arity={leading:[],trailing:[],extra:[],proxy:!1},this.options=[],this.paths=[],this.cliIndex=e,this.cliOpts=r}addPath(e){this.paths.push(e)}setArity({leading:e=this.arity.leading,trailing:r=this.arity.trailing,extra:s=this.arity.extra,proxy:a=this.arity.proxy}){Object.assign(this.arity,{leading:e,trailing:r,extra:s,proxy:a})}addPositional({name:e="arg",required:r=!0}={}){if(!r&&this.arity.extra===Hl)throw new Error("Optional parameters cannot be declared when using .rest() or .proxy()");if(!r&&this.arity.trailing.length>0)throw new Error("Optional parameters cannot be declared after the required trailing positional arguments");!r&&this.arity.extra!==Hl?this.arity.extra.push(e):this.arity.extra!==Hl&&this.arity.extra.length===0?this.arity.leading.push(e):this.arity.trailing.push(e)}addRest({name:e="arg",required:r=0}={}){if(this.arity.extra===Hl)throw new Error("Infinite lists cannot be declared multiple times in the same command");if(this.arity.trailing.length>0)throw new Error("Infinite lists cannot be declared after the required trailing positional arguments");for(let s=0;s1)throw new Error("The arity cannot be higher than 1 when the option only supports the --arg=value syntax");if(!Number.isInteger(s))throw new Error(`The arity must be an integer, got ${s}`);if(s<0)throw new Error(`The arity must be positive, got ${s}`);let f=e.reduce((p,h)=>h.length>p.length?h:p,"");for(let p of e)this.allOptionNames.set(p,f);this.options.push({preferredName:f,nameSet:e,description:r,arity:s,hidden:a,required:n,allowBinding:c})}setContext(e){this.context=e}usage({detailed:e=!0,inlineOptions:r=!0}={}){let s=[this.cliOpts.binaryName],a=[];if(this.paths.length>0&&s.push(...this.paths[0]),e){for(let{preferredName:c,nameSet:f,arity:p,hidden:h,description:E,required:C}of this.options){if(h)continue;let S=[];for(let I=0;I`:`[${P}]`)}s.push(...this.arity.leading.map(c=>`<${c}>`)),this.arity.extra===Hl?s.push("..."):s.push(...this.arity.extra.map(c=>`[${c}]`)),s.push(...this.arity.trailing.map(c=>`<${c}>`))}return{usage:s.join(" "),options:a}}compile(){if(typeof this.context>"u")throw new Error("Assertion failed: No context attached");let e=Mre(),r=En.InitialNode,s=this.usage().usage,a=this.options.filter(f=>f.required).map(f=>f.nameSet);r=Ou(e,_l()),Ia(e,En.InitialNode,ei.StartOfInput,r,["setCandidateState",{candidateUsage:s,requiredOptions:a}]);let n=this.arity.proxy?"always":"isNotOptionLike",c=this.paths.length>0?this.paths:[[]];for(let f of c){let p=r;if(f.length>0){let S=Ou(e,_l());BE(e,p,S),this.registerOptions(e,S),p=S}for(let S=0;S0||!this.arity.proxy){let S=Ou(e,_l());Hs(e,p,"isHelp",S,["useHelp",this.cliIndex]),Hs(e,S,"always",S,"pushExtra"),Ia(e,S,ei.EndOfInput,En.SuccessNode,["setSelectedIndex",Nd]),this.registerOptions(e,p)}this.arity.leading.length>0&&(Ia(e,p,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,p,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let h=p;for(let S=0;S0||S+1!==this.arity.leading.length)&&(Ia(e,P,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,P,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex])),Hs(e,h,"isNotOptionLike",P,"pushPositional"),h=P}let E=h;if(this.arity.extra===Hl||this.arity.extra.length>0){let S=Ou(e,_l());if(BE(e,h,S),this.arity.extra===Hl){let P=Ou(e,_l());this.arity.proxy||this.registerOptions(e,P),Hs(e,h,n,P,"pushExtraNoLimits"),Hs(e,P,n,P,"pushExtraNoLimits"),BE(e,P,S)}else for(let P=0;P0)&&this.registerOptions(e,I),Hs(e,E,n,I,"pushExtra"),BE(e,I,S),E=I}E=S}this.arity.trailing.length>0&&(Ia(e,E,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,E,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let C=E;for(let S=0;S=0&&e{let c=n?ei.EndOfPartialInput:ei.EndOfInput;return yYe(s,a,{endToken:c})}}}}});function jre(){return Ux.default&&"getColorDepth"in Ux.default.WriteStream.prototype?Ux.default.WriteStream.prototype.getColorDepth():process.env.FORCE_COLOR==="0"?1:process.env.FORCE_COLOR==="1"||typeof process.stdout<"u"&&process.stdout.isTTY?8:1}function Gre(t){let e=Hre;if(typeof e>"u"){if(t.stdout===process.stdout&&t.stderr===process.stderr)return null;let{AsyncLocalStorage:r}=Ie("async_hooks");e=Hre=new r;let s=process.stdout._write;process.stdout._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?s.call(this,n,c,f):p.stdout.write(n,c,f)};let a=process.stderr._write;process.stderr._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?a.call(this,n,c,f):p.stderr.write(n,c,f)}}return r=>e.run(t,r)}var Ux,Hre,qre=Xe(()=>{Ux=ut(Ie("tty"),1)});var _x,Wre=Xe(()=>{a0();_x=class t extends ot{constructor(e){super(),this.contexts=e,this.commands=[]}static from(e,r){let s=new t(r);s.path=e.path;for(let a of e.options)switch(a.name){case"-c":s.commands.push(Number(a.value));break;case"-i":s.index=Number(a.value);break}return s}async execute(){let e=this.commands;if(typeof this.index<"u"&&this.index>=0&&this.index1){this.context.stdout.write(`Multiple commands match your selection: +`),this.context.stdout.write(` +`);let r=0;for(let s of this.commands)this.context.stdout.write(this.cli.usage(this.contexts[s].commandClass,{prefix:`${r++}. `.padStart(5)}));this.context.stdout.write(` +`),this.context.stdout.write(`Run again with -h= to see the longer details of any of those commands. +`)}}}});async function Jre(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=zre(t);return Ca.from(r,e).runExit(s,a)}async function Kre(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=zre(t);return Ca.from(r,e).run(s,a)}function zre(t){let e,r,s,a;switch(typeof process<"u"&&typeof process.argv<"u"&&(s=process.argv.slice(2)),t.length){case 1:r=t[0];break;case 2:t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],Array.isArray(t[1])?s=t[1]:a=t[1]):(e=t[0],r=t[1]);break;case 3:Array.isArray(t[2])?(e=t[0],r=t[1],s=t[2]):t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],s=t[1],a=t[2]):(e=t[0],r=t[1],a=t[2]);break;default:e=t[0],r=t[1],s=t[2],a=t[3];break}if(typeof s>"u")throw new Error("The argv parameter must be provided when running Clipanion outside of a Node context");return{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}}function Vre(t){return t()}var Yre,Ca,Xre=Xe(()=>{Px();Mx();f_();qre();a0();Wre();Yre=Symbol("clipanion/errorCommand");Ca=class t{constructor({binaryLabel:e,binaryName:r="...",binaryVersion:s,enableCapture:a=!1,enableColors:n}={}){this.registrations=new Map,this.builder=new Lx({binaryName:r}),this.binaryLabel=e,this.binaryName=r,this.binaryVersion=s,this.enableCapture=a,this.enableColors=n}static from(e,r={}){let s=new t(r),a=Array.isArray(e)?e:[e];for(let n of a)s.register(n);return s}register(e){var r;let s=new Map,a=new e;for(let p in a){let h=a[p];typeof h=="object"&&h!==null&&h[ot.isOption]&&s.set(p,h)}let n=this.builder.command(),c=n.cliIndex,f=(r=e.paths)!==null&&r!==void 0?r:a.paths;if(typeof f<"u")for(let p of f)n.addPath(p);this.registrations.set(e,{specs:s,builder:n,index:c});for(let[p,{definition:h}]of s.entries())h(n,p);n.setContext({commandClass:e})}process(e,r){let{input:s,context:a,partial:n}=typeof e=="object"&&Array.isArray(e)?{input:e,context:r}:e,{contexts:c,process:f}=this.builder.compile(),p=f(s,{partial:n}),h={...t.defaultContext,...a};switch(p.selectedIndex){case Nd:{let E=_x.from(p,c);return E.context=h,E.tokens=p.tokens,E}default:{let{commandClass:E}=c[p.selectedIndex],C=this.registrations.get(E);if(typeof C>"u")throw new Error("Assertion failed: Expected the command class to have been registered.");let S=new E;S.context=h,S.tokens=p.tokens,S.path=p.path;try{for(let[P,{transformer:I}]of C.specs.entries())S[P]=I(C.builder,P,p,h);return S}catch(P){throw P[Yre]=S,P}}break}}async run(e,r){var s,a;let n,c={...t.defaultContext,...r},f=(s=this.enableColors)!==null&&s!==void 0?s:c.colorDepth>1;if(!Array.isArray(e))n=e;else try{n=this.process(e,c)}catch(E){return c.stdout.write(this.error(E,{colored:f})),1}if(n.help)return c.stdout.write(this.usage(n,{colored:f,detailed:!0})),0;n.context=c,n.cli={binaryLabel:this.binaryLabel,binaryName:this.binaryName,binaryVersion:this.binaryVersion,enableCapture:this.enableCapture,enableColors:this.enableColors,definitions:()=>this.definitions(),definition:E=>this.definition(E),error:(E,C)=>this.error(E,C),format:E=>this.format(E),process:(E,C)=>this.process(E,{...c,...C}),run:(E,C)=>this.run(E,{...c,...C}),usage:(E,C)=>this.usage(E,C)};let p=this.enableCapture&&(a=Gre(c))!==null&&a!==void 0?a:Vre,h;try{h=await p(()=>n.validateAndExecute().catch(E=>n.catch(E).then(()=>0)))}catch(E){return c.stdout.write(this.error(E,{colored:f,command:n})),1}return h}async runExit(e,r){process.exitCode=await this.run(e,r)}definition(e,{colored:r=!1}={}){if(!e.usage)return null;let{usage:s}=this.getUsageByRegistration(e,{detailed:!1}),{usage:a,options:n}=this.getUsageByRegistration(e,{detailed:!0,inlineOptions:!1}),c=typeof e.usage.category<"u"?Ho(e.usage.category,{format:this.format(r),paragraphs:!1}):void 0,f=typeof e.usage.description<"u"?Ho(e.usage.description,{format:this.format(r),paragraphs:!1}):void 0,p=typeof e.usage.details<"u"?Ho(e.usage.details,{format:this.format(r),paragraphs:!0}):void 0,h=typeof e.usage.examples<"u"?e.usage.examples.map(([E,C])=>[Ho(E,{format:this.format(r),paragraphs:!1}),C.replace(/\$0/g,this.binaryName)]):void 0;return{path:s,usage:a,category:c,description:f,details:p,examples:h,options:n}}definitions({colored:e=!1}={}){let r=[];for(let s of this.registrations.keys()){let a=this.definition(s,{colored:e});a&&r.push(a)}return r}usage(e=null,{colored:r,detailed:s=!1,prefix:a="$ "}={}){var n;if(e===null){for(let p of this.registrations.keys()){let h=p.paths,E=typeof p.usage<"u";if(!h||h.length===0||h.length===1&&h[0].length===0||((n=h?.some(P=>P.length===0))!==null&&n!==void 0?n:!1))if(e){e=null;break}else e=p;else if(E){e=null;continue}}e&&(s=!0)}let c=e!==null&&e instanceof ot?e.constructor:e,f="";if(c)if(s){let{description:p="",details:h="",examples:E=[]}=c.usage||{};p!==""&&(f+=Ho(p,{format:this.format(r),paragraphs:!1}).replace(/^./,P=>P.toUpperCase()),f+=` +`),(h!==""||E.length>0)&&(f+=`${this.format(r).header("Usage")} +`,f+=` +`);let{usage:C,options:S}=this.getUsageByRegistration(c,{inlineOptions:!1});if(f+=`${this.format(r).bold(a)}${C} +`,S.length>0){f+=` +`,f+=`${this.format(r).header("Options")} +`;let P=S.reduce((I,R)=>Math.max(I,R.definition.length),0);f+=` +`;for(let{definition:I,description:R}of S)f+=` ${this.format(r).bold(I.padEnd(P))} ${Ho(R,{format:this.format(r),paragraphs:!1})}`}if(h!==""&&(f+=` +`,f+=`${this.format(r).header("Details")} +`,f+=` +`,f+=Ho(h,{format:this.format(r),paragraphs:!0})),E.length>0){f+=` +`,f+=`${this.format(r).header("Examples")} +`;for(let[P,I]of E)f+=` +`,f+=Ho(P,{format:this.format(r),paragraphs:!1}),f+=`${I.replace(/^/m,` ${this.format(r).bold(a)}`).replace(/\$0/g,this.binaryName)} +`}}else{let{usage:p}=this.getUsageByRegistration(c);f+=`${this.format(r).bold(a)}${p} +`}else{let p=new Map;for(let[S,{index:P}]of this.registrations.entries()){if(typeof S.usage>"u")continue;let I=typeof S.usage.category<"u"?Ho(S.usage.category,{format:this.format(r),paragraphs:!1}):null,R=p.get(I);typeof R>"u"&&p.set(I,R=[]);let{usage:N}=this.getUsageByIndex(P);R.push({commandClass:S,usage:N})}let h=Array.from(p.keys()).sort((S,P)=>S===null?-1:P===null?1:S.localeCompare(P,"en",{usage:"sort",caseFirst:"upper"})),E=typeof this.binaryLabel<"u",C=typeof this.binaryVersion<"u";E||C?(E&&C?f+=`${this.format(r).header(`${this.binaryLabel} - ${this.binaryVersion}`)} + +`:E?f+=`${this.format(r).header(`${this.binaryLabel}`)} +`:f+=`${this.format(r).header(`${this.binaryVersion}`)} +`,f+=` ${this.format(r).bold(a)}${this.binaryName} +`):f+=`${this.format(r).bold(a)}${this.binaryName} +`;for(let S of h){let P=p.get(S).slice().sort((R,N)=>R.usage.localeCompare(N.usage,"en",{usage:"sort",caseFirst:"upper"})),I=S!==null?S.trim():"General commands";f+=` +`,f+=`${this.format(r).header(`${I}`)} +`;for(let{commandClass:R,usage:N}of P){let U=R.usage.description||"undocumented";f+=` +`,f+=` ${this.format(r).bold(N)} +`,f+=` ${Ho(U,{format:this.format(r),paragraphs:!1})}`}}f+=` +`,f+=Ho("You can also print more details about any of these commands by calling them with the `-h,--help` flag right after the command name.",{format:this.format(r),paragraphs:!0})}return f}error(e,r){var s,{colored:a,command:n=(s=e[Yre])!==null&&s!==void 0?s:null}=r===void 0?{}:r;(!e||typeof e!="object"||!("stack"in e))&&(e=new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(e)})`));let c="",f=e.name.replace(/([a-z])([A-Z])/g,"$1 $2");f==="Error"&&(f="Internal Error"),c+=`${this.format(a).error(f)}: ${e.message} +`;let p=e.clipanion;return typeof p<"u"?p.type==="usage"&&(c+=` +`,c+=this.usage(n)):e.stack&&(c+=`${e.stack.replace(/^.*\n/,"")} +`),c}format(e){var r;return((r=e??this.enableColors)!==null&&r!==void 0?r:t.defaultContext.colorDepth>1)?kre:Qre}getUsageByRegistration(e,r){let s=this.registrations.get(e);if(typeof s>"u")throw new Error("Assertion failed: Unregistered command");return this.getUsageByIndex(s.index,r)}getUsageByIndex(e,r){return this.builder.getBuilderByIndex(e).usage(r)}};Ca.defaultContext={env:process.env,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr,colorDepth:jre()}});var rB,Zre=Xe(()=>{a0();rB=class extends ot{async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.definitions(),null,2)} +`)}};rB.paths=[["--clipanion=definitions"]]});var nB,$re=Xe(()=>{a0();nB=class extends ot{async execute(){this.context.stdout.write(this.cli.usage())}};nB.paths=[["-h"],["--help"]]});function Hx(t={}){return ya({definition(e,r){var s;e.addProxy({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){return s.positionals.map(({value:a})=>a)}})}var w_=Xe(()=>{Cp()});var iB,ene=Xe(()=>{a0();w_();iB=class extends ot{constructor(){super(...arguments),this.args=Hx()}async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.process(this.args).tokens,null,2)} +`)}};iB.paths=[["--clipanion=tokens"]]});var sB,tne=Xe(()=>{a0();sB=class extends ot{async execute(){var e;this.context.stdout.write(`${(e=this.cli.binaryVersion)!==null&&e!==void 0?e:""} +`)}};sB.paths=[["-v"],["--version"]]});var B_={};Vt(B_,{DefinitionsCommand:()=>rB,HelpCommand:()=>nB,TokensCommand:()=>iB,VersionCommand:()=>sB});var rne=Xe(()=>{Zre();$re();ene();tne()});function nne(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return ya({definition(p){p.addOption({names:c,arity:n,hidden:a?.hidden,description:a?.description,required:a.required})},transformer(p,h,E){let C,S=typeof s<"u"?[...s]:void 0;for(let{name:P,value:I}of E.options)f.has(P)&&(C=P,S=S??[],S.push(I));return typeof S<"u"?Od(C??h,S,a.validator):S}})}var ine=Xe(()=>{Cp()});function sne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return ya({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E=S);return E}})}var one=Xe(()=>{Cp()});function ane(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return ya({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E??(E=0),S?E+=1:E=0);return E}})}var lne=Xe(()=>{Cp()});function cne(t={}){return ya({definition(e,r){var s;e.addRest({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){let a=c=>{let f=s.positionals[c];return f.extra===Hl||f.extra===!1&&cc)}})}var une=Xe(()=>{Mx();Cp()});function vYe(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return ya({definition(p){p.addOption({names:c,arity:a.tolerateBoolean?0:n,hidden:a.hidden,description:a.description,required:a.required})},transformer(p,h,E,C){let S,P=s;typeof a.env<"u"&&C.env[a.env]&&(S=a.env,P=C.env[a.env]);for(let{name:I,value:R}of E.options)f.has(I)&&(S=I,P=R);return typeof P=="string"?Od(S??h,P,a.validator):P}})}function SYe(t={}){let{required:e=!0}=t;return ya({definition(r,s){var a;r.addPositional({name:(a=t.name)!==null&&a!==void 0?a:s,required:t.required})},transformer(r,s,a){var n;for(let c=0;c{Mx();Cp()});var ge={};Vt(ge,{Array:()=>nne,Boolean:()=>sne,Counter:()=>ane,Proxy:()=>Hx,Rest:()=>cne,String:()=>fne,applyValidator:()=>Od,cleanValidationError:()=>Qx,formatError:()=>z2,isOptionSymbol:()=>K2,makeCommandOption:()=>ya,rerouteArguments:()=>Gf});var pne=Xe(()=>{Cp();w_();ine();one();lne();une();Ane()});var oB={};Vt(oB,{Builtins:()=>B_,Cli:()=>Ca,Command:()=>ot,Option:()=>ge,UsageError:()=>nt,formatMarkdownish:()=>Ho,run:()=>Kre,runExit:()=>Jre});var Yt=Xe(()=>{kx();f_();a0();Xre();rne();pne()});var hne=_((VTt,DYe)=>{DYe.exports={name:"dotenv",version:"16.3.1",description:"Loads environment variables from .env file",main:"lib/main.js",types:"lib/main.d.ts",exports:{".":{types:"./lib/main.d.ts",require:"./lib/main.js",default:"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},scripts:{"dts-check":"tsc --project tests/types/tsconfig.json",lint:"standard","lint-readme":"standard-markdown",pretest:"npm run lint && npm run dts-check",test:"tap tests/*.js --100 -Rspec",prerelease:"npm test",release:"standard-version"},repository:{type:"git",url:"git://github.com/motdotla/dotenv.git"},funding:"https://github.com/motdotla/dotenv?sponsor=1",keywords:["dotenv","env",".env","environment","variables","config","settings"],readmeFilename:"README.md",license:"BSD-2-Clause",devDependencies:{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3",decache:"^4.6.1",sinon:"^14.0.1",standard:"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0",tap:"^16.3.0",tar:"^6.1.11",typescript:"^4.8.4"},engines:{node:">=12"},browser:{fs:!1}}});var yne=_((JTt,wp)=>{var gne=Ie("fs"),S_=Ie("path"),bYe=Ie("os"),PYe=Ie("crypto"),xYe=hne(),D_=xYe.version,kYe=/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;function QYe(t){let e={},r=t.toString();r=r.replace(/\r\n?/mg,` +`);let s;for(;(s=kYe.exec(r))!=null;){let a=s[1],n=s[2]||"";n=n.trim();let c=n[0];n=n.replace(/^(['"`])([\s\S]*)\1$/mg,"$2"),c==='"'&&(n=n.replace(/\\n/g,` +`),n=n.replace(/\\r/g,"\r")),e[a]=n}return e}function TYe(t){let e=mne(t),r=js.configDotenv({path:e});if(!r.parsed)throw new Error(`MISSING_DATA: Cannot parse ${e} for an unknown reason`);let s=dne(t).split(","),a=s.length,n;for(let c=0;c=a)throw f}return js.parse(n)}function RYe(t){console.log(`[dotenv@${D_}][INFO] ${t}`)}function FYe(t){console.log(`[dotenv@${D_}][WARN] ${t}`)}function v_(t){console.log(`[dotenv@${D_}][DEBUG] ${t}`)}function dne(t){return t&&t.DOTENV_KEY&&t.DOTENV_KEY.length>0?t.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:""}function NYe(t,e){let r;try{r=new URL(e)}catch(f){throw f.code==="ERR_INVALID_URL"?new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development"):f}let s=r.password;if(!s)throw new Error("INVALID_DOTENV_KEY: Missing key part");let a=r.searchParams.get("environment");if(!a)throw new Error("INVALID_DOTENV_KEY: Missing environment part");let n=`DOTENV_VAULT_${a.toUpperCase()}`,c=t.parsed[n];if(!c)throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${n} in your .env.vault file.`);return{ciphertext:c,key:s}}function mne(t){let e=S_.resolve(process.cwd(),".env");return t&&t.path&&t.path.length>0&&(e=t.path),e.endsWith(".vault")?e:`${e}.vault`}function OYe(t){return t[0]==="~"?S_.join(bYe.homedir(),t.slice(1)):t}function LYe(t){RYe("Loading env from encrypted .env.vault");let e=js._parseVault(t),r=process.env;return t&&t.processEnv!=null&&(r=t.processEnv),js.populate(r,e,t),{parsed:e}}function MYe(t){let e=S_.resolve(process.cwd(),".env"),r="utf8",s=!!(t&&t.debug);t&&(t.path!=null&&(e=OYe(t.path)),t.encoding!=null&&(r=t.encoding));try{let a=js.parse(gne.readFileSync(e,{encoding:r})),n=process.env;return t&&t.processEnv!=null&&(n=t.processEnv),js.populate(n,a,t),{parsed:a}}catch(a){return s&&v_(`Failed to load ${e} ${a.message}`),{error:a}}}function UYe(t){let e=mne(t);return dne(t).length===0?js.configDotenv(t):gne.existsSync(e)?js._configVault(t):(FYe(`You set DOTENV_KEY but you are missing a .env.vault file at ${e}. Did you forget to build it?`),js.configDotenv(t))}function _Ye(t,e){let r=Buffer.from(e.slice(-64),"hex"),s=Buffer.from(t,"base64"),a=s.slice(0,12),n=s.slice(-16);s=s.slice(12,-16);try{let c=PYe.createDecipheriv("aes-256-gcm",r,a);return c.setAuthTag(n),`${c.update(s)}${c.final()}`}catch(c){let f=c instanceof RangeError,p=c.message==="Invalid key length",h=c.message==="Unsupported state or unable to authenticate data";if(f||p){let E="INVALID_DOTENV_KEY: It must be 64 characters long (or more)";throw new Error(E)}else if(h){let E="DECRYPTION_FAILED: Please check your DOTENV_KEY";throw new Error(E)}else throw console.error("Error: ",c.code),console.error("Error: ",c.message),c}}function HYe(t,e,r={}){let s=!!(r&&r.debug),a=!!(r&&r.override);if(typeof e!="object")throw new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");for(let n of Object.keys(e))Object.prototype.hasOwnProperty.call(t,n)?(a===!0&&(t[n]=e[n]),s&&v_(a===!0?`"${n}" is already defined and WAS overwritten`:`"${n}" is already defined and was NOT overwritten`)):t[n]=e[n]}var js={configDotenv:MYe,_configVault:LYe,_parseVault:TYe,config:UYe,decrypt:_Ye,parse:QYe,populate:HYe};wp.exports.configDotenv=js.configDotenv;wp.exports._configVault=js._configVault;wp.exports._parseVault=js._parseVault;wp.exports.config=js.config;wp.exports.decrypt=js.decrypt;wp.exports.parse=js.parse;wp.exports.populate=js.populate;wp.exports=js});var Ine=_((KTt,Ene)=>{"use strict";Ene.exports=(t,...e)=>new Promise(r=>{r(t(...e))})});var Ld=_((zTt,b_)=>{"use strict";var jYe=Ine(),Cne=t=>{if(t<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],r=0,s=()=>{r--,e.length>0&&e.shift()()},a=(f,p,...h)=>{r++;let E=jYe(f,...h);p(E),E.then(s,s)},n=(f,p,...h)=>{rnew Promise(h=>n(f,h,...p));return Object.defineProperties(c,{activeCount:{get:()=>r},pendingCount:{get:()=>e.length}}),c};b_.exports=Cne;b_.exports.default=Cne});function Yf(t){return`YN${t.toString(10).padStart(4,"0")}`}function jx(t){let e=Number(t.slice(2));if(typeof Br[e]>"u")throw new Error(`Unknown message name: "${t}"`);return e}var Br,Gx=Xe(()=>{Br=(Me=>(Me[Me.UNNAMED=0]="UNNAMED",Me[Me.EXCEPTION=1]="EXCEPTION",Me[Me.MISSING_PEER_DEPENDENCY=2]="MISSING_PEER_DEPENDENCY",Me[Me.CYCLIC_DEPENDENCIES=3]="CYCLIC_DEPENDENCIES",Me[Me.DISABLED_BUILD_SCRIPTS=4]="DISABLED_BUILD_SCRIPTS",Me[Me.BUILD_DISABLED=5]="BUILD_DISABLED",Me[Me.SOFT_LINK_BUILD=6]="SOFT_LINK_BUILD",Me[Me.MUST_BUILD=7]="MUST_BUILD",Me[Me.MUST_REBUILD=8]="MUST_REBUILD",Me[Me.BUILD_FAILED=9]="BUILD_FAILED",Me[Me.RESOLVER_NOT_FOUND=10]="RESOLVER_NOT_FOUND",Me[Me.FETCHER_NOT_FOUND=11]="FETCHER_NOT_FOUND",Me[Me.LINKER_NOT_FOUND=12]="LINKER_NOT_FOUND",Me[Me.FETCH_NOT_CACHED=13]="FETCH_NOT_CACHED",Me[Me.YARN_IMPORT_FAILED=14]="YARN_IMPORT_FAILED",Me[Me.REMOTE_INVALID=15]="REMOTE_INVALID",Me[Me.REMOTE_NOT_FOUND=16]="REMOTE_NOT_FOUND",Me[Me.RESOLUTION_PACK=17]="RESOLUTION_PACK",Me[Me.CACHE_CHECKSUM_MISMATCH=18]="CACHE_CHECKSUM_MISMATCH",Me[Me.UNUSED_CACHE_ENTRY=19]="UNUSED_CACHE_ENTRY",Me[Me.MISSING_LOCKFILE_ENTRY=20]="MISSING_LOCKFILE_ENTRY",Me[Me.WORKSPACE_NOT_FOUND=21]="WORKSPACE_NOT_FOUND",Me[Me.TOO_MANY_MATCHING_WORKSPACES=22]="TOO_MANY_MATCHING_WORKSPACES",Me[Me.CONSTRAINTS_MISSING_DEPENDENCY=23]="CONSTRAINTS_MISSING_DEPENDENCY",Me[Me.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY=24]="CONSTRAINTS_INCOMPATIBLE_DEPENDENCY",Me[Me.CONSTRAINTS_EXTRANEOUS_DEPENDENCY=25]="CONSTRAINTS_EXTRANEOUS_DEPENDENCY",Me[Me.CONSTRAINTS_INVALID_DEPENDENCY=26]="CONSTRAINTS_INVALID_DEPENDENCY",Me[Me.CANT_SUGGEST_RESOLUTIONS=27]="CANT_SUGGEST_RESOLUTIONS",Me[Me.FROZEN_LOCKFILE_EXCEPTION=28]="FROZEN_LOCKFILE_EXCEPTION",Me[Me.CROSS_DRIVE_VIRTUAL_LOCAL=29]="CROSS_DRIVE_VIRTUAL_LOCAL",Me[Me.FETCH_FAILED=30]="FETCH_FAILED",Me[Me.DANGEROUS_NODE_MODULES=31]="DANGEROUS_NODE_MODULES",Me[Me.NODE_GYP_INJECTED=32]="NODE_GYP_INJECTED",Me[Me.AUTHENTICATION_NOT_FOUND=33]="AUTHENTICATION_NOT_FOUND",Me[Me.INVALID_CONFIGURATION_KEY=34]="INVALID_CONFIGURATION_KEY",Me[Me.NETWORK_ERROR=35]="NETWORK_ERROR",Me[Me.LIFECYCLE_SCRIPT=36]="LIFECYCLE_SCRIPT",Me[Me.CONSTRAINTS_MISSING_FIELD=37]="CONSTRAINTS_MISSING_FIELD",Me[Me.CONSTRAINTS_INCOMPATIBLE_FIELD=38]="CONSTRAINTS_INCOMPATIBLE_FIELD",Me[Me.CONSTRAINTS_EXTRANEOUS_FIELD=39]="CONSTRAINTS_EXTRANEOUS_FIELD",Me[Me.CONSTRAINTS_INVALID_FIELD=40]="CONSTRAINTS_INVALID_FIELD",Me[Me.AUTHENTICATION_INVALID=41]="AUTHENTICATION_INVALID",Me[Me.PROLOG_UNKNOWN_ERROR=42]="PROLOG_UNKNOWN_ERROR",Me[Me.PROLOG_SYNTAX_ERROR=43]="PROLOG_SYNTAX_ERROR",Me[Me.PROLOG_EXISTENCE_ERROR=44]="PROLOG_EXISTENCE_ERROR",Me[Me.STACK_OVERFLOW_RESOLUTION=45]="STACK_OVERFLOW_RESOLUTION",Me[Me.AUTOMERGE_FAILED_TO_PARSE=46]="AUTOMERGE_FAILED_TO_PARSE",Me[Me.AUTOMERGE_IMMUTABLE=47]="AUTOMERGE_IMMUTABLE",Me[Me.AUTOMERGE_SUCCESS=48]="AUTOMERGE_SUCCESS",Me[Me.AUTOMERGE_REQUIRED=49]="AUTOMERGE_REQUIRED",Me[Me.DEPRECATED_CLI_SETTINGS=50]="DEPRECATED_CLI_SETTINGS",Me[Me.PLUGIN_NAME_NOT_FOUND=51]="PLUGIN_NAME_NOT_FOUND",Me[Me.INVALID_PLUGIN_REFERENCE=52]="INVALID_PLUGIN_REFERENCE",Me[Me.CONSTRAINTS_AMBIGUITY=53]="CONSTRAINTS_AMBIGUITY",Me[Me.CACHE_OUTSIDE_PROJECT=54]="CACHE_OUTSIDE_PROJECT",Me[Me.IMMUTABLE_INSTALL=55]="IMMUTABLE_INSTALL",Me[Me.IMMUTABLE_CACHE=56]="IMMUTABLE_CACHE",Me[Me.INVALID_MANIFEST=57]="INVALID_MANIFEST",Me[Me.PACKAGE_PREPARATION_FAILED=58]="PACKAGE_PREPARATION_FAILED",Me[Me.INVALID_RANGE_PEER_DEPENDENCY=59]="INVALID_RANGE_PEER_DEPENDENCY",Me[Me.INCOMPATIBLE_PEER_DEPENDENCY=60]="INCOMPATIBLE_PEER_DEPENDENCY",Me[Me.DEPRECATED_PACKAGE=61]="DEPRECATED_PACKAGE",Me[Me.INCOMPATIBLE_OS=62]="INCOMPATIBLE_OS",Me[Me.INCOMPATIBLE_CPU=63]="INCOMPATIBLE_CPU",Me[Me.FROZEN_ARTIFACT_EXCEPTION=64]="FROZEN_ARTIFACT_EXCEPTION",Me[Me.TELEMETRY_NOTICE=65]="TELEMETRY_NOTICE",Me[Me.PATCH_HUNK_FAILED=66]="PATCH_HUNK_FAILED",Me[Me.INVALID_CONFIGURATION_VALUE=67]="INVALID_CONFIGURATION_VALUE",Me[Me.UNUSED_PACKAGE_EXTENSION=68]="UNUSED_PACKAGE_EXTENSION",Me[Me.REDUNDANT_PACKAGE_EXTENSION=69]="REDUNDANT_PACKAGE_EXTENSION",Me[Me.AUTO_NM_SUCCESS=70]="AUTO_NM_SUCCESS",Me[Me.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK=71]="NM_CANT_INSTALL_EXTERNAL_SOFT_LINK",Me[Me.NM_PRESERVE_SYMLINKS_REQUIRED=72]="NM_PRESERVE_SYMLINKS_REQUIRED",Me[Me.UPDATE_LOCKFILE_ONLY_SKIP_LINK=73]="UPDATE_LOCKFILE_ONLY_SKIP_LINK",Me[Me.NM_HARDLINKS_MODE_DOWNGRADED=74]="NM_HARDLINKS_MODE_DOWNGRADED",Me[Me.PROLOG_INSTANTIATION_ERROR=75]="PROLOG_INSTANTIATION_ERROR",Me[Me.INCOMPATIBLE_ARCHITECTURE=76]="INCOMPATIBLE_ARCHITECTURE",Me[Me.GHOST_ARCHITECTURE=77]="GHOST_ARCHITECTURE",Me[Me.RESOLUTION_MISMATCH=78]="RESOLUTION_MISMATCH",Me[Me.PROLOG_LIMIT_EXCEEDED=79]="PROLOG_LIMIT_EXCEEDED",Me[Me.NETWORK_DISABLED=80]="NETWORK_DISABLED",Me[Me.NETWORK_UNSAFE_HTTP=81]="NETWORK_UNSAFE_HTTP",Me[Me.RESOLUTION_FAILED=82]="RESOLUTION_FAILED",Me[Me.AUTOMERGE_GIT_ERROR=83]="AUTOMERGE_GIT_ERROR",Me[Me.CONSTRAINTS_CHECK_FAILED=84]="CONSTRAINTS_CHECK_FAILED",Me[Me.UPDATED_RESOLUTION_RECORD=85]="UPDATED_RESOLUTION_RECORD",Me[Me.EXPLAIN_PEER_DEPENDENCIES_CTA=86]="EXPLAIN_PEER_DEPENDENCIES_CTA",Me[Me.MIGRATION_SUCCESS=87]="MIGRATION_SUCCESS",Me[Me.VERSION_NOTICE=88]="VERSION_NOTICE",Me[Me.TIPS_NOTICE=89]="TIPS_NOTICE",Me[Me.OFFLINE_MODE_ENABLED=90]="OFFLINE_MODE_ENABLED",Me[Me.INVALID_PROVENANCE_ENVIRONMENT=91]="INVALID_PROVENANCE_ENVIRONMENT",Me))(Br||{})});var aB=_((ZTt,wne)=>{var GYe="2.0.0",qYe=Number.MAX_SAFE_INTEGER||9007199254740991,WYe=16,YYe=250,VYe=["major","premajor","minor","preminor","patch","prepatch","prerelease"];wne.exports={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:WYe,MAX_SAFE_BUILD_LENGTH:YYe,MAX_SAFE_INTEGER:qYe,RELEASE_TYPES:VYe,SEMVER_SPEC_VERSION:GYe,FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2}});var lB=_(($Tt,Bne)=>{var JYe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...t)=>console.error("SEMVER",...t):()=>{};Bne.exports=JYe});var vE=_((Bp,vne)=>{var{MAX_SAFE_COMPONENT_LENGTH:P_,MAX_SAFE_BUILD_LENGTH:KYe,MAX_LENGTH:zYe}=aB(),XYe=lB();Bp=vne.exports={};var ZYe=Bp.re=[],$Ye=Bp.safeRe=[],rr=Bp.src=[],nr=Bp.t={},eVe=0,x_="[a-zA-Z0-9-]",tVe=[["\\s",1],["\\d",zYe],[x_,KYe]],rVe=t=>{for(let[e,r]of tVe)t=t.split(`${e}*`).join(`${e}{0,${r}}`).split(`${e}+`).join(`${e}{1,${r}}`);return t},Jr=(t,e,r)=>{let s=rVe(e),a=eVe++;XYe(t,a,e),nr[t]=a,rr[a]=e,ZYe[a]=new RegExp(e,r?"g":void 0),$Ye[a]=new RegExp(s,r?"g":void 0)};Jr("NUMERICIDENTIFIER","0|[1-9]\\d*");Jr("NUMERICIDENTIFIERLOOSE","\\d+");Jr("NONNUMERICIDENTIFIER",`\\d*[a-zA-Z-]${x_}*`);Jr("MAINVERSION",`(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})`);Jr("MAINVERSIONLOOSE",`(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})`);Jr("PRERELEASEIDENTIFIER",`(?:${rr[nr.NUMERICIDENTIFIER]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Jr("PRERELEASEIDENTIFIERLOOSE",`(?:${rr[nr.NUMERICIDENTIFIERLOOSE]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Jr("PRERELEASE",`(?:-(${rr[nr.PRERELEASEIDENTIFIER]}(?:\\.${rr[nr.PRERELEASEIDENTIFIER]})*))`);Jr("PRERELEASELOOSE",`(?:-?(${rr[nr.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${rr[nr.PRERELEASEIDENTIFIERLOOSE]})*))`);Jr("BUILDIDENTIFIER",`${x_}+`);Jr("BUILD",`(?:\\+(${rr[nr.BUILDIDENTIFIER]}(?:\\.${rr[nr.BUILDIDENTIFIER]})*))`);Jr("FULLPLAIN",`v?${rr[nr.MAINVERSION]}${rr[nr.PRERELEASE]}?${rr[nr.BUILD]}?`);Jr("FULL",`^${rr[nr.FULLPLAIN]}$`);Jr("LOOSEPLAIN",`[v=\\s]*${rr[nr.MAINVERSIONLOOSE]}${rr[nr.PRERELEASELOOSE]}?${rr[nr.BUILD]}?`);Jr("LOOSE",`^${rr[nr.LOOSEPLAIN]}$`);Jr("GTLT","((?:<|>)?=?)");Jr("XRANGEIDENTIFIERLOOSE",`${rr[nr.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);Jr("XRANGEIDENTIFIER",`${rr[nr.NUMERICIDENTIFIER]}|x|X|\\*`);Jr("XRANGEPLAIN",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:${rr[nr.PRERELEASE]})?${rr[nr.BUILD]}?)?)?`);Jr("XRANGEPLAINLOOSE",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:${rr[nr.PRERELEASELOOSE]})?${rr[nr.BUILD]}?)?)?`);Jr("XRANGE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAIN]}$`);Jr("XRANGELOOSE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("COERCEPLAIN",`(^|[^\\d])(\\d{1,${P_}})(?:\\.(\\d{1,${P_}}))?(?:\\.(\\d{1,${P_}}))?`);Jr("COERCE",`${rr[nr.COERCEPLAIN]}(?:$|[^\\d])`);Jr("COERCEFULL",rr[nr.COERCEPLAIN]+`(?:${rr[nr.PRERELEASE]})?(?:${rr[nr.BUILD]})?(?:$|[^\\d])`);Jr("COERCERTL",rr[nr.COERCE],!0);Jr("COERCERTLFULL",rr[nr.COERCEFULL],!0);Jr("LONETILDE","(?:~>?)");Jr("TILDETRIM",`(\\s*)${rr[nr.LONETILDE]}\\s+`,!0);Bp.tildeTrimReplace="$1~";Jr("TILDE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAIN]}$`);Jr("TILDELOOSE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("LONECARET","(?:\\^)");Jr("CARETTRIM",`(\\s*)${rr[nr.LONECARET]}\\s+`,!0);Bp.caretTrimReplace="$1^";Jr("CARET",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAIN]}$`);Jr("CARETLOOSE",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("COMPARATORLOOSE",`^${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]})$|^$`);Jr("COMPARATOR",`^${rr[nr.GTLT]}\\s*(${rr[nr.FULLPLAIN]})$|^$`);Jr("COMPARATORTRIM",`(\\s*)${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]}|${rr[nr.XRANGEPLAIN]})`,!0);Bp.comparatorTrimReplace="$1$2$3";Jr("HYPHENRANGE",`^\\s*(${rr[nr.XRANGEPLAIN]})\\s+-\\s+(${rr[nr.XRANGEPLAIN]})\\s*$`);Jr("HYPHENRANGELOOSE",`^\\s*(${rr[nr.XRANGEPLAINLOOSE]})\\s+-\\s+(${rr[nr.XRANGEPLAINLOOSE]})\\s*$`);Jr("STAR","(<|>)?=?\\s*\\*");Jr("GTE0","^\\s*>=\\s*0\\.0\\.0\\s*$");Jr("GTE0PRE","^\\s*>=\\s*0\\.0\\.0-0\\s*$")});var qx=_((eRt,Sne)=>{var nVe=Object.freeze({loose:!0}),iVe=Object.freeze({}),sVe=t=>t?typeof t!="object"?nVe:t:iVe;Sne.exports=sVe});var k_=_((tRt,Pne)=>{var Dne=/^[0-9]+$/,bne=(t,e)=>{let r=Dne.test(t),s=Dne.test(e);return r&&s&&(t=+t,e=+e),t===e?0:r&&!s?-1:s&&!r?1:tbne(e,t);Pne.exports={compareIdentifiers:bne,rcompareIdentifiers:oVe}});var jo=_((rRt,Tne)=>{var Wx=lB(),{MAX_LENGTH:xne,MAX_SAFE_INTEGER:Yx}=aB(),{safeRe:kne,t:Qne}=vE(),aVe=qx(),{compareIdentifiers:SE}=k_(),Q_=class t{constructor(e,r){if(r=aVe(r),e instanceof t){if(e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid version. Must be a string. Got type "${typeof e}".`);if(e.length>xne)throw new TypeError(`version is longer than ${xne} characters`);Wx("SemVer",e,r),this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease;let s=e.trim().match(r.loose?kne[Qne.LOOSE]:kne[Qne.FULL]);if(!s)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+s[1],this.minor=+s[2],this.patch=+s[3],this.major>Yx||this.major<0)throw new TypeError("Invalid major version");if(this.minor>Yx||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>Yx||this.patch<0)throw new TypeError("Invalid patch version");s[4]?this.prerelease=s[4].split(".").map(a=>{if(/^[0-9]+$/.test(a)){let n=+a;if(n>=0&&n=0;)typeof this.prerelease[n]=="number"&&(this.prerelease[n]++,n=-2);if(n===-1){if(r===this.prerelease.join(".")&&s===!1)throw new Error("invalid increment argument: identifier already exists");this.prerelease.push(a)}}if(r){let n=[r,a];s===!1&&(n=[r]),SE(this.prerelease[0],r)===0?isNaN(this.prerelease[1])&&(this.prerelease=n):this.prerelease=n}break}default:throw new Error(`invalid increment argument: ${e}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(".")}`),this}};Tne.exports=Q_});var Md=_((nRt,Fne)=>{var Rne=jo(),lVe=(t,e,r=!1)=>{if(t instanceof Rne)return t;try{return new Rne(t,e)}catch(s){if(!r)return null;throw s}};Fne.exports=lVe});var One=_((iRt,Nne)=>{var cVe=Md(),uVe=(t,e)=>{let r=cVe(t,e);return r?r.version:null};Nne.exports=uVe});var Mne=_((sRt,Lne)=>{var fVe=Md(),AVe=(t,e)=>{let r=fVe(t.trim().replace(/^[=v]+/,""),e);return r?r.version:null};Lne.exports=AVe});var Hne=_((oRt,_ne)=>{var Une=jo(),pVe=(t,e,r,s,a)=>{typeof r=="string"&&(a=s,s=r,r=void 0);try{return new Une(t instanceof Une?t.version:t,r).inc(e,s,a).version}catch{return null}};_ne.exports=pVe});var qne=_((aRt,Gne)=>{var jne=Md(),hVe=(t,e)=>{let r=jne(t,null,!0),s=jne(e,null,!0),a=r.compare(s);if(a===0)return null;let n=a>0,c=n?r:s,f=n?s:r,p=!!c.prerelease.length;if(!!f.prerelease.length&&!p)return!f.patch&&!f.minor?"major":c.patch?"patch":c.minor?"minor":"major";let E=p?"pre":"";return r.major!==s.major?E+"major":r.minor!==s.minor?E+"minor":r.patch!==s.patch?E+"patch":"prerelease"};Gne.exports=hVe});var Yne=_((lRt,Wne)=>{var gVe=jo(),dVe=(t,e)=>new gVe(t,e).major;Wne.exports=dVe});var Jne=_((cRt,Vne)=>{var mVe=jo(),yVe=(t,e)=>new mVe(t,e).minor;Vne.exports=yVe});var zne=_((uRt,Kne)=>{var EVe=jo(),IVe=(t,e)=>new EVe(t,e).patch;Kne.exports=IVe});var Zne=_((fRt,Xne)=>{var CVe=Md(),wVe=(t,e)=>{let r=CVe(t,e);return r&&r.prerelease.length?r.prerelease:null};Xne.exports=wVe});var Bc=_((ARt,eie)=>{var $ne=jo(),BVe=(t,e,r)=>new $ne(t,r).compare(new $ne(e,r));eie.exports=BVe});var rie=_((pRt,tie)=>{var vVe=Bc(),SVe=(t,e,r)=>vVe(e,t,r);tie.exports=SVe});var iie=_((hRt,nie)=>{var DVe=Bc(),bVe=(t,e)=>DVe(t,e,!0);nie.exports=bVe});var Vx=_((gRt,oie)=>{var sie=jo(),PVe=(t,e,r)=>{let s=new sie(t,r),a=new sie(e,r);return s.compare(a)||s.compareBuild(a)};oie.exports=PVe});var lie=_((dRt,aie)=>{var xVe=Vx(),kVe=(t,e)=>t.sort((r,s)=>xVe(r,s,e));aie.exports=kVe});var uie=_((mRt,cie)=>{var QVe=Vx(),TVe=(t,e)=>t.sort((r,s)=>QVe(s,r,e));cie.exports=TVe});var cB=_((yRt,fie)=>{var RVe=Bc(),FVe=(t,e,r)=>RVe(t,e,r)>0;fie.exports=FVe});var Jx=_((ERt,Aie)=>{var NVe=Bc(),OVe=(t,e,r)=>NVe(t,e,r)<0;Aie.exports=OVe});var T_=_((IRt,pie)=>{var LVe=Bc(),MVe=(t,e,r)=>LVe(t,e,r)===0;pie.exports=MVe});var R_=_((CRt,hie)=>{var UVe=Bc(),_Ve=(t,e,r)=>UVe(t,e,r)!==0;hie.exports=_Ve});var Kx=_((wRt,gie)=>{var HVe=Bc(),jVe=(t,e,r)=>HVe(t,e,r)>=0;gie.exports=jVe});var zx=_((BRt,die)=>{var GVe=Bc(),qVe=(t,e,r)=>GVe(t,e,r)<=0;die.exports=qVe});var F_=_((vRt,mie)=>{var WVe=T_(),YVe=R_(),VVe=cB(),JVe=Kx(),KVe=Jx(),zVe=zx(),XVe=(t,e,r,s)=>{switch(e){case"===":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t===r;case"!==":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t!==r;case"":case"=":case"==":return WVe(t,r,s);case"!=":return YVe(t,r,s);case">":return VVe(t,r,s);case">=":return JVe(t,r,s);case"<":return KVe(t,r,s);case"<=":return zVe(t,r,s);default:throw new TypeError(`Invalid operator: ${e}`)}};mie.exports=XVe});var Eie=_((SRt,yie)=>{var ZVe=jo(),$Ve=Md(),{safeRe:Xx,t:Zx}=vE(),e7e=(t,e)=>{if(t instanceof ZVe)return t;if(typeof t=="number"&&(t=String(t)),typeof t!="string")return null;e=e||{};let r=null;if(!e.rtl)r=t.match(e.includePrerelease?Xx[Zx.COERCEFULL]:Xx[Zx.COERCE]);else{let p=e.includePrerelease?Xx[Zx.COERCERTLFULL]:Xx[Zx.COERCERTL],h;for(;(h=p.exec(t))&&(!r||r.index+r[0].length!==t.length);)(!r||h.index+h[0].length!==r.index+r[0].length)&&(r=h),p.lastIndex=h.index+h[1].length+h[2].length;p.lastIndex=-1}if(r===null)return null;let s=r[2],a=r[3]||"0",n=r[4]||"0",c=e.includePrerelease&&r[5]?`-${r[5]}`:"",f=e.includePrerelease&&r[6]?`+${r[6]}`:"";return $Ve(`${s}.${a}.${n}${c}${f}`,e)};yie.exports=e7e});var Cie=_((DRt,Iie)=>{"use strict";Iie.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var $x=_((bRt,wie)=>{"use strict";wie.exports=Fn;Fn.Node=Ud;Fn.create=Fn;function Fn(t){var e=this;if(e instanceof Fn||(e=new Fn),e.tail=null,e.head=null,e.length=0,t&&typeof t.forEach=="function")t.forEach(function(a){e.push(a)});else if(arguments.length>0)for(var r=0,s=arguments.length;r1)r=e;else if(this.head)s=this.head.next,r=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=0;s!==null;a++)r=t(r,s.value,a),s=s.next;return r};Fn.prototype.reduceReverse=function(t,e){var r,s=this.tail;if(arguments.length>1)r=e;else if(this.tail)s=this.tail.prev,r=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=this.length-1;s!==null;a--)r=t(r,s.value,a),s=s.prev;return r};Fn.prototype.toArray=function(){for(var t=new Array(this.length),e=0,r=this.head;r!==null;e++)t[e]=r.value,r=r.next;return t};Fn.prototype.toArrayReverse=function(){for(var t=new Array(this.length),e=0,r=this.tail;r!==null;e++)t[e]=r.value,r=r.prev;return t};Fn.prototype.slice=function(t,e){e=e||this.length,e<0&&(e+=this.length),t=t||0,t<0&&(t+=this.length);var r=new Fn;if(ethis.length&&(e=this.length);for(var s=0,a=this.head;a!==null&&sthis.length&&(e=this.length);for(var s=this.length,a=this.tail;a!==null&&s>e;s--)a=a.prev;for(;a!==null&&s>t;s--,a=a.prev)r.push(a.value);return r};Fn.prototype.splice=function(t,e,...r){t>this.length&&(t=this.length-1),t<0&&(t=this.length+t);for(var s=0,a=this.head;a!==null&&s{"use strict";var i7e=$x(),_d=Symbol("max"),Sp=Symbol("length"),DE=Symbol("lengthCalculator"),fB=Symbol("allowStale"),Hd=Symbol("maxAge"),vp=Symbol("dispose"),Bie=Symbol("noDisposeOnSet"),Gs=Symbol("lruList"),Lu=Symbol("cache"),Sie=Symbol("updateAgeOnGet"),N_=()=>1,L_=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let r=this[_d]=e.max||1/0,s=e.length||N_;if(this[DE]=typeof s!="function"?N_:s,this[fB]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[Hd]=e.maxAge||0,this[vp]=e.dispose,this[Bie]=e.noDisposeOnSet||!1,this[Sie]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[_d]=e||1/0,uB(this)}get max(){return this[_d]}set allowStale(e){this[fB]=!!e}get allowStale(){return this[fB]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[Hd]=e,uB(this)}get maxAge(){return this[Hd]}set lengthCalculator(e){typeof e!="function"&&(e=N_),e!==this[DE]&&(this[DE]=e,this[Sp]=0,this[Gs].forEach(r=>{r.length=this[DE](r.value,r.key),this[Sp]+=r.length})),uB(this)}get lengthCalculator(){return this[DE]}get length(){return this[Sp]}get itemCount(){return this[Gs].length}rforEach(e,r){r=r||this;for(let s=this[Gs].tail;s!==null;){let a=s.prev;vie(this,e,s,r),s=a}}forEach(e,r){r=r||this;for(let s=this[Gs].head;s!==null;){let a=s.next;vie(this,e,s,r),s=a}}keys(){return this[Gs].toArray().map(e=>e.key)}values(){return this[Gs].toArray().map(e=>e.value)}reset(){this[vp]&&this[Gs]&&this[Gs].length&&this[Gs].forEach(e=>this[vp](e.key,e.value)),this[Lu]=new Map,this[Gs]=new i7e,this[Sp]=0}dump(){return this[Gs].map(e=>ek(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[Gs]}set(e,r,s){if(s=s||this[Hd],s&&typeof s!="number")throw new TypeError("maxAge must be a number");let a=s?Date.now():0,n=this[DE](r,e);if(this[Lu].has(e)){if(n>this[_d])return bE(this,this[Lu].get(e)),!1;let p=this[Lu].get(e).value;return this[vp]&&(this[Bie]||this[vp](e,p.value)),p.now=a,p.maxAge=s,p.value=r,this[Sp]+=n-p.length,p.length=n,this.get(e),uB(this),!0}let c=new M_(e,r,n,a,s);return c.length>this[_d]?(this[vp]&&this[vp](e,r),!1):(this[Sp]+=c.length,this[Gs].unshift(c),this[Lu].set(e,this[Gs].head),uB(this),!0)}has(e){if(!this[Lu].has(e))return!1;let r=this[Lu].get(e).value;return!ek(this,r)}get(e){return O_(this,e,!0)}peek(e){return O_(this,e,!1)}pop(){let e=this[Gs].tail;return e?(bE(this,e),e.value):null}del(e){bE(this,this[Lu].get(e))}load(e){this.reset();let r=Date.now();for(let s=e.length-1;s>=0;s--){let a=e[s],n=a.e||0;if(n===0)this.set(a.k,a.v);else{let c=n-r;c>0&&this.set(a.k,a.v,c)}}}prune(){this[Lu].forEach((e,r)=>O_(this,r,!1))}},O_=(t,e,r)=>{let s=t[Lu].get(e);if(s){let a=s.value;if(ek(t,a)){if(bE(t,s),!t[fB])return}else r&&(t[Sie]&&(s.value.now=Date.now()),t[Gs].unshiftNode(s));return a.value}},ek=(t,e)=>{if(!e||!e.maxAge&&!t[Hd])return!1;let r=Date.now()-e.now;return e.maxAge?r>e.maxAge:t[Hd]&&r>t[Hd]},uB=t=>{if(t[Sp]>t[_d])for(let e=t[Gs].tail;t[Sp]>t[_d]&&e!==null;){let r=e.prev;bE(t,e),e=r}},bE=(t,e)=>{if(e){let r=e.value;t[vp]&&t[vp](r.key,r.value),t[Sp]-=r.length,t[Lu].delete(r.key),t[Gs].removeNode(e)}},M_=class{constructor(e,r,s,a,n){this.key=e,this.value=r,this.length=s,this.now=a,this.maxAge=n||0}},vie=(t,e,r,s)=>{let a=r.value;ek(t,a)&&(bE(t,r),t[fB]||(a=void 0)),a&&e.call(s,a.value,a.key,t)};Die.exports=L_});var vc=_((xRt,Qie)=>{var U_=class t{constructor(e,r){if(r=o7e(r),e instanceof t)return e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease?e:new t(e.raw,r);if(e instanceof __)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease,this.raw=e.trim().split(/\s+/).join(" "),this.set=this.raw.split("||").map(s=>this.parseRange(s.trim())).filter(s=>s.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${this.raw}`);if(this.set.length>1){let s=this.set[0];if(this.set=this.set.filter(a=>!xie(a[0])),this.set.length===0)this.set=[s];else if(this.set.length>1){for(let a of this.set)if(a.length===1&&p7e(a[0])){this.set=[a];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){let s=((this.options.includePrerelease&&f7e)|(this.options.loose&&A7e))+":"+e,a=Pie.get(s);if(a)return a;let n=this.options.loose,c=n?sl[wa.HYPHENRANGELOOSE]:sl[wa.HYPHENRANGE];e=e.replace(c,B7e(this.options.includePrerelease)),vi("hyphen replace",e),e=e.replace(sl[wa.COMPARATORTRIM],l7e),vi("comparator trim",e),e=e.replace(sl[wa.TILDETRIM],c7e),vi("tilde trim",e),e=e.replace(sl[wa.CARETTRIM],u7e),vi("caret trim",e);let f=e.split(" ").map(C=>h7e(C,this.options)).join(" ").split(/\s+/).map(C=>w7e(C,this.options));n&&(f=f.filter(C=>(vi("loose invalid filter",C,this.options),!!C.match(sl[wa.COMPARATORLOOSE])))),vi("range list",f);let p=new Map,h=f.map(C=>new __(C,this.options));for(let C of h){if(xie(C))return[C];p.set(C.value,C)}p.size>1&&p.has("")&&p.delete("");let E=[...p.values()];return Pie.set(s,E),E}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Range is required");return this.set.some(s=>kie(s,r)&&e.set.some(a=>kie(a,r)&&s.every(n=>a.every(c=>n.intersects(c,r)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new a7e(e,this.options)}catch{return!1}for(let r=0;rt.value==="<0.0.0-0",p7e=t=>t.value==="",kie=(t,e)=>{let r=!0,s=t.slice(),a=s.pop();for(;r&&s.length;)r=s.every(n=>a.intersects(n,e)),a=s.pop();return r},h7e=(t,e)=>(vi("comp",t,e),t=m7e(t,e),vi("caret",t),t=g7e(t,e),vi("tildes",t),t=E7e(t,e),vi("xrange",t),t=C7e(t,e),vi("stars",t),t),Ba=t=>!t||t.toLowerCase()==="x"||t==="*",g7e=(t,e)=>t.trim().split(/\s+/).map(r=>d7e(r,e)).join(" "),d7e=(t,e)=>{let r=e.loose?sl[wa.TILDELOOSE]:sl[wa.TILDE];return t.replace(r,(s,a,n,c,f)=>{vi("tilde",t,s,a,n,c,f);let p;return Ba(a)?p="":Ba(n)?p=`>=${a}.0.0 <${+a+1}.0.0-0`:Ba(c)?p=`>=${a}.${n}.0 <${a}.${+n+1}.0-0`:f?(vi("replaceTilde pr",f),p=`>=${a}.${n}.${c}-${f} <${a}.${+n+1}.0-0`):p=`>=${a}.${n}.${c} <${a}.${+n+1}.0-0`,vi("tilde return",p),p})},m7e=(t,e)=>t.trim().split(/\s+/).map(r=>y7e(r,e)).join(" "),y7e=(t,e)=>{vi("caret",t,e);let r=e.loose?sl[wa.CARETLOOSE]:sl[wa.CARET],s=e.includePrerelease?"-0":"";return t.replace(r,(a,n,c,f,p)=>{vi("caret",t,a,n,c,f,p);let h;return Ba(n)?h="":Ba(c)?h=`>=${n}.0.0${s} <${+n+1}.0.0-0`:Ba(f)?n==="0"?h=`>=${n}.${c}.0${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.0${s} <${+n+1}.0.0-0`:p?(vi("replaceCaret pr",p),n==="0"?c==="0"?h=`>=${n}.${c}.${f}-${p} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}-${p} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f}-${p} <${+n+1}.0.0-0`):(vi("no pr"),n==="0"?c==="0"?h=`>=${n}.${c}.${f}${s} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f} <${+n+1}.0.0-0`),vi("caret return",h),h})},E7e=(t,e)=>(vi("replaceXRanges",t,e),t.split(/\s+/).map(r=>I7e(r,e)).join(" ")),I7e=(t,e)=>{t=t.trim();let r=e.loose?sl[wa.XRANGELOOSE]:sl[wa.XRANGE];return t.replace(r,(s,a,n,c,f,p)=>{vi("xRange",t,s,a,n,c,f,p);let h=Ba(n),E=h||Ba(c),C=E||Ba(f),S=C;return a==="="&&S&&(a=""),p=e.includePrerelease?"-0":"",h?a===">"||a==="<"?s="<0.0.0-0":s="*":a&&S?(E&&(c=0),f=0,a===">"?(a=">=",E?(n=+n+1,c=0,f=0):(c=+c+1,f=0)):a==="<="&&(a="<",E?n=+n+1:c=+c+1),a==="<"&&(p="-0"),s=`${a+n}.${c}.${f}${p}`):E?s=`>=${n}.0.0${p} <${+n+1}.0.0-0`:C&&(s=`>=${n}.${c}.0${p} <${n}.${+c+1}.0-0`),vi("xRange return",s),s})},C7e=(t,e)=>(vi("replaceStars",t,e),t.trim().replace(sl[wa.STAR],"")),w7e=(t,e)=>(vi("replaceGTE0",t,e),t.trim().replace(sl[e.includePrerelease?wa.GTE0PRE:wa.GTE0],"")),B7e=t=>(e,r,s,a,n,c,f,p,h,E,C,S,P)=>(Ba(s)?r="":Ba(a)?r=`>=${s}.0.0${t?"-0":""}`:Ba(n)?r=`>=${s}.${a}.0${t?"-0":""}`:c?r=`>=${r}`:r=`>=${r}${t?"-0":""}`,Ba(h)?p="":Ba(E)?p=`<${+h+1}.0.0-0`:Ba(C)?p=`<${h}.${+E+1}.0-0`:S?p=`<=${h}.${E}.${C}-${S}`:t?p=`<${h}.${E}.${+C+1}-0`:p=`<=${p}`,`${r} ${p}`.trim()),v7e=(t,e,r)=>{for(let s=0;s0){let a=t[s].semver;if(a.major===e.major&&a.minor===e.minor&&a.patch===e.patch)return!0}return!1}return!0}});var AB=_((kRt,Lie)=>{var pB=Symbol("SemVer ANY"),G_=class t{static get ANY(){return pB}constructor(e,r){if(r=Tie(r),e instanceof t){if(e.loose===!!r.loose)return e;e=e.value}e=e.trim().split(/\s+/).join(" "),j_("comparator",e,r),this.options=r,this.loose=!!r.loose,this.parse(e),this.semver===pB?this.value="":this.value=this.operator+this.semver.version,j_("comp",this)}parse(e){let r=this.options.loose?Rie[Fie.COMPARATORLOOSE]:Rie[Fie.COMPARATOR],s=e.match(r);if(!s)throw new TypeError(`Invalid comparator: ${e}`);this.operator=s[1]!==void 0?s[1]:"",this.operator==="="&&(this.operator=""),s[2]?this.semver=new Nie(s[2],this.options.loose):this.semver=pB}toString(){return this.value}test(e){if(j_("Comparator.test",e,this.options.loose),this.semver===pB||e===pB)return!0;if(typeof e=="string")try{e=new Nie(e,this.options)}catch{return!1}return H_(e,this.operator,this.semver,this.options)}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Comparator is required");return this.operator===""?this.value===""?!0:new Oie(e.value,r).test(this.value):e.operator===""?e.value===""?!0:new Oie(this.value,r).test(e.semver):(r=Tie(r),r.includePrerelease&&(this.value==="<0.0.0-0"||e.value==="<0.0.0-0")||!r.includePrerelease&&(this.value.startsWith("<0.0.0")||e.value.startsWith("<0.0.0"))?!1:!!(this.operator.startsWith(">")&&e.operator.startsWith(">")||this.operator.startsWith("<")&&e.operator.startsWith("<")||this.semver.version===e.semver.version&&this.operator.includes("=")&&e.operator.includes("=")||H_(this.semver,"<",e.semver,r)&&this.operator.startsWith(">")&&e.operator.startsWith("<")||H_(this.semver,">",e.semver,r)&&this.operator.startsWith("<")&&e.operator.startsWith(">")))}};Lie.exports=G_;var Tie=qx(),{safeRe:Rie,t:Fie}=vE(),H_=F_(),j_=lB(),Nie=jo(),Oie=vc()});var hB=_((QRt,Mie)=>{var S7e=vc(),D7e=(t,e,r)=>{try{e=new S7e(e,r)}catch{return!1}return e.test(t)};Mie.exports=D7e});var _ie=_((TRt,Uie)=>{var b7e=vc(),P7e=(t,e)=>new b7e(t,e).set.map(r=>r.map(s=>s.value).join(" ").trim().split(" "));Uie.exports=P7e});var jie=_((RRt,Hie)=>{var x7e=jo(),k7e=vc(),Q7e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new k7e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===-1)&&(s=c,a=new x7e(s,r))}),s};Hie.exports=Q7e});var qie=_((FRt,Gie)=>{var T7e=jo(),R7e=vc(),F7e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new R7e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===1)&&(s=c,a=new T7e(s,r))}),s};Gie.exports=F7e});var Vie=_((NRt,Yie)=>{var q_=jo(),N7e=vc(),Wie=cB(),O7e=(t,e)=>{t=new N7e(t,e);let r=new q_("0.0.0");if(t.test(r)||(r=new q_("0.0.0-0"),t.test(r)))return r;r=null;for(let s=0;s{let f=new q_(c.semver.version);switch(c.operator){case">":f.prerelease.length===0?f.patch++:f.prerelease.push(0),f.raw=f.format();case"":case">=":(!n||Wie(f,n))&&(n=f);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${c.operator}`)}}),n&&(!r||Wie(r,n))&&(r=n)}return r&&t.test(r)?r:null};Yie.exports=O7e});var Kie=_((ORt,Jie)=>{var L7e=vc(),M7e=(t,e)=>{try{return new L7e(t,e).range||"*"}catch{return null}};Jie.exports=M7e});var tk=_((LRt,$ie)=>{var U7e=jo(),Zie=AB(),{ANY:_7e}=Zie,H7e=vc(),j7e=hB(),zie=cB(),Xie=Jx(),G7e=zx(),q7e=Kx(),W7e=(t,e,r,s)=>{t=new U7e(t,s),e=new H7e(e,s);let a,n,c,f,p;switch(r){case">":a=zie,n=G7e,c=Xie,f=">",p=">=";break;case"<":a=Xie,n=q7e,c=zie,f="<",p="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(j7e(t,e,s))return!1;for(let h=0;h{P.semver===_7e&&(P=new Zie(">=0.0.0")),C=C||P,S=S||P,a(P.semver,C.semver,s)?C=P:c(P.semver,S.semver,s)&&(S=P)}),C.operator===f||C.operator===p||(!S.operator||S.operator===f)&&n(t,S.semver))return!1;if(S.operator===p&&c(t,S.semver))return!1}return!0};$ie.exports=W7e});var tse=_((MRt,ese)=>{var Y7e=tk(),V7e=(t,e,r)=>Y7e(t,e,">",r);ese.exports=V7e});var nse=_((URt,rse)=>{var J7e=tk(),K7e=(t,e,r)=>J7e(t,e,"<",r);rse.exports=K7e});var ose=_((_Rt,sse)=>{var ise=vc(),z7e=(t,e,r)=>(t=new ise(t,r),e=new ise(e,r),t.intersects(e,r));sse.exports=z7e});var lse=_((HRt,ase)=>{var X7e=hB(),Z7e=Bc();ase.exports=(t,e,r)=>{let s=[],a=null,n=null,c=t.sort((E,C)=>Z7e(E,C,r));for(let E of c)X7e(E,e,r)?(n=E,a||(a=E)):(n&&s.push([a,n]),n=null,a=null);a&&s.push([a,null]);let f=[];for(let[E,C]of s)E===C?f.push(E):!C&&E===c[0]?f.push("*"):C?E===c[0]?f.push(`<=${C}`):f.push(`${E} - ${C}`):f.push(`>=${E}`);let p=f.join(" || "),h=typeof e.raw=="string"?e.raw:String(e);return p.length{var cse=vc(),Y_=AB(),{ANY:W_}=Y_,gB=hB(),V_=Bc(),$7e=(t,e,r={})=>{if(t===e)return!0;t=new cse(t,r),e=new cse(e,r);let s=!1;e:for(let a of t.set){for(let n of e.set){let c=tJe(a,n,r);if(s=s||c!==null,c)continue e}if(s)return!1}return!0},eJe=[new Y_(">=0.0.0-0")],use=[new Y_(">=0.0.0")],tJe=(t,e,r)=>{if(t===e)return!0;if(t.length===1&&t[0].semver===W_){if(e.length===1&&e[0].semver===W_)return!0;r.includePrerelease?t=eJe:t=use}if(e.length===1&&e[0].semver===W_){if(r.includePrerelease)return!0;e=use}let s=new Set,a,n;for(let P of t)P.operator===">"||P.operator===">="?a=fse(a,P,r):P.operator==="<"||P.operator==="<="?n=Ase(n,P,r):s.add(P.semver);if(s.size>1)return null;let c;if(a&&n){if(c=V_(a.semver,n.semver,r),c>0)return null;if(c===0&&(a.operator!==">="||n.operator!=="<="))return null}for(let P of s){if(a&&!gB(P,String(a),r)||n&&!gB(P,String(n),r))return null;for(let I of e)if(!gB(P,String(I),r))return!1;return!0}let f,p,h,E,C=n&&!r.includePrerelease&&n.semver.prerelease.length?n.semver:!1,S=a&&!r.includePrerelease&&a.semver.prerelease.length?a.semver:!1;C&&C.prerelease.length===1&&n.operator==="<"&&C.prerelease[0]===0&&(C=!1);for(let P of e){if(E=E||P.operator===">"||P.operator===">=",h=h||P.operator==="<"||P.operator==="<=",a){if(S&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===S.major&&P.semver.minor===S.minor&&P.semver.patch===S.patch&&(S=!1),P.operator===">"||P.operator===">="){if(f=fse(a,P,r),f===P&&f!==a)return!1}else if(a.operator===">="&&!gB(a.semver,String(P),r))return!1}if(n){if(C&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===C.major&&P.semver.minor===C.minor&&P.semver.patch===C.patch&&(C=!1),P.operator==="<"||P.operator==="<="){if(p=Ase(n,P,r),p===P&&p!==n)return!1}else if(n.operator==="<="&&!gB(n.semver,String(P),r))return!1}if(!P.operator&&(n||a)&&c!==0)return!1}return!(a&&h&&!n&&c!==0||n&&E&&!a&&c!==0||S||C)},fse=(t,e,r)=>{if(!t)return e;let s=V_(t.semver,e.semver,r);return s>0?t:s<0||e.operator===">"&&t.operator===">="?e:t},Ase=(t,e,r)=>{if(!t)return e;let s=V_(t.semver,e.semver,r);return s<0?t:s>0||e.operator==="<"&&t.operator==="<="?e:t};pse.exports=$7e});var Ai=_((GRt,mse)=>{var J_=vE(),gse=aB(),rJe=jo(),dse=k_(),nJe=Md(),iJe=One(),sJe=Mne(),oJe=Hne(),aJe=qne(),lJe=Yne(),cJe=Jne(),uJe=zne(),fJe=Zne(),AJe=Bc(),pJe=rie(),hJe=iie(),gJe=Vx(),dJe=lie(),mJe=uie(),yJe=cB(),EJe=Jx(),IJe=T_(),CJe=R_(),wJe=Kx(),BJe=zx(),vJe=F_(),SJe=Eie(),DJe=AB(),bJe=vc(),PJe=hB(),xJe=_ie(),kJe=jie(),QJe=qie(),TJe=Vie(),RJe=Kie(),FJe=tk(),NJe=tse(),OJe=nse(),LJe=ose(),MJe=lse(),UJe=hse();mse.exports={parse:nJe,valid:iJe,clean:sJe,inc:oJe,diff:aJe,major:lJe,minor:cJe,patch:uJe,prerelease:fJe,compare:AJe,rcompare:pJe,compareLoose:hJe,compareBuild:gJe,sort:dJe,rsort:mJe,gt:yJe,lt:EJe,eq:IJe,neq:CJe,gte:wJe,lte:BJe,cmp:vJe,coerce:SJe,Comparator:DJe,Range:bJe,satisfies:PJe,toComparators:xJe,maxSatisfying:kJe,minSatisfying:QJe,minVersion:TJe,validRange:RJe,outside:FJe,gtr:NJe,ltr:OJe,intersects:LJe,simplifyRange:MJe,subset:UJe,SemVer:rJe,re:J_.re,src:J_.src,tokens:J_.t,SEMVER_SPEC_VERSION:gse.SEMVER_SPEC_VERSION,RELEASE_TYPES:gse.RELEASE_TYPES,compareIdentifiers:dse.compareIdentifiers,rcompareIdentifiers:dse.rcompareIdentifiers}});var Ese=_((qRt,yse)=>{"use strict";function _Je(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function jd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,jd)}_Je(jd,Error);jd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C{switch(Te[1]){case"|":return xe|Te[3];case"&":return xe&Te[3];case"^":return xe^Te[3]}},$)},S="!",P=Fe("!",!1),I=function($){return!$},R="(",N=Fe("(",!1),U=")",W=Fe(")",!1),ee=function($){return $},ie=/^[^ \t\n\r()!|&\^]/,ue=Ne([" "," ",` +`,"\r","(",")","!","|","&","^"],!0,!1),le=function($){return e.queryPattern.test($)},me=function($){return e.checkFn($)},pe=ke("whitespace"),Be=/^[ \t\n\r]/,Ce=Ne([" "," ",` +`,"\r"],!1,!1),g=0,we=0,ye=[{line:1,column:1}],Ae=0,se=[],Z=0,De;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function Re(){return t.substring(we,g)}function mt(){return Ue(we,g)}function j($,oe){throw oe=oe!==void 0?oe:Ue(we,g),b([ke($)],t.substring(we,g),oe)}function rt($,oe){throw oe=oe!==void 0?oe:Ue(we,g),w($,oe)}function Fe($,oe){return{type:"literal",text:$,ignoreCase:oe}}function Ne($,oe,xe){return{type:"class",parts:$,inverted:oe,ignoreCase:xe}}function Pe(){return{type:"any"}}function Ve(){return{type:"end"}}function ke($){return{type:"other",description:$}}function it($){var oe=ye[$],xe;if(oe)return oe;for(xe=$-1;!ye[xe];)xe--;for(oe=ye[xe],oe={line:oe.line,column:oe.column};xe<$;)t.charCodeAt(xe)===10?(oe.line++,oe.column=1):oe.column++,xe++;return ye[$]=oe,oe}function Ue($,oe){var xe=it($),Te=it(oe);return{start:{offset:$,line:xe.line,column:xe.column},end:{offset:oe,line:Te.line,column:Te.column}}}function x($){gAe&&(Ae=g,se=[]),se.push($))}function w($,oe){return new jd($,null,null,oe)}function b($,oe,xe){return new jd(jd.buildMessage($,oe),$,oe,xe)}function y(){var $,oe,xe,Te,lt,Ct,qt,ir;if($=g,oe=F(),oe!==r){for(xe=[],Te=g,lt=X(),lt!==r?(t.charCodeAt(g)===124?(Ct=n,g++):(Ct=r,Z===0&&x(c)),Ct===r&&(t.charCodeAt(g)===38?(Ct=f,g++):(Ct=r,Z===0&&x(p)),Ct===r&&(t.charCodeAt(g)===94?(Ct=h,g++):(Ct=r,Z===0&&x(E)))),Ct!==r?(qt=X(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Ct,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);Te!==r;)xe.push(Te),Te=g,lt=X(),lt!==r?(t.charCodeAt(g)===124?(Ct=n,g++):(Ct=r,Z===0&&x(c)),Ct===r&&(t.charCodeAt(g)===38?(Ct=f,g++):(Ct=r,Z===0&&x(p)),Ct===r&&(t.charCodeAt(g)===94?(Ct=h,g++):(Ct=r,Z===0&&x(E)))),Ct!==r?(qt=X(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Ct,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);xe!==r?(we=$,oe=C(oe,xe),$=oe):(g=$,$=r)}else g=$,$=r;return $}function F(){var $,oe,xe,Te,lt,Ct;return $=g,t.charCodeAt(g)===33?(oe=S,g++):(oe=r,Z===0&&x(P)),oe!==r?(xe=F(),xe!==r?(we=$,oe=I(xe),$=oe):(g=$,$=r)):(g=$,$=r),$===r&&($=g,t.charCodeAt(g)===40?(oe=R,g++):(oe=r,Z===0&&x(N)),oe!==r?(xe=X(),xe!==r?(Te=y(),Te!==r?(lt=X(),lt!==r?(t.charCodeAt(g)===41?(Ct=U,g++):(Ct=r,Z===0&&x(W)),Ct!==r?(we=$,oe=ee(Te),$=oe):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r),$===r&&($=z())),$}function z(){var $,oe,xe,Te,lt;if($=g,oe=X(),oe!==r){if(xe=g,Te=[],ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,Z===0&&x(ue)),lt!==r)for(;lt!==r;)Te.push(lt),ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,Z===0&&x(ue));else Te=r;Te!==r?xe=t.substring(xe,g):xe=Te,xe!==r?(we=g,Te=le(xe),Te?Te=void 0:Te=r,Te!==r?(we=$,oe=me(xe),$=oe):(g=$,$=r)):(g=$,$=r)}else g=$,$=r;return $}function X(){var $,oe;for(Z++,$=[],Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,Z===0&&x(Ce));oe!==r;)$.push(oe),Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,Z===0&&x(Ce));return Z--,$===r&&(oe=r,Z===0&&x(pe)),$}if(De=a(),De!==r&&g===t.length)return De;throw De!==r&&g{var{parse:jJe}=Ese();rk.makeParser=(t=/[a-z]+/)=>(e,r)=>jJe(e,{queryPattern:t,checkFn:r});rk.parse=rk.makeParser()});var wse=_((YRt,Cse)=>{"use strict";Cse.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});var K_=_((VRt,vse)=>{var dB=wse(),Bse={};for(let t of Object.keys(dB))Bse[dB[t]]=t;var hr={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};vse.exports=hr;for(let t of Object.keys(hr)){if(!("channels"in hr[t]))throw new Error("missing channels property: "+t);if(!("labels"in hr[t]))throw new Error("missing channel labels property: "+t);if(hr[t].labels.length!==hr[t].channels)throw new Error("channel and label counts mismatch: "+t);let{channels:e,labels:r}=hr[t];delete hr[t].channels,delete hr[t].labels,Object.defineProperty(hr[t],"channels",{value:e}),Object.defineProperty(hr[t],"labels",{value:r})}hr.rgb.hsl=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(e,r,s),n=Math.max(e,r,s),c=n-a,f,p;n===a?f=0:e===n?f=(r-s)/c:r===n?f=2+(s-e)/c:s===n&&(f=4+(e-r)/c),f=Math.min(f*60,360),f<0&&(f+=360);let h=(a+n)/2;return n===a?p=0:h<=.5?p=c/(n+a):p=c/(2-n-a),[f,p*100,h*100]};hr.rgb.hsv=function(t){let e,r,s,a,n,c=t[0]/255,f=t[1]/255,p=t[2]/255,h=Math.max(c,f,p),E=h-Math.min(c,f,p),C=function(S){return(h-S)/6/E+1/2};return E===0?(a=0,n=0):(n=E/h,e=C(c),r=C(f),s=C(p),c===h?a=s-r:f===h?a=1/3+e-s:p===h&&(a=2/3+r-e),a<0?a+=1:a>1&&(a-=1)),[a*360,n*100,h*100]};hr.rgb.hwb=function(t){let e=t[0],r=t[1],s=t[2],a=hr.rgb.hsl(t)[0],n=1/255*Math.min(e,Math.min(r,s));return s=1-1/255*Math.max(e,Math.max(r,s)),[a,n*100,s*100]};hr.rgb.cmyk=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(1-e,1-r,1-s),n=(1-e-a)/(1-a)||0,c=(1-r-a)/(1-a)||0,f=(1-s-a)/(1-a)||0;return[n*100,c*100,f*100,a*100]};function GJe(t,e){return(t[0]-e[0])**2+(t[1]-e[1])**2+(t[2]-e[2])**2}hr.rgb.keyword=function(t){let e=Bse[t];if(e)return e;let r=1/0,s;for(let a of Object.keys(dB)){let n=dB[a],c=GJe(t,n);c.04045?((e+.055)/1.055)**2.4:e/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92,s=s>.04045?((s+.055)/1.055)**2.4:s/12.92;let a=e*.4124+r*.3576+s*.1805,n=e*.2126+r*.7152+s*.0722,c=e*.0193+r*.1192+s*.9505;return[a*100,n*100,c*100]};hr.rgb.lab=function(t){let e=hr.rgb.xyz(t),r=e[0],s=e[1],a=e[2];r/=95.047,s/=100,a/=108.883,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,a=a>.008856?a**(1/3):7.787*a+16/116;let n=116*s-16,c=500*(r-s),f=200*(s-a);return[n,c,f]};hr.hsl.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a,n,c;if(r===0)return c=s*255,[c,c,c];s<.5?a=s*(1+r):a=s+r-s*r;let f=2*s-a,p=[0,0,0];for(let h=0;h<3;h++)n=e+1/3*-(h-1),n<0&&n++,n>1&&n--,6*n<1?c=f+(a-f)*6*n:2*n<1?c=a:3*n<2?c=f+(a-f)*(2/3-n)*6:c=f,p[h]=c*255;return p};hr.hsl.hsv=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=r,n=Math.max(s,.01);s*=2,r*=s<=1?s:2-s,a*=n<=1?n:2-n;let c=(s+r)/2,f=s===0?2*a/(n+a):2*r/(s+r);return[e,f*100,c*100]};hr.hsv.rgb=function(t){let e=t[0]/60,r=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,n=e-Math.floor(e),c=255*s*(1-r),f=255*s*(1-r*n),p=255*s*(1-r*(1-n));switch(s*=255,a){case 0:return[s,p,c];case 1:return[f,s,c];case 2:return[c,s,p];case 3:return[c,f,s];case 4:return[p,c,s];case 5:return[s,c,f]}};hr.hsv.hsl=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=Math.max(s,.01),n,c;c=(2-r)*s;let f=(2-r)*a;return n=r*a,n/=f<=1?f:2-f,n=n||0,c/=2,[e,n*100,c*100]};hr.hwb.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a=r+s,n;a>1&&(r/=a,s/=a);let c=Math.floor(6*e),f=1-s;n=6*e-c,c&1&&(n=1-n);let p=r+n*(f-r),h,E,C;switch(c){default:case 6:case 0:h=f,E=p,C=r;break;case 1:h=p,E=f,C=r;break;case 2:h=r,E=f,C=p;break;case 3:h=r,E=p,C=f;break;case 4:h=p,E=r,C=f;break;case 5:h=f,E=r,C=p;break}return[h*255,E*255,C*255]};hr.cmyk.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a=t[3]/100,n=1-Math.min(1,e*(1-a)+a),c=1-Math.min(1,r*(1-a)+a),f=1-Math.min(1,s*(1-a)+a);return[n*255,c*255,f*255]};hr.xyz.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a,n,c;return a=e*3.2406+r*-1.5372+s*-.4986,n=e*-.9689+r*1.8758+s*.0415,c=e*.0557+r*-.204+s*1.057,a=a>.0031308?1.055*a**(1/2.4)-.055:a*12.92,n=n>.0031308?1.055*n**(1/2.4)-.055:n*12.92,c=c>.0031308?1.055*c**(1/2.4)-.055:c*12.92,a=Math.min(Math.max(0,a),1),n=Math.min(Math.max(0,n),1),c=Math.min(Math.max(0,c),1),[a*255,n*255,c*255]};hr.xyz.lab=function(t){let e=t[0],r=t[1],s=t[2];e/=95.047,r/=100,s/=108.883,e=e>.008856?e**(1/3):7.787*e+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116;let a=116*r-16,n=500*(e-r),c=200*(r-s);return[a,n,c]};hr.lab.xyz=function(t){let e=t[0],r=t[1],s=t[2],a,n,c;n=(e+16)/116,a=r/500+n,c=n-s/200;let f=n**3,p=a**3,h=c**3;return n=f>.008856?f:(n-16/116)/7.787,a=p>.008856?p:(a-16/116)/7.787,c=h>.008856?h:(c-16/116)/7.787,a*=95.047,n*=100,c*=108.883,[a,n,c]};hr.lab.lch=function(t){let e=t[0],r=t[1],s=t[2],a;a=Math.atan2(s,r)*360/2/Math.PI,a<0&&(a+=360);let c=Math.sqrt(r*r+s*s);return[e,c,a]};hr.lch.lab=function(t){let e=t[0],r=t[1],a=t[2]/360*2*Math.PI,n=r*Math.cos(a),c=r*Math.sin(a);return[e,n,c]};hr.rgb.ansi16=function(t,e=null){let[r,s,a]=t,n=e===null?hr.rgb.hsv(t)[2]:e;if(n=Math.round(n/50),n===0)return 30;let c=30+(Math.round(a/255)<<2|Math.round(s/255)<<1|Math.round(r/255));return n===2&&(c+=60),c};hr.hsv.ansi16=function(t){return hr.rgb.ansi16(hr.hsv.rgb(t),t[2])};hr.rgb.ansi256=function(t){let e=t[0],r=t[1],s=t[2];return e===r&&r===s?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(s/255*5)};hr.ansi16.rgb=function(t){let e=t%10;if(e===0||e===7)return t>50&&(e+=3.5),e=e/10.5*255,[e,e,e];let r=(~~(t>50)+1)*.5,s=(e&1)*r*255,a=(e>>1&1)*r*255,n=(e>>2&1)*r*255;return[s,a,n]};hr.ansi256.rgb=function(t){if(t>=232){let n=(t-232)*10+8;return[n,n,n]}t-=16;let e,r=Math.floor(t/36)/5*255,s=Math.floor((e=t%36)/6)/5*255,a=e%6/5*255;return[r,s,a]};hr.rgb.hex=function(t){let r=(((Math.round(t[0])&255)<<16)+((Math.round(t[1])&255)<<8)+(Math.round(t[2])&255)).toString(16).toUpperCase();return"000000".substring(r.length)+r};hr.hex.rgb=function(t){let e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];let r=e[0];e[0].length===3&&(r=r.split("").map(f=>f+f).join(""));let s=parseInt(r,16),a=s>>16&255,n=s>>8&255,c=s&255;return[a,n,c]};hr.rgb.hcg=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.max(Math.max(e,r),s),n=Math.min(Math.min(e,r),s),c=a-n,f,p;return c<1?f=n/(1-c):f=0,c<=0?p=0:a===e?p=(r-s)/c%6:a===r?p=2+(s-e)/c:p=4+(e-r)/c,p/=6,p%=1,[p*360,c*100,f*100]};hr.hsl.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=r<.5?2*e*r:2*e*(1-r),a=0;return s<1&&(a=(r-.5*s)/(1-s)),[t[0],s*100,a*100]};hr.hsv.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=e*r,a=0;return s<1&&(a=(r-s)/(1-s)),[t[0],s*100,a*100]};hr.hcg.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100;if(r===0)return[s*255,s*255,s*255];let a=[0,0,0],n=e%1*6,c=n%1,f=1-c,p=0;switch(Math.floor(n)){case 0:a[0]=1,a[1]=c,a[2]=0;break;case 1:a[0]=f,a[1]=1,a[2]=0;break;case 2:a[0]=0,a[1]=1,a[2]=c;break;case 3:a[0]=0,a[1]=f,a[2]=1;break;case 4:a[0]=c,a[1]=0,a[2]=1;break;default:a[0]=1,a[1]=0,a[2]=f}return p=(1-r)*s,[(r*a[0]+p)*255,(r*a[1]+p)*255,(r*a[2]+p)*255]};hr.hcg.hsv=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e),a=0;return s>0&&(a=e/s),[t[0],a*100,s*100]};hr.hcg.hsl=function(t){let e=t[1]/100,s=t[2]/100*(1-e)+.5*e,a=0;return s>0&&s<.5?a=e/(2*s):s>=.5&&s<1&&(a=e/(2*(1-s))),[t[0],a*100,s*100]};hr.hcg.hwb=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e);return[t[0],(s-e)*100,(1-s)*100]};hr.hwb.hcg=function(t){let e=t[1]/100,s=1-t[2]/100,a=s-e,n=0;return a<1&&(n=(s-a)/(1-a)),[t[0],a*100,n*100]};hr.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]};hr.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]};hr.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]};hr.gray.hsl=function(t){return[0,0,t[0]]};hr.gray.hsv=hr.gray.hsl;hr.gray.hwb=function(t){return[0,100,t[0]]};hr.gray.cmyk=function(t){return[0,0,0,t[0]]};hr.gray.lab=function(t){return[t[0],0,0]};hr.gray.hex=function(t){let e=Math.round(t[0]/100*255)&255,s=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(s.length)+s};hr.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}});var Dse=_((JRt,Sse)=>{var nk=K_();function qJe(){let t={},e=Object.keys(nk);for(let r=e.length,s=0;s{var z_=K_(),JJe=Dse(),PE={},KJe=Object.keys(z_);function zJe(t){let e=function(...r){let s=r[0];return s==null?s:(s.length>1&&(r=s),t(r))};return"conversion"in t&&(e.conversion=t.conversion),e}function XJe(t){let e=function(...r){let s=r[0];if(s==null)return s;s.length>1&&(r=s);let a=t(r);if(typeof a=="object")for(let n=a.length,c=0;c{PE[t]={},Object.defineProperty(PE[t],"channels",{value:z_[t].channels}),Object.defineProperty(PE[t],"labels",{value:z_[t].labels});let e=JJe(t);Object.keys(e).forEach(s=>{let a=e[s];PE[t][s]=XJe(a),PE[t][s].raw=zJe(a)})});bse.exports=PE});var sk=_((zRt,Rse)=>{"use strict";var xse=(t,e)=>(...r)=>`\x1B[${t(...r)+e}m`,kse=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};5;${s}m`},Qse=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};2;${s[0]};${s[1]};${s[2]}m`},ik=t=>t,Tse=(t,e,r)=>[t,e,r],xE=(t,e,r)=>{Object.defineProperty(t,e,{get:()=>{let s=r();return Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0}),s},enumerable:!0,configurable:!0})},X_,kE=(t,e,r,s)=>{X_===void 0&&(X_=Pse());let a=s?10:0,n={};for(let[c,f]of Object.entries(X_)){let p=c==="ansi16"?"ansi":c;c===e?n[p]=t(r,a):typeof f=="object"&&(n[p]=t(f[e],a))}return n};function ZJe(){let t=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[r,s]of Object.entries(e)){for(let[a,n]of Object.entries(s))e[a]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},s[a]=e[a],t.set(n[0],n[1]);Object.defineProperty(e,r,{value:s,enumerable:!1})}return Object.defineProperty(e,"codes",{value:t,enumerable:!1}),e.color.close="\x1B[39m",e.bgColor.close="\x1B[49m",xE(e.color,"ansi",()=>kE(xse,"ansi16",ik,!1)),xE(e.color,"ansi256",()=>kE(kse,"ansi256",ik,!1)),xE(e.color,"ansi16m",()=>kE(Qse,"rgb",Tse,!1)),xE(e.bgColor,"ansi",()=>kE(xse,"ansi16",ik,!0)),xE(e.bgColor,"ansi256",()=>kE(kse,"ansi256",ik,!0)),xE(e.bgColor,"ansi16m",()=>kE(Qse,"rgb",Tse,!0)),e}Object.defineProperty(Rse,"exports",{enumerable:!0,get:ZJe})});var Nse=_((XRt,Fse)=>{"use strict";Fse.exports=(t,e=process.argv)=>{let r=t.startsWith("-")?"":t.length===1?"-":"--",s=e.indexOf(r+t),a=e.indexOf("--");return s!==-1&&(a===-1||s{"use strict";var $Je=Ie("os"),Ose=Ie("tty"),Sc=Nse(),{env:bs}=process,l0;Sc("no-color")||Sc("no-colors")||Sc("color=false")||Sc("color=never")?l0=0:(Sc("color")||Sc("colors")||Sc("color=true")||Sc("color=always"))&&(l0=1);"FORCE_COLOR"in bs&&(bs.FORCE_COLOR==="true"?l0=1:bs.FORCE_COLOR==="false"?l0=0:l0=bs.FORCE_COLOR.length===0?1:Math.min(parseInt(bs.FORCE_COLOR,10),3));function Z_(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function $_(t,e){if(l0===0)return 0;if(Sc("color=16m")||Sc("color=full")||Sc("color=truecolor"))return 3;if(Sc("color=256"))return 2;if(t&&!e&&l0===void 0)return 0;let r=l0||0;if(bs.TERM==="dumb")return r;if(process.platform==="win32"){let s=$Je.release().split(".");return Number(s[0])>=10&&Number(s[2])>=10586?Number(s[2])>=14931?3:2:1}if("CI"in bs)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some(s=>s in bs)||bs.CI_NAME==="codeship"?1:r;if("TEAMCITY_VERSION"in bs)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(bs.TEAMCITY_VERSION)?1:0;if("GITHUB_ACTIONS"in bs)return 1;if(bs.COLORTERM==="truecolor")return 3;if("TERM_PROGRAM"in bs){let s=parseInt((bs.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(bs.TERM_PROGRAM){case"iTerm.app":return s>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(bs.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(bs.TERM)||"COLORTERM"in bs?1:r}function eKe(t){let e=$_(t,t&&t.isTTY);return Z_(e)}Lse.exports={supportsColor:eKe,stdout:Z_($_(!0,Ose.isatty(1))),stderr:Z_($_(!0,Ose.isatty(2)))}});var _se=_(($Rt,Use)=>{"use strict";var tKe=(t,e,r)=>{let s=t.indexOf(e);if(s===-1)return t;let a=e.length,n=0,c="";do c+=t.substr(n,s-n)+e+r,n=s+a,s=t.indexOf(e,n);while(s!==-1);return c+=t.substr(n),c},rKe=(t,e,r,s)=>{let a=0,n="";do{let c=t[s-1]==="\r";n+=t.substr(a,(c?s-1:s)-a)+e+(c?`\r +`:` +`)+r,a=s+1,s=t.indexOf(` +`,a)}while(s!==-1);return n+=t.substr(a),n};Use.exports={stringReplaceAll:tKe,stringEncaseCRLFWithFirstIndex:rKe}});var Wse=_((eFt,qse)=>{"use strict";var nKe=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,Hse=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,iKe=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,sKe=/\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi,oKe=new Map([["n",` +`],["r","\r"],["t"," "],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e","\x1B"],["a","\x07"]]);function Gse(t){let e=t[0]==="u",r=t[1]==="{";return e&&!r&&t.length===5||t[0]==="x"&&t.length===3?String.fromCharCode(parseInt(t.slice(1),16)):e&&r?String.fromCodePoint(parseInt(t.slice(2,-1),16)):oKe.get(t)||t}function aKe(t,e){let r=[],s=e.trim().split(/\s*,\s*/g),a;for(let n of s){let c=Number(n);if(!Number.isNaN(c))r.push(c);else if(a=n.match(iKe))r.push(a[2].replace(sKe,(f,p,h)=>p?Gse(p):h));else throw new Error(`Invalid Chalk template style argument: ${n} (in style '${t}')`)}return r}function lKe(t){Hse.lastIndex=0;let e=[],r;for(;(r=Hse.exec(t))!==null;){let s=r[1];if(r[2]){let a=aKe(s,r[2]);e.push([s].concat(a))}else e.push([s])}return e}function jse(t,e){let r={};for(let a of e)for(let n of a.styles)r[n[0]]=a.inverse?null:n.slice(1);let s=t;for(let[a,n]of Object.entries(r))if(Array.isArray(n)){if(!(a in s))throw new Error(`Unknown Chalk style: ${a}`);s=n.length>0?s[a](...n):s[a]}return s}qse.exports=(t,e)=>{let r=[],s=[],a=[];if(e.replace(nKe,(n,c,f,p,h,E)=>{if(c)a.push(Gse(c));else if(p){let C=a.join("");a=[],s.push(r.length===0?C:jse(t,r)(C)),r.push({inverse:f,styles:lKe(p)})}else if(h){if(r.length===0)throw new Error("Found extraneous } in Chalk template literal");s.push(jse(t,r)(a.join(""))),a=[],r.pop()}else a.push(E)}),s.push(a.join("")),r.length>0){let n=`Chalk template literal is missing ${r.length} closing bracket${r.length===1?"":"s"} (\`}\`)`;throw new Error(n)}return s.join("")}});var TE=_((tFt,Xse)=>{"use strict";var mB=sk(),{stdout:t4,stderr:r4}=Mse(),{stringReplaceAll:cKe,stringEncaseCRLFWithFirstIndex:uKe}=_se(),{isArray:ok}=Array,Vse=["ansi","ansi","ansi256","ansi16m"],QE=Object.create(null),fKe=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");let r=t4?t4.level:0;t.level=e.level===void 0?r:e.level},n4=class{constructor(e){return Jse(e)}},Jse=t=>{let e={};return fKe(e,t),e.template=(...r)=>zse(e.template,...r),Object.setPrototypeOf(e,ak.prototype),Object.setPrototypeOf(e.template,e),e.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},e.template.Instance=n4,e.template};function ak(t){return Jse(t)}for(let[t,e]of Object.entries(mB))QE[t]={get(){let r=lk(this,i4(e.open,e.close,this._styler),this._isEmpty);return Object.defineProperty(this,t,{value:r}),r}};QE.visible={get(){let t=lk(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:t}),t}};var Kse=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(let t of Kse)QE[t]={get(){let{level:e}=this;return function(...r){let s=i4(mB.color[Vse[e]][t](...r),mB.color.close,this._styler);return lk(this,s,this._isEmpty)}}};for(let t of Kse){let e="bg"+t[0].toUpperCase()+t.slice(1);QE[e]={get(){let{level:r}=this;return function(...s){let a=i4(mB.bgColor[Vse[r]][t](...s),mB.bgColor.close,this._styler);return lk(this,a,this._isEmpty)}}}}var AKe=Object.defineProperties(()=>{},{...QE,level:{enumerable:!0,get(){return this._generator.level},set(t){this._generator.level=t}}}),i4=(t,e,r)=>{let s,a;return r===void 0?(s=t,a=e):(s=r.openAll+t,a=e+r.closeAll),{open:t,close:e,openAll:s,closeAll:a,parent:r}},lk=(t,e,r)=>{let s=(...a)=>ok(a[0])&&ok(a[0].raw)?Yse(s,zse(s,...a)):Yse(s,a.length===1?""+a[0]:a.join(" "));return Object.setPrototypeOf(s,AKe),s._generator=t,s._styler=e,s._isEmpty=r,s},Yse=(t,e)=>{if(t.level<=0||!e)return t._isEmpty?"":e;let r=t._styler;if(r===void 0)return e;let{openAll:s,closeAll:a}=r;if(e.indexOf("\x1B")!==-1)for(;r!==void 0;)e=cKe(e,r.close,r.open),r=r.parent;let n=e.indexOf(` +`);return n!==-1&&(e=uKe(e,a,s,n)),s+e+a},e4,zse=(t,...e)=>{let[r]=e;if(!ok(r)||!ok(r.raw))return e.join(" ");let s=e.slice(1),a=[r.raw[0]];for(let n=1;n{"use strict";Dc.isInteger=t=>typeof t=="number"?Number.isInteger(t):typeof t=="string"&&t.trim()!==""?Number.isInteger(Number(t)):!1;Dc.find=(t,e)=>t.nodes.find(r=>r.type===e);Dc.exceedsLimit=(t,e,r=1,s)=>s===!1||!Dc.isInteger(t)||!Dc.isInteger(e)?!1:(Number(e)-Number(t))/Number(r)>=s;Dc.escapeNode=(t,e=0,r)=>{let s=t.nodes[e];s&&(r&&s.type===r||s.type==="open"||s.type==="close")&&s.escaped!==!0&&(s.value="\\"+s.value,s.escaped=!0)};Dc.encloseBrace=t=>t.type!=="brace"||t.commas>>0+t.ranges>>0?!1:(t.invalid=!0,!0);Dc.isInvalidBrace=t=>t.type!=="brace"?!1:t.invalid===!0||t.dollar?!0:!(t.commas>>0+t.ranges>>0)||t.open!==!0||t.close!==!0?(t.invalid=!0,!0):!1;Dc.isOpenOrClose=t=>t.type==="open"||t.type==="close"?!0:t.open===!0||t.close===!0;Dc.reduce=t=>t.reduce((e,r)=>(r.type==="text"&&e.push(r.value),r.type==="range"&&(r.type="text"),e),[]);Dc.flatten=(...t)=>{let e=[],r=s=>{for(let a=0;a{"use strict";var Zse=uk();$se.exports=(t,e={})=>{let r=(s,a={})=>{let n=e.escapeInvalid&&Zse.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f="";if(s.value)return(n||c)&&Zse.isOpenOrClose(s)?"\\"+s.value:s.value;if(s.value)return s.value;if(s.nodes)for(let p of s.nodes)f+=r(p);return f};return r(t)}});var toe=_((iFt,eoe)=>{"use strict";eoe.exports=function(t){return typeof t=="number"?t-t===0:typeof t=="string"&&t.trim()!==""?Number.isFinite?Number.isFinite(+t):isFinite(+t):!1}});var uoe=_((sFt,coe)=>{"use strict";var roe=toe(),Gd=(t,e,r)=>{if(roe(t)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(e===void 0||t===e)return String(t);if(roe(e)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let s={relaxZeros:!0,...r};typeof s.strictZeros=="boolean"&&(s.relaxZeros=s.strictZeros===!1);let a=String(s.relaxZeros),n=String(s.shorthand),c=String(s.capture),f=String(s.wrap),p=t+":"+e+"="+a+n+c+f;if(Gd.cache.hasOwnProperty(p))return Gd.cache[p].result;let h=Math.min(t,e),E=Math.max(t,e);if(Math.abs(h-E)===1){let R=t+"|"+e;return s.capture?`(${R})`:s.wrap===!1?R:`(?:${R})`}let C=loe(t)||loe(e),S={min:t,max:e,a:h,b:E},P=[],I=[];if(C&&(S.isPadded=C,S.maxLen=String(S.max).length),h<0){let R=E<0?Math.abs(E):1;I=noe(R,Math.abs(h),S,s),h=S.a=0}return E>=0&&(P=noe(h,E,S,s)),S.negatives=I,S.positives=P,S.result=pKe(I,P,s),s.capture===!0?S.result=`(${S.result})`:s.wrap!==!1&&P.length+I.length>1&&(S.result=`(?:${S.result})`),Gd.cache[p]=S,S.result};function pKe(t,e,r){let s=s4(t,e,"-",!1,r)||[],a=s4(e,t,"",!1,r)||[],n=s4(t,e,"-?",!0,r)||[];return s.concat(n).concat(a).join("|")}function hKe(t,e){let r=1,s=1,a=soe(t,r),n=new Set([e]);for(;t<=a&&a<=e;)n.add(a),r+=1,a=soe(t,r);for(a=ooe(e+1,s)-1;t1&&f.count.pop(),f.count.push(E.count[0]),f.string=f.pattern+aoe(f.count),c=h+1;continue}r.isPadded&&(C=EKe(h,r,s)),E.string=C+E.pattern+aoe(E.count),n.push(E),c=h+1,f=E}return n}function s4(t,e,r,s,a){let n=[];for(let c of t){let{string:f}=c;!s&&!ioe(e,"string",f)&&n.push(r+f),s&&ioe(e,"string",f)&&n.push(r+f)}return n}function dKe(t,e){let r=[];for(let s=0;se?1:e>t?-1:0}function ioe(t,e,r){return t.some(s=>s[e]===r)}function soe(t,e){return Number(String(t).slice(0,-e)+"9".repeat(e))}function ooe(t,e){return t-t%Math.pow(10,e)}function aoe(t){let[e=0,r=""]=t;return r||e>1?`{${e+(r?","+r:"")}}`:""}function yKe(t,e,r){return`[${t}${e-t===1?"":"-"}${e}]`}function loe(t){return/^-?(0+)\d/.test(t)}function EKe(t,e,r){if(!e.isPadded)return t;let s=Math.abs(e.maxLen-String(t).length),a=r.relaxZeros!==!1;switch(s){case 0:return"";case 1:return a?"0?":"0";case 2:return a?"0{0,2}":"00";default:return a?`0{0,${s}}`:`0{${s}}`}}Gd.cache={};Gd.clearCache=()=>Gd.cache={};coe.exports=Gd});var l4=_((oFt,yoe)=>{"use strict";var IKe=Ie("util"),poe=uoe(),foe=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),CKe=t=>e=>t===!0?Number(e):String(e),o4=t=>typeof t=="number"||typeof t=="string"&&t!=="",yB=t=>Number.isInteger(+t),a4=t=>{let e=`${t}`,r=-1;if(e[0]==="-"&&(e=e.slice(1)),e==="0")return!1;for(;e[++r]==="0";);return r>0},wKe=(t,e,r)=>typeof t=="string"||typeof e=="string"?!0:r.stringify===!0,BKe=(t,e,r)=>{if(e>0){let s=t[0]==="-"?"-":"";s&&(t=t.slice(1)),t=s+t.padStart(s?e-1:e,"0")}return r===!1?String(t):t},Aoe=(t,e)=>{let r=t[0]==="-"?"-":"";for(r&&(t=t.slice(1),e--);t.length{t.negatives.sort((c,f)=>cf?1:0),t.positives.sort((c,f)=>cf?1:0);let r=e.capture?"":"?:",s="",a="",n;return t.positives.length&&(s=t.positives.join("|")),t.negatives.length&&(a=`-(${r}${t.negatives.join("|")})`),s&&a?n=`${s}|${a}`:n=s||a,e.wrap?`(${r}${n})`:n},hoe=(t,e,r,s)=>{if(r)return poe(t,e,{wrap:!1,...s});let a=String.fromCharCode(t);if(t===e)return a;let n=String.fromCharCode(e);return`[${a}-${n}]`},goe=(t,e,r)=>{if(Array.isArray(t)){let s=r.wrap===!0,a=r.capture?"":"?:";return s?`(${a}${t.join("|")})`:t.join("|")}return poe(t,e,r)},doe=(...t)=>new RangeError("Invalid range arguments: "+IKe.inspect(...t)),moe=(t,e,r)=>{if(r.strictRanges===!0)throw doe([t,e]);return[]},SKe=(t,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step "${t}" to be a number`);return[]},DKe=(t,e,r=1,s={})=>{let a=Number(t),n=Number(e);if(!Number.isInteger(a)||!Number.isInteger(n)){if(s.strictRanges===!0)throw doe([t,e]);return[]}a===0&&(a=0),n===0&&(n=0);let c=a>n,f=String(t),p=String(e),h=String(r);r=Math.max(Math.abs(r),1);let E=a4(f)||a4(p)||a4(h),C=E?Math.max(f.length,p.length,h.length):0,S=E===!1&&wKe(t,e,s)===!1,P=s.transform||CKe(S);if(s.toRegex&&r===1)return hoe(Aoe(t,C),Aoe(e,C),!0,s);let I={negatives:[],positives:[]},R=W=>I[W<0?"negatives":"positives"].push(Math.abs(W)),N=[],U=0;for(;c?a>=n:a<=n;)s.toRegex===!0&&r>1?R(a):N.push(BKe(P(a,U),C,S)),a=c?a-r:a+r,U++;return s.toRegex===!0?r>1?vKe(I,s):goe(N,null,{wrap:!1,...s}):N},bKe=(t,e,r=1,s={})=>{if(!yB(t)&&t.length>1||!yB(e)&&e.length>1)return moe(t,e,s);let a=s.transform||(S=>String.fromCharCode(S)),n=`${t}`.charCodeAt(0),c=`${e}`.charCodeAt(0),f=n>c,p=Math.min(n,c),h=Math.max(n,c);if(s.toRegex&&r===1)return hoe(p,h,!1,s);let E=[],C=0;for(;f?n>=c:n<=c;)E.push(a(n,C)),n=f?n-r:n+r,C++;return s.toRegex===!0?goe(E,null,{wrap:!1,options:s}):E},Ak=(t,e,r,s={})=>{if(e==null&&o4(t))return[t];if(!o4(t)||!o4(e))return moe(t,e,s);if(typeof r=="function")return Ak(t,e,1,{transform:r});if(foe(r))return Ak(t,e,0,r);let a={...s};return a.capture===!0&&(a.wrap=!0),r=r||a.step||1,yB(r)?yB(t)&&yB(e)?DKe(t,e,r,a):bKe(t,e,Math.max(Math.abs(r),1),a):r!=null&&!foe(r)?SKe(r,a):Ak(t,e,1,r)};yoe.exports=Ak});var Coe=_((aFt,Ioe)=>{"use strict";var PKe=l4(),Eoe=uk(),xKe=(t,e={})=>{let r=(s,a={})=>{let n=Eoe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f=n===!0||c===!0,p=e.escapeInvalid===!0?"\\":"",h="";if(s.isOpen===!0||s.isClose===!0)return p+s.value;if(s.type==="open")return f?p+s.value:"(";if(s.type==="close")return f?p+s.value:")";if(s.type==="comma")return s.prev.type==="comma"?"":f?s.value:"|";if(s.value)return s.value;if(s.nodes&&s.ranges>0){let E=Eoe.reduce(s.nodes),C=PKe(...E,{...e,wrap:!1,toRegex:!0});if(C.length!==0)return E.length>1&&C.length>1?`(${C})`:C}if(s.nodes)for(let E of s.nodes)h+=r(E,s);return h};return r(t)};Ioe.exports=xKe});var voe=_((lFt,Boe)=>{"use strict";var kKe=l4(),woe=fk(),RE=uk(),qd=(t="",e="",r=!1)=>{let s=[];if(t=[].concat(t),e=[].concat(e),!e.length)return t;if(!t.length)return r?RE.flatten(e).map(a=>`{${a}}`):e;for(let a of t)if(Array.isArray(a))for(let n of a)s.push(qd(n,e,r));else for(let n of e)r===!0&&typeof n=="string"&&(n=`{${n}}`),s.push(Array.isArray(n)?qd(a,n,r):a+n);return RE.flatten(s)},QKe=(t,e={})=>{let r=e.rangeLimit===void 0?1e3:e.rangeLimit,s=(a,n={})=>{a.queue=[];let c=n,f=n.queue;for(;c.type!=="brace"&&c.type!=="root"&&c.parent;)c=c.parent,f=c.queue;if(a.invalid||a.dollar){f.push(qd(f.pop(),woe(a,e)));return}if(a.type==="brace"&&a.invalid!==!0&&a.nodes.length===2){f.push(qd(f.pop(),["{}"]));return}if(a.nodes&&a.ranges>0){let C=RE.reduce(a.nodes);if(RE.exceedsLimit(...C,e.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let S=kKe(...C,e);S.length===0&&(S=woe(a,e)),f.push(qd(f.pop(),S)),a.nodes=[];return}let p=RE.encloseBrace(a),h=a.queue,E=a;for(;E.type!=="brace"&&E.type!=="root"&&E.parent;)E=E.parent,h=E.queue;for(let C=0;C{"use strict";Soe.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` +`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Qoe=_((uFt,koe)=>{"use strict";var TKe=fk(),{MAX_LENGTH:boe,CHAR_BACKSLASH:c4,CHAR_BACKTICK:RKe,CHAR_COMMA:FKe,CHAR_DOT:NKe,CHAR_LEFT_PARENTHESES:OKe,CHAR_RIGHT_PARENTHESES:LKe,CHAR_LEFT_CURLY_BRACE:MKe,CHAR_RIGHT_CURLY_BRACE:UKe,CHAR_LEFT_SQUARE_BRACKET:Poe,CHAR_RIGHT_SQUARE_BRACKET:xoe,CHAR_DOUBLE_QUOTE:_Ke,CHAR_SINGLE_QUOTE:HKe,CHAR_NO_BREAK_SPACE:jKe,CHAR_ZERO_WIDTH_NOBREAK_SPACE:GKe}=Doe(),qKe=(t,e={})=>{if(typeof t!="string")throw new TypeError("Expected a string");let r=e||{},s=typeof r.maxLength=="number"?Math.min(boe,r.maxLength):boe;if(t.length>s)throw new SyntaxError(`Input length (${t.length}), exceeds max characters (${s})`);let a={type:"root",input:t,nodes:[]},n=[a],c=a,f=a,p=0,h=t.length,E=0,C=0,S,P={},I=()=>t[E++],R=N=>{if(N.type==="text"&&f.type==="dot"&&(f.type="text"),f&&f.type==="text"&&N.type==="text"){f.value+=N.value;return}return c.nodes.push(N),N.parent=c,N.prev=f,f=N,N};for(R({type:"bos"});E0){if(c.ranges>0){c.ranges=0;let N=c.nodes.shift();c.nodes=[N,{type:"text",value:TKe(c)}]}R({type:"comma",value:S}),c.commas++;continue}if(S===NKe&&C>0&&c.commas===0){let N=c.nodes;if(C===0||N.length===0){R({type:"text",value:S});continue}if(f.type==="dot"){if(c.range=[],f.value+=S,f.type="range",c.nodes.length!==3&&c.nodes.length!==5){c.invalid=!0,c.ranges=0,f.type="text";continue}c.ranges++,c.args=[];continue}if(f.type==="range"){N.pop();let U=N[N.length-1];U.value+=f.value+S,f=U,c.ranges--;continue}R({type:"dot",value:S});continue}R({type:"text",value:S})}do if(c=n.pop(),c.type!=="root"){c.nodes.forEach(W=>{W.nodes||(W.type==="open"&&(W.isOpen=!0),W.type==="close"&&(W.isClose=!0),W.nodes||(W.type="text"),W.invalid=!0)});let N=n[n.length-1],U=N.nodes.indexOf(c);N.nodes.splice(U,1,...c.nodes)}while(n.length>0);return R({type:"eos"}),a};koe.exports=qKe});var Foe=_((fFt,Roe)=>{"use strict";var Toe=fk(),WKe=Coe(),YKe=voe(),VKe=Qoe(),jl=(t,e={})=>{let r=[];if(Array.isArray(t))for(let s of t){let a=jl.create(s,e);Array.isArray(a)?r.push(...a):r.push(a)}else r=[].concat(jl.create(t,e));return e&&e.expand===!0&&e.nodupes===!0&&(r=[...new Set(r)]),r};jl.parse=(t,e={})=>VKe(t,e);jl.stringify=(t,e={})=>Toe(typeof t=="string"?jl.parse(t,e):t,e);jl.compile=(t,e={})=>(typeof t=="string"&&(t=jl.parse(t,e)),WKe(t,e));jl.expand=(t,e={})=>{typeof t=="string"&&(t=jl.parse(t,e));let r=YKe(t,e);return e.noempty===!0&&(r=r.filter(Boolean)),e.nodupes===!0&&(r=[...new Set(r)]),r};jl.create=(t,e={})=>t===""||t.length<3?[t]:e.expand!==!0?jl.compile(t,e):jl.expand(t,e);Roe.exports=jl});var EB=_((AFt,Uoe)=>{"use strict";var JKe=Ie("path"),Vf="\\\\/",Noe=`[^${Vf}]`,Dp="\\.",KKe="\\+",zKe="\\?",pk="\\/",XKe="(?=.)",Ooe="[^/]",u4=`(?:${pk}|$)`,Loe=`(?:^|${pk})`,f4=`${Dp}{1,2}${u4}`,ZKe=`(?!${Dp})`,$Ke=`(?!${Loe}${f4})`,eze=`(?!${Dp}{0,1}${u4})`,tze=`(?!${f4})`,rze=`[^.${pk}]`,nze=`${Ooe}*?`,Moe={DOT_LITERAL:Dp,PLUS_LITERAL:KKe,QMARK_LITERAL:zKe,SLASH_LITERAL:pk,ONE_CHAR:XKe,QMARK:Ooe,END_ANCHOR:u4,DOTS_SLASH:f4,NO_DOT:ZKe,NO_DOTS:$Ke,NO_DOT_SLASH:eze,NO_DOTS_SLASH:tze,QMARK_NO_DOT:rze,STAR:nze,START_ANCHOR:Loe},ize={...Moe,SLASH_LITERAL:`[${Vf}]`,QMARK:Noe,STAR:`${Noe}*?`,DOTS_SLASH:`${Dp}{1,2}(?:[${Vf}]|$)`,NO_DOT:`(?!${Dp})`,NO_DOTS:`(?!(?:^|[${Vf}])${Dp}{1,2}(?:[${Vf}]|$))`,NO_DOT_SLASH:`(?!${Dp}{0,1}(?:[${Vf}]|$))`,NO_DOTS_SLASH:`(?!${Dp}{1,2}(?:[${Vf}]|$))`,QMARK_NO_DOT:`[^.${Vf}]`,START_ANCHOR:`(?:^|[${Vf}])`,END_ANCHOR:`(?:[${Vf}]|$)`},sze={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Uoe.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:sze,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:JKe.sep,extglobChars(t){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${t.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(t){return t===!0?ize:Moe}}});var IB=_(ol=>{"use strict";var oze=Ie("path"),aze=process.platform==="win32",{REGEX_BACKSLASH:lze,REGEX_REMOVE_BACKSLASH:cze,REGEX_SPECIAL_CHARS:uze,REGEX_SPECIAL_CHARS_GLOBAL:fze}=EB();ol.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);ol.hasRegexChars=t=>uze.test(t);ol.isRegexChar=t=>t.length===1&&ol.hasRegexChars(t);ol.escapeRegex=t=>t.replace(fze,"\\$1");ol.toPosixSlashes=t=>t.replace(lze,"/");ol.removeBackslashes=t=>t.replace(cze,e=>e==="\\"?"":e);ol.supportsLookbehinds=()=>{let t=process.version.slice(1).split(".").map(Number);return t.length===3&&t[0]>=9||t[0]===8&&t[1]>=10};ol.isWindows=t=>t&&typeof t.windows=="boolean"?t.windows:aze===!0||oze.sep==="\\";ol.escapeLast=(t,e,r)=>{let s=t.lastIndexOf(e,r);return s===-1?t:t[s-1]==="\\"?ol.escapeLast(t,e,s-1):`${t.slice(0,s)}\\${t.slice(s)}`};ol.removePrefix=(t,e={})=>{let r=t;return r.startsWith("./")&&(r=r.slice(2),e.prefix="./"),r};ol.wrapOutput=(t,e={},r={})=>{let s=r.contains?"":"^",a=r.contains?"":"$",n=`${s}(?:${t})${a}`;return e.negated===!0&&(n=`(?:^(?!${n}).*$)`),n}});var Voe=_((hFt,Yoe)=>{"use strict";var _oe=IB(),{CHAR_ASTERISK:A4,CHAR_AT:Aze,CHAR_BACKWARD_SLASH:CB,CHAR_COMMA:pze,CHAR_DOT:p4,CHAR_EXCLAMATION_MARK:h4,CHAR_FORWARD_SLASH:Woe,CHAR_LEFT_CURLY_BRACE:g4,CHAR_LEFT_PARENTHESES:d4,CHAR_LEFT_SQUARE_BRACKET:hze,CHAR_PLUS:gze,CHAR_QUESTION_MARK:Hoe,CHAR_RIGHT_CURLY_BRACE:dze,CHAR_RIGHT_PARENTHESES:joe,CHAR_RIGHT_SQUARE_BRACKET:mze}=EB(),Goe=t=>t===Woe||t===CB,qoe=t=>{t.isPrefix!==!0&&(t.depth=t.isGlobstar?1/0:1)},yze=(t,e)=>{let r=e||{},s=t.length-1,a=r.parts===!0||r.scanToEnd===!0,n=[],c=[],f=[],p=t,h=-1,E=0,C=0,S=!1,P=!1,I=!1,R=!1,N=!1,U=!1,W=!1,ee=!1,ie=!1,ue=!1,le=0,me,pe,Be={value:"",depth:0,isGlob:!1},Ce=()=>h>=s,g=()=>p.charCodeAt(h+1),we=()=>(me=pe,p.charCodeAt(++h));for(;h0&&(Ae=p.slice(0,E),p=p.slice(E),C-=E),ye&&I===!0&&C>0?(ye=p.slice(0,C),se=p.slice(C)):I===!0?(ye="",se=p):ye=p,ye&&ye!==""&&ye!=="/"&&ye!==p&&Goe(ye.charCodeAt(ye.length-1))&&(ye=ye.slice(0,-1)),r.unescape===!0&&(se&&(se=_oe.removeBackslashes(se)),ye&&W===!0&&(ye=_oe.removeBackslashes(ye)));let Z={prefix:Ae,input:t,start:E,base:ye,glob:se,isBrace:S,isBracket:P,isGlob:I,isExtglob:R,isGlobstar:N,negated:ee,negatedExtglob:ie};if(r.tokens===!0&&(Z.maxDepth=0,Goe(pe)||c.push(Be),Z.tokens=c),r.parts===!0||r.tokens===!0){let De;for(let Re=0;Re{"use strict";var hk=EB(),Gl=IB(),{MAX_LENGTH:gk,POSIX_REGEX_SOURCE:Eze,REGEX_NON_SPECIAL_CHARS:Ize,REGEX_SPECIAL_CHARS_BACKREF:Cze,REPLACEMENTS:Joe}=hk,wze=(t,e)=>{if(typeof e.expandRange=="function")return e.expandRange(...t,e);t.sort();let r=`[${t.join("-")}]`;try{new RegExp(r)}catch{return t.map(a=>Gl.escapeRegex(a)).join("..")}return r},FE=(t,e)=>`Missing ${t}: "${e}" - use "\\\\${e}" to match literal characters`,m4=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");t=Joe[t]||t;let r={...e},s=typeof r.maxLength=="number"?Math.min(gk,r.maxLength):gk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);let n={type:"bos",value:"",output:r.prepend||""},c=[n],f=r.capture?"":"?:",p=Gl.isWindows(e),h=hk.globChars(p),E=hk.extglobChars(h),{DOT_LITERAL:C,PLUS_LITERAL:S,SLASH_LITERAL:P,ONE_CHAR:I,DOTS_SLASH:R,NO_DOT:N,NO_DOT_SLASH:U,NO_DOTS_SLASH:W,QMARK:ee,QMARK_NO_DOT:ie,STAR:ue,START_ANCHOR:le}=h,me=x=>`(${f}(?:(?!${le}${x.dot?R:C}).)*?)`,pe=r.dot?"":N,Be=r.dot?ee:ie,Ce=r.bash===!0?me(r):ue;r.capture&&(Ce=`(${Ce})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let g={input:t,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:c};t=Gl.removePrefix(t,g),a=t.length;let we=[],ye=[],Ae=[],se=n,Z,De=()=>g.index===a-1,Re=g.peek=(x=1)=>t[g.index+x],mt=g.advance=()=>t[++g.index]||"",j=()=>t.slice(g.index+1),rt=(x="",w=0)=>{g.consumed+=x,g.index+=w},Fe=x=>{g.output+=x.output!=null?x.output:x.value,rt(x.value)},Ne=()=>{let x=1;for(;Re()==="!"&&(Re(2)!=="("||Re(3)==="?");)mt(),g.start++,x++;return x%2===0?!1:(g.negated=!0,g.start++,!0)},Pe=x=>{g[x]++,Ae.push(x)},Ve=x=>{g[x]--,Ae.pop()},ke=x=>{if(se.type==="globstar"){let w=g.braces>0&&(x.type==="comma"||x.type==="brace"),b=x.extglob===!0||we.length&&(x.type==="pipe"||x.type==="paren");x.type!=="slash"&&x.type!=="paren"&&!w&&!b&&(g.output=g.output.slice(0,-se.output.length),se.type="star",se.value="*",se.output=Ce,g.output+=se.output)}if(we.length&&x.type!=="paren"&&(we[we.length-1].inner+=x.value),(x.value||x.output)&&Fe(x),se&&se.type==="text"&&x.type==="text"){se.value+=x.value,se.output=(se.output||"")+x.value;return}x.prev=se,c.push(x),se=x},it=(x,w)=>{let b={...E[w],conditions:1,inner:""};b.prev=se,b.parens=g.parens,b.output=g.output;let y=(r.capture?"(":"")+b.open;Pe("parens"),ke({type:x,value:w,output:g.output?"":I}),ke({type:"paren",extglob:!0,value:mt(),output:y}),we.push(b)},Ue=x=>{let w=x.close+(r.capture?")":""),b;if(x.type==="negate"){let y=Ce;if(x.inner&&x.inner.length>1&&x.inner.includes("/")&&(y=me(r)),(y!==Ce||De()||/^\)+$/.test(j()))&&(w=x.close=`)$))${y}`),x.inner.includes("*")&&(b=j())&&/^\.[^\\/.]+$/.test(b)){let F=m4(b,{...e,fastpaths:!1}).output;w=x.close=`)${F})${y})`}x.prev.type==="bos"&&(g.negatedExtglob=!0)}ke({type:"paren",extglob:!0,value:Z,output:w}),Ve("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(t)){let x=!1,w=t.replace(Cze,(b,y,F,z,X,$)=>z==="\\"?(x=!0,b):z==="?"?y?y+z+(X?ee.repeat(X.length):""):$===0?Be+(X?ee.repeat(X.length):""):ee.repeat(F.length):z==="."?C.repeat(F.length):z==="*"?y?y+z+(X?Ce:""):Ce:y?b:`\\${b}`);return x===!0&&(r.unescape===!0?w=w.replace(/\\/g,""):w=w.replace(/\\+/g,b=>b.length%2===0?"\\\\":b?"\\":"")),w===t&&r.contains===!0?(g.output=t,g):(g.output=Gl.wrapOutput(w,g,e),g)}for(;!De();){if(Z=mt(),Z==="\0")continue;if(Z==="\\"){let b=Re();if(b==="/"&&r.bash!==!0||b==="."||b===";")continue;if(!b){Z+="\\",ke({type:"text",value:Z});continue}let y=/^\\+/.exec(j()),F=0;if(y&&y[0].length>2&&(F=y[0].length,g.index+=F,F%2!==0&&(Z+="\\")),r.unescape===!0?Z=mt():Z+=mt(),g.brackets===0){ke({type:"text",value:Z});continue}}if(g.brackets>0&&(Z!=="]"||se.value==="["||se.value==="[^")){if(r.posix!==!1&&Z===":"){let b=se.value.slice(1);if(b.includes("[")&&(se.posix=!0,b.includes(":"))){let y=se.value.lastIndexOf("["),F=se.value.slice(0,y),z=se.value.slice(y+2),X=Eze[z];if(X){se.value=F+X,g.backtrack=!0,mt(),!n.output&&c.indexOf(se)===1&&(n.output=I);continue}}}(Z==="["&&Re()!==":"||Z==="-"&&Re()==="]")&&(Z=`\\${Z}`),Z==="]"&&(se.value==="["||se.value==="[^")&&(Z=`\\${Z}`),r.posix===!0&&Z==="!"&&se.value==="["&&(Z="^"),se.value+=Z,Fe({value:Z});continue}if(g.quotes===1&&Z!=='"'){Z=Gl.escapeRegex(Z),se.value+=Z,Fe({value:Z});continue}if(Z==='"'){g.quotes=g.quotes===1?0:1,r.keepQuotes===!0&&ke({type:"text",value:Z});continue}if(Z==="("){Pe("parens"),ke({type:"paren",value:Z});continue}if(Z===")"){if(g.parens===0&&r.strictBrackets===!0)throw new SyntaxError(FE("opening","("));let b=we[we.length-1];if(b&&g.parens===b.parens+1){Ue(we.pop());continue}ke({type:"paren",value:Z,output:g.parens?")":"\\)"}),Ve("parens");continue}if(Z==="["){if(r.nobracket===!0||!j().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(FE("closing","]"));Z=`\\${Z}`}else Pe("brackets");ke({type:"bracket",value:Z});continue}if(Z==="]"){if(r.nobracket===!0||se&&se.type==="bracket"&&se.value.length===1){ke({type:"text",value:Z,output:`\\${Z}`});continue}if(g.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(FE("opening","["));ke({type:"text",value:Z,output:`\\${Z}`});continue}Ve("brackets");let b=se.value.slice(1);if(se.posix!==!0&&b[0]==="^"&&!b.includes("/")&&(Z=`/${Z}`),se.value+=Z,Fe({value:Z}),r.literalBrackets===!1||Gl.hasRegexChars(b))continue;let y=Gl.escapeRegex(se.value);if(g.output=g.output.slice(0,-se.value.length),r.literalBrackets===!0){g.output+=y,se.value=y;continue}se.value=`(${f}${y}|${se.value})`,g.output+=se.value;continue}if(Z==="{"&&r.nobrace!==!0){Pe("braces");let b={type:"brace",value:Z,output:"(",outputIndex:g.output.length,tokensIndex:g.tokens.length};ye.push(b),ke(b);continue}if(Z==="}"){let b=ye[ye.length-1];if(r.nobrace===!0||!b){ke({type:"text",value:Z,output:Z});continue}let y=")";if(b.dots===!0){let F=c.slice(),z=[];for(let X=F.length-1;X>=0&&(c.pop(),F[X].type!=="brace");X--)F[X].type!=="dots"&&z.unshift(F[X].value);y=wze(z,r),g.backtrack=!0}if(b.comma!==!0&&b.dots!==!0){let F=g.output.slice(0,b.outputIndex),z=g.tokens.slice(b.tokensIndex);b.value=b.output="\\{",Z=y="\\}",g.output=F;for(let X of z)g.output+=X.output||X.value}ke({type:"brace",value:Z,output:y}),Ve("braces"),ye.pop();continue}if(Z==="|"){we.length>0&&we[we.length-1].conditions++,ke({type:"text",value:Z});continue}if(Z===","){let b=Z,y=ye[ye.length-1];y&&Ae[Ae.length-1]==="braces"&&(y.comma=!0,b="|"),ke({type:"comma",value:Z,output:b});continue}if(Z==="/"){if(se.type==="dot"&&g.index===g.start+1){g.start=g.index+1,g.consumed="",g.output="",c.pop(),se=n;continue}ke({type:"slash",value:Z,output:P});continue}if(Z==="."){if(g.braces>0&&se.type==="dot"){se.value==="."&&(se.output=C);let b=ye[ye.length-1];se.type="dots",se.output+=Z,se.value+=Z,b.dots=!0;continue}if(g.braces+g.parens===0&&se.type!=="bos"&&se.type!=="slash"){ke({type:"text",value:Z,output:C});continue}ke({type:"dot",value:Z,output:C});continue}if(Z==="?"){if(!(se&&se.value==="(")&&r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("qmark",Z);continue}if(se&&se.type==="paren"){let y=Re(),F=Z;if(y==="<"&&!Gl.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(se.value==="("&&!/[!=<:]/.test(y)||y==="<"&&!/<([!=]|\w+>)/.test(j()))&&(F=`\\${Z}`),ke({type:"text",value:Z,output:F});continue}if(r.dot!==!0&&(se.type==="slash"||se.type==="bos")){ke({type:"qmark",value:Z,output:ie});continue}ke({type:"qmark",value:Z,output:ee});continue}if(Z==="!"){if(r.noextglob!==!0&&Re()==="("&&(Re(2)!=="?"||!/[!=<:]/.test(Re(3)))){it("negate",Z);continue}if(r.nonegate!==!0&&g.index===0){Ne();continue}}if(Z==="+"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("plus",Z);continue}if(se&&se.value==="("||r.regex===!1){ke({type:"plus",value:Z,output:S});continue}if(se&&(se.type==="bracket"||se.type==="paren"||se.type==="brace")||g.parens>0){ke({type:"plus",value:Z});continue}ke({type:"plus",value:S});continue}if(Z==="@"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){ke({type:"at",extglob:!0,value:Z,output:""});continue}ke({type:"text",value:Z});continue}if(Z!=="*"){(Z==="$"||Z==="^")&&(Z=`\\${Z}`);let b=Ize.exec(j());b&&(Z+=b[0],g.index+=b[0].length),ke({type:"text",value:Z});continue}if(se&&(se.type==="globstar"||se.star===!0)){se.type="star",se.star=!0,se.value+=Z,se.output=Ce,g.backtrack=!0,g.globstar=!0,rt(Z);continue}let x=j();if(r.noextglob!==!0&&/^\([^?]/.test(x)){it("star",Z);continue}if(se.type==="star"){if(r.noglobstar===!0){rt(Z);continue}let b=se.prev,y=b.prev,F=b.type==="slash"||b.type==="bos",z=y&&(y.type==="star"||y.type==="globstar");if(r.bash===!0&&(!F||x[0]&&x[0]!=="/")){ke({type:"star",value:Z,output:""});continue}let X=g.braces>0&&(b.type==="comma"||b.type==="brace"),$=we.length&&(b.type==="pipe"||b.type==="paren");if(!F&&b.type!=="paren"&&!X&&!$){ke({type:"star",value:Z,output:""});continue}for(;x.slice(0,3)==="/**";){let oe=t[g.index+4];if(oe&&oe!=="/")break;x=x.slice(3),rt("/**",3)}if(b.type==="bos"&&De()){se.type="globstar",se.value+=Z,se.output=me(r),g.output=se.output,g.globstar=!0,rt(Z);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&!z&&De()){g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=me(r)+(r.strictSlashes?")":"|$)"),se.value+=Z,g.globstar=!0,g.output+=b.output+se.output,rt(Z);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&x[0]==="/"){let oe=x[1]!==void 0?"|$":"";g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=`${me(r)}${P}|${P}${oe})`,se.value+=Z,g.output+=b.output+se.output,g.globstar=!0,rt(Z+mt()),ke({type:"slash",value:"/",output:""});continue}if(b.type==="bos"&&x[0]==="/"){se.type="globstar",se.value+=Z,se.output=`(?:^|${P}|${me(r)}${P})`,g.output=se.output,g.globstar=!0,rt(Z+mt()),ke({type:"slash",value:"/",output:""});continue}g.output=g.output.slice(0,-se.output.length),se.type="globstar",se.output=me(r),se.value+=Z,g.output+=se.output,g.globstar=!0,rt(Z);continue}let w={type:"star",value:Z,output:Ce};if(r.bash===!0){w.output=".*?",(se.type==="bos"||se.type==="slash")&&(w.output=pe+w.output),ke(w);continue}if(se&&(se.type==="bracket"||se.type==="paren")&&r.regex===!0){w.output=Z,ke(w);continue}(g.index===g.start||se.type==="slash"||se.type==="dot")&&(se.type==="dot"?(g.output+=U,se.output+=U):r.dot===!0?(g.output+=W,se.output+=W):(g.output+=pe,se.output+=pe),Re()!=="*"&&(g.output+=I,se.output+=I)),ke(w)}for(;g.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing","]"));g.output=Gl.escapeLast(g.output,"["),Ve("brackets")}for(;g.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing",")"));g.output=Gl.escapeLast(g.output,"("),Ve("parens")}for(;g.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing","}"));g.output=Gl.escapeLast(g.output,"{"),Ve("braces")}if(r.strictSlashes!==!0&&(se.type==="star"||se.type==="bracket")&&ke({type:"maybe_slash",value:"",output:`${P}?`}),g.backtrack===!0){g.output="";for(let x of g.tokens)g.output+=x.output!=null?x.output:x.value,x.suffix&&(g.output+=x.suffix)}return g};m4.fastpaths=(t,e)=>{let r={...e},s=typeof r.maxLength=="number"?Math.min(gk,r.maxLength):gk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);t=Joe[t]||t;let n=Gl.isWindows(e),{DOT_LITERAL:c,SLASH_LITERAL:f,ONE_CHAR:p,DOTS_SLASH:h,NO_DOT:E,NO_DOTS:C,NO_DOTS_SLASH:S,STAR:P,START_ANCHOR:I}=hk.globChars(n),R=r.dot?C:E,N=r.dot?S:E,U=r.capture?"":"?:",W={negated:!1,prefix:""},ee=r.bash===!0?".*?":P;r.capture&&(ee=`(${ee})`);let ie=pe=>pe.noglobstar===!0?ee:`(${U}(?:(?!${I}${pe.dot?h:c}).)*?)`,ue=pe=>{switch(pe){case"*":return`${R}${p}${ee}`;case".*":return`${c}${p}${ee}`;case"*.*":return`${R}${ee}${c}${p}${ee}`;case"*/*":return`${R}${ee}${f}${p}${N}${ee}`;case"**":return R+ie(r);case"**/*":return`(?:${R}${ie(r)}${f})?${N}${p}${ee}`;case"**/*.*":return`(?:${R}${ie(r)}${f})?${N}${ee}${c}${p}${ee}`;case"**/.*":return`(?:${R}${ie(r)}${f})?${c}${p}${ee}`;default:{let Be=/^(.*?)\.(\w+)$/.exec(pe);if(!Be)return;let Ce=ue(Be[1]);return Ce?Ce+c+Be[2]:void 0}}},le=Gl.removePrefix(t,W),me=ue(le);return me&&r.strictSlashes!==!0&&(me+=`${f}?`),me};Koe.exports=m4});var Zoe=_((dFt,Xoe)=>{"use strict";var Bze=Ie("path"),vze=Voe(),y4=zoe(),E4=IB(),Sze=EB(),Dze=t=>t&&typeof t=="object"&&!Array.isArray(t),Zi=(t,e,r=!1)=>{if(Array.isArray(t)){let E=t.map(S=>Zi(S,e,r));return S=>{for(let P of E){let I=P(S);if(I)return I}return!1}}let s=Dze(t)&&t.tokens&&t.input;if(t===""||typeof t!="string"&&!s)throw new TypeError("Expected pattern to be a non-empty string");let a=e||{},n=E4.isWindows(e),c=s?Zi.compileRe(t,e):Zi.makeRe(t,e,!1,!0),f=c.state;delete c.state;let p=()=>!1;if(a.ignore){let E={...e,ignore:null,onMatch:null,onResult:null};p=Zi(a.ignore,E,r)}let h=(E,C=!1)=>{let{isMatch:S,match:P,output:I}=Zi.test(E,c,e,{glob:t,posix:n}),R={glob:t,state:f,regex:c,posix:n,input:E,output:I,match:P,isMatch:S};return typeof a.onResult=="function"&&a.onResult(R),S===!1?(R.isMatch=!1,C?R:!1):p(E)?(typeof a.onIgnore=="function"&&a.onIgnore(R),R.isMatch=!1,C?R:!1):(typeof a.onMatch=="function"&&a.onMatch(R),C?R:!0)};return r&&(h.state=f),h};Zi.test=(t,e,r,{glob:s,posix:a}={})=>{if(typeof t!="string")throw new TypeError("Expected input to be a string");if(t==="")return{isMatch:!1,output:""};let n=r||{},c=n.format||(a?E4.toPosixSlashes:null),f=t===s,p=f&&c?c(t):t;return f===!1&&(p=c?c(t):t,f=p===s),(f===!1||n.capture===!0)&&(n.matchBase===!0||n.basename===!0?f=Zi.matchBase(t,e,r,a):f=e.exec(p)),{isMatch:!!f,match:f,output:p}};Zi.matchBase=(t,e,r,s=E4.isWindows(r))=>(e instanceof RegExp?e:Zi.makeRe(e,r)).test(Bze.basename(t));Zi.isMatch=(t,e,r)=>Zi(e,r)(t);Zi.parse=(t,e)=>Array.isArray(t)?t.map(r=>Zi.parse(r,e)):y4(t,{...e,fastpaths:!1});Zi.scan=(t,e)=>vze(t,e);Zi.compileRe=(t,e,r=!1,s=!1)=>{if(r===!0)return t.output;let a=e||{},n=a.contains?"":"^",c=a.contains?"":"$",f=`${n}(?:${t.output})${c}`;t&&t.negated===!0&&(f=`^(?!${f}).*$`);let p=Zi.toRegex(f,e);return s===!0&&(p.state=t),p};Zi.makeRe=(t,e={},r=!1,s=!1)=>{if(!t||typeof t!="string")throw new TypeError("Expected a non-empty string");let a={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(t[0]==="."||t[0]==="*")&&(a.output=y4.fastpaths(t,e)),a.output||(a=y4(t,e)),Zi.compileRe(a,e,r,s)};Zi.toRegex=(t,e)=>{try{let r=e||{};return new RegExp(t,r.flags||(r.nocase?"i":""))}catch(r){if(e&&e.debug===!0)throw r;return/$^/}};Zi.constants=Sze;Xoe.exports=Zi});var eae=_((mFt,$oe)=>{"use strict";$oe.exports=Zoe()});var Go=_((yFt,iae)=>{"use strict";var rae=Ie("util"),nae=Foe(),Jf=eae(),I4=IB(),tae=t=>t===""||t==="./",xi=(t,e,r)=>{e=[].concat(e),t=[].concat(t);let s=new Set,a=new Set,n=new Set,c=0,f=E=>{n.add(E.output),r&&r.onResult&&r.onResult(E)};for(let E=0;E!s.has(E));if(r&&h.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${e.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?e.map(E=>E.replace(/\\/g,"")):e}return h};xi.match=xi;xi.matcher=(t,e)=>Jf(t,e);xi.isMatch=(t,e,r)=>Jf(e,r)(t);xi.any=xi.isMatch;xi.not=(t,e,r={})=>{e=[].concat(e).map(String);let s=new Set,a=[],n=f=>{r.onResult&&r.onResult(f),a.push(f.output)},c=new Set(xi(t,e,{...r,onResult:n}));for(let f of a)c.has(f)||s.add(f);return[...s]};xi.contains=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${rae.inspect(t)}"`);if(Array.isArray(e))return e.some(s=>xi.contains(t,s,r));if(typeof e=="string"){if(tae(t)||tae(e))return!1;if(t.includes(e)||t.startsWith("./")&&t.slice(2).includes(e))return!0}return xi.isMatch(t,e,{...r,contains:!0})};xi.matchKeys=(t,e,r)=>{if(!I4.isObject(t))throw new TypeError("Expected the first argument to be an object");let s=xi(Object.keys(t),e,r),a={};for(let n of s)a[n]=t[n];return a};xi.some=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(s.some(c=>n(c)))return!0}return!1};xi.every=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(!s.every(c=>n(c)))return!1}return!0};xi.all=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${rae.inspect(t)}"`);return[].concat(e).every(s=>Jf(s,r)(t))};xi.capture=(t,e,r)=>{let s=I4.isWindows(r),n=Jf.makeRe(String(t),{...r,capture:!0}).exec(s?I4.toPosixSlashes(e):e);if(n)return n.slice(1).map(c=>c===void 0?"":c)};xi.makeRe=(...t)=>Jf.makeRe(...t);xi.scan=(...t)=>Jf.scan(...t);xi.parse=(t,e)=>{let r=[];for(let s of[].concat(t||[]))for(let a of nae(String(s),e))r.push(Jf.parse(a,e));return r};xi.braces=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return e&&e.nobrace===!0||!/\{.*\}/.test(t)?[t]:nae(t,e)};xi.braceExpand=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return xi.braces(t,{...e,expand:!0})};iae.exports=xi});var oae=_((EFt,sae)=>{"use strict";sae.exports=({onlyFirst:t=!1}={})=>{let e=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(e,t?void 0:"g")}});var dk=_((IFt,aae)=>{"use strict";var bze=oae();aae.exports=t=>typeof t=="string"?t.replace(bze(),""):t});function lae(t){return Number.isSafeInteger(t)&&t>=0}var cae=Xe(()=>{});function uae(t){return t!=null&&typeof t!="function"&&lae(t.length)}var fae=Xe(()=>{cae()});function bc(t){return t==="__proto__"}var wB=Xe(()=>{});function NE(t){switch(typeof t){case"number":case"symbol":return!1;case"string":return t.includes(".")||t.includes("[")||t.includes("]")}}var mk=Xe(()=>{});function OE(t){return typeof t=="string"||typeof t=="symbol"?t:Object.is(t?.valueOf?.(),-0)?"-0":String(t)}var yk=Xe(()=>{});function Mu(t){let e=[],r=t.length;if(r===0)return e;let s=0,a="",n="",c=!1;for(t.charCodeAt(0)===46&&(e.push(""),s++);s{});function va(t,e,r){if(t==null)return r;switch(typeof e){case"string":{if(bc(e))return r;let s=t[e];return s===void 0?NE(e)?va(t,Mu(e),r):r:s}case"number":case"symbol":{typeof e=="number"&&(e=OE(e));let s=t[e];return s===void 0?r:s}default:{if(Array.isArray(e))return Pze(t,e,r);if(Object.is(e?.valueOf(),-0)?e="-0":e=String(e),bc(e))return r;let s=t[e];return s===void 0?r:s}}}function Pze(t,e,r){if(e.length===0)return r;let s=t;for(let a=0;a{wB();mk();yk();LE()});function C4(t){return t!==null&&(typeof t=="object"||typeof t=="function")}var Aae=Xe(()=>{});function ME(t){return t==null||typeof t!="object"&&typeof t!="function"}var Ik=Xe(()=>{});function Ck(t,e){return t===e||Number.isNaN(t)&&Number.isNaN(e)}var w4=Xe(()=>{});function Wd(t){return Object.getOwnPropertySymbols(t).filter(e=>Object.prototype.propertyIsEnumerable.call(t,e))}var wk=Xe(()=>{});function Yd(t){return t==null?t===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(t)}var Bk=Xe(()=>{});var vk,UE,_E,HE,Vd,Sk,Dk,bk,Pk,xk,pae,kk,jE,hae,Qk,Tk,Rk,Fk,Nk,gae,Ok,Lk,Mk,dae,Uk,_k,Hk=Xe(()=>{vk="[object RegExp]",UE="[object String]",_E="[object Number]",HE="[object Boolean]",Vd="[object Arguments]",Sk="[object Symbol]",Dk="[object Date]",bk="[object Map]",Pk="[object Set]",xk="[object Array]",pae="[object Function]",kk="[object ArrayBuffer]",jE="[object Object]",hae="[object Error]",Qk="[object DataView]",Tk="[object Uint8Array]",Rk="[object Uint8ClampedArray]",Fk="[object Uint16Array]",Nk="[object Uint32Array]",gae="[object BigUint64Array]",Ok="[object Int8Array]",Lk="[object Int16Array]",Mk="[object Int32Array]",dae="[object BigInt64Array]",Uk="[object Float32Array]",_k="[object Float64Array]"});function GE(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}var jk=Xe(()=>{});function mae(t,e){return u0(t,void 0,t,new Map,e)}function u0(t,e,r,s=new Map,a=void 0){let n=a?.(t,e,r,s);if(n!=null)return n;if(ME(t))return t;if(s.has(t))return s.get(t);if(Array.isArray(t)){let c=new Array(t.length);s.set(t,c);for(let f=0;f{wk();Bk();Hk();Ik();jk()});function yae(t){return u0(t,void 0,t,new Map,void 0)}var Eae=Xe(()=>{B4()});function Iae(t,e){return mae(t,(r,s,a,n)=>{let c=e?.(r,s,a,n);if(c!=null)return c;if(typeof t=="object")switch(Object.prototype.toString.call(t)){case _E:case UE:case HE:{let f=new t.constructor(t?.valueOf());return c0(f,t),f}case Vd:{let f={};return c0(f,t),f.length=t.length,f[Symbol.iterator]=t[Symbol.iterator],f}default:return}})}var Cae=Xe(()=>{B4();Hk()});function f0(t){return Iae(t)}var v4=Xe(()=>{Cae()});function Gk(t,e=Number.MAX_SAFE_INTEGER){switch(typeof t){case"number":return Number.isInteger(t)&&t>=0&&t{kze=/^(?:0|[1-9]\d*)$/});function BB(t){return t!==null&&typeof t=="object"&&Yd(t)==="[object Arguments]"}var D4=Xe(()=>{Bk()});function vB(t,e){let r;if(Array.isArray(e)?r=e:typeof e=="string"&&NE(e)&&t?.[e]==null?r=Mu(e):r=[e],r.length===0)return!1;let s=t;for(let a=0;a{mk();S4();D4();LE()});function P4(t){return typeof t=="object"&&t!==null}var wae=Xe(()=>{});function Bae(t){return typeof t=="symbol"||t instanceof Symbol}var vae=Xe(()=>{});function Sae(t,e){return Array.isArray(t)?!1:typeof t=="number"||typeof t=="boolean"||t==null||Bae(t)?!0:typeof t=="string"&&(Tze.test(t)||!Qze.test(t))||e!=null&&Object.hasOwn(e,t)}var Qze,Tze,Dae=Xe(()=>{vae();Qze=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Tze=/^\w*$/});function A0(t,e){if(t==null)return!0;switch(typeof e){case"symbol":case"number":case"object":{if(Array.isArray(e))return bae(t,e);if(typeof e=="number"?e=OE(e):typeof e=="object"&&(Object.is(e?.valueOf(),-0)?e="-0":e=String(e)),bc(e))return!1;if(t?.[e]===void 0)return!0;try{return delete t[e],!0}catch{return!1}}case"string":{if(t?.[e]===void 0&&NE(e))return bae(t,Mu(e));if(bc(e))return!1;try{return delete t[e],!0}catch{return!1}}}}function bae(t,e){let r=va(t,e.slice(0,-1),t),s=e[e.length-1];if(r?.[s]===void 0)return!0;if(bc(s))return!1;try{return delete r[s],!0}catch{return!1}}var x4=Xe(()=>{Ek();wB();mk();yk();LE()});function Pae(t){return t==null}var xae=Xe(()=>{});var kae,Qae=Xe(()=>{w4();kae=(t,e,r)=>{let s=t[e];(!(Object.hasOwn(t,e)&&Ck(s,r))||r===void 0&&!(e in t))&&(t[e]=r)}});function Tae(t,e,r,s){if(t==null&&!C4(t))return t;let a=Sae(e,t)?[e]:Array.isArray(e)?e:typeof e=="string"?Mu(e):[e],n=t;for(let c=0;c{wB();Qae();S4();Dae();yk();Aae();LE()});function Jd(t,e,r){return Tae(t,e,()=>r,()=>{})}var k4=Xe(()=>{Rae()});function Fae(t,e=0,r={}){typeof r!="object"&&(r={});let s=null,a=null,n=null,c=0,f=null,p,{leading:h=!1,trailing:E=!0,maxWait:C}=r,S="maxWait"in r,P=S?Math.max(Number(C)||0,e):0,I=ue=>(s!==null&&(p=t.apply(a,s)),s=a=null,c=ue,p),R=ue=>(c=ue,f=setTimeout(ee,e),h&&s!==null?I(ue):p),N=ue=>(f=null,E&&s!==null?I(ue):p),U=ue=>{if(n===null)return!0;let le=ue-n,me=le>=e||le<0,pe=S&&ue-c>=P;return me||pe},W=ue=>{let le=n===null?0:ue-n,me=e-le,pe=P-(ue-c);return S?Math.min(me,pe):me},ee=()=>{let ue=Date.now();if(U(ue))return N(ue);f=setTimeout(ee,W(ue))},ie=function(...ue){let le=Date.now(),me=U(le);if(s=ue,a=this,n=le,me){if(f===null)return R(le);if(S)return clearTimeout(f),f=setTimeout(ee,e),I(le)}return f===null&&(f=setTimeout(ee,e)),p};return ie.cancel=()=>{f!==null&&clearTimeout(f),c=0,n=s=a=f=null},ie.flush=()=>f===null?p:N(Date.now()),ie}var Nae=Xe(()=>{});function Q4(t,e=0,r={}){let{leading:s=!0,trailing:a=!0}=r;return Fae(t,e,{leading:s,maxWait:e,trailing:a})}var Oae=Xe(()=>{Nae()});function T4(t){if(t==null)return"";if(typeof t=="string")return t;if(Array.isArray(t))return t.map(T4).join(",");let e=String(t);return e==="0"&&Object.is(Number(t),-0)?"-0":e}var Lae=Xe(()=>{});function R4(t){if(!t||typeof t!="object")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.prototype||Object.getPrototypeOf(e)===null?Object.prototype.toString.call(t)==="[object Object]":!1}var Mae=Xe(()=>{});function Uae(t,e,r){return SB(t,e,void 0,void 0,void 0,void 0,r)}function SB(t,e,r,s,a,n,c){let f=c(t,e,r,s,a,n);if(f!==void 0)return f;if(typeof t==typeof e)switch(typeof t){case"bigint":case"string":case"boolean":case"symbol":case"undefined":return t===e;case"number":return t===e||Object.is(t,e);case"function":return t===e;case"object":return DB(t,e,n,c)}return DB(t,e,n,c)}function DB(t,e,r,s){if(Object.is(t,e))return!0;let a=Yd(t),n=Yd(e);if(a===Vd&&(a=jE),n===Vd&&(n=jE),a!==n)return!1;switch(a){case UE:return t.toString()===e.toString();case _E:{let p=t.valueOf(),h=e.valueOf();return Ck(p,h)}case HE:case Dk:case Sk:return Object.is(t.valueOf(),e.valueOf());case vk:return t.source===e.source&&t.flags===e.flags;case pae:return t===e}r=r??new Map;let c=r.get(t),f=r.get(e);if(c!=null&&f!=null)return c===e;r.set(t,e),r.set(e,t);try{switch(a){case bk:{if(t.size!==e.size)return!1;for(let[p,h]of t.entries())if(!e.has(p)||!SB(h,e.get(p),p,t,e,r,s))return!1;return!0}case Pk:{if(t.size!==e.size)return!1;let p=Array.from(t.values()),h=Array.from(e.values());for(let E=0;ESB(C,P,void 0,t,e,r,s));if(S===-1)return!1;h.splice(S,1)}return!0}case xk:case Tk:case Rk:case Fk:case Nk:case gae:case Ok:case Lk:case Mk:case dae:case Uk:case _k:{if(typeof Buffer<"u"&&Buffer.isBuffer(t)!==Buffer.isBuffer(e)||t.length!==e.length)return!1;for(let p=0;p{Mae();wk();Bk();Hk();w4()});function Hae(){}var jae=Xe(()=>{});function F4(t,e){return Uae(t,e,Hae)}var Gae=Xe(()=>{_ae();jae()});function qae(t){return GE(t)}var Wae=Xe(()=>{jk()});function Yae(t){if(typeof t!="object"||t==null)return!1;if(Object.getPrototypeOf(t)===null)return!0;if(Object.prototype.toString.call(t)!=="[object Object]"){let r=t[Symbol.toStringTag];return r==null||!Object.getOwnPropertyDescriptor(t,Symbol.toStringTag)?.writable?!1:t.toString()===`[object ${r}]`}let e=t;for(;Object.getPrototypeOf(e)!==null;)e=Object.getPrototypeOf(e);return Object.getPrototypeOf(t)===e}var Vae=Xe(()=>{});function Jae(t){if(ME(t))return t;if(Array.isArray(t)||GE(t)||t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer)return t.slice(0);let e=Object.getPrototypeOf(t),r=e.constructor;if(t instanceof Date||t instanceof Map||t instanceof Set)return new r(t);if(t instanceof RegExp){let s=new r(t);return s.lastIndex=t.lastIndex,s}if(t instanceof DataView)return new r(t.buffer.slice(0));if(t instanceof Error){let s=new r(t.message);return s.stack=t.stack,s.name=t.name,s.cause=t.cause,s}if(typeof File<"u"&&t instanceof File)return new r([t],t.name,{type:t.type,lastModified:t.lastModified});if(typeof t=="object"){let s=Object.create(e);return Object.assign(s,t)}return t}var Kae=Xe(()=>{Ik();jk()});function N4(t,...e){let r=e.slice(0,-1),s=e[e.length-1],a=t;for(let n=0;n{v4();wB();Kae();Ik();wk();D4();wae();Vae();Wae()});function O4(t,...e){if(t==null)return{};let r=yae(t);for(let s=0;s{x4();Eae()});function Kd(t,...e){if(Pae(t))return{};let r={};for(let s=0;s{Ek();b4();k4();fae();xae()});function $ae(t){return t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()}var ele=Xe(()=>{});function bB(t){return $ae(T4(t))}var tle=Xe(()=>{ele();Lae()});var ql=Xe(()=>{Oae();Gae();v4();Ek();b4();zae();Xae();Zae();k4();x4();tle();LE()});var je={};Vt(je,{AsyncActions:()=>U4,BufferStream:()=>M4,CachingStrategy:()=>fle,DefaultStream:()=>_4,allSettledSafe:()=>Uu,assertNever:()=>G4,bufferStream:()=>WE,buildIgnorePattern:()=>Uze,convertMapsToIndexableObjects:()=>Yk,dynamicRequire:()=>Pp,escapeRegExp:()=>Fze,getArrayWithDefault:()=>xB,getFactoryWithDefault:()=>Yl,getMapWithDefault:()=>q4,getSetWithDefault:()=>bp,groupBy:()=>jze,isIndexableObject:()=>L4,isPathLike:()=>_ze,isTaggedYarnVersion:()=>Rze,makeDeferred:()=>lle,mapAndFilter:()=>Wl,mapAndFind:()=>p0,mergeIntoTarget:()=>ple,overrideType:()=>Nze,parseBoolean:()=>kB,parseDuration:()=>Jk,parseInt:()=>YE,parseOptionalBoolean:()=>Ale,plural:()=>Wk,prettifyAsyncErrors:()=>qE,prettifySyncErrors:()=>W4,releaseAfterUseAsync:()=>Lze,replaceEnvVariables:()=>Vk,sortMap:()=>qs,toMerged:()=>Hze,tryParseOptionalBoolean:()=>Y4,validateEnum:()=>Oze});function Rze(t){return!!(sle.default.valid(t)&&t.match(/^[^-]+(-rc\.[0-9]+)?$/))}function Wk(t,{one:e,more:r,zero:s=r}){return t===0?s:t===1?e:r}function Fze(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Nze(t){}function G4(t){throw new Error(`Assertion failed: Unexpected object '${t}'`)}function Oze(t,e){let r=Object.values(t);if(!r.includes(e))throw new nt(`Invalid value for enumeration: ${JSON.stringify(e)} (expected one of ${r.map(s=>JSON.stringify(s)).join(", ")})`);return e}function Wl(t,e){let r=[];for(let s of t){let a=e(s);a!==ole&&r.push(a)}return r}function p0(t,e){for(let r of t){let s=e(r);if(s!==ale)return s}}function L4(t){return typeof t=="object"&&t!==null}async function Uu(t){let e=await Promise.allSettled(t),r=[];for(let s of e){if(s.status==="rejected")throw s.reason;r.push(s.value)}return r}function Yk(t){if(t instanceof Map&&(t=Object.fromEntries(t)),L4(t))for(let e of Object.keys(t)){let r=t[e];L4(r)&&(t[e]=Yk(r))}return t}function Yl(t,e,r){let s=t.get(e);return typeof s>"u"&&t.set(e,s=r()),s}function xB(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=[]),r}function bp(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Set),r}function q4(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Map),r}async function Lze(t,e){if(e==null)return await t();try{return await t()}finally{await e()}}async function qE(t,e){try{return await t()}catch(r){throw r.message=e(r.message),r}}function W4(t,e){try{return t()}catch(r){throw r.message=e(r.message),r}}async function WE(t){return await new Promise((e,r)=>{let s=[];t.on("error",a=>{r(a)}),t.on("data",a=>{s.push(a)}),t.on("end",()=>{e(Buffer.concat(s))})})}function lle(){let t,e;return{promise:new Promise((s,a)=>{t=s,e=a}),resolve:t,reject:e}}function cle(t){return PB(fe.fromPortablePath(t))}function ule(path){let physicalPath=fe.fromPortablePath(path),currentCacheEntry=PB.cache[physicalPath];delete PB.cache[physicalPath];let result;try{result=cle(physicalPath);let freshCacheEntry=PB.cache[physicalPath],dynamicModule=eval("module"),freshCacheIndex=dynamicModule.children.indexOf(freshCacheEntry);freshCacheIndex!==-1&&dynamicModule.children.splice(freshCacheIndex,1)}finally{PB.cache[physicalPath]=currentCacheEntry}return result}function Mze(t){let e=rle.get(t),r=ce.statSync(t);if(e?.mtime===r.mtimeMs)return e.instance;let s=ule(t);return rle.set(t,{mtime:r.mtimeMs,instance:s}),s}function Pp(t,{cachingStrategy:e=2}={}){switch(e){case 0:return ule(t);case 1:return Mze(t);case 2:return cle(t);default:throw new Error("Unsupported caching strategy")}}function qs(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function Uze(t){return t.length===0?null:t.map(e=>`(${nle.default.makeRe(e,{windows:!1,dot:!0}).source})`).join("|")}function Vk(t,{env:e}){let r=/\\?\${(?[\d\w_]+)(?:)?(?:-(?[^}]*))?}/g;return t.replace(r,(s,...a)=>{if(s.startsWith("\\"))return s.slice(1);let{variableName:n,colon:c,fallback:f}=a[a.length-1],p=Object.hasOwn(e,n),h=e[n];if(h||p&&!c)return h;if(f!=null)return f;throw new nt(`Environment variable not found (${n})`)})}function kB(t){switch(t){case"true":case"1":case 1:case!0:return!0;case"false":case"0":case 0:case!1:return!1;default:throw new Error(`Couldn't parse "${t}" as a boolean`)}}function Ale(t){return typeof t>"u"?t:kB(t)}function Y4(t){try{return Ale(t)}catch{return null}}function _ze(t){return!!(fe.isAbsolute(t)||t.match(/^(\.{1,2}|~)\//))}function ple(t,...e){let r=c=>({value:c}),s=r(t),a=e.map(c=>r(c)),{value:n}=N4(s,...a,(c,f)=>{if(Array.isArray(c)&&Array.isArray(f)){for(let p of f)c.find(h=>F4(h,p))||c.push(p);return c}});return n}function Hze(...t){return ple({},...t)}function jze(t,e){let r=Object.create(null);for(let s of t){let a=s[e];r[a]??=[],r[a].push(s)}return r}function YE(t){return typeof t=="string"?Number.parseInt(t,10):t}function Jk(t,e){let r=Gze.exec(t)?.groups;if(!r)throw new Error(`Couldn't parse "${t}" as a duration`);if(r.unit===void 0)return parseFloat(r.num);let s=H4[r.unit];if(!s)throw new Error(`Invalid duration unit "${r.unit}"`);return parseFloat(r.num)*s/H4[e]}var nle,ile,sle,j4,ole,ale,M4,U4,_4,PB,rle,fle,H4,Gze,Pc=Xe(()=>{Dt();Yt();ql();nle=ut(Go()),ile=ut(Ld()),sle=ut(Ai()),j4=Ie("stream");ole=Symbol();Wl.skip=ole;ale=Symbol();p0.skip=ale;M4=class extends j4.Transform{constructor(){super(...arguments);this.chunks=[]}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: BufferStream only accept buffers");this.chunks.push(r),a(null,null)}_flush(r){r(null,Buffer.concat(this.chunks))}};U4=class{constructor(e){this.deferred=new Map;this.promises=new Map;this.limit=(0,ile.default)(e)}set(e,r){let s=this.deferred.get(e);typeof s>"u"&&this.deferred.set(e,s=lle());let a=this.limit(()=>r());return this.promises.set(e,a),a.then(()=>{this.promises.get(e)===a&&s.resolve()},n=>{this.promises.get(e)===a&&s.reject(n)}),s.promise}reduce(e,r){let s=this.promises.get(e)??Promise.resolve();this.set(e,()=>r(s))}async wait(){await Promise.all(this.promises.values())}},_4=class extends j4.Transform{constructor(r=Buffer.alloc(0)){super();this.active=!0;this.ifEmpty=r}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: DefaultStream only accept buffers");this.active=!1,a(null,r)}_flush(r){this.active&&this.ifEmpty.length>0?r(null,this.ifEmpty):r(null)}},PB=eval("require");rle=new Map;fle=(s=>(s[s.NoCache=0]="NoCache",s[s.FsTime=1]="FsTime",s[s.Node=2]="Node",s))(fle||{});H4={ms:1,s:1e3,m:60*1e3,h:60*60*1e3,d:24*60*60*1e3,w:7*24*60*60*1e3},Gze=new RegExp(`^(?\\d*\\.?\\d+)(?${Object.keys(H4).join("|")})?$`)});var VE,V4,J4,hle=Xe(()=>{VE=(r=>(r.HARD="HARD",r.SOFT="SOFT",r))(VE||{}),V4=(s=>(s.Dependency="Dependency",s.PeerDependency="PeerDependency",s.PeerDependencyMeta="PeerDependencyMeta",s))(V4||{}),J4=(s=>(s.Inactive="inactive",s.Redundant="redundant",s.Active="active",s))(J4||{})});var he={};Vt(he,{LogLevel:()=>eQ,Style:()=>Xk,Type:()=>ht,addLogFilterSupport:()=>RB,applyColor:()=>ri,applyHyperlink:()=>KE,applyStyle:()=>zd,json:()=>Xd,jsonOrPretty:()=>Yze,mark:()=>$4,pretty:()=>Ht,prettyField:()=>Kf,prettyList:()=>Z4,prettyTruncatedLocatorList:()=>$k,stripAnsi:()=>JE.default,supportsColor:()=>Zk,supportsHyperlinks:()=>X4,tuple:()=>_u});function gle(t){let e=["KiB","MiB","GiB","TiB"],r=e.length;for(;r>1&&t<1024**r;)r-=1;let s=1024**r;return`${Math.floor(t*100/s)/100} ${e[r-1]}`}function Kk(t,e){if(Array.isArray(e))return e.length===0?ri(t,"[]",ht.CODE):ri(t,"[ ",ht.CODE)+e.map(r=>Kk(t,r)).join(", ")+ri(t," ]",ht.CODE);if(typeof e=="string")return ri(t,JSON.stringify(e),ht.STRING);if(typeof e=="number")return ri(t,JSON.stringify(e),ht.NUMBER);if(typeof e=="boolean")return ri(t,JSON.stringify(e),ht.BOOLEAN);if(e===null)return ri(t,"null",ht.NULL);if(typeof e=="object"&&Object.getPrototypeOf(e)===Object.prototype){let r=Object.entries(e);return r.length===0?ri(t,"{}",ht.CODE):ri(t,"{ ",ht.CODE)+r.map(([s,a])=>`${Kk(t,s)}: ${Kk(t,a)}`).join(", ")+ri(t," }",ht.CODE)}if(typeof e>"u")return ri(t,"undefined",ht.NULL);throw new Error("Assertion failed: The value doesn't seem to be a valid JSON object")}function _u(t,e){return[e,t]}function zd(t,e,r){return t.get("enableColors")&&r&2&&(e=TB.default.bold(e)),e}function ri(t,e,r){if(!t.get("enableColors"))return e;let s=qze.get(r);if(s===null)return e;let a=typeof s>"u"?r:z4.level>=3?s[0]:s[1],n=typeof a=="number"?K4.ansi256(a):a.startsWith("#")?K4.hex(a):K4[a];if(typeof n!="function")throw new Error(`Invalid format type ${a}`);return n(e)}function KE(t,e,r){return t.get("enableHyperlinks")?Wze?`\x1B]8;;${r}\x1B\\${e}\x1B]8;;\x1B\\`:`\x1B]8;;${r}\x07${e}\x1B]8;;\x07`:e}function Ht(t,e,r){if(e===null)return ri(t,"null",ht.NULL);if(Object.hasOwn(zk,r))return zk[r].pretty(t,e);if(typeof e!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof e}`);return ri(t,e,r)}function Z4(t,e,r,{separator:s=", "}={}){return[...e].map(a=>Ht(t,a,r)).join(s)}function Xd(t,e){if(t===null)return null;if(Object.hasOwn(zk,e))return zk[e].json(t);if(typeof t!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof t}`);return t}function Yze(t,e,[r,s]){return t?Xd(r,s):Ht(e,r,s)}function $4(t){return{Check:ri(t,"\u2713","green"),Cross:ri(t,"\u2718","red"),Question:ri(t,"?","cyan")}}function Kf(t,{label:e,value:[r,s]}){return`${Ht(t,e,ht.CODE)}: ${Ht(t,r,s)}`}function $k(t,e,r){let s=[],a=[...e],n=r;for(;a.length>0;){let h=a[0],E=`${Yr(t,h)}, `,C=e3(h).length+2;if(s.length>0&&nh).join("").slice(0,-2);let c="X".repeat(a.length.toString().length),f=`and ${c} more.`,p=a.length;for(;s.length>1&&nh).join(""),f.replace(c,Ht(t,p,ht.NUMBER))].join("")}function RB(t,{configuration:e}){let r=e.get("logFilters"),s=new Map,a=new Map,n=[];for(let C of r){let S=C.get("level");if(typeof S>"u")continue;let P=C.get("code");typeof P<"u"&&s.set(P,S);let I=C.get("text");typeof I<"u"&&a.set(I,S);let R=C.get("pattern");typeof R<"u"&&n.push([dle.default.matcher(R,{contains:!0}),S])}n.reverse();let c=(C,S,P)=>{if(C===null||C===0)return P;let I=a.size>0||n.length>0?(0,JE.default)(S):S;if(a.size>0){let R=a.get(I);if(typeof R<"u")return R??P}if(n.length>0){for(let[R,N]of n)if(R(I))return N??P}if(s.size>0){let R=s.get(Yf(C));if(typeof R<"u")return R??P}return P},f=t.reportInfo,p=t.reportWarning,h=t.reportError,E=function(C,S,P,I){switch(c(S,P,I)){case"info":f.call(C,S,P);break;case"warning":p.call(C,S??0,P);break;case"error":h.call(C,S??0,P);break}};t.reportInfo=function(...C){return E(this,...C,"info")},t.reportWarning=function(...C){return E(this,...C,"warning")},t.reportError=function(...C){return E(this,...C,"error")}}var TB,QB,dle,JE,ht,Xk,z4,Zk,X4,K4,qze,qo,zk,Wze,eQ,xc=Xe(()=>{Dt();TB=ut(TE()),QB=ut(Fd());Yt();dle=ut(Go()),JE=ut(dk());Gx();Wo();ht={NO_HINT:"NO_HINT",ID:"ID",NULL:"NULL",SCOPE:"SCOPE",NAME:"NAME",RANGE:"RANGE",REFERENCE:"REFERENCE",NUMBER:"NUMBER",STRING:"STRING",BOOLEAN:"BOOLEAN",PATH:"PATH",URL:"URL",ADDED:"ADDED",REMOVED:"REMOVED",CODE:"CODE",INSPECT:"INSPECT",DURATION:"DURATION",SIZE:"SIZE",SIZE_DIFF:"SIZE_DIFF",IDENT:"IDENT",DESCRIPTOR:"DESCRIPTOR",LOCATOR:"LOCATOR",RESOLUTION:"RESOLUTION",DEPENDENT:"DEPENDENT",PACKAGE_EXTENSION:"PACKAGE_EXTENSION",SETTING:"SETTING",MARKDOWN:"MARKDOWN",MARKDOWN_INLINE:"MARKDOWN_INLINE"},Xk=(e=>(e[e.BOLD=2]="BOLD",e))(Xk||{}),z4=QB.default.GITHUB_ACTIONS?{level:2}:TB.default.supportsColor?{level:TB.default.supportsColor.level}:{level:0},Zk=z4.level!==0,X4=Zk&&!QB.default.GITHUB_ACTIONS&&!QB.default.CIRCLE&&!QB.default.GITLAB,K4=new TB.default.Instance(z4),qze=new Map([[ht.NO_HINT,null],[ht.NULL,["#a853b5",129]],[ht.SCOPE,["#d75f00",166]],[ht.NAME,["#d7875f",173]],[ht.RANGE,["#00afaf",37]],[ht.REFERENCE,["#87afff",111]],[ht.NUMBER,["#ffd700",220]],[ht.STRING,["#b4bd68",32]],[ht.BOOLEAN,["#faa023",209]],[ht.PATH,["#d75fd7",170]],[ht.URL,["#d75fd7",170]],[ht.ADDED,["#5faf00",70]],[ht.REMOVED,["#ff3131",160]],[ht.CODE,["#87afff",111]],[ht.SIZE,["#ffd700",220]]]),qo=t=>t;zk={[ht.ID]:qo({pretty:(t,e)=>typeof e=="number"?ri(t,`${e}`,ht.NUMBER):ri(t,e,ht.CODE),json:t=>t}),[ht.INSPECT]:qo({pretty:(t,e)=>Kk(t,e),json:t=>t}),[ht.NUMBER]:qo({pretty:(t,e)=>ri(t,`${e}`,ht.NUMBER),json:t=>t}),[ht.IDENT]:qo({pretty:(t,e)=>$i(t,e),json:t=>un(t)}),[ht.LOCATOR]:qo({pretty:(t,e)=>Yr(t,e),json:t=>ll(t)}),[ht.DESCRIPTOR]:qo({pretty:(t,e)=>ni(t,e),json:t=>al(t)}),[ht.RESOLUTION]:qo({pretty:(t,{descriptor:e,locator:r})=>FB(t,e,r),json:({descriptor:t,locator:e})=>({descriptor:al(t),locator:e!==null?ll(e):null})}),[ht.DEPENDENT]:qo({pretty:(t,{locator:e,descriptor:r})=>t3(t,e,r),json:({locator:t,descriptor:e})=>({locator:ll(t),descriptor:al(e)})}),[ht.PACKAGE_EXTENSION]:qo({pretty:(t,e)=>{switch(e.type){case"Dependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"dependencies",ht.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"peerDependencies",ht.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependencyMeta":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"peerDependenciesMeta",ht.CODE)} \u27A4 ${$i(t,Sa(e.selector))} \u27A4 ${ri(t,e.key,ht.CODE)}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${e.type}`)}},json:t=>{switch(t.type){case"Dependency":return`${un(t.parentDescriptor)} > ${un(t.descriptor)}`;case"PeerDependency":return`${un(t.parentDescriptor)} >> ${un(t.descriptor)}`;case"PeerDependencyMeta":return`${un(t.parentDescriptor)} >> ${t.selector} / ${t.key}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${t.type}`)}}}),[ht.SETTING]:qo({pretty:(t,e)=>(t.get(e),KE(t,ri(t,e,ht.CODE),`https://yarnpkg.com/configuration/yarnrc#${e}`)),json:t=>t}),[ht.DURATION]:qo({pretty:(t,e)=>{if(e>1e3*60){let r=Math.floor(e/1e3/60),s=Math.ceil((e-r*60*1e3)/1e3);return s===0?`${r}m`:`${r}m ${s}s`}else{let r=Math.floor(e/1e3),s=e-r*1e3;return s===0?`${r}s`:`${r}s ${s}ms`}},json:t=>t}),[ht.SIZE]:qo({pretty:(t,e)=>ri(t,gle(e),ht.NUMBER),json:t=>t}),[ht.SIZE_DIFF]:qo({pretty:(t,e)=>{let r=e>=0?"+":"-",s=r==="+"?ht.REMOVED:ht.ADDED;return ri(t,`${r} ${gle(Math.max(Math.abs(e),1))}`,s)},json:t=>t}),[ht.PATH]:qo({pretty:(t,e)=>ri(t,fe.fromPortablePath(e),ht.PATH),json:t=>fe.fromPortablePath(t)}),[ht.MARKDOWN]:qo({pretty:(t,{text:e,format:r,paragraphs:s})=>Ho(e,{format:r,paragraphs:s}),json:({text:t})=>t}),[ht.MARKDOWN_INLINE]:qo({pretty:(t,e)=>(e=e.replace(/(`+)((?:.|[\n])*?)\1/g,(r,s,a)=>Ht(t,s+a+s,ht.CODE)),e=e.replace(/(\*\*)((?:.|[\n])*?)\1/g,(r,s,a)=>zd(t,a,2)),e),json:t=>t})};Wze=!!process.env.KONSOLE_VERSION;eQ=(a=>(a.Error="error",a.Warning="warning",a.Info="info",a.Discard="discard",a))(eQ||{})});var mle=_(zE=>{"use strict";Object.defineProperty(zE,"__esModule",{value:!0});zE.splitWhen=zE.flatten=void 0;function Vze(t){return t.reduce((e,r)=>[].concat(e,r),[])}zE.flatten=Vze;function Jze(t,e){let r=[[]],s=0;for(let a of t)e(a)?(s++,r[s]=[]):r[s].push(a);return r}zE.splitWhen=Jze});var yle=_(tQ=>{"use strict";Object.defineProperty(tQ,"__esModule",{value:!0});tQ.isEnoentCodeError=void 0;function Kze(t){return t.code==="ENOENT"}tQ.isEnoentCodeError=Kze});var Ele=_(rQ=>{"use strict";Object.defineProperty(rQ,"__esModule",{value:!0});rQ.createDirentFromStats=void 0;var r3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function zze(t,e){return new r3(t,e)}rQ.createDirentFromStats=zze});var Ble=_(cs=>{"use strict";Object.defineProperty(cs,"__esModule",{value:!0});cs.convertPosixPathToPattern=cs.convertWindowsPathToPattern=cs.convertPathToPattern=cs.escapePosixPath=cs.escapeWindowsPath=cs.escape=cs.removeLeadingDotSegment=cs.makeAbsolute=cs.unixify=void 0;var Xze=Ie("os"),Zze=Ie("path"),Ile=Xze.platform()==="win32",$ze=2,eXe=/(\\?)([()*?[\]{|}]|^!|[!+@](?=\()|\\(?![!()*+?@[\]{|}]))/g,tXe=/(\\?)([()[\]{}]|^!|[!+@](?=\())/g,rXe=/^\\\\([.?])/,nXe=/\\(?![!()+@[\]{}])/g;function iXe(t){return t.replace(/\\/g,"/")}cs.unixify=iXe;function sXe(t,e){return Zze.resolve(t,e)}cs.makeAbsolute=sXe;function oXe(t){if(t.charAt(0)==="."){let e=t.charAt(1);if(e==="/"||e==="\\")return t.slice($ze)}return t}cs.removeLeadingDotSegment=oXe;cs.escape=Ile?n3:i3;function n3(t){return t.replace(tXe,"\\$2")}cs.escapeWindowsPath=n3;function i3(t){return t.replace(eXe,"\\$2")}cs.escapePosixPath=i3;cs.convertPathToPattern=Ile?Cle:wle;function Cle(t){return n3(t).replace(rXe,"//$1").replace(nXe,"/")}cs.convertWindowsPathToPattern=Cle;function wle(t){return i3(t)}cs.convertPosixPathToPattern=wle});var Sle=_((JOt,vle)=>{vle.exports=function(e){if(typeof e!="string"||e==="")return!1;for(var r;r=/(\\).|([@?!+*]\(.*\))/g.exec(e);){if(r[2])return!0;e=e.slice(r.index+r[0].length)}return!1}});var Ple=_((KOt,ble)=>{var aXe=Sle(),Dle={"{":"}","(":")","[":"]"},lXe=function(t){if(t[0]==="!")return!0;for(var e=0,r=-2,s=-2,a=-2,n=-2,c=-2;ee&&(c===-1||c>s||(c=t.indexOf("\\",e),c===-1||c>s)))||a!==-1&&t[e]==="{"&&t[e+1]!=="}"&&(a=t.indexOf("}",e),a>e&&(c=t.indexOf("\\",e),c===-1||c>a))||n!==-1&&t[e]==="("&&t[e+1]==="?"&&/[:!=]/.test(t[e+2])&&t[e+3]!==")"&&(n=t.indexOf(")",e),n>e&&(c=t.indexOf("\\",e),c===-1||c>n))||r!==-1&&t[e]==="("&&t[e+1]!=="|"&&(rr&&(c=t.indexOf("\\",r),c===-1||c>n))))return!0;if(t[e]==="\\"){var f=t[e+1];e+=2;var p=Dle[f];if(p){var h=t.indexOf(p,e);h!==-1&&(e=h+1)}if(t[e]==="!")return!0}else e++}return!1},cXe=function(t){if(t[0]==="!")return!0;for(var e=0;e{"use strict";var uXe=Ple(),fXe=Ie("path").posix.dirname,AXe=Ie("os").platform()==="win32",s3="/",pXe=/\\/g,hXe=/[\{\[].*[\}\]]$/,gXe=/(^|[^\\])([\{\[]|\([^\)]+$)/,dXe=/\\([\!\*\?\|\[\]\(\)\{\}])/g;xle.exports=function(e,r){var s=Object.assign({flipBackslashes:!0},r);s.flipBackslashes&&AXe&&e.indexOf(s3)<0&&(e=e.replace(pXe,s3)),hXe.test(e)&&(e+=s3),e+="a";do e=fXe(e);while(uXe(e)||gXe.test(e));return e.replace(dXe,"$1")}});var Mle=_(jr=>{"use strict";Object.defineProperty(jr,"__esModule",{value:!0});jr.removeDuplicateSlashes=jr.matchAny=jr.convertPatternsToRe=jr.makeRe=jr.getPatternParts=jr.expandBraceExpansion=jr.expandPatternsWithBraceExpansion=jr.isAffectDepthOfReadingPattern=jr.endsWithSlashGlobStar=jr.hasGlobStar=jr.getBaseDirectory=jr.isPatternRelatedToParentDirectory=jr.getPatternsOutsideCurrentDirectory=jr.getPatternsInsideCurrentDirectory=jr.getPositivePatterns=jr.getNegativePatterns=jr.isPositivePattern=jr.isNegativePattern=jr.convertToNegativePattern=jr.convertToPositivePattern=jr.isDynamicPattern=jr.isStaticPattern=void 0;var mXe=Ie("path"),yXe=kle(),o3=Go(),Qle="**",EXe="\\",IXe=/[*?]|^!/,CXe=/\[[^[]*]/,wXe=/(?:^|[^!*+?@])\([^(]*\|[^|]*\)/,BXe=/[!*+?@]\([^(]*\)/,vXe=/,|\.\./,SXe=/(?!^)\/{2,}/g;function Tle(t,e={}){return!Rle(t,e)}jr.isStaticPattern=Tle;function Rle(t,e={}){return t===""?!1:!!(e.caseSensitiveMatch===!1||t.includes(EXe)||IXe.test(t)||CXe.test(t)||wXe.test(t)||e.extglob!==!1&&BXe.test(t)||e.braceExpansion!==!1&&DXe(t))}jr.isDynamicPattern=Rle;function DXe(t){let e=t.indexOf("{");if(e===-1)return!1;let r=t.indexOf("}",e+1);if(r===-1)return!1;let s=t.slice(e,r);return vXe.test(s)}function bXe(t){return nQ(t)?t.slice(1):t}jr.convertToPositivePattern=bXe;function PXe(t){return"!"+t}jr.convertToNegativePattern=PXe;function nQ(t){return t.startsWith("!")&&t[1]!=="("}jr.isNegativePattern=nQ;function Fle(t){return!nQ(t)}jr.isPositivePattern=Fle;function xXe(t){return t.filter(nQ)}jr.getNegativePatterns=xXe;function kXe(t){return t.filter(Fle)}jr.getPositivePatterns=kXe;function QXe(t){return t.filter(e=>!a3(e))}jr.getPatternsInsideCurrentDirectory=QXe;function TXe(t){return t.filter(a3)}jr.getPatternsOutsideCurrentDirectory=TXe;function a3(t){return t.startsWith("..")||t.startsWith("./..")}jr.isPatternRelatedToParentDirectory=a3;function RXe(t){return yXe(t,{flipBackslashes:!1})}jr.getBaseDirectory=RXe;function FXe(t){return t.includes(Qle)}jr.hasGlobStar=FXe;function Nle(t){return t.endsWith("/"+Qle)}jr.endsWithSlashGlobStar=Nle;function NXe(t){let e=mXe.basename(t);return Nle(t)||Tle(e)}jr.isAffectDepthOfReadingPattern=NXe;function OXe(t){return t.reduce((e,r)=>e.concat(Ole(r)),[])}jr.expandPatternsWithBraceExpansion=OXe;function Ole(t){let e=o3.braces(t,{expand:!0,nodupes:!0,keepEscaping:!0});return e.sort((r,s)=>r.length-s.length),e.filter(r=>r!=="")}jr.expandBraceExpansion=Ole;function LXe(t,e){let{parts:r}=o3.scan(t,Object.assign(Object.assign({},e),{parts:!0}));return r.length===0&&(r=[t]),r[0].startsWith("/")&&(r[0]=r[0].slice(1),r.unshift("")),r}jr.getPatternParts=LXe;function Lle(t,e){return o3.makeRe(t,e)}jr.makeRe=Lle;function MXe(t,e){return t.map(r=>Lle(r,e))}jr.convertPatternsToRe=MXe;function UXe(t,e){return e.some(r=>r.test(t))}jr.matchAny=UXe;function _Xe(t){return t.replace(SXe,"/")}jr.removeDuplicateSlashes=_Xe});var jle=_((ZOt,Hle)=>{"use strict";var HXe=Ie("stream"),Ule=HXe.PassThrough,jXe=Array.prototype.slice;Hle.exports=GXe;function GXe(){let t=[],e=jXe.call(arguments),r=!1,s=e[e.length-1];s&&!Array.isArray(s)&&s.pipe==null?e.pop():s={};let a=s.end!==!1,n=s.pipeError===!0;s.objectMode==null&&(s.objectMode=!0),s.highWaterMark==null&&(s.highWaterMark=64*1024);let c=Ule(s);function f(){for(let E=0,C=arguments.length;E0||(r=!1,p())}function P(I){function R(){I.removeListener("merge2UnpipeEnd",R),I.removeListener("end",R),n&&I.removeListener("error",N),S()}function N(U){c.emit("error",U)}if(I._readableState.endEmitted)return S();I.on("merge2UnpipeEnd",R),I.on("end",R),n&&I.on("error",N),I.pipe(c,{end:!1}),I.resume()}for(let I=0;I{"use strict";Object.defineProperty(iQ,"__esModule",{value:!0});iQ.merge=void 0;var qXe=jle();function WXe(t){let e=qXe(t);return t.forEach(r=>{r.once("error",s=>e.emit("error",s))}),e.once("close",()=>Gle(t)),e.once("end",()=>Gle(t)),e}iQ.merge=WXe;function Gle(t){t.forEach(e=>e.emit("close"))}});var Wle=_(XE=>{"use strict";Object.defineProperty(XE,"__esModule",{value:!0});XE.isEmpty=XE.isString=void 0;function YXe(t){return typeof t=="string"}XE.isString=YXe;function VXe(t){return t===""}XE.isEmpty=VXe});var xp=_(Yo=>{"use strict";Object.defineProperty(Yo,"__esModule",{value:!0});Yo.string=Yo.stream=Yo.pattern=Yo.path=Yo.fs=Yo.errno=Yo.array=void 0;var JXe=mle();Yo.array=JXe;var KXe=yle();Yo.errno=KXe;var zXe=Ele();Yo.fs=zXe;var XXe=Ble();Yo.path=XXe;var ZXe=Mle();Yo.pattern=ZXe;var $Xe=qle();Yo.stream=$Xe;var eZe=Wle();Yo.string=eZe});var Kle=_(Vo=>{"use strict";Object.defineProperty(Vo,"__esModule",{value:!0});Vo.convertPatternGroupToTask=Vo.convertPatternGroupsToTasks=Vo.groupPatternsByBaseDirectory=Vo.getNegativePatternsAsPositive=Vo.getPositivePatterns=Vo.convertPatternsToTasks=Vo.generate=void 0;var Hu=xp();function tZe(t,e){let r=Yle(t,e),s=Yle(e.ignore,e),a=Vle(r),n=Jle(r,s),c=a.filter(E=>Hu.pattern.isStaticPattern(E,e)),f=a.filter(E=>Hu.pattern.isDynamicPattern(E,e)),p=l3(c,n,!1),h=l3(f,n,!0);return p.concat(h)}Vo.generate=tZe;function Yle(t,e){let r=t;return e.braceExpansion&&(r=Hu.pattern.expandPatternsWithBraceExpansion(r)),e.baseNameMatch&&(r=r.map(s=>s.includes("/")?s:`**/${s}`)),r.map(s=>Hu.pattern.removeDuplicateSlashes(s))}function l3(t,e,r){let s=[],a=Hu.pattern.getPatternsOutsideCurrentDirectory(t),n=Hu.pattern.getPatternsInsideCurrentDirectory(t),c=c3(a),f=c3(n);return s.push(...u3(c,e,r)),"."in f?s.push(f3(".",n,e,r)):s.push(...u3(f,e,r)),s}Vo.convertPatternsToTasks=l3;function Vle(t){return Hu.pattern.getPositivePatterns(t)}Vo.getPositivePatterns=Vle;function Jle(t,e){return Hu.pattern.getNegativePatterns(t).concat(e).map(Hu.pattern.convertToPositivePattern)}Vo.getNegativePatternsAsPositive=Jle;function c3(t){let e={};return t.reduce((r,s)=>{let a=Hu.pattern.getBaseDirectory(s);return a in r?r[a].push(s):r[a]=[s],r},e)}Vo.groupPatternsByBaseDirectory=c3;function u3(t,e,r){return Object.keys(t).map(s=>f3(s,t[s],e,r))}Vo.convertPatternGroupsToTasks=u3;function f3(t,e,r,s){return{dynamic:s,positive:e,negative:r,base:t,patterns:[].concat(e,r.map(Hu.pattern.convertToNegativePattern))}}Vo.convertPatternGroupToTask=f3});var Xle=_(sQ=>{"use strict";Object.defineProperty(sQ,"__esModule",{value:!0});sQ.read=void 0;function rZe(t,e,r){e.fs.lstat(t,(s,a)=>{if(s!==null){zle(r,s);return}if(!a.isSymbolicLink()||!e.followSymbolicLink){A3(r,a);return}e.fs.stat(t,(n,c)=>{if(n!==null){if(e.throwErrorOnBrokenSymbolicLink){zle(r,n);return}A3(r,a);return}e.markSymbolicLink&&(c.isSymbolicLink=()=>!0),A3(r,c)})})}sQ.read=rZe;function zle(t,e){t(e)}function A3(t,e){t(null,e)}});var Zle=_(oQ=>{"use strict";Object.defineProperty(oQ,"__esModule",{value:!0});oQ.read=void 0;function nZe(t,e){let r=e.fs.lstatSync(t);if(!r.isSymbolicLink()||!e.followSymbolicLink)return r;try{let s=e.fs.statSync(t);return e.markSymbolicLink&&(s.isSymbolicLink=()=>!0),s}catch(s){if(!e.throwErrorOnBrokenSymbolicLink)return r;throw s}}oQ.read=nZe});var $le=_(h0=>{"use strict";Object.defineProperty(h0,"__esModule",{value:!0});h0.createFileSystemAdapter=h0.FILE_SYSTEM_ADAPTER=void 0;var aQ=Ie("fs");h0.FILE_SYSTEM_ADAPTER={lstat:aQ.lstat,stat:aQ.stat,lstatSync:aQ.lstatSync,statSync:aQ.statSync};function iZe(t){return t===void 0?h0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},h0.FILE_SYSTEM_ADAPTER),t)}h0.createFileSystemAdapter=iZe});var ece=_(h3=>{"use strict";Object.defineProperty(h3,"__esModule",{value:!0});var sZe=$le(),p3=class{constructor(e={}){this._options=e,this.followSymbolicLink=this._getValue(this._options.followSymbolicLink,!0),this.fs=sZe.createFileSystemAdapter(this._options.fs),this.markSymbolicLink=this._getValue(this._options.markSymbolicLink,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0)}_getValue(e,r){return e??r}};h3.default=p3});var Zd=_(g0=>{"use strict";Object.defineProperty(g0,"__esModule",{value:!0});g0.statSync=g0.stat=g0.Settings=void 0;var tce=Xle(),oZe=Zle(),g3=ece();g0.Settings=g3.default;function aZe(t,e,r){if(typeof e=="function"){tce.read(t,d3(),e);return}tce.read(t,d3(e),r)}g0.stat=aZe;function lZe(t,e){let r=d3(e);return oZe.read(t,r)}g0.statSync=lZe;function d3(t={}){return t instanceof g3.default?t:new g3.default(t)}});var ice=_((lLt,nce)=>{var rce;nce.exports=typeof queueMicrotask=="function"?queueMicrotask.bind(typeof window<"u"?window:global):t=>(rce||(rce=Promise.resolve())).then(t).catch(e=>setTimeout(()=>{throw e},0))});var oce=_((cLt,sce)=>{sce.exports=uZe;var cZe=ice();function uZe(t,e){let r,s,a,n=!0;Array.isArray(t)?(r=[],s=t.length):(a=Object.keys(t),r={},s=a.length);function c(p){function h(){e&&e(p,r),e=null}n?cZe(h):h()}function f(p,h,E){r[p]=E,(--s===0||h)&&c(h)}s?a?a.forEach(function(p){t[p](function(h,E){f(p,h,E)})}):t.forEach(function(p,h){p(function(E,C){f(h,E,C)})}):c(null),n=!1}});var m3=_(cQ=>{"use strict";Object.defineProperty(cQ,"__esModule",{value:!0});cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=void 0;var lQ=process.versions.node.split(".");if(lQ[0]===void 0||lQ[1]===void 0)throw new Error(`Unexpected behavior. The 'process.versions.node' variable has invalid value: ${process.versions.node}`);var ace=Number.parseInt(lQ[0],10),fZe=Number.parseInt(lQ[1],10),lce=10,AZe=10,pZe=ace>lce,hZe=ace===lce&&fZe>=AZe;cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=pZe||hZe});var cce=_(uQ=>{"use strict";Object.defineProperty(uQ,"__esModule",{value:!0});uQ.createDirentFromStats=void 0;var y3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function gZe(t,e){return new y3(t,e)}uQ.createDirentFromStats=gZe});var E3=_(fQ=>{"use strict";Object.defineProperty(fQ,"__esModule",{value:!0});fQ.fs=void 0;var dZe=cce();fQ.fs=dZe});var I3=_(AQ=>{"use strict";Object.defineProperty(AQ,"__esModule",{value:!0});AQ.joinPathSegments=void 0;function mZe(t,e,r){return t.endsWith(r)?t+e:t+r+e}AQ.joinPathSegments=mZe});var gce=_(d0=>{"use strict";Object.defineProperty(d0,"__esModule",{value:!0});d0.readdir=d0.readdirWithFileTypes=d0.read=void 0;var yZe=Zd(),uce=oce(),EZe=m3(),fce=E3(),Ace=I3();function IZe(t,e,r){if(!e.stats&&EZe.IS_SUPPORT_READDIR_WITH_FILE_TYPES){pce(t,e,r);return}hce(t,e,r)}d0.read=IZe;function pce(t,e,r){e.fs.readdir(t,{withFileTypes:!0},(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(f=>({dirent:f,name:f.name,path:Ace.joinPathSegments(t,f.name,e.pathSegmentSeparator)}));if(!e.followSymbolicLinks){C3(r,n);return}let c=n.map(f=>CZe(f,e));uce(c,(f,p)=>{if(f!==null){pQ(r,f);return}C3(r,p)})})}d0.readdirWithFileTypes=pce;function CZe(t,e){return r=>{if(!t.dirent.isSymbolicLink()){r(null,t);return}e.fs.stat(t.path,(s,a)=>{if(s!==null){if(e.throwErrorOnBrokenSymbolicLink){r(s);return}r(null,t);return}t.dirent=fce.fs.createDirentFromStats(t.name,a),r(null,t)})}}function hce(t,e,r){e.fs.readdir(t,(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(c=>{let f=Ace.joinPathSegments(t,c,e.pathSegmentSeparator);return p=>{yZe.stat(f,e.fsStatSettings,(h,E)=>{if(h!==null){p(h);return}let C={name:c,path:f,dirent:fce.fs.createDirentFromStats(c,E)};e.stats&&(C.stats=E),p(null,C)})}});uce(n,(c,f)=>{if(c!==null){pQ(r,c);return}C3(r,f)})})}d0.readdir=hce;function pQ(t,e){t(e)}function C3(t,e){t(null,e)}});var Ice=_(m0=>{"use strict";Object.defineProperty(m0,"__esModule",{value:!0});m0.readdir=m0.readdirWithFileTypes=m0.read=void 0;var wZe=Zd(),BZe=m3(),dce=E3(),mce=I3();function vZe(t,e){return!e.stats&&BZe.IS_SUPPORT_READDIR_WITH_FILE_TYPES?yce(t,e):Ece(t,e)}m0.read=vZe;function yce(t,e){return e.fs.readdirSync(t,{withFileTypes:!0}).map(s=>{let a={dirent:s,name:s.name,path:mce.joinPathSegments(t,s.name,e.pathSegmentSeparator)};if(a.dirent.isSymbolicLink()&&e.followSymbolicLinks)try{let n=e.fs.statSync(a.path);a.dirent=dce.fs.createDirentFromStats(a.name,n)}catch(n){if(e.throwErrorOnBrokenSymbolicLink)throw n}return a})}m0.readdirWithFileTypes=yce;function Ece(t,e){return e.fs.readdirSync(t).map(s=>{let a=mce.joinPathSegments(t,s,e.pathSegmentSeparator),n=wZe.statSync(a,e.fsStatSettings),c={name:s,path:a,dirent:dce.fs.createDirentFromStats(s,n)};return e.stats&&(c.stats=n),c})}m0.readdir=Ece});var Cce=_(y0=>{"use strict";Object.defineProperty(y0,"__esModule",{value:!0});y0.createFileSystemAdapter=y0.FILE_SYSTEM_ADAPTER=void 0;var ZE=Ie("fs");y0.FILE_SYSTEM_ADAPTER={lstat:ZE.lstat,stat:ZE.stat,lstatSync:ZE.lstatSync,statSync:ZE.statSync,readdir:ZE.readdir,readdirSync:ZE.readdirSync};function SZe(t){return t===void 0?y0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},y0.FILE_SYSTEM_ADAPTER),t)}y0.createFileSystemAdapter=SZe});var wce=_(B3=>{"use strict";Object.defineProperty(B3,"__esModule",{value:!0});var DZe=Ie("path"),bZe=Zd(),PZe=Cce(),w3=class{constructor(e={}){this._options=e,this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!1),this.fs=PZe.createFileSystemAdapter(this._options.fs),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,DZe.sep),this.stats=this._getValue(this._options.stats,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0),this.fsStatSettings=new bZe.Settings({followSymbolicLink:this.followSymbolicLinks,fs:this.fs,throwErrorOnBrokenSymbolicLink:this.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};B3.default=w3});var hQ=_(E0=>{"use strict";Object.defineProperty(E0,"__esModule",{value:!0});E0.Settings=E0.scandirSync=E0.scandir=void 0;var Bce=gce(),xZe=Ice(),v3=wce();E0.Settings=v3.default;function kZe(t,e,r){if(typeof e=="function"){Bce.read(t,S3(),e);return}Bce.read(t,S3(e),r)}E0.scandir=kZe;function QZe(t,e){let r=S3(e);return xZe.read(t,r)}E0.scandirSync=QZe;function S3(t={}){return t instanceof v3.default?t:new v3.default(t)}});var Sce=_((ELt,vce)=>{"use strict";function TZe(t){var e=new t,r=e;function s(){var n=e;return n.next?e=n.next:(e=new t,r=e),n.next=null,n}function a(n){r.next=n,r=n}return{get:s,release:a}}vce.exports=TZe});var bce=_((ILt,D3)=>{"use strict";var RZe=Sce();function Dce(t,e,r){if(typeof t=="function"&&(r=e,e=t,t=null),!(r>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");var s=RZe(FZe),a=null,n=null,c=0,f=null,p={push:R,drain:kc,saturated:kc,pause:E,paused:!1,get concurrency(){return r},set concurrency(ue){if(!(ue>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");if(r=ue,!p.paused)for(;a&&c=r||p.paused?n?(n.next=me,n=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function N(ue,le){var me=s.get();me.context=t,me.release=U,me.value=ue,me.callback=le||kc,me.errorHandler=f,c>=r||p.paused?a?(me.next=a,a=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function U(ue){ue&&s.release(ue);var le=a;le&&c<=r?p.paused?c--:(n===a&&(n=null),a=le.next,le.next=null,e.call(t,le.value,le.worked),n===null&&p.empty()):--c===0&&p.drain()}function W(){a=null,n=null,p.drain=kc}function ee(){a=null,n=null,p.drain(),p.drain=kc}function ie(ue){f=ue}}function kc(){}function FZe(){this.value=null,this.callback=kc,this.next=null,this.release=kc,this.context=null,this.errorHandler=null;var t=this;this.worked=function(r,s){var a=t.callback,n=t.errorHandler,c=t.value;t.value=null,t.callback=kc,t.errorHandler&&n(r,c),a.call(t.context,r,s),t.release(t)}}function NZe(t,e,r){typeof t=="function"&&(r=e,e=t,t=null);function s(E,C){e.call(this,E).then(function(S){C(null,S)},C)}var a=Dce(t,s,r),n=a.push,c=a.unshift;return a.push=f,a.unshift=p,a.drained=h,a;function f(E){var C=new Promise(function(S,P){n(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(kc),C}function p(E){var C=new Promise(function(S,P){c(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(kc),C}function h(){if(a.idle())return new Promise(function(S){S()});var E=a.drain,C=new Promise(function(S){a.drain=function(){E(),S()}});return C}}D3.exports=Dce;D3.exports.promise=NZe});var gQ=_(zf=>{"use strict";Object.defineProperty(zf,"__esModule",{value:!0});zf.joinPathSegments=zf.replacePathSegmentSeparator=zf.isAppliedFilter=zf.isFatalError=void 0;function OZe(t,e){return t.errorFilter===null?!0:!t.errorFilter(e)}zf.isFatalError=OZe;function LZe(t,e){return t===null||t(e)}zf.isAppliedFilter=LZe;function MZe(t,e){return t.split(/[/\\]/).join(e)}zf.replacePathSegmentSeparator=MZe;function UZe(t,e,r){return t===""?e:t.endsWith(r)?t+e:t+r+e}zf.joinPathSegments=UZe});var x3=_(P3=>{"use strict";Object.defineProperty(P3,"__esModule",{value:!0});var _Ze=gQ(),b3=class{constructor(e,r){this._root=e,this._settings=r,this._root=_Ze.replacePathSegmentSeparator(e,r.pathSegmentSeparator)}};P3.default=b3});var T3=_(Q3=>{"use strict";Object.defineProperty(Q3,"__esModule",{value:!0});var HZe=Ie("events"),jZe=hQ(),GZe=bce(),dQ=gQ(),qZe=x3(),k3=class extends qZe.default{constructor(e,r){super(e,r),this._settings=r,this._scandir=jZe.scandir,this._emitter=new HZe.EventEmitter,this._queue=GZe(this._worker.bind(this),this._settings.concurrency),this._isFatalError=!1,this._isDestroyed=!1,this._queue.drain=()=>{this._isFatalError||this._emitter.emit("end")}}read(){return this._isFatalError=!1,this._isDestroyed=!1,setImmediate(()=>{this._pushToQueue(this._root,this._settings.basePath)}),this._emitter}get isDestroyed(){return this._isDestroyed}destroy(){if(this._isDestroyed)throw new Error("The reader is already destroyed");this._isDestroyed=!0,this._queue.killAndDrain()}onEntry(e){this._emitter.on("entry",e)}onError(e){this._emitter.once("error",e)}onEnd(e){this._emitter.once("end",e)}_pushToQueue(e,r){let s={directory:e,base:r};this._queue.push(s,a=>{a!==null&&this._handleError(a)})}_worker(e,r){this._scandir(e.directory,this._settings.fsScandirSettings,(s,a)=>{if(s!==null){r(s,void 0);return}for(let n of a)this._handleEntry(n,e.base);r(null,void 0)})}_handleError(e){this._isDestroyed||!dQ.isFatalError(this._settings,e)||(this._isFatalError=!0,this._isDestroyed=!0,this._emitter.emit("error",e))}_handleEntry(e,r){if(this._isDestroyed||this._isFatalError)return;let s=e.path;r!==void 0&&(e.path=dQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),dQ.isAppliedFilter(this._settings.entryFilter,e)&&this._emitEntry(e),e.dirent.isDirectory()&&dQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_emitEntry(e){this._emitter.emit("entry",e)}};Q3.default=k3});var Pce=_(F3=>{"use strict";Object.defineProperty(F3,"__esModule",{value:!0});var WZe=T3(),R3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new WZe.default(this._root,this._settings),this._storage=[]}read(e){this._reader.onError(r=>{YZe(e,r)}),this._reader.onEntry(r=>{this._storage.push(r)}),this._reader.onEnd(()=>{VZe(e,this._storage)}),this._reader.read()}};F3.default=R3;function YZe(t,e){t(e)}function VZe(t,e){t(null,e)}});var xce=_(O3=>{"use strict";Object.defineProperty(O3,"__esModule",{value:!0});var JZe=Ie("stream"),KZe=T3(),N3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new KZe.default(this._root,this._settings),this._stream=new JZe.Readable({objectMode:!0,read:()=>{},destroy:()=>{this._reader.isDestroyed||this._reader.destroy()}})}read(){return this._reader.onError(e=>{this._stream.emit("error",e)}),this._reader.onEntry(e=>{this._stream.push(e)}),this._reader.onEnd(()=>{this._stream.push(null)}),this._reader.read(),this._stream}};O3.default=N3});var kce=_(M3=>{"use strict";Object.defineProperty(M3,"__esModule",{value:!0});var zZe=hQ(),mQ=gQ(),XZe=x3(),L3=class extends XZe.default{constructor(){super(...arguments),this._scandir=zZe.scandirSync,this._storage=[],this._queue=new Set}read(){return this._pushToQueue(this._root,this._settings.basePath),this._handleQueue(),this._storage}_pushToQueue(e,r){this._queue.add({directory:e,base:r})}_handleQueue(){for(let e of this._queue.values())this._handleDirectory(e.directory,e.base)}_handleDirectory(e,r){try{let s=this._scandir(e,this._settings.fsScandirSettings);for(let a of s)this._handleEntry(a,r)}catch(s){this._handleError(s)}}_handleError(e){if(mQ.isFatalError(this._settings,e))throw e}_handleEntry(e,r){let s=e.path;r!==void 0&&(e.path=mQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),mQ.isAppliedFilter(this._settings.entryFilter,e)&&this._pushToStorage(e),e.dirent.isDirectory()&&mQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_pushToStorage(e){this._storage.push(e)}};M3.default=L3});var Qce=_(_3=>{"use strict";Object.defineProperty(_3,"__esModule",{value:!0});var ZZe=kce(),U3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new ZZe.default(this._root,this._settings)}read(){return this._reader.read()}};_3.default=U3});var Tce=_(j3=>{"use strict";Object.defineProperty(j3,"__esModule",{value:!0});var $Ze=Ie("path"),e$e=hQ(),H3=class{constructor(e={}){this._options=e,this.basePath=this._getValue(this._options.basePath,void 0),this.concurrency=this._getValue(this._options.concurrency,Number.POSITIVE_INFINITY),this.deepFilter=this._getValue(this._options.deepFilter,null),this.entryFilter=this._getValue(this._options.entryFilter,null),this.errorFilter=this._getValue(this._options.errorFilter,null),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,$Ze.sep),this.fsScandirSettings=new e$e.Settings({followSymbolicLinks:this._options.followSymbolicLinks,fs:this._options.fs,pathSegmentSeparator:this._options.pathSegmentSeparator,stats:this._options.stats,throwErrorOnBrokenSymbolicLink:this._options.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};j3.default=H3});var EQ=_(Xf=>{"use strict";Object.defineProperty(Xf,"__esModule",{value:!0});Xf.Settings=Xf.walkStream=Xf.walkSync=Xf.walk=void 0;var Rce=Pce(),t$e=xce(),r$e=Qce(),G3=Tce();Xf.Settings=G3.default;function n$e(t,e,r){if(typeof e=="function"){new Rce.default(t,yQ()).read(e);return}new Rce.default(t,yQ(e)).read(r)}Xf.walk=n$e;function i$e(t,e){let r=yQ(e);return new r$e.default(t,r).read()}Xf.walkSync=i$e;function s$e(t,e){let r=yQ(e);return new t$e.default(t,r).read()}Xf.walkStream=s$e;function yQ(t={}){return t instanceof G3.default?t:new G3.default(t)}});var IQ=_(W3=>{"use strict";Object.defineProperty(W3,"__esModule",{value:!0});var o$e=Ie("path"),a$e=Zd(),Fce=xp(),q3=class{constructor(e){this._settings=e,this._fsStatSettings=new a$e.Settings({followSymbolicLink:this._settings.followSymbolicLinks,fs:this._settings.fs,throwErrorOnBrokenSymbolicLink:this._settings.followSymbolicLinks})}_getFullEntryPath(e){return o$e.resolve(this._settings.cwd,e)}_makeEntry(e,r){let s={name:r,path:r,dirent:Fce.fs.createDirentFromStats(r,e)};return this._settings.stats&&(s.stats=e),s}_isFatalError(e){return!Fce.errno.isEnoentCodeError(e)&&!this._settings.suppressErrors}};W3.default=q3});var J3=_(V3=>{"use strict";Object.defineProperty(V3,"__esModule",{value:!0});var l$e=Ie("stream"),c$e=Zd(),u$e=EQ(),f$e=IQ(),Y3=class extends f$e.default{constructor(){super(...arguments),this._walkStream=u$e.walkStream,this._stat=c$e.stat}dynamic(e,r){return this._walkStream(e,r)}static(e,r){let s=e.map(this._getFullEntryPath,this),a=new l$e.PassThrough({objectMode:!0});a._write=(n,c,f)=>this._getEntry(s[n],e[n],r).then(p=>{p!==null&&r.entryFilter(p)&&a.push(p),n===s.length-1&&a.end(),f()}).catch(f);for(let n=0;nthis._makeEntry(a,r)).catch(a=>{if(s.errorFilter(a))return null;throw a})}_getStat(e){return new Promise((r,s)=>{this._stat(e,this._fsStatSettings,(a,n)=>a===null?r(n):s(a))})}};V3.default=Y3});var Nce=_(z3=>{"use strict";Object.defineProperty(z3,"__esModule",{value:!0});var A$e=EQ(),p$e=IQ(),h$e=J3(),K3=class extends p$e.default{constructor(){super(...arguments),this._walkAsync=A$e.walk,this._readerStream=new h$e.default(this._settings)}dynamic(e,r){return new Promise((s,a)=>{this._walkAsync(e,r,(n,c)=>{n===null?s(c):a(n)})})}async static(e,r){let s=[],a=this._readerStream.static(e,r);return new Promise((n,c)=>{a.once("error",c),a.on("data",f=>s.push(f)),a.once("end",()=>n(s))})}};z3.default=K3});var Oce=_(Z3=>{"use strict";Object.defineProperty(Z3,"__esModule",{value:!0});var NB=xp(),X3=class{constructor(e,r,s){this._patterns=e,this._settings=r,this._micromatchOptions=s,this._storage=[],this._fillStorage()}_fillStorage(){for(let e of this._patterns){let r=this._getPatternSegments(e),s=this._splitSegmentsIntoSections(r);this._storage.push({complete:s.length<=1,pattern:e,segments:r,sections:s})}}_getPatternSegments(e){return NB.pattern.getPatternParts(e,this._micromatchOptions).map(s=>NB.pattern.isDynamicPattern(s,this._settings)?{dynamic:!0,pattern:s,patternRe:NB.pattern.makeRe(s,this._micromatchOptions)}:{dynamic:!1,pattern:s})}_splitSegmentsIntoSections(e){return NB.array.splitWhen(e,r=>r.dynamic&&NB.pattern.hasGlobStar(r.pattern))}};Z3.default=X3});var Lce=_(e8=>{"use strict";Object.defineProperty(e8,"__esModule",{value:!0});var g$e=Oce(),$3=class extends g$e.default{match(e){let r=e.split("/"),s=r.length,a=this._storage.filter(n=>!n.complete||n.segments.length>s);for(let n of a){let c=n.sections[0];if(!n.complete&&s>c.length||r.every((p,h)=>{let E=n.segments[h];return!!(E.dynamic&&E.patternRe.test(p)||!E.dynamic&&E.pattern===p)}))return!0}return!1}};e8.default=$3});var Mce=_(r8=>{"use strict";Object.defineProperty(r8,"__esModule",{value:!0});var CQ=xp(),d$e=Lce(),t8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r}getFilter(e,r,s){let a=this._getMatcher(r),n=this._getNegativePatternsRe(s);return c=>this._filter(e,c,a,n)}_getMatcher(e){return new d$e.default(e,this._settings,this._micromatchOptions)}_getNegativePatternsRe(e){let r=e.filter(CQ.pattern.isAffectDepthOfReadingPattern);return CQ.pattern.convertPatternsToRe(r,this._micromatchOptions)}_filter(e,r,s,a){if(this._isSkippedByDeep(e,r.path)||this._isSkippedSymbolicLink(r))return!1;let n=CQ.path.removeLeadingDotSegment(r.path);return this._isSkippedByPositivePatterns(n,s)?!1:this._isSkippedByNegativePatterns(n,a)}_isSkippedByDeep(e,r){return this._settings.deep===1/0?!1:this._getEntryLevel(e,r)>=this._settings.deep}_getEntryLevel(e,r){let s=r.split("/").length;if(e==="")return s;let a=e.split("/").length;return s-a}_isSkippedSymbolicLink(e){return!this._settings.followSymbolicLinks&&e.dirent.isSymbolicLink()}_isSkippedByPositivePatterns(e,r){return!this._settings.baseNameMatch&&!r.match(e)}_isSkippedByNegativePatterns(e,r){return!CQ.pattern.matchAny(e,r)}};r8.default=t8});var Uce=_(i8=>{"use strict";Object.defineProperty(i8,"__esModule",{value:!0});var $d=xp(),n8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r,this.index=new Map}getFilter(e,r){let s=$d.pattern.convertPatternsToRe(e,this._micromatchOptions),a=$d.pattern.convertPatternsToRe(r,Object.assign(Object.assign({},this._micromatchOptions),{dot:!0}));return n=>this._filter(n,s,a)}_filter(e,r,s){let a=$d.path.removeLeadingDotSegment(e.path);if(this._settings.unique&&this._isDuplicateEntry(a)||this._onlyFileFilter(e)||this._onlyDirectoryFilter(e)||this._isSkippedByAbsoluteNegativePatterns(a,s))return!1;let n=e.dirent.isDirectory(),c=this._isMatchToPatterns(a,r,n)&&!this._isMatchToPatterns(a,s,n);return this._settings.unique&&c&&this._createIndexRecord(a),c}_isDuplicateEntry(e){return this.index.has(e)}_createIndexRecord(e){this.index.set(e,void 0)}_onlyFileFilter(e){return this._settings.onlyFiles&&!e.dirent.isFile()}_onlyDirectoryFilter(e){return this._settings.onlyDirectories&&!e.dirent.isDirectory()}_isSkippedByAbsoluteNegativePatterns(e,r){if(!this._settings.absolute)return!1;let s=$d.path.makeAbsolute(this._settings.cwd,e);return $d.pattern.matchAny(s,r)}_isMatchToPatterns(e,r,s){let a=$d.pattern.matchAny(e,r);return!a&&s?$d.pattern.matchAny(e+"/",r):a}};i8.default=n8});var _ce=_(o8=>{"use strict";Object.defineProperty(o8,"__esModule",{value:!0});var m$e=xp(),s8=class{constructor(e){this._settings=e}getFilter(){return e=>this._isNonFatalError(e)}_isNonFatalError(e){return m$e.errno.isEnoentCodeError(e)||this._settings.suppressErrors}};o8.default=s8});var jce=_(l8=>{"use strict";Object.defineProperty(l8,"__esModule",{value:!0});var Hce=xp(),a8=class{constructor(e){this._settings=e}getTransformer(){return e=>this._transform(e)}_transform(e){let r=e.path;return this._settings.absolute&&(r=Hce.path.makeAbsolute(this._settings.cwd,r),r=Hce.path.unixify(r)),this._settings.markDirectories&&e.dirent.isDirectory()&&(r+="/"),this._settings.objectMode?Object.assign(Object.assign({},e),{path:r}):r}};l8.default=a8});var wQ=_(u8=>{"use strict";Object.defineProperty(u8,"__esModule",{value:!0});var y$e=Ie("path"),E$e=Mce(),I$e=Uce(),C$e=_ce(),w$e=jce(),c8=class{constructor(e){this._settings=e,this.errorFilter=new C$e.default(this._settings),this.entryFilter=new I$e.default(this._settings,this._getMicromatchOptions()),this.deepFilter=new E$e.default(this._settings,this._getMicromatchOptions()),this.entryTransformer=new w$e.default(this._settings)}_getRootDirectory(e){return y$e.resolve(this._settings.cwd,e.base)}_getReaderOptions(e){let r=e.base==="."?"":e.base;return{basePath:r,pathSegmentSeparator:"/",concurrency:this._settings.concurrency,deepFilter:this.deepFilter.getFilter(r,e.positive,e.negative),entryFilter:this.entryFilter.getFilter(e.positive,e.negative),errorFilter:this.errorFilter.getFilter(),followSymbolicLinks:this._settings.followSymbolicLinks,fs:this._settings.fs,stats:this._settings.stats,throwErrorOnBrokenSymbolicLink:this._settings.throwErrorOnBrokenSymbolicLink,transform:this.entryTransformer.getTransformer()}}_getMicromatchOptions(){return{dot:this._settings.dot,matchBase:this._settings.baseNameMatch,nobrace:!this._settings.braceExpansion,nocase:!this._settings.caseSensitiveMatch,noext:!this._settings.extglob,noglobstar:!this._settings.globstar,posix:!0,strictSlashes:!1}}};u8.default=c8});var Gce=_(A8=>{"use strict";Object.defineProperty(A8,"__esModule",{value:!0});var B$e=Nce(),v$e=wQ(),f8=class extends v$e.default{constructor(){super(...arguments),this._reader=new B$e.default(this._settings)}async read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return(await this.api(r,e,s)).map(n=>s.transform(n))}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};A8.default=f8});var qce=_(h8=>{"use strict";Object.defineProperty(h8,"__esModule",{value:!0});var S$e=Ie("stream"),D$e=J3(),b$e=wQ(),p8=class extends b$e.default{constructor(){super(...arguments),this._reader=new D$e.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e),a=this.api(r,e,s),n=new S$e.Readable({objectMode:!0,read:()=>{}});return a.once("error",c=>n.emit("error",c)).on("data",c=>n.emit("data",s.transform(c))).once("end",()=>n.emit("end")),n.once("close",()=>a.destroy()),n}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};h8.default=p8});var Wce=_(d8=>{"use strict";Object.defineProperty(d8,"__esModule",{value:!0});var P$e=Zd(),x$e=EQ(),k$e=IQ(),g8=class extends k$e.default{constructor(){super(...arguments),this._walkSync=x$e.walkSync,this._statSync=P$e.statSync}dynamic(e,r){return this._walkSync(e,r)}static(e,r){let s=[];for(let a of e){let n=this._getFullEntryPath(a),c=this._getEntry(n,a,r);c===null||!r.entryFilter(c)||s.push(c)}return s}_getEntry(e,r,s){try{let a=this._getStat(e);return this._makeEntry(a,r)}catch(a){if(s.errorFilter(a))return null;throw a}}_getStat(e){return this._statSync(e,this._fsStatSettings)}};d8.default=g8});var Yce=_(y8=>{"use strict";Object.defineProperty(y8,"__esModule",{value:!0});var Q$e=Wce(),T$e=wQ(),m8=class extends T$e.default{constructor(){super(...arguments),this._reader=new Q$e.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return this.api(r,e,s).map(s.transform)}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};y8.default=m8});var Vce=_(eI=>{"use strict";Object.defineProperty(eI,"__esModule",{value:!0});eI.DEFAULT_FILE_SYSTEM_ADAPTER=void 0;var $E=Ie("fs"),R$e=Ie("os"),F$e=Math.max(R$e.cpus().length,1);eI.DEFAULT_FILE_SYSTEM_ADAPTER={lstat:$E.lstat,lstatSync:$E.lstatSync,stat:$E.stat,statSync:$E.statSync,readdir:$E.readdir,readdirSync:$E.readdirSync};var E8=class{constructor(e={}){this._options=e,this.absolute=this._getValue(this._options.absolute,!1),this.baseNameMatch=this._getValue(this._options.baseNameMatch,!1),this.braceExpansion=this._getValue(this._options.braceExpansion,!0),this.caseSensitiveMatch=this._getValue(this._options.caseSensitiveMatch,!0),this.concurrency=this._getValue(this._options.concurrency,F$e),this.cwd=this._getValue(this._options.cwd,process.cwd()),this.deep=this._getValue(this._options.deep,1/0),this.dot=this._getValue(this._options.dot,!1),this.extglob=this._getValue(this._options.extglob,!0),this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!0),this.fs=this._getFileSystemMethods(this._options.fs),this.globstar=this._getValue(this._options.globstar,!0),this.ignore=this._getValue(this._options.ignore,[]),this.markDirectories=this._getValue(this._options.markDirectories,!1),this.objectMode=this._getValue(this._options.objectMode,!1),this.onlyDirectories=this._getValue(this._options.onlyDirectories,!1),this.onlyFiles=this._getValue(this._options.onlyFiles,!0),this.stats=this._getValue(this._options.stats,!1),this.suppressErrors=this._getValue(this._options.suppressErrors,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!1),this.unique=this._getValue(this._options.unique,!0),this.onlyDirectories&&(this.onlyFiles=!1),this.stats&&(this.objectMode=!0),this.ignore=[].concat(this.ignore)}_getValue(e,r){return e===void 0?r:e}_getFileSystemMethods(e={}){return Object.assign(Object.assign({},eI.DEFAULT_FILE_SYSTEM_ADAPTER),e)}};eI.default=E8});var BQ=_((WLt,Kce)=>{"use strict";var Jce=Kle(),N$e=Gce(),O$e=qce(),L$e=Yce(),I8=Vce(),Qc=xp();async function C8(t,e){ju(t);let r=w8(t,N$e.default,e),s=await Promise.all(r);return Qc.array.flatten(s)}(function(t){t.glob=t,t.globSync=e,t.globStream=r,t.async=t;function e(h,E){ju(h);let C=w8(h,L$e.default,E);return Qc.array.flatten(C)}t.sync=e;function r(h,E){ju(h);let C=w8(h,O$e.default,E);return Qc.stream.merge(C)}t.stream=r;function s(h,E){ju(h);let C=[].concat(h),S=new I8.default(E);return Jce.generate(C,S)}t.generateTasks=s;function a(h,E){ju(h);let C=new I8.default(E);return Qc.pattern.isDynamicPattern(h,C)}t.isDynamicPattern=a;function n(h){return ju(h),Qc.path.escape(h)}t.escapePath=n;function c(h){return ju(h),Qc.path.convertPathToPattern(h)}t.convertPathToPattern=c;let f;(function(h){function E(S){return ju(S),Qc.path.escapePosixPath(S)}h.escapePath=E;function C(S){return ju(S),Qc.path.convertPosixPathToPattern(S)}h.convertPathToPattern=C})(f=t.posix||(t.posix={}));let p;(function(h){function E(S){return ju(S),Qc.path.escapeWindowsPath(S)}h.escapePath=E;function C(S){return ju(S),Qc.path.convertWindowsPathToPattern(S)}h.convertPathToPattern=C})(p=t.win32||(t.win32={}))})(C8||(C8={}));function w8(t,e,r){let s=[].concat(t),a=new I8.default(r),n=Jce.generate(s,a),c=new e(a);return n.map(c.read,c)}function ju(t){if(![].concat(t).every(s=>Qc.string.isString(s)&&!Qc.string.isEmpty(s)))throw new TypeError("Patterns must be a string (non empty) or an array of strings")}Kce.exports=C8});var Nn={};Vt(Nn,{checksumFile:()=>SQ,checksumPattern:()=>DQ,makeHash:()=>us});function us(...t){let e=(0,vQ.createHash)("sha512"),r="";for(let s of t)typeof s=="string"?r+=s:s&&(r&&(e.update(r),r=""),e.update(s));return r&&e.update(r),e.digest("hex")}async function SQ(t,{baseFs:e,algorithm:r}={baseFs:ce,algorithm:"sha512"}){let s=await e.openPromise(t,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,vQ.createHash)(r),f=0;for(;(f=await e.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await e.closePromise(s)}}async function DQ(t,{cwd:e}){let s=(await(0,B8.default)(t,{cwd:fe.fromPortablePath(e),onlyDirectories:!0})).map(f=>`${f}/**/*`),a=await(0,B8.default)([t,...s],{cwd:fe.fromPortablePath(e),onlyFiles:!1});a.sort();let n=await Promise.all(a.map(async f=>{let p=[Buffer.from(f)],h=J.join(e,fe.toPortablePath(f)),E=await ce.lstatPromise(h);return E.isSymbolicLink()?p.push(Buffer.from(await ce.readlinkPromise(h))):E.isFile()&&p.push(await ce.readFilePromise(h)),p.join("\0")})),c=(0,vQ.createHash)("sha512");for(let f of n)c.update(f);return c.digest("hex")}var vQ,B8,I0=Xe(()=>{Dt();vQ=Ie("crypto"),B8=ut(BQ())});var G={};Vt(G,{allPeerRequests:()=>qB,areDescriptorsEqual:()=>eue,areIdentsEqual:()=>UB,areLocatorsEqual:()=>_B,areVirtualPackagesEquivalent:()=>Y$e,bindDescriptor:()=>q$e,bindLocator:()=>W$e,convertDescriptorToLocator:()=>bQ,convertLocatorToDescriptor:()=>S8,convertPackageToLocator:()=>H$e,convertToIdent:()=>_$e,convertToManifestRange:()=>ret,copyPackage:()=>LB,devirtualizeDescriptor:()=>MB,devirtualizeLocator:()=>rI,ensureDevirtualizedDescriptor:()=>j$e,ensureDevirtualizedLocator:()=>G$e,getIdentVendorPath:()=>x8,isPackageCompatible:()=>TQ,isVirtualDescriptor:()=>kp,isVirtualLocator:()=>Gu,makeDescriptor:()=>On,makeIdent:()=>Da,makeLocator:()=>Ws,makeRange:()=>kQ,parseDescriptor:()=>C0,parseFileStyleRange:()=>eet,parseIdent:()=>Sa,parseLocator:()=>Qp,parseRange:()=>em,prettyDependent:()=>t3,prettyDescriptor:()=>ni,prettyIdent:()=>$i,prettyLocator:()=>Yr,prettyLocatorNoColors:()=>e3,prettyRange:()=>iI,prettyReference:()=>jB,prettyResolution:()=>FB,prettyWorkspace:()=>GB,renamePackage:()=>D8,slugifyIdent:()=>v8,slugifyLocator:()=>nI,sortDescriptors:()=>sI,stringifyDescriptor:()=>al,stringifyIdent:()=>un,stringifyLocator:()=>ll,tryParseDescriptor:()=>HB,tryParseIdent:()=>tue,tryParseLocator:()=>xQ,tryParseRange:()=>$$e,unwrapIdentFromScope:()=>iet,virtualizeDescriptor:()=>b8,virtualizePackage:()=>P8,wrapIdentIntoScope:()=>net});function Da(t,e){if(t?.startsWith("@"))throw new Error("Invalid scope: don't prefix it with '@'");return{identHash:us(t,e),scope:t,name:e}}function On(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:us(t.identHash,e),range:e}}function Ws(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:us(t.identHash,e),reference:e}}function _$e(t){return{identHash:t.identHash,scope:t.scope,name:t.name}}function bQ(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.descriptorHash,reference:t.range}}function S8(t){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:t.locatorHash,range:t.reference}}function H$e(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.locatorHash,reference:t.reference}}function D8(t,e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.locatorHash,reference:e.reference,version:t.version,languageName:t.languageName,linkType:t.linkType,conditions:t.conditions,dependencies:new Map(t.dependencies),peerDependencies:new Map(t.peerDependencies),dependenciesMeta:new Map(t.dependenciesMeta),peerDependenciesMeta:new Map(t.peerDependenciesMeta),bin:new Map(t.bin)}}function LB(t){return D8(t,t)}function b8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return On(t,`virtual:${e}#${t.range}`)}function P8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return D8(t,Ws(t,`virtual:${e}#${t.reference}`))}function kp(t){return t.range.startsWith(OB)}function Gu(t){return t.reference.startsWith(OB)}function MB(t){if(!kp(t))throw new Error("Not a virtual descriptor");return On(t,t.range.replace(PQ,""))}function rI(t){if(!Gu(t))throw new Error("Not a virtual descriptor");return Ws(t,t.reference.replace(PQ,""))}function j$e(t){return kp(t)?On(t,t.range.replace(PQ,"")):t}function G$e(t){return Gu(t)?Ws(t,t.reference.replace(PQ,"")):t}function q$e(t,e){return t.range.includes("::")?t:On(t,`${t.range}::${tI.default.stringify(e)}`)}function W$e(t,e){return t.reference.includes("::")?t:Ws(t,`${t.reference}::${tI.default.stringify(e)}`)}function UB(t,e){return t.identHash===e.identHash}function eue(t,e){return t.descriptorHash===e.descriptorHash}function _B(t,e){return t.locatorHash===e.locatorHash}function Y$e(t,e){if(!Gu(t))throw new Error("Invalid package type");if(!Gu(e))throw new Error("Invalid package type");if(!UB(t,e)||t.dependencies.size!==e.dependencies.size)return!1;for(let r of t.dependencies.values()){let s=e.dependencies.get(r.identHash);if(!s||!eue(r,s))return!1}return!0}function Sa(t){let e=tue(t);if(!e)throw new Error(`Invalid ident (${t})`);return e}function tue(t){let e=t.match(V$e);if(!e)return null;let[,r,s]=e;return Da(typeof r<"u"?r:null,s)}function C0(t,e=!1){let r=HB(t,e);if(!r)throw new Error(`Invalid descriptor (${t})`);return r}function HB(t,e=!1){let r=e?t.match(J$e):t.match(K$e);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid range (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return On(Da(c,a),f)}function Qp(t,e=!1){let r=xQ(t,e);if(!r)throw new Error(`Invalid locator (${t})`);return r}function xQ(t,e=!1){let r=e?t.match(z$e):t.match(X$e);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid reference (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return Ws(Da(c,a),f)}function em(t,e){let r=t.match(Z$e);if(r===null)throw new Error(`Invalid range (${t})`);let s=typeof r[1]<"u"?r[1]:null;if(typeof e?.requireProtocol=="string"&&s!==e.requireProtocol)throw new Error(`Invalid protocol (${s})`);if(e?.requireProtocol&&s===null)throw new Error(`Missing protocol (${s})`);let a=typeof r[3]<"u"?decodeURIComponent(r[2]):null;if(e?.requireSource&&a===null)throw new Error(`Missing source (${t})`);let n=typeof r[3]<"u"?decodeURIComponent(r[3]):decodeURIComponent(r[2]),c=e?.parseSelector?tI.default.parse(n):n,f=typeof r[4]<"u"?tI.default.parse(r[4]):null;return{protocol:s,source:a,selector:c,params:f}}function $$e(t,e){try{return em(t,e)}catch{return null}}function eet(t,{protocol:e}){let{selector:r,params:s}=em(t,{requireProtocol:e,requireBindings:!0});if(typeof s.locator!="string")throw new Error(`Assertion failed: Invalid bindings for ${t}`);return{parentLocator:Qp(s.locator,!0),path:r}}function zce(t){return t=t.replaceAll("%","%25"),t=t.replaceAll(":","%3A"),t=t.replaceAll("#","%23"),t}function tet(t){return t===null?!1:Object.entries(t).length>0}function kQ({protocol:t,source:e,selector:r,params:s}){let a="";return t!==null&&(a+=`${t}`),e!==null&&(a+=`${zce(e)}#`),a+=zce(r),tet(s)&&(a+=`::${tI.default.stringify(s)}`),a}function ret(t){let{params:e,protocol:r,source:s,selector:a}=em(t);for(let n in e)n.startsWith("__")&&delete e[n];return kQ({protocol:r,source:s,params:e,selector:a})}function un(t){return t.scope?`@${t.scope}/${t.name}`:`${t.name}`}function net(t,e){return t.scope?Da(e,`${t.scope}__${t.name}`):Da(e,t.name)}function iet(t,e){if(t.scope!==e)return t;let r=t.name.indexOf("__");if(r===-1)return Da(null,t.name);let s=t.name.slice(0,r),a=t.name.slice(r+2);return Da(s,a)}function al(t){return t.scope?`@${t.scope}/${t.name}@${t.range}`:`${t.name}@${t.range}`}function ll(t){return t.scope?`@${t.scope}/${t.name}@${t.reference}`:`${t.name}@${t.reference}`}function v8(t){return t.scope!==null?`@${t.scope}-${t.name}`:t.name}function nI(t){let{protocol:e,selector:r}=em(t.reference),s=e!==null?e.replace(set,""):"exotic",a=Xce.default.valid(r),n=a!==null?`${s}-${a}`:`${s}`,c=10;return t.scope?`${v8(t)}-${n}-${t.locatorHash.slice(0,c)}`:`${v8(t)}-${n}-${t.locatorHash.slice(0,c)}`}function $i(t,e){return e.scope?`${Ht(t,`@${e.scope}/`,ht.SCOPE)}${Ht(t,e.name,ht.NAME)}`:`${Ht(t,e.name,ht.NAME)}`}function QQ(t){if(t.startsWith(OB)){let e=QQ(t.substring(t.indexOf("#")+1)),r=t.substring(OB.length,OB.length+M$e);return`${e} [${r}]`}else return t.replace(oet,"?[...]")}function iI(t,e){return`${Ht(t,QQ(e),ht.RANGE)}`}function ni(t,e){return`${$i(t,e)}${Ht(t,"@",ht.RANGE)}${iI(t,e.range)}`}function jB(t,e){return`${Ht(t,QQ(e),ht.REFERENCE)}`}function Yr(t,e){return`${$i(t,e)}${Ht(t,"@",ht.REFERENCE)}${jB(t,e.reference)}`}function e3(t){return`${un(t)}@${QQ(t.reference)}`}function sI(t){return qs(t,[e=>un(e),e=>e.range])}function GB(t,e){return $i(t,e.anchoredLocator)}function FB(t,e,r){let s=kp(e)?MB(e):e;return r===null?`${ni(t,s)} \u2192 ${$4(t).Cross}`:s.identHash===r.identHash?`${ni(t,s)} \u2192 ${jB(t,r.reference)}`:`${ni(t,s)} \u2192 ${Yr(t,r)}`}function t3(t,e,r){return r===null?`${Yr(t,e)}`:`${Yr(t,e)} (via ${iI(t,r.range)})`}function x8(t){return`node_modules/${un(t)}`}function TQ(t,e){return t.conditions?U$e(t.conditions,r=>{let[,s,a]=r.match($ce),n=e[s];return n?n.includes(a):!0}):!0}function qB(t){let e=new Set;if("children"in t)e.add(t);else for(let r of t.requests.values())e.add(r);for(let r of e)for(let s of r.children.values())e.add(s);return e}var tI,Xce,Zce,OB,M$e,$ce,U$e,PQ,V$e,J$e,K$e,z$e,X$e,Z$e,set,oet,Wo=Xe(()=>{tI=ut(Ie("querystring")),Xce=ut(Ai()),Zce=ut(Ise());xc();I0();Pc();Wo();OB="virtual:",M$e=5,$ce=/(os|cpu|libc)=([a-z0-9_-]+)/,U$e=(0,Zce.makeParser)($ce);PQ=/^[^#]*#/;V$e=/^(?:@([^/]+?)\/)?([^@/]+)$/;J$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,K$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;z$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,X$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;Z$e=/^([^#:]*:)?((?:(?!::)[^#])*)(?:#((?:(?!::).)*))?(?:::(.*))?$/;set=/:$/;oet=/\?.*/});var rue,nue=Xe(()=>{Wo();rue={hooks:{reduceDependency:(t,e,r,s,{resolver:a,resolveOptions:n})=>{for(let{pattern:c,reference:f}of e.topLevelWorkspace.manifest.resolutions){if(c.from&&(c.from.fullName!==un(r)||e.configuration.normalizeLocator(Ws(Sa(c.from.fullName),c.from.description??r.reference)).locatorHash!==r.locatorHash)||c.descriptor.fullName!==un(t)||e.configuration.normalizeDependency(On(Qp(c.descriptor.fullName),c.descriptor.description??t.range)).descriptorHash!==t.descriptorHash)continue;return a.bindDescriptor(e.configuration.normalizeDependency(On(t,f)),e.topLevelWorkspace.anchoredLocator,n)}return t},validateProject:async(t,e)=>{for(let r of t.workspaces){let s=GB(t.configuration,r);await t.configuration.triggerHook(a=>a.validateWorkspace,r,{reportWarning:(a,n)=>e.reportWarning(a,`${s}: ${n}`),reportError:(a,n)=>e.reportError(a,`${s}: ${n}`)})}},validateWorkspace:async(t,e)=>{let{manifest:r}=t;r.resolutions.length&&t.cwd!==t.project.cwd&&r.errors.push(new Error("Resolutions field will be ignored"));for(let s of r.errors)e.reportWarning(57,s.message)}}}});var Ei,tm=Xe(()=>{Ei=class t{static{this.protocol="workspace:"}supportsDescriptor(e,r){return!!(e.range.startsWith(t.protocol)||r.project.tryWorkspaceByDescriptor(e)!==null)}supportsLocator(e,r){return!!e.reference.startsWith(t.protocol)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[s.project.getWorkspaceByDescriptor(e).anchoredLocator]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.getWorkspaceByCwd(e.reference.slice(t.protocol.length));return{...e,version:s.manifest.version||"0.0.0",languageName:"unknown",linkType:"SOFT",conditions:null,dependencies:r.project.configuration.normalizeDependencyMap(new Map([...s.manifest.dependencies,...s.manifest.devDependencies])),peerDependencies:new Map([...s.manifest.peerDependencies]),dependenciesMeta:s.manifest.dependenciesMeta,peerDependenciesMeta:s.manifest.peerDependenciesMeta,bin:s.manifest.bin}}}});var Fr={};Vt(Fr,{SemVer:()=>lue.SemVer,clean:()=>cet,getComparator:()=>oue,mergeComparators:()=>k8,satisfiesWithPrereleases:()=>Zf,simplifyRanges:()=>Q8,stringifyComparator:()=>aue,validRange:()=>cl});function Zf(t,e,r=!1){if(!t)return!1;let s=`${e}${r}`,a=iue.get(s);if(typeof a>"u")try{a=new Tp.default.Range(e,{includePrerelease:!0,loose:r})}catch{return!1}finally{iue.set(s,a||null)}else if(a===null)return!1;let n;try{n=new Tp.default.SemVer(t,a)}catch{return!1}return a.test(n)?!0:(n.prerelease&&(n.prerelease=[]),a.set.some(c=>{for(let f of c)f.semver.prerelease&&(f.semver.prerelease=[]);return c.every(f=>f.test(n))}))}function cl(t){if(t.indexOf(":")!==-1)return null;let e=sue.get(t);if(typeof e<"u")return e;try{e=new Tp.default.Range(t)}catch{e=null}return sue.set(t,e),e}function cet(t){let e=aet.exec(t);return e?e[1]:null}function oue(t){if(t.semver===Tp.default.Comparator.ANY)return{gt:null,lt:null};switch(t.operator){case"":return{gt:[">=",t.semver],lt:["<=",t.semver]};case">":case">=":return{gt:[t.operator,t.semver],lt:null};case"<":case"<=":return{gt:null,lt:[t.operator,t.semver]};default:throw new Error(`Assertion failed: Unexpected comparator operator (${t.operator})`)}}function k8(t){if(t.length===0)return null;let e=null,r=null;for(let s of t){if(s.gt){let a=e!==null?Tp.default.compare(s.gt[1],e[1]):null;(a===null||a>0||a===0&&s.gt[0]===">")&&(e=s.gt)}if(s.lt){let a=r!==null?Tp.default.compare(s.lt[1],r[1]):null;(a===null||a<0||a===0&&s.lt[0]==="<")&&(r=s.lt)}}if(e&&r){let s=Tp.default.compare(e[1],r[1]);if(s===0&&(e[0]===">"||r[0]==="<")||s>0)return null}return{gt:e,lt:r}}function aue(t){if(t.gt&&t.lt){if(t.gt[0]===">="&&t.lt[0]==="<="&&t.gt[1].version===t.lt[1].version)return t.gt[1].version;if(t.gt[0]===">="&&t.lt[0]==="<"){if(t.lt[1].version===`${t.gt[1].major+1}.0.0-0`)return`^${t.gt[1].version}`;if(t.lt[1].version===`${t.gt[1].major}.${t.gt[1].minor+1}.0-0`)return`~${t.gt[1].version}`}}let e=[];return t.gt&&e.push(t.gt[0]+t.gt[1].version),t.lt&&e.push(t.lt[0]+t.lt[1].version),e.length?e.join(" "):"*"}function Q8(t){let e=t.map(uet).map(s=>cl(s).set.map(a=>a.map(n=>oue(n)))),r=e.shift().map(s=>k8(s)).filter(s=>s!==null);for(let s of e){let a=[];for(let n of r)for(let c of s){let f=k8([n,...c]);f!==null&&a.push(f)}r=a}return r.length===0?null:r.map(s=>aue(s)).join(" || ")}function uet(t){let e=t.split("||");if(e.length>1){let r=new Set;for(let s of e)e.some(a=>a!==s&&Tp.default.subset(s,a))||r.add(s);if(r.size{Tp=ut(Ai()),lue=ut(Ai()),iue=new Map;sue=new Map;aet=/^(?:[\sv=]*?)((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\s*)$/});function cue(t){let e=t.match(/^[ \t]+/m);return e?e[0]:" "}function uue(t){return t.charCodeAt(0)===65279?t.slice(1):t}function ba(t){return t.replace(/\\/g,"/")}function RQ(t,{yamlCompatibilityMode:e}){return e?Y4(t):typeof t>"u"||typeof t=="boolean"?t:null}function fue(t,e){let r=e.search(/[^!]/);if(r===-1)return"invalid";let s=r%2===0?"":"!",a=e.slice(r);return`${s}${t}=${a}`}function T8(t,e){return e.length===1?fue(t,e[0]):`(${e.map(r=>fue(t,r)).join(" | ")})`}var Aue,Ut,oI=Xe(()=>{Dt();wc();Aue=ut(Ai());tm();Pc();Rp();Wo();Ut=class t{constructor(){this.indent=" ";this.name=null;this.version=null;this.os=null;this.cpu=null;this.libc=null;this.type=null;this.packageManager=null;this.private=!1;this.license=null;this.main=null;this.module=null;this.browser=null;this.languageName=null;this.bin=new Map;this.scripts=new Map;this.dependencies=new Map;this.devDependencies=new Map;this.peerDependencies=new Map;this.workspaceDefinitions=[];this.dependenciesMeta=new Map;this.peerDependenciesMeta=new Map;this.resolutions=[];this.files=null;this.publishConfig=null;this.installConfig=null;this.preferUnplugged=null;this.raw={};this.errors=[]}static{this.fileName="package.json"}static{this.allDependencies=["dependencies","devDependencies","peerDependencies"]}static{this.hardDependencies=["dependencies","devDependencies"]}static async tryFind(e,{baseFs:r=new Yn}={}){let s=J.join(e,"package.json");try{return await t.fromFile(s,{baseFs:r})}catch(a){if(a.code==="ENOENT")return null;throw a}}static async find(e,{baseFs:r}={}){let s=await t.tryFind(e,{baseFs:r});if(s===null)throw new Error("Manifest not found");return s}static async fromFile(e,{baseFs:r=new Yn}={}){let s=new t;return await s.loadFile(e,{baseFs:r}),s}static fromText(e){let r=new t;return r.loadFromText(e),r}loadFromText(e){let r;try{r=JSON.parse(uue(e)||"{}")}catch(s){throw s.message+=` (when parsing ${e})`,s}this.load(r),this.indent=cue(e)}async loadFile(e,{baseFs:r=new Yn}){let s=await r.readFilePromise(e,"utf8"),a;try{a=JSON.parse(uue(s)||"{}")}catch(n){throw n.message+=` (when parsing ${e})`,n}this.load(a),this.indent=cue(s)}load(e,{yamlCompatibilityMode:r=!1}={}){if(typeof e!="object"||e===null)throw new Error(`Utterly invalid manifest data (${e})`);this.raw=e;let s=[];if(this.name=null,typeof e.name=="string")try{this.name=Sa(e.name)}catch{s.push(new Error("Parsing failed for the 'name' field"))}if(typeof e.version=="string"?this.version=e.version:this.version=null,Array.isArray(e.os)){let n=[];this.os=n;for(let c of e.os)typeof c!="string"?s.push(new Error("Parsing failed for the 'os' field")):n.push(c)}else this.os=null;if(Array.isArray(e.cpu)){let n=[];this.cpu=n;for(let c of e.cpu)typeof c!="string"?s.push(new Error("Parsing failed for the 'cpu' field")):n.push(c)}else this.cpu=null;if(Array.isArray(e.libc)){let n=[];this.libc=n;for(let c of e.libc)typeof c!="string"?s.push(new Error("Parsing failed for the 'libc' field")):n.push(c)}else this.libc=null;if(typeof e.type=="string"?this.type=e.type:this.type=null,typeof e.packageManager=="string"?this.packageManager=e.packageManager:this.packageManager=null,typeof e.private=="boolean"?this.private=e.private:this.private=!1,typeof e.license=="string"?this.license=e.license:this.license=null,typeof e.languageName=="string"?this.languageName=e.languageName:this.languageName=null,typeof e.main=="string"?this.main=ba(e.main):this.main=null,typeof e.module=="string"?this.module=ba(e.module):this.module=null,e.browser!=null)if(typeof e.browser=="string")this.browser=ba(e.browser);else{this.browser=new Map;for(let[n,c]of Object.entries(e.browser))this.browser.set(ba(n),typeof c=="string"?ba(c):c)}else this.browser=null;if(this.bin=new Map,typeof e.bin=="string")e.bin.trim()===""?s.push(new Error("Invalid bin field")):this.name!==null?this.bin.set(this.name.name,ba(e.bin)):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.bin=="object"&&e.bin!==null)for(let[n,c]of Object.entries(e.bin)){if(typeof c!="string"||c.trim()===""){s.push(new Error(`Invalid bin definition for '${n}'`));continue}let f=Sa(n);this.bin.set(f.name,ba(c))}if(this.scripts=new Map,typeof e.scripts=="object"&&e.scripts!==null)for(let[n,c]of Object.entries(e.scripts)){if(typeof c!="string"){s.push(new Error(`Invalid script definition for '${n}'`));continue}this.scripts.set(n,c)}if(this.dependencies=new Map,typeof e.dependencies=="object"&&e.dependencies!==null)for(let[n,c]of Object.entries(e.dependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p)}if(this.devDependencies=new Map,typeof e.devDependencies=="object"&&e.devDependencies!==null)for(let[n,c]of Object.entries(e.devDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.devDependencies.set(p.identHash,p)}if(this.peerDependencies=new Map,typeof e.peerDependencies=="object"&&e.peerDependencies!==null)for(let[n,c]of Object.entries(e.peerDependencies)){let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}(typeof c!="string"||!c.startsWith(Ei.protocol)&&!cl(c))&&(s.push(new Error(`Invalid dependency range for '${n}'`)),c="*");let p=On(f,c);this.peerDependencies.set(p.identHash,p)}typeof e.workspaces=="object"&&e.workspaces!==null&&e.workspaces.nohoist&&s.push(new Error("'nohoist' is deprecated, please use 'installConfig.hoistingLimits' instead"));let a=Array.isArray(e.workspaces)?e.workspaces:typeof e.workspaces=="object"&&e.workspaces!==null&&Array.isArray(e.workspaces.packages)?e.workspaces.packages:[];this.workspaceDefinitions=[];for(let n of a){if(typeof n!="string"){s.push(new Error(`Invalid workspace definition for '${n}'`));continue}this.workspaceDefinitions.push({pattern:n})}if(this.dependenciesMeta=new Map,typeof e.dependenciesMeta=="object"&&e.dependenciesMeta!==null)for(let[n,c]of Object.entries(e.dependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}`));continue}let f=C0(n),p=this.ensureDependencyMeta(f),h=RQ(c.built,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid built meta field for '${n}'`));continue}let E=RQ(c.optional,{yamlCompatibilityMode:r});if(E===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}let C=RQ(c.unplugged,{yamlCompatibilityMode:r});if(C===null){s.push(new Error(`Invalid unplugged meta field for '${n}'`));continue}Object.assign(p,{built:h,optional:E,unplugged:C})}if(this.peerDependenciesMeta=new Map,typeof e.peerDependenciesMeta=="object"&&e.peerDependenciesMeta!==null)for(let[n,c]of Object.entries(e.peerDependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}'`));continue}let f=C0(n),p=this.ensurePeerDependencyMeta(f),h=RQ(c.optional,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}Object.assign(p,{optional:h})}if(this.resolutions=[],typeof e.resolutions=="object"&&e.resolutions!==null)for(let[n,c]of Object.entries(e.resolutions)){if(typeof c!="string"){s.push(new Error(`Invalid resolution entry for '${n}'`));continue}try{this.resolutions.push({pattern:px(n),reference:c})}catch(f){s.push(f);continue}}if(Array.isArray(e.files)){this.files=new Set;for(let n of e.files){if(typeof n!="string"){s.push(new Error(`Invalid files entry for '${n}'`));continue}this.files.add(n)}}else this.files=null;if(typeof e.publishConfig=="object"&&e.publishConfig!==null){if(this.publishConfig={},typeof e.publishConfig.access=="string"&&(this.publishConfig.access=e.publishConfig.access),typeof e.publishConfig.main=="string"&&(this.publishConfig.main=ba(e.publishConfig.main)),typeof e.publishConfig.module=="string"&&(this.publishConfig.module=ba(e.publishConfig.module)),e.publishConfig.browser!=null)if(typeof e.publishConfig.browser=="string")this.publishConfig.browser=ba(e.publishConfig.browser);else{this.publishConfig.browser=new Map;for(let[n,c]of Object.entries(e.publishConfig.browser))this.publishConfig.browser.set(ba(n),typeof c=="string"?ba(c):c)}if(typeof e.publishConfig.registry=="string"&&(this.publishConfig.registry=e.publishConfig.registry),typeof e.publishConfig.provenance=="boolean"&&(this.publishConfig.provenance=e.publishConfig.provenance),typeof e.publishConfig.bin=="string")this.name!==null?this.publishConfig.bin=new Map([[this.name.name,ba(e.publishConfig.bin)]]):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.publishConfig.bin=="object"&&e.publishConfig.bin!==null){this.publishConfig.bin=new Map;for(let[n,c]of Object.entries(e.publishConfig.bin)){if(typeof c!="string"){s.push(new Error(`Invalid bin definition for '${n}'`));continue}this.publishConfig.bin.set(n,ba(c))}}if(Array.isArray(e.publishConfig.executableFiles)){this.publishConfig.executableFiles=new Set;for(let n of e.publishConfig.executableFiles){if(typeof n!="string"){s.push(new Error("Invalid executable file definition"));continue}this.publishConfig.executableFiles.add(ba(n))}}}else this.publishConfig=null;if(typeof e.installConfig=="object"&&e.installConfig!==null){this.installConfig={};for(let n of Object.keys(e.installConfig))n==="hoistingLimits"?typeof e.installConfig.hoistingLimits=="string"?this.installConfig.hoistingLimits=e.installConfig.hoistingLimits:s.push(new Error("Invalid hoisting limits definition")):n=="selfReferences"?typeof e.installConfig.selfReferences=="boolean"?this.installConfig.selfReferences=e.installConfig.selfReferences:s.push(new Error("Invalid selfReferences definition, must be a boolean value")):s.push(new Error(`Unrecognized installConfig key: ${n}`))}else this.installConfig=null;if(typeof e.optionalDependencies=="object"&&e.optionalDependencies!==null)for(let[n,c]of Object.entries(e.optionalDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p);let h=On(f,"unknown"),E=this.ensureDependencyMeta(h);Object.assign(E,{optional:!0})}typeof e.preferUnplugged=="boolean"?this.preferUnplugged=e.preferUnplugged:this.preferUnplugged=null,this.errors=s}getForScope(e){switch(e){case"dependencies":return this.dependencies;case"devDependencies":return this.devDependencies;case"peerDependencies":return this.peerDependencies;default:throw new Error(`Unsupported value ("${e}")`)}}hasConsumerDependency(e){return!!(this.dependencies.has(e.identHash)||this.peerDependencies.has(e.identHash))}hasHardDependency(e){return!!(this.dependencies.has(e.identHash)||this.devDependencies.has(e.identHash))}hasSoftDependency(e){return!!this.peerDependencies.has(e.identHash)}hasDependency(e){return!!(this.hasHardDependency(e)||this.hasSoftDependency(e))}getConditions(){let e=[];return this.os&&this.os.length>0&&e.push(T8("os",this.os)),this.cpu&&this.cpu.length>0&&e.push(T8("cpu",this.cpu)),this.libc&&this.libc.length>0&&e.push(T8("libc",this.libc)),e.length>0?e.join(" & "):null}ensureDependencyMeta(e){if(e.range!=="unknown"&&!Aue.default.valid(e.range))throw new Error(`Invalid meta field range for '${al(e)}'`);let r=un(e),s=e.range!=="unknown"?e.range:null,a=this.dependenciesMeta.get(r);a||this.dependenciesMeta.set(r,a=new Map);let n=a.get(s);return n||a.set(s,n={}),n}ensurePeerDependencyMeta(e){if(e.range!=="unknown")throw new Error(`Invalid meta field range for '${al(e)}'`);let r=un(e),s=this.peerDependenciesMeta.get(r);return s||this.peerDependenciesMeta.set(r,s={}),s}setRawField(e,r,{after:s=[]}={}){let a=new Set(s.filter(n=>Object.hasOwn(this.raw,n)));if(a.size===0||Object.hasOwn(this.raw,e))this.raw[e]=r;else{let n=this.raw,c=this.raw={},f=!1;for(let p of Object.keys(n))c[p]=n[p],f||(a.delete(p),a.size===0&&(c[e]=r,f=!0))}}exportTo(e,{compatibilityMode:r=!0}={}){if(Object.assign(e,this.raw),this.name!==null?e.name=un(this.name):delete e.name,this.version!==null?e.version=this.version:delete e.version,this.os!==null?e.os=this.os:delete e.os,this.cpu!==null?e.cpu=this.cpu:delete e.cpu,this.type!==null?e.type=this.type:delete e.type,this.packageManager!==null?e.packageManager=this.packageManager:delete e.packageManager,this.private?e.private=!0:delete e.private,this.license!==null?e.license=this.license:delete e.license,this.languageName!==null?e.languageName=this.languageName:delete e.languageName,this.main!==null?e.main=this.main:delete e.main,this.module!==null?e.module=this.module:delete e.module,this.browser!==null){let n=this.browser;typeof n=="string"?e.browser=n:n instanceof Map&&(e.browser=Object.assign({},...Array.from(n.keys()).sort().map(c=>({[c]:n.get(c)}))))}else delete e.browser;this.bin.size===1&&this.name!==null&&this.bin.has(this.name.name)?e.bin=this.bin.get(this.name.name):this.bin.size>0?e.bin=Object.assign({},...Array.from(this.bin.keys()).sort().map(n=>({[n]:this.bin.get(n)}))):delete e.bin,this.workspaceDefinitions.length>0?this.raw.workspaces&&!Array.isArray(this.raw.workspaces)?e.workspaces={...this.raw.workspaces,packages:this.workspaceDefinitions.map(({pattern:n})=>n)}:e.workspaces=this.workspaceDefinitions.map(({pattern:n})=>n):this.raw.workspaces&&!Array.isArray(this.raw.workspaces)&&Object.keys(this.raw.workspaces).length>0?e.workspaces=this.raw.workspaces:delete e.workspaces;let s=[],a=[];for(let n of this.dependencies.values()){let c=this.dependenciesMeta.get(un(n)),f=!1;if(r&&c){let p=c.get(null);p&&p.optional&&(f=!0)}f?a.push(n):s.push(n)}s.length>0?e.dependencies=Object.assign({},...sI(s).map(n=>({[un(n)]:n.range}))):delete e.dependencies,a.length>0?e.optionalDependencies=Object.assign({},...sI(a).map(n=>({[un(n)]:n.range}))):delete e.optionalDependencies,this.devDependencies.size>0?e.devDependencies=Object.assign({},...sI(this.devDependencies.values()).map(n=>({[un(n)]:n.range}))):delete e.devDependencies,this.peerDependencies.size>0?e.peerDependencies=Object.assign({},...sI(this.peerDependencies.values()).map(n=>({[un(n)]:n.range}))):delete e.peerDependencies,e.dependenciesMeta={};for(let[n,c]of qs(this.dependenciesMeta.entries(),([f,p])=>f))for(let[f,p]of qs(c.entries(),([h,E])=>h!==null?`0${h}`:"1")){let h=f!==null?al(On(Sa(n),f)):n,E={...p};r&&f===null&&delete E.optional,Object.keys(E).length!==0&&(e.dependenciesMeta[h]=E)}if(Object.keys(e.dependenciesMeta).length===0&&delete e.dependenciesMeta,this.peerDependenciesMeta.size>0?e.peerDependenciesMeta=Object.assign({},...qs(this.peerDependenciesMeta.entries(),([n,c])=>n).map(([n,c])=>({[n]:c}))):delete e.peerDependenciesMeta,this.resolutions.length>0?e.resolutions=Object.assign({},...this.resolutions.map(({pattern:n,reference:c})=>({[hx(n)]:c}))):delete e.resolutions,this.files!==null?e.files=Array.from(this.files):delete e.files,this.preferUnplugged!==null?e.preferUnplugged=this.preferUnplugged:delete e.preferUnplugged,this.scripts!==null&&this.scripts.size>0){e.scripts??={};for(let n of Object.keys(e.scripts))this.scripts.has(n)||delete e.scripts[n];for(let[n,c]of this.scripts.entries())e.scripts[n]=c}else delete e.scripts;return e}}});function Aet(t){return typeof t.reportCode<"u"}var pue,hue,fet,jt,Ao,Tc=Xe(()=>{ql();pue=Ie("stream"),hue=Ie("string_decoder"),fet=15,jt=class extends Error{constructor(r,s,a){super(s);this.reportExtra=a;this.reportCode=r}};Ao=class{constructor(){this.cacheHits=new Set;this.cacheMisses=new Set;this.reportedInfos=new Set;this.reportedWarnings=new Set;this.reportedErrors=new Set}getRecommendedLength(){return 180}reportCacheHit(e){this.cacheHits.add(e.locatorHash)}reportCacheMiss(e,r){this.cacheMisses.add(e.locatorHash)}static progressViaCounter(e){let r=0,s,a=new Promise(p=>{s=p}),n=p=>{let h=s;a=new Promise(E=>{s=E}),r=p,h()},c=(p=0)=>{n(r+1)},f=async function*(){for(;r{r=c}),a=Q4(c=>{let f=r;s=new Promise(p=>{r=p}),e=c,f()},1e3/fet),n=async function*(){for(;;)await s,yield{title:e}}();return{[Symbol.asyncIterator](){return n},hasProgress:!1,hasTitle:!0,setTitle:a}}async startProgressPromise(e,r){let s=this.reportProgress(e);try{return await r(e)}finally{s.stop()}}startProgressSync(e,r){let s=this.reportProgress(e);try{return r(e)}finally{s.stop()}}reportInfoOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedInfos.has(a)||(this.reportedInfos.add(a),this.reportInfo(e,r),s?.reportExtra?.(this))}reportWarningOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedWarnings.has(a)||(this.reportedWarnings.add(a),this.reportWarning(e,r),s?.reportExtra?.(this))}reportErrorOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedErrors.has(a)||(this.reportedErrors.add(a),this.reportError(e,r),s?.reportExtra?.(this))}reportExceptionOnce(e){Aet(e)?this.reportErrorOnce(e.reportCode,e.message,{key:e,reportExtra:e.reportExtra}):this.reportErrorOnce(1,e.stack||e.message,{key:e})}createStreamReporter(e=null){let r=new pue.PassThrough,s=new hue.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` +`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",e!==null?this.reportInfo(null,`${e} ${p}`):this.reportInfo(null,p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&(e!==null?this.reportInfo(null,`${e} ${n}`):this.reportInfo(null,n))}),r}}});var aI,R8=Xe(()=>{Tc();Wo();aI=class{constructor(e){this.fetchers=e}supports(e,r){return!!this.tryFetcher(e,r)}getLocalPath(e,r){return this.getFetcher(e,r).getLocalPath(e,r)}async fetch(e,r){return await this.getFetcher(e,r).fetch(e,r)}tryFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));return s||null}getFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));if(!s)throw new jt(11,`${Yr(r.project.configuration,e)} isn't supported by any available fetcher`);return s}}});var rm,F8=Xe(()=>{Wo();rm=class{constructor(e){this.resolvers=e.filter(r=>r)}supportsDescriptor(e,r){return!!this.tryResolverByDescriptor(e,r)}supportsLocator(e,r){return!!this.tryResolverByLocator(e,r)}shouldPersistResolution(e,r){return this.getResolverByLocator(e,r).shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.getResolverByDescriptor(e,s).bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.getResolverByDescriptor(e,r).getResolutionDependencies(e,r)}async getCandidates(e,r,s){return await this.getResolverByDescriptor(e,s).getCandidates(e,r,s)}async getSatisfying(e,r,s,a){return this.getResolverByDescriptor(e,a).getSatisfying(e,r,s,a)}async resolve(e,r){return await this.getResolverByLocator(e,r).resolve(e,r)}tryResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));return s||null}getResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));if(!s)throw new Error(`${ni(r.project.configuration,e)} isn't supported by any available resolver`);return s}tryResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));return s||null}getResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));if(!s)throw new Error(`${Yr(r.project.configuration,e)} isn't supported by any available resolver`);return s}}});var lI,N8=Xe(()=>{Dt();Wo();lI=class{supports(e){return!!e.reference.startsWith("virtual:")}getLocalPath(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ws(e,a);return r.fetcher.getLocalPath(n,r)}async fetch(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ws(e,a),c=await r.fetcher.fetch(n,r);return await this.ensureVirtualLink(e,c,r)}getLocatorFilename(e){return nI(e)}async ensureVirtualLink(e,r,s){let a=r.packageFs.getRealPath(),n=s.project.configuration.get("virtualFolder"),c=this.getLocatorFilename(e),f=uo.makeVirtualPath(n,c,a),p=new _f(f,{baseFs:r.packageFs,pathUtils:J});return{...r,packageFs:p}}}});var FQ,gue=Xe(()=>{FQ=class t{static{this.protocol="virtual:"}static isVirtualDescriptor(e){return!!e.range.startsWith(t.protocol)}static isVirtualLocator(e){return!!e.reference.startsWith(t.protocol)}supportsDescriptor(e,r){return t.isVirtualDescriptor(e)}supportsLocator(e,r){return t.isVirtualLocator(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){throw new Error('Assertion failed: calling "bindDescriptor" on a virtual descriptor is unsupported')}getResolutionDependencies(e,r){throw new Error('Assertion failed: calling "getResolutionDependencies" on a virtual descriptor is unsupported')}async getCandidates(e,r,s){throw new Error('Assertion failed: calling "getCandidates" on a virtual descriptor is unsupported')}async getSatisfying(e,r,s,a){throw new Error('Assertion failed: calling "getSatisfying" on a virtual descriptor is unsupported')}async resolve(e,r){throw new Error('Assertion failed: calling "resolve" on a virtual locator is unsupported')}}});var cI,O8=Xe(()=>{Dt();tm();cI=class{supports(e){return!!e.reference.startsWith(Ei.protocol)}getLocalPath(e,r){return this.getWorkspace(e,r).cwd}async fetch(e,r){let s=this.getWorkspace(e,r).cwd;return{packageFs:new Sn(s),prefixPath:vt.dot,localPath:s}}getWorkspace(e,r){return r.project.getWorkspaceByCwd(e.reference.slice(Ei.protocol.length))}}});function WB(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function due(t){return typeof t>"u"?3:WB(t)?0:Array.isArray(t)?1:2}function U8(t,e){return Object.hasOwn(t,e)}function het(t){return WB(t)&&U8(t,"onConflict")&&typeof t.onConflict=="string"}function get(t){if(typeof t>"u")return{onConflict:"default",value:t};if(!het(t))return{onConflict:"default",value:t};if(U8(t,"value"))return t;let{onConflict:e,...r}=t;return{onConflict:e,value:r}}function mue(t,e){let r=WB(t)&&U8(t,e)?t[e]:void 0;return get(r)}function uI(t,e){return[t,e,yue]}function _8(t){return Array.isArray(t)?t[2]===yue:!1}function L8(t,e){if(WB(t)){let r={};for(let s of Object.keys(t))r[s]=L8(t[s],e);return uI(e,r)}return Array.isArray(t)?uI(e,t.map(r=>L8(r,e))):uI(e,t)}function M8(t,e,r,s,a){let n,c=[],f=a,p=0;for(let E=a-1;E>=s;--E){let[C,S]=t[E],{onConflict:P,value:I}=mue(S,r),R=due(I);if(R!==3){if(n??=R,R!==n||P==="hardReset"){p=f;break}if(R===2)return uI(C,I);if(c.unshift([C,I]),P==="reset"){p=E;break}P==="extend"&&E===s&&(s=0),f=E}}if(typeof n>"u")return null;let h=c.map(([E])=>E).join(", ");switch(n){case 1:return uI(h,new Array().concat(...c.map(([E,C])=>C.map(S=>L8(S,E)))));case 0:{let E=Object.assign({},...c.map(([,R])=>R)),C=Object.keys(E),S={},P=t.map(([R,N])=>[R,mue(N,r).value]),I=pet(P,([R,N])=>{let U=due(N);return U!==0&&U!==3});if(I!==-1){let R=P.slice(I+1);for(let N of C)S[N]=M8(R,e,N,0,R.length)}else for(let R of C)S[R]=M8(P,e,R,p,P.length);return uI(h,S)}default:throw new Error("Assertion failed: Non-extendable value type")}}function Eue(t){return M8(t.map(([e,r])=>[e,{".":r}]),[],".",0,t.length)}function YB(t){return _8(t)?t[1]:t}function NQ(t){let e=_8(t)?t[1]:t;if(Array.isArray(e))return e.map(r=>NQ(r));if(WB(e)){let r={};for(let[s,a]of Object.entries(e))r[s]=NQ(a);return r}return e}function H8(t){return _8(t)?t[0]:null}var pet,yue,Iue=Xe(()=>{pet=(t,e,r)=>{let s=[...t];return s.reverse(),s.findIndex(e,r)};yue=Symbol()});var OQ={};Vt(OQ,{getDefaultGlobalFolder:()=>G8,getHomeFolder:()=>fI,isFolderInside:()=>q8});function G8(){if(process.platform==="win32"){let t=fe.toPortablePath(process.env.LOCALAPPDATA||fe.join((0,j8.homedir)(),"AppData","Local"));return J.resolve(t,"Yarn/Berry")}if(process.env.XDG_DATA_HOME){let t=fe.toPortablePath(process.env.XDG_DATA_HOME);return J.resolve(t,"yarn/berry")}return J.resolve(fI(),".yarn/berry")}function fI(){return fe.toPortablePath((0,j8.homedir)()||"/usr/local/share")}function q8(t,e){let r=J.relative(e,t);return r&&!r.startsWith("..")&&!J.isAbsolute(r)}var j8,LQ=Xe(()=>{Dt();j8=Ie("os")});var Bue=_((EMt,wue)=>{"use strict";var W8=Ie("https"),Y8=Ie("http"),{URL:Cue}=Ie("url"),V8=class extends Y8.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r=="string"?new Cue(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:"CONNECT",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?"keep-alive":"close",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||"")}:${decodeURIComponent(this.proxy.password||"")}`).toString("base64");s.headers["proxy-authorization"]=`Basic ${n}`}this.proxy.protocol==="https:"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol==="http:"?Y8:W8).request(s);a.once("connect",(n,c,f)=>{a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200?r(null,c):(c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null))}),a.once("timeout",()=>{a.destroy(new Error("Proxy timeout"))}),a.once("error",n=>{a.removeAllListeners(),r(n,null)}),a.end()}},J8=class extends W8.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r=="string"?new Cue(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:"CONNECT",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?"keep-alive":"close",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||"")}:${decodeURIComponent(this.proxy.password||"")}`).toString("base64");s.headers["proxy-authorization"]=`Basic ${n}`}this.proxy.protocol==="https:"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol==="http:"?Y8:W8).request(s);a.once("connect",(n,c,f)=>{if(a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200){let p=super.createConnection({...e,socket:c});r(null,p)}else c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null)}),a.once("timeout",()=>{a.destroy(new Error("Proxy timeout"))}),a.once("error",n=>{a.removeAllListeners(),r(n,null)}),a.end()}};wue.exports={HttpProxyAgent:V8,HttpsProxyAgent:J8}});var K8,vue,Sue,Due=Xe(()=>{K8=ut(Bue(),1),vue=K8.default.HttpProxyAgent,Sue=K8.default.HttpsProxyAgent});var Np=_((Fp,MQ)=>{"use strict";Object.defineProperty(Fp,"__esModule",{value:!0});var bue=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array"];function met(t){return bue.includes(t)}var yet=["Function","Generator","AsyncGenerator","GeneratorFunction","AsyncGeneratorFunction","AsyncFunction","Observable","Array","Buffer","Blob","Object","RegExp","Date","Error","Map","Set","WeakMap","WeakSet","ArrayBuffer","SharedArrayBuffer","DataView","Promise","URL","FormData","URLSearchParams","HTMLElement",...bue];function Eet(t){return yet.includes(t)}var Iet=["null","undefined","string","number","bigint","boolean","symbol"];function Cet(t){return Iet.includes(t)}function AI(t){return e=>typeof e===t}var{toString:Pue}=Object.prototype,VB=t=>{let e=Pue.call(t).slice(8,-1);if(/HTML\w+Element/.test(e)&&be.domElement(t))return"HTMLElement";if(Eet(e))return e},pi=t=>e=>VB(e)===t;function be(t){if(t===null)return"null";switch(typeof t){case"undefined":return"undefined";case"string":return"string";case"number":return"number";case"boolean":return"boolean";case"function":return"Function";case"bigint":return"bigint";case"symbol":return"symbol";default:}if(be.observable(t))return"Observable";if(be.array(t))return"Array";if(be.buffer(t))return"Buffer";let e=VB(t);if(e)return e;if(t instanceof String||t instanceof Boolean||t instanceof Number)throw new TypeError("Please don't use object wrappers for primitive types");return"Object"}be.undefined=AI("undefined");be.string=AI("string");var wet=AI("number");be.number=t=>wet(t)&&!be.nan(t);be.bigint=AI("bigint");be.function_=AI("function");be.null_=t=>t===null;be.class_=t=>be.function_(t)&&t.toString().startsWith("class ");be.boolean=t=>t===!0||t===!1;be.symbol=AI("symbol");be.numericString=t=>be.string(t)&&!be.emptyStringOrWhitespace(t)&&!Number.isNaN(Number(t));be.array=(t,e)=>Array.isArray(t)?be.function_(e)?t.every(e):!0:!1;be.buffer=t=>{var e,r,s,a;return(a=(s=(r=(e=t)===null||e===void 0?void 0:e.constructor)===null||r===void 0?void 0:r.isBuffer)===null||s===void 0?void 0:s.call(r,t))!==null&&a!==void 0?a:!1};be.blob=t=>pi("Blob")(t);be.nullOrUndefined=t=>be.null_(t)||be.undefined(t);be.object=t=>!be.null_(t)&&(typeof t=="object"||be.function_(t));be.iterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.iterator])};be.asyncIterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.asyncIterator])};be.generator=t=>{var e,r;return be.iterable(t)&&be.function_((e=t)===null||e===void 0?void 0:e.next)&&be.function_((r=t)===null||r===void 0?void 0:r.throw)};be.asyncGenerator=t=>be.asyncIterable(t)&&be.function_(t.next)&&be.function_(t.throw);be.nativePromise=t=>pi("Promise")(t);var Bet=t=>{var e,r;return be.function_((e=t)===null||e===void 0?void 0:e.then)&&be.function_((r=t)===null||r===void 0?void 0:r.catch)};be.promise=t=>be.nativePromise(t)||Bet(t);be.generatorFunction=pi("GeneratorFunction");be.asyncGeneratorFunction=t=>VB(t)==="AsyncGeneratorFunction";be.asyncFunction=t=>VB(t)==="AsyncFunction";be.boundFunction=t=>be.function_(t)&&!t.hasOwnProperty("prototype");be.regExp=pi("RegExp");be.date=pi("Date");be.error=pi("Error");be.map=t=>pi("Map")(t);be.set=t=>pi("Set")(t);be.weakMap=t=>pi("WeakMap")(t);be.weakSet=t=>pi("WeakSet")(t);be.int8Array=pi("Int8Array");be.uint8Array=pi("Uint8Array");be.uint8ClampedArray=pi("Uint8ClampedArray");be.int16Array=pi("Int16Array");be.uint16Array=pi("Uint16Array");be.int32Array=pi("Int32Array");be.uint32Array=pi("Uint32Array");be.float32Array=pi("Float32Array");be.float64Array=pi("Float64Array");be.bigInt64Array=pi("BigInt64Array");be.bigUint64Array=pi("BigUint64Array");be.arrayBuffer=pi("ArrayBuffer");be.sharedArrayBuffer=pi("SharedArrayBuffer");be.dataView=pi("DataView");be.enumCase=(t,e)=>Object.values(e).includes(t);be.directInstanceOf=(t,e)=>Object.getPrototypeOf(t)===e.prototype;be.urlInstance=t=>pi("URL")(t);be.urlString=t=>{if(!be.string(t))return!1;try{return new URL(t),!0}catch{return!1}};be.truthy=t=>!!t;be.falsy=t=>!t;be.nan=t=>Number.isNaN(t);be.primitive=t=>be.null_(t)||Cet(typeof t);be.integer=t=>Number.isInteger(t);be.safeInteger=t=>Number.isSafeInteger(t);be.plainObject=t=>{if(Pue.call(t)!=="[object Object]")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.getPrototypeOf({})};be.typedArray=t=>met(VB(t));var vet=t=>be.safeInteger(t)&&t>=0;be.arrayLike=t=>!be.nullOrUndefined(t)&&!be.function_(t)&&vet(t.length);be.inRange=(t,e)=>{if(be.number(e))return t>=Math.min(0,e)&&t<=Math.max(e,0);if(be.array(e)&&e.length===2)return t>=Math.min(...e)&&t<=Math.max(...e);throw new TypeError(`Invalid range: ${JSON.stringify(e)}`)};var Det=1,bet=["innerHTML","ownerDocument","style","attributes","nodeValue"];be.domElement=t=>be.object(t)&&t.nodeType===Det&&be.string(t.nodeName)&&!be.plainObject(t)&&bet.every(e=>e in t);be.observable=t=>{var e,r,s,a;return t?t===((r=(e=t)[Symbol.observable])===null||r===void 0?void 0:r.call(e))||t===((a=(s=t)["@@observable"])===null||a===void 0?void 0:a.call(s)):!1};be.nodeStream=t=>be.object(t)&&be.function_(t.pipe)&&!be.observable(t);be.infinite=t=>t===1/0||t===-1/0;var xue=t=>e=>be.integer(e)&&Math.abs(e%2)===t;be.evenInteger=xue(0);be.oddInteger=xue(1);be.emptyArray=t=>be.array(t)&&t.length===0;be.nonEmptyArray=t=>be.array(t)&&t.length>0;be.emptyString=t=>be.string(t)&&t.length===0;var Pet=t=>be.string(t)&&!/\S/.test(t);be.emptyStringOrWhitespace=t=>be.emptyString(t)||Pet(t);be.nonEmptyString=t=>be.string(t)&&t.length>0;be.nonEmptyStringAndNotWhitespace=t=>be.string(t)&&!be.emptyStringOrWhitespace(t);be.emptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length===0;be.nonEmptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length>0;be.emptySet=t=>be.set(t)&&t.size===0;be.nonEmptySet=t=>be.set(t)&&t.size>0;be.emptyMap=t=>be.map(t)&&t.size===0;be.nonEmptyMap=t=>be.map(t)&&t.size>0;be.propertyKey=t=>be.any([be.string,be.number,be.symbol],t);be.formData=t=>pi("FormData")(t);be.urlSearchParams=t=>pi("URLSearchParams")(t);var kue=(t,e,r)=>{if(!be.function_(e))throw new TypeError(`Invalid predicate: ${JSON.stringify(e)}`);if(r.length===0)throw new TypeError("Invalid number of values");return t.call(r,e)};be.any=(t,...e)=>(be.array(t)?t:[t]).some(s=>kue(Array.prototype.some,s,e));be.all=(t,...e)=>kue(Array.prototype.every,t,e);var _t=(t,e,r,s={})=>{if(!t){let{multipleValues:a}=s,n=a?`received values of types ${[...new Set(r.map(c=>`\`${be(c)}\``))].join(", ")}`:`received value of type \`${be(r)}\``;throw new TypeError(`Expected value which is \`${e}\`, ${n}.`)}};Fp.assert={undefined:t=>_t(be.undefined(t),"undefined",t),string:t=>_t(be.string(t),"string",t),number:t=>_t(be.number(t),"number",t),bigint:t=>_t(be.bigint(t),"bigint",t),function_:t=>_t(be.function_(t),"Function",t),null_:t=>_t(be.null_(t),"null",t),class_:t=>_t(be.class_(t),"Class",t),boolean:t=>_t(be.boolean(t),"boolean",t),symbol:t=>_t(be.symbol(t),"symbol",t),numericString:t=>_t(be.numericString(t),"string with a number",t),array:(t,e)=>{_t(be.array(t),"Array",t),e&&t.forEach(e)},buffer:t=>_t(be.buffer(t),"Buffer",t),blob:t=>_t(be.blob(t),"Blob",t),nullOrUndefined:t=>_t(be.nullOrUndefined(t),"null or undefined",t),object:t=>_t(be.object(t),"Object",t),iterable:t=>_t(be.iterable(t),"Iterable",t),asyncIterable:t=>_t(be.asyncIterable(t),"AsyncIterable",t),generator:t=>_t(be.generator(t),"Generator",t),asyncGenerator:t=>_t(be.asyncGenerator(t),"AsyncGenerator",t),nativePromise:t=>_t(be.nativePromise(t),"native Promise",t),promise:t=>_t(be.promise(t),"Promise",t),generatorFunction:t=>_t(be.generatorFunction(t),"GeneratorFunction",t),asyncGeneratorFunction:t=>_t(be.asyncGeneratorFunction(t),"AsyncGeneratorFunction",t),asyncFunction:t=>_t(be.asyncFunction(t),"AsyncFunction",t),boundFunction:t=>_t(be.boundFunction(t),"Function",t),regExp:t=>_t(be.regExp(t),"RegExp",t),date:t=>_t(be.date(t),"Date",t),error:t=>_t(be.error(t),"Error",t),map:t=>_t(be.map(t),"Map",t),set:t=>_t(be.set(t),"Set",t),weakMap:t=>_t(be.weakMap(t),"WeakMap",t),weakSet:t=>_t(be.weakSet(t),"WeakSet",t),int8Array:t=>_t(be.int8Array(t),"Int8Array",t),uint8Array:t=>_t(be.uint8Array(t),"Uint8Array",t),uint8ClampedArray:t=>_t(be.uint8ClampedArray(t),"Uint8ClampedArray",t),int16Array:t=>_t(be.int16Array(t),"Int16Array",t),uint16Array:t=>_t(be.uint16Array(t),"Uint16Array",t),int32Array:t=>_t(be.int32Array(t),"Int32Array",t),uint32Array:t=>_t(be.uint32Array(t),"Uint32Array",t),float32Array:t=>_t(be.float32Array(t),"Float32Array",t),float64Array:t=>_t(be.float64Array(t),"Float64Array",t),bigInt64Array:t=>_t(be.bigInt64Array(t),"BigInt64Array",t),bigUint64Array:t=>_t(be.bigUint64Array(t),"BigUint64Array",t),arrayBuffer:t=>_t(be.arrayBuffer(t),"ArrayBuffer",t),sharedArrayBuffer:t=>_t(be.sharedArrayBuffer(t),"SharedArrayBuffer",t),dataView:t=>_t(be.dataView(t),"DataView",t),enumCase:(t,e)=>_t(be.enumCase(t,e),"EnumCase",t),urlInstance:t=>_t(be.urlInstance(t),"URL",t),urlString:t=>_t(be.urlString(t),"string with a URL",t),truthy:t=>_t(be.truthy(t),"truthy",t),falsy:t=>_t(be.falsy(t),"falsy",t),nan:t=>_t(be.nan(t),"NaN",t),primitive:t=>_t(be.primitive(t),"primitive",t),integer:t=>_t(be.integer(t),"integer",t),safeInteger:t=>_t(be.safeInteger(t),"integer",t),plainObject:t=>_t(be.plainObject(t),"plain object",t),typedArray:t=>_t(be.typedArray(t),"TypedArray",t),arrayLike:t=>_t(be.arrayLike(t),"array-like",t),domElement:t=>_t(be.domElement(t),"HTMLElement",t),observable:t=>_t(be.observable(t),"Observable",t),nodeStream:t=>_t(be.nodeStream(t),"Node.js Stream",t),infinite:t=>_t(be.infinite(t),"infinite number",t),emptyArray:t=>_t(be.emptyArray(t),"empty array",t),nonEmptyArray:t=>_t(be.nonEmptyArray(t),"non-empty array",t),emptyString:t=>_t(be.emptyString(t),"empty string",t),emptyStringOrWhitespace:t=>_t(be.emptyStringOrWhitespace(t),"empty string or whitespace",t),nonEmptyString:t=>_t(be.nonEmptyString(t),"non-empty string",t),nonEmptyStringAndNotWhitespace:t=>_t(be.nonEmptyStringAndNotWhitespace(t),"non-empty string and not whitespace",t),emptyObject:t=>_t(be.emptyObject(t),"empty object",t),nonEmptyObject:t=>_t(be.nonEmptyObject(t),"non-empty object",t),emptySet:t=>_t(be.emptySet(t),"empty set",t),nonEmptySet:t=>_t(be.nonEmptySet(t),"non-empty set",t),emptyMap:t=>_t(be.emptyMap(t),"empty map",t),nonEmptyMap:t=>_t(be.nonEmptyMap(t),"non-empty map",t),propertyKey:t=>_t(be.propertyKey(t),"PropertyKey",t),formData:t=>_t(be.formData(t),"FormData",t),urlSearchParams:t=>_t(be.urlSearchParams(t),"URLSearchParams",t),evenInteger:t=>_t(be.evenInteger(t),"even integer",t),oddInteger:t=>_t(be.oddInteger(t),"odd integer",t),directInstanceOf:(t,e)=>_t(be.directInstanceOf(t,e),"T",t),inRange:(t,e)=>_t(be.inRange(t,e),"in range",t),any:(t,...e)=>_t(be.any(t,...e),"predicate returns truthy for any value",e,{multipleValues:!0}),all:(t,...e)=>_t(be.all(t,...e),"predicate returns truthy for all values",e,{multipleValues:!0})};Object.defineProperties(be,{class:{value:be.class_},function:{value:be.function_},null:{value:be.null_}});Object.defineProperties(Fp.assert,{class:{value:Fp.assert.class_},function:{value:Fp.assert.function_},null:{value:Fp.assert.null_}});Fp.default=be;MQ.exports=be;MQ.exports.default=be;MQ.exports.assert=Fp.assert});var Que=_((CMt,z8)=>{"use strict";var UQ=class extends Error{constructor(e){super(e||"Promise was canceled"),this.name="CancelError"}get isCanceled(){return!0}},_Q=class t{static fn(e){return(...r)=>new t((s,a,n)=>{r.push(n),e(...r).then(s,a)})}constructor(e){this._cancelHandlers=[],this._isPending=!0,this._isCanceled=!1,this._rejectOnCancel=!0,this._promise=new Promise((r,s)=>{this._reject=s;let a=f=>{this._isPending=!1,r(f)},n=f=>{this._isPending=!1,s(f)},c=f=>{if(!this._isPending)throw new Error("The `onCancel` handler was attached after the promise settled.");this._cancelHandlers.push(f)};return Object.defineProperties(c,{shouldReject:{get:()=>this._rejectOnCancel,set:f=>{this._rejectOnCancel=f}}}),e(a,n,c)})}then(e,r){return this._promise.then(e,r)}catch(e){return this._promise.catch(e)}finally(e){return this._promise.finally(e)}cancel(e){if(!(!this._isPending||this._isCanceled)){if(this._cancelHandlers.length>0)try{for(let r of this._cancelHandlers)r()}catch(r){this._reject(r)}this._isCanceled=!0,this._rejectOnCancel&&this._reject(new UQ(e))}}get isCanceled(){return this._isCanceled}};Object.setPrototypeOf(_Q.prototype,Promise.prototype);z8.exports=_Q;z8.exports.CancelError=UQ});var Tue=_((Z8,$8)=>{"use strict";Object.defineProperty(Z8,"__esModule",{value:!0});function xet(t){return t.encrypted}var X8=(t,e)=>{let r;typeof e=="function"?r={connect:e}:r=e;let s=typeof r.connect=="function",a=typeof r.secureConnect=="function",n=typeof r.close=="function",c=()=>{s&&r.connect(),xet(t)&&a&&(t.authorized?r.secureConnect():t.authorizationError||t.once("secureConnect",r.secureConnect)),n&&t.once("close",r.close)};t.writable&&!t.connecting?c():t.connecting?t.once("connect",c):t.destroyed&&n&&r.close(t._hadError)};Z8.default=X8;$8.exports=X8;$8.exports.default=X8});var Rue=_((tH,rH)=>{"use strict";Object.defineProperty(tH,"__esModule",{value:!0});var ket=Tue(),Qet=Number(process.versions.node.split(".")[0]),eH=t=>{let e={start:Date.now(),socket:void 0,lookup:void 0,connect:void 0,secureConnect:void 0,upload:void 0,response:void 0,end:void 0,error:void 0,abort:void 0,phases:{wait:void 0,dns:void 0,tcp:void 0,tls:void 0,request:void 0,firstByte:void 0,download:void 0,total:void 0}};t.timings=e;let r=c=>{let f=c.emit.bind(c);c.emit=(p,...h)=>(p==="error"&&(e.error=Date.now(),e.phases.total=e.error-e.start,c.emit=f),f(p,...h))};r(t),t.prependOnceListener("abort",()=>{e.abort=Date.now(),(!e.response||Qet>=13)&&(e.phases.total=Date.now()-e.start)});let s=c=>{e.socket=Date.now(),e.phases.wait=e.socket-e.start;let f=()=>{e.lookup=Date.now(),e.phases.dns=e.lookup-e.socket};c.prependOnceListener("lookup",f),ket.default(c,{connect:()=>{e.connect=Date.now(),e.lookup===void 0&&(c.removeListener("lookup",f),e.lookup=e.connect,e.phases.dns=e.lookup-e.socket),e.phases.tcp=e.connect-e.lookup},secureConnect:()=>{e.secureConnect=Date.now(),e.phases.tls=e.secureConnect-e.connect}})};t.socket?s(t.socket):t.prependOnceListener("socket",s);let a=()=>{var c;e.upload=Date.now(),e.phases.request=e.upload-(c=e.secureConnect,c??e.connect)};return(typeof t.writableFinished=="boolean"?t.writableFinished:t.finished&&t.outputSize===0&&(!t.socket||t.socket.writableLength===0))?a():t.prependOnceListener("finish",a),t.prependOnceListener("response",c=>{e.response=Date.now(),e.phases.firstByte=e.response-e.upload,c.timings=e,r(c),c.prependOnceListener("end",()=>{e.end=Date.now(),e.phases.download=e.end-e.response,e.phases.total=e.end-e.start})}),e};tH.default=eH;rH.exports=eH;rH.exports.default=eH});var _ue=_((wMt,sH)=>{"use strict";var{V4MAPPED:Tet,ADDRCONFIG:Ret,ALL:Uue,promises:{Resolver:Fue},lookup:Fet}=Ie("dns"),{promisify:nH}=Ie("util"),Net=Ie("os"),pI=Symbol("cacheableLookupCreateConnection"),iH=Symbol("cacheableLookupInstance"),Nue=Symbol("expires"),Oet=typeof Uue=="number",Oue=t=>{if(!(t&&typeof t.createConnection=="function"))throw new Error("Expected an Agent instance as the first argument")},Let=t=>{for(let e of t)e.family!==6&&(e.address=`::ffff:${e.address}`,e.family=6)},Lue=()=>{let t=!1,e=!1;for(let r of Object.values(Net.networkInterfaces()))for(let s of r)if(!s.internal&&(s.family==="IPv6"?e=!0:t=!0,t&&e))return{has4:t,has6:e};return{has4:t,has6:e}},Met=t=>Symbol.iterator in t,Mue={ttl:!0},Uet={all:!0},HQ=class{constructor({cache:e=new Map,maxTtl:r=1/0,fallbackDuration:s=3600,errorTtl:a=.15,resolver:n=new Fue,lookup:c=Fet}={}){if(this.maxTtl=r,this.errorTtl=a,this._cache=e,this._resolver=n,this._dnsLookup=nH(c),this._resolver instanceof Fue?(this._resolve4=this._resolver.resolve4.bind(this._resolver),this._resolve6=this._resolver.resolve6.bind(this._resolver)):(this._resolve4=nH(this._resolver.resolve4.bind(this._resolver)),this._resolve6=nH(this._resolver.resolve6.bind(this._resolver))),this._iface=Lue(),this._pending={},this._nextRemovalTime=!1,this._hostnamesToFallback=new Set,s<1)this._fallback=!1;else{this._fallback=!0;let f=setInterval(()=>{this._hostnamesToFallback.clear()},s*1e3);f.unref&&f.unref()}this.lookup=this.lookup.bind(this),this.lookupAsync=this.lookupAsync.bind(this)}set servers(e){this.clear(),this._resolver.setServers(e)}get servers(){return this._resolver.getServers()}lookup(e,r,s){if(typeof r=="function"?(s=r,r={}):typeof r=="number"&&(r={family:r}),!s)throw new Error("Callback must be a function.");this.lookupAsync(e,r).then(a=>{r.all?s(null,a):s(null,a.address,a.family,a.expires,a.ttl)},s)}async lookupAsync(e,r={}){typeof r=="number"&&(r={family:r});let s=await this.query(e);if(r.family===6){let a=s.filter(n=>n.family===6);r.hints&Tet&&(Oet&&r.hints&Uue||a.length===0)?Let(s):s=a}else r.family===4&&(s=s.filter(a=>a.family===4));if(r.hints&Ret){let{_iface:a}=this;s=s.filter(n=>n.family===6?a.has6:a.has4)}if(s.length===0){let a=new Error(`cacheableLookup ENOTFOUND ${e}`);throw a.code="ENOTFOUND",a.hostname=e,a}return r.all?s:s[0]}async query(e){let r=await this._cache.get(e);if(!r){let s=this._pending[e];if(s)r=await s;else{let a=this.queryAndCache(e);this._pending[e]=a,r=await a}}return r=r.map(s=>({...s})),r}async _resolve(e){let r=async h=>{try{return await h}catch(E){if(E.code==="ENODATA"||E.code==="ENOTFOUND")return[];throw E}},[s,a]=await Promise.all([this._resolve4(e,Mue),this._resolve6(e,Mue)].map(h=>r(h))),n=0,c=0,f=0,p=Date.now();for(let h of s)h.family=4,h.expires=p+h.ttl*1e3,n=Math.max(n,h.ttl);for(let h of a)h.family=6,h.expires=p+h.ttl*1e3,c=Math.max(c,h.ttl);return s.length>0?a.length>0?f=Math.min(n,c):f=n:f=c,{entries:[...s,...a],cacheTtl:f}}async _lookup(e){try{return{entries:await this._dnsLookup(e,{all:!0}),cacheTtl:0}}catch{return{entries:[],cacheTtl:0}}}async _set(e,r,s){if(this.maxTtl>0&&s>0){s=Math.min(s,this.maxTtl)*1e3,r[Nue]=Date.now()+s;try{await this._cache.set(e,r,s)}catch(a){this.lookupAsync=async()=>{let n=new Error("Cache Error. Please recreate the CacheableLookup instance.");throw n.cause=a,n}}Met(this._cache)&&this._tick(s)}}async queryAndCache(e){if(this._hostnamesToFallback.has(e))return this._dnsLookup(e,Uet);try{let r=await this._resolve(e);r.entries.length===0&&this._fallback&&(r=await this._lookup(e),r.entries.length!==0&&this._hostnamesToFallback.add(e));let s=r.entries.length===0?this.errorTtl:r.cacheTtl;return await this._set(e,r.entries,s),delete this._pending[e],r.entries}catch(r){throw delete this._pending[e],r}}_tick(e){let r=this._nextRemovalTime;(!r||e{this._nextRemovalTime=!1;let s=1/0,a=Date.now();for(let[n,c]of this._cache){let f=c[Nue];a>=f?this._cache.delete(n):f("lookup"in r||(r.lookup=this.lookup),e[pI](r,s))}uninstall(e){if(Oue(e),e[pI]){if(e[iH]!==this)throw new Error("The agent is not owned by this CacheableLookup instance");e.createConnection=e[pI],delete e[pI],delete e[iH]}}updateInterfaceInfo(){let{_iface:e}=this;this._iface=Lue(),(e.has4&&!this._iface.has4||e.has6&&!this._iface.has6)&&this._cache.clear()}clear(e){if(e){this._cache.delete(e);return}this._cache.clear()}};sH.exports=HQ;sH.exports.default=HQ});var Gue=_((BMt,oH)=>{"use strict";var _et=typeof URL>"u"?Ie("url").URL:URL,Het="text/plain",jet="us-ascii",Hue=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),Get=(t,{stripHash:e})=>{let r=t.match(/^data:([^,]*?),([^#]*?)(?:#(.*))?$/);if(!r)throw new Error(`Invalid URL: ${t}`);let s=r[1].split(";"),a=r[2],n=e?"":r[3],c=!1;s[s.length-1]==="base64"&&(s.pop(),c=!0);let f=(s.shift()||"").toLowerCase(),h=[...s.map(E=>{let[C,S=""]=E.split("=").map(P=>P.trim());return C==="charset"&&(S=S.toLowerCase(),S===jet)?"":`${C}${S?`=${S}`:""}`}).filter(Boolean)];return c&&h.push("base64"),(h.length!==0||f&&f!==Het)&&h.unshift(f),`data:${h.join(";")},${c?a.trim():a}${n?`#${n}`:""}`},jue=(t,e)=>{if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},Reflect.has(e,"normalizeHttps"))throw new Error("options.normalizeHttps is renamed to options.forceHttp");if(Reflect.has(e,"normalizeHttp"))throw new Error("options.normalizeHttp is renamed to options.forceHttps");if(Reflect.has(e,"stripFragment"))throw new Error("options.stripFragment is renamed to options.stripHash");if(t=t.trim(),/^data:/i.test(t))return Get(t,e);let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new _et(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash&&(a.hash=""),a.pathname&&(a.pathname=a.pathname.replace(/((?!:).|^)\/{2,}/g,(n,c)=>/^(?!\/)/g.test(c)?`${c}/`:"/")),a.pathname&&(a.pathname=decodeURI(a.pathname)),e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let n=a.pathname.split("/"),c=n[n.length-1];Hue(c,e.removeDirectoryIndex)&&(n=n.slice(0,n.length-1),a.pathname=n.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let n of[...a.searchParams.keys()])Hue(n,e.removeQueryParameters)&&a.searchParams.delete(n);return e.sortQueryParameters&&a.searchParams.sort(),e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,"")),t=a.toString(),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t};oH.exports=jue;oH.exports.default=jue});var Yue=_((vMt,Wue)=>{Wue.exports=que;function que(t,e){if(t&&e)return que(t)(e);if(typeof t!="function")throw new TypeError("need wrapper function");return Object.keys(t).forEach(function(s){r[s]=t[s]}),r;function r(){for(var s=new Array(arguments.length),a=0;a{var Vue=Yue();aH.exports=Vue(jQ);aH.exports.strict=Vue(Jue);jQ.proto=jQ(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return jQ(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return Jue(this)},configurable:!0})});function jQ(t){var e=function(){return e.called?e.value:(e.called=!0,e.value=t.apply(this,arguments))};return e.called=!1,e}function Jue(t){var e=function(){if(e.called)throw new Error(e.onceError);return e.called=!0,e.value=t.apply(this,arguments)},r=t.name||"Function wrapped with `once`";return e.onceError=r+" shouldn't be called more than once",e.called=!1,e}});var cH=_((DMt,zue)=>{var qet=lH(),Wet=function(){},Yet=function(t){return t.setHeader&&typeof t.abort=="function"},Vet=function(t){return t.stdio&&Array.isArray(t.stdio)&&t.stdio.length===3},Kue=function(t,e,r){if(typeof e=="function")return Kue(t,null,e);e||(e={}),r=qet(r||Wet);var s=t._writableState,a=t._readableState,n=e.readable||e.readable!==!1&&t.readable,c=e.writable||e.writable!==!1&&t.writable,f=function(){t.writable||p()},p=function(){c=!1,n||r.call(t)},h=function(){n=!1,c||r.call(t)},E=function(I){r.call(t,I?new Error("exited with error code: "+I):null)},C=function(I){r.call(t,I)},S=function(){if(n&&!(a&&a.ended))return r.call(t,new Error("premature close"));if(c&&!(s&&s.ended))return r.call(t,new Error("premature close"))},P=function(){t.req.on("finish",p)};return Yet(t)?(t.on("complete",p),t.on("abort",S),t.req?P():t.on("request",P)):c&&!s&&(t.on("end",f),t.on("close",f)),Vet(t)&&t.on("exit",E),t.on("end",h),t.on("finish",p),e.error!==!1&&t.on("error",C),t.on("close",S),function(){t.removeListener("complete",p),t.removeListener("abort",S),t.removeListener("request",P),t.req&&t.req.removeListener("finish",p),t.removeListener("end",f),t.removeListener("close",f),t.removeListener("finish",p),t.removeListener("exit",E),t.removeListener("end",h),t.removeListener("error",C),t.removeListener("close",S)}};zue.exports=Kue});var $ue=_((bMt,Zue)=>{var Jet=lH(),Ket=cH(),uH=Ie("fs"),JB=function(){},zet=/^v?\.0/.test(process.version),GQ=function(t){return typeof t=="function"},Xet=function(t){return!zet||!uH?!1:(t instanceof(uH.ReadStream||JB)||t instanceof(uH.WriteStream||JB))&&GQ(t.close)},Zet=function(t){return t.setHeader&&GQ(t.abort)},$et=function(t,e,r,s){s=Jet(s);var a=!1;t.on("close",function(){a=!0}),Ket(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,Xet(t))return t.close(JB);if(Zet(t))return t.abort();if(GQ(t.destroy))return t.destroy();s(c||new Error("stream was destroyed"))}}},Xue=function(t){t()},ett=function(t,e){return t.pipe(e)},ttt=function(){var t=Array.prototype.slice.call(arguments),e=GQ(t[t.length-1]||JB)&&t.pop()||JB;if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new Error("pump requires two streams per minimum");var r,s=t.map(function(a,n){var c=n0;return $et(a,c,f,function(p){r||(r=p),p&&s.forEach(Xue),!c&&(s.forEach(Xue),e(r))})});return t.reduce(ett)};Zue.exports=ttt});var tfe=_((PMt,efe)=>{"use strict";var{PassThrough:rtt}=Ie("stream");efe.exports=t=>{t={...t};let{array:e}=t,{encoding:r}=t,s=r==="buffer",a=!1;e?a=!(r||s):r=r||"utf8",s&&(r=null);let n=new rtt({objectMode:a});r&&n.setEncoding(r);let c=0,f=[];return n.on("data",p=>{f.push(p),a?c=f.length:c+=p.length}),n.getBufferedValue=()=>e?f:s?Buffer.concat(f,c):f.join(""),n.getBufferedLength=()=>c,n}});var rfe=_((xMt,hI)=>{"use strict";var ntt=$ue(),itt=tfe(),qQ=class extends Error{constructor(){super("maxBuffer exceeded"),this.name="MaxBufferError"}};async function WQ(t,e){if(!t)return Promise.reject(new Error("Expected a stream"));e={maxBuffer:1/0,...e};let{maxBuffer:r}=e,s;return await new Promise((a,n)=>{let c=f=>{f&&(f.bufferedData=s.getBufferedValue()),n(f)};s=ntt(t,itt(e),f=>{if(f){c(f);return}a()}),s.on("data",()=>{s.getBufferedLength()>r&&c(new qQ)})}),s.getBufferedValue()}hI.exports=WQ;hI.exports.default=WQ;hI.exports.buffer=(t,e)=>WQ(t,{...e,encoding:"buffer"});hI.exports.array=(t,e)=>WQ(t,{...e,array:!0});hI.exports.MaxBufferError=qQ});var ife=_((QMt,nfe)=>{"use strict";var stt=new Set([200,203,204,206,300,301,308,404,405,410,414,501]),ott=new Set([200,203,204,300,301,302,303,307,308,404,405,410,414,501]),att=new Set([500,502,503,504]),ltt={date:!0,connection:!0,"keep-alive":!0,"proxy-authenticate":!0,"proxy-authorization":!0,te:!0,trailer:!0,"transfer-encoding":!0,upgrade:!0},ctt={"content-length":!0,"content-encoding":!0,"transfer-encoding":!0,"content-range":!0};function nm(t){let e=parseInt(t,10);return isFinite(e)?e:0}function utt(t){return t?att.has(t.status):!0}function fH(t){let e={};if(!t)return e;let r=t.trim().split(/,/);for(let s of r){let[a,n]=s.split(/=/,2);e[a.trim()]=n===void 0?!0:n.trim().replace(/^"|"$/g,"")}return e}function ftt(t){let e=[];for(let r in t){let s=t[r];e.push(s===!0?r:r+"="+s)}if(e.length)return e.join(", ")}nfe.exports=class{constructor(e,r,{shared:s,cacheHeuristic:a,immutableMinTimeToLive:n,ignoreCargoCult:c,_fromObject:f}={}){if(f){this._fromObject(f);return}if(!r||!r.headers)throw Error("Response headers missing");this._assertRequestHasHeaders(e),this._responseTime=this.now(),this._isShared=s!==!1,this._cacheHeuristic=a!==void 0?a:.1,this._immutableMinTtl=n!==void 0?n:24*3600*1e3,this._status="status"in r?r.status:200,this._resHeaders=r.headers,this._rescc=fH(r.headers["cache-control"]),this._method="method"in e?e.method:"GET",this._url=e.url,this._host=e.headers.host,this._noAuthorization=!e.headers.authorization,this._reqHeaders=r.headers.vary?e.headers:null,this._reqcc=fH(e.headers["cache-control"]),c&&"pre-check"in this._rescc&&"post-check"in this._rescc&&(delete this._rescc["pre-check"],delete this._rescc["post-check"],delete this._rescc["no-cache"],delete this._rescc["no-store"],delete this._rescc["must-revalidate"],this._resHeaders=Object.assign({},this._resHeaders,{"cache-control":ftt(this._rescc)}),delete this._resHeaders.expires,delete this._resHeaders.pragma),r.headers["cache-control"]==null&&/no-cache/.test(r.headers.pragma)&&(this._rescc["no-cache"]=!0)}now(){return Date.now()}storable(){return!!(!this._reqcc["no-store"]&&(this._method==="GET"||this._method==="HEAD"||this._method==="POST"&&this._hasExplicitExpiration())&&ott.has(this._status)&&!this._rescc["no-store"]&&(!this._isShared||!this._rescc.private)&&(!this._isShared||this._noAuthorization||this._allowsStoringAuthenticated())&&(this._resHeaders.expires||this._rescc["max-age"]||this._isShared&&this._rescc["s-maxage"]||this._rescc.public||stt.has(this._status)))}_hasExplicitExpiration(){return this._isShared&&this._rescc["s-maxage"]||this._rescc["max-age"]||this._resHeaders.expires}_assertRequestHasHeaders(e){if(!e||!e.headers)throw Error("Request headers missing")}satisfiesWithoutRevalidation(e){this._assertRequestHasHeaders(e);let r=fH(e.headers["cache-control"]);return r["no-cache"]||/no-cache/.test(e.headers.pragma)||r["max-age"]&&this.age()>r["max-age"]||r["min-fresh"]&&this.timeToLive()<1e3*r["min-fresh"]||this.stale()&&!(r["max-stale"]&&!this._rescc["must-revalidate"]&&(r["max-stale"]===!0||r["max-stale"]>this.age()-this.maxAge()))?!1:this._requestMatches(e,!1)}_requestMatches(e,r){return(!this._url||this._url===e.url)&&this._host===e.headers.host&&(!e.method||this._method===e.method||r&&e.method==="HEAD")&&this._varyMatches(e)}_allowsStoringAuthenticated(){return this._rescc["must-revalidate"]||this._rescc.public||this._rescc["s-maxage"]}_varyMatches(e){if(!this._resHeaders.vary)return!0;if(this._resHeaders.vary==="*")return!1;let r=this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/);for(let s of r)if(e.headers[s]!==this._reqHeaders[s])return!1;return!0}_copyWithoutHopByHopHeaders(e){let r={};for(let s in e)ltt[s]||(r[s]=e[s]);if(e.connection){let s=e.connection.trim().split(/\s*,\s*/);for(let a of s)delete r[a]}if(r.warning){let s=r.warning.split(/,/).filter(a=>!/^\s*1[0-9][0-9]/.test(a));s.length?r.warning=s.join(",").trim():delete r.warning}return r}responseHeaders(){let e=this._copyWithoutHopByHopHeaders(this._resHeaders),r=this.age();return r>3600*24&&!this._hasExplicitExpiration()&&this.maxAge()>3600*24&&(e.warning=(e.warning?`${e.warning}, `:"")+'113 - "rfc7234 5.5.4"'),e.age=`${Math.round(r)}`,e.date=new Date(this.now()).toUTCString(),e}date(){let e=Date.parse(this._resHeaders.date);return isFinite(e)?e:this._responseTime}age(){let e=this._ageValue(),r=(this.now()-this._responseTime)/1e3;return e+r}_ageValue(){return nm(this._resHeaders.age)}maxAge(){if(!this.storable()||this._rescc["no-cache"]||this._isShared&&this._resHeaders["set-cookie"]&&!this._rescc.public&&!this._rescc.immutable||this._resHeaders.vary==="*")return 0;if(this._isShared){if(this._rescc["proxy-revalidate"])return 0;if(this._rescc["s-maxage"])return nm(this._rescc["s-maxage"])}if(this._rescc["max-age"])return nm(this._rescc["max-age"]);let e=this._rescc.immutable?this._immutableMinTtl:0,r=this.date();if(this._resHeaders.expires){let s=Date.parse(this._resHeaders.expires);return Number.isNaN(s)||ss)return Math.max(e,(r-s)/1e3*this._cacheHeuristic)}return e}timeToLive(){let e=this.maxAge()-this.age(),r=e+nm(this._rescc["stale-if-error"]),s=e+nm(this._rescc["stale-while-revalidate"]);return Math.max(0,e,r,s)*1e3}stale(){return this.maxAge()<=this.age()}_useStaleIfError(){return this.maxAge()+nm(this._rescc["stale-if-error"])>this.age()}useStaleWhileRevalidate(){return this.maxAge()+nm(this._rescc["stale-while-revalidate"])>this.age()}static fromObject(e){return new this(void 0,void 0,{_fromObject:e})}_fromObject(e){if(this._responseTime)throw Error("Reinitialized");if(!e||e.v!==1)throw Error("Invalid serialization");this._responseTime=e.t,this._isShared=e.sh,this._cacheHeuristic=e.ch,this._immutableMinTtl=e.imm!==void 0?e.imm:24*3600*1e3,this._status=e.st,this._resHeaders=e.resh,this._rescc=e.rescc,this._method=e.m,this._url=e.u,this._host=e.h,this._noAuthorization=e.a,this._reqHeaders=e.reqh,this._reqcc=e.reqcc}toObject(){return{v:1,t:this._responseTime,sh:this._isShared,ch:this._cacheHeuristic,imm:this._immutableMinTtl,st:this._status,resh:this._resHeaders,rescc:this._rescc,m:this._method,u:this._url,h:this._host,a:this._noAuthorization,reqh:this._reqHeaders,reqcc:this._reqcc}}revalidationHeaders(e){this._assertRequestHasHeaders(e);let r=this._copyWithoutHopByHopHeaders(e.headers);if(delete r["if-range"],!this._requestMatches(e,!0)||!this.storable())return delete r["if-none-match"],delete r["if-modified-since"],r;if(this._resHeaders.etag&&(r["if-none-match"]=r["if-none-match"]?`${r["if-none-match"]}, ${this._resHeaders.etag}`:this._resHeaders.etag),r["accept-ranges"]||r["if-match"]||r["if-unmodified-since"]||this._method&&this._method!="GET"){if(delete r["if-modified-since"],r["if-none-match"]){let a=r["if-none-match"].split(/,/).filter(n=>!/^\s*W\//.test(n));a.length?r["if-none-match"]=a.join(",").trim():delete r["if-none-match"]}}else this._resHeaders["last-modified"]&&!r["if-modified-since"]&&(r["if-modified-since"]=this._resHeaders["last-modified"]);return r}revalidatedPolicy(e,r){if(this._assertRequestHasHeaders(e),this._useStaleIfError()&&utt(r))return{modified:!1,matches:!1,policy:this};if(!r||!r.headers)throw Error("Response headers missing");let s=!1;if(r.status!==void 0&&r.status!=304?s=!1:r.headers.etag&&!/^\s*W\//.test(r.headers.etag)?s=this._resHeaders.etag&&this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag:this._resHeaders.etag&&r.headers.etag?s=this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag.replace(/^\s*W\//,""):this._resHeaders["last-modified"]?s=this._resHeaders["last-modified"]===r.headers["last-modified"]:!this._resHeaders.etag&&!this._resHeaders["last-modified"]&&!r.headers.etag&&!r.headers["last-modified"]&&(s=!0),!s)return{policy:new this.constructor(e,r),modified:r.status!=304,matches:!1};let a={};for(let c in this._resHeaders)a[c]=c in r.headers&&!ctt[c]?r.headers[c]:this._resHeaders[c];let n=Object.assign({},r,{status:this._status,method:this._method,headers:a});return{policy:new this.constructor(e,n,{shared:this._isShared,cacheHeuristic:this._cacheHeuristic,immutableMinTimeToLive:this._immutableMinTtl}),modified:!1,matches:!0}}}});var YQ=_((TMt,sfe)=>{"use strict";sfe.exports=t=>{let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=s;return e}});var afe=_((RMt,ofe)=>{"use strict";var Att=Ie("stream").Readable,ptt=YQ(),AH=class extends Att{constructor(e,r,s,a){if(typeof e!="number")throw new TypeError("Argument `statusCode` should be a number");if(typeof r!="object")throw new TypeError("Argument `headers` should be an object");if(!(s instanceof Buffer))throw new TypeError("Argument `body` should be a buffer");if(typeof a!="string")throw new TypeError("Argument `url` should be a string");super(),this.statusCode=e,this.headers=ptt(r),this.body=s,this.url=a}_read(){this.push(this.body),this.push(null)}};ofe.exports=AH});var cfe=_((FMt,lfe)=>{"use strict";var htt=["destroy","setTimeout","socket","headers","trailers","rawHeaders","statusCode","httpVersion","httpVersionMinor","httpVersionMajor","rawTrailers","statusMessage"];lfe.exports=(t,e)=>{let r=new Set(Object.keys(t).concat(htt));for(let s of r)s in e||(e[s]=typeof t[s]=="function"?t[s].bind(t):t[s])}});var ffe=_((NMt,ufe)=>{"use strict";var gtt=Ie("stream").PassThrough,dtt=cfe(),mtt=t=>{if(!(t&&t.pipe))throw new TypeError("Parameter `response` must be a response stream.");let e=new gtt;return dtt(t,e),t.pipe(e)};ufe.exports=mtt});var Afe=_(pH=>{pH.stringify=function t(e){if(typeof e>"u")return e;if(e&&Buffer.isBuffer(e))return JSON.stringify(":base64:"+e.toString("base64"));if(e&&e.toJSON&&(e=e.toJSON()),e&&typeof e=="object"){var r="",s=Array.isArray(e);r=s?"[":"{";var a=!0;for(var n in e){var c=typeof e[n]=="function"||!s&&typeof e[n]>"u";Object.hasOwnProperty.call(e,n)&&!c&&(a||(r+=","),a=!1,s?e[n]==null?r+="null":r+=t(e[n]):e[n]!==void 0&&(r+=t(n)+":"+t(e[n])))}return r+=s?"]":"}",r}else return typeof e=="string"?JSON.stringify(/^:/.test(e)?":"+e:e):typeof e>"u"?"null":JSON.stringify(e)};pH.parse=function(t){return JSON.parse(t,function(e,r){return typeof r=="string"?/^:base64:/.test(r)?Buffer.from(r.substring(8),"base64"):/^:/.test(r)?r.substring(1):r:r})}});var dfe=_((LMt,gfe)=>{"use strict";var ytt=Ie("events"),pfe=Afe(),Ett=t=>{let e={redis:"@keyv/redis",rediss:"@keyv/redis",mongodb:"@keyv/mongo",mongo:"@keyv/mongo",sqlite:"@keyv/sqlite",postgresql:"@keyv/postgres",postgres:"@keyv/postgres",mysql:"@keyv/mysql",etcd:"@keyv/etcd",offline:"@keyv/offline",tiered:"@keyv/tiered"};if(t.adapter||t.uri){let r=t.adapter||/^[^:+]*/.exec(t.uri)[0];return new(Ie(e[r]))(t)}return new Map},hfe=["sqlite","postgres","mysql","mongo","redis","tiered"],hH=class extends ytt{constructor(e,{emitErrors:r=!0,...s}={}){if(super(),this.opts={namespace:"keyv",serialize:pfe.stringify,deserialize:pfe.parse,...typeof e=="string"?{uri:e}:e,...s},!this.opts.store){let n={...this.opts};this.opts.store=Ett(n)}if(this.opts.compression){let n=this.opts.compression;this.opts.serialize=n.serialize.bind(n),this.opts.deserialize=n.deserialize.bind(n)}typeof this.opts.store.on=="function"&&r&&this.opts.store.on("error",n=>this.emit("error",n)),this.opts.store.namespace=this.opts.namespace;let a=n=>async function*(){for await(let[c,f]of typeof n=="function"?n(this.opts.store.namespace):n){let p=await this.opts.deserialize(f);if(!(this.opts.store.namespace&&!c.includes(this.opts.store.namespace))){if(typeof p.expires=="number"&&Date.now()>p.expires){this.delete(c);continue}yield[this._getKeyUnprefix(c),p.value]}}};typeof this.opts.store[Symbol.iterator]=="function"&&this.opts.store instanceof Map?this.iterator=a(this.opts.store):typeof this.opts.store.iterator=="function"&&this.opts.store.opts&&this._checkIterableAdaptar()&&(this.iterator=a(this.opts.store.iterator.bind(this.opts.store)))}_checkIterableAdaptar(){return hfe.includes(this.opts.store.opts.dialect)||hfe.findIndex(e=>this.opts.store.opts.url.includes(e))>=0}_getKeyPrefix(e){return`${this.opts.namespace}:${e}`}_getKeyPrefixArray(e){return e.map(r=>`${this.opts.namespace}:${r}`)}_getKeyUnprefix(e){return e.split(":").splice(1).join(":")}get(e,r){let{store:s}=this.opts,a=Array.isArray(e),n=a?this._getKeyPrefixArray(e):this._getKeyPrefix(e);if(a&&s.getMany===void 0){let c=[];for(let f of n)c.push(Promise.resolve().then(()=>s.get(f)).then(p=>typeof p=="string"?this.opts.deserialize(p):this.opts.compression?this.opts.deserialize(p):p).then(p=>{if(p!=null)return typeof p.expires=="number"&&Date.now()>p.expires?this.delete(f).then(()=>{}):r&&r.raw?p:p.value}));return Promise.allSettled(c).then(f=>{let p=[];for(let h of f)p.push(h.value);return p})}return Promise.resolve().then(()=>a?s.getMany(n):s.get(n)).then(c=>typeof c=="string"?this.opts.deserialize(c):this.opts.compression?this.opts.deserialize(c):c).then(c=>{if(c!=null)return a?c.map((f,p)=>{if(typeof f=="string"&&(f=this.opts.deserialize(f)),f!=null){if(typeof f.expires=="number"&&Date.now()>f.expires){this.delete(e[p]).then(()=>{});return}return r&&r.raw?f:f.value}}):typeof c.expires=="number"&&Date.now()>c.expires?this.delete(e).then(()=>{}):r&&r.raw?c:c.value})}set(e,r,s){let a=this._getKeyPrefix(e);typeof s>"u"&&(s=this.opts.ttl),s===0&&(s=void 0);let{store:n}=this.opts;return Promise.resolve().then(()=>{let c=typeof s=="number"?Date.now()+s:null;return typeof r=="symbol"&&this.emit("error","symbol cannot be serialized"),r={value:r,expires:c},this.opts.serialize(r)}).then(c=>n.set(a,c,s)).then(()=>!0)}delete(e){let{store:r}=this.opts;if(Array.isArray(e)){let a=this._getKeyPrefixArray(e);if(r.deleteMany===void 0){let n=[];for(let c of a)n.push(r.delete(c));return Promise.allSettled(n).then(c=>c.every(f=>f.value===!0))}return Promise.resolve().then(()=>r.deleteMany(a))}let s=this._getKeyPrefix(e);return Promise.resolve().then(()=>r.delete(s))}clear(){let{store:e}=this.opts;return Promise.resolve().then(()=>e.clear())}has(e){let r=this._getKeyPrefix(e),{store:s}=this.opts;return Promise.resolve().then(async()=>typeof s.has=="function"?s.has(r):await s.get(r)!==void 0)}disconnect(){let{store:e}=this.opts;if(typeof e.disconnect=="function")return e.disconnect()}};gfe.exports=hH});var Efe=_((UMt,yfe)=>{"use strict";var Itt=Ie("events"),VQ=Ie("url"),Ctt=Gue(),wtt=rfe(),gH=ife(),mfe=afe(),Btt=YQ(),vtt=ffe(),Stt=dfe(),KB=class t{constructor(e,r){if(typeof e!="function")throw new TypeError("Parameter `request` must be a function");return this.cache=new Stt({uri:typeof r=="string"&&r,store:typeof r!="string"&&r,namespace:"cacheable-request"}),this.createCacheableRequest(e)}createCacheableRequest(e){return(r,s)=>{let a;if(typeof r=="string")a=dH(VQ.parse(r)),r={};else if(r instanceof VQ.URL)a=dH(VQ.parse(r.toString())),r={};else{let[C,...S]=(r.path||"").split("?"),P=S.length>0?`?${S.join("?")}`:"";a=dH({...r,pathname:C,search:P})}r={headers:{},method:"GET",cache:!0,strictTtl:!1,automaticFailover:!1,...r,...Dtt(a)},r.headers=Btt(r.headers);let n=new Itt,c=Ctt(VQ.format(a),{stripWWW:!1,removeTrailingSlash:!1,stripAuthentication:!1}),f=`${r.method}:${c}`,p=!1,h=!1,E=C=>{h=!0;let S=!1,P,I=new Promise(N=>{P=()=>{S||(S=!0,N())}}),R=N=>{if(p&&!C.forceRefresh){N.status=N.statusCode;let W=gH.fromObject(p.cachePolicy).revalidatedPolicy(C,N);if(!W.modified){let ee=W.policy.responseHeaders();N=new mfe(p.statusCode,ee,p.body,p.url),N.cachePolicy=W.policy,N.fromCache=!0}}N.fromCache||(N.cachePolicy=new gH(C,N,C),N.fromCache=!1);let U;C.cache&&N.cachePolicy.storable()?(U=vtt(N),(async()=>{try{let W=wtt.buffer(N);if(await Promise.race([I,new Promise(le=>N.once("end",le))]),S)return;let ee=await W,ie={cachePolicy:N.cachePolicy.toObject(),url:N.url,statusCode:N.fromCache?p.statusCode:N.statusCode,body:ee},ue=C.strictTtl?N.cachePolicy.timeToLive():void 0;C.maxTtl&&(ue=ue?Math.min(ue,C.maxTtl):C.maxTtl),await this.cache.set(f,ie,ue)}catch(W){n.emit("error",new t.CacheError(W))}})()):C.cache&&p&&(async()=>{try{await this.cache.delete(f)}catch(W){n.emit("error",new t.CacheError(W))}})(),n.emit("response",U||N),typeof s=="function"&&s(U||N)};try{let N=e(C,R);N.once("error",P),N.once("abort",P),n.emit("request",N)}catch(N){n.emit("error",new t.RequestError(N))}};return(async()=>{let C=async P=>{await Promise.resolve();let I=P.cache?await this.cache.get(f):void 0;if(typeof I>"u")return E(P);let R=gH.fromObject(I.cachePolicy);if(R.satisfiesWithoutRevalidation(P)&&!P.forceRefresh){let N=R.responseHeaders(),U=new mfe(I.statusCode,N,I.body,I.url);U.cachePolicy=R,U.fromCache=!0,n.emit("response",U),typeof s=="function"&&s(U)}else p=I,P.headers=R.revalidationHeaders(P),E(P)},S=P=>n.emit("error",new t.CacheError(P));this.cache.once("error",S),n.on("response",()=>this.cache.removeListener("error",S));try{await C(r)}catch(P){r.automaticFailover&&!h&&E(r),n.emit("error",new t.CacheError(P))}})(),n}}};function Dtt(t){let e={...t};return e.path=`${t.pathname||"/"}${t.search||""}`,delete e.pathname,delete e.search,e}function dH(t){return{protocol:t.protocol,auth:t.auth,hostname:t.hostname||t.host||"localhost",port:t.port,pathname:t.pathname,search:t.search}}KB.RequestError=class extends Error{constructor(t){super(t.message),this.name="RequestError",Object.assign(this,t)}};KB.CacheError=class extends Error{constructor(t){super(t.message),this.name="CacheError",Object.assign(this,t)}};yfe.exports=KB});var Cfe=_((jMt,Ife)=>{"use strict";var btt=["aborted","complete","headers","httpVersion","httpVersionMinor","httpVersionMajor","method","rawHeaders","rawTrailers","setTimeout","socket","statusCode","statusMessage","trailers","url"];Ife.exports=(t,e)=>{if(e._readableState.autoDestroy)throw new Error("The second stream must have the `autoDestroy` option set to `false`");let r=new Set(Object.keys(t).concat(btt)),s={};for(let a of r)a in e||(s[a]={get(){let n=t[a];return typeof n=="function"?n.bind(t):n},set(n){t[a]=n},enumerable:!0,configurable:!1});return Object.defineProperties(e,s),t.once("aborted",()=>{e.destroy(),e.emit("aborted")}),t.once("close",()=>{t.complete&&e.readable?e.once("end",()=>{e.emit("close")}):e.emit("close")}),e}});var Bfe=_((GMt,wfe)=>{"use strict";var{Transform:Ptt,PassThrough:xtt}=Ie("stream"),mH=Ie("zlib"),ktt=Cfe();wfe.exports=t=>{let e=(t.headers["content-encoding"]||"").toLowerCase();if(!["gzip","deflate","br"].includes(e))return t;let r=e==="br";if(r&&typeof mH.createBrotliDecompress!="function")return t.destroy(new Error("Brotli is not supported on Node.js < 12")),t;let s=!0,a=new Ptt({transform(f,p,h){s=!1,h(null,f)},flush(f){f()}}),n=new xtt({autoDestroy:!1,destroy(f,p){t.destroy(),p(f)}}),c=r?mH.createBrotliDecompress():mH.createUnzip();return c.once("error",f=>{if(s&&!t.readable){n.end();return}n.destroy(f)}),ktt(t,n),t.pipe(a).pipe(c).pipe(n),n}});var EH=_((qMt,vfe)=>{"use strict";var yH=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");this.maxSize=e.maxSize,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_set(e,r){if(this.cache.set(e,r),this._size++,this._size>=this.maxSize){if(this._size=0,typeof this.onEviction=="function")for(let[s,a]of this.oldCache.entries())this.onEviction(s,a);this.oldCache=this.cache,this.cache=new Map}}get(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e)){let r=this.oldCache.get(e);return this.oldCache.delete(e),this._set(e,r),r}}set(e,r){return this.cache.has(e)?this.cache.set(e,r):this._set(e,r),this}has(e){return this.cache.has(e)||this.oldCache.has(e)}peek(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e))return this.oldCache.get(e)}delete(e){let r=this.cache.delete(e);return r&&this._size--,this.oldCache.delete(e)||r}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache)yield e;for(let e of this.oldCache){let[r]=e;this.cache.has(r)||(yield e)}}get size(){let e=0;for(let r of this.oldCache.keys())this.cache.has(r)||e++;return Math.min(this._size+e,this.maxSize)}};vfe.exports=yH});var CH=_((WMt,Pfe)=>{"use strict";var Qtt=Ie("events"),Ttt=Ie("tls"),Rtt=Ie("http2"),Ftt=EH(),Pa=Symbol("currentStreamsCount"),Sfe=Symbol("request"),Rc=Symbol("cachedOriginSet"),gI=Symbol("gracefullyClosing"),Ntt=["maxDeflateDynamicTableSize","maxSessionMemory","maxHeaderListPairs","maxOutstandingPings","maxReservedRemoteStreams","maxSendHeaderBlockLength","paddingStrategy","localAddress","path","rejectUnauthorized","minDHSize","ca","cert","clientCertEngine","ciphers","key","pfx","servername","minVersion","maxVersion","secureProtocol","crl","honorCipherOrder","ecdhCurve","dhparam","secureOptions","sessionIdContext"],Ott=(t,e,r)=>{let s=0,a=t.length;for(;s>>1;r(t[n],e)?s=n+1:a=n}return s},Ltt=(t,e)=>t.remoteSettings.maxConcurrentStreams>e.remoteSettings.maxConcurrentStreams,IH=(t,e)=>{for(let r of t)r[Rc].lengthe[Rc].includes(s))&&r[Pa]+e[Pa]<=e.remoteSettings.maxConcurrentStreams&&bfe(r)},Mtt=(t,e)=>{for(let r of t)e[Rc].lengthr[Rc].includes(s))&&e[Pa]+r[Pa]<=r.remoteSettings.maxConcurrentStreams&&bfe(e)},Dfe=({agent:t,isFree:e})=>{let r={};for(let s in t.sessions){let n=t.sessions[s].filter(c=>{let f=c[im.kCurrentStreamsCount]{t[gI]=!0,t[Pa]===0&&t.close()},im=class t extends Qtt{constructor({timeout:e=6e4,maxSessions:r=1/0,maxFreeSessions:s=10,maxCachedTlsSessions:a=100}={}){super(),this.sessions={},this.queue={},this.timeout=e,this.maxSessions=r,this.maxFreeSessions=s,this._freeSessionsCount=0,this._sessionsCount=0,this.settings={enablePush:!1},this.tlsSessionCache=new Ftt({maxSize:a})}static normalizeOrigin(e,r){return typeof e=="string"&&(e=new URL(e)),r&&e.hostname!==r&&(e.hostname=r),e.origin}normalizeOptions(e){let r="";if(e)for(let s of Ntt)e[s]&&(r+=`:${e[s]}`);return r}_tryToCreateNewSession(e,r){if(!(e in this.queue)||!(r in this.queue[e]))return;let s=this.queue[e][r];this._sessionsCount{Array.isArray(s)?(s=[...s],a()):s=[{resolve:a,reject:n}];let c=this.normalizeOptions(r),f=t.normalizeOrigin(e,r&&r.servername);if(f===void 0){for(let{reject:E}of s)E(new TypeError("The `origin` argument needs to be a string or an URL object"));return}if(c in this.sessions){let E=this.sessions[c],C=-1,S=-1,P;for(let I of E){let R=I.remoteSettings.maxConcurrentStreams;if(R=R||I[gI]||I.destroyed)continue;P||(C=R),N>S&&(P=I,S=N)}}if(P){if(s.length!==1){for(let{reject:I}of s){let R=new Error(`Expected the length of listeners to be 1, got ${s.length}. +Please report this to https://github.com/szmarczak/http2-wrapper/`);I(R)}return}s[0].resolve(P);return}}if(c in this.queue){if(f in this.queue[c]){this.queue[c][f].listeners.push(...s),this._tryToCreateNewSession(c,f);return}}else this.queue[c]={};let p=()=>{c in this.queue&&this.queue[c][f]===h&&(delete this.queue[c][f],Object.keys(this.queue[c]).length===0&&delete this.queue[c])},h=()=>{let E=`${f}:${c}`,C=!1;try{let S=Rtt.connect(e,{createConnection:this.createConnection,settings:this.settings,session:this.tlsSessionCache.get(E),...r});S[Pa]=0,S[gI]=!1;let P=()=>S[Pa]{this.tlsSessionCache.set(E,N)}),S.once("error",N=>{for(let{reject:U}of s)U(N);this.tlsSessionCache.delete(E)}),S.setTimeout(this.timeout,()=>{S.destroy()}),S.once("close",()=>{if(C){I&&this._freeSessionsCount--,this._sessionsCount--;let N=this.sessions[c];N.splice(N.indexOf(S),1),N.length===0&&delete this.sessions[c]}else{let N=new Error("Session closed without receiving a SETTINGS frame");N.code="HTTP2WRAPPER_NOSETTINGS";for(let{reject:U}of s)U(N);p()}this._tryToCreateNewSession(c,f)});let R=()=>{if(!(!(c in this.queue)||!P())){for(let N of S[Rc])if(N in this.queue[c]){let{listeners:U}=this.queue[c][N];for(;U.length!==0&&P();)U.shift().resolve(S);let W=this.queue[c];if(W[N].listeners.length===0&&(delete W[N],Object.keys(W).length===0)){delete this.queue[c];break}if(!P())break}}};S.on("origin",()=>{S[Rc]=S.originSet,P()&&(R(),IH(this.sessions[c],S))}),S.once("remoteSettings",()=>{if(S.ref(),S.unref(),this._sessionsCount++,h.destroyed){let N=new Error("Agent has been destroyed");for(let U of s)U.reject(N);S.destroy();return}S[Rc]=S.originSet;{let N=this.sessions;if(c in N){let U=N[c];U.splice(Ott(U,S,Ltt),0,S)}else N[c]=[S]}this._freeSessionsCount+=1,C=!0,this.emit("session",S),R(),p(),S[Pa]===0&&this._freeSessionsCount>this.maxFreeSessions&&S.close(),s.length!==0&&(this.getSession(f,r,s),s.length=0),S.on("remoteSettings",()=>{R(),IH(this.sessions[c],S)})}),S[Sfe]=S.request,S.request=(N,U)=>{if(S[gI])throw new Error("The session is gracefully closing. No new streams are allowed.");let W=S[Sfe](N,U);return S.ref(),++S[Pa],S[Pa]===S.remoteSettings.maxConcurrentStreams&&this._freeSessionsCount--,W.once("close",()=>{if(I=P(),--S[Pa],!S.destroyed&&!S.closed&&(Mtt(this.sessions[c],S),P()&&!S.closed)){I||(this._freeSessionsCount++,I=!0);let ee=S[Pa]===0;ee&&S.unref(),ee&&(this._freeSessionsCount>this.maxFreeSessions||S[gI])?S.close():(IH(this.sessions[c],S),R())}}),W}}catch(S){for(let P of s)P.reject(S);p()}};h.listeners=s,h.completed=!1,h.destroyed=!1,this.queue[c][f]=h,this._tryToCreateNewSession(c,f)})}request(e,r,s,a){return new Promise((n,c)=>{this.getSession(e,r,[{reject:c,resolve:f=>{try{n(f.request(s,a))}catch(p){c(p)}}}])})}createConnection(e,r){return t.connect(e,r)}static connect(e,r){r.ALPNProtocols=["h2"];let s=e.port||443,a=e.hostname||e.host;return typeof r.servername>"u"&&(r.servername=a),Ttt.connect(s,a,r)}closeFreeSessions(){for(let e of Object.values(this.sessions))for(let r of e)r[Pa]===0&&r.close()}destroy(e){for(let r of Object.values(this.sessions))for(let s of r)s.destroy(e);for(let r of Object.values(this.queue))for(let s of Object.values(r))s.destroyed=!0;this.queue={}}get freeSessions(){return Dfe({agent:this,isFree:!0})}get busySessions(){return Dfe({agent:this,isFree:!1})}};im.kCurrentStreamsCount=Pa;im.kGracefullyClosing=gI;Pfe.exports={Agent:im,globalAgent:new im}});var BH=_((YMt,xfe)=>{"use strict";var{Readable:Utt}=Ie("stream"),wH=class extends Utt{constructor(e,r){super({highWaterMark:r,autoDestroy:!1}),this.statusCode=null,this.statusMessage="",this.httpVersion="2.0",this.httpVersionMajor=2,this.httpVersionMinor=0,this.headers={},this.trailers={},this.req=null,this.aborted=!1,this.complete=!1,this.upgrade=null,this.rawHeaders=[],this.rawTrailers=[],this.socket=e,this.connection=e,this._dumped=!1}_destroy(e){this.req._request.destroy(e)}setTimeout(e,r){return this.req.setTimeout(e,r),this}_dump(){this._dumped||(this._dumped=!0,this.removeAllListeners("data"),this.resume())}_read(){this.req&&this.req._request.resume()}};xfe.exports=wH});var vH=_((VMt,kfe)=>{"use strict";kfe.exports=t=>{let e={protocol:t.protocol,hostname:typeof t.hostname=="string"&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return typeof t.port=="string"&&t.port.length!==0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var Tfe=_((JMt,Qfe)=>{"use strict";Qfe.exports=(t,e,r)=>{for(let s of r)t.on(s,(...a)=>e.emit(s,...a))}});var Ffe=_((KMt,Rfe)=>{"use strict";Rfe.exports=t=>{switch(t){case":method":case":scheme":case":authority":case":path":return!0;default:return!1}}});var Ofe=_((XMt,Nfe)=>{"use strict";var dI=(t,e,r)=>{Nfe.exports[e]=class extends t{constructor(...a){super(typeof r=="string"?r:r(a)),this.name=`${super.name} [${e}]`,this.code=e}}};dI(TypeError,"ERR_INVALID_ARG_TYPE",t=>{let e=t[0].includes(".")?"property":"argument",r=t[1],s=Array.isArray(r);return s&&(r=`${r.slice(0,-1).join(", ")} or ${r.slice(-1)}`),`The "${t[0]}" ${e} must be ${s?"one of":"of"} type ${r}. Received ${typeof t[2]}`});dI(TypeError,"ERR_INVALID_PROTOCOL",t=>`Protocol "${t[0]}" not supported. Expected "${t[1]}"`);dI(Error,"ERR_HTTP_HEADERS_SENT",t=>`Cannot ${t[0]} headers after they are sent to the client`);dI(TypeError,"ERR_INVALID_HTTP_TOKEN",t=>`${t[0]} must be a valid HTTP token [${t[1]}]`);dI(TypeError,"ERR_HTTP_INVALID_HEADER_VALUE",t=>`Invalid value "${t[0]} for header "${t[1]}"`);dI(TypeError,"ERR_INVALID_CHAR",t=>`Invalid character in ${t[0]} [${t[1]}]`)});var xH=_((ZMt,Gfe)=>{"use strict";var _tt=Ie("http2"),{Writable:Htt}=Ie("stream"),{Agent:Lfe,globalAgent:jtt}=CH(),Gtt=BH(),qtt=vH(),Wtt=Tfe(),Ytt=Ffe(),{ERR_INVALID_ARG_TYPE:SH,ERR_INVALID_PROTOCOL:Vtt,ERR_HTTP_HEADERS_SENT:Mfe,ERR_INVALID_HTTP_TOKEN:Jtt,ERR_HTTP_INVALID_HEADER_VALUE:Ktt,ERR_INVALID_CHAR:ztt}=Ofe(),{HTTP2_HEADER_STATUS:Ufe,HTTP2_HEADER_METHOD:_fe,HTTP2_HEADER_PATH:Hfe,HTTP2_METHOD_CONNECT:Xtt}=_tt.constants,Jo=Symbol("headers"),DH=Symbol("origin"),bH=Symbol("session"),jfe=Symbol("options"),JQ=Symbol("flushedHeaders"),zB=Symbol("jobs"),Ztt=/^[\^`\-\w!#$%&*+.|~]+$/,$tt=/[^\t\u0020-\u007E\u0080-\u00FF]/,PH=class extends Htt{constructor(e,r,s){super({autoDestroy:!1});let a=typeof e=="string"||e instanceof URL;if(a&&(e=qtt(e instanceof URL?e:new URL(e))),typeof r=="function"||r===void 0?(s=r,r=a?e:{...e}):r={...e,...r},r.h2session)this[bH]=r.h2session;else if(r.agent===!1)this.agent=new Lfe({maxFreeSessions:0});else if(typeof r.agent>"u"||r.agent===null)typeof r.createConnection=="function"?(this.agent=new Lfe({maxFreeSessions:0}),this.agent.createConnection=r.createConnection):this.agent=jtt;else if(typeof r.agent.request=="function")this.agent=r.agent;else throw new SH("options.agent",["Agent-like Object","undefined","false"],r.agent);if(r.protocol&&r.protocol!=="https:")throw new Vtt(r.protocol,"https:");let n=r.port||r.defaultPort||this.agent&&this.agent.defaultPort||443,c=r.hostname||r.host||"localhost";delete r.hostname,delete r.host,delete r.port;let{timeout:f}=r;if(r.timeout=void 0,this[Jo]=Object.create(null),this[zB]=[],this.socket=null,this.connection=null,this.method=r.method||"GET",this.path=r.path,this.res=null,this.aborted=!1,this.reusedSocket=!1,r.headers)for(let[p,h]of Object.entries(r.headers))this.setHeader(p,h);r.auth&&!("authorization"in this[Jo])&&(this[Jo].authorization="Basic "+Buffer.from(r.auth).toString("base64")),r.session=r.tlsSession,r.path=r.socketPath,this[jfe]=r,n===443?(this[DH]=`https://${c}`,":authority"in this[Jo]||(this[Jo][":authority"]=c)):(this[DH]=`https://${c}:${n}`,":authority"in this[Jo]||(this[Jo][":authority"]=`${c}:${n}`)),f&&this.setTimeout(f),s&&this.once("response",s),this[JQ]=!1}get method(){return this[Jo][_fe]}set method(e){e&&(this[Jo][_fe]=e.toUpperCase())}get path(){return this[Jo][Hfe]}set path(e){e&&(this[Jo][Hfe]=e)}get _mustNotHaveABody(){return this.method==="GET"||this.method==="HEAD"||this.method==="DELETE"}_write(e,r,s){if(this._mustNotHaveABody){s(new Error("The GET, HEAD and DELETE methods must NOT have a body"));return}this.flushHeaders();let a=()=>this._request.write(e,r,s);this._request?a():this[zB].push(a)}_final(e){if(this.destroyed)return;this.flushHeaders();let r=()=>{if(this._mustNotHaveABody){e();return}this._request.end(e)};this._request?r():this[zB].push(r)}abort(){this.res&&this.res.complete||(this.aborted||process.nextTick(()=>this.emit("abort")),this.aborted=!0,this.destroy())}_destroy(e,r){this.res&&this.res._dump(),this._request&&this._request.destroy(),r(e)}async flushHeaders(){if(this[JQ]||this.destroyed)return;this[JQ]=!0;let e=this.method===Xtt,r=s=>{if(this._request=s,this.destroyed){s.destroy();return}e||Wtt(s,this,["timeout","continue","close","error"]);let a=c=>(...f)=>{!this.writable&&!this.destroyed?c(...f):this.once("finish",()=>{c(...f)})};s.once("response",a((c,f,p)=>{let h=new Gtt(this.socket,s.readableHighWaterMark);this.res=h,h.req=this,h.statusCode=c[Ufe],h.headers=c,h.rawHeaders=p,h.once("end",()=>{this.aborted?(h.aborted=!0,h.emit("aborted")):(h.complete=!0,h.socket=null,h.connection=null)}),e?(h.upgrade=!0,this.emit("connect",h,s,Buffer.alloc(0))?this.emit("close"):s.destroy()):(s.on("data",E=>{!h._dumped&&!h.push(E)&&s.pause()}),s.once("end",()=>{h.push(null)}),this.emit("response",h)||h._dump())})),s.once("headers",a(c=>this.emit("information",{statusCode:c[Ufe]}))),s.once("trailers",a((c,f,p)=>{let{res:h}=this;h.trailers=c,h.rawTrailers=p}));let{socket:n}=s.session;this.socket=n,this.connection=n;for(let c of this[zB])c();this.emit("socket",this.socket)};if(this[bH])try{r(this[bH].request(this[Jo]))}catch(s){this.emit("error",s)}else{this.reusedSocket=!0;try{r(await this.agent.request(this[DH],this[jfe],this[Jo]))}catch(s){this.emit("error",s)}}}getHeader(e){if(typeof e!="string")throw new SH("name","string",e);return this[Jo][e.toLowerCase()]}get headersSent(){return this[JQ]}removeHeader(e){if(typeof e!="string")throw new SH("name","string",e);if(this.headersSent)throw new Mfe("remove");delete this[Jo][e.toLowerCase()]}setHeader(e,r){if(this.headersSent)throw new Mfe("set");if(typeof e!="string"||!Ztt.test(e)&&!Ytt(e))throw new Jtt("Header name",e);if(typeof r>"u")throw new Ktt(r,e);if($tt.test(r))throw new ztt("header content",e);this[Jo][e.toLowerCase()]=r}setNoDelay(){}setSocketKeepAlive(){}setTimeout(e,r){let s=()=>this._request.setTimeout(e,r);return this._request?s():this[zB].push(s),this}get maxHeadersCount(){if(!this.destroyed&&this._request)return this._request.session.localSettings.maxHeaderListSize}set maxHeadersCount(e){}};Gfe.exports=PH});var Wfe=_(($Mt,qfe)=>{"use strict";var ert=Ie("tls");qfe.exports=(t={},e=ert.connect)=>new Promise((r,s)=>{let a=!1,n,c=async()=>{await p,n.off("timeout",f),n.off("error",s),t.resolveSocket?(r({alpnProtocol:n.alpnProtocol,socket:n,timeout:a}),a&&(await Promise.resolve(),n.emit("timeout"))):(n.destroy(),r({alpnProtocol:n.alpnProtocol,timeout:a}))},f=async()=>{a=!0,c()},p=(async()=>{try{n=await e(t,c),n.on("error",s),n.once("timeout",f)}catch(h){s(h)}})()})});var Vfe=_((eUt,Yfe)=>{"use strict";var trt=Ie("net");Yfe.exports=t=>{let e=t.host,r=t.headers&&t.headers.host;return r&&(r.startsWith("[")?r.indexOf("]")===-1?e=r:e=r.slice(1,-1):e=r.split(":",1)[0]),trt.isIP(e)?"":e}});var zfe=_((tUt,QH)=>{"use strict";var Jfe=Ie("http"),kH=Ie("https"),rrt=Wfe(),nrt=EH(),irt=xH(),srt=Vfe(),ort=vH(),KQ=new nrt({maxSize:100}),XB=new Map,Kfe=(t,e,r)=>{e._httpMessage={shouldKeepAlive:!0};let s=()=>{t.emit("free",e,r)};e.on("free",s);let a=()=>{t.removeSocket(e,r)};e.on("close",a);let n=()=>{t.removeSocket(e,r),e.off("close",a),e.off("free",s),e.off("agentRemove",n)};e.on("agentRemove",n),t.emit("free",e,r)},art=async t=>{let e=`${t.host}:${t.port}:${t.ALPNProtocols.sort()}`;if(!KQ.has(e)){if(XB.has(e))return(await XB.get(e)).alpnProtocol;let{path:r,agent:s}=t;t.path=t.socketPath;let a=rrt(t);XB.set(e,a);try{let{socket:n,alpnProtocol:c}=await a;if(KQ.set(e,c),t.path=r,c==="h2")n.destroy();else{let{globalAgent:f}=kH,p=kH.Agent.prototype.createConnection;s?s.createConnection===p?Kfe(s,n,t):n.destroy():f.createConnection===p?Kfe(f,n,t):n.destroy()}return XB.delete(e),c}catch(n){throw XB.delete(e),n}}return KQ.get(e)};QH.exports=async(t,e,r)=>{if((typeof t=="string"||t instanceof URL)&&(t=ort(new URL(t))),typeof e=="function"&&(r=e,e=void 0),e={ALPNProtocols:["h2","http/1.1"],...t,...e,resolveSocket:!0},!Array.isArray(e.ALPNProtocols)||e.ALPNProtocols.length===0)throw new Error("The `ALPNProtocols` option must be an Array with at least one entry");e.protocol=e.protocol||"https:";let s=e.protocol==="https:";e.host=e.hostname||e.host||"localhost",e.session=e.tlsSession,e.servername=e.servername||srt(e),e.port=e.port||(s?443:80),e._defaultAgent=s?kH.globalAgent:Jfe.globalAgent;let a=e.agent;if(a){if(a.addRequest)throw new Error("The `options.agent` object can contain only `http`, `https` or `http2` properties");e.agent=a[s?"https":"http"]}return s&&await art(e)==="h2"?(a&&(e.agent=a.http2),new irt(e,r)):Jfe.request(e,r)};QH.exports.protocolCache=KQ});var Zfe=_((rUt,Xfe)=>{"use strict";var lrt=Ie("http2"),crt=CH(),TH=xH(),urt=BH(),frt=zfe(),Art=(t,e,r)=>new TH(t,e,r),prt=(t,e,r)=>{let s=new TH(t,e,r);return s.end(),s};Xfe.exports={...lrt,ClientRequest:TH,IncomingMessage:urt,...crt,request:Art,get:prt,auto:frt}});var FH=_(RH=>{"use strict";Object.defineProperty(RH,"__esModule",{value:!0});var $fe=Np();RH.default=t=>$fe.default.nodeStream(t)&&$fe.default.function_(t.getBoundary)});var nAe=_(NH=>{"use strict";Object.defineProperty(NH,"__esModule",{value:!0});var tAe=Ie("fs"),rAe=Ie("util"),eAe=Np(),hrt=FH(),grt=rAe.promisify(tAe.stat);NH.default=async(t,e)=>{if(e&&"content-length"in e)return Number(e["content-length"]);if(!t)return 0;if(eAe.default.string(t))return Buffer.byteLength(t);if(eAe.default.buffer(t))return t.length;if(hrt.default(t))return rAe.promisify(t.getLength.bind(t))();if(t instanceof tAe.ReadStream){let{size:r}=await grt(t.path);return r===0?void 0:r}}});var LH=_(OH=>{"use strict";Object.defineProperty(OH,"__esModule",{value:!0});function drt(t,e,r){let s={};for(let a of r)s[a]=(...n)=>{e.emit(a,...n)},t.on(a,s[a]);return()=>{for(let a of r)t.off(a,s[a])}}OH.default=drt});var iAe=_(MH=>{"use strict";Object.defineProperty(MH,"__esModule",{value:!0});MH.default=()=>{let t=[];return{once(e,r,s){e.once(r,s),t.push({origin:e,event:r,fn:s})},unhandleAll(){for(let e of t){let{origin:r,event:s,fn:a}=e;r.removeListener(s,a)}t.length=0}}}});var oAe=_(ZB=>{"use strict";Object.defineProperty(ZB,"__esModule",{value:!0});ZB.TimeoutError=void 0;var mrt=Ie("net"),yrt=iAe(),sAe=Symbol("reentry"),Ert=()=>{},zQ=class extends Error{constructor(e,r){super(`Timeout awaiting '${r}' for ${e}ms`),this.event=r,this.name="TimeoutError",this.code="ETIMEDOUT"}};ZB.TimeoutError=zQ;ZB.default=(t,e,r)=>{if(sAe in t)return Ert;t[sAe]=!0;let s=[],{once:a,unhandleAll:n}=yrt.default(),c=(C,S,P)=>{var I;let R=setTimeout(S,C,C,P);(I=R.unref)===null||I===void 0||I.call(R);let N=()=>{clearTimeout(R)};return s.push(N),N},{host:f,hostname:p}=r,h=(C,S)=>{t.destroy(new zQ(C,S))},E=()=>{for(let C of s)C();n()};if(t.once("error",C=>{if(E(),t.listenerCount("error")===0)throw C}),t.once("close",E),a(t,"response",C=>{a(C,"end",E)}),typeof e.request<"u"&&c(e.request,h,"request"),typeof e.socket<"u"){let C=()=>{h(e.socket,"socket")};t.setTimeout(e.socket,C),s.push(()=>{t.removeListener("timeout",C)})}return a(t,"socket",C=>{var S;let{socketPath:P}=t;if(C.connecting){let I=!!(P??mrt.isIP((S=p??f)!==null&&S!==void 0?S:"")!==0);if(typeof e.lookup<"u"&&!I&&typeof C.address().address>"u"){let R=c(e.lookup,h,"lookup");a(C,"lookup",R)}if(typeof e.connect<"u"){let R=()=>c(e.connect,h,"connect");I?a(C,"connect",R()):a(C,"lookup",N=>{N===null&&a(C,"connect",R())})}typeof e.secureConnect<"u"&&r.protocol==="https:"&&a(C,"connect",()=>{let R=c(e.secureConnect,h,"secureConnect");a(C,"secureConnect",R)})}if(typeof e.send<"u"){let I=()=>c(e.send,h,"send");C.connecting?a(C,"connect",()=>{a(t,"upload-complete",I())}):a(t,"upload-complete",I())}}),typeof e.response<"u"&&a(t,"upload-complete",()=>{let C=c(e.response,h,"response");a(t,"response",C)}),E}});var lAe=_(UH=>{"use strict";Object.defineProperty(UH,"__esModule",{value:!0});var aAe=Np();UH.default=t=>{t=t;let e={protocol:t.protocol,hostname:aAe.default.string(t.hostname)&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return aAe.default.string(t.port)&&t.port.length>0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var cAe=_(_H=>{"use strict";Object.defineProperty(_H,"__esModule",{value:!0});var Irt=Ie("url"),Crt=["protocol","host","hostname","port","pathname","search"];_H.default=(t,e)=>{var r,s;if(e.path){if(e.pathname)throw new TypeError("Parameters `path` and `pathname` are mutually exclusive.");if(e.search)throw new TypeError("Parameters `path` and `search` are mutually exclusive.");if(e.searchParams)throw new TypeError("Parameters `path` and `searchParams` are mutually exclusive.")}if(e.search&&e.searchParams)throw new TypeError("Parameters `search` and `searchParams` are mutually exclusive.");if(!t){if(!e.protocol)throw new TypeError("No URL protocol specified");t=`${e.protocol}//${(s=(r=e.hostname)!==null&&r!==void 0?r:e.host)!==null&&s!==void 0?s:""}`}let a=new Irt.URL(t);if(e.path){let n=e.path.indexOf("?");n===-1?e.pathname=e.path:(e.pathname=e.path.slice(0,n),e.search=e.path.slice(n+1)),delete e.path}for(let n of Crt)e[n]&&(a[n]=e[n].toString());return a}});var uAe=_(jH=>{"use strict";Object.defineProperty(jH,"__esModule",{value:!0});var HH=class{constructor(){this.weakMap=new WeakMap,this.map=new Map}set(e,r){typeof e=="object"?this.weakMap.set(e,r):this.map.set(e,r)}get(e){return typeof e=="object"?this.weakMap.get(e):this.map.get(e)}has(e){return typeof e=="object"?this.weakMap.has(e):this.map.has(e)}};jH.default=HH});var qH=_(GH=>{"use strict";Object.defineProperty(GH,"__esModule",{value:!0});var wrt=async t=>{let e=[],r=0;for await(let s of t)e.push(s),r+=Buffer.byteLength(s);return Buffer.isBuffer(e[0])?Buffer.concat(e,r):Buffer.from(e.join(""))};GH.default=wrt});var AAe=_(sm=>{"use strict";Object.defineProperty(sm,"__esModule",{value:!0});sm.dnsLookupIpVersionToFamily=sm.isDnsLookupIpVersion=void 0;var fAe={auto:0,ipv4:4,ipv6:6};sm.isDnsLookupIpVersion=t=>t in fAe;sm.dnsLookupIpVersionToFamily=t=>{if(sm.isDnsLookupIpVersion(t))return fAe[t];throw new Error("Invalid DNS lookup IP version")}});var WH=_(XQ=>{"use strict";Object.defineProperty(XQ,"__esModule",{value:!0});XQ.isResponseOk=void 0;XQ.isResponseOk=t=>{let{statusCode:e}=t,r=t.request.options.followRedirect?299:399;return e>=200&&e<=r||e===304}});var hAe=_(YH=>{"use strict";Object.defineProperty(YH,"__esModule",{value:!0});var pAe=new Set;YH.default=t=>{pAe.has(t)||(pAe.add(t),process.emitWarning(`Got: ${t}`,{type:"DeprecationWarning"}))}});var gAe=_(VH=>{"use strict";Object.defineProperty(VH,"__esModule",{value:!0});var Si=Np(),Brt=(t,e)=>{if(Si.default.null_(t.encoding))throw new TypeError("To get a Buffer, set `options.responseType` to `buffer` instead");Si.assert.any([Si.default.string,Si.default.undefined],t.encoding),Si.assert.any([Si.default.boolean,Si.default.undefined],t.resolveBodyOnly),Si.assert.any([Si.default.boolean,Si.default.undefined],t.methodRewriting),Si.assert.any([Si.default.boolean,Si.default.undefined],t.isStream),Si.assert.any([Si.default.string,Si.default.undefined],t.responseType),t.responseType===void 0&&(t.responseType="text");let{retry:r}=t;if(e?t.retry={...e.retry}:t.retry={calculateDelay:s=>s.computedValue,limit:0,methods:[],statusCodes:[],errorCodes:[],maxRetryAfter:void 0},Si.default.object(r)?(t.retry={...t.retry,...r},t.retry.methods=[...new Set(t.retry.methods.map(s=>s.toUpperCase()))],t.retry.statusCodes=[...new Set(t.retry.statusCodes)],t.retry.errorCodes=[...new Set(t.retry.errorCodes)]):Si.default.number(r)&&(t.retry.limit=r),Si.default.undefined(t.retry.maxRetryAfter)&&(t.retry.maxRetryAfter=Math.min(...[t.timeout.request,t.timeout.connect].filter(Si.default.number))),Si.default.object(t.pagination)){e&&(t.pagination={...e.pagination,...t.pagination});let{pagination:s}=t;if(!Si.default.function_(s.transform))throw new Error("`options.pagination.transform` must be implemented");if(!Si.default.function_(s.shouldContinue))throw new Error("`options.pagination.shouldContinue` must be implemented");if(!Si.default.function_(s.filter))throw new TypeError("`options.pagination.filter` must be implemented");if(!Si.default.function_(s.paginate))throw new Error("`options.pagination.paginate` must be implemented")}return t.responseType==="json"&&t.headers.accept===void 0&&(t.headers.accept="application/json"),t};VH.default=Brt});var dAe=_($B=>{"use strict";Object.defineProperty($B,"__esModule",{value:!0});$B.retryAfterStatusCodes=void 0;$B.retryAfterStatusCodes=new Set([413,429,503]);var vrt=({attemptCount:t,retryOptions:e,error:r,retryAfter:s})=>{if(t>e.limit)return 0;let a=e.methods.includes(r.options.method),n=e.errorCodes.includes(r.code),c=r.response&&e.statusCodes.includes(r.response.statusCode);if(!a||!n&&!c)return 0;if(r.response){if(s)return e.maxRetryAfter===void 0||s>e.maxRetryAfter?0:s;if(r.response.statusCode===413)return 0}let f=Math.random()*100;return 2**(t-1)*1e3+f};$B.default=vrt});var rv=_(Ln=>{"use strict";Object.defineProperty(Ln,"__esModule",{value:!0});Ln.UnsupportedProtocolError=Ln.ReadError=Ln.TimeoutError=Ln.UploadError=Ln.CacheError=Ln.HTTPError=Ln.MaxRedirectsError=Ln.RequestError=Ln.setNonEnumerableProperties=Ln.knownHookEvents=Ln.withoutBody=Ln.kIsNormalizedAlready=void 0;var mAe=Ie("util"),yAe=Ie("stream"),Srt=Ie("fs"),w0=Ie("url"),EAe=Ie("http"),JH=Ie("http"),Drt=Ie("https"),brt=Rue(),Prt=_ue(),IAe=Efe(),xrt=Bfe(),krt=Zfe(),Qrt=YQ(),at=Np(),Trt=nAe(),CAe=FH(),Rrt=LH(),wAe=oAe(),Frt=lAe(),BAe=cAe(),Nrt=uAe(),Ort=qH(),vAe=AAe(),Lrt=WH(),B0=hAe(),Mrt=gAe(),Urt=dAe(),KH,po=Symbol("request"),eT=Symbol("response"),mI=Symbol("responseSize"),yI=Symbol("downloadedSize"),EI=Symbol("bodySize"),II=Symbol("uploadedSize"),ZQ=Symbol("serverResponsesPiped"),SAe=Symbol("unproxyEvents"),DAe=Symbol("isFromCache"),zH=Symbol("cancelTimeouts"),bAe=Symbol("startedReading"),CI=Symbol("stopReading"),$Q=Symbol("triggerRead"),v0=Symbol("body"),ev=Symbol("jobs"),PAe=Symbol("originalResponse"),xAe=Symbol("retryTimeout");Ln.kIsNormalizedAlready=Symbol("isNormalizedAlready");var _rt=at.default.string(process.versions.brotli);Ln.withoutBody=new Set(["GET","HEAD"]);Ln.knownHookEvents=["init","beforeRequest","beforeRedirect","beforeError","beforeRetry","afterResponse"];function Hrt(t){for(let e in t){let r=t[e];if(!at.default.string(r)&&!at.default.number(r)&&!at.default.boolean(r)&&!at.default.null_(r)&&!at.default.undefined(r))throw new TypeError(`The \`searchParams\` value '${String(r)}' must be a string, number, boolean or null`)}}function jrt(t){return at.default.object(t)&&!("statusCode"in t)}var XH=new Nrt.default,Grt=async t=>new Promise((e,r)=>{let s=a=>{r(a)};t.pending||e(),t.once("error",s),t.once("ready",()=>{t.off("error",s),e()})}),qrt=new Set([300,301,302,303,304,307,308]),Wrt=["context","body","json","form"];Ln.setNonEnumerableProperties=(t,e)=>{let r={};for(let s of t)if(s)for(let a of Wrt)a in s&&(r[a]={writable:!0,configurable:!0,enumerable:!1,value:s[a]});Object.defineProperties(e,r)};var fs=class extends Error{constructor(e,r,s){var a;if(super(e),Error.captureStackTrace(this,this.constructor),this.name="RequestError",this.code=r.code,s instanceof aT?(Object.defineProperty(this,"request",{enumerable:!1,value:s}),Object.defineProperty(this,"response",{enumerable:!1,value:s[eT]}),Object.defineProperty(this,"options",{enumerable:!1,value:s.options})):Object.defineProperty(this,"options",{enumerable:!1,value:s}),this.timings=(a=this.request)===null||a===void 0?void 0:a.timings,at.default.string(r.stack)&&at.default.string(this.stack)){let n=this.stack.indexOf(this.message)+this.message.length,c=this.stack.slice(n).split(` +`).reverse(),f=r.stack.slice(r.stack.indexOf(r.message)+r.message.length).split(` +`).reverse();for(;f.length!==0&&f[0]===c[0];)c.shift();this.stack=`${this.stack.slice(0,n)}${c.reverse().join(` +`)}${f.reverse().join(` +`)}`}}};Ln.RequestError=fs;var tT=class extends fs{constructor(e){super(`Redirected ${e.options.maxRedirects} times. Aborting.`,{},e),this.name="MaxRedirectsError"}};Ln.MaxRedirectsError=tT;var rT=class extends fs{constructor(e){super(`Response code ${e.statusCode} (${e.statusMessage})`,{},e.request),this.name="HTTPError"}};Ln.HTTPError=rT;var nT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="CacheError"}};Ln.CacheError=nT;var iT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="UploadError"}};Ln.UploadError=iT;var sT=class extends fs{constructor(e,r,s){super(e.message,e,s),this.name="TimeoutError",this.event=e.event,this.timings=r}};Ln.TimeoutError=sT;var tv=class extends fs{constructor(e,r){super(e.message,e,r),this.name="ReadError"}};Ln.ReadError=tv;var oT=class extends fs{constructor(e){super(`Unsupported protocol "${e.url.protocol}"`,{},e),this.name="UnsupportedProtocolError"}};Ln.UnsupportedProtocolError=oT;var Yrt=["socket","connect","continue","information","upgrade","timeout"],aT=class extends yAe.Duplex{constructor(e,r={},s){super({autoDestroy:!1,highWaterMark:0}),this[yI]=0,this[II]=0,this.requestInitialized=!1,this[ZQ]=new Set,this.redirects=[],this[CI]=!1,this[$Q]=!1,this[ev]=[],this.retryCount=0,this._progressCallbacks=[];let a=()=>this._unlockWrite(),n=()=>this._lockWrite();this.on("pipe",h=>{h.prependListener("data",a),h.on("data",n),h.prependListener("end",a),h.on("end",n)}),this.on("unpipe",h=>{h.off("data",a),h.off("data",n),h.off("end",a),h.off("end",n)}),this.on("pipe",h=>{h instanceof JH.IncomingMessage&&(this.options.headers={...h.headers,...this.options.headers})});let{json:c,body:f,form:p}=r;if((c||f||p)&&this._lockWrite(),Ln.kIsNormalizedAlready in r)this.options=r;else try{this.options=this.constructor.normalizeArguments(e,r,s)}catch(h){at.default.nodeStream(r.body)&&r.body.destroy(),this.destroy(h);return}(async()=>{var h;try{this.options.body instanceof Srt.ReadStream&&await Grt(this.options.body);let{url:E}=this.options;if(!E)throw new TypeError("Missing `url` property");if(this.requestUrl=E.toString(),decodeURI(this.requestUrl),await this._finalizeBody(),await this._makeRequest(),this.destroyed){(h=this[po])===null||h===void 0||h.destroy();return}for(let C of this[ev])C();this[ev].length=0,this.requestInitialized=!0}catch(E){if(E instanceof fs){this._beforeError(E);return}this.destroyed||this.destroy(E)}})()}static normalizeArguments(e,r,s){var a,n,c,f,p;let h=r;if(at.default.object(e)&&!at.default.urlInstance(e))r={...s,...e,...r};else{if(e&&r&&r.url!==void 0)throw new TypeError("The `url` option is mutually exclusive with the `input` argument");r={...s,...r},e!==void 0&&(r.url=e),at.default.urlInstance(r.url)&&(r.url=new w0.URL(r.url.toString()))}if(r.cache===!1&&(r.cache=void 0),r.dnsCache===!1&&(r.dnsCache=void 0),at.assert.any([at.default.string,at.default.undefined],r.method),at.assert.any([at.default.object,at.default.undefined],r.headers),at.assert.any([at.default.string,at.default.urlInstance,at.default.undefined],r.prefixUrl),at.assert.any([at.default.object,at.default.undefined],r.cookieJar),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.searchParams),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.cache),at.assert.any([at.default.object,at.default.number,at.default.undefined],r.timeout),at.assert.any([at.default.object,at.default.undefined],r.context),at.assert.any([at.default.object,at.default.undefined],r.hooks),at.assert.any([at.default.boolean,at.default.undefined],r.decompress),at.assert.any([at.default.boolean,at.default.undefined],r.ignoreInvalidCookies),at.assert.any([at.default.boolean,at.default.undefined],r.followRedirect),at.assert.any([at.default.number,at.default.undefined],r.maxRedirects),at.assert.any([at.default.boolean,at.default.undefined],r.throwHttpErrors),at.assert.any([at.default.boolean,at.default.undefined],r.http2),at.assert.any([at.default.boolean,at.default.undefined],r.allowGetBody),at.assert.any([at.default.string,at.default.undefined],r.localAddress),at.assert.any([vAe.isDnsLookupIpVersion,at.default.undefined],r.dnsLookupIpVersion),at.assert.any([at.default.object,at.default.undefined],r.https),at.assert.any([at.default.boolean,at.default.undefined],r.rejectUnauthorized),r.https&&(at.assert.any([at.default.boolean,at.default.undefined],r.https.rejectUnauthorized),at.assert.any([at.default.function_,at.default.undefined],r.https.checkServerIdentity),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificateAuthority),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.key),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificate),at.assert.any([at.default.string,at.default.undefined],r.https.passphrase),at.assert.any([at.default.string,at.default.buffer,at.default.array,at.default.undefined],r.https.pfx)),at.assert.any([at.default.object,at.default.undefined],r.cacheOptions),at.default.string(r.method)?r.method=r.method.toUpperCase():r.method="GET",r.headers===s?.headers?r.headers={...r.headers}:r.headers=Qrt({...s?.headers,...r.headers}),"slashes"in r)throw new TypeError("The legacy `url.Url` has been deprecated. Use `URL` instead.");if("auth"in r)throw new TypeError("Parameter `auth` is deprecated. Use `username` / `password` instead.");if("searchParams"in r&&r.searchParams&&r.searchParams!==s?.searchParams){let P;if(at.default.string(r.searchParams)||r.searchParams instanceof w0.URLSearchParams)P=new w0.URLSearchParams(r.searchParams);else{Hrt(r.searchParams),P=new w0.URLSearchParams;for(let I in r.searchParams){let R=r.searchParams[I];R===null?P.append(I,""):R!==void 0&&P.append(I,R)}}(a=s?.searchParams)===null||a===void 0||a.forEach((I,R)=>{P.has(R)||P.append(R,I)}),r.searchParams=P}if(r.username=(n=r.username)!==null&&n!==void 0?n:"",r.password=(c=r.password)!==null&&c!==void 0?c:"",at.default.undefined(r.prefixUrl)?r.prefixUrl=(f=s?.prefixUrl)!==null&&f!==void 0?f:"":(r.prefixUrl=r.prefixUrl.toString(),r.prefixUrl!==""&&!r.prefixUrl.endsWith("/")&&(r.prefixUrl+="/")),at.default.string(r.url)){if(r.url.startsWith("/"))throw new Error("`input` must not start with a slash when using `prefixUrl`");r.url=BAe.default(r.prefixUrl+r.url,r)}else(at.default.undefined(r.url)&&r.prefixUrl!==""||r.protocol)&&(r.url=BAe.default(r.prefixUrl,r));if(r.url){"port"in r&&delete r.port;let{prefixUrl:P}=r;Object.defineProperty(r,"prefixUrl",{set:R=>{let N=r.url;if(!N.href.startsWith(R))throw new Error(`Cannot change \`prefixUrl\` from ${P} to ${R}: ${N.href}`);r.url=new w0.URL(R+N.href.slice(P.length)),P=R},get:()=>P});let{protocol:I}=r.url;if(I==="unix:"&&(I="http:",r.url=new w0.URL(`http://unix${r.url.pathname}${r.url.search}`)),r.searchParams&&(r.url.search=r.searchParams.toString()),I!=="http:"&&I!=="https:")throw new oT(r);r.username===""?r.username=r.url.username:r.url.username=r.username,r.password===""?r.password=r.url.password:r.url.password=r.password}let{cookieJar:E}=r;if(E){let{setCookie:P,getCookieString:I}=E;at.assert.function_(P),at.assert.function_(I),P.length===4&&I.length===0&&(P=mAe.promisify(P.bind(r.cookieJar)),I=mAe.promisify(I.bind(r.cookieJar)),r.cookieJar={setCookie:P,getCookieString:I})}let{cache:C}=r;if(C&&(XH.has(C)||XH.set(C,new IAe((P,I)=>{let R=P[po](P,I);return at.default.promise(R)&&(R.once=(N,U)=>{if(N==="error")R.catch(U);else if(N==="abort")(async()=>{try{(await R).once("abort",U)}catch{}})();else throw new Error(`Unknown HTTP2 promise event: ${N}`);return R}),R},C))),r.cacheOptions={...r.cacheOptions},r.dnsCache===!0)KH||(KH=new Prt.default),r.dnsCache=KH;else if(!at.default.undefined(r.dnsCache)&&!r.dnsCache.lookup)throw new TypeError(`Parameter \`dnsCache\` must be a CacheableLookup instance or a boolean, got ${at.default(r.dnsCache)}`);at.default.number(r.timeout)?r.timeout={request:r.timeout}:s&&r.timeout!==s.timeout?r.timeout={...s.timeout,...r.timeout}:r.timeout={...r.timeout},r.context||(r.context={});let S=r.hooks===s?.hooks;r.hooks={...r.hooks};for(let P of Ln.knownHookEvents)if(P in r.hooks)if(at.default.array(r.hooks[P]))r.hooks[P]=[...r.hooks[P]];else throw new TypeError(`Parameter \`${P}\` must be an Array, got ${at.default(r.hooks[P])}`);else r.hooks[P]=[];if(s&&!S)for(let P of Ln.knownHookEvents)s.hooks[P].length>0&&(r.hooks[P]=[...s.hooks[P],...r.hooks[P]]);if("family"in r&&B0.default('"options.family" was never documented, please use "options.dnsLookupIpVersion"'),s?.https&&(r.https={...s.https,...r.https}),"rejectUnauthorized"in r&&B0.default('"options.rejectUnauthorized" is now deprecated, please use "options.https.rejectUnauthorized"'),"checkServerIdentity"in r&&B0.default('"options.checkServerIdentity" was never documented, please use "options.https.checkServerIdentity"'),"ca"in r&&B0.default('"options.ca" was never documented, please use "options.https.certificateAuthority"'),"key"in r&&B0.default('"options.key" was never documented, please use "options.https.key"'),"cert"in r&&B0.default('"options.cert" was never documented, please use "options.https.certificate"'),"passphrase"in r&&B0.default('"options.passphrase" was never documented, please use "options.https.passphrase"'),"pfx"in r&&B0.default('"options.pfx" was never documented, please use "options.https.pfx"'),"followRedirects"in r)throw new TypeError("The `followRedirects` option does not exist. Use `followRedirect` instead.");if(r.agent){for(let P in r.agent)if(P!=="http"&&P!=="https"&&P!=="http2")throw new TypeError(`Expected the \`options.agent\` properties to be \`http\`, \`https\` or \`http2\`, got \`${P}\``)}return r.maxRedirects=(p=r.maxRedirects)!==null&&p!==void 0?p:0,Ln.setNonEnumerableProperties([s,h],r),Mrt.default(r,s)}_lockWrite(){let e=()=>{throw new TypeError("The payload has been already provided")};this.write=e,this.end=e}_unlockWrite(){this.write=super.write,this.end=super.end}async _finalizeBody(){let{options:e}=this,{headers:r}=e,s=!at.default.undefined(e.form),a=!at.default.undefined(e.json),n=!at.default.undefined(e.body),c=s||a||n,f=Ln.withoutBody.has(e.method)&&!(e.method==="GET"&&e.allowGetBody);if(this._cannotHaveBody=f,c){if(f)throw new TypeError(`The \`${e.method}\` method cannot be used with a body`);if([n,s,a].filter(p=>p).length>1)throw new TypeError("The `body`, `json` and `form` options are mutually exclusive");if(n&&!(e.body instanceof yAe.Readable)&&!at.default.string(e.body)&&!at.default.buffer(e.body)&&!CAe.default(e.body))throw new TypeError("The `body` option must be a stream.Readable, string or Buffer");if(s&&!at.default.object(e.form))throw new TypeError("The `form` option must be an Object");{let p=!at.default.string(r["content-type"]);n?(CAe.default(e.body)&&p&&(r["content-type"]=`multipart/form-data; boundary=${e.body.getBoundary()}`),this[v0]=e.body):s?(p&&(r["content-type"]="application/x-www-form-urlencoded"),this[v0]=new w0.URLSearchParams(e.form).toString()):(p&&(r["content-type"]="application/json"),this[v0]=e.stringifyJson(e.json));let h=await Trt.default(this[v0],e.headers);at.default.undefined(r["content-length"])&&at.default.undefined(r["transfer-encoding"])&&!f&&!at.default.undefined(h)&&(r["content-length"]=String(h))}}else f?this._lockWrite():this._unlockWrite();this[EI]=Number(r["content-length"])||void 0}async _onResponseBase(e){let{options:r}=this,{url:s}=r;this[PAe]=e,r.decompress&&(e=xrt(e));let a=e.statusCode,n=e;n.statusMessage=n.statusMessage?n.statusMessage:EAe.STATUS_CODES[a],n.url=r.url.toString(),n.requestUrl=this.requestUrl,n.redirectUrls=this.redirects,n.request=this,n.isFromCache=e.fromCache||!1,n.ip=this.ip,n.retryCount=this.retryCount,this[DAe]=n.isFromCache,this[mI]=Number(e.headers["content-length"])||void 0,this[eT]=e,e.once("end",()=>{this[mI]=this[yI],this.emit("downloadProgress",this.downloadProgress)}),e.once("error",f=>{e.destroy(),this._beforeError(new tv(f,this))}),e.once("aborted",()=>{this._beforeError(new tv({name:"Error",message:"The server aborted pending request",code:"ECONNRESET"},this))}),this.emit("downloadProgress",this.downloadProgress);let c=e.headers["set-cookie"];if(at.default.object(r.cookieJar)&&c){let f=c.map(async p=>r.cookieJar.setCookie(p,s.toString()));r.ignoreInvalidCookies&&(f=f.map(async p=>p.catch(()=>{})));try{await Promise.all(f)}catch(p){this._beforeError(p);return}}if(r.followRedirect&&e.headers.location&&qrt.has(a)){if(e.resume(),this[po]&&(this[zH](),delete this[po],this[SAe]()),(a===303&&r.method!=="GET"&&r.method!=="HEAD"||!r.methodRewriting)&&(r.method="GET","body"in r&&delete r.body,"json"in r&&delete r.json,"form"in r&&delete r.form,this[v0]=void 0,delete r.headers["content-length"]),this.redirects.length>=r.maxRedirects){this._beforeError(new tT(this));return}try{let p=Buffer.from(e.headers.location,"binary").toString(),h=new w0.URL(p,s),E=h.toString();decodeURI(E),h.hostname!==s.hostname||h.port!==s.port?("host"in r.headers&&delete r.headers.host,"cookie"in r.headers&&delete r.headers.cookie,"authorization"in r.headers&&delete r.headers.authorization,(r.username||r.password)&&(r.username="",r.password="")):(h.username=r.username,h.password=r.password),this.redirects.push(E),r.url=h;for(let C of r.hooks.beforeRedirect)await C(r,n);this.emit("redirect",n,r),await this._makeRequest()}catch(p){this._beforeError(p);return}return}if(r.isStream&&r.throwHttpErrors&&!Lrt.isResponseOk(n)){this._beforeError(new rT(n));return}e.on("readable",()=>{this[$Q]&&this._read()}),this.on("resume",()=>{e.resume()}),this.on("pause",()=>{e.pause()}),e.once("end",()=>{this.push(null)}),this.emit("response",e);for(let f of this[ZQ])if(!f.headersSent){for(let p in e.headers){let h=r.decompress?p!=="content-encoding":!0,E=e.headers[p];h&&f.setHeader(p,E)}f.statusCode=a}}async _onResponse(e){try{await this._onResponseBase(e)}catch(r){this._beforeError(r)}}_onRequest(e){let{options:r}=this,{timeout:s,url:a}=r;brt.default(e),this[zH]=wAe.default(e,s,a);let n=r.cache?"cacheableResponse":"response";e.once(n,p=>{this._onResponse(p)}),e.once("error",p=>{var h;e.destroy(),(h=e.res)===null||h===void 0||h.removeAllListeners("end"),p=p instanceof wAe.TimeoutError?new sT(p,this.timings,this):new fs(p.message,p,this),this._beforeError(p)}),this[SAe]=Rrt.default(e,this,Yrt),this[po]=e,this.emit("uploadProgress",this.uploadProgress);let c=this[v0],f=this.redirects.length===0?this:e;at.default.nodeStream(c)?(c.pipe(f),c.once("error",p=>{this._beforeError(new iT(p,this))})):(this._unlockWrite(),at.default.undefined(c)?(this._cannotHaveBody||this._noPipe)&&(f.end(),this._lockWrite()):(this._writeRequest(c,void 0,()=>{}),f.end(),this._lockWrite())),this.emit("request",e)}async _createCacheableRequest(e,r){return new Promise((s,a)=>{Object.assign(r,Frt.default(e)),delete r.url;let n,c=XH.get(r.cache)(r,async f=>{f._readableState.autoDestroy=!1,n&&(await n).emit("cacheableResponse",f),s(f)});r.url=e,c.once("error",a),c.once("request",async f=>{n=f,s(n)})})}async _makeRequest(){var e,r,s,a,n;let{options:c}=this,{headers:f}=c;for(let U in f)if(at.default.undefined(f[U]))delete f[U];else if(at.default.null_(f[U]))throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${U}\` header`);if(c.decompress&&at.default.undefined(f["accept-encoding"])&&(f["accept-encoding"]=_rt?"gzip, deflate, br":"gzip, deflate"),c.cookieJar){let U=await c.cookieJar.getCookieString(c.url.toString());at.default.nonEmptyString(U)&&(c.headers.cookie=U)}for(let U of c.hooks.beforeRequest){let W=await U(c);if(!at.default.undefined(W)){c.request=()=>W;break}}c.body&&this[v0]!==c.body&&(this[v0]=c.body);let{agent:p,request:h,timeout:E,url:C}=c;if(c.dnsCache&&!("lookup"in c)&&(c.lookup=c.dnsCache.lookup),C.hostname==="unix"){let U=/(?.+?):(?.+)/.exec(`${C.pathname}${C.search}`);if(U?.groups){let{socketPath:W,path:ee}=U.groups;Object.assign(c,{socketPath:W,path:ee,host:""})}}let S=C.protocol==="https:",P;c.http2?P=krt.auto:P=S?Drt.request:EAe.request;let I=(e=c.request)!==null&&e!==void 0?e:P,R=c.cache?this._createCacheableRequest:I;p&&!c.http2&&(c.agent=p[S?"https":"http"]),c[po]=I,delete c.request,delete c.timeout;let N=c;if(N.shared=(r=c.cacheOptions)===null||r===void 0?void 0:r.shared,N.cacheHeuristic=(s=c.cacheOptions)===null||s===void 0?void 0:s.cacheHeuristic,N.immutableMinTimeToLive=(a=c.cacheOptions)===null||a===void 0?void 0:a.immutableMinTimeToLive,N.ignoreCargoCult=(n=c.cacheOptions)===null||n===void 0?void 0:n.ignoreCargoCult,c.dnsLookupIpVersion!==void 0)try{N.family=vAe.dnsLookupIpVersionToFamily(c.dnsLookupIpVersion)}catch{throw new Error("Invalid `dnsLookupIpVersion` option value")}c.https&&("rejectUnauthorized"in c.https&&(N.rejectUnauthorized=c.https.rejectUnauthorized),c.https.checkServerIdentity&&(N.checkServerIdentity=c.https.checkServerIdentity),c.https.certificateAuthority&&(N.ca=c.https.certificateAuthority),c.https.certificate&&(N.cert=c.https.certificate),c.https.key&&(N.key=c.https.key),c.https.passphrase&&(N.passphrase=c.https.passphrase),c.https.pfx&&(N.pfx=c.https.pfx));try{let U=await R(C,N);at.default.undefined(U)&&(U=P(C,N)),c.request=h,c.timeout=E,c.agent=p,c.https&&("rejectUnauthorized"in c.https&&delete N.rejectUnauthorized,c.https.checkServerIdentity&&delete N.checkServerIdentity,c.https.certificateAuthority&&delete N.ca,c.https.certificate&&delete N.cert,c.https.key&&delete N.key,c.https.passphrase&&delete N.passphrase,c.https.pfx&&delete N.pfx),jrt(U)?this._onRequest(U):this.writable?(this.once("finish",()=>{this._onResponse(U)}),this._unlockWrite(),this.end(),this._lockWrite()):this._onResponse(U)}catch(U){throw U instanceof IAe.CacheError?new nT(U,this):new fs(U.message,U,this)}}async _error(e){try{for(let r of this.options.hooks.beforeError)e=await r(e)}catch(r){e=new fs(r.message,r,this)}this.destroy(e)}_beforeError(e){if(this[CI])return;let{options:r}=this,s=this.retryCount+1;this[CI]=!0,e instanceof fs||(e=new fs(e.message,e,this));let a=e,{response:n}=a;(async()=>{if(n&&!n.body){n.setEncoding(this._readableState.encoding);try{n.rawBody=await Ort.default(n),n.body=n.rawBody.toString()}catch{}}if(this.listenerCount("retry")!==0){let c;try{let f;n&&"retry-after"in n.headers&&(f=Number(n.headers["retry-after"]),Number.isNaN(f)?(f=Date.parse(n.headers["retry-after"])-Date.now(),f<=0&&(f=1)):f*=1e3),c=await r.retry.calculateDelay({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:Urt.default({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:0})})}catch(f){this._error(new fs(f.message,f,this));return}if(c){let f=async()=>{try{for(let p of this.options.hooks.beforeRetry)await p(this.options,a,s)}catch(p){this._error(new fs(p.message,e,this));return}this.destroyed||(this.destroy(),this.emit("retry",s,e))};this[xAe]=setTimeout(f,c);return}}this._error(a)})()}_read(){this[$Q]=!0;let e=this[eT];if(e&&!this[CI]){e.readableLength&&(this[$Q]=!1);let r;for(;(r=e.read())!==null;){this[yI]+=r.length,this[bAe]=!0;let s=this.downloadProgress;s.percent<1&&this.emit("downloadProgress",s),this.push(r)}}}_write(e,r,s){let a=()=>{this._writeRequest(e,r,s)};this.requestInitialized?a():this[ev].push(a)}_writeRequest(e,r,s){this[po].destroyed||(this._progressCallbacks.push(()=>{this[II]+=Buffer.byteLength(e,r);let a=this.uploadProgress;a.percent<1&&this.emit("uploadProgress",a)}),this[po].write(e,r,a=>{!a&&this._progressCallbacks.length>0&&this._progressCallbacks.shift()(),s(a)}))}_final(e){let r=()=>{for(;this._progressCallbacks.length!==0;)this._progressCallbacks.shift()();if(!(po in this)){e();return}if(this[po].destroyed){e();return}this[po].end(s=>{s||(this[EI]=this[II],this.emit("uploadProgress",this.uploadProgress),this[po].emit("upload-complete")),e(s)})};this.requestInitialized?r():this[ev].push(r)}_destroy(e,r){var s;this[CI]=!0,clearTimeout(this[xAe]),po in this&&(this[zH](),!((s=this[eT])===null||s===void 0)&&s.complete||this[po].destroy()),e!==null&&!at.default.undefined(e)&&!(e instanceof fs)&&(e=new fs(e.message,e,this)),r(e)}get _isAboutToError(){return this[CI]}get ip(){var e;return(e=this.socket)===null||e===void 0?void 0:e.remoteAddress}get aborted(){var e,r,s;return((r=(e=this[po])===null||e===void 0?void 0:e.destroyed)!==null&&r!==void 0?r:this.destroyed)&&!(!((s=this[PAe])===null||s===void 0)&&s.complete)}get socket(){var e,r;return(r=(e=this[po])===null||e===void 0?void 0:e.socket)!==null&&r!==void 0?r:void 0}get downloadProgress(){let e;return this[mI]?e=this[yI]/this[mI]:this[mI]===this[yI]?e=1:e=0,{percent:e,transferred:this[yI],total:this[mI]}}get uploadProgress(){let e;return this[EI]?e=this[II]/this[EI]:this[EI]===this[II]?e=1:e=0,{percent:e,transferred:this[II],total:this[EI]}}get timings(){var e;return(e=this[po])===null||e===void 0?void 0:e.timings}get isFromCache(){return this[DAe]}pipe(e,r){if(this[bAe])throw new Error("Failed to pipe. The response has been emitted already.");return e instanceof JH.ServerResponse&&this[ZQ].add(e),super.pipe(e,r)}unpipe(e){return e instanceof JH.ServerResponse&&this[ZQ].delete(e),super.unpipe(e),this}};Ln.default=aT});var nv=_(qu=>{"use strict";var Vrt=qu&&qu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Jrt=qu&&qu.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Vrt(e,t,r)};Object.defineProperty(qu,"__esModule",{value:!0});qu.CancelError=qu.ParseError=void 0;var kAe=rv(),ZH=class extends kAe.RequestError{constructor(e,r){let{options:s}=r.request;super(`${e.message} in "${s.url.toString()}"`,e,r.request),this.name="ParseError"}};qu.ParseError=ZH;var $H=class extends kAe.RequestError{constructor(e){super("Promise was canceled",{},e),this.name="CancelError"}get isCanceled(){return!0}};qu.CancelError=$H;Jrt(rv(),qu)});var TAe=_(ej=>{"use strict";Object.defineProperty(ej,"__esModule",{value:!0});var QAe=nv(),Krt=(t,e,r,s)=>{let{rawBody:a}=t;try{if(e==="text")return a.toString(s);if(e==="json")return a.length===0?"":r(a.toString());if(e==="buffer")return a;throw new QAe.ParseError({message:`Unknown body type '${e}'`,name:"Error"},t)}catch(n){throw new QAe.ParseError(n,t)}};ej.default=Krt});var tj=_(S0=>{"use strict";var zrt=S0&&S0.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Xrt=S0&&S0.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&zrt(e,t,r)};Object.defineProperty(S0,"__esModule",{value:!0});var Zrt=Ie("events"),$rt=Np(),ent=Que(),lT=nv(),RAe=TAe(),FAe=rv(),tnt=LH(),rnt=qH(),NAe=WH(),nnt=["request","response","redirect","uploadProgress","downloadProgress"];function OAe(t){let e,r,s=new Zrt.EventEmitter,a=new ent((c,f,p)=>{let h=E=>{let C=new FAe.default(void 0,t);C.retryCount=E,C._noPipe=!0,p(()=>C.destroy()),p.shouldReject=!1,p(()=>f(new lT.CancelError(C))),e=C,C.once("response",async I=>{var R;if(I.retryCount=E,I.request.aborted)return;let N;try{N=await rnt.default(C),I.rawBody=N}catch{return}if(C._isAboutToError)return;let U=((R=I.headers["content-encoding"])!==null&&R!==void 0?R:"").toLowerCase(),W=["gzip","deflate","br"].includes(U),{options:ee}=C;if(W&&!ee.decompress)I.body=N;else try{I.body=RAe.default(I,ee.responseType,ee.parseJson,ee.encoding)}catch(ie){if(I.body=N.toString(),NAe.isResponseOk(I)){C._beforeError(ie);return}}try{for(let[ie,ue]of ee.hooks.afterResponse.entries())I=await ue(I,async le=>{let me=FAe.default.normalizeArguments(void 0,{...le,retry:{calculateDelay:()=>0},throwHttpErrors:!1,resolveBodyOnly:!1},ee);me.hooks.afterResponse=me.hooks.afterResponse.slice(0,ie);for(let Be of me.hooks.beforeRetry)await Be(me);let pe=OAe(me);return p(()=>{pe.catch(()=>{}),pe.cancel()}),pe})}catch(ie){C._beforeError(new lT.RequestError(ie.message,ie,C));return}if(!NAe.isResponseOk(I)){C._beforeError(new lT.HTTPError(I));return}r=I,c(C.options.resolveBodyOnly?I.body:I)});let S=I=>{if(a.isCanceled)return;let{options:R}=C;if(I instanceof lT.HTTPError&&!R.throwHttpErrors){let{response:N}=I;c(C.options.resolveBodyOnly?N.body:N);return}f(I)};C.once("error",S);let P=C.options.body;C.once("retry",(I,R)=>{var N,U;if(P===((N=R.request)===null||N===void 0?void 0:N.options.body)&&$rt.default.nodeStream((U=R.request)===null||U===void 0?void 0:U.options.body)){S(R);return}h(I)}),tnt.default(C,s,nnt)};h(0)});a.on=(c,f)=>(s.on(c,f),a);let n=c=>{let f=(async()=>{await a;let{options:p}=r.request;return RAe.default(r,c,p.parseJson,p.encoding)})();return Object.defineProperties(f,Object.getOwnPropertyDescriptors(a)),f};return a.json=()=>{let{headers:c}=e.options;return!e.writableFinished&&c.accept===void 0&&(c.accept="application/json"),n("json")},a.buffer=()=>n("buffer"),a.text=()=>n("text"),a}S0.default=OAe;Xrt(nv(),S0)});var LAe=_(rj=>{"use strict";Object.defineProperty(rj,"__esModule",{value:!0});var int=nv();function snt(t,...e){let r=(async()=>{if(t instanceof int.RequestError)try{for(let a of e)if(a)for(let n of a)t=await n(t)}catch(a){t=a}throw t})(),s=()=>r;return r.json=s,r.text=s,r.buffer=s,r.on=s,r}rj.default=snt});var _Ae=_(nj=>{"use strict";Object.defineProperty(nj,"__esModule",{value:!0});var MAe=Np();function UAe(t){for(let e of Object.values(t))(MAe.default.plainObject(e)||MAe.default.array(e))&&UAe(e);return Object.freeze(t)}nj.default=UAe});var jAe=_(HAe=>{"use strict";Object.defineProperty(HAe,"__esModule",{value:!0})});var ij=_(Nc=>{"use strict";var ont=Nc&&Nc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),ant=Nc&&Nc.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&ont(e,t,r)};Object.defineProperty(Nc,"__esModule",{value:!0});Nc.defaultHandler=void 0;var GAe=Np(),Fc=tj(),lnt=LAe(),uT=rv(),cnt=_Ae(),unt={RequestError:Fc.RequestError,CacheError:Fc.CacheError,ReadError:Fc.ReadError,HTTPError:Fc.HTTPError,MaxRedirectsError:Fc.MaxRedirectsError,TimeoutError:Fc.TimeoutError,ParseError:Fc.ParseError,CancelError:Fc.CancelError,UnsupportedProtocolError:Fc.UnsupportedProtocolError,UploadError:Fc.UploadError},fnt=async t=>new Promise(e=>{setTimeout(e,t)}),{normalizeArguments:cT}=uT.default,qAe=(...t)=>{let e;for(let r of t)e=cT(void 0,r,e);return e},Ant=t=>t.isStream?new uT.default(void 0,t):Fc.default(t),pnt=t=>"defaults"in t&&"options"in t.defaults,hnt=["get","post","put","patch","head","delete"];Nc.defaultHandler=(t,e)=>e(t);var WAe=(t,e)=>{if(t)for(let r of t)r(e)},YAe=t=>{t._rawHandlers=t.handlers,t.handlers=t.handlers.map(s=>(a,n)=>{let c,f=s(a,p=>(c=n(p),c));if(f!==c&&!a.isStream&&c){let p=f,{then:h,catch:E,finally:C}=p;Object.setPrototypeOf(p,Object.getPrototypeOf(c)),Object.defineProperties(p,Object.getOwnPropertyDescriptors(c)),p.then=h,p.catch=E,p.finally=C}return f});let e=(s,a={},n)=>{var c,f;let p=0,h=E=>t.handlers[p++](E,p===t.handlers.length?Ant:h);if(GAe.default.plainObject(s)){let E={...s,...a};uT.setNonEnumerableProperties([s,a],E),a=E,s=void 0}try{let E;try{WAe(t.options.hooks.init,a),WAe((c=a.hooks)===null||c===void 0?void 0:c.init,a)}catch(S){E=S}let C=cT(s,a,n??t.options);if(C[uT.kIsNormalizedAlready]=!0,E)throw new Fc.RequestError(E.message,E,C);return h(C)}catch(E){if(a.isStream)throw E;return lnt.default(E,t.options.hooks.beforeError,(f=a.hooks)===null||f===void 0?void 0:f.beforeError)}};e.extend=(...s)=>{let a=[t.options],n=[...t._rawHandlers],c;for(let f of s)pnt(f)?(a.push(f.defaults.options),n.push(...f.defaults._rawHandlers),c=f.defaults.mutableDefaults):(a.push(f),"handlers"in f&&n.push(...f.handlers),c=f.mutableDefaults);return n=n.filter(f=>f!==Nc.defaultHandler),n.length===0&&n.push(Nc.defaultHandler),YAe({options:qAe(...a),handlers:n,mutableDefaults:!!c})};let r=async function*(s,a){let n=cT(s,a,t.options);n.resolveBodyOnly=!1;let c=n.pagination;if(!GAe.default.object(c))throw new TypeError("`options.pagination` must be implemented");let f=[],{countLimit:p}=c,h=0;for(;h{let n=[];for await(let c of r(s,a))n.push(c);return n},e.paginate.each=r,e.stream=(s,a)=>e(s,{...a,isStream:!0});for(let s of hnt)e[s]=(a,n)=>e(a,{...n,method:s}),e.stream[s]=(a,n)=>e(a,{...n,method:s,isStream:!0});return Object.assign(e,unt),Object.defineProperty(e,"defaults",{value:t.mutableDefaults?t:cnt.default(t),writable:t.mutableDefaults,configurable:t.mutableDefaults,enumerable:!0}),e.mergeOptions=qAe,e};Nc.default=YAe;ant(jAe(),Nc)});var KAe=_((Op,fT)=>{"use strict";var gnt=Op&&Op.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),VAe=Op&&Op.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&gnt(e,t,r)};Object.defineProperty(Op,"__esModule",{value:!0});var dnt=Ie("url"),JAe=ij(),mnt={options:{method:"GET",retry:{limit:2,methods:["GET","PUT","HEAD","DELETE","OPTIONS","TRACE"],statusCodes:[408,413,429,500,502,503,504,521,522,524],errorCodes:["ETIMEDOUT","ECONNRESET","EADDRINUSE","ECONNREFUSED","EPIPE","ENOTFOUND","ENETUNREACH","EAI_AGAIN"],maxRetryAfter:void 0,calculateDelay:({computedValue:t})=>t},timeout:{},headers:{"user-agent":"got (https://github.com/sindresorhus/got)"},hooks:{init:[],beforeRequest:[],beforeRedirect:[],beforeRetry:[],beforeError:[],afterResponse:[]},cache:void 0,dnsCache:void 0,decompress:!0,throwHttpErrors:!0,followRedirect:!0,isStream:!1,responseType:"text",resolveBodyOnly:!1,maxRedirects:10,prefixUrl:"",methodRewriting:!0,ignoreInvalidCookies:!1,context:{},http2:!1,allowGetBody:!1,https:void 0,pagination:{transform:t=>t.request.options.responseType==="json"?t.body:JSON.parse(t.body),paginate:t=>{if(!Reflect.has(t.headers,"link"))return!1;let e=t.headers.link.split(","),r;for(let s of e){let a=s.split(";");if(a[1].includes("next")){r=a[0].trimStart().trim(),r=r.slice(1,-1);break}}return r?{url:new dnt.URL(r)}:!1},filter:()=>!0,shouldContinue:()=>!0,countLimit:1/0,backoff:0,requestLimit:1e4,stackAllItems:!0},parseJson:t=>JSON.parse(t),stringifyJson:t=>JSON.stringify(t),cacheOptions:{}},handlers:[JAe.defaultHandler],mutableDefaults:!1},sj=JAe.default(mnt);Op.default=sj;fT.exports=sj;fT.exports.default=sj;fT.exports.__esModule=!0;VAe(ij(),Op);VAe(tj(),Op)});var nn={};Vt(nn,{Method:()=>tpe,del:()=>wnt,get:()=>lj,getNetworkSettings:()=>epe,post:()=>cj,put:()=>Cnt,request:()=>iv});async function oj(t){return Yl(XAe,t,()=>ce.readFilePromise(t).then(e=>(XAe.set(t,e),e)))}function Int({statusCode:t,statusMessage:e},r){let s=Ht(r,t,ht.NUMBER),a=`https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${t}`;return KE(r,`${s}${e?` (${e})`:""}`,a)}async function AT(t,{configuration:e,customErrorMessage:r}){try{return await t}catch(s){if(s.name!=="HTTPError")throw s;let a=r?.(s,e)??s.response.body?.error;a==null&&(s.message.startsWith("Response code")?a="The remote server failed to provide the requested resource":a=s.message),s.code==="ETIMEDOUT"&&s.event==="socket"&&(a+=`(can be increased via ${Ht(e,"httpTimeout",ht.SETTING)})`);let n=new jt(35,a,c=>{s.response&&c.reportError(35,` ${Kf(e,{label:"Response Code",value:_u(ht.NO_HINT,Int(s.response,e))})}`),s.request&&(c.reportError(35,` ${Kf(e,{label:"Request Method",value:_u(ht.NO_HINT,s.request.options.method)})}`),c.reportError(35,` ${Kf(e,{label:"Request URL",value:_u(ht.URL,s.request.requestUrl)})}`)),s.request.redirects.length>0&&c.reportError(35,` ${Kf(e,{label:"Request Redirects",value:_u(ht.NO_HINT,Z4(e,s.request.redirects,ht.URL))})}`),s.request.retryCount===s.request.options.retry.limit&&c.reportError(35,` ${Kf(e,{label:"Request Retry Count",value:_u(ht.NO_HINT,`${Ht(e,s.request.retryCount,ht.NUMBER)} (can be increased via ${Ht(e,"httpRetry",ht.SETTING)})`)})}`)});throw n.originalError=s,n}}function epe(t,e){let r=[...e.configuration.get("networkSettings")].sort(([c],[f])=>f.length-c.length),s={enableNetwork:void 0,httpsCaFilePath:void 0,httpProxy:void 0,httpsProxy:void 0,httpsKeyFilePath:void 0,httpsCertFilePath:void 0},a=Object.keys(s),n=typeof t=="string"?new URL(t):t;for(let[c,f]of r)if(aj.default.isMatch(n.hostname,c))for(let p of a){let h=f.get(p);h!==null&&typeof s[p]>"u"&&(s[p]=h)}for(let c of a)typeof s[c]>"u"&&(s[c]=e.configuration.get(c));return s}async function iv(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET",wrapNetworkRequest:f}){let p={target:t,body:e,configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c},h=async()=>await Bnt(t,e,p),E=typeof f<"u"?await f(h,p):h;return await(await r.reduceHook(S=>S.wrapNetworkRequest,E,p))()}async function lj(t,{configuration:e,jsonResponse:r,customErrorMessage:s,wrapNetworkRequest:a,...n}){let c=()=>AT(iv(t,null,{configuration:e,wrapNetworkRequest:a,...n}),{configuration:e,customErrorMessage:s}).then(p=>p.body),f=await(typeof a<"u"?c():Yl(zAe,t,()=>c().then(p=>(zAe.set(t,p),p))));return r?JSON.parse(f.toString()):f}async function Cnt(t,e,{customErrorMessage:r,...s}){return(await AT(iv(t,e,{...s,method:"PUT"}),{customErrorMessage:r,configuration:s.configuration})).body}async function cj(t,e,{customErrorMessage:r,...s}){return(await AT(iv(t,e,{...s,method:"POST"}),{customErrorMessage:r,configuration:s.configuration})).body}async function wnt(t,{customErrorMessage:e,...r}){return(await AT(iv(t,null,{...r,method:"DELETE"}),{customErrorMessage:e,configuration:r.configuration})).body}async function Bnt(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET"}){let f=typeof t=="string"?new URL(t):t,p=epe(f,{configuration:r});if(p.enableNetwork===!1)throw new jt(80,`Request to '${f.href}' has been blocked because of your configuration settings`);if(f.protocol==="http:"&&!aj.default.isMatch(f.hostname,r.get("unsafeHttpWhitelist")))throw new jt(81,`Unsafe http requests must be explicitly whitelisted in your configuration (${f.hostname})`);let h={headers:s,method:c};h.responseType=n?"json":"buffer",e!==null&&(Buffer.isBuffer(e)||!a&&typeof e=="string"?h.body=e:h.json=e);let E=r.get("httpTimeout"),C=r.get("httpRetry"),S=r.get("enableStrictSsl"),P=p.httpsCaFilePath,I=p.httpsCertFilePath,R=p.httpsKeyFilePath,{default:N}=await Promise.resolve().then(()=>ut(KAe())),U=P?await oj(P):void 0,W=I?await oj(I):void 0,ee=R?await oj(R):void 0,ie={rejectUnauthorized:S,ca:U,cert:W,key:ee},ue={http:p.httpProxy?new vue({proxy:p.httpProxy,proxyRequestOptions:ie}):ynt,https:p.httpsProxy?new Sue({proxy:p.httpsProxy,proxyRequestOptions:ie}):Ent},le=N.extend({timeout:{socket:E},retry:C,agent:ue,https:{rejectUnauthorized:S,certificateAuthority:U,certificate:W,key:ee},...h});return r.getLimit("networkConcurrency")(()=>le(f))}var ZAe,$Ae,aj,zAe,XAe,ynt,Ent,tpe,pT=Xe(()=>{Dt();Due();ZAe=Ie("https"),$Ae=Ie("http"),aj=ut(Go());Tc();xc();Pc();zAe=new Map,XAe=new Map,ynt=new $Ae.Agent({keepAlive:!0}),Ent=new ZAe.Agent({keepAlive:!0});tpe=(a=>(a.GET="GET",a.PUT="PUT",a.POST="POST",a.DELETE="DELETE",a))(tpe||{})});var Ui={};Vt(Ui,{availableParallelism:()=>fj,getArchitecture:()=>sv,getArchitectureName:()=>Pnt,getArchitectureSet:()=>uj,getCaller:()=>Tnt,major:()=>vnt,openUrl:()=>Snt});function bnt(){if(process.platform!=="linux")return null;let t;try{t=ce.readFileSync(Dnt)}catch{}if(typeof t<"u"){if(t&&(t.includes("GLIBC")||t.includes("GNU libc")||t.includes("GNU C Library")))return"glibc";if(t&&t.includes("musl"))return"musl"}let r=(process.report?.getReport()??{}).sharedObjects??[],s=/\/(?:(ld-linux-|[^/]+-linux-gnu\/)|(libc.musl-|ld-musl-))/;return p0(r,a=>{let n=a.match(s);if(!n)return p0.skip;if(n[1])return"glibc";if(n[2])return"musl";throw new Error("Assertion failed: Expected the libc variant to have been detected")})??null}function sv(){return npe=npe??{os:(process.env.YARN_IS_TEST_ENV?process.env.YARN_OS_OVERRIDE:void 0)??process.platform,cpu:(process.env.YARN_IS_TEST_ENV?process.env.YARN_CPU_OVERRIDE:void 0)??process.arch,libc:(process.env.YARN_IS_TEST_ENV?process.env.YARN_LIBC_OVERRIDE:void 0)??bnt()}}function Pnt(t=sv()){return t.libc?`${t.os}-${t.cpu}-${t.libc}`:`${t.os}-${t.cpu}`}function uj(){let t=sv();return ipe=ipe??{os:[t.os],cpu:[t.cpu],libc:t.libc?[t.libc]:[]}}function Qnt(t){let e=xnt.exec(t);if(!e)return null;let r=e[2]&&e[2].indexOf("native")===0,s=e[2]&&e[2].indexOf("eval")===0,a=knt.exec(e[2]);return s&&a!=null&&(e[2]=a[1],e[3]=a[2],e[4]=a[3]),{file:r?null:e[2],methodName:e[1]||"",arguments:r?[e[2]]:[],line:e[3]?+e[3]:null,column:e[4]?+e[4]:null}}function Tnt(){let e=new Error().stack.split(` +`)[3];return Qnt(e)}function fj(){return typeof hT.default.availableParallelism<"u"?hT.default.availableParallelism():Math.max(1,hT.default.cpus().length)}var hT,vnt,rpe,Snt,Dnt,npe,ipe,xnt,knt,gT=Xe(()=>{Dt();hT=ut(Ie("os"));dT();Pc();vnt=Number(process.versions.node.split(".")[0]),rpe=new Map([["darwin","open"],["linux","xdg-open"],["win32","explorer.exe"]]).get(process.platform),Snt=typeof rpe<"u"?async t=>{try{return await Aj(rpe,[t],{cwd:J.cwd()}),!0}catch{return!1}}:void 0,Dnt="/usr/bin/ldd";xnt=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,knt=/\((\S*)(?::(\d+))(?::(\d+))\)/});function yj(t,e,r,s,a){let n=YB(r);if(s.isArray||s.type==="ANY"&&Array.isArray(n))return Array.isArray(n)?n.map((c,f)=>pj(t,`${e}[${f}]`,c,s,a)):String(n).split(/,/).map(c=>pj(t,e,c,s,a));if(Array.isArray(n))throw new Error(`Non-array configuration settings "${e}" cannot be an array`);return pj(t,e,r,s,a)}function pj(t,e,r,s,a){let n=YB(r);switch(s.type){case"ANY":return NQ(n);case"SHAPE":return Ont(t,e,r,s,a);case"MAP":return Lnt(t,e,r,s,a)}if(n===null&&!s.isNullable&&s.default!==null)throw new Error(`Non-nullable configuration settings "${e}" cannot be set to null`);if("values"in s&&s.values?.includes(n))return n;let f=(()=>{if(s.type==="BOOLEAN"&&typeof n!="string")return kB(n);if(typeof n!="string")throw new Error(`Expected configuration setting "${e}" to be a string, got ${typeof n}`);let p=Vk(n,{env:t.env});switch(s.type){case"ABSOLUTE_PATH":{let h=a,E=H8(r);return E&&E[0]!=="<"&&(h=J.dirname(E)),J.resolve(h,fe.toPortablePath(p))}case"LOCATOR_LOOSE":return Qp(p,!1);case"NUMBER":return parseInt(p);case"LOCATOR":return Qp(p);case"BOOLEAN":return kB(p);case"DURATION":return Jk(p,s.unit);default:return p}})();if("values"in s&&s.values&&!s.values.includes(f))throw new Error(`Invalid value, expected one of ${s.values.join(", ")}`);return f}function Ont(t,e,r,s,a){let n=YB(r);if(typeof n!="object"||Array.isArray(n))throw new nt(`Object configuration settings "${e}" must be an object`);let c=Ej(t,s,{ignoreArrays:!0});if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=`${e}.${f}`;if(!s.properties[f])throw new nt(`Unrecognized configuration settings found: ${e}.${f} - run "yarn config" to see the list of settings supported in Yarn`);c.set(f,yj(t,h,p,s.properties[f],a))}return c}function Lnt(t,e,r,s,a){let n=YB(r),c=new Map;if(typeof n!="object"||Array.isArray(n))throw new nt(`Map configuration settings "${e}" must be an object`);if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=s.normalizeKeys?s.normalizeKeys(f):f,E=`${e}['${h}']`,C=s.valueDefinition;c.set(h,yj(t,E,p,C,a))}return c}function Ej(t,e,{ignoreArrays:r=!1}={}){switch(e.type){case"SHAPE":{if(e.isArray&&!r)return[];let s=new Map;for(let[a,n]of Object.entries(e.properties))s.set(a,Ej(t,n));return s}case"MAP":return e.isArray&&!r?[]:new Map;case"ABSOLUTE_PATH":return e.default===null?null:t.projectCwd===null?Array.isArray(e.default)?e.default.map(s=>J.normalize(s)):J.isAbsolute(e.default)?J.normalize(e.default):e.isNullable?null:void 0:Array.isArray(e.default)?e.default.map(s=>J.resolve(t.projectCwd,s)):J.resolve(t.projectCwd,e.default);case"DURATION":return Jk(e.default,e.unit);default:return e.default}}function yT(t,e,r){if(e.type==="SECRET"&&typeof t=="string"&&r.hideSecrets)return Nnt;if(e.type==="ABSOLUTE_PATH"&&typeof t=="string"&&r.getNativePaths)return fe.fromPortablePath(t);if(e.isArray&&Array.isArray(t)){let s=[];for(let a of t)s.push(yT(a,e,r));return s}if(e.type==="MAP"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=yT(n,e.valueDefinition,r);typeof c<"u"&&s.set(a,c)}return s}if(e.type==="SHAPE"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=e.properties[a],f=yT(n,c,r);typeof f<"u"&&s.set(a,f)}return s}return t}function Mnt(){let t={};for(let[e,r]of Object.entries(process.env))e=e.toLowerCase(),e.startsWith(ET)&&(e=(0,ope.default)(e.slice(ET.length)),t[e]=r);return t}function gj(){let t=`${ET}rc_filename`;for(let[e,r]of Object.entries(process.env))if(e.toLowerCase()===t&&typeof r=="string")return r;return dj}async function spe(t){try{return await ce.readFilePromise(t)}catch{return Buffer.of()}}async function Unt(t,e){return Buffer.compare(...await Promise.all([spe(t),spe(e)]))===0}async function _nt(t,e){let[r,s]=await Promise.all([ce.statPromise(t),ce.statPromise(e)]);return r.dev===s.dev&&r.ino===s.ino}async function jnt({configuration:t,selfPath:e}){let r=t.get("yarnPath");return t.get("ignorePath")||r===null||r===e||await Hnt(r,e)?null:r}var ope,Lp,ape,lpe,cpe,hj,Rnt,ov,Fnt,Mp,ET,dj,Nnt,wI,upe,mj,IT,mT,Hnt,ze,av=Xe(()=>{Dt();wc();ope=ut(Sre()),Lp=ut(Fd());Yt();ape=ut(yne()),lpe=Ie("module"),cpe=ut(Ld()),hj=Ie("stream");nue();oI();R8();F8();N8();gue();O8();tm();Iue();LQ();xc();I0();pT();Pc();gT();Rp();Wo();Rnt=function(){if(!Lp.GITHUB_ACTIONS||!process.env.GITHUB_EVENT_PATH)return!1;let t=fe.toPortablePath(process.env.GITHUB_EVENT_PATH),e;try{e=ce.readJsonSync(t)}catch{return!1}return!(!("repository"in e)||!e.repository||(e.repository.private??!0))}(),ov=new Set(["@yarnpkg/plugin-constraints","@yarnpkg/plugin-exec","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]),Fnt=new Set(["isTestEnv","injectNpmUser","injectNpmPassword","injectNpm2FaToken","zipDataEpilogue","cacheCheckpointOverride","cacheVersionOverride","lockfileVersionOverride","osOverride","cpuOverride","libcOverride","binFolder","version","flags","profile","gpg","ignoreNode","wrapOutput","home","confDir","registry","ignoreCwd"]),Mp=/^(?!v)[a-z0-9._-]+$/i,ET="yarn_",dj=".yarnrc.yml",Nnt="********",wI=(C=>(C.ANY="ANY",C.BOOLEAN="BOOLEAN",C.ABSOLUTE_PATH="ABSOLUTE_PATH",C.LOCATOR="LOCATOR",C.LOCATOR_LOOSE="LOCATOR_LOOSE",C.NUMBER="NUMBER",C.STRING="STRING",C.DURATION="DURATION",C.SECRET="SECRET",C.SHAPE="SHAPE",C.MAP="MAP",C))(wI||{}),upe=ht,mj=(c=>(c.MILLISECONDS="ms",c.SECONDS="s",c.MINUTES="m",c.HOURS="h",c.DAYS="d",c.WEEKS="w",c))(mj||{}),IT=(r=>(r.JUNCTIONS="junctions",r.SYMLINKS="symlinks",r))(IT||{}),mT={lastUpdateCheck:{description:"Last timestamp we checked whether new Yarn versions were available",type:"STRING",default:null},yarnPath:{description:"Path to the local executable that must be used over the global one",type:"ABSOLUTE_PATH",default:null},ignorePath:{description:"If true, the local executable will be ignored when using the global one",type:"BOOLEAN",default:!1},globalFolder:{description:"Folder where all system-global files are stored",type:"ABSOLUTE_PATH",default:G8()},cacheFolder:{description:"Folder where the cache files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/cache"},compressionLevel:{description:"Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)",type:"NUMBER",values:["mixed",0,1,2,3,4,5,6,7,8,9],default:0},virtualFolder:{description:"Folder where the virtual packages (cf doc) will be mapped on the disk (must be named __virtual__)",type:"ABSOLUTE_PATH",default:"./.yarn/__virtual__"},installStatePath:{description:"Path of the file where the install state will be persisted",type:"ABSOLUTE_PATH",default:"./.yarn/install-state.gz"},immutablePatterns:{description:"Array of glob patterns; files matching them won't be allowed to change during immutable installs",type:"STRING",default:[],isArray:!0},rcFilename:{description:"Name of the files where the configuration can be found",type:"STRING",default:gj()},enableGlobalCache:{description:"If true, the system-wide cache folder will be used regardless of `cache-folder`",type:"BOOLEAN",default:!0},cacheMigrationMode:{description:"Defines the conditions under which Yarn upgrades should cause the cache archives to be regenerated.",type:"STRING",values:["always","match-spec","required-only"],default:"always"},enableColors:{description:"If true, the CLI is allowed to use colors in its output",type:"BOOLEAN",default:Zk,defaultText:""},enableHyperlinks:{description:"If true, the CLI is allowed to use hyperlinks in its output",type:"BOOLEAN",default:X4,defaultText:""},enableInlineBuilds:{description:"If true, the CLI will print the build output on the command line",type:"BOOLEAN",default:Lp.isCI,defaultText:""},enableMessageNames:{description:"If true, the CLI will prefix most messages with codes suitable for search engines",type:"BOOLEAN",default:!0},enableProgressBars:{description:"If true, the CLI is allowed to show a progress bar for long-running events",type:"BOOLEAN",default:!Lp.isCI,defaultText:""},enableTimers:{description:"If true, the CLI is allowed to print the time spent executing commands",type:"BOOLEAN",default:!0},enableTips:{description:"If true, installs will print a helpful message every day of the week",type:"BOOLEAN",default:!Lp.isCI,defaultText:""},preferInteractive:{description:"If true, the CLI will automatically use the interactive mode when called from a TTY",type:"BOOLEAN",default:!1},preferTruncatedLines:{description:"If true, the CLI will truncate lines that would go beyond the size of the terminal",type:"BOOLEAN",default:!1},progressBarStyle:{description:"Which style of progress bar should be used (only when progress bars are enabled)",type:"STRING",default:void 0,defaultText:""},defaultLanguageName:{description:"Default language mode that should be used when a package doesn't offer any insight",type:"STRING",default:"node"},defaultProtocol:{description:"Default resolution protocol used when resolving pure semver and tag ranges",type:"STRING",default:"npm:"},enableTransparentWorkspaces:{description:"If false, Yarn won't automatically resolve workspace dependencies unless they use the `workspace:` protocol",type:"BOOLEAN",default:!0},supportedArchitectures:{description:"Architectures that Yarn will fetch and inject into the resolver",type:"SHAPE",properties:{os:{description:"Array of supported process.platform strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},cpu:{description:"Array of supported process.arch strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},libc:{description:"Array of supported libc libraries, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]}}},enableMirror:{description:"If true, the downloaded packages will be retrieved and stored in both the local and global folders",type:"BOOLEAN",default:!0},enableNetwork:{description:"If false, Yarn will refuse to use the network if required to",type:"BOOLEAN",default:!0},enableOfflineMode:{description:"If true, Yarn will attempt to retrieve files and metadata from the global cache rather than the network",type:"BOOLEAN",default:!1},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},unsafeHttpWhitelist:{description:"List of the hostnames for which http queries are allowed (glob patterns are supported)",type:"STRING",default:[],isArray:!0},httpTimeout:{description:"Timeout of each http request",type:"DURATION",unit:"ms",default:"1m"},httpRetry:{description:"Retry times on http failure",type:"NUMBER",default:3},networkConcurrency:{description:"Maximal number of concurrent requests",type:"NUMBER",default:50},taskPoolConcurrency:{description:"Maximal amount of concurrent heavy task processing",type:"NUMBER",default:fj()},taskPoolMode:{description:"Execution strategy for heavy tasks",type:"STRING",values:["async","workers"],default:"workers"},networkSettings:{description:"Network settings per hostname (glob patterns are supported)",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{httpsCaFilePath:{description:"Path to file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},enableNetwork:{description:"If false, the package manager will refuse to use the network if required to",type:"BOOLEAN",default:null},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null}}}},httpsCaFilePath:{description:"A path to a file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null},enableStrictSsl:{description:"If false, SSL certificate errors will be ignored",type:"BOOLEAN",default:!0},logFilters:{description:"Overrides for log levels",type:"SHAPE",isArray:!0,concatenateValues:!0,properties:{code:{description:"Code of the messages covered by this override",type:"STRING",default:void 0},text:{description:"Code of the texts covered by this override",type:"STRING",default:void 0},pattern:{description:"Code of the patterns covered by this override",type:"STRING",default:void 0},level:{description:"Log level override, set to null to remove override",type:"STRING",values:Object.values(eQ),isNullable:!0,default:void 0}}},enableTelemetry:{description:"If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry",type:"BOOLEAN",default:!0},telemetryInterval:{description:"Minimal amount of time between two telemetry uploads",type:"DURATION",unit:"d",default:"7d"},telemetryUserId:{description:"If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.",type:"STRING",default:null},enableHardenedMode:{description:"If true, automatically enable --check-resolutions --refresh-lockfile on installs",type:"BOOLEAN",default:Lp.isPR&&Rnt,defaultText:""},enableScripts:{description:"If true, packages are allowed to have install scripts by default",type:"BOOLEAN",default:!0},enableStrictSettings:{description:"If true, unknown settings will cause Yarn to abort",type:"BOOLEAN",default:!0},enableImmutableCache:{description:"If true, the cache is reputed immutable and actions that would modify it will throw",type:"BOOLEAN",default:!1},enableCacheClean:{description:"If false, disallows the `cache clean` command",type:"BOOLEAN",default:!0},checksumBehavior:{description:"Enumeration defining what to do when a checksum doesn't match expectations",type:"STRING",default:"throw"},injectEnvironmentFiles:{description:"List of all the environment files that Yarn should inject inside the process when it starts",type:"ABSOLUTE_PATH",default:[".env.yarn?"],isArray:!0},packageExtensions:{description:"Map of package corrections to apply on the dependency tree",type:"MAP",valueDefinition:{description:"The extension that will be applied to any package whose version matches the specified range",type:"SHAPE",properties:{dependencies:{description:"The set of dependencies that must be made available to the current package in order for it to work properly",type:"MAP",valueDefinition:{description:"A range",type:"STRING"}},peerDependencies:{description:"Inherited dependencies - the consumer of the package will be tasked to provide them",type:"MAP",valueDefinition:{description:"A semver range",type:"STRING"}},peerDependenciesMeta:{description:"Extra information related to the dependencies listed in the peerDependencies field",type:"MAP",valueDefinition:{description:"The peerDependency meta",type:"SHAPE",properties:{optional:{description:"If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error",type:"BOOLEAN",default:!1}}}}}}}};Hnt=process.platform==="win32"?Unt:_nt;ze=class t{constructor(e){this.isCI=Lp.isCI;this.projectCwd=null;this.plugins=new Map;this.settings=new Map;this.values=new Map;this.sources=new Map;this.invalid=new Map;this.env={};this.limits=new Map;this.packageExtensions=null;this.startingCwd=e}static{this.deleteProperty=Symbol()}static{this.telemetry=null}static create(e,r,s){let a=new t(e);typeof r<"u"&&!(r instanceof Map)&&(a.projectCwd=r),a.importSettings(mT);let n=typeof s<"u"?s:r instanceof Map?r:new Map;for(let[c,f]of n)a.activatePlugin(c,f);return a}static async find(e,r,{strict:s=!0,usePathCheck:a=null,useRc:n=!0}={}){let c=Mnt();delete c.rcFilename;let f=new t(e),p=await t.findRcFiles(e),h=await t.findFolderRcFile(fI());h&&(p.find(me=>me.path===h.path)||p.unshift(h));let E=Eue(p.map(le=>[le.path,le.data])),C=vt.dot,S=new Set(Object.keys(mT)),P=({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe})=>({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe}),I=({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe,...Be})=>{let Ce={};for(let[g,we]of Object.entries(Be))S.has(g)&&(Ce[g]=we);return Ce},R=({yarnPath:le,ignorePath:me,...pe})=>{let Be={};for(let[Ce,g]of Object.entries(pe))S.has(Ce)||(Be[Ce]=g);return Be};if(f.importSettings(P(mT)),f.useWithSource("",P(c),e,{strict:!1}),E){let[le,me]=E;f.useWithSource(le,P(me),C,{strict:!1})}if(a){if(await jnt({configuration:f,selfPath:a})!==null)return f;f.useWithSource("",{ignorePath:!0},e,{strict:!1,overwrite:!0})}let N=await t.findProjectCwd(e);f.startingCwd=e,f.projectCwd=N;let U=Object.assign(Object.create(null),process.env);f.env=U;let W=await Promise.all(f.get("injectEnvironmentFiles").map(async le=>{let me=le.endsWith("?")?await ce.readFilePromise(le.slice(0,-1),"utf8").catch(()=>""):await ce.readFilePromise(le,"utf8");return(0,ape.parse)(me)}));for(let le of W)for(let[me,pe]of Object.entries(le))f.env[me]=Vk(pe,{env:U});if(f.importSettings(I(mT)),f.useWithSource("",I(c),e,{strict:s}),E){let[le,me]=E;f.useWithSource(le,I(me),C,{strict:s})}let ee=le=>"default"in le?le.default:le,ie=new Map([["@@core",rue]]);if(r!==null)for(let le of r.plugins.keys())ie.set(le,ee(r.modules.get(le)));for(let[le,me]of ie)f.activatePlugin(le,me);let ue=new Map([]);if(r!==null){let le=new Map;for(let[Be,Ce]of r.modules)le.set(Be,()=>Ce);let me=new Set,pe=async(Be,Ce)=>{let{factory:g,name:we}=Pp(Be);if(!g||me.has(we))return;let ye=new Map(le),Ae=Z=>{if((0,lpe.isBuiltin)(Z))return Pp(Z);if(ye.has(Z))return ye.get(Z)();throw new nt(`This plugin cannot access the package referenced via ${Z} which is neither a builtin, nor an exposed entry`)},se=await qE(async()=>ee(await g(Ae)),Z=>`${Z} (when initializing ${we}, defined in ${Ce})`);le.set(we,()=>se),me.add(we),ue.set(we,se)};if(c.plugins)for(let Be of c.plugins.split(";")){let Ce=J.resolve(e,fe.toPortablePath(Be));await pe(Ce,"")}for(let{path:Be,cwd:Ce,data:g}of p)if(n&&Array.isArray(g.plugins))for(let we of g.plugins){let ye=typeof we!="string"?we.path:we,Ae=we?.spec??"",se=we?.checksum??"";if(ov.has(Ae))continue;let Z=J.resolve(Ce,fe.toPortablePath(ye));if(!await ce.existsPromise(Z)){if(!Ae){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,".gitignore",ht.NAME),rt=Ht(f,f.values.get("rcFilename"),ht.NAME),Fe=Ht(f,"https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored",ht.URL);throw new nt(`Missing source for the ${mt} plugin - please try to remove the plugin from ${rt} then reinstall it manually. This error usually occurs because ${j} is incorrect, check ${Fe} to make sure your plugin folder isn't gitignored.`)}if(!Ae.match(/^https?:/)){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,f.values.get("rcFilename"),ht.NAME);throw new nt(`Failed to recognize the source for the ${mt} plugin - please try to delete the plugin from ${j} then reinstall it manually.`)}let De=await lj(Ae,{configuration:f}),Re=us(De);if(se&&se!==Re){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,f.values.get("rcFilename"),ht.NAME),rt=Ht(f,`yarn plugin import ${Ae}`,ht.CODE);throw new nt(`Failed to fetch the ${mt} plugin from its remote location: its checksum seems to have changed. If this is expected, please remove the plugin from ${j} then run ${rt} to reimport it.`)}await ce.mkdirPromise(J.dirname(Z),{recursive:!0}),await ce.writeFilePromise(Z,De)}await pe(Z,Be)}}for(let[le,me]of ue)f.activatePlugin(le,me);if(f.useWithSource("",R(c),e,{strict:s}),E){let[le,me]=E;f.useWithSource(le,R(me),C,{strict:s})}return f.get("enableGlobalCache")&&(f.values.set("cacheFolder",`${f.get("globalFolder")}/cache`),f.sources.set("cacheFolder","")),f}static async findRcFiles(e){let r=gj(),s=[],a=e,n=null;for(;a!==n;){n=a;let c=J.join(n,r);if(ce.existsSync(c)){let f,p;try{p=await ce.readFilePromise(c,"utf8"),f=ls(p)}catch{let h="";throw p?.match(/^\s+(?!-)[^:]+\s+\S+/m)&&(h=" (in particular, make sure you list the colons after each key name)"),new nt(`Parse error when loading ${c}; please check it's proper Yaml${h}`)}s.unshift({path:c,cwd:n,data:f})}a=J.dirname(n)}return s}static async findFolderRcFile(e){let r=J.join(e,Er.rc),s;try{s=await ce.readFilePromise(r,"utf8")}catch(n){if(n.code==="ENOENT")return null;throw n}let a=ls(s);return{path:r,cwd:e,data:a}}static async findProjectCwd(e){let r=null,s=e,a=null;for(;s!==a;){if(a=s,ce.existsSync(J.join(a,Er.lockfile)))return a;ce.existsSync(J.join(a,Er.manifest))&&(r=a),s=J.dirname(a)}return r}static async updateConfiguration(e,r,s={}){let a=gj(),n=J.join(e,a),c=ce.existsSync(n)?ls(await ce.readFilePromise(n,"utf8")):{},f=!1,p;if(typeof r=="function"){try{p=r(c)}catch{p=r({})}if(p===c)return!1}else{p=c;for(let h of Object.keys(r)){let E=c[h],C=r[h],S;if(typeof C=="function")try{S=C(E)}catch{S=C(void 0)}else S=C;E!==S&&(S===t.deleteProperty?delete p[h]:p[h]=S,f=!0)}if(!f)return!1}return await ce.changeFilePromise(n,nl(p),{automaticNewlines:!0}),!0}static async addPlugin(e,r){r.length!==0&&await t.updateConfiguration(e,s=>{let a=s.plugins??[];if(a.length===0)return{...s,plugins:r};let n=[],c=[...r];for(let f of a){let p=typeof f!="string"?f.path:f,h=c.find(E=>E.path===p);h?(n.push(h),c=c.filter(E=>E!==h)):n.push(f)}return n.push(...c),{...s,plugins:n}})}static async updateHomeConfiguration(e){let r=fI();return await t.updateConfiguration(r,e)}activatePlugin(e,r){this.plugins.set(e,r),typeof r.configuration<"u"&&this.importSettings(r.configuration)}importSettings(e){for(let[r,s]of Object.entries(e))if(s!=null){if(this.settings.has(r))throw new Error(`Cannot redefine settings "${r}"`);this.settings.set(r,s),this.values.set(r,Ej(this,s))}}useWithSource(e,r,s,a){try{this.use(e,r,s,a)}catch(n){throw n.message+=` (in ${Ht(this,e,ht.PATH)})`,n}}use(e,r,s,{strict:a=!0,overwrite:n=!1}={}){a=a&&this.get("enableStrictSettings");for(let c of["enableStrictSettings",...Object.keys(r)]){let f=r[c],p=H8(f);if(p&&(e=p),typeof f>"u"||c==="plugins"||e===""&&Fnt.has(c))continue;if(c==="rcFilename")throw new nt(`The rcFilename settings can only be set via ${`${ET}RC_FILENAME`.toUpperCase()}, not via a rc file`);let h=this.settings.get(c);if(!h){let C=fI(),S=e[0]!=="<"?J.dirname(e):null;if(a&&!(S!==null?C===S:!1))throw new nt(`Unrecognized or legacy configuration settings found: ${c} - run "yarn config" to see the list of settings supported in Yarn`);this.invalid.set(c,e);continue}if(this.sources.has(c)&&!(n||h.type==="MAP"||h.isArray&&h.concatenateValues))continue;let E;try{E=yj(this,c,f,h,s)}catch(C){throw C.message+=` in ${Ht(this,e,ht.PATH)}`,C}if(c==="enableStrictSettings"&&e!==""){a=E;continue}if(h.type==="MAP"){let C=this.values.get(c);this.values.set(c,new Map(n?[...C,...E]:[...E,...C])),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else if(h.isArray&&h.concatenateValues){let C=this.values.get(c);this.values.set(c,n?[...C,...E]:[...E,...C]),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else this.values.set(c,E),this.sources.set(c,e)}}get(e){if(!this.values.has(e))throw new Error(`Invalid configuration key "${e}"`);return this.values.get(e)}getSpecial(e,{hideSecrets:r=!1,getNativePaths:s=!1}){let a=this.get(e),n=this.settings.get(e);if(typeof n>"u")throw new nt(`Couldn't find a configuration settings named "${e}"`);return yT(a,n,{hideSecrets:r,getNativePaths:s})}getSubprocessStreams(e,{header:r,prefix:s,report:a}){let n,c,f=ce.createWriteStream(e);if(this.get("enableInlineBuilds")){let p=a.createStreamReporter(`${s} ${Ht(this,"STDOUT","green")}`),h=a.createStreamReporter(`${s} ${Ht(this,"STDERR","red")}`);n=new hj.PassThrough,n.pipe(p),n.pipe(f),c=new hj.PassThrough,c.pipe(h),c.pipe(f)}else n=f,c=f,typeof r<"u"&&n.write(`${r} +`);return{stdout:n,stderr:c}}makeResolver(){let e=[];for(let r of this.plugins.values())for(let s of r.resolvers||[])e.push(new s);return new rm([new FQ,new Ei,...e])}makeFetcher(){let e=[];for(let r of this.plugins.values())for(let s of r.fetchers||[])e.push(new s);return new aI([new lI,new cI,...e])}getLinkers(){let e=[];for(let r of this.plugins.values())for(let s of r.linkers||[])e.push(new s);return e}getSupportedArchitectures(){let e=sv(),r=this.get("supportedArchitectures"),s=r.get("os");s!==null&&(s=s.map(c=>c==="current"?e.os:c));let a=r.get("cpu");a!==null&&(a=a.map(c=>c==="current"?e.cpu:c));let n=r.get("libc");return n!==null&&(n=Wl(n,c=>c==="current"?e.libc??Wl.skip:c)),{os:s,cpu:a,libc:n}}isInteractive({interactive:e,stdout:r}){return r.isTTY?e??this.get("preferInteractive"):!1}async getPackageExtensions(){if(this.packageExtensions!==null)return this.packageExtensions;this.packageExtensions=new Map;let e=this.packageExtensions,r=(s,a,{userProvided:n=!1}={})=>{if(!cl(s.range))throw new Error("Only semver ranges are allowed as keys for the packageExtensions setting");let c=new Ut;c.load(a,{yamlCompatibilityMode:!0});let f=xB(e,s.identHash),p=[];f.push([s.range,p]);let h={status:"inactive",userProvided:n,parentDescriptor:s};for(let E of c.dependencies.values())p.push({...h,type:"Dependency",descriptor:E});for(let E of c.peerDependencies.values())p.push({...h,type:"PeerDependency",descriptor:E});for(let[E,C]of c.peerDependenciesMeta)for(let[S,P]of Object.entries(C))p.push({...h,type:"PeerDependencyMeta",selector:E,key:S,value:P})};await this.triggerHook(s=>s.registerPackageExtensions,this,r);for(let[s,a]of this.get("packageExtensions"))r(C0(s,!0),Yk(a),{userProvided:!0});return e}normalizeLocator(e){return cl(e.reference)?Ws(e,`${this.get("defaultProtocol")}${e.reference}`):Mp.test(e.reference)?Ws(e,`${this.get("defaultProtocol")}${e.reference}`):e}normalizeDependency(e){return cl(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):Mp.test(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):e}normalizeDependencyMap(e){return new Map([...e].map(([r,s])=>[r,this.normalizeDependency(s)]))}normalizePackage(e,{packageExtensions:r}){let s=LB(e),a=r.get(e.identHash);if(typeof a<"u"){let c=e.version;if(c!==null){for(let[f,p]of a)if(Zf(c,f))for(let h of p)switch(h.status==="inactive"&&(h.status="redundant"),h.type){case"Dependency":typeof s.dependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.dependencies.set(h.descriptor.identHash,this.normalizeDependency(h.descriptor)));break;case"PeerDependency":typeof s.peerDependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.peerDependencies.set(h.descriptor.identHash,h.descriptor));break;case"PeerDependencyMeta":{let E=s.peerDependenciesMeta.get(h.selector);(typeof E>"u"||!Object.hasOwn(E,h.key)||E[h.key]!==h.value)&&(h.status="active",Yl(s.peerDependenciesMeta,h.selector,()=>({}))[h.key]=h.value)}break;default:G4(h)}}}let n=c=>c.scope?`${c.scope}__${c.name}`:`${c.name}`;for(let c of s.peerDependenciesMeta.keys()){let f=Sa(c);s.peerDependencies.has(f.identHash)||s.peerDependencies.set(f.identHash,On(f,"*"))}for(let c of s.peerDependencies.values()){if(c.scope==="types")continue;let f=n(c),p=Da("types",f),h=un(p);s.peerDependencies.has(p.identHash)||s.peerDependenciesMeta.has(h)||s.dependencies.has(p.identHash)||(s.peerDependencies.set(p.identHash,On(p,"*")),s.peerDependenciesMeta.set(h,{optional:!0}))}return s.dependencies=new Map(qs(s.dependencies,([,c])=>al(c))),s.peerDependencies=new Map(qs(s.peerDependencies,([,c])=>al(c))),s}getLimit(e){return Yl(this.limits,e,()=>(0,cpe.default)(this.get(e)))}async triggerHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);n&&await n(...r)}}async triggerMultipleHooks(e,r){for(let s of r)await this.triggerHook(e,...s)}async reduceHook(e,r,...s){let a=r;for(let n of this.plugins.values()){let c=n.hooks;if(!c)continue;let f=e(c);f&&(a=await f(a,...s))}return a}async firstHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);if(!n)continue;let c=await n(...r);if(typeof c<"u")return c}return null}}});var qr={};Vt(qr,{EndStrategy:()=>Bj,ExecError:()=>CT,PipeError:()=>lv,execvp:()=>Aj,pipevp:()=>Wu});function om(t){return t!==null&&typeof t.fd=="number"}function Ij(){}function Cj(){for(let t of am)t.kill()}async function Wu(t,e,{cwd:r,env:s=process.env,strict:a=!1,stdin:n=null,stdout:c,stderr:f,end:p=2}){let h=["pipe","pipe","pipe"];n===null?h[0]="ignore":om(n)&&(h[0]=n),om(c)&&(h[1]=c),om(f)&&(h[2]=f);let E=(0,wj.default)(t,e,{cwd:fe.fromPortablePath(r),env:{...s,PWD:fe.fromPortablePath(r)},stdio:h});am.add(E),am.size===1&&(process.on("SIGINT",Ij),process.on("SIGTERM",Cj)),!om(n)&&n!==null&&n.pipe(E.stdin),om(c)||E.stdout.pipe(c,{end:!1}),om(f)||E.stderr.pipe(f,{end:!1});let C=()=>{for(let S of new Set([c,f]))om(S)||S.end()};return new Promise((S,P)=>{E.on("error",I=>{am.delete(E),am.size===0&&(process.off("SIGINT",Ij),process.off("SIGTERM",Cj)),(p===2||p===1)&&C(),P(I)}),E.on("close",(I,R)=>{am.delete(E),am.size===0&&(process.off("SIGINT",Ij),process.off("SIGTERM",Cj)),(p===2||p===1&&I!==0)&&C(),I===0||!a?S({code:vj(I,R)}):P(new lv({fileName:t,code:I,signal:R}))})})}async function Aj(t,e,{cwd:r,env:s=process.env,encoding:a="utf8",strict:n=!1}){let c=["ignore","pipe","pipe"],f=[],p=[],h=fe.fromPortablePath(r);typeof s.PWD<"u"&&(s={...s,PWD:h});let E=(0,wj.default)(t,e,{cwd:h,env:s,stdio:c});return E.stdout.on("data",C=>{f.push(C)}),E.stderr.on("data",C=>{p.push(C)}),await new Promise((C,S)=>{E.on("error",P=>{let I=ze.create(r),R=Ht(I,t,ht.PATH);S(new jt(1,`Process ${R} failed to spawn`,N=>{N.reportError(1,` ${Kf(I,{label:"Thrown Error",value:_u(ht.NO_HINT,P.message)})}`)}))}),E.on("close",(P,I)=>{let R=a==="buffer"?Buffer.concat(f):Buffer.concat(f).toString(a),N=a==="buffer"?Buffer.concat(p):Buffer.concat(p).toString(a);P===0||!n?C({code:vj(P,I),stdout:R,stderr:N}):S(new CT({fileName:t,code:P,signal:I,stdout:R,stderr:N}))})})}function vj(t,e){let r=Gnt.get(e);return typeof r<"u"?128+r:t??1}function qnt(t,e,{configuration:r,report:s}){s.reportError(1,` ${Kf(r,t!==null?{label:"Exit Code",value:_u(ht.NUMBER,t)}:{label:"Exit Signal",value:_u(ht.CODE,e)})}`)}var wj,Bj,lv,CT,am,Gnt,dT=Xe(()=>{Dt();wj=ut(_U());av();Tc();xc();Bj=(s=>(s[s.Never=0]="Never",s[s.ErrorCode=1]="ErrorCode",s[s.Always=2]="Always",s))(Bj||{}),lv=class extends jt{constructor({fileName:e,code:r,signal:s}){let a=ze.create(J.cwd()),n=Ht(a,e,ht.PATH);super(1,`Child ${n} reported an error`,c=>{qnt(r,s,{configuration:a,report:c})}),this.code=vj(r,s)}},CT=class extends lv{constructor({fileName:e,code:r,signal:s,stdout:a,stderr:n}){super({fileName:e,code:r,signal:s}),this.stdout=a,this.stderr=n}};am=new Set;Gnt=new Map([["SIGINT",2],["SIGQUIT",3],["SIGKILL",9],["SIGTERM",15]])});function Ape(t){fpe=t}function cv(){return typeof Sj>"u"&&(Sj=fpe()),Sj}var Sj,fpe,Dj=Xe(()=>{fpe=()=>{throw new Error("Assertion failed: No libzip instance is available, and no factory was configured")}});var ppe=_((wT,Pj)=>{var Wnt=Object.assign({},Ie("fs")),bj=function(){var t=typeof document<"u"&&document.currentScript?document.currentScript.src:void 0;return typeof __filename<"u"&&(t=t||__filename),function(e){e=e||{};var r=typeof e<"u"?e:{},s,a;r.ready=new Promise(function(Ke,st){s=Ke,a=st});var n={},c;for(c in r)r.hasOwnProperty(c)&&(n[c]=r[c]);var f=[],p="./this.program",h=function(Ke,st){throw st},E=!1,C=!0,S="";function P(Ke){return r.locateFile?r.locateFile(Ke,S):S+Ke}var I,R,N,U;C&&(E?S=Ie("path").dirname(S)+"/":S=__dirname+"/",I=function(st,St){var lr=Me(st);return lr?St?lr:lr.toString():(N||(N=Wnt),U||(U=Ie("path")),st=U.normalize(st),N.readFileSync(st,St?null:"utf8"))},R=function(st){var St=I(st,!0);return St.buffer||(St=new Uint8Array(St)),we(St.buffer),St},process.argv.length>1&&(p=process.argv[1].replace(/\\/g,"/")),f=process.argv.slice(2),h=function(Ke){process.exit(Ke)},r.inspect=function(){return"[Emscripten Module object]"});var W=r.print||console.log.bind(console),ee=r.printErr||console.warn.bind(console);for(c in n)n.hasOwnProperty(c)&&(r[c]=n[c]);n=null,r.arguments&&(f=r.arguments),r.thisProgram&&(p=r.thisProgram),r.quit&&(h=r.quit);var ie=0,ue=function(Ke){ie=Ke},le;r.wasmBinary&&(le=r.wasmBinary);var me=r.noExitRuntime||!0;typeof WebAssembly!="object"&&rs("no native wasm support detected");function pe(Ke,st,St){switch(st=st||"i8",st.charAt(st.length-1)==="*"&&(st="i32"),st){case"i1":return Ve[Ke>>0];case"i8":return Ve[Ke>>0];case"i16":return mh((Ke>>1)*2);case"i32":return to((Ke>>2)*4);case"i64":return to((Ke>>2)*4);case"float":return Af((Ke>>2)*4);case"double":return dh((Ke>>3)*8);default:rs("invalid type for getValue: "+st)}return null}var Be,Ce=!1,g;function we(Ke,st){Ke||rs("Assertion failed: "+st)}function ye(Ke){var st=r["_"+Ke];return we(st,"Cannot call unknown function "+Ke+", make sure it is exported"),st}function Ae(Ke,st,St,lr,te){var Ee={string:function(qi){var Tn=0;if(qi!=null&&qi!==0){var Ga=(qi.length<<2)+1;Tn=wi(Ga),mt(qi,Tn,Ga)}return Tn},array:function(qi){var Tn=wi(qi.length);return Fe(qi,Tn),Tn}};function Oe(qi){return st==="string"?De(qi):st==="boolean"?!!qi:qi}var dt=ye(Ke),Et=[],bt=0;if(lr)for(var tr=0;tr=St)&&ke[lr];)++lr;return Z.decode(ke.subarray(Ke,lr))}function Re(Ke,st,St,lr){if(!(lr>0))return 0;for(var te=St,Ee=St+lr-1,Oe=0;Oe=55296&&dt<=57343){var Et=Ke.charCodeAt(++Oe);dt=65536+((dt&1023)<<10)|Et&1023}if(dt<=127){if(St>=Ee)break;st[St++]=dt}else if(dt<=2047){if(St+1>=Ee)break;st[St++]=192|dt>>6,st[St++]=128|dt&63}else if(dt<=65535){if(St+2>=Ee)break;st[St++]=224|dt>>12,st[St++]=128|dt>>6&63,st[St++]=128|dt&63}else{if(St+3>=Ee)break;st[St++]=240|dt>>18,st[St++]=128|dt>>12&63,st[St++]=128|dt>>6&63,st[St++]=128|dt&63}}return st[St]=0,St-te}function mt(Ke,st,St){return Re(Ke,ke,st,St)}function j(Ke){for(var st=0,St=0;St=55296&&lr<=57343&&(lr=65536+((lr&1023)<<10)|Ke.charCodeAt(++St)&1023),lr<=127?++st:lr<=2047?st+=2:lr<=65535?st+=3:st+=4}return st}function rt(Ke){var st=j(Ke)+1,St=La(st);return St&&Re(Ke,Ve,St,st),St}function Fe(Ke,st){Ve.set(Ke,st)}function Ne(Ke,st){return Ke%st>0&&(Ke+=st-Ke%st),Ke}var Pe,Ve,ke,it,Ue,x,w,b,y,F;function z(Ke){Pe=Ke,r.HEAP_DATA_VIEW=F=new DataView(Ke),r.HEAP8=Ve=new Int8Array(Ke),r.HEAP16=it=new Int16Array(Ke),r.HEAP32=x=new Int32Array(Ke),r.HEAPU8=ke=new Uint8Array(Ke),r.HEAPU16=Ue=new Uint16Array(Ke),r.HEAPU32=w=new Uint32Array(Ke),r.HEAPF32=b=new Float32Array(Ke),r.HEAPF64=y=new Float64Array(Ke)}var X=r.INITIAL_MEMORY||16777216,$,oe=[],xe=[],Te=[],lt=!1;function Ct(){if(r.preRun)for(typeof r.preRun=="function"&&(r.preRun=[r.preRun]);r.preRun.length;)Pt(r.preRun.shift());Ts(oe)}function qt(){lt=!0,Ts(xe)}function ir(){if(r.postRun)for(typeof r.postRun=="function"&&(r.postRun=[r.postRun]);r.postRun.length;)Pr(r.postRun.shift());Ts(Te)}function Pt(Ke){oe.unshift(Ke)}function gn(Ke){xe.unshift(Ke)}function Pr(Ke){Te.unshift(Ke)}var Ir=0,Or=null,on=null;function ai(Ke){Ir++,r.monitorRunDependencies&&r.monitorRunDependencies(Ir)}function Io(Ke){if(Ir--,r.monitorRunDependencies&&r.monitorRunDependencies(Ir),Ir==0&&(Or!==null&&(clearInterval(Or),Or=null),on)){var st=on;on=null,st()}}r.preloadedImages={},r.preloadedAudios={};function rs(Ke){r.onAbort&&r.onAbort(Ke),Ke+="",ee(Ke),Ce=!0,g=1,Ke="abort("+Ke+"). Build with -s ASSERTIONS=1 for more info.";var st=new WebAssembly.RuntimeError(Ke);throw a(st),st}var $s="data:application/octet-stream;base64,";function Co(Ke){return Ke.startsWith($s)}var ji="data:application/octet-stream;base64,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QCAAKAJIIgEgASADaiADEAcaAkAgAyAAKAJsIgFNBEAgACABIANrNgJsDAELIABCADcCbAsgACAAKAJoIANrIgE2AmggACAAKAJYIANrNgJYIAEgACgChC5JBEAgACABNgKELgsgAEH8gAEoAgARAwAgAiADaiECCwJAIAAoAgAiASgCBCIERQ0AIAAoAjwhBSAAIAIgBCACIARJGyICBH8gACgCSCAAKAJoaiAFaiEFIAEgBCACazYCBAJAAkACQAJAIAEoAhwiBCgCFEEBaw4CAQACCyAEQaABaiAFIAEoAgAgAkHcgAEoAgARCAAMAgsgASABKAIwIAUgASgCACACQcSAASgCABEEADYCMAwBCyAFIAEoAgAgAhAHGgsgASABKAIAIAJqNgIAIAEgASgCCCACajYCCCAAKAI8BSAFCyACaiICNgI8AkAgACgChC4iASACakEDSQ0AIAAoAmggAWshAQJAIAAoAnRBgQhPBEAgACAAIAAoAkggAWoiAi0AACACLQABIAAoAnwRAAA2AlQMAQsgAUUNACAAIAFBAWsgACgChAERAgAaCyAAKAKELiAAKAI8IgJBAUZrIgRFDQAgACABIAQgACgCgAERBQAgACAAKAKELiAEazYChC4gACgCPCECCyACQYUCSw0AIAAoAgAoAgRFDQAgACgCMCEBDAELCwJAIAAoAkQiAiAAKAJAIgNNDQAgAAJ/IAAoAjwgACgCaGoiASADSwRAIAAoAkggAWpBACACIAFrIgNBggIgA0GCAkkbIgMQGSABIANqDAELIAFBggJqIgEgA00NASAAKAJIIANqQQAgAiADayICIAEgA2siAyACIANJGyIDEBkgACgCQCADags2AkALC50CAQF/AkAgAAJ/IAAoAqAuIgFBwABGBEAgACgCBCAAKAIQaiAAKQOYLjcAACAAQgA3A5guIAAgACgCEEEIajYCEEEADAELIAFBIE4EQCAAKAIEIAAoAhBqIAApA5guPgAAIAAgAEGcLmo1AgA3A5guIAAgACgCEEEEajYCECAAIAAoAqAuQSBrIgE2AqAuCyABQRBOBEAgACgCBCAAKAIQaiAAKQOYLj0AACAAIAAoAhBBAmo2AhAgACAAKQOYLkIQiDcDmC4gACAAKAKgLkEQayIBNgKgLgsgAUEISA0BIAAgACgCECIBQQFqNgIQ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KAJUIgEEfyABQQAgARAiEBkgACgCVAVBAAsQBiAAQQA2AlQLC9wMAgl/AX4jAEFAaiIGJAACQAJAAkACQAJAIAEoAjBBABAjIgVBAkZBACABKAI4QQAQIyIEQQFGGw0AIAVBAUZBACAEQQJGGw0AIAVBAkciAw0BIARBAkcNAQsgASABLwEMQYAQcjsBDEEAIQMMAQsgASABLwEMQf/vA3E7AQxBACEFIANFBEBB9eABIAEoAjAgAEEIahBpIgVFDQILIAJBgAJxBEAgBSEDDAELIARBAkcEQCAFIQMMAQtB9cYBIAEoAjggAEEIahBpIgNFBEAgBRAcDAILIAMgBTYCAAsgASABLwEMQf7/A3EgAS8BUiIFQQBHcjsBDAJAAkACQAJAAn8CQAJAIAEpAyhC/v///w9WDQAgASkDIEL+////D1YNACACQYAEcUUNASABKQNIQv////8PVA0BCyAFQYECa0H//wNxQQNJIQdBAQwBCyAFQYECa0H//wNxIQQgAkGACnFBgApHDQEgBEEDSSEHQQALIQkgBkIcEBciBEUEQCAAQQhqIgAEQCAAQQA2AgQgAEEONgIACyADEBwMBQsgAkGACHEhBQJAAkAgAkGAAnEEQAJAIAUNACABKQMgQv////8PVg0AIAEpAyhCgICAgBBUDQMLIAQgASkDKBAYIAEpAyAhDAwBCwJAAkACQCAFDQAgASkDIEL/////D1YNACABKQMoIgxC/////w9WDQEgASkDSEKAgICAEFQNBAsgASkDKCIMQv////8PVA0BCyAEIAwQGAsgASkDICIMQv////8PWgRAIAQgDBAYCyABKQNIIgxC/////w9UDQELIAQgDBAYCyAELQAARQRAIABBCGoiAARAIABBADYCBCAAQRQ2AgALIAQQCCADEBwMBQtBASEKQQEgBC0AAAR+IAQpAxAFQgALp0H//wNxIAYQRyEFIAQQCCAFIAM2AgAgBw0BDAILIAMhBSAEQQJLDQELIAZCBxAXIgRFBEAgAEEIaiIABEAgAEEANgIEIABBDjYCAAsgBRAcDAMLIARBAhANIARBhxJBAhAsIAQgAS0AUhBwIAQgAS8BEBANIAQtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAsgBBAIDAILQYGyAkEHIAYQRyEDIAQQCCADIAU2AgBBASEL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NgIEIABB7A42AhggGiEdDAMLIARB/wFxIgJBwABxRQRAIAggDSADLwECQQJ0aiAdp0F/IAJ0QX9zcUECdGoiAy0AASICayEIIB0gAq2IIR0gAy0AACIERQ0HDAELCyAEQSBxBEAgB0G//gA2AgQgASECDAgLIAdB0f4ANgIEIABB0A42AhggASECDAcLIB1BfyAGdEF/c62DIBt8IhunIQUgCCAEQQ9xIgNrIQggGiAErUIPg4ghHSABIBdrIgYgAjMBAiAaQX8gA3RBf3Otg3ynIgRPDQIgBCAGayIGIBlNDQEgBygCjEdFDQEgB0HR/gA2AgQgAEG5DDYCGAsgASECIAshBQwFCwJAIA5FBEAgDCARIAZraiEDDAELIAYgDk0EQCAMIA4gBmtqIQMMAQsgDCARIAYgDmsiBmtqIQMgBSAGTQ0AIAUgBmshBQJAAkAgASADTSABIA8gAWusIhogBq0iGyAaIBtUGyIapyIGaiICIANLcQ0AIAMgBmogAUsgASADT3ENACABIAMgBhAHGiACIQEMAQsgASADIAMgAWsiASABQR91IgFqIAFzIgIQByACaiEBIBogAq0iHn0iHFANACACIANqIQIDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgASACKQAANwAAIAEgAikAGDcAGCABIAIpABA3ABAgASACKQAINwAIIBpCIH0hGiACQSBqIQIgAUEgaiEBIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAEgAikAADcAACABIAIpABg3ABggASACKQAQNwAQIAEgAikACDcACCABIAIpADg3ADggASACKQAwNwAwIAEgAikAKDcAKCABIAIpACA3ACAgASACKQBYNwBYIAEgAikAUDcAUCABIAIpAEg3AEggASACKQBANwBAIAEgAikAYDcAYCABIAIpAGg3AGggASACKQBwNwBwIAEgAikAeDcAeCACQYABaiECIAFBgAFqIQEgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAEgAikAADcAACABIAIpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCABIAIpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCABIAIoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCABIAIvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCABIAItAAA6AAAgAkEBaiECIAFBAWohAQsgHEIAUg0ACwsgDiEGIAwhAwsgBSAGSwRAAkACQCABIANNIAEgDyABa6wiGiAGrSIbIBogG1QbIhqnIglqIgIgA0txDQAgAyAJaiABSyABIANPcQ0AIAEgAyAJEAcaDAELIAEgAyADIAFrIgEgAUEfdSIBaiABcyIBEAcgAWohAiAaIAGtIh59IhxQDQAgASADaiEBA0ACQCAcIB4gHCAeVBsiG0IgVARAIBshGgwBCyAbIhpCIH0iIEIFiEIBfEIDgyIfUEUEQANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCAaQiB9IRogAUEgaiEBIAJBIGohAiAfQgF9Ih9CAFINAAsLICBC4ABUDQADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggAiABKQA4NwA4IAIgASkAMDcAMCACIAEpACg3ACggAiABKQAgNwAgIAIgASkAWDcAWCACIAEpAFA3AFAgAiABKQBINwBIIAIgASkAQDcAQCACIAEpAGA3AGAgAiABKQBoNwBoIAIgASkAcDcAcCACIAEpAHg3AHggAUGAAWohASACQYABaiECIBpCgAF9IhpCH1YNAAsLIBpCEFoEQCACIAEpAAA3AAAgAiABKQAINwAIIBpCEH0hGiACQRBqIQIgAUEQaiEBCyAaQghaBEAgAiABKQAANwAAIBpCCH0hGiACQQhqIQIgAUEIaiEBCyAaQgRaBEAgAiABKAAANgAAIBpCBH0hGiACQQRqIQIgAUEEaiEBCyAaQgJaBEAgAiABLwAAOwAAIBpCAn0hGiACQQJqIQIgAUECaiEBCyAcIBt9IRwgGlBFBEAgAiABLQAAOgAAIAJBAWohAiABQQFqIQELIBxCAFINAAsLIAUgBmshAUEAIARrIQUCQCAEQQdLBEAgBCEDDAELIAEgBE0EQCAEIQMMAQsgAiAEayEFA0ACQCACIAUpAAA3AAAgBEEBdCEDIAEgBGshASACIARqIQIgBEEDSw0AIAMhBCABIANLDQELC0EAIANrIQULIAIgBWohBAJAIAUgDyACa6wiGiABrSIbIBogG1QbIhqnIgFIIAVBf0pxDQAgBUEBSCABIARqIAJLcQ0AIAIgBCABEAcgAWohAgwDCyACIAQgAyADQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANAiABIARqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAILAkAgASADTSABIA8gAWusIhogBa0iGyAaIBtUGyIapyIEaiICIANLcQ0AIAMgBGogAUsgASADT3ENACABIAMgBBAHGgwCCyABIAMgAyABayIBIAFBH3UiAWogAXMiARAHIAFqIQIgGiABrSIefSIcUA0BIAEgA2ohAQNAAkAgHCAeIBwgHlQbIhtCIFQEQCAbIRoMAQsgGyIaQiB9IiBCBYhCAXxCA4MiH1BFBEADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggGkIgfSEaIAFBIGohASACQSBqIQIgH0IBfSIfQgBSDQALCyAgQuAAVA0AA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIAIgASkAODcAOCACIAEpADA3ADAgAiABKQAoNwAoIAIgASkAIDcAICACIAEpAFg3AFggAiABKQBQNwBQIAIgASkASDcASCACIAEpAEA3AEAgAiABKQBgNwBgIAIgASkAaDcAaCACIAEpAHA3AHAgAiABKQB4NwB4IAFBgAFqIQEgAkGAAWohAiAaQoABfSIaQh9WDQALCyAaQhBaBEAgAiABKQAANwAAIAIgASkACDcACCAaQhB9IRogAkEQaiECIAFBEGohAQsgGkIIWgRAIAIgASkAADcAACAaQgh9IRogAkEIaiECIAFBCGohAQsgGkIEWgRAIAIgASgAADYAACAaQgR9IRogAkEEaiECIAFBBGohAQsgGkICWgRAIAIgAS8AADsAACAaQgJ9IRogAkECaiECIAFBAmohAQsgHCAbfSEcIBpQRQRAIAIgAS0AADoAACACQQFqIQIgAUEBaiEBCyAcUEUNAAsMAQsCQAJAIBYEQAJAIAQgBUkEQCAHKAKYRyAESw0BCyABIARrIQMCQEEAIARrIgVBf0ogDyABa6wiGiAbIBogG1QbIhqnIgIgBUpxDQAgBUEBSCACIANqIAFLcQ0AIAEgAyACEAcgAmohAgwFCyABIAMgBCAEQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANBCABIANqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAQLIBAgAWsiCUEBaiIGIAUgBSAGSxshAyABIARrIQIgAUEHcUUNAiADRQ0CIAEgAi0AADoAACACQQFqIQIgAUEBaiIGQQdxQQAgA0EBayIFGw0BIAYhASAFIQMgCSEGDAILAkAgBCAFSQRAIAcoAphHIARLDQELIAEgASAEayIGKQAANwAAIAEgBUEBa0EHcUEBaiIDaiECIAUgA2siBEUNAyADIAZqIQEDQCACIAEpAAA3AAAgAUEIaiEBIAJBCGohAiAEQQhrIgQNAAsMAwsgASAEIAUQPyECDAILIAEgAi0AADoAASAJQQFrIQYgA0ECayEFIAJBAWohAgJAIAFBAmoiCkEHcUUNACAFRQ0AIAEgAi0AADoAAiAJQQJrIQYgA0EDayEFIAJBAWohAgJAIAFBA2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAAyAJQQNrIQYgA0EEayEFIAJBAWohAgJAIAFBBGoiCkEHcUUNACAFRQ0AIAEgAi0AADoABCAJQQRrIQYgA0EFayEFIAJBAWohAgJAIAFBBWoiCkEHcUUNACAFRQ0AIAEgAi0AADoABSAJQQVrIQYgA0EGayEFIAJBAWohAgJAIAFBBmoiCkEHcUUNACAFRQ0AIAEgAi0AADoABiAJQQZrIQYgA0EHayEFIAJBAWohAgJAIAFBB2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAByAJQQdrIQYgA0EIayEDIAFBCGohASACQQFqIQIMBgsgCiEBIAUhAwwFCyAKIQEgBSEDDAQLIAohASAFIQMMAwsgCiEBIAUhAwwCCyAKIQEgBSEDDAELIAohASAFIQMLAkACQCAGQRdNBEAgA0UNASADQQFrIQUgA0EHcSIEBEADQCABIAItAAA6AAAgA0EBayEDIAFBAWohASACQQFqIQIgBEEBayIEDQALCyAFQQdJDQEDQCABIAItAAA6AAAgASACLQABOgABIAEgAi0AAjoAAiABIAItAAM6AAMgASACLQAEOgAEIAEgAi0ABToABSABIAItAAY6AAYgASACLQAHOgAHIAFBCGohASACQQhqIQIgA0EIayIDDQALDAELIAMNAQsgASECDAELIAEgBCADED8hAgsgCyEFDAELIAEgAy0AAjoAACABQQFqIQILIAUgFE8NACACIBNJDQELCyAAIAI2AgwgACAFIAhBA3ZrIgE2AgAgACATIAJrQYMCajYCECAAIBQgAWtBDmo2AgQgByAIQQdxIgA2AogBIAcgHUJ/IACthkJ/hYM+AoQBC+cFAQR/IAMgAiACIANLGyEEIAAgAWshAgJAIABBB3FFDQAgBEUNACAAIAItAAA6AAAgA0EBayEGIAJBAWohAiAAQQFqIgdBB3FBACAEQQFrIgUbRQRAIAchACAFIQQgBiEDDAELIAAgAi0AADoAASADQQJrIQYgBEECayEFIAJBAWohAgJAIABBAmoiB0EHcUUNACAFRQ0AIAAgAi0AADoAAiADQQNrIQYgBEEDayEFIAJBAWohAgJAIABBA2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAAyADQQRrIQYgBEEEayEFIAJBAWohAgJAIABBBGoiB0EHcUUNACAFRQ0AIAAgAi0AADoABCADQQVrIQYgBEEFayEFIAJBAWohAgJAIABBBWoiB0EHcUUNACAFRQ0AIAAgAi0AADoABSADQQZrIQYgBEEGayEFIAJBAWohAgJAIABBBmoiB0EHcUUNACAFRQ0AIAAgAi0AADoABiADQQdrIQYgBEEHayEFIAJBAWohAgJAIABBB2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAByADQQhrIQMgBEEIayEEIABBCGohACACQQFqIQIMBgsgByEAIAUhBCAGIQMMBQsgByEAIAUhBCAGIQMMBAsgByEAIAUhBCAGIQMMAwsgByEAIAUhBCAGIQMMAgsgByEAIAUhBCAGIQMMAQsgByEAIAUhBCAGIQMLAkAgA0EXTQRAIARFDQEgBEEBayEBIARBB3EiAwRAA0AgACACLQAAOgAAIARBAWshBCAAQQFqIQAgAkEBaiECIANBAWsi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CyAAIAMtAAAEfiADKQMQBUIAC6dB//8DcSADKAIEEEcLIQUgAxAICyAEQRBqJAAgBQvgAQICfwF+QTAQCSICRQRAIAEEQCABQQA2AgQgAUEONgIAC0EADwsgAkIANwMIIAJBADYCACACQgA3AxAgAkIANwMYIAJCADcDICACQgA3ACUgAFAEQCACDwsCQCAAQv////8AVg0AIACnQQR0EAkiA0UNACACIAM2AgBBACEBQgEhBANAIAMgAUEEdGoiAUIANwIAIAFCADcABSAAIARSBEAgBKchASAEQgF8IQQMAQsLIAIgADcDCCACIAA3AxAgAg8LIAEEQCABQQA2AgQgAUEONgIAC0EAEBAgAhAGQQAL7gECA38BfiMAQRBrIgQkAAJAIARBDGpCBBAXIgNFBEBBfyECDAELAkAgAQRAIAJBgAZxIQUDQAJAIAUgASgCBHFFDQACQCADKQMIQgBUBEAgA0EAOgAADAELIANCADcDECADQQE6AAALIAMgAS8BCBANIAMgAS8BChANIAMtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAtBfyECDAQLQX8hAiAAIARBDGpCBBAbQQBIDQMgATMBCiIGUA0AIAAgASgCDCAGEBtBAEgNAwsgASgCACIBDQALC0EAIQILIAMQCAsgBEEQaiQAIAILPAEBfyAABEAgAUGABnEhAQNAIAEgACgCBHEEQCACIAAvAQpqQQRqIQILIAAoAgAiAA0ACwsgAkH//wNxC5wBAQN/IABFBEBBAA8LIAAhAwNAAn8CQAJAIAAvAQgiAUH04AFNBEAgAUEBRg0BIAFB9cYBRg0BDAILIAFBgbICRg0AIAFB9eABRw0BCyAAKAIAIQEgAEEANgIAIAAoAgwQBiAAEAYgASADIAAgA0YbIQMCQCACRQRAQQAhAgwBCyACIAE2AgALIAEMAQsgACICKAIACyIADQALIAMLsgQCBX8BfgJAAkACQCAAIAGtEBciAQRAIAEtAAANAUEAIQAMAgsgBARAIARBADYCBCAEQQ42AgALQQAPC0EAIQADQCABLQAABH4gASkDCCABKQMQfQVCAAtCBFQNASABEAwhByABIAEQDCIGrRATIghFBEBBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAwNAIAAoAgAhASAAKAIMEAYg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DCAAQRg2AggLDAMLIAAgBiAFLwFqQQAgBCABEQYAIQEgBhALIAFFDQILAkAgDUUEQCABIQMMAQsgACABIAUvAWgQgQEhAyABEAsgA0UNAQsCQCAJRSAMckUEQCADIQEMAQsgACADQQEQgAEhASADEAsgAUUNAQsgASEDDAELQQAhAwsgBUHwAGokACADC4UBAQF/IAFFBEAgAEEIaiIABEAgAEEANgIEIABBEjYCAAtBAA8LQTgQCSIDRQRAIABBCGoiAARAIABBADYCBCAAQQ42AgALQQAPCyADQQA2AhAgA0IANwIIIANCADcDKCADQQA2AgQgAyACNgIAIANCADcDGCADQQA2AjAgACABQTsgAxBCCw8AIAAgASACQQBBABCCAQusAgECfyABRQRAIABBCGoiAARAIABBADYCBCAAQRI2AgALQQAPCwJAIAJBfUsNACACQf//A3FBCEYNACAAQQhqIgAEQCAAQQA2AgQgAEEQNgIAC0EADwsCQEGwwAAQCSIFBEAgBUEANgIIIAVCADcCACAFQYiBAUGogQEgAxs2AqhAIAUgAjYCFCAFIAM6ABAgBUEAOgAPIAVBADsBDCAFIAMgAkF9SyIGcToADiAFQQggAiAGG0H//wNxIAQgBUGIgQFBqIEBIAMbKAIAEQAAIgI2AqxAIAINASAFEDEgBRAGCyAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0EADwsgACABQTogBRBCIgAEfyAABSAFKAKsQCAFKAKoQCgCBBEDACAFEDEgBRAGQQALC6ABAQF/IAIgACgCBCIDIAIgA0kbIgIEQCAAIAMgAms2AgQCQAJAAkACQCAAKAIcIgMoAhRBAWsOAgEAAgsgA0GgAWogASAAKAIAIAJB3IABKAIAEQgADAILIAAgACgCMCABIAAoAgAgAkHEgAEoAgARBAA2AjAMAQsgASAAKAIAIAIQBxoLIAAgACgCACACajYCACAAIAAoAgggAmo2AggLC7cCAQR/QX4hAgJAIABFDQAgACgCIEUNACAAKAIkIgRFDQAgACgCHCIBRQ0AIAEoAgAgAEcNAAJAAkAgASgCICIDQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyADQZoFRg0AIANBKkcN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DCABajYCDCAGIAYoAgggAWo2AgggBSAFKAIUIAFqNgIUIAUgBSgCECABazYCECAGIAYoAhAgAWsiATYCECABDQAgBiAGKAIENgIIC0EAIQRBACEJIAMoAhBFDQAMCgsgAygCHCgCJCEGIAMgAygCGCIBQQFqNgIYIAEgBmotAAAhASADIARBAWo2AhAgAygCBCAEaiABOgAAIAEEQCADKAIQIQQMAQsLIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0HnADYCIAsCQCADKAIcKAIsBEAgAygCDCADKAIQIgFBAmpJBH8gBRAKIAMoAhANAkEABSABCyADKAIEaiAFKAIwOwAAIAMgAygCEEECajYCECADQaABakHkgAEoAgARAQAaCyADQfEANgIgIAUQCiADKAIQRQ0BDAcLDAYLIAUoAgQNAQsgAygCPA0AIApFDQEgAygCIEGaBUYNAQsCfyADKAKIASIBRQRAIAMgChCFAQwBCwJAAkACQCADKAKMAUECaw4CAAECCwJ/AkADQAJAAkAgAygCPA0AIAMQLyADKAI8DQAgCg0BQQAMBAsgAygCSCADKAJoai0AACEEIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qQQA6AAAgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtaiAEOgAAIAMgBEECdGoiASABLwHkAUEBajsB5AEgAyADKAI8QQFrNgI8IAMgAygCaEEBaiIBNgJoIAMoAvAtIAMoAvQtRw0BQQAhBCADIAMoAlgiBkEATgR/IAMoAkggBmoFQQALIAEgBmtBABAPIAMgAygCaDYCWCADKAIAEAogAygCACgCEA0BDAILCyADQQA2AoQuIApBBEYEQCADIAMoAlgiAUEATgR/IAMoAkggAWoFQQALIAMoAmggAWtBARAPIAMgAygCaDYCWCADKAIAEApBA0ECIAMoAgAoAhAbDAILIAMoAvAtBEBBACEEIAMgAygCWCIBQQBOBH8gAygCSCABagVBAAsgAygCaCABa0EAEA8gAyADKAJoNgJYIAMoAgAQCiADKAIAKAIQRQ0BC0EBIQQLIAQL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bGl0ZXJhbC9sZW5ndGhzIHNldABpbnZhbGlkIGNvZGUgbGVuZ3RocyBzZXQAdW5rbm93biBoZWFkZXIgZmxhZ3Mgc2V0AGludmFsaWQgZGlzdGFuY2VzIHNldABpbnZhbGlkIGJpdCBsZW5ndGggcmVwZWF0AEZpbGUgYWxyZWFkeSBleGlzdHMAdG9vIG1hbnkgbGVuZ3RoIG9yIGRpc3RhbmNlIHN5bWJvbHMAaW52YWxpZCBzdG9yZWQgYmxvY2sgbGVuZ3RocwAlcyVzJXMAYnVmZmVyIGVycm9yAE5vIGVycm9yAHN0cmVhbSBlcnJvcgBUZWxsIGVycm9yAEludGVybmFsIGVycm9yAFNlZWsgZXJyb3IAV3JpdGUgZXJyb3IAZmlsZSBlcnJvcgBSZWFkIGVycm9yAFpsaWIgZXJyb3IAZGF0YSBlcnJvcgBDUkMgZXJyb3IAaW5jb21wYXRpYmxlIHZlcnNpb24AaW52YWxpZCBjb2RlIC0tIG1pc3NpbmcgZW5kLW9mLWJsb2NrAGluY29ycmVjdCBoZWFkZXIgY2hlY2sAaW5jb3JyZWN0IGxlbmd0aCBjaGVjawBpbmNvcnJlY3QgZGF0YSBjaGVjawBpbnZhbGlkIGRpc3RhbmNlIHRvbyBmYXIgYmFjawBoZWFkZXIgY3JjIG1pc21hdGNoADEuMi4xMy56bGliLW5nAGludmFsaWQgd2luZG93IHNpemUAUmVhZC1vbmx5IGFyY2hpdmUATm90IGEgemlwIGFyY2hpdmUAUmVzb3VyY2Ugc3RpbGwgaW4gdXNlAE1hbGxvYyBmYWlsdXJlAGludmFsaWQgYmxvY2sgdHlwZQBGYWlsdXJlIHRvIGNyZWF0ZSB0ZW1wb3JhcnkgZmlsZQBDYW4ndCBvcGVuIGZpbGUATm8gc3VjaCBmaWxlAFByZW1hdHVyZSBlbmQgb2YgZmlsZQBDYW4ndCByZW1vdmUgZmlsZQBpbnZhbGlkIGxpdGVyYWwvbGVuZ3RoIGNvZGUAaW52YWxpZCBkaXN0YW5jZSBjb2RlAHVua25vd24gY29tcHJlc3Npb24gbWV0aG9kAHN0cmVhbSBlbmQAQ29tcHJlc3NlZCBkYXRhIGludmFsaWQATXVsdGktZGlzayB6aXAgYXJjaGl2ZXMgbm90IHN1cHBvcnRl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UgAIANIACAAyAAgAsgAIAHIACADyAAgACgAIAIoACABKAAgAygAIACoACACqAAgAagAIAOoACAAaAAgAmgAIAFoACADaAAgAOgAIALoACAB6AAgA+gAIAAYACACGAAgARgAIAMYACAAmAAgApgAIAGYACADmAAgAFgAIAJYACABWAAgA1gAIADYACAC2AAgAdgAIAPYACAAOAAgAjgAIAE4ACADOAAgALgAIAK4ACABuAAgA7gAIAB4ACACeAAgAXgAIAN4ACAA+AAgAvgAIAH4ACAD+AAgAAQAIAIEACABBAAgAwQAIACEACAChAAgAYQAIAOEACAARAAgAkQAIAFEACADRAAgAMQAIALEACABxAAgA8QAIAAkACACJAAgASQAIAMkACAApAAgAqQAIAGkACADpAAgAGQAIAJkACABZAAgA2QAIADkACAC5AAgAeQAIAPkACAAFAAgAhQAIAEUACADFAAgAJQAIAKUACABlAAgA5QAIABUACACVAAgAVQAIANUACAA1AAgAtQAIAHUACAD1AAgADQAIAI0ACABNAAgAzQAIAC0ACACtAAgAbQAIAO0ACAAdAAgAnQAIAF0ACADdAAgAPQAIAL0ACAB9AAgA/QAIABMACQATAQkAkwAJAJMBCQBTAAkAUwEJANMACQDTAQkAMwAJADMBCQCzAAkAswEJAHMACQBzAQkA8wAJAPMBCQALAAkACwEJAIsACQCLAQkASwAJAEsBCQDLAAkAywEJACsACQArAQkAqwAJAKsBCQBrAAkAawEJAOsACQDrAQkAGwAJABsBCQCbAAkAmwEJAFsACQBbAQkA2wAJANsBCQA7AAkAOwEJALsACQC7AQkAewAJAHsBCQD7AAkA+wEJAAcACQAHAQkAhwAJAIcBCQBHAAkARwEJAMcACQDHAQkAJwAJACcBCQCnAAkApwEJAGcACQBnAQkA5wAJAOcBCQAXAAkAFwEJAJcACQCXAQkAVwAJAFcBCQDXAAkA1wEJADcACQA3AQkAtwAJALcBCQB3AAkAdwEJAPcACQD3AQkADwAJAA8BCQCPAAkAjwEJAE8ACQBPAQkAzwAJAM8BCQAvAAkALwEJAK8ACQCvAQkAbwAJAG8BCQDvAAkA7wEJAB8ACQAfAQkAnwAJAJ8BCQBfAAkAXwEJAN8ACQDfAQkAPwAJAD8BCQC/AAkA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SXMgYSBkaXJlY3RvcnkAVGV4dCBmaWxlIGJ1c3kARXhlYyBmb3JtYXQgZXJyb3IASW52YWxpZCBhcmd1bWVudABBcmd1bWVudCBsaXN0IHRvbyBsb25nAFN5bWJvbGljIGxpbmsgbG9vcABGaWxlbmFtZSB0b28gbG9uZwBUb28gbWFueSBvcGVuIGZpbGVzIGluIHN5c3RlbQBObyBmaWxlIGRlc2NyaXB0b3JzIGF2YWlsYWJsZQBCYWQgZmlsZSBkZXNjcmlwdG9yAE5vIGNoaWxkIHByb2Nlc3MAQmFkIGFkZHJlc3MARmlsZSB0b28gbGFyZ2UAVG9vIG1hbnkgbGlua3MATm8gbG9ja3MgYXZhaWxhYmxlAFJlc291cmNlIGRlYWRsb2NrIHdvdWxkIG9jY3VyAFN0YXRlIG5vdCByZWNvdmVyYWJsZQBQcmV2aW91cyBvd25lciBkaWVkAE9wZXJhdGlvbiBjYW5jZWxlZABGdW5jdGlvbiBub3QgaW1wbGVtZW50ZWQATm8gbWVzc2FnZSBvZiBkZXNpcmVkIHR5cGUASWRlbnRpZmllciByZW1vdmVkAERldmljZSBub3QgYSBzdHJlYW0ATm8gZGF0YSBhdmFpbGFibGUARGV2aWNlIHRpbWVvdXQAT3V0IG9mIHN0cmVhbXMgcmVzb3VyY2VzAExpbmsgaGFzIGJlZW4gc2V2ZXJlZABQcm90b2NvbCBlcnJvcgBCYWQgbWVzc2FnZQBGaWxlIGRlc2NyaXB0b3IgaW4gYmFkIHN0YXRlAE5vdCBhIHNvY2tldABEZXN0aW5hdGlvbiBhZGRyZXNzIHJlcXVpcmVkAE1lc3NhZ2UgdG9vIGxhcmdlAFByb3RvY29sIHdyb25nIHR5cGUgZm9yIHNvY2tldABQcm90b2NvbCBub3QgYXZhaWxhYmxlAFByb3RvY29sIG5vdCBzdXBwb3J0ZWQAU29ja2V0IHR5cGUgbm90IHN1cHBvcnRlZABOb3Qgc3VwcG9ydGVkAFByb3RvY29sIGZhbWlseSBub3Qgc3VwcG9ydGVkAEFkZHJlc3MgZmFtaWx5IG5vdCBzdXBwb3J0ZWQgYnkgcHJvdG9jb2wAQWRkcmVzcyBub3QgYXZhaWxhYmxlAE5ldHdvcmsgaXMgZG93bgBOZXR3b3JrIHVucmVhY2hhYmxlAENvbm5lY3Rpb24gcmVzZXQgYnkgbmV0d29yawBDb25uZWN0aW9uIGFib3J0ZWQATm8gYnVmZmVyIHNwYWNlIGF2YWlsYWJsZQBTb2NrZXQgaXMgY29ubmVjdGVkAFNvY2tldCBub3QgY29ubmVjdGVkAENhbm5vdCBzZW5kIGFmdGVyIHNvY2tldCBzaHV0ZG93bgBPcGVyYXRpb24gYWxyZWFkeSBpbiBwcm9ncmVzcwBPcGVyYXRpb24gaW4gcHJvZ3Jlc3MAU3RhbGUgZmlsZSBoYW5kbGUAUmVtb3RlIEkvTyBlcnJvcgBRdW90YSBleGNlZWRlZABObyBtZWRpdW0gZm91bmQAV3JvbmcgbWVkaXVtIHR5cGUATm8gZXJyb3IgaW5mb3JtYXRpb24AQcCAAQuFARMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAgERQADEAAAAyAAAAMwAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAQfSCAQsCXEQAQbCDAQsQ/////////////////////w==";Co(ji)||(ji=P(ji));function eo(Ke){try{if(Ke==ji&&le)return new Uint8Array(le);var st=Me(Ke);if(st)return st;if(R)return R(Ke);throw"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)"}catch(St){rs(St)}}function wo(Ke,st){var St,lr,te;try{te=eo(Ke),lr=new WebAssembly.Module(te),St=new WebAssembly.Instance(lr,st)}catch(Oe){var Ee=Oe.toString();throw ee("failed to compile wasm module: "+Ee),(Ee.includes("imported Memory")||Ee.includes("memory import"))&&ee("Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time)."),Oe}return[St,lr]}function QA(){var Ke={a:cu};function st(te,Ee){var Oe=te.exports;r.asm=Oe,Be=r.asm.g,z(Be.buffer),$=r.asm.W,gn(r.asm.h),Io("wasm-instantiate")}if(ai("wasm-instantiate"),r.instantiateWasm)try{var St=r.instantiateWasm(Ke,st);return St}catch(te){return ee("Module.instantiateWasm callback failed with error: "+te),!1}var lr=wo(ji,Ke);return st(lr[0]),r.asm}function Af(Ke){return F.getFloat32(Ke,!0)}function dh(Ke){return F.getFloat64(Ke,!0)}function mh(Ke){return F.getInt16(Ke,!0)}function to(Ke){return F.getInt32(Ke,!0)}function jn(Ke,st){F.setInt32(Ke,st,!0)}function Ts(Ke){for(;Ke.length>0;){var st=Ke.shift();if(typeof st=="function"){st(r);continue}var St=st.func;typeof St=="number"?st.arg===void 0?$.get(St)():$.get(St)(st.arg):St(st.arg===void 0?null:st.arg)}}function ro(Ke,st){var St=new Date(to((Ke>>2)*4)*1e3);jn((st>>2)*4,St.getUTCSeconds()),jn((st+4>>2)*4,St.getUTCMinutes()),jn((st+8>>2)*4,St.getUTCHours()),jn((st+12>>2)*4,St.getUTCDate()),jn((st+16>>2)*4,St.getUTCMonth()),jn((st+20>>2)*4,St.getUTCFullYear()-1900),jn((st+24>>2)*4,St.getUTCDay()),jn((st+36>>2)*4,0),jn((st+32>>2)*4,0);var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),te=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((st+28>>2)*4,te),ro.GMTString||(ro.GMTString=rt("GMT")),jn((st+40>>2)*4,ro.GMTString),st}function ou(Ke,st){return ro(Ke,st)}function au(Ke,st,St){ke.copyWithin(Ke,st,st+St)}function lu(Ke){try{return Be.grow(Ke-Pe.byteLength+65535>>>16),z(Be.buffer),1}catch{}}function TA(Ke){var st=ke.length;Ke=Ke>>>0;var St=2147483648;if(Ke>St)return!1;for(var lr=1;lr<=4;lr*=2){var te=st*(1+.2/lr);te=Math.min(te,Ke+100663296);var Ee=Math.min(St,Ne(Math.max(Ke,te),65536)),Oe=lu(Ee);if(Oe)return!0}return!1}function RA(Ke){ue(Ke)}function oa(Ke){var st=Date.now()/1e3|0;return Ke&&jn((Ke>>2)*4,st),st}function aa(){if(aa.called)return;aa.called=!0;var Ke=new Date().getFullYear(),st=new Date(Ke,0,1),St=new Date(Ke,6,1),lr=st.getTimezoneOffset(),te=St.getTimezoneOffset(),Ee=Math.max(lr,te);jn((vl()>>2)*4,Ee*60),jn((Is()>>2)*4,+(lr!=te));function Oe(An){var li=An.toTimeString().match(/\(([A-Za-z ]+)\)$/);return li?li[1]:"GMT"}var dt=Oe(st),Et=Oe(St),bt=rt(dt),tr=rt(Et);te>2)*4,bt),jn((Mi()+4>>2)*4,tr)):(jn((Mi()>>2)*4,tr),jn((Mi()+4>>2)*4,bt))}function FA(Ke){aa();var st=Date.UTC(to((Ke+20>>2)*4)+1900,to((Ke+16>>2)*4),to((Ke+12>>2)*4),to((Ke+8>>2)*4),to((Ke+4>>2)*4),to((Ke>>2)*4),0),St=new Date(st);jn((Ke+24>>2)*4,St.getUTCDay());var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),te=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((Ke+28>>2)*4,te),St.getTime()/1e3|0}var gr=typeof atob=="function"?atob:function(Ke){var st="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",St="",lr,te,Ee,Oe,dt,Et,bt,tr=0;Ke=Ke.replace(/[^A-Za-z0-9\+\/\=]/g,"");do Oe=st.indexOf(Ke.charAt(tr++)),dt=st.indexOf(Ke.charAt(tr++)),Et=st.indexOf(Ke.charAt(tr++)),bt=st.indexOf(Ke.charAt(tr++)),lr=Oe<<2|dt>>4,te=(dt&15)<<4|Et>>2,Ee=(Et&3)<<6|bt,St=St+String.fromCharCode(lr),Et!==64&&(St=St+String.fromCharCode(te)),bt!==64&&(St=St+String.fromCharCode(Ee));while(tr0||(Ct(),Ir>0))return;function st(){Qn||(Qn=!0,r.calledRun=!0,!Ce&&(qt(),s(r),r.onRuntimeInitialized&&r.onRuntimeInitialized(),ir()))}r.setStatus?(r.setStatus("Running..."),setTimeout(function(){setTimeout(function(){r.setStatus("")},1),st()},1)):st()}if(r.run=Ac,r.preInit)for(typeof r.preInit=="function"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();return Ac(),e}}();typeof wT=="object"&&typeof Pj=="object"?Pj.exports=bj:typeof define=="function"&&define.amd?define([],function(){return bj}):typeof wT=="object"&&(wT.createModule=bj)});var Up,hpe,gpe,dpe=Xe(()=>{Up=["number","number"],hpe=(Z=>(Z[Z.ZIP_ER_OK=0]="ZIP_ER_OK",Z[Z.ZIP_ER_MULTIDISK=1]="ZIP_ER_MULTIDISK",Z[Z.ZIP_ER_RENAME=2]="ZIP_ER_RENAME",Z[Z.ZIP_ER_CLOSE=3]="ZIP_ER_CLOSE",Z[Z.ZIP_ER_SEEK=4]="ZIP_ER_SEEK",Z[Z.ZIP_ER_READ=5]="ZIP_ER_READ",Z[Z.ZIP_ER_WRITE=6]="ZIP_ER_WRITE",Z[Z.ZIP_ER_CRC=7]="ZIP_ER_CRC",Z[Z.ZIP_ER_ZIPCLOSED=8]="ZIP_ER_ZIPCLOSED",Z[Z.ZIP_ER_NOENT=9]="ZIP_ER_NOENT",Z[Z.ZIP_ER_EXISTS=10]="ZIP_ER_EXISTS",Z[Z.ZIP_ER_OPEN=11]="ZIP_ER_OPEN",Z[Z.ZIP_ER_TMPOPEN=12]="ZIP_ER_TMPOPEN",Z[Z.ZIP_ER_ZLIB=13]="ZIP_ER_ZLIB",Z[Z.ZIP_ER_MEMORY=14]="ZIP_ER_MEMORY",Z[Z.ZIP_ER_CHANGED=15]="ZIP_ER_CHANGED",Z[Z.ZIP_ER_COMPNOTSUPP=16]="ZIP_ER_COMPNOTSUPP",Z[Z.ZIP_ER_EOF=17]="ZIP_ER_EOF",Z[Z.ZIP_ER_INVAL=18]="ZIP_ER_INVAL",Z[Z.ZIP_ER_NOZIP=19]="ZIP_ER_NOZIP",Z[Z.ZIP_ER_INTERNAL=20]="ZIP_ER_INTERNAL",Z[Z.ZIP_ER_INCONS=21]="ZIP_ER_INCONS",Z[Z.ZIP_ER_REMOVE=22]="ZIP_ER_REMOVE",Z[Z.ZIP_ER_DELETED=23]="ZIP_ER_DELETED",Z[Z.ZIP_ER_ENCRNOTSUPP=24]="ZIP_ER_ENCRNOTSUPP",Z[Z.ZIP_ER_RDONLY=25]="ZIP_ER_RDONLY",Z[Z.ZIP_ER_NOPASSWD=26]="ZIP_ER_NOPASSWD",Z[Z.ZIP_ER_WRONGPASSWD=27]="ZIP_ER_WRONGPASSWD",Z[Z.ZIP_ER_OPNOTSUPP=28]="ZIP_ER_OPNOTSUPP",Z[Z.ZIP_ER_INUSE=29]="ZIP_ER_INUSE",Z[Z.ZIP_ER_TELL=30]="ZIP_ER_TELL",Z[Z.ZIP_ER_COMPRESSED_DATA=31]="ZIP_ER_COMPRESSED_DATA",Z))(hpe||{}),gpe=t=>({get HEAPU8(){return t.HEAPU8},errors:hpe,SEEK_SET:0,SEEK_CUR:1,SEEK_END:2,ZIP_CHECKCONS:4,ZIP_EXCL:2,ZIP_RDONLY:16,ZIP_FL_OVERWRITE:8192,ZIP_FL_COMPRESSED:4,ZIP_OPSYS_DOS:0,ZIP_OPSYS_AMIGA:1,ZIP_OPSYS_OPENVMS:2,ZIP_OPSYS_UNIX:3,ZIP_OPSYS_VM_CMS:4,ZIP_OPSYS_ATARI_ST:5,ZIP_OPSYS_OS_2:6,ZIP_OPSYS_MACINTOSH:7,ZIP_OPSYS_Z_SYSTEM:8,ZIP_OPSYS_CPM:9,ZIP_OPSYS_WINDOWS_NTFS:10,ZIP_OPSYS_MVS:11,ZIP_OPSYS_VSE:12,ZIP_OPSYS_ACORN_RISC:13,ZIP_OPSYS_VFAT:14,ZIP_OPSYS_ALTERNATE_MVS:15,ZIP_OPSYS_BEOS:16,ZIP_OPSYS_TANDEM:17,ZIP_OPSYS_OS_400:18,ZIP_OPSYS_OS_X:19,ZIP_CM_DEFAULT:-1,ZIP_CM_STORE:0,ZIP_CM_DEFLATE:8,uint08S:t._malloc(1),uint32S:t._malloc(4),malloc:t._malloc,free:t._free,getValue:t.getValue,openFromSource:t.cwrap("zip_open_from_source","number",["number","number","number"]),close:t.cwrap("zip_close","number",["number"]),discard:t.cwrap("zip_discard",null,["number"]),getError:t.cwrap("zip_get_error","number",["number"]),getName:t.cwrap("zip_get_name","string",["number","number","number"]),getNumEntries:t.cwrap("zip_get_num_entries","number",["number","number"]),delete:t.cwrap("zip_delete","number",["number","number"]),statIndex:t.cwrap("zip_stat_index","number",["number",...Up,"number","number"]),fopenIndex:t.cwrap("zip_fopen_index","number",["number",...Up,"number"]),fread:t.cwrap("zip_fread","number",["number","number","number","number"]),fclose:t.cwrap("zip_fclose","number",["number"]),dir:{add:t.cwrap("zip_dir_add","number",["number","string"])},file:{add:t.cwrap("zip_file_add","number",["number","string","number","number"]),getError:t.cwrap("zip_file_get_error","number",["number"]),getExternalAttributes:t.cwrap("zip_file_get_external_attributes","number",["number",...Up,"number","number","number"]),setExternalAttributes:t.cwrap("zip_file_set_external_attributes","number",["number",...Up,"number","number","number"]),setMtime:t.cwrap("zip_file_set_mtime","number",["number",...Up,"number","number"]),setCompression:t.cwrap("zip_set_file_compression","number",["number",...Up,"number","number"])},ext:{countSymlinks:t.cwrap("zip_ext_count_symlinks","number",["number"])},error:{initWithCode:t.cwrap("zip_error_init_with_code",null,["number","number"]),strerror:t.cwrap("zip_error_strerror","string",["number"])},name:{locate:t.cwrap("zip_name_locate","number",["number","string","number"])},source:{fromUnattachedBuffer:t.cwrap("zip_source_buffer_create","number",["number",...Up,"number","number"]),fromBuffer:t.cwrap("zip_source_buffer","number",["number","number",...Up,"number"]),free:t.cwrap("zip_source_free",null,["number"]),keep:t.cwrap("zip_source_keep",null,["number"]),open:t.cwrap("zip_source_open","number",["number"]),close:t.cwrap("zip_source_close","number",["number"]),seek:t.cwrap("zip_source_seek","number",["number",...Up,"number"]),tell:t.cwrap("zip_source_tell","number",["number"]),read:t.cwrap("zip_source_read","number",["number","number","number"]),error:t.cwrap("zip_source_error","number",["number"])},struct:{statS:t.cwrap("zipstruct_statS","number",[]),statSize:t.cwrap("zipstruct_stat_size","number",["number"]),statCompSize:t.cwrap("zipstruct_stat_comp_size","number",["number"]),statCompMethod:t.cwrap("zipstruct_stat_comp_method","number",["number"]),statMtime:t.cwrap("zipstruct_stat_mtime","number",["number"]),statCrc:t.cwrap("zipstruct_stat_crc","number",["number"]),errorS:t.cwrap("zipstruct_errorS","number",[]),errorCodeZip:t.cwrap("zipstruct_error_code_zip","number",["number"])}})});function xj(t,e){let r=t.indexOf(e);if(r<=0)return null;let s=r;for(;r>=0&&(s=r+e.length,t[s]!==J.sep);){if(t[r-1]===J.sep)return null;r=t.indexOf(e,s)}return t.length>s&&t[s]!==J.sep?null:t.slice(0,s)}var $f,mpe=Xe(()=>{Dt();Dt();eA();$f=class t extends e0{static async openPromise(e,r){let s=new t(r);try{return await e(s)}finally{s.saveAndClose()}}constructor(e={}){let r=e.fileExtensions,s=e.readOnlyArchives,a=typeof r>"u"?f=>xj(f,".zip"):f=>{for(let p of r){let h=xj(f,p);if(h)return h}return null},n=(f,p)=>new As(p,{baseFs:f,readOnly:s,stats:f.statSync(p),customZipImplementation:e.customZipImplementation}),c=async(f,p)=>{let h={baseFs:f,readOnly:s,stats:await f.statPromise(p),customZipImplementation:e.customZipImplementation};return()=>new As(p,h)};super({...e,factorySync:n,factoryPromise:c,getMountPoint:a})}}});var kj,BI,Qj=Xe(()=>{Dj();kj=class extends Error{constructor(e,r){super(e),this.name="Libzip Error",this.code=r}},BI=class{constructor(e){this.filesShouldBeCached=!0;let r="buffer"in e?e.buffer:e.baseFs.readFileSync(e.path);this.libzip=cv();let s=this.libzip.malloc(4);try{let c=0;e.readOnly&&(c|=this.libzip.ZIP_RDONLY);let f=this.allocateUnattachedSource(r);try{this.zip=this.libzip.openFromSource(f,c,s),this.lzSource=f}catch(p){throw this.libzip.source.free(f),p}if(this.zip===0){let p=this.libzip.struct.errorS();throw this.libzip.error.initWithCode(p,this.libzip.getValue(s,"i32")),this.makeLibzipError(p)}}finally{this.libzip.free(s)}let a=this.libzip.getNumEntries(this.zip,0),n=new Array(a);for(let c=0;c>>0,n=this.libzip.struct.statMtime(r)>>>0,c=this.libzip.struct.statCrc(r)>>>0;return{size:a,mtime:n,crc:c}}makeLibzipError(e){let r=this.libzip.struct.errorCodeZip(e),s=this.libzip.error.strerror(e),a=new kj(s,this.libzip.errors[r]);if(r===this.libzip.errors.ZIP_ER_CHANGED)throw new Error(`Assertion failed: Unexpected libzip error: ${a.message}`);return a}setFileSource(e,r,s){let a=this.allocateSource(s);try{let n=this.libzip.file.add(this.zip,e,a,this.libzip.ZIP_FL_OVERWRITE);if(n===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(r!==null&&this.libzip.file.setCompression(this.zip,n,0,r[0],r[1])===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return n}catch(n){throw this.libzip.source.free(a),n}}setMtime(e,r){if(this.libzip.file.setMtime(this.zip,e,0,r,0)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}getExternalAttributes(e){if(this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let s=this.libzip.getValue(this.libzip.uint08S,"i8")>>>0,a=this.libzip.getValue(this.libzip.uint32S,"i32")>>>0;return[s,a]}setExternalAttributes(e,r,s){if(this.libzip.file.setExternalAttributes(this.zip,e,0,0,r,s)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}locate(e){return this.libzip.name.locate(this.zip,e,0)}getFileSource(e){let r=this.libzip.struct.statS();if(this.libzip.statIndex(this.zip,e,0,0,r)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let a=this.libzip.struct.statCompSize(r),n=this.libzip.struct.statCompMethod(r),c=this.libzip.malloc(a);try{let f=this.libzip.fopenIndex(this.zip,e,0,this.libzip.ZIP_FL_COMPRESSED);if(f===0)throw this.makeLibzipError(this.libzip.getError(this.zip));try{let p=this.libzip.fread(f,c,a,0);if(p===-1)throw this.makeLibzipError(this.libzip.file.getError(f));if(pa)throw new Error("Overread");let h=this.libzip.HEAPU8.subarray(c,c+a);return{data:Buffer.from(h),compressionMethod:n}}finally{this.libzip.fclose(f)}}finally{this.libzip.free(c)}}deleteEntry(e){if(this.libzip.delete(this.zip,e)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}addDirectory(e){let r=this.libzip.dir.add(this.zip,e);if(r===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return r}getBufferAndClose(){try{if(this.libzip.source.keep(this.lzSource),this.libzip.close(this.zip)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(this.libzip.source.open(this.lzSource)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_END)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let e=this.libzip.source.tell(this.lzSource);if(e===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_SET)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let r=this.libzip.malloc(e);if(!r)throw new Error("Couldn't allocate enough memory");try{let s=this.libzip.source.read(this.lzSource,r,e);if(s===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(se)throw new Error("Overread");let a=Buffer.from(this.libzip.HEAPU8.subarray(r,r+e));return process.env.YARN_IS_TEST_ENV&&process.env.YARN_ZIP_DATA_EPILOGUE&&(a=Buffer.concat([a,Buffer.from(process.env.YARN_ZIP_DATA_EPILOGUE)])),a}finally{this.libzip.free(r)}}finally{this.libzip.source.close(this.lzSource),this.libzip.source.free(this.lzSource)}}allocateBuffer(e){Buffer.isBuffer(e)||(e=Buffer.from(e));let r=this.libzip.malloc(e.byteLength);if(!r)throw new Error("Couldn't allocate enough memory");return new Uint8Array(this.libzip.HEAPU8.buffer,r,e.byteLength).set(e),{buffer:r,byteLength:e.byteLength}}allocateUnattachedSource(e){let r=this.libzip.struct.errorS(),{buffer:s,byteLength:a}=this.allocateBuffer(e),n=this.libzip.source.fromUnattachedBuffer(s,a,0,1,r);if(n===0)throw this.libzip.free(r),this.makeLibzipError(r);return n}allocateSource(e){let{buffer:r,byteLength:s}=this.allocateBuffer(e),a=this.libzip.source.fromBuffer(this.zip,r,s,0,1);if(a===0)throw this.libzip.free(r),this.makeLibzipError(this.libzip.getError(this.zip));return a}discard(){this.libzip.discard(this.zip)}}});function Ynt(t){if(typeof t=="string"&&String(+t)===t)return+t;if(typeof t=="number"&&Number.isFinite(t))return t<0?Date.now()/1e3:t;if(ype.types.isDate(t))return t.getTime()/1e3;throw new Error("Invalid time")}function BT(){return Buffer.from([80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])}var xa,Tj,ype,Rj,lm,Fj,Nj,Epe,As,vT=Xe(()=>{Dt();Dt();Dt();Dt();Dt();Dt();xa=Ie("fs"),Tj=Ie("stream"),ype=Ie("util"),Rj=ut(Ie("zlib"));Qj();lm=3,Fj=0,Nj=8,Epe="mixed";As=class extends Uf{constructor(r,s={}){super();this.listings=new Map;this.entries=new Map;this.fileSources=new Map;this.fds=new Map;this.nextFd=0;this.ready=!1;this.readOnly=!1;s.readOnly&&(this.readOnly=!0);let a=s;this.level=typeof a.level<"u"?a.level:Epe;let n=s.customZipImplementation??BI;if(typeof r=="string"){let{baseFs:f=new Yn}=a;this.baseFs=f,this.path=r}else this.path=null,this.baseFs=null;if(s.stats)this.stats=s.stats;else if(typeof r=="string")try{this.stats=this.baseFs.statSync(r)}catch(f){if(f.code==="ENOENT"&&a.create)this.stats=$a.makeDefaultStats();else throw f}else this.stats=$a.makeDefaultStats();typeof r=="string"?s.create?this.zipImpl=new n({buffer:BT(),readOnly:this.readOnly}):this.zipImpl=new n({path:r,baseFs:this.baseFs,readOnly:this.readOnly,size:this.stats.size}):this.zipImpl=new n({buffer:r??BT(),readOnly:this.readOnly}),this.listings.set(vt.root,new Set);let c=this.zipImpl.getListings();for(let f=0;f{this.closeSync(f)}})}async readPromise(r,s,a,n,c){return this.readSync(r,s,a,n,c)}readSync(r,s,a=0,n=s.byteLength,c=-1){let f=this.fds.get(r);if(typeof f>"u")throw or.EBADF("read");let p=c===-1||c===null?f.cursor:c,h=this.readFileSync(f.p);h.copy(s,a,p,p+n);let E=Math.max(0,Math.min(h.length-p,n));return(c===-1||c===null)&&(f.cursor+=E),E}async writePromise(r,s,a,n,c){return typeof s=="string"?this.writeSync(r,s,c):this.writeSync(r,s,a,n,c)}writeSync(r,s,a,n,c){throw typeof this.fds.get(r)>"u"?or.EBADF("read"):new Error("Unimplemented")}async closePromise(r){return this.closeSync(r)}closeSync(r){if(typeof this.fds.get(r)>"u")throw or.EBADF("read");this.fds.delete(r)}createReadStream(r,{encoding:s}={}){if(r===null)throw new Error("Unimplemented");let a=this.openSync(r,"r"),n=Object.assign(new Tj.PassThrough({emitClose:!0,autoDestroy:!0,destroy:(f,p)=>{clearImmediate(c),this.closeSync(a),p(f)}}),{close(){n.destroy()},bytesRead:0,path:r,pending:!1}),c=setImmediate(async()=>{try{let f=await this.readFilePromise(r,s);n.bytesRead=f.length,n.end(f)}catch(f){n.destroy(f)}});return n}createWriteStream(r,{encoding:s}={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);if(r===null)throw new Error("Unimplemented");let a=[],n=this.openSync(r,"w"),c=Object.assign(new Tj.PassThrough({autoDestroy:!0,emitClose:!0,destroy:(f,p)=>{try{f?p(f):(this.writeFileSync(r,Buffer.concat(a),s),p(null))}catch(h){p(h)}finally{this.closeSync(n)}}}),{close(){c.destroy()},bytesWritten:0,path:r,pending:!1});return c.on("data",f=>{let p=Buffer.from(f);c.bytesWritten+=p.length,a.push(p)}),c}async realpathPromise(r){return this.realpathSync(r)}realpathSync(r){let s=this.resolveFilename(`lstat '${r}'`,r);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`lstat '${r}'`);return s}async existsPromise(r){return this.existsSync(r)}existsSync(r){if(!this.ready)throw or.EBUSY(`archive closed, existsSync '${r}'`);if(this.symlinkCount===0){let a=J.resolve(vt.root,r);return this.entries.has(a)||this.listings.has(a)}let s;try{s=this.resolveFilename(`stat '${r}'`,r,void 0,!1)}catch{return!1}return s===void 0?!1:this.entries.has(s)||this.listings.has(s)}async accessPromise(r,s){return this.accessSync(r,s)}accessSync(r,s=xa.constants.F_OK){let a=this.resolveFilename(`access '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`access '${r}'`);if(this.readOnly&&s&xa.constants.W_OK)throw or.EROFS(`access '${r}'`)}async statPromise(r,s={bigint:!1}){return s.bigint?this.statSync(r,{bigint:!0}):this.statSync(r)}statSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`stat '${r}'`,r,void 0,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`stat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`stat '${r}'`);return this.statImpl(`stat '${r}'`,a,s)}}async fstatPromise(r,s){return this.fstatSync(r,s)}fstatSync(r,s){let a=this.fds.get(r);if(typeof a>"u")throw or.EBADF("fstatSync");let{p:n}=a,c=this.resolveFilename(`stat '${n}'`,n);if(!this.entries.has(c)&&!this.listings.has(c))throw or.ENOENT(`stat '${n}'`);if(n[n.length-1]==="/"&&!this.listings.has(c))throw or.ENOTDIR(`stat '${n}'`);return this.statImpl(`fstat '${n}'`,c,s)}async lstatPromise(r,s={bigint:!1}){return s.bigint?this.lstatSync(r,{bigint:!0}):this.lstatSync(r)}lstatSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`lstat '${r}'`,r,!1,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`lstat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`lstat '${r}'`);return this.statImpl(`lstat '${r}'`,a,s)}}statImpl(r,s,a={}){let n=this.entries.get(s);if(typeof n<"u"){let c=this.zipImpl.stat(n),f=c.crc,p=c.size,h=c.mtime*1e3,E=this.stats.uid,C=this.stats.gid,S=512,P=Math.ceil(c.size/S),I=h,R=h,N=h,U=new Date(I),W=new Date(R),ee=new Date(N),ie=new Date(h),ue=this.listings.has(s)?xa.constants.S_IFDIR:this.isSymbolicLink(n)?xa.constants.S_IFLNK:xa.constants.S_IFREG,le=ue===xa.constants.S_IFDIR?493:420,me=ue|this.getUnixMode(n,le)&511,pe=Object.assign(new $a.StatEntry,{uid:E,gid:C,size:p,blksize:S,blocks:P,atime:U,birthtime:W,ctime:ee,mtime:ie,atimeMs:I,birthtimeMs:R,ctimeMs:N,mtimeMs:h,mode:me,crc:f});return a.bigint===!0?$a.convertToBigIntStats(pe):pe}if(this.listings.has(s)){let c=this.stats.uid,f=this.stats.gid,p=0,h=512,E=0,C=this.stats.mtimeMs,S=this.stats.mtimeMs,P=this.stats.mtimeMs,I=this.stats.mtimeMs,R=new Date(C),N=new Date(S),U=new Date(P),W=new Date(I),ee=xa.constants.S_IFDIR|493,ue=Object.assign(new $a.StatEntry,{uid:c,gid:f,size:p,blksize:h,blocks:E,atime:R,birthtime:N,ctime:U,mtime:W,atimeMs:C,birthtimeMs:S,ctimeMs:P,mtimeMs:I,mode:ee,crc:0});return a.bigint===!0?$a.convertToBigIntStats(ue):ue}throw new Error("Unreachable")}getUnixMode(r,s){let[a,n]=this.zipImpl.getExternalAttributes(r);return a!==lm?s:n>>>16}registerListing(r){let s=this.listings.get(r);if(s)return s;this.registerListing(J.dirname(r)).add(J.basename(r));let n=new Set;return this.listings.set(r,n),n}registerEntry(r,s){this.registerListing(J.dirname(r)).add(J.basename(r)),this.entries.set(r,s)}unregisterListing(r){this.listings.delete(r),this.listings.get(J.dirname(r))?.delete(J.basename(r))}unregisterEntry(r){this.unregisterListing(r);let s=this.entries.get(r);this.entries.delete(r),!(typeof s>"u")&&(this.fileSources.delete(s),this.isSymbolicLink(s)&&this.symlinkCount--)}deleteEntry(r,s){this.unregisterEntry(r),this.zipImpl.deleteEntry(s)}resolveFilename(r,s,a=!0,n=!0){if(!this.ready)throw or.EBUSY(`archive closed, ${r}`);let c=J.resolve(vt.root,s);if(c==="/")return vt.root;let f=this.entries.get(c);if(a&&f!==void 0)if(this.symlinkCount!==0&&this.isSymbolicLink(f)){let p=this.getFileSource(f).toString();return this.resolveFilename(r,J.resolve(J.dirname(c),p),!0,n)}else return c;for(;;){let p=this.resolveFilename(r,J.dirname(c),!0,n);if(p===void 0)return p;let h=this.listings.has(p),E=this.entries.has(p);if(!h&&!E){if(n===!1)return;throw or.ENOENT(r)}if(!h)throw or.ENOTDIR(r);if(c=J.resolve(p,J.basename(c)),!a||this.symlinkCount===0)break;let C=this.zipImpl.locate(c.slice(1));if(C===-1)break;if(this.isSymbolicLink(C)){let S=this.getFileSource(C).toString();c=J.resolve(J.dirname(c),S)}else break}return c}setFileSource(r,s){let a=Buffer.isBuffer(s)?s:Buffer.from(s),n=J.relative(vt.root,r),c=null;this.level!=="mixed"&&(c=[this.level===0?Fj:Nj,this.level]);let f=this.zipImpl.setFileSource(n,c,a);return this.fileSources.set(f,a),f}isSymbolicLink(r){if(this.symlinkCount===0)return!1;let[s,a]=this.zipImpl.getExternalAttributes(r);return s!==lm?!1:(a>>>16&xa.constants.S_IFMT)===xa.constants.S_IFLNK}getFileSource(r,s={asyncDecompress:!1}){let a=this.fileSources.get(r);if(typeof a<"u")return a;let{data:n,compressionMethod:c}=this.zipImpl.getFileSource(r);if(c===Fj)return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,n),n;if(c===Nj){if(s.asyncDecompress)return new Promise((f,p)=>{Rj.default.inflateRaw(n,(h,E)=>{h?p(h):(this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,E),f(E))})});{let f=Rj.default.inflateRawSync(n);return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,f),f}}else throw new Error(`Unsupported compression method: ${c}`)}async fchmodPromise(r,s){return this.chmodPromise(this.fdToPath(r,"fchmod"),s)}fchmodSync(r,s){return this.chmodSync(this.fdToPath(r,"fchmodSync"),s)}async chmodPromise(r,s){return this.chmodSync(r,s)}chmodSync(r,s){if(this.readOnly)throw or.EROFS(`chmod '${r}'`);s&=493;let a=this.resolveFilename(`chmod '${r}'`,r,!1),n=this.entries.get(a);if(typeof n>"u")throw new Error(`Assertion failed: The entry should have been registered (${a})`);let f=this.getUnixMode(n,xa.constants.S_IFREG|0)&-512|s;this.zipImpl.setExternalAttributes(n,lm,f<<16)}async fchownPromise(r,s,a){return this.chownPromise(this.fdToPath(r,"fchown"),s,a)}fchownSync(r,s,a){return this.chownSync(this.fdToPath(r,"fchownSync"),s,a)}async chownPromise(r,s,a){return this.chownSync(r,s,a)}chownSync(r,s,a){throw new Error("Unimplemented")}async renamePromise(r,s){return this.renameSync(r,s)}renameSync(r,s){throw new Error("Unimplemented")}async copyFilePromise(r,s,a){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=await this.getFileSource(n,{asyncDecompress:!0}),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}copyFileSync(r,s,a=0){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=this.getFileSource(n),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}prepareCopyFile(r,s,a=0){if(this.readOnly)throw or.EROFS(`copyfile '${r} -> '${s}'`);if(a&xa.constants.COPYFILE_FICLONE_FORCE)throw or.ENOSYS("unsupported clone operation",`copyfile '${r}' -> ${s}'`);let n=this.resolveFilename(`copyfile '${r} -> ${s}'`,r),c=this.entries.get(n);if(typeof c>"u")throw or.EINVAL(`copyfile '${r}' -> '${s}'`);let f=this.resolveFilename(`copyfile '${r}' -> ${s}'`,s),p=this.entries.get(f);if(a&(xa.constants.COPYFILE_EXCL|xa.constants.COPYFILE_FICLONE_FORCE)&&typeof p<"u")throw or.EEXIST(`copyfile '${r}' -> '${s}'`);return{indexSource:c,resolvedDestP:f,indexDest:p}}async appendFilePromise(r,s,a){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFilePromise(r,s,a)}appendFileSync(r,s,a={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFileSync(r,s,a)}fdToPath(r,s){let a=this.fds.get(r)?.p;if(typeof a>"u")throw or.EBADF(s);return a}async writeFilePromise(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([await this.getFileSource(f,{asyncDecompress:!0}),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&await this.chmodPromise(p,c)}writeFileSync(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([this.getFileSource(f),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&this.chmodSync(p,c)}prepareWriteFile(r,s){if(typeof r=="number"&&(r=this.fdToPath(r,"read")),this.readOnly)throw or.EROFS(`open '${r}'`);let a=this.resolveFilename(`open '${r}'`,r);if(this.listings.has(a))throw or.EISDIR(`open '${r}'`);let n=null,c=null;typeof s=="string"?n=s:typeof s=="object"&&({encoding:n=null,mode:c=null}=s);let f=this.entries.get(a);return{encoding:n,mode:c,resolvedP:a,index:f}}async unlinkPromise(r){return this.unlinkSync(r)}unlinkSync(r){if(this.readOnly)throw or.EROFS(`unlink '${r}'`);let s=this.resolveFilename(`unlink '${r}'`,r);if(this.listings.has(s))throw or.EISDIR(`unlink '${r}'`);let a=this.entries.get(s);if(typeof a>"u")throw or.EINVAL(`unlink '${r}'`);this.deleteEntry(s,a)}async utimesPromise(r,s,a){return this.utimesSync(r,s,a)}utimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`utimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r);this.utimesImpl(n,a)}async lutimesPromise(r,s,a){return this.lutimesSync(r,s,a)}lutimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`lutimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r,!1);this.utimesImpl(n,a)}utimesImpl(r,s){this.listings.has(r)&&(this.entries.has(r)||this.hydrateDirectory(r));let a=this.entries.get(r);if(a===void 0)throw new Error("Unreachable");this.zipImpl.setMtime(a,Ynt(s))}async mkdirPromise(r,s){return this.mkdirSync(r,s)}mkdirSync(r,{mode:s=493,recursive:a=!1}={}){if(a)return this.mkdirpSync(r,{chmod:s});if(this.readOnly)throw or.EROFS(`mkdir '${r}'`);let n=this.resolveFilename(`mkdir '${r}'`,r);if(this.entries.has(n)||this.listings.has(n))throw or.EEXIST(`mkdir '${r}'`);this.hydrateDirectory(n),this.chmodSync(n,s)}async rmdirPromise(r,s){return this.rmdirSync(r,s)}rmdirSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rmdir '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rmdir '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rmdir '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rmdir '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rmdir '${r}'`);this.deleteEntry(r,c)}async rmPromise(r,s){return this.rmSync(r,s)}rmSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rm '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rm '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rm '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rm '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rm '${r}'`);this.deleteEntry(r,c)}hydrateDirectory(r){let s=this.zipImpl.addDirectory(J.relative(vt.root,r));return this.registerListing(r),this.registerEntry(r,s),s}async linkPromise(r,s){return this.linkSync(r,s)}linkSync(r,s){throw or.EOPNOTSUPP(`link '${r}' -> '${s}'`)}async symlinkPromise(r,s){return this.symlinkSync(r,s)}symlinkSync(r,s){if(this.readOnly)throw or.EROFS(`symlink '${r}' -> '${s}'`);let a=this.resolveFilename(`symlink '${r}' -> '${s}'`,s);if(this.listings.has(a))throw or.EISDIR(`symlink '${r}' -> '${s}'`);if(this.entries.has(a))throw or.EEXIST(`symlink '${r}' -> '${s}'`);let n=this.setFileSource(a,r);this.registerEntry(a,n),this.zipImpl.setExternalAttributes(n,lm,(xa.constants.S_IFLNK|511)<<16),this.symlinkCount+=1}async readFilePromise(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=await this.readFileBuffer(r,{asyncDecompress:!0});return s?a.toString(s):a}readFileSync(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=this.readFileBuffer(r);return s?a.toString(s):a}readFileBuffer(r,s={asyncDecompress:!1}){typeof r=="number"&&(r=this.fdToPath(r,"read"));let a=this.resolveFilename(`open '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`open '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(a))throw or.EISDIR("read");let n=this.entries.get(a);if(n===void 0)throw new Error("Unreachable");return this.getFileSource(n,s)}async readdirPromise(r,s){return this.readdirSync(r,s)}readdirSync(r,s){let a=this.resolveFilename(`scandir '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`scandir '${r}'`);let n=this.listings.get(a);if(!n)throw or.ENOTDIR(`scandir '${r}'`);if(s?.recursive)if(s?.withFileTypes){let c=Array.from(n,f=>Object.assign(this.statImpl("lstat",J.join(r,f)),{name:f,path:vt.dot,parentPath:vt.dot}));for(let f of c){if(!f.isDirectory())continue;let p=J.join(f.path,f.name),h=this.listings.get(J.join(a,p));for(let E of h)c.push(Object.assign(this.statImpl("lstat",J.join(r,p,E)),{name:E,path:p,parentPath:p}))}return c}else{let c=[...n];for(let f of c){let p=this.listings.get(J.join(a,f));if(!(typeof p>"u"))for(let h of p)c.push(J.join(f,h))}return c}else return s?.withFileTypes?Array.from(n,c=>Object.assign(this.statImpl("lstat",J.join(r,c)),{name:c,path:void 0,parentPath:void 0})):[...n]}async readlinkPromise(r){let s=this.prepareReadlink(r);return(await this.getFileSource(s,{asyncDecompress:!0})).toString()}readlinkSync(r){let s=this.prepareReadlink(r);return this.getFileSource(s).toString()}prepareReadlink(r){let s=this.resolveFilename(`readlink '${r}'`,r,!1);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`readlink '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(s))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(s))throw or.EINVAL(`readlink '${r}'`);let a=this.entries.get(s);if(a===void 0)throw new Error("Unreachable");if(!this.isSymbolicLink(a))throw or.EINVAL(`readlink '${r}'`);return a}async truncatePromise(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=await this.getFileSource(n,{asyncDecompress:!0}),f=Buffer.alloc(s,0);return c.copy(f),await this.writeFilePromise(r,f)}truncateSync(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=this.getFileSource(n),f=Buffer.alloc(s,0);return c.copy(f),this.writeFileSync(r,f)}async ftruncatePromise(r,s){return this.truncatePromise(this.fdToPath(r,"ftruncate"),s)}ftruncateSync(r,s){return this.truncateSync(this.fdToPath(r,"ftruncateSync"),s)}watch(r,s,a){let n;switch(typeof s){case"function":case"string":case"undefined":n=!0;break;default:({persistent:n=!0}=s);break}if(!n)return{on:()=>{},close:()=>{}};let c=setInterval(()=>{},24*60*60*1e3);return{on:()=>{},close:()=>{clearInterval(c)}}}watchFile(r,s,a){let n=J.resolve(vt.root,r);return sE(this,n,s,a)}unwatchFile(r,s){let a=J.resolve(vt.root,r);return md(this,a,s)}}});function Cpe(t,e,r=Buffer.alloc(0),s){let a=new As(r),n=C=>C===e||C.startsWith(`${e}/`)?C.slice(0,e.length):null,c=async(C,S)=>()=>a,f=(C,S)=>a,p={...t},h=new Yn(p),E=new e0({baseFs:h,getMountPoint:n,factoryPromise:c,factorySync:f,magicByte:21,maxAge:1/0,typeCheck:s?.typeCheck});return U2(Ipe.default,new t0(E)),a}var Ipe,wpe=Xe(()=>{Dt();Ipe=ut(Ie("fs"));vT()});var Bpe=Xe(()=>{mpe();vT();wpe()});var Oj,uv,ST,vpe=Xe(()=>{Dt();vT();Oj={CENTRAL_DIRECTORY:33639248,END_OF_CENTRAL_DIRECTORY:101010256},uv=22,ST=class t{constructor(e){this.filesShouldBeCached=!1;if("buffer"in e)throw new Error("Buffer based zip archives are not supported");if(!e.readOnly)throw new Error("Writable zip archives are not supported");this.baseFs=e.baseFs,this.fd=this.baseFs.openSync(e.path,"r");try{this.entries=t.readZipSync(this.fd,this.baseFs,e.size)}catch(r){throw this.baseFs.closeSync(this.fd),this.fd="closed",r}}static readZipSync(e,r,s){if(s=0;N--)if(n.readUInt32LE(N)===Oj.END_OF_CENTRAL_DIRECTORY){a=N;break}if(a===-1)throw new Error("Not a zip archive")}let c=n.readUInt16LE(a+10),f=n.readUInt32LE(a+12),p=n.readUInt32LE(a+16),h=n.readUInt16LE(a+20);if(a+h+uv>n.length)throw new Error("Zip archive inconsistent");if(c==65535||f==4294967295||p==4294967295)throw new Error("Zip 64 is not supported");if(f>s)throw new Error("Zip archive inconsistent");if(c>f/46)throw new Error("Zip archive inconsistent");let E=Buffer.alloc(f);if(r.readSync(e,E,0,E.length,p)!==E.length)throw new Error("Zip archive inconsistent");let C=[],S=0,P=0,I=0;for(;PE.length)throw new Error("Zip archive inconsistent");if(E.readUInt32LE(S)!==Oj.CENTRAL_DIRECTORY)throw new Error("Zip archive inconsistent");let N=E.readUInt16LE(S+4)>>>8;if(E.readUInt16LE(S+8)&1)throw new Error("Encrypted zip files are not supported");let W=E.readUInt16LE(S+10),ee=E.readUInt32LE(S+16),ie=E.readUInt16LE(S+28),ue=E.readUInt16LE(S+30),le=E.readUInt16LE(S+32),me=E.readUInt32LE(S+42),pe=E.toString("utf8",S+46,S+46+ie).replaceAll("\0"," ");if(pe.includes("\0"))throw new Error("Invalid ZIP file");let Be=E.readUInt32LE(S+20),Ce=E.readUInt32LE(S+38);C.push({name:pe,os:N,mtime:fi.SAFE_TIME,crc:ee,compressionMethod:W,isSymbolicLink:N===lm&&(Ce>>>16&fi.S_IFMT)===fi.S_IFLNK,size:E.readUInt32LE(S+24),compressedSize:Be,externalAttributes:Ce,localHeaderOffset:me}),I+=Be,P+=1,S+=46+ie+ue+le}if(I>s)throw new Error("Zip archive inconsistent");if(S!==E.length)throw new Error("Zip archive inconsistent");return C}getExternalAttributes(e){let r=this.entries[e];return[r.os,r.externalAttributes]}getListings(){return this.entries.map(e=>e.name)}getSymlinkCount(){let e=0;for(let r of this.entries)r.isSymbolicLink&&(e+=1);return e}stat(e){let r=this.entries[e];return{crc:r.crc,mtime:r.mtime,size:r.size}}locate(e){for(let r=0;rEpe,DEFLATE:()=>Nj,JsZipImpl:()=>ST,LibZipImpl:()=>BI,STORE:()=>Fj,ZIP_UNIX:()=>lm,ZipFS:()=>As,ZipOpenFS:()=>$f,getArchivePart:()=>xj,getLibzipPromise:()=>Jnt,getLibzipSync:()=>Vnt,makeEmptyArchive:()=>BT,mountMemoryDrive:()=>Cpe});function Vnt(){return cv()}async function Jnt(){return cv()}var Spe,eA=Xe(()=>{Dj();Spe=ut(ppe());dpe();Bpe();vpe();Qj();Ape(()=>{let t=(0,Spe.default)();return gpe(t)})});var Av,Dpe=Xe(()=>{Dt();Yt();pv();Av=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",process.cwd(),{description:"The directory to run the command in"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.usage={description:"run a command using yarn's portable shell",details:` + This command will run a command using Yarn's portable shell. + + Make sure to escape glob patterns, redirections, and other features that might be expanded by your own shell. + + Note: To escape something from Yarn's shell, you might have to escape it twice, the first time from your own shell. + + Note: Don't use this command in Yarn scripts, as Yarn's shell is automatically used. + + For a list of features, visit: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-shell/README.md. + `,examples:[["Run a simple command","$0 echo Hello"],["Run a command with a glob pattern","$0 echo '*.js'"],["Run a command with a redirection","$0 echo Hello World '>' hello.txt"],["Run a command with an escaped glob pattern (The double escape is needed in Unix shells)",`$0 echo '"*.js"'`],["Run a command with a variable (Double quotes are needed in Unix shells, to prevent them from expanding the variable)",'$0 "GREETING=Hello echo $GREETING World"']]}}async execute(){let r=this.args.length>0?`${this.commandName} ${this.args.join(" ")}`:this.commandName;return await vI(r,[],{cwd:fe.toPortablePath(this.cwd),stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}}});var Vl,bpe=Xe(()=>{Vl=class extends Error{constructor(e){super(e),this.name="ShellError"}}});var PT={};Vt(PT,{fastGlobOptions:()=>kpe,isBraceExpansion:()=>Lj,isGlobPattern:()=>Knt,match:()=>znt,micromatchOptions:()=>bT});function Knt(t){if(!DT.default.scan(t,bT).isGlob)return!1;try{DT.default.parse(t,bT)}catch{return!1}return!0}function znt(t,{cwd:e,baseFs:r}){return(0,Ppe.default)(t,{...kpe,cwd:fe.fromPortablePath(e),fs:ax(xpe.default,new t0(r))})}function Lj(t){return DT.default.scan(t,bT).isBrace}var Ppe,xpe,DT,bT,kpe,Qpe=Xe(()=>{Dt();Ppe=ut(BQ()),xpe=ut(Ie("fs")),DT=ut(Go()),bT={strictBrackets:!0},kpe={onlyDirectories:!1,onlyFiles:!1}});function Mj(){}function Uj(){for(let t of cm)t.kill()}function Npe(t,e,r,s){return a=>{let n=a[0]instanceof tA.Transform?"pipe":a[0],c=a[1]instanceof tA.Transform?"pipe":a[1],f=a[2]instanceof tA.Transform?"pipe":a[2],p=(0,Rpe.default)(t,e,{...s,stdio:[n,c,f]});return cm.add(p),cm.size===1&&(process.on("SIGINT",Mj),process.on("SIGTERM",Uj)),a[0]instanceof tA.Transform&&a[0].pipe(p.stdin),a[1]instanceof tA.Transform&&p.stdout.pipe(a[1],{end:!1}),a[2]instanceof tA.Transform&&p.stderr.pipe(a[2],{end:!1}),{stdin:p.stdin,promise:new Promise(h=>{p.on("error",E=>{switch(cm.delete(p),cm.size===0&&(process.off("SIGINT",Mj),process.off("SIGTERM",Uj)),E.code){case"ENOENT":a[2].write(`command not found: ${t} +`),h(127);break;case"EACCES":a[2].write(`permission denied: ${t} +`),h(128);break;default:a[2].write(`uncaught error: ${E.message} +`),h(1);break}}),p.on("close",E=>{cm.delete(p),cm.size===0&&(process.off("SIGINT",Mj),process.off("SIGTERM",Uj)),h(E!==null?E:129)})})}}}function Ope(t){return e=>{let r=e[0]==="pipe"?new tA.PassThrough:e[0];return{stdin:r,promise:Promise.resolve().then(()=>t({stdin:r,stdout:e[1],stderr:e[2]}))}}}function xT(t,e){return Hj.start(t,e)}function Tpe(t,e=null){let r=new tA.PassThrough,s=new Fpe.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` +`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",t(e!==null?`${e} ${p}`:p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&t(e!==null?`${e} ${n}`:n)}),r}function Lpe(t,{prefix:e}){return{stdout:Tpe(r=>t.stdout.write(`${r} +`),t.stdout.isTTY?e:null),stderr:Tpe(r=>t.stderr.write(`${r} +`),t.stderr.isTTY?e:null)}}var Rpe,tA,Fpe,cm,Oc,_j,Hj,jj=Xe(()=>{Rpe=ut(_U()),tA=Ie("stream"),Fpe=Ie("string_decoder"),cm=new Set;Oc=class{constructor(e){this.stream=e}close(){}get(){return this.stream}},_j=class{constructor(){this.stream=null}close(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");this.stream.end()}attach(e){this.stream=e}get(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");return this.stream}},Hj=class t{constructor(e,r){this.stdin=null;this.stdout=null;this.stderr=null;this.pipe=null;this.ancestor=e,this.implementation=r}static start(e,{stdin:r,stdout:s,stderr:a}){let n=new t(null,e);return n.stdin=r,n.stdout=s,n.stderr=a,n}pipeTo(e,r=1){let s=new t(this,e),a=new _j;return s.pipe=a,s.stdout=this.stdout,s.stderr=this.stderr,(r&1)===1?this.stdout=a:this.ancestor!==null&&(this.stderr=this.ancestor.stdout),(r&2)===2?this.stderr=a:this.ancestor!==null&&(this.stderr=this.ancestor.stderr),s}async exec(){let e=["ignore","ignore","ignore"];if(this.pipe)e[0]="pipe";else{if(this.stdin===null)throw new Error("Assertion failed: No input stream registered");e[0]=this.stdin.get()}let r;if(this.stdout===null)throw new Error("Assertion failed: No output stream registered");r=this.stdout,e[1]=r.get();let s;if(this.stderr===null)throw new Error("Assertion failed: No error stream registered");s=this.stderr,e[2]=s.get();let a=this.implementation(e);return this.pipe&&this.pipe.attach(a.stdin),await a.promise.then(n=>(r.close(),s.close(),n))}async run(){let e=[];for(let s=this;s;s=s.ancestor)e.push(s.exec());return(await Promise.all(e))[0]}}});var mv={};Vt(mv,{EntryCommand:()=>Av,ShellError:()=>Vl,execute:()=>vI,globUtils:()=>PT});function Mpe(t,e,r){let s=new Jl.PassThrough({autoDestroy:!0});switch(t){case 0:(e&1)===1&&r.stdin.pipe(s,{end:!1}),(e&2)===2&&r.stdin instanceof Jl.Writable&&s.pipe(r.stdin,{end:!1});break;case 1:(e&1)===1&&r.stdout.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stdout,{end:!1});break;case 2:(e&1)===1&&r.stderr.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stderr,{end:!1});break;default:throw new Vl(`Bad file descriptor: "${t}"`)}return s}function QT(t,e={}){let r={...t,...e};return r.environment={...t.environment,...e.environment},r.variables={...t.variables,...e.variables},r}async function Znt(t,e,r){let s=[],a=new Jl.PassThrough;return a.on("data",n=>s.push(n)),await TT(t,e,QT(r,{stdout:a})),Buffer.concat(s).toString().replace(/[\r\n]+$/,"")}async function Upe(t,e,r){let s=t.map(async n=>{let c=await um(n.args,e,r);return{name:n.name,value:c.join(" ")}});return(await Promise.all(s)).reduce((n,c)=>(n[c.name]=c.value,n),{})}function kT(t){return t.match(/[^ \r\n\t]+/g)||[]}async function Wpe(t,e,r,s,a=s){switch(t.name){case"$":s(String(process.pid));break;case"#":s(String(e.args.length));break;case"@":if(t.quoted)for(let n of e.args)a(n);else for(let n of e.args){let c=kT(n);for(let f=0;f=0&&n"u"&&(t.defaultValue?c=(await um(t.defaultValue,e,r)).join(" "):t.alternativeValue&&(c="")),typeof c>"u")throw f?new Vl(`Unbound argument #${n}`):new Vl(`Unbound variable "${t.name}"`);if(t.quoted)s(c);else{let p=kT(c);for(let E=0;Es.push(n));let a=Number(s.join(" "));return Number.isNaN(a)?hv({type:"variable",name:s.join(" ")},e,r):hv({type:"number",value:a},e,r)}else return $nt[t.type](await hv(t.left,e,r),await hv(t.right,e,r))}async function um(t,e,r){let s=new Map,a=[],n=[],c=E=>{n.push(E)},f=()=>{n.length>0&&a.push(n.join("")),n=[]},p=E=>{c(E),f()},h=(E,C,S)=>{let P=JSON.stringify({type:E,fd:C}),I=s.get(P);typeof I>"u"&&s.set(P,I=[]),I.push(S)};for(let E of t){let C=!1;switch(E.type){case"redirection":{let S=await um(E.args,e,r);for(let P of S)h(E.subtype,E.fd,P)}break;case"argument":for(let S of E.segments)switch(S.type){case"text":c(S.text);break;case"glob":c(S.pattern),C=!0;break;case"shell":{let P=await Znt(S.shell,e,r);if(S.quoted)c(P);else{let I=kT(P);for(let R=0;R"u")throw new Error("Assertion failed: Expected a glob pattern to have been set");let P=await e.glob.match(S,{cwd:r.cwd,baseFs:e.baseFs});if(P.length===0){let I=Lj(S)?". Note: Brace expansion of arbitrary strings isn't currently supported. For more details, please read this issue: https://github.com/yarnpkg/berry/issues/22":"";throw new Vl(`No matches found: "${S}"${I}`)}for(let I of P.sort())p(I)}}if(s.size>0){let E=[];for(let[C,S]of s.entries())E.splice(E.length,0,C,String(S.length),...S);a.splice(0,0,"__ysh_set_redirects",...E,"--")}return a}function gv(t,e,r){e.builtins.has(t[0])||(t=["command",...t]);let s=fe.fromPortablePath(r.cwd),a=r.environment;typeof a.PWD<"u"&&(a={...a,PWD:s});let[n,...c]=t;if(n==="command")return Npe(c[0],c.slice(1),e,{cwd:s,env:a});let f=e.builtins.get(n);if(typeof f>"u")throw new Error(`Assertion failed: A builtin should exist for "${n}"`);return Ope(async({stdin:p,stdout:h,stderr:E})=>{let{stdin:C,stdout:S,stderr:P}=r;r.stdin=p,r.stdout=h,r.stderr=E;try{return await f(c,e,r)}finally{r.stdin=C,r.stdout=S,r.stderr=P}})}function eit(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,QT(r,{stdin:a}));return{stdin:a,promise:n}}}function tit(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,r);return{stdin:a,promise:n}}}function _pe(t,e,r,s){if(e.length===0)return t;{let a;do a=String(Math.random());while(Object.hasOwn(s.procedures,a));return s.procedures={...s.procedures},s.procedures[a]=t,gv([...e,"__ysh_run_procedure",a],r,s)}}async function Hpe(t,e,r){let s=t,a=null,n=null;for(;s;){let c=s.then?{...r}:r,f;switch(s.type){case"command":{let p=await um(s.args,e,r),h=await Upe(s.envs,e,r);f=s.envs.length?gv(p,e,QT(c,{environment:h})):gv(p,e,c)}break;case"subshell":{let p=await um(s.args,e,r),h=eit(s.subshell,e,c);f=_pe(h,p,e,c)}break;case"group":{let p=await um(s.args,e,r),h=tit(s.group,e,c);f=_pe(h,p,e,c)}break;case"envs":{let p=await Upe(s.envs,e,r);c.environment={...c.environment,...p},f=gv(["true"],e,c)}break}if(typeof f>"u")throw new Error("Assertion failed: An action should have been generated");if(a===null)n=xT(f,{stdin:new Oc(c.stdin),stdout:new Oc(c.stdout),stderr:new Oc(c.stderr)});else{if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");switch(a){case"|":n=n.pipeTo(f,1);break;case"|&":n=n.pipeTo(f,3);break}}s.then?(a=s.then.type,s=s.then.chain):s=null}if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");return await n.run()}async function rit(t,e,r,{background:s=!1}={}){function a(n){let c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[n%c.length];return jpe.default.hex(f)}if(s){let n=r.nextBackgroundJobIndex++,c=a(n),f=`[${n}]`,p=c(f),{stdout:h,stderr:E}=Lpe(r,{prefix:p});return r.backgroundJobs.push(Hpe(t,e,QT(r,{stdout:h,stderr:E})).catch(C=>E.write(`${C.message} +`)).finally(()=>{r.stdout.isTTY&&r.stdout.write(`Job ${p}, '${c(AE(t))}' has ended +`)})),0}return await Hpe(t,e,r)}async function nit(t,e,r,{background:s=!1}={}){let a,n=f=>{a=f,r.variables["?"]=String(f)},c=async f=>{try{return await rit(f.chain,e,r,{background:s&&typeof f.then>"u"})}catch(p){if(!(p instanceof Vl))throw p;return r.stderr.write(`${p.message} +`),1}};for(n(await c(t));t.then;){if(r.exitCode!==null)return r.exitCode;switch(t.then.type){case"&&":a===0&&n(await c(t.then.line));break;case"||":a!==0&&n(await c(t.then.line));break;default:throw new Error(`Assertion failed: Unsupported command type: "${t.then.type}"`)}t=t.then.line}return a}async function TT(t,e,r){let s=r.backgroundJobs;r.backgroundJobs=[];let a=0;for(let{command:n,type:c}of t){if(a=await nit(n,e,r,{background:c==="&"}),r.exitCode!==null)return r.exitCode;r.variables["?"]=String(a)}return await Promise.all(r.backgroundJobs),r.backgroundJobs=s,a}function Ype(t){switch(t.type){case"variable":return t.name==="@"||t.name==="#"||t.name==="*"||Number.isFinite(parseInt(t.name,10))||"defaultValue"in t&&!!t.defaultValue&&t.defaultValue.some(e=>dv(e))||"alternativeValue"in t&&!!t.alternativeValue&&t.alternativeValue.some(e=>dv(e));case"arithmetic":return Gj(t.arithmetic);case"shell":return qj(t.shell);default:return!1}}function dv(t){switch(t.type){case"redirection":return t.args.some(e=>dv(e));case"argument":return t.segments.some(e=>Ype(e));default:throw new Error(`Assertion failed: Unsupported argument type: "${t.type}"`)}}function Gj(t){switch(t.type){case"variable":return Ype(t);case"number":return!1;default:return Gj(t.left)||Gj(t.right)}}function qj(t){return t.some(({command:e})=>{for(;e;){let r=e.chain;for(;r;){let s;switch(r.type){case"subshell":s=qj(r.subshell);break;case"command":s=r.envs.some(a=>a.args.some(n=>dv(n)))||r.args.some(a=>dv(a));break}if(s)return!0;if(!r.then)break;r=r.then.chain}if(!e.then)break;e=e.then.line}return!1})}async function vI(t,e=[],{baseFs:r=new Yn,builtins:s={},cwd:a=fe.toPortablePath(process.cwd()),env:n=process.env,stdin:c=process.stdin,stdout:f=process.stdout,stderr:p=process.stderr,variables:h={},glob:E=PT}={}){let C={};for(let[I,R]of Object.entries(n))typeof R<"u"&&(C[I]=R);let S=new Map(Xnt);for(let[I,R]of Object.entries(s))S.set(I,R);c===null&&(c=new Jl.PassThrough,c.end());let P=ux(t,E);if(!qj(P)&&P.length>0&&e.length>0){let{command:I}=P[P.length-1];for(;I.then;)I=I.then.line;let R=I.chain;for(;R.then;)R=R.then.chain;R.type==="command"&&(R.args=R.args.concat(e.map(N=>({type:"argument",segments:[{type:"text",text:N}]}))))}return await TT(P,{args:e,baseFs:r,builtins:S,initialStdin:c,initialStdout:f,initialStderr:p,glob:E},{cwd:a,environment:C,exitCode:null,procedures:{},stdin:c,stdout:f,stderr:p,variables:Object.assign({},h,{"?":0}),nextBackgroundJobIndex:1,backgroundJobs:[]})}var jpe,Gpe,Jl,qpe,Xnt,$nt,pv=Xe(()=>{Dt();wc();jpe=ut(TE()),Gpe=Ie("os"),Jl=Ie("stream"),qpe=Ie("timers/promises");Dpe();bpe();Qpe();jj();jj();Xnt=new Map([["cd",async([t=(0,Gpe.homedir)(),...e],r,s)=>{let a=J.resolve(s.cwd,fe.toPortablePath(t));if(!(await r.baseFs.statPromise(a).catch(c=>{throw c.code==="ENOENT"?new Vl(`cd: no such file or directory: ${t}`):c})).isDirectory())throw new Vl(`cd: not a directory: ${t}`);return s.cwd=a,0}],["pwd",async(t,e,r)=>(r.stdout.write(`${fe.fromPortablePath(r.cwd)} +`),0)],[":",async(t,e,r)=>0],["true",async(t,e,r)=>0],["false",async(t,e,r)=>1],["exit",async([t,...e],r,s)=>s.exitCode=parseInt(t??s.variables["?"],10)],["echo",async(t,e,r)=>(r.stdout.write(`${t.join(" ")} +`),0)],["sleep",async([t],e,r)=>{if(typeof t>"u")throw new Vl("sleep: missing operand");let s=Number(t);if(Number.isNaN(s))throw new Vl(`sleep: invalid time interval '${t}'`);return await(0,qpe.setTimeout)(1e3*s,0)}],["unset",async(t,e,r)=>{for(let s of t)delete r.environment[s],delete r.variables[s];return 0}],["__ysh_run_procedure",async(t,e,r)=>{let s=r.procedures[t[0]];return await xT(s,{stdin:new Oc(r.stdin),stdout:new Oc(r.stdout),stderr:new Oc(r.stderr)}).run()}],["__ysh_set_redirects",async(t,e,r)=>{let s=r.stdin,a=r.stdout,n=r.stderr,c=[],f=[],p=[],h=0;for(;t[h]!=="--";){let C=t[h++],{type:S,fd:P}=JSON.parse(C),I=W=>{switch(P){case null:case 0:c.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},R=W=>{switch(P){case null:case 1:f.push(W);break;case 2:p.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},N=Number(t[h++]),U=h+N;for(let W=h;We.baseFs.createReadStream(J.resolve(r.cwd,fe.toPortablePath(t[W]))));break;case"<<<":I(()=>{let ee=new Jl.PassThrough;return process.nextTick(()=>{ee.write(`${t[W]} +`),ee.end()}),ee});break;case"<&":I(()=>Mpe(Number(t[W]),1,r));break;case">":case">>":{let ee=J.resolve(r.cwd,fe.toPortablePath(t[W]));R(ee==="/dev/null"?new Jl.Writable({autoDestroy:!0,emitClose:!0,write(ie,ue,le){setImmediate(le)}}):e.baseFs.createWriteStream(ee,S===">>"?{flags:"a"}:void 0))}break;case">&":R(Mpe(Number(t[W]),2,r));break;default:throw new Error(`Assertion failed: Unsupported redirection type: "${S}"`)}}if(c.length>0){let C=new Jl.PassThrough;s=C;let S=P=>{if(P===c.length)C.end();else{let I=c[P]();I.pipe(C,{end:!1}),I.on("end",()=>{S(P+1)})}};S(0)}if(f.length>0){let C=new Jl.PassThrough;a=C;for(let S of f)C.pipe(S)}if(p.length>0){let C=new Jl.PassThrough;n=C;for(let S of p)C.pipe(S)}let E=await xT(gv(t.slice(h+1),e,r),{stdin:new Oc(s),stdout:new Oc(a),stderr:new Oc(n)}).run();return await Promise.all(f.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),await Promise.all(p.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),E}]]);$nt={addition:(t,e)=>t+e,subtraction:(t,e)=>t-e,multiplication:(t,e)=>t*e,division:(t,e)=>Math.trunc(t/e)}});var Vpe=_((S4t,RT)=>{function iit(){var t=0,e=1,r=2,s=3,a=4,n=5,c=6,f=7,p=8,h=9,E=10,C=11,S=12,P=13,I=14,R=15,N=16,U=17,W=0,ee=1,ie=2,ue=3,le=4;function me(g,we){return 55296<=g.charCodeAt(we)&&g.charCodeAt(we)<=56319&&56320<=g.charCodeAt(we+1)&&g.charCodeAt(we+1)<=57343}function pe(g,we){we===void 0&&(we=0);var ye=g.charCodeAt(we);if(55296<=ye&&ye<=56319&&we=1){var Ae=g.charCodeAt(we-1),se=ye;return 55296<=Ae&&Ae<=56319?(Ae-55296)*1024+(se-56320)+65536:se}return ye}function Be(g,we,ye){var Ae=[g].concat(we).concat([ye]),se=Ae[Ae.length-2],Z=ye,De=Ae.lastIndexOf(I);if(De>1&&Ae.slice(1,De).every(function(j){return j==s})&&[s,P,U].indexOf(g)==-1)return ie;var Re=Ae.lastIndexOf(a);if(Re>0&&Ae.slice(1,Re).every(function(j){return j==a})&&[S,a].indexOf(se)==-1)return Ae.filter(function(j){return j==a}).length%2==1?ue:le;if(se==t&&Z==e)return W;if(se==r||se==t||se==e)return Z==I&&we.every(function(j){return j==s})?ie:ee;if(Z==r||Z==t||Z==e)return ee;if(se==c&&(Z==c||Z==f||Z==h||Z==E))return W;if((se==h||se==f)&&(Z==f||Z==p))return W;if((se==E||se==p)&&Z==p)return W;if(Z==s||Z==R)return W;if(Z==n)return W;if(se==S)return W;var mt=Ae.indexOf(s)!=-1?Ae.lastIndexOf(s)-1:Ae.length-2;return[P,U].indexOf(Ae[mt])!=-1&&Ae.slice(mt+1,-1).every(function(j){return j==s})&&Z==I||se==R&&[N,U].indexOf(Z)!=-1?W:we.indexOf(a)!=-1?ie:se==a&&Z==a?W:ee}this.nextBreak=function(g,we){if(we===void 0&&(we=0),we<0)return 0;if(we>=g.length-1)return g.length;for(var ye=Ce(pe(g,we)),Ae=[],se=we+1;se{var sit=/^(.*?)(\x1b\[[^m]+m|\x1b\]8;;.*?(\x1b\\|\u0007))/,FT;function oit(){if(FT)return FT;if(typeof Intl.Segmenter<"u"){let t=new Intl.Segmenter("en",{granularity:"grapheme"});return FT=e=>Array.from(t.segment(e),({segment:r})=>r)}else{let t=Vpe(),e=new t;return FT=r=>e.splitGraphemes(r)}}Jpe.exports=(t,e=0,r=t.length)=>{if(e<0||r<0)throw new RangeError("Negative indices aren't supported by this implementation");let s=r-e,a="",n=0,c=0;for(;t.length>0;){let f=t.match(sit)||[t,t,void 0],p=oit()(f[1]),h=Math.min(e-n,p.length);p=p.slice(h);let E=Math.min(s-c,p.length);a+=p.slice(0,E).join(""),n+=h,c+=E,typeof f[2]<"u"&&(a+=f[2]),t=t.slice(f[0].length)}return a}});var fn,yv=Xe(()=>{fn=process.env.YARN_IS_TEST_ENV?"0.0.0":"4.12.0"});function the(t,{configuration:e,json:r}){if(!e.get("enableMessageNames"))return"";let a=Yf(t===null?0:t);return!r&&t===null?Ht(e,a,"grey"):a}function Wj(t,{configuration:e,json:r}){let s=the(t,{configuration:e,json:r});if(!s||t===null||t===0)return s;let a=Br[t],n=`https://yarnpkg.com/advanced/error-codes#${s}---${a}`.toLowerCase();return KE(e,s,n)}async function SI({configuration:t,stdout:e,forceError:r},s){let a=await Ot.start({configuration:t,stdout:e,includeFooter:!1},async n=>{let c=!1,f=!1;for(let p of s)typeof p.option<"u"&&(p.error||r?(f=!0,n.reportError(50,p.message)):(c=!0,n.reportWarning(50,p.message)),p.callback?.());c&&!f&&n.reportSeparator()});return a.hasErrors()?a.exitCode():null}var $pe,NT,ait,zpe,Xpe,D0,ehe,Zpe,lit,cit,OT,uit,Ot,Ev=Xe(()=>{$pe=ut(Kpe()),NT=ut(Fd());Gx();Tc();yv();xc();ait="\xB7",zpe=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],Xpe=80,D0=NT.default.GITHUB_ACTIONS?{start:t=>`::group::${t} +`,end:t=>`::endgroup:: +`}:NT.default.TRAVIS?{start:t=>`travis_fold:start:${t} +`,end:t=>`travis_fold:end:${t} +`}:NT.default.GITLAB?{start:t=>`section_start:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}[collapsed=true]\r\x1B[0K${t} +`,end:t=>`section_end:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}\r\x1B[0K`}:null,ehe=D0!==null,Zpe=new Date,lit=["iTerm.app","Apple_Terminal","WarpTerminal","vscode"].includes(process.env.TERM_PROGRAM)||!!process.env.WT_SESSION,cit=t=>t,OT=cit({patrick:{date:[17,3],chars:["\u{1F340}","\u{1F331}"],size:40},simba:{date:[19,7],chars:["\u{1F981}","\u{1F334}"],size:40},jack:{date:[31,10],chars:["\u{1F383}","\u{1F987}"],size:40},hogsfather:{date:[31,12],chars:["\u{1F389}","\u{1F384}"],size:40},default:{chars:["=","-"],size:80}}),uit=lit&&Object.keys(OT).find(t=>{let e=OT[t];return!(e.date&&(e.date[0]!==Zpe.getDate()||e.date[1]!==Zpe.getMonth()+1))})||"default";Ot=class extends Ao{constructor({configuration:r,stdout:s,json:a=!1,forceSectionAlignment:n=!1,includeNames:c=!0,includePrefix:f=!0,includeFooter:p=!0,includeLogs:h=!a,includeInfos:E=h,includeWarnings:C=h}){super();this.uncommitted=new Set;this.warningCount=0;this.errorCount=0;this.timerFooter=[];this.startTime=Date.now();this.indent=0;this.level=0;this.progress=new Map;this.progressTime=0;this.progressFrame=0;this.progressTimeout=null;this.progressStyle=null;this.progressMaxScaledSize=null;if(RB(this,{configuration:r}),this.configuration=r,this.forceSectionAlignment=n,this.includeNames=c,this.includePrefix=f,this.includeFooter=p,this.includeInfos=E,this.includeWarnings=C,this.json=a,this.stdout=s,r.get("enableProgressBars")&&!a&&s.isTTY&&s.columns>22){let S=r.get("progressBarStyle")||uit;if(!Object.hasOwn(OT,S))throw new Error("Assertion failed: Invalid progress bar style");this.progressStyle=OT[S];let P=Math.min(this.getRecommendedLength(),80);this.progressMaxScaledSize=Math.floor(this.progressStyle.size*P/80)}}static async start(r,s){let a=new this(r),n=process.emitWarning;process.emitWarning=(c,f)=>{if(typeof c!="string"){let h=c;c=h.message,f=f??h.name}let p=typeof f<"u"?`${f}: ${c}`:c;a.reportWarning(0,p)},r.includeVersion&&a.reportInfo(0,zd(r.configuration,`Yarn ${fn}`,2));try{await s(a)}catch(c){a.reportExceptionOnce(c)}finally{await a.finalize(),process.emitWarning=n}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}getRecommendedLength(){let s=this.progressStyle!==null?this.stdout.columns-1:super.getRecommendedLength();return Math.max(40,s-12-this.indent*2)}startSectionSync({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}async startSectionPromise({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return await n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}startTimerImpl(r,s,a){return{cb:typeof s=="function"?s:a,reportHeader:()=>{this.level+=1,this.reportInfo(null,`\u250C ${r}`),this.indent+=1,D0!==null&&!this.json&&this.includeInfos&&this.stdout.write(D0.start(r))},reportFooter:f=>{if(this.indent-=1,D0!==null&&!this.json&&this.includeInfos){this.stdout.write(D0.end(r));for(let p of this.timerFooter)p()}this.configuration.get("enableTimers")&&f>200?this.reportInfo(null,`\u2514 Completed in ${Ht(this.configuration,f,ht.DURATION)}`):this.reportInfo(null,"\u2514 Completed"),this.level-=1},skipIfEmpty:(typeof s=="function"?{}:s).skipIfEmpty}}startTimerSync(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionSync(c,n)}async startTimerPromise(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionPromise(c,n)}reportSeparator(){this.indent===0?this.writeLine(""):this.reportInfo(null,"")}reportInfo(r,s){if(!this.includeInfos)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"",c=`${this.formatPrefix(n,"blueBright")}${s}`;this.json?this.reportJson({type:"info",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(c)}reportWarning(r,s){if(this.warningCount+=1,!this.includeWarnings)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"warning",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"yellowBright")}${s}`)}reportError(r,s){this.errorCount+=1,this.timerFooter.push(()=>this.reportErrorImpl(r,s)),this.reportErrorImpl(r,s)}reportErrorImpl(r,s){this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"error",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"redBright")}${s}`,{truncate:!1})}reportFold(r,s){if(!D0)return;let a=`${D0.start(r)}${s}${D0.end(r)}`;this.timerFooter.push(()=>this.stdout.write(a))}reportProgress(r){if(this.progressStyle===null)return{...Promise.resolve(),stop:()=>{}};if(r.hasProgress&&r.hasTitle)throw new Error("Unimplemented: Progress bars can't have both progress and titles.");let s=!1,a=Promise.resolve().then(async()=>{let c={progress:r.hasProgress?0:void 0,title:r.hasTitle?"":void 0};this.progress.set(r,{definition:c,lastScaledSize:r.hasProgress?-1:void 0,lastTitle:void 0}),this.refreshProgress({delta:-1});for await(let{progress:f,title:p}of r)s||c.progress===f&&c.title===p||(c.progress=f,c.title=p,this.refreshProgress());n()}),n=()=>{s||(s=!0,this.progress.delete(r),this.refreshProgress({delta:1}))};return{...a,stop:n}}reportJson(r){this.json&&this.writeLine(`${JSON.stringify(r)}`)}async finalize(){if(!this.includeFooter)return;let r="";this.errorCount>0?r="Failed with errors":this.warningCount>0?r="Done with warnings":r="Done";let s=Ht(this.configuration,Date.now()-this.startTime,ht.DURATION),a=this.configuration.get("enableTimers")?`${r} in ${s}`:r;this.errorCount>0?this.reportError(0,a):this.warningCount>0?this.reportWarning(0,a):this.reportInfo(0,a)}writeLine(r,{truncate:s}={}){this.clearProgress({clear:!0}),this.stdout.write(`${this.truncate(r,{truncate:s})} +`),this.writeProgress()}writeLines(r,{truncate:s}={}){this.clearProgress({delta:r.length});for(let a of r)this.stdout.write(`${this.truncate(a,{truncate:s})} +`);this.writeProgress()}commit(){let r=this.uncommitted;this.uncommitted=new Set;for(let s of r)s.committed=!0,s.action()}clearProgress({delta:r=0,clear:s=!1}){this.progressStyle!==null&&this.progress.size+r>0&&(this.stdout.write(`\x1B[${this.progress.size+r}A`),(r>0||s)&&this.stdout.write("\x1B[0J"))}writeProgress(){if(this.progressStyle===null||(this.progressTimeout!==null&&clearTimeout(this.progressTimeout),this.progressTimeout=null,this.progress.size===0))return;let r=Date.now();r-this.progressTime>Xpe&&(this.progressFrame=(this.progressFrame+1)%zpe.length,this.progressTime=r);let s=zpe[this.progressFrame];for(let a of this.progress.values()){let n="";if(typeof a.lastScaledSize<"u"){let h=this.progressStyle.chars[0].repeat(a.lastScaledSize),E=this.progressStyle.chars[1].repeat(this.progressMaxScaledSize-a.lastScaledSize);n=` ${h}${E}`}let c=this.formatName(null),f=c?`${c}: `:"",p=a.definition.title?` ${a.definition.title}`:"";this.stdout.write(`${Ht(this.configuration,"\u27A4","blueBright")} ${f}${s}${n}${p} +`)}this.progressTimeout=setTimeout(()=>{this.refreshProgress({force:!0})},Xpe)}refreshProgress({delta:r=0,force:s=!1}={}){let a=!1,n=!1;if(s||this.progress.size===0)a=!0;else for(let c of this.progress.values()){let f=typeof c.definition.progress<"u"?Math.trunc(this.progressMaxScaledSize*c.definition.progress):void 0,p=c.lastScaledSize;c.lastScaledSize=f;let h=c.lastTitle;if(c.lastTitle=c.definition.title,f!==p||(n=h!==c.definition.title)){a=!0;break}}a&&(this.clearProgress({delta:r,clear:n}),this.writeProgress())}truncate(r,{truncate:s}={}){return this.progressStyle===null&&(s=!1),typeof s>"u"&&(s=this.configuration.get("preferTruncatedLines")),s&&(r=(0,$pe.default)(r,0,this.stdout.columns-1)),r}formatName(r){return this.includeNames?the(r,{configuration:this.configuration,json:this.json}):""}formatPrefix(r,s){return this.includePrefix?`${Ht(this.configuration,"\u27A4",s)} ${r}${this.formatIndent()}`:""}formatNameWithHyperlink(r){return this.includeNames?Wj(r,{configuration:this.configuration,json:this.json}):""}formatIndent(){return this.level>0||!this.forceSectionAlignment?"\u2502 ".repeat(this.indent):`${ait} `}}});var In={};Vt(In,{PackageManager:()=>nhe,detectPackageManager:()=>ihe,executePackageAccessibleBinary:()=>che,executePackageScript:()=>LT,executePackageShellcode:()=>Yj,executeWorkspaceAccessibleBinary:()=>mit,executeWorkspaceLifecycleScript:()=>ahe,executeWorkspaceScript:()=>ohe,getPackageAccessibleBinaries:()=>MT,getWorkspaceAccessibleBinaries:()=>lhe,hasPackageScript:()=>hit,hasWorkspaceScript:()=>Vj,isNodeScript:()=>Jj,makeScriptEnv:()=>Iv,maybeExecuteWorkspaceLifecycleScript:()=>dit,prepareExternalProject:()=>pit});async function b0(t,e,r,s=[]){if(process.platform==="win32"){let a=`@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"${r}" ${s.map(n=>`"${n.replace('"','""')}"`).join(" ")} %*`;await ce.writeFilePromise(J.format({dir:t,name:e,ext:".cmd"}),a)}await ce.writeFilePromise(J.join(t,e),`#!/bin/sh +exec "${r}" ${s.map(a=>`'${a.replace(/'/g,`'"'"'`)}'`).join(" ")} "$@" +`,{mode:493})}async function ihe(t){let e=await Ut.tryFind(t);if(e?.packageManager){let s=xQ(e.packageManager);if(s?.name){let a=`found ${JSON.stringify({packageManager:e.packageManager})} in manifest`,[n]=s.reference.split(".");switch(s.name){case"yarn":return{packageManagerField:!0,packageManager:Number(n)===1?"Yarn Classic":"Yarn",reason:a};case"npm":return{packageManagerField:!0,packageManager:"npm",reason:a};case"pnpm":return{packageManagerField:!0,packageManager:"pnpm",reason:a}}}}let r;try{r=await ce.readFilePromise(J.join(t,Er.lockfile),"utf8")}catch{}return r!==void 0?r.match(/^__metadata:$/m)?{packageManager:"Yarn",reason:'"__metadata" key found in yarn.lock'}:{packageManager:"Yarn Classic",reason:'"__metadata" key not found in yarn.lock, must be a Yarn classic lockfile'}:ce.existsSync(J.join(t,"package-lock.json"))?{packageManager:"npm",reason:`found npm's "package-lock.json" lockfile`}:ce.existsSync(J.join(t,"pnpm-lock.yaml"))?{packageManager:"pnpm",reason:`found pnpm's "pnpm-lock.yaml" lockfile`}:null}async function Iv({project:t,locator:e,binFolder:r,ignoreCorepack:s,lifecycleScript:a,baseEnv:n=t?.configuration.env??process.env}){let c={};for(let[E,C]of Object.entries(n))typeof C<"u"&&(c[E.toLowerCase()!=="path"?E:"PATH"]=C);let f=fe.fromPortablePath(r);c.BERRY_BIN_FOLDER=fe.fromPortablePath(f);let p=process.env.COREPACK_ROOT&&!s?fe.join(process.env.COREPACK_ROOT,"dist/yarn.js"):process.argv[1];if(await Promise.all([b0(r,"node",process.execPath),...fn!==null?[b0(r,"run",process.execPath,[p,"run"]),b0(r,"yarn",process.execPath,[p]),b0(r,"yarnpkg",process.execPath,[p]),b0(r,"node-gyp",process.execPath,[p,"run","--top-level","node-gyp"])]:[]]),t&&(c.INIT_CWD=fe.fromPortablePath(t.configuration.startingCwd),c.PROJECT_CWD=fe.fromPortablePath(t.cwd)),c.PATH=c.PATH?`${f}${fe.delimiter}${c.PATH}`:`${f}`,c.npm_execpath=`${f}${fe.sep}yarn`,c.npm_node_execpath=`${f}${fe.sep}node`,e){if(!t)throw new Error("Assertion failed: Missing project");let E=t.tryWorkspaceByLocator(e),C=E?E.manifest.version??"":t.storedPackages.get(e.locatorHash).version??"";c.npm_package_name=un(e),c.npm_package_version=C;let S;if(E)S=E.cwd;else{let P=t.storedPackages.get(e.locatorHash);if(!P)throw new Error(`Package for ${Yr(t.configuration,e)} not found in the project`);let I=t.configuration.getLinkers(),R={project:t,report:new Ot({stdout:new P0.PassThrough,configuration:t.configuration})},N=I.find(U=>U.supportsPackage(P,R));if(!N)throw new Error(`The package ${Yr(t.configuration,P)} isn't supported by any of the available linkers`);S=await N.findPackageLocation(P,R)}c.npm_package_json=fe.fromPortablePath(J.join(S,Er.manifest))}let h=fn!==null?`yarn/${fn}`:`yarn/${Pp("@yarnpkg/core").version}-core`;return c.npm_config_user_agent=`${h} npm/? node/${process.version} ${process.platform} ${process.arch}`,a&&(c.npm_lifecycle_event=a),t&&await t.configuration.triggerHook(E=>E.setupScriptEnvironment,t,c,async(E,C,S)=>await b0(r,E,C,S)),c}async function pit(t,e,{configuration:r,report:s,workspace:a=null,locator:n=null}){await Ait(async()=>{await ce.mktempPromise(async c=>{let f=J.join(c,"pack.log"),p=null,{stdout:h,stderr:E}=r.getSubprocessStreams(f,{prefix:fe.fromPortablePath(t),report:s}),C=n&&Gu(n)?rI(n):n,S=C?ll(C):"an external project";h.write(`Packing ${S} from sources +`);let P=await ihe(t),I;P!==null?(h.write(`Using ${P.packageManager} for bootstrap. Reason: ${P.reason} + +`),I=P.packageManager):(h.write(`No package manager configuration detected; defaulting to Yarn + +`),I="Yarn");let R=I==="Yarn"&&!P?.packageManagerField;await ce.mktempPromise(async N=>{let U=await Iv({binFolder:N,ignoreCorepack:R,baseEnv:{...process.env,COREPACK_ENABLE_AUTO_PIN:"0"}}),ee=new Map([["Yarn Classic",async()=>{let ue=a!==null?["workspace",a]:[],le=J.join(t,Er.manifest),me=await ce.readFilePromise(le),pe=await Wu(process.execPath,[process.argv[1],"set","version","classic","--only-if-needed","--yarn-path"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(pe.code!==0)return pe.code;await ce.writeFilePromise(le,me),await ce.appendFilePromise(J.join(t,".npmignore"),`/.yarn +`),h.write(` +`),delete U.NODE_ENV;let Be=await Wu("yarn",["install"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(Be.code!==0)return Be.code;h.write(` +`);let Ce=await Wu("yarn",[...ue,"pack","--filename",fe.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return Ce.code!==0?Ce.code:0}],["Yarn",async()=>{let ue=a!==null?["workspace",a]:[];U.YARN_ENABLE_INLINE_BUILDS="1";let le=J.join(t,Er.lockfile);await ce.existsPromise(le)||await ce.writeFilePromise(le,"");let me=await Wu("yarn",[...ue,"pack","--install-if-needed","--filename",fe.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return me.code!==0?me.code:0}],["npm",async()=>{if(a!==null){let we=new P0.PassThrough,ye=WE(we);we.pipe(h,{end:!1});let Ae=await Wu("npm",["--version"],{cwd:t,env:U,stdin:p,stdout:we,stderr:E,end:0});if(we.end(),Ae.code!==0)return h.end(),E.end(),Ae.code;let se=(await ye).toString().trim();if(!Zf(se,">=7.x")){let Z=Da(null,"npm"),De=On(Z,se),Re=On(Z,">=7.x");throw new Error(`Workspaces aren't supported by ${ni(r,De)}; please upgrade to ${ni(r,Re)} (npm has been detected as the primary package manager for ${Ht(r,t,ht.PATH)})`)}}let ue=a!==null?["--workspace",a]:[];delete U.npm_config_user_agent,delete U.npm_config_production,delete U.NPM_CONFIG_PRODUCTION,delete U.NODE_ENV;let le=await Wu("npm",["install","--legacy-peer-deps"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(le.code!==0)return le.code;let me=new P0.PassThrough,pe=WE(me);me.pipe(h);let Be=await Wu("npm",["pack","--silent",...ue],{cwd:t,env:U,stdin:p,stdout:me,stderr:E});if(Be.code!==0)return Be.code;let Ce=(await pe).toString().trim().replace(/^.*\n/s,""),g=J.resolve(t,fe.toPortablePath(Ce));return await ce.renamePromise(g,e),0}]]).get(I);if(typeof ee>"u")throw new Error("Assertion failed: Unsupported workflow");let ie=await ee();if(!(ie===0||typeof ie>"u"))throw ce.detachTemp(c),new jt(58,`Packing the package failed (exit code ${ie}, logs can be found here: ${Ht(r,f,ht.PATH)})`)})})})}async function hit(t,e,{project:r}){let s=r.tryWorkspaceByLocator(t);if(s!==null)return Vj(s,e);let a=r.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r.configuration,t)} not found in the project`);return await $f.openPromise(async n=>{let c=r.configuration,f=r.configuration.getLinkers(),p={project:r,report:new Ot({stdout:new P0.PassThrough,configuration:c})},h=f.find(P=>P.supportsPackage(a,p));if(!h)throw new Error(`The package ${Yr(r.configuration,a)} isn't supported by any of the available linkers`);let E=await h.findPackageLocation(a,p),C=new Sn(E,{baseFs:n});return(await Ut.find(vt.dot,{baseFs:C})).scripts.has(e)})}async function LT(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await ce.mktempPromise(async p=>{let{manifest:h,env:E,cwd:C}=await she(t,{project:a,binFolder:p,cwd:s,lifecycleScript:e}),S=h.scripts.get(e);if(typeof S>"u")return 1;let P=async()=>await vI(S,r,{cwd:C,env:E,stdin:n,stdout:c,stderr:f});return await(await a.configuration.reduceHook(R=>R.wrapScriptExecution,P,a,t,e,{script:S,args:r,cwd:C,env:E,stdin:n,stdout:c,stderr:f}))()})}async function Yj(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await ce.mktempPromise(async p=>{let{env:h,cwd:E}=await she(t,{project:a,binFolder:p,cwd:s});return await vI(e,r,{cwd:E,env:h,stdin:n,stdout:c,stderr:f})})}async function git(t,{binFolder:e,cwd:r,lifecycleScript:s}){let a=await Iv({project:t.project,locator:t.anchoredLocator,binFolder:e,lifecycleScript:s});return await Kj(e,await lhe(t)),typeof r>"u"&&(r=J.dirname(await ce.realpathPromise(J.join(t.cwd,"package.json")))),{manifest:t.manifest,binFolder:e,env:a,cwd:r}}async function she(t,{project:e,binFolder:r,cwd:s,lifecycleScript:a}){let n=e.tryWorkspaceByLocator(t);if(n!==null)return git(n,{binFolder:r,cwd:s,lifecycleScript:a});let c=e.storedPackages.get(t.locatorHash);if(!c)throw new Error(`Package for ${Yr(e.configuration,t)} not found in the project`);return await $f.openPromise(async f=>{let p=e.configuration,h=e.configuration.getLinkers(),E={project:e,report:new Ot({stdout:new P0.PassThrough,configuration:p})},C=h.find(N=>N.supportsPackage(c,E));if(!C)throw new Error(`The package ${Yr(e.configuration,c)} isn't supported by any of the available linkers`);let S=await Iv({project:e,locator:t,binFolder:r,lifecycleScript:a});await Kj(r,await MT(t,{project:e}));let P=await C.findPackageLocation(c,E),I=new Sn(P,{baseFs:f}),R=await Ut.find(vt.dot,{baseFs:I});return typeof s>"u"&&(s=P),{manifest:R,binFolder:r,env:S,cwd:s}})}async function ohe(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c}){return await LT(t.anchoredLocator,e,r,{cwd:s,project:t.project,stdin:a,stdout:n,stderr:c})}function Vj(t,e){return t.manifest.scripts.has(e)}async function ahe(t,e,{cwd:r,report:s}){let{configuration:a}=t.project,n=null;await ce.mktempPromise(async c=>{let f=J.join(c,`${e}.log`),p=`# This file contains the result of Yarn calling the "${e}" lifecycle script inside a workspace ("${fe.fromPortablePath(t.cwd)}") +`,{stdout:h,stderr:E}=a.getSubprocessStreams(f,{report:s,prefix:Yr(a,t.anchoredLocator),header:p});s.reportInfo(36,`Calling the "${e}" lifecycle script`);let C=await ohe(t,e,[],{cwd:r,stdin:n,stdout:h,stderr:E});if(h.end(),E.end(),C!==0)throw ce.detachTemp(c),new jt(36,`${bB(e)} script failed (exit code ${Ht(a,C,ht.NUMBER)}, logs can be found here: ${Ht(a,f,ht.PATH)}); run ${Ht(a,`yarn ${e}`,ht.CODE)} to investigate`)})}async function dit(t,e,r){Vj(t,e)&&await ahe(t,e,r)}function Jj(t){let e=J.extname(t);if(e.match(/\.[cm]?[jt]sx?$/))return!0;if(e===".exe"||e===".bin")return!1;let r=Buffer.alloc(4),s;try{s=ce.openSync(t,"r")}catch{return!0}try{ce.readSync(s,r,0,r.length,0)}finally{ce.closeSync(s)}let a=r.readUint32BE();return!(a===3405691582||a===3489328638||a===2135247942||(a&4294901760)===1297743872)}async function MT(t,{project:e}){let r=e.configuration,s=new Map,a=e.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r,t)} not found in the project`);let n=new P0.Writable,c=r.getLinkers(),f={project:e,report:new Ot({configuration:r,stdout:n})},p=new Set([t.locatorHash]);for(let E of a.dependencies.values()){let C=e.storedResolutions.get(E.descriptorHash);if(!C)throw new Error(`Assertion failed: The resolution (${ni(r,E)}) should have been registered`);p.add(C)}let h=await Promise.all(Array.from(p,async E=>{let C=e.storedPackages.get(E);if(!C)throw new Error(`Assertion failed: The package (${E}) should have been registered`);if(C.bin.size===0)return Wl.skip;let S=c.find(I=>I.supportsPackage(C,f));if(!S)return Wl.skip;let P=null;try{P=await S.findPackageLocation(C,f)}catch(I){if(I.code==="LOCATOR_NOT_INSTALLED")return Wl.skip;throw I}return{dependency:C,packageLocation:P}}));for(let E of h){if(E===Wl.skip)continue;let{dependency:C,packageLocation:S}=E;for(let[P,I]of C.bin){let R=J.resolve(S,I);s.set(P,[C,fe.fromPortablePath(R),Jj(R)])}}return s}async function lhe(t){return await MT(t.anchoredLocator,{project:t.project})}async function Kj(t,e){await Promise.all(Array.from(e,([r,[,s,a]])=>a?b0(t,r,process.execPath,[s]):b0(t,r,s,[])))}async function che(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f,nodeArgs:p=[],packageAccessibleBinaries:h}){h??=await MT(t,{project:a});let E=h.get(e);if(!E)throw new Error(`Binary not found (${e}) for ${Yr(a.configuration,t)}`);return await ce.mktempPromise(async C=>{let[,S]=E,P=await Iv({project:a,locator:t,binFolder:C});await Kj(P.BERRY_BIN_FOLDER,h);let I=Jj(fe.toPortablePath(S))?Wu(process.execPath,[...p,S,...r],{cwd:s,env:P,stdin:n,stdout:c,stderr:f}):Wu(S,r,{cwd:s,env:P,stdin:n,stdout:c,stderr:f}),R;try{R=await I}finally{await ce.removePromise(P.BERRY_BIN_FOLDER)}return R.code})}async function mit(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f}){return await che(t.anchoredLocator,e,r,{project:t.project,cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f})}var rhe,P0,nhe,fit,Ait,zj=Xe(()=>{Dt();Dt();eA();pv();ql();rhe=ut(Ld()),P0=Ie("stream");oI();Tc();Ev();yv();dT();xc();Pc();Rp();Wo();nhe=(a=>(a.Yarn1="Yarn Classic",a.Yarn2="Yarn",a.Npm="npm",a.Pnpm="pnpm",a))(nhe||{});fit=2,Ait=(0,rhe.default)(fit)});var DI=_((J4t,fhe)=>{"use strict";var uhe=new Map([["C","cwd"],["f","file"],["z","gzip"],["P","preservePaths"],["U","unlink"],["strip-components","strip"],["stripComponents","strip"],["keep-newer","newer"],["keepNewer","newer"],["keep-newer-files","newer"],["keepNewerFiles","newer"],["k","keep"],["keep-existing","keep"],["keepExisting","keep"],["m","noMtime"],["no-mtime","noMtime"],["p","preserveOwner"],["L","follow"],["h","follow"]]);fhe.exports=t=>t?Object.keys(t).map(e=>[uhe.has(e)?uhe.get(e):e,t[e]]).reduce((e,r)=>(e[r[0]]=r[1],e),Object.create(null)):{}});var PI=_((K4t,Ihe)=>{"use strict";var Ahe=typeof process=="object"&&process?process:{stdout:null,stderr:null},yit=Ie("events"),phe=Ie("stream"),hhe=Ie("string_decoder").StringDecoder,_p=Symbol("EOF"),Hp=Symbol("maybeEmitEnd"),x0=Symbol("emittedEnd"),UT=Symbol("emittingEnd"),Cv=Symbol("emittedError"),_T=Symbol("closed"),ghe=Symbol("read"),HT=Symbol("flush"),dhe=Symbol("flushChunk"),ul=Symbol("encoding"),jp=Symbol("decoder"),jT=Symbol("flowing"),wv=Symbol("paused"),bI=Symbol("resume"),Ys=Symbol("bufferLength"),Xj=Symbol("bufferPush"),Zj=Symbol("bufferShift"),Ko=Symbol("objectMode"),zo=Symbol("destroyed"),$j=Symbol("emitData"),mhe=Symbol("emitEnd"),e6=Symbol("emitEnd2"),Gp=Symbol("async"),Bv=t=>Promise.resolve().then(t),yhe=global._MP_NO_ITERATOR_SYMBOLS_!=="1",Eit=yhe&&Symbol.asyncIterator||Symbol("asyncIterator not implemented"),Iit=yhe&&Symbol.iterator||Symbol("iterator not implemented"),Cit=t=>t==="end"||t==="finish"||t==="prefinish",wit=t=>t instanceof ArrayBuffer||typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,Bit=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),GT=class{constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[bI](),r.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},t6=class extends GT{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}};Ihe.exports=class Ehe extends phe{constructor(e){super(),this[jT]=!1,this[wv]=!1,this.pipes=[],this.buffer=[],this[Ko]=e&&e.objectMode||!1,this[Ko]?this[ul]=null:this[ul]=e&&e.encoding||null,this[ul]==="buffer"&&(this[ul]=null),this[Gp]=e&&!!e.async||!1,this[jp]=this[ul]?new hhe(this[ul]):null,this[_p]=!1,this[x0]=!1,this[UT]=!1,this[_T]=!1,this[Cv]=null,this.writable=!0,this.readable=!0,this[Ys]=0,this[zo]=!1}get bufferLength(){return this[Ys]}get encoding(){return this[ul]}set encoding(e){if(this[Ko])throw new Error("cannot set encoding in objectMode");if(this[ul]&&e!==this[ul]&&(this[jp]&&this[jp].lastNeed||this[Ys]))throw new Error("cannot change encoding");this[ul]!==e&&(this[jp]=e?new hhe(e):null,this.buffer.length&&(this.buffer=this.buffer.map(r=>this[jp].write(r)))),this[ul]=e}setEncoding(e){this.encoding=e}get objectMode(){return this[Ko]}set objectMode(e){this[Ko]=this[Ko]||!!e}get async(){return this[Gp]}set async(e){this[Gp]=this[Gp]||!!e}write(e,r,s){if(this[_p])throw new Error("write after end");if(this[zo])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[Gp]?Bv:n=>n();return!this[Ko]&&!Buffer.isBuffer(e)&&(Bit(e)?e=Buffer.from(e.buffer,e.byteOffset,e.byteLength):wit(e)?e=Buffer.from(e):typeof e!="string"&&(this.objectMode=!0)),this[Ko]?(this.flowing&&this[Ys]!==0&&this[HT](!0),this.flowing?this.emit("data",e):this[Xj](e),this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing):e.length?(typeof e=="string"&&!(r===this[ul]&&!this[jp].lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[ul]&&(e=this[jp].write(e)),this.flowing&&this[Ys]!==0&&this[HT](!0),this.flowing?this.emit("data",e):this[Xj](e),this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing):(this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing)}read(e){if(this[zo])return null;if(this[Ys]===0||e===0||e>this[Ys])return this[Hp](),null;this[Ko]&&(e=null),this.buffer.length>1&&!this[Ko]&&(this.encoding?this.buffer=[this.buffer.join("")]:this.buffer=[Buffer.concat(this.buffer,this[Ys])]);let r=this[ghe](e||null,this.buffer[0]);return this[Hp](),r}[ghe](e,r){return e===r.length||e===null?this[Zj]():(this.buffer[0]=r.slice(e),r=r.slice(0,e),this[Ys]-=e),this.emit("data",r),!this.buffer.length&&!this[_p]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=null),typeof r=="function"&&(s=r,r="utf8"),e&&this.write(e,r),s&&this.once("end",s),this[_p]=!0,this.writable=!1,(this.flowing||!this[wv])&&this[Hp](),this}[bI](){this[zo]||(this[wv]=!1,this[jT]=!0,this.emit("resume"),this.buffer.length?this[HT]():this[_p]?this[Hp]():this.emit("drain"))}resume(){return this[bI]()}pause(){this[jT]=!1,this[wv]=!0}get destroyed(){return this[zo]}get flowing(){return this[jT]}get paused(){return this[wv]}[Xj](e){this[Ko]?this[Ys]+=1:this[Ys]+=e.length,this.buffer.push(e)}[Zj](){return this.buffer.length&&(this[Ko]?this[Ys]-=1:this[Ys]-=this.buffer[0].length),this.buffer.shift()}[HT](e){do;while(this[dhe](this[Zj]()));!e&&!this.buffer.length&&!this[_p]&&this.emit("drain")}[dhe](e){return e?(this.emit("data",e),this.flowing):!1}pipe(e,r){if(this[zo])return;let s=this[x0];return r=r||{},e===Ahe.stdout||e===Ahe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this.pipes.push(r.proxyErrors?new t6(this,e,r):new GT(this,e,r)),this[Gp]?Bv(()=>this[bI]()):this[bI]()),e}unpipe(e){let r=this.pipes.find(s=>s.dest===e);r&&(this.pipes.splice(this.pipes.indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);return e==="data"&&!this.pipes.length&&!this.flowing?this[bI]():e==="readable"&&this[Ys]!==0?super.emit("readable"):Cit(e)&&this[x0]?(super.emit(e),this.removeAllListeners(e)):e==="error"&&this[Cv]&&(this[Gp]?Bv(()=>r.call(this,this[Cv])):r.call(this,this[Cv])),s}get emittedEnd(){return this[x0]}[Hp](){!this[UT]&&!this[x0]&&!this[zo]&&this.buffer.length===0&&this[_p]&&(this[UT]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[_T]&&this.emit("close"),this[UT]=!1)}emit(e,r,...s){if(e!=="error"&&e!=="close"&&e!==zo&&this[zo])return;if(e==="data")return r?this[Gp]?Bv(()=>this[$j](r)):this[$j](r):!1;if(e==="end")return this[mhe]();if(e==="close"){if(this[_T]=!0,!this[x0]&&!this[zo])return;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[Cv]=r;let n=super.emit("error",r);return this[Hp](),n}else if(e==="resume"){let n=super.emit("resume");return this[Hp](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,r,...s);return this[Hp](),a}[$j](e){for(let s of this.pipes)s.dest.write(e)===!1&&this.pause();let r=super.emit("data",e);return this[Hp](),r}[mhe](){this[x0]||(this[x0]=!0,this.readable=!1,this[Gp]?Bv(()=>this[e6]()):this[e6]())}[e6](){if(this[jp]){let r=this[jp].end();if(r){for(let s of this.pipes)s.dest.write(r);super.emit("data",r)}}for(let r of this.pipes)r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}collect(){let e=[];this[Ko]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[Ko]||(e.dataLength+=s.length)}),r.then(()=>e)}concat(){return this[Ko]?Promise.reject(new Error("cannot concat in objectMode")):this.collect().then(e=>this[Ko]?Promise.reject(new Error("cannot concat in objectMode")):this[ul]?e.join(""):Buffer.concat(e,e.dataLength))}promise(){return new Promise((e,r)=>{this.on(zo,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Eit](){return{next:()=>{let r=this.read();if(r!==null)return Promise.resolve({done:!1,value:r});if(this[_p])return Promise.resolve({done:!0});let s=null,a=null,n=h=>{this.removeListener("data",c),this.removeListener("end",f),a(h)},c=h=>{this.removeListener("error",n),this.removeListener("end",f),this.pause(),s({value:h,done:!!this[_p]})},f=()=>{this.removeListener("error",n),this.removeListener("data",c),s({done:!0})},p=()=>n(new Error("stream destroyed"));return new Promise((h,E)=>{a=E,s=h,this.once(zo,p),this.once("error",n),this.once("end",f),this.once("data",c)})}}}[Iit](){return{next:()=>{let r=this.read();return{value:r,done:r===null}}}}destroy(e){return this[zo]?(e?this.emit("error",e):this.emit(zo),this):(this[zo]=!0,this.buffer.length=0,this[Ys]=0,typeof this.close=="function"&&!this[_T]&&this.close(),e?this.emit("error",e):this.emit(zo),this)}static isStream(e){return!!e&&(e instanceof Ehe||e instanceof phe||e instanceof yit&&(typeof e.pipe=="function"||typeof e.write=="function"&&typeof e.end=="function"))}}});var whe=_((z4t,Che)=>{var vit=Ie("zlib").constants||{ZLIB_VERNUM:4736};Che.exports=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:1/0,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},vit))});var m6=_(Kl=>{"use strict";var o6=Ie("assert"),k0=Ie("buffer").Buffer,She=Ie("zlib"),fm=Kl.constants=whe(),Sit=PI(),Bhe=k0.concat,Am=Symbol("_superWrite"),kI=class extends Error{constructor(e){super("zlib: "+e.message),this.code=e.code,this.errno=e.errno,this.code||(this.code="ZLIB_ERROR"),this.message="zlib: "+e.message,Error.captureStackTrace(this,this.constructor)}get name(){return"ZlibError"}},Dit=Symbol("opts"),vv=Symbol("flushFlag"),vhe=Symbol("finishFlushFlag"),d6=Symbol("fullFlushFlag"),Ii=Symbol("handle"),qT=Symbol("onError"),xI=Symbol("sawError"),r6=Symbol("level"),n6=Symbol("strategy"),i6=Symbol("ended"),X4t=Symbol("_defaultFullFlush"),WT=class extends Sit{constructor(e,r){if(!e||typeof e!="object")throw new TypeError("invalid options for ZlibBase constructor");super(e),this[xI]=!1,this[i6]=!1,this[Dit]=e,this[vv]=e.flush,this[vhe]=e.finishFlush;try{this[Ii]=new She[r](e)}catch(s){throw new kI(s)}this[qT]=s=>{this[xI]||(this[xI]=!0,this.close(),this.emit("error",s))},this[Ii].on("error",s=>this[qT](new kI(s))),this.once("end",()=>this.close)}close(){this[Ii]&&(this[Ii].close(),this[Ii]=null,this.emit("close"))}reset(){if(!this[xI])return o6(this[Ii],"zlib binding closed"),this[Ii].reset()}flush(e){this.ended||(typeof e!="number"&&(e=this[d6]),this.write(Object.assign(k0.alloc(0),{[vv]:e})))}end(e,r,s){return e&&this.write(e,r),this.flush(this[vhe]),this[i6]=!0,super.end(null,null,s)}get ended(){return this[i6]}write(e,r,s){if(typeof r=="function"&&(s=r,r="utf8"),typeof e=="string"&&(e=k0.from(e,r)),this[xI])return;o6(this[Ii],"zlib binding closed");let a=this[Ii]._handle,n=a.close;a.close=()=>{};let c=this[Ii].close;this[Ii].close=()=>{},k0.concat=h=>h;let f;try{let h=typeof e[vv]=="number"?e[vv]:this[vv];f=this[Ii]._processChunk(e,h),k0.concat=Bhe}catch(h){k0.concat=Bhe,this[qT](new kI(h))}finally{this[Ii]&&(this[Ii]._handle=a,a.close=n,this[Ii].close=c,this[Ii].removeAllListeners("error"))}this[Ii]&&this[Ii].on("error",h=>this[qT](new kI(h)));let p;if(f)if(Array.isArray(f)&&f.length>0){p=this[Am](k0.from(f[0]));for(let h=1;h{this.flush(a),n()};try{this[Ii].params(e,r)}finally{this[Ii].flush=s}this[Ii]&&(this[r6]=e,this[n6]=r)}}}},a6=class extends qp{constructor(e){super(e,"Deflate")}},l6=class extends qp{constructor(e){super(e,"Inflate")}},s6=Symbol("_portable"),c6=class extends qp{constructor(e){super(e,"Gzip"),this[s6]=e&&!!e.portable}[Am](e){return this[s6]?(this[s6]=!1,e[9]=255,super[Am](e)):super[Am](e)}},u6=class extends qp{constructor(e){super(e,"Gunzip")}},f6=class extends qp{constructor(e){super(e,"DeflateRaw")}},A6=class extends qp{constructor(e){super(e,"InflateRaw")}},p6=class extends qp{constructor(e){super(e,"Unzip")}},YT=class extends WT{constructor(e,r){e=e||{},e.flush=e.flush||fm.BROTLI_OPERATION_PROCESS,e.finishFlush=e.finishFlush||fm.BROTLI_OPERATION_FINISH,super(e,r),this[d6]=fm.BROTLI_OPERATION_FLUSH}},h6=class extends YT{constructor(e){super(e,"BrotliCompress")}},g6=class extends YT{constructor(e){super(e,"BrotliDecompress")}};Kl.Deflate=a6;Kl.Inflate=l6;Kl.Gzip=c6;Kl.Gunzip=u6;Kl.DeflateRaw=f6;Kl.InflateRaw=A6;Kl.Unzip=p6;typeof She.BrotliCompress=="function"?(Kl.BrotliCompress=h6,Kl.BrotliDecompress=g6):Kl.BrotliCompress=Kl.BrotliDecompress=class{constructor(){throw new Error("Brotli is not supported in this version of Node.js")}}});var QI=_((e3t,Dhe)=>{var bit=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;Dhe.exports=bit!=="win32"?t=>t:t=>t&&t.replace(/\\/g,"/")});var VT=_((r3t,bhe)=>{"use strict";var Pit=PI(),y6=QI(),E6=Symbol("slurp");bhe.exports=class extends Pit{constructor(e,r,s){switch(super(),this.pause(),this.extended=r,this.globalExtended=s,this.header=e,this.startBlockSize=512*Math.ceil(e.size/512),this.blockRemain=this.startBlockSize,this.remain=e.size,this.type=e.type,this.meta=!1,this.ignore=!1,this.type){case"File":case"OldFile":case"Link":case"SymbolicLink":case"CharacterDevice":case"BlockDevice":case"Directory":case"FIFO":case"ContiguousFile":case"GNUDumpDir":break;case"NextFileHasLongLinkpath":case"NextFileHasLongPath":case"OldGnuLongPath":case"GlobalExtendedHeader":case"ExtendedHeader":case"OldExtendedHeader":this.meta=!0;break;default:this.ignore=!0}this.path=y6(e.path),this.mode=e.mode,this.mode&&(this.mode=this.mode&4095),this.uid=e.uid,this.gid=e.gid,this.uname=e.uname,this.gname=e.gname,this.size=e.size,this.mtime=e.mtime,this.atime=e.atime,this.ctime=e.ctime,this.linkpath=y6(e.linkpath),this.uname=e.uname,this.gname=e.gname,r&&this[E6](r),s&&this[E6](s,!0)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");let s=this.remain,a=this.blockRemain;return this.remain=Math.max(0,s-r),this.blockRemain=Math.max(0,a-r),this.ignore?!0:s>=r?super.write(e):super.write(e.slice(0,s))}[E6](e,r){for(let s in e)e[s]!==null&&e[s]!==void 0&&!(r&&s==="path")&&(this[s]=s==="path"||s==="linkpath"?y6(e[s]):e[s])}}});var I6=_(JT=>{"use strict";JT.name=new Map([["0","File"],["","OldFile"],["1","Link"],["2","SymbolicLink"],["3","CharacterDevice"],["4","BlockDevice"],["5","Directory"],["6","FIFO"],["7","ContiguousFile"],["g","GlobalExtendedHeader"],["x","ExtendedHeader"],["A","SolarisACL"],["D","GNUDumpDir"],["I","Inode"],["K","NextFileHasLongLinkpath"],["L","NextFileHasLongPath"],["M","ContinuationFile"],["N","OldGnuLongPath"],["S","SparseFile"],["V","TapeVolumeHeader"],["X","OldExtendedHeader"]]);JT.code=new Map(Array.from(JT.name).map(t=>[t[1],t[0]]))});var Qhe=_((i3t,khe)=>{"use strict";var xit=(t,e)=>{if(Number.isSafeInteger(t))t<0?Qit(t,e):kit(t,e);else throw Error("cannot encode number outside of javascript safe integer range");return e},kit=(t,e)=>{e[0]=128;for(var r=e.length;r>1;r--)e[r-1]=t&255,t=Math.floor(t/256)},Qit=(t,e)=>{e[0]=255;var r=!1;t=t*-1;for(var s=e.length;s>1;s--){var a=t&255;t=Math.floor(t/256),r?e[s-1]=Phe(a):a===0?e[s-1]=0:(r=!0,e[s-1]=xhe(a))}},Tit=t=>{let e=t[0],r=e===128?Fit(t.slice(1,t.length)):e===255?Rit(t):null;if(r===null)throw Error("invalid base256 encoding");if(!Number.isSafeInteger(r))throw Error("parsed number outside of javascript safe integer range");return r},Rit=t=>{for(var e=t.length,r=0,s=!1,a=e-1;a>-1;a--){var n=t[a],c;s?c=Phe(n):n===0?c=n:(s=!0,c=xhe(n)),c!==0&&(r-=c*Math.pow(256,e-a-1))}return r},Fit=t=>{for(var e=t.length,r=0,s=e-1;s>-1;s--){var a=t[s];a!==0&&(r+=a*Math.pow(256,e-s-1))}return r},Phe=t=>(255^t)&255,xhe=t=>(255^t)+1&255;khe.exports={encode:xit,parse:Tit}});var RI=_((s3t,Rhe)=>{"use strict";var C6=I6(),TI=Ie("path").posix,The=Qhe(),w6=Symbol("slurp"),zl=Symbol("type"),S6=class{constructor(e,r,s,a){this.cksumValid=!1,this.needPax=!1,this.nullBlock=!1,this.block=null,this.path=null,this.mode=null,this.uid=null,this.gid=null,this.size=null,this.mtime=null,this.cksum=null,this[zl]="0",this.linkpath=null,this.uname=null,this.gname=null,this.devmaj=0,this.devmin=0,this.atime=null,this.ctime=null,Buffer.isBuffer(e)?this.decode(e,r||0,s,a):e&&this.set(e)}decode(e,r,s,a){if(r||(r=0),!e||!(e.length>=r+512))throw new Error("need 512 bytes for header");if(this.path=pm(e,r,100),this.mode=Q0(e,r+100,8),this.uid=Q0(e,r+108,8),this.gid=Q0(e,r+116,8),this.size=Q0(e,r+124,12),this.mtime=B6(e,r+136,12),this.cksum=Q0(e,r+148,12),this[w6](s),this[w6](a,!0),this[zl]=pm(e,r+156,1),this[zl]===""&&(this[zl]="0"),this[zl]==="0"&&this.path.substr(-1)==="/"&&(this[zl]="5"),this[zl]==="5"&&(this.size=0),this.linkpath=pm(e,r+157,100),e.slice(r+257,r+265).toString()==="ustar\x0000")if(this.uname=pm(e,r+265,32),this.gname=pm(e,r+297,32),this.devmaj=Q0(e,r+329,8),this.devmin=Q0(e,r+337,8),e[r+475]!==0){let c=pm(e,r+345,155);this.path=c+"/"+this.path}else{let c=pm(e,r+345,130);c&&(this.path=c+"/"+this.path),this.atime=B6(e,r+476,12),this.ctime=B6(e,r+488,12)}let n=8*32;for(let c=r;c=r+512))throw new Error("need 512 bytes for header");let s=this.ctime||this.atime?130:155,a=Nit(this.path||"",s),n=a[0],c=a[1];this.needPax=a[2],this.needPax=hm(e,r,100,n)||this.needPax,this.needPax=T0(e,r+100,8,this.mode)||this.needPax,this.needPax=T0(e,r+108,8,this.uid)||this.needPax,this.needPax=T0(e,r+116,8,this.gid)||this.needPax,this.needPax=T0(e,r+124,12,this.size)||this.needPax,this.needPax=v6(e,r+136,12,this.mtime)||this.needPax,e[r+156]=this[zl].charCodeAt(0),this.needPax=hm(e,r+157,100,this.linkpath)||this.needPax,e.write("ustar\x0000",r+257,8),this.needPax=hm(e,r+265,32,this.uname)||this.needPax,this.needPax=hm(e,r+297,32,this.gname)||this.needPax,this.needPax=T0(e,r+329,8,this.devmaj)||this.needPax,this.needPax=T0(e,r+337,8,this.devmin)||this.needPax,this.needPax=hm(e,r+345,s,c)||this.needPax,e[r+475]!==0?this.needPax=hm(e,r+345,155,c)||this.needPax:(this.needPax=hm(e,r+345,130,c)||this.needPax,this.needPax=v6(e,r+476,12,this.atime)||this.needPax,this.needPax=v6(e,r+488,12,this.ctime)||this.needPax);let f=8*32;for(let p=r;p{let s=t,a="",n,c=TI.parse(t).root||".";if(Buffer.byteLength(s)<100)n=[s,a,!1];else{a=TI.dirname(s),s=TI.basename(s);do Buffer.byteLength(s)<=100&&Buffer.byteLength(a)<=e?n=[s,a,!1]:Buffer.byteLength(s)>100&&Buffer.byteLength(a)<=e?n=[s.substr(0,99),a,!0]:(s=TI.join(TI.basename(a),s),a=TI.dirname(a));while(a!==c&&!n);n||(n=[t.substr(0,99),"",!0])}return n},pm=(t,e,r)=>t.slice(e,e+r).toString("utf8").replace(/\0.*/,""),B6=(t,e,r)=>Oit(Q0(t,e,r)),Oit=t=>t===null?null:new Date(t*1e3),Q0=(t,e,r)=>t[e]&128?The.parse(t.slice(e,e+r)):Mit(t,e,r),Lit=t=>isNaN(t)?null:t,Mit=(t,e,r)=>Lit(parseInt(t.slice(e,e+r).toString("utf8").replace(/\0.*$/,"").trim(),8)),Uit={12:8589934591,8:2097151},T0=(t,e,r,s)=>s===null?!1:s>Uit[r]||s<0?(The.encode(s,t.slice(e,e+r)),!0):(_it(t,e,r,s),!1),_it=(t,e,r,s)=>t.write(Hit(s,r),e,r,"ascii"),Hit=(t,e)=>jit(Math.floor(t).toString(8),e),jit=(t,e)=>(t.length===e-1?t:new Array(e-t.length-1).join("0")+t+" ")+"\0",v6=(t,e,r,s)=>s===null?!1:T0(t,e,r,s.getTime()/1e3),Git=new Array(156).join("\0"),hm=(t,e,r,s)=>s===null?!1:(t.write(s+Git,e,r,"utf8"),s.length!==Buffer.byteLength(s)||s.length>r);Rhe.exports=S6});var KT=_((o3t,Fhe)=>{"use strict";var qit=RI(),Wit=Ie("path"),Sv=class{constructor(e,r){this.atime=e.atime||null,this.charset=e.charset||null,this.comment=e.comment||null,this.ctime=e.ctime||null,this.gid=e.gid||null,this.gname=e.gname||null,this.linkpath=e.linkpath||null,this.mtime=e.mtime||null,this.path=e.path||null,this.size=e.size||null,this.uid=e.uid||null,this.uname=e.uname||null,this.dev=e.dev||null,this.ino=e.ino||null,this.nlink=e.nlink||null,this.global=r||!1}encode(){let e=this.encodeBody();if(e==="")return null;let r=Buffer.byteLength(e),s=512*Math.ceil(1+r/512),a=Buffer.allocUnsafe(s);for(let n=0;n<512;n++)a[n]=0;new qit({path:("PaxHeader/"+Wit.basename(this.path)).slice(0,99),mode:this.mode||420,uid:this.uid||null,gid:this.gid||null,size:r,mtime:this.mtime||null,type:this.global?"GlobalExtendedHeader":"ExtendedHeader",linkpath:"",uname:this.uname||"",gname:this.gname||"",devmaj:0,devmin:0,atime:this.atime||null,ctime:this.ctime||null}).encode(a),a.write(e,512,r,"utf8");for(let n=r+512;n=Math.pow(10,n)&&(n+=1),n+a+s}};Sv.parse=(t,e,r)=>new Sv(Yit(Vit(t),e),r);var Yit=(t,e)=>e?Object.keys(t).reduce((r,s)=>(r[s]=t[s],r),e):t,Vit=t=>t.replace(/\n$/,"").split(` +`).reduce(Jit,Object.create(null)),Jit=(t,e)=>{let r=parseInt(e,10);if(r!==Buffer.byteLength(e)+1)return t;e=e.substr((r+" ").length);let s=e.split("="),a=s.shift().replace(/^SCHILY\.(dev|ino|nlink)/,"$1");if(!a)return t;let n=s.join("=");return t[a]=/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(a)?new Date(n*1e3):/^[0-9]+$/.test(n)?+n:n,t};Fhe.exports=Sv});var FI=_((a3t,Nhe)=>{Nhe.exports=t=>{let e=t.length-1,r=-1;for(;e>-1&&t.charAt(e)==="/";)r=e,e--;return r===-1?t:t.slice(0,r)}});var zT=_((l3t,Ohe)=>{"use strict";Ohe.exports=t=>class extends t{warn(e,r,s={}){this.file&&(s.file=this.file),this.cwd&&(s.cwd=this.cwd),s.code=r instanceof Error&&r.code||e,s.tarCode=e,!this.strict&&s.recoverable!==!1?(r instanceof Error&&(s=Object.assign(r,s),r=r.message),this.emit("warn",s.tarCode,r,s)):r instanceof Error?this.emit("error",Object.assign(r,s)):this.emit("error",Object.assign(new Error(`${e}: ${r}`),s))}}});var b6=_((u3t,Lhe)=>{"use strict";var XT=["|","<",">","?",":"],D6=XT.map(t=>String.fromCharCode(61440+t.charCodeAt(0))),Kit=new Map(XT.map((t,e)=>[t,D6[e]])),zit=new Map(D6.map((t,e)=>[t,XT[e]]));Lhe.exports={encode:t=>XT.reduce((e,r)=>e.split(r).join(Kit.get(r)),t),decode:t=>D6.reduce((e,r)=>e.split(r).join(zit.get(r)),t)}});var P6=_((f3t,Uhe)=>{var{isAbsolute:Xit,parse:Mhe}=Ie("path").win32;Uhe.exports=t=>{let e="",r=Mhe(t);for(;Xit(t)||r.root;){let s=t.charAt(0)==="/"&&t.slice(0,4)!=="//?/"?"/":r.root;t=t.substr(s.length),e+=s,r=Mhe(t)}return[e,t]}});var Hhe=_((A3t,_he)=>{"use strict";_he.exports=(t,e,r)=>(t&=4095,r&&(t=(t|384)&-19),e&&(t&256&&(t|=64),t&32&&(t|=8),t&4&&(t|=1)),t)});var M6=_((g3t,t0e)=>{"use strict";var Jhe=PI(),Khe=KT(),zhe=RI(),nA=Ie("fs"),jhe=Ie("path"),rA=QI(),Zit=FI(),Xhe=(t,e)=>e?(t=rA(t).replace(/^\.(\/|$)/,""),Zit(e)+"/"+t):rA(t),$it=16*1024*1024,Ghe=Symbol("process"),qhe=Symbol("file"),Whe=Symbol("directory"),k6=Symbol("symlink"),Yhe=Symbol("hardlink"),Dv=Symbol("header"),ZT=Symbol("read"),Q6=Symbol("lstat"),$T=Symbol("onlstat"),T6=Symbol("onread"),R6=Symbol("onreadlink"),F6=Symbol("openfile"),N6=Symbol("onopenfile"),R0=Symbol("close"),eR=Symbol("mode"),O6=Symbol("awaitDrain"),x6=Symbol("ondrain"),iA=Symbol("prefix"),Vhe=Symbol("hadError"),Zhe=zT(),est=b6(),$he=P6(),e0e=Hhe(),tR=Zhe(class extends Jhe{constructor(e,r){if(r=r||{},super(r),typeof e!="string")throw new TypeError("path is required");this.path=rA(e),this.portable=!!r.portable,this.myuid=process.getuid&&process.getuid()||0,this.myuser=process.env.USER||"",this.maxReadSize=r.maxReadSize||$it,this.linkCache=r.linkCache||new Map,this.statCache=r.statCache||new Map,this.preservePaths=!!r.preservePaths,this.cwd=rA(r.cwd||process.cwd()),this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.mtime=r.mtime||null,this.prefix=r.prefix?rA(r.prefix):null,this.fd=null,this.blockLen=null,this.blockRemain=null,this.buf=null,this.offset=null,this.length=null,this.pos=null,this.remain=null,typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=$he(this.path);a&&(this.path=n,s=a)}this.win32=!!r.win32||process.platform==="win32",this.win32&&(this.path=est.decode(this.path.replace(/\\/g,"/")),e=e.replace(/\\/g,"/")),this.absolute=rA(r.absolute||jhe.resolve(this.cwd,e)),this.path===""&&(this.path="./"),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.statCache.has(this.absolute)?this[$T](this.statCache.get(this.absolute)):this[Q6]()}emit(e,...r){return e==="error"&&(this[Vhe]=!0),super.emit(e,...r)}[Q6](){nA.lstat(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[$T](r)})}[$T](e){this.statCache.set(this.absolute,e),this.stat=e,e.isFile()||(e.size=0),this.type=rst(e),this.emit("stat",e),this[Ghe]()}[Ghe](){switch(this.type){case"File":return this[qhe]();case"Directory":return this[Whe]();case"SymbolicLink":return this[k6]();default:return this.end()}}[eR](e){return e0e(e,this.type==="Directory",this.portable)}[iA](e){return Xhe(e,this.prefix)}[Dv](){this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.header=new zhe({path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,mode:this[eR](this.stat.mode),uid:this.portable?null:this.stat.uid,gid:this.portable?null:this.stat.gid,size:this.stat.size,mtime:this.noMtime?null:this.mtime||this.stat.mtime,type:this.type,uname:this.portable?null:this.stat.uid===this.myuid?this.myuser:"",atime:this.portable?null:this.stat.atime,ctime:this.portable?null:this.stat.ctime}),this.header.encode()&&!this.noPax&&super.write(new Khe({atime:this.portable?null:this.header.atime,ctime:this.portable?null:this.header.ctime,gid:this.portable?null:this.header.gid,mtime:this.noMtime?null:this.mtime||this.header.mtime,path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?null:this.header.uid,uname:this.portable?null:this.header.uname,dev:this.portable?null:this.stat.dev,ino:this.portable?null:this.stat.ino,nlink:this.portable?null:this.stat.nlink}).encode()),super.write(this.header.block)}[Whe](){this.path.substr(-1)!=="/"&&(this.path+="/"),this.stat.size=0,this[Dv](),this.end()}[k6](){nA.readlink(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[R6](r)})}[R6](e){this.linkpath=rA(e),this[Dv](),this.end()}[Yhe](e){this.type="Link",this.linkpath=rA(jhe.relative(this.cwd,e)),this.stat.size=0,this[Dv](),this.end()}[qhe](){if(this.stat.nlink>1){let e=this.stat.dev+":"+this.stat.ino;if(this.linkCache.has(e)){let r=this.linkCache.get(e);if(r.indexOf(this.cwd)===0)return this[Yhe](r)}this.linkCache.set(e,this.absolute)}if(this[Dv](),this.stat.size===0)return this.end();this[F6]()}[F6](){nA.open(this.absolute,"r",(e,r)=>{if(e)return this.emit("error",e);this[N6](r)})}[N6](e){if(this.fd=e,this[Vhe])return this[R0]();this.blockLen=512*Math.ceil(this.stat.size/512),this.blockRemain=this.blockLen;let r=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(r),this.offset=0,this.pos=0,this.remain=this.stat.size,this.length=this.buf.length,this[ZT]()}[ZT](){let{fd:e,buf:r,offset:s,length:a,pos:n}=this;nA.read(e,r,s,a,n,(c,f)=>{if(c)return this[R0](()=>this.emit("error",c));this[T6](f)})}[R0](e){nA.close(this.fd,e)}[T6](e){if(e<=0&&this.remain>0){let a=new Error("encountered unexpected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[R0](()=>this.emit("error",a))}if(e>this.remain){let a=new Error("did not encounter expected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[R0](()=>this.emit("error",a))}if(e===this.remain)for(let a=e;athis[x6]())}[O6](e){this.once("drain",e)}write(e){if(this.blockRemaine?this.emit("error",e):this.end());this.offset>=this.length&&(this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length)),this.offset=0),this.length=this.buf.length-this.offset,this[ZT]()}}),L6=class extends tR{[Q6](){this[$T](nA.lstatSync(this.absolute))}[k6](){this[R6](nA.readlinkSync(this.absolute))}[F6](){this[N6](nA.openSync(this.absolute,"r"))}[ZT](){let e=!0;try{let{fd:r,buf:s,offset:a,length:n,pos:c}=this,f=nA.readSync(r,s,a,n,c);this[T6](f),e=!1}finally{if(e)try{this[R0](()=>{})}catch{}}}[O6](e){e()}[R0](e){nA.closeSync(this.fd),e()}},tst=Zhe(class extends Jhe{constructor(e,r){r=r||{},super(r),this.preservePaths=!!r.preservePaths,this.portable=!!r.portable,this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.readEntry=e,this.type=e.type,this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.prefix=r.prefix||null,this.path=rA(e.path),this.mode=this[eR](e.mode),this.uid=this.portable?null:e.uid,this.gid=this.portable?null:e.gid,this.uname=this.portable?null:e.uname,this.gname=this.portable?null:e.gname,this.size=e.size,this.mtime=this.noMtime?null:r.mtime||e.mtime,this.atime=this.portable?null:e.atime,this.ctime=this.portable?null:e.ctime,this.linkpath=rA(e.linkpath),typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=$he(this.path);a&&(this.path=n,s=a)}this.remain=e.size,this.blockRemain=e.startBlockSize,this.header=new zhe({path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?null:this.uid,gid:this.portable?null:this.gid,size:this.size,mtime:this.noMtime?null:this.mtime,type:this.type,uname:this.portable?null:this.uname,atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime}),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.header.encode()&&!this.noPax&&super.write(new Khe({atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime,gid:this.portable?null:this.gid,mtime:this.noMtime?null:this.mtime,path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,size:this.size,uid:this.portable?null:this.uid,uname:this.portable?null:this.uname,dev:this.portable?null:this.readEntry.dev,ino:this.portable?null:this.readEntry.ino,nlink:this.portable?null:this.readEntry.nlink}).encode()),super.write(this.header.block),e.pipe(this)}[iA](e){return Xhe(e,this.prefix)}[eR](e){return e0e(e,this.type==="Directory",this.portable)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");return this.blockRemain-=r,super.write(e)}end(){return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),super.end()}});tR.Sync=L6;tR.Tar=tst;var rst=t=>t.isFile()?"File":t.isDirectory()?"Directory":t.isSymbolicLink()?"SymbolicLink":"Unsupported";t0e.exports=tR});var uR=_((m3t,l0e)=>{"use strict";var lR=class{constructor(e,r){this.path=e||"./",this.absolute=r,this.entry=null,this.stat=null,this.readdir=null,this.pending=!1,this.ignore=!1,this.piped=!1}},nst=PI(),ist=m6(),sst=VT(),V6=M6(),ost=V6.Sync,ast=V6.Tar,lst=$x(),r0e=Buffer.alloc(1024),iR=Symbol("onStat"),rR=Symbol("ended"),sA=Symbol("queue"),NI=Symbol("current"),gm=Symbol("process"),nR=Symbol("processing"),n0e=Symbol("processJob"),oA=Symbol("jobs"),U6=Symbol("jobDone"),sR=Symbol("addFSEntry"),i0e=Symbol("addTarEntry"),G6=Symbol("stat"),q6=Symbol("readdir"),oR=Symbol("onreaddir"),aR=Symbol("pipe"),s0e=Symbol("entry"),_6=Symbol("entryOpt"),W6=Symbol("writeEntryClass"),a0e=Symbol("write"),H6=Symbol("ondrain"),cR=Ie("fs"),o0e=Ie("path"),cst=zT(),j6=QI(),J6=cst(class extends nst{constructor(e){super(e),e=e||Object.create(null),this.opt=e,this.file=e.file||"",this.cwd=e.cwd||process.cwd(),this.maxReadSize=e.maxReadSize,this.preservePaths=!!e.preservePaths,this.strict=!!e.strict,this.noPax=!!e.noPax,this.prefix=j6(e.prefix||""),this.linkCache=e.linkCache||new Map,this.statCache=e.statCache||new Map,this.readdirCache=e.readdirCache||new Map,this[W6]=V6,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),this.portable=!!e.portable,this.zip=null,e.gzip?(typeof e.gzip!="object"&&(e.gzip={}),this.portable&&(e.gzip.portable=!0),this.zip=new ist.Gzip(e.gzip),this.zip.on("data",r=>super.write(r)),this.zip.on("end",r=>super.end()),this.zip.on("drain",r=>this[H6]()),this.on("resume",r=>this.zip.resume())):this.on("drain",this[H6]),this.noDirRecurse=!!e.noDirRecurse,this.follow=!!e.follow,this.noMtime=!!e.noMtime,this.mtime=e.mtime||null,this.filter=typeof e.filter=="function"?e.filter:r=>!0,this[sA]=new lst,this[oA]=0,this.jobs=+e.jobs||4,this[nR]=!1,this[rR]=!1}[a0e](e){return super.write(e)}add(e){return this.write(e),this}end(e){return e&&this.write(e),this[rR]=!0,this[gm](),this}write(e){if(this[rR])throw new Error("write after end");return e instanceof sst?this[i0e](e):this[sR](e),this.flowing}[i0e](e){let r=j6(o0e.resolve(this.cwd,e.path));if(!this.filter(e.path,e))e.resume();else{let s=new lR(e.path,r,!1);s.entry=new ast(e,this[_6](s)),s.entry.on("end",a=>this[U6](s)),this[oA]+=1,this[sA].push(s)}this[gm]()}[sR](e){let r=j6(o0e.resolve(this.cwd,e));this[sA].push(new lR(e,r)),this[gm]()}[G6](e){e.pending=!0,this[oA]+=1;let r=this.follow?"stat":"lstat";cR[r](e.absolute,(s,a)=>{e.pending=!1,this[oA]-=1,s?this.emit("error",s):this[iR](e,a)})}[iR](e,r){this.statCache.set(e.absolute,r),e.stat=r,this.filter(e.path,r)||(e.ignore=!0),this[gm]()}[q6](e){e.pending=!0,this[oA]+=1,cR.readdir(e.absolute,(r,s)=>{if(e.pending=!1,this[oA]-=1,r)return this.emit("error",r);this[oR](e,s)})}[oR](e,r){this.readdirCache.set(e.absolute,r),e.readdir=r,this[gm]()}[gm](){if(!this[nR]){this[nR]=!0;for(let e=this[sA].head;e!==null&&this[oA]this.warn(r,s,a),noPax:this.noPax,cwd:this.cwd,absolute:e.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix}}[s0e](e){this[oA]+=1;try{return new this[W6](e.path,this[_6](e)).on("end",()=>this[U6](e)).on("error",r=>this.emit("error",r))}catch(r){this.emit("error",r)}}[H6](){this[NI]&&this[NI].entry&&this[NI].entry.resume()}[aR](e){e.piped=!0,e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[sR](c+a)});let r=e.entry,s=this.zip;s?r.on("data",a=>{s.write(a)||r.pause()}):r.on("data",a=>{super.write(a)||r.pause()})}pause(){return this.zip&&this.zip.pause(),super.pause()}}),Y6=class extends J6{constructor(e){super(e),this[W6]=ost}pause(){}resume(){}[G6](e){let r=this.follow?"statSync":"lstatSync";this[iR](e,cR[r](e.absolute))}[q6](e,r){this[oR](e,cR.readdirSync(e.absolute))}[aR](e){let r=e.entry,s=this.zip;e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[sR](c+a)}),s?r.on("data",a=>{s.write(a)}):r.on("data",a=>{super[a0e](a)})}};J6.Sync=Y6;l0e.exports=J6});var GI=_(Pv=>{"use strict";var ust=PI(),fst=Ie("events").EventEmitter,fl=Ie("fs"),X6=fl.writev;if(!X6){let t=process.binding("fs"),e=t.FSReqWrap||t.FSReqCallback;X6=(r,s,a,n)=>{let c=(p,h)=>n(p,h,s),f=new e;f.oncomplete=c,t.writeBuffers(r,s,a,f)}}var HI=Symbol("_autoClose"),Yu=Symbol("_close"),bv=Symbol("_ended"),ii=Symbol("_fd"),c0e=Symbol("_finished"),N0=Symbol("_flags"),K6=Symbol("_flush"),Z6=Symbol("_handleChunk"),$6=Symbol("_makeBuf"),gR=Symbol("_mode"),fR=Symbol("_needDrain"),UI=Symbol("_onerror"),jI=Symbol("_onopen"),z6=Symbol("_onread"),LI=Symbol("_onwrite"),O0=Symbol("_open"),Wp=Symbol("_path"),dm=Symbol("_pos"),aA=Symbol("_queue"),MI=Symbol("_read"),u0e=Symbol("_readSize"),F0=Symbol("_reading"),AR=Symbol("_remain"),f0e=Symbol("_size"),pR=Symbol("_write"),OI=Symbol("_writing"),hR=Symbol("_defaultFlag"),_I=Symbol("_errored"),dR=class extends ust{constructor(e,r){if(r=r||{},super(r),this.readable=!0,this.writable=!1,typeof e!="string")throw new TypeError("path must be a string");this[_I]=!1,this[ii]=typeof r.fd=="number"?r.fd:null,this[Wp]=e,this[u0e]=r.readSize||16*1024*1024,this[F0]=!1,this[f0e]=typeof r.size=="number"?r.size:1/0,this[AR]=this[f0e],this[HI]=typeof r.autoClose=="boolean"?r.autoClose:!0,typeof this[ii]=="number"?this[MI]():this[O0]()}get fd(){return this[ii]}get path(){return this[Wp]}write(){throw new TypeError("this is a readable stream")}end(){throw new TypeError("this is a readable stream")}[O0](){fl.open(this[Wp],"r",(e,r)=>this[jI](e,r))}[jI](e,r){e?this[UI](e):(this[ii]=r,this.emit("open",r),this[MI]())}[$6](){return Buffer.allocUnsafe(Math.min(this[u0e],this[AR]))}[MI](){if(!this[F0]){this[F0]=!0;let e=this[$6]();if(e.length===0)return process.nextTick(()=>this[z6](null,0,e));fl.read(this[ii],e,0,e.length,null,(r,s,a)=>this[z6](r,s,a))}}[z6](e,r,s){this[F0]=!1,e?this[UI](e):this[Z6](r,s)&&this[MI]()}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.close(e,r=>r?this.emit("error",r):this.emit("close"))}}[UI](e){this[F0]=!0,this[Yu](),this.emit("error",e)}[Z6](e,r){let s=!1;return this[AR]-=e,e>0&&(s=super.write(ethis[jI](e,r))}[jI](e,r){this[hR]&&this[N0]==="r+"&&e&&e.code==="ENOENT"?(this[N0]="w",this[O0]()):e?this[UI](e):(this[ii]=r,this.emit("open",r),this[K6]())}end(e,r){return e&&this.write(e,r),this[bv]=!0,!this[OI]&&!this[aA].length&&typeof this[ii]=="number"&&this[LI](null,0),this}write(e,r){return typeof e=="string"&&(e=Buffer.from(e,r)),this[bv]?(this.emit("error",new Error("write() after end()")),!1):this[ii]===null||this[OI]||this[aA].length?(this[aA].push(e),this[fR]=!0,!1):(this[OI]=!0,this[pR](e),!0)}[pR](e){fl.write(this[ii],e,0,e.length,this[dm],(r,s)=>this[LI](r,s))}[LI](e,r){e?this[UI](e):(this[dm]!==null&&(this[dm]+=r),this[aA].length?this[K6]():(this[OI]=!1,this[bv]&&!this[c0e]?(this[c0e]=!0,this[Yu](),this.emit("finish")):this[fR]&&(this[fR]=!1,this.emit("drain"))))}[K6](){if(this[aA].length===0)this[bv]&&this[LI](null,0);else if(this[aA].length===1)this[pR](this[aA].pop());else{let e=this[aA];this[aA]=[],X6(this[ii],e,this[dm],(r,s)=>this[LI](r,s))}}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.close(e,r=>r?this.emit("error",r):this.emit("close"))}}},tG=class extends mR{[O0](){let e;if(this[hR]&&this[N0]==="r+")try{e=fl.openSync(this[Wp],this[N0],this[gR])}catch(r){if(r.code==="ENOENT")return this[N0]="w",this[O0]();throw r}else e=fl.openSync(this[Wp],this[N0],this[gR]);this[jI](null,e)}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.closeSync(e),this.emit("close")}}[pR](e){let r=!0;try{this[LI](null,fl.writeSync(this[ii],e,0,e.length,this[dm])),r=!1}finally{if(r)try{this[Yu]()}catch{}}}};Pv.ReadStream=dR;Pv.ReadStreamSync=eG;Pv.WriteStream=mR;Pv.WriteStreamSync=tG});var vR=_((I3t,y0e)=>{"use strict";var Ast=zT(),pst=RI(),hst=Ie("events"),gst=$x(),dst=1024*1024,mst=VT(),A0e=KT(),yst=m6(),rG=Buffer.from([31,139]),Lc=Symbol("state"),mm=Symbol("writeEntry"),Yp=Symbol("readEntry"),nG=Symbol("nextEntry"),p0e=Symbol("processEntry"),Mc=Symbol("extendedHeader"),xv=Symbol("globalExtendedHeader"),L0=Symbol("meta"),h0e=Symbol("emitMeta"),Di=Symbol("buffer"),Vp=Symbol("queue"),ym=Symbol("ended"),g0e=Symbol("emittedEnd"),Em=Symbol("emit"),Al=Symbol("unzip"),yR=Symbol("consumeChunk"),ER=Symbol("consumeChunkSub"),iG=Symbol("consumeBody"),d0e=Symbol("consumeMeta"),m0e=Symbol("consumeHeader"),IR=Symbol("consuming"),sG=Symbol("bufferConcat"),oG=Symbol("maybeEnd"),kv=Symbol("writing"),M0=Symbol("aborted"),CR=Symbol("onDone"),Im=Symbol("sawValidEntry"),wR=Symbol("sawNullBlock"),BR=Symbol("sawEOF"),Est=t=>!0;y0e.exports=Ast(class extends hst{constructor(e){e=e||{},super(e),this.file=e.file||"",this[Im]=null,this.on(CR,r=>{(this[Lc]==="begin"||this[Im]===!1)&&this.warn("TAR_BAD_ARCHIVE","Unrecognized archive format")}),e.ondone?this.on(CR,e.ondone):this.on(CR,r=>{this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close")}),this.strict=!!e.strict,this.maxMetaEntrySize=e.maxMetaEntrySize||dst,this.filter=typeof e.filter=="function"?e.filter:Est,this.writable=!0,this.readable=!1,this[Vp]=new gst,this[Di]=null,this[Yp]=null,this[mm]=null,this[Lc]="begin",this[L0]="",this[Mc]=null,this[xv]=null,this[ym]=!1,this[Al]=null,this[M0]=!1,this[wR]=!1,this[BR]=!1,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),typeof e.onentry=="function"&&this.on("entry",e.onentry)}[m0e](e,r){this[Im]===null&&(this[Im]=!1);let s;try{s=new pst(e,r,this[Mc],this[xv])}catch(a){return this.warn("TAR_ENTRY_INVALID",a)}if(s.nullBlock)this[wR]?(this[BR]=!0,this[Lc]==="begin"&&(this[Lc]="header"),this[Em]("eof")):(this[wR]=!0,this[Em]("nullBlock"));else if(this[wR]=!1,!s.cksumValid)this.warn("TAR_ENTRY_INVALID","checksum failure",{header:s});else if(!s.path)this.warn("TAR_ENTRY_INVALID","path is required",{header:s});else{let a=s.type;if(/^(Symbolic)?Link$/.test(a)&&!s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath required",{header:s});else if(!/^(Symbolic)?Link$/.test(a)&&s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath forbidden",{header:s});else{let n=this[mm]=new mst(s,this[Mc],this[xv]);if(!this[Im])if(n.remain){let c=()=>{n.invalid||(this[Im]=!0)};n.on("end",c)}else this[Im]=!0;n.meta?n.size>this.maxMetaEntrySize?(n.ignore=!0,this[Em]("ignoredEntry",n),this[Lc]="ignore",n.resume()):n.size>0&&(this[L0]="",n.on("data",c=>this[L0]+=c),this[Lc]="meta"):(this[Mc]=null,n.ignore=n.ignore||!this.filter(n.path,n),n.ignore?(this[Em]("ignoredEntry",n),this[Lc]=n.remain?"ignore":"header",n.resume()):(n.remain?this[Lc]="body":(this[Lc]="header",n.end()),this[Yp]?this[Vp].push(n):(this[Vp].push(n),this[nG]())))}}}[p0e](e){let r=!0;return e?Array.isArray(e)?this.emit.apply(this,e):(this[Yp]=e,this.emit("entry",e),e.emittedEnd||(e.on("end",s=>this[nG]()),r=!1)):(this[Yp]=null,r=!1),r}[nG](){do;while(this[p0e](this[Vp].shift()));if(!this[Vp].length){let e=this[Yp];!e||e.flowing||e.size===e.remain?this[kv]||this.emit("drain"):e.once("drain",s=>this.emit("drain"))}}[iG](e,r){let s=this[mm],a=s.blockRemain,n=a>=e.length&&r===0?e:e.slice(r,r+a);return s.write(n),s.blockRemain||(this[Lc]="header",this[mm]=null,s.end()),n.length}[d0e](e,r){let s=this[mm],a=this[iG](e,r);return this[mm]||this[h0e](s),a}[Em](e,r,s){!this[Vp].length&&!this[Yp]?this.emit(e,r,s):this[Vp].push([e,r,s])}[h0e](e){switch(this[Em]("meta",this[L0]),e.type){case"ExtendedHeader":case"OldExtendedHeader":this[Mc]=A0e.parse(this[L0],this[Mc],!1);break;case"GlobalExtendedHeader":this[xv]=A0e.parse(this[L0],this[xv],!0);break;case"NextFileHasLongPath":case"OldGnuLongPath":this[Mc]=this[Mc]||Object.create(null),this[Mc].path=this[L0].replace(/\0.*/,"");break;case"NextFileHasLongLinkpath":this[Mc]=this[Mc]||Object.create(null),this[Mc].linkpath=this[L0].replace(/\0.*/,"");break;default:throw new Error("unknown meta: "+e.type)}}abort(e){this[M0]=!0,this.emit("abort",e),this.warn("TAR_ABORT",e,{recoverable:!1})}write(e){if(this[M0])return;if(this[Al]===null&&e){if(this[Di]&&(e=Buffer.concat([this[Di],e]),this[Di]=null),e.lengththis[yR](n)),this[Al].on("error",n=>this.abort(n)),this[Al].on("end",n=>{this[ym]=!0,this[yR]()}),this[kv]=!0;let a=this[Al][s?"end":"write"](e);return this[kv]=!1,a}}this[kv]=!0,this[Al]?this[Al].write(e):this[yR](e),this[kv]=!1;let r=this[Vp].length?!1:this[Yp]?this[Yp].flowing:!0;return!r&&!this[Vp].length&&this[Yp].once("drain",s=>this.emit("drain")),r}[sG](e){e&&!this[M0]&&(this[Di]=this[Di]?Buffer.concat([this[Di],e]):e)}[oG](){if(this[ym]&&!this[g0e]&&!this[M0]&&!this[IR]){this[g0e]=!0;let e=this[mm];if(e&&e.blockRemain){let r=this[Di]?this[Di].length:0;this.warn("TAR_BAD_ARCHIVE",`Truncated input (needed ${e.blockRemain} more bytes, only ${r} available)`,{entry:e}),this[Di]&&e.write(this[Di]),e.end()}this[Em](CR)}}[yR](e){if(this[IR])this[sG](e);else if(!e&&!this[Di])this[oG]();else{if(this[IR]=!0,this[Di]){this[sG](e);let r=this[Di];this[Di]=null,this[ER](r)}else this[ER](e);for(;this[Di]&&this[Di].length>=512&&!this[M0]&&!this[BR];){let r=this[Di];this[Di]=null,this[ER](r)}this[IR]=!1}(!this[Di]||this[ym])&&this[oG]()}[ER](e){let r=0,s=e.length;for(;r+512<=s&&!this[M0]&&!this[BR];)switch(this[Lc]){case"begin":case"header":this[m0e](e,r),r+=512;break;case"ignore":case"body":r+=this[iG](e,r);break;case"meta":r+=this[d0e](e,r);break;default:throw new Error("invalid state: "+this[Lc])}r{"use strict";var Ist=DI(),I0e=vR(),qI=Ie("fs"),Cst=GI(),E0e=Ie("path"),aG=FI();w0e.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=Ist(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&Bst(s,e),s.noResume||wst(s),s.file&&s.sync?vst(s):s.file?Sst(s,r):C0e(s)};var wst=t=>{let e=t.onentry;t.onentry=e?r=>{e(r),r.resume()}:r=>r.resume()},Bst=(t,e)=>{let r=new Map(e.map(n=>[aG(n),!0])),s=t.filter,a=(n,c)=>{let f=c||E0e.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(E0e.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(aG(n)):n=>a(aG(n))},vst=t=>{let e=C0e(t),r=t.file,s=!0,a;try{let n=qI.statSync(r),c=t.maxReadSize||16*1024*1024;if(n.size{let r=new I0e(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("end",c),qI.stat(a,(p,h)=>{if(p)f(p);else{let E=new Cst.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},C0e=t=>new I0e(t)});var P0e=_((w3t,b0e)=>{"use strict";var Dst=DI(),DR=uR(),B0e=GI(),v0e=SR(),S0e=Ie("path");b0e.exports=(t,e,r)=>{if(typeof e=="function"&&(r=e),Array.isArray(t)&&(e=t,t={}),!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);let s=Dst(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return s.file&&s.sync?bst(s,e):s.file?Pst(s,e,r):s.sync?xst(s,e):kst(s,e)};var bst=(t,e)=>{let r=new DR.Sync(t),s=new B0e.WriteStreamSync(t.file,{mode:t.mode||438});r.pipe(s),D0e(r,e)},Pst=(t,e,r)=>{let s=new DR(t),a=new B0e.WriteStream(t.file,{mode:t.mode||438});s.pipe(a);let n=new Promise((c,f)=>{a.on("error",f),a.on("close",c),s.on("error",f)});return lG(s,e),r?n.then(r,r):n},D0e=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?v0e({file:S0e.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},lG=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return v0e({file:S0e.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>lG(t,e));t.add(r)}t.end()},xst=(t,e)=>{let r=new DR.Sync(t);return D0e(r,e),r},kst=(t,e)=>{let r=new DR(t);return lG(r,e),r}});var cG=_((B3t,N0e)=>{"use strict";var Qst=DI(),x0e=uR(),Xl=Ie("fs"),k0e=GI(),Q0e=SR(),T0e=Ie("path"),R0e=RI();N0e.exports=(t,e,r)=>{let s=Qst(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),s.sync?Tst(s,e):Fst(s,e,r)};var Tst=(t,e)=>{let r=new x0e.Sync(t),s=!0,a,n;try{try{a=Xl.openSync(t.file,"r+")}catch(p){if(p.code==="ENOENT")a=Xl.openSync(t.file,"w+");else throw p}let c=Xl.fstatSync(a),f=Buffer.alloc(512);e:for(n=0;nc.size)break;n+=h,t.mtimeCache&&t.mtimeCache.set(p.path,p.mtime)}s=!1,Rst(t,r,n,a,e)}finally{if(s)try{Xl.closeSync(a)}catch{}}},Rst=(t,e,r,s,a)=>{let n=new k0e.WriteStreamSync(t.file,{fd:s,start:r});e.pipe(n),Nst(e,a)},Fst=(t,e,r)=>{e=Array.from(e);let s=new x0e(t),a=(c,f,p)=>{let h=(I,R)=>{I?Xl.close(c,N=>p(I)):p(null,R)},E=0;if(f===0)return h(null,0);let C=0,S=Buffer.alloc(512),P=(I,R)=>{if(I)return h(I);if(C+=R,C<512&&R)return Xl.read(c,S,C,S.length-C,E+C,P);if(E===0&&S[0]===31&&S[1]===139)return h(new Error("cannot append to compressed archives"));if(C<512)return h(null,E);let N=new R0e(S);if(!N.cksumValid)return h(null,E);let U=512*Math.ceil(N.size/512);if(E+U+512>f||(E+=U+512,E>=f))return h(null,E);t.mtimeCache&&t.mtimeCache.set(N.path,N.mtime),C=0,Xl.read(c,S,0,512,E,P)};Xl.read(c,S,0,512,E,P)},n=new Promise((c,f)=>{s.on("error",f);let p="r+",h=(E,C)=>{if(E&&E.code==="ENOENT"&&p==="r+")return p="w+",Xl.open(t.file,p,h);if(E)return f(E);Xl.fstat(C,(S,P)=>{if(S)return Xl.close(C,()=>f(S));a(C,P.size,(I,R)=>{if(I)return f(I);let N=new k0e.WriteStream(t.file,{fd:C,start:R});s.pipe(N),N.on("error",f),N.on("close",c),F0e(s,e)})})};Xl.open(t.file,p,h)});return r?n.then(r,r):n},Nst=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?Q0e({file:T0e.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},F0e=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return Q0e({file:T0e.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>F0e(t,e));t.add(r)}t.end()}});var L0e=_((v3t,O0e)=>{"use strict";var Ost=DI(),Lst=cG();O0e.exports=(t,e,r)=>{let s=Ost(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),Mst(s),Lst(s,e,r)};var Mst=t=>{let e=t.filter;t.mtimeCache||(t.mtimeCache=new Map),t.filter=e?(r,s)=>e(r,s)&&!(t.mtimeCache.get(r)>s.mtime):(r,s)=>!(t.mtimeCache.get(r)>s.mtime)}});var _0e=_((S3t,U0e)=>{var{promisify:M0e}=Ie("util"),U0=Ie("fs"),Ust=t=>{if(!t)t={mode:511,fs:U0};else if(typeof t=="object")t={mode:511,fs:U0,...t};else if(typeof t=="number")t={mode:t,fs:U0};else if(typeof t=="string")t={mode:parseInt(t,8),fs:U0};else throw new TypeError("invalid options argument");return t.mkdir=t.mkdir||t.fs.mkdir||U0.mkdir,t.mkdirAsync=M0e(t.mkdir),t.stat=t.stat||t.fs.stat||U0.stat,t.statAsync=M0e(t.stat),t.statSync=t.statSync||t.fs.statSync||U0.statSync,t.mkdirSync=t.mkdirSync||t.fs.mkdirSync||U0.mkdirSync,t};U0e.exports=Ust});var j0e=_((D3t,H0e)=>{var _st=process.platform,{resolve:Hst,parse:jst}=Ie("path"),Gst=t=>{if(/\0/.test(t))throw Object.assign(new TypeError("path must be a string without null bytes"),{path:t,code:"ERR_INVALID_ARG_VALUE"});if(t=Hst(t),_st==="win32"){let e=/[*|"<>?:]/,{root:r}=jst(t);if(e.test(t.substr(r.length)))throw Object.assign(new Error("Illegal characters in path."),{path:t,code:"EINVAL"})}return t};H0e.exports=Gst});var V0e=_((b3t,Y0e)=>{var{dirname:G0e}=Ie("path"),q0e=(t,e,r=void 0)=>r===e?Promise.resolve():t.statAsync(e).then(s=>s.isDirectory()?r:void 0,s=>s.code==="ENOENT"?q0e(t,G0e(e),e):void 0),W0e=(t,e,r=void 0)=>{if(r!==e)try{return t.statSync(e).isDirectory()?r:void 0}catch(s){return s.code==="ENOENT"?W0e(t,G0e(e),e):void 0}};Y0e.exports={findMade:q0e,findMadeSync:W0e}});var AG=_((P3t,K0e)=>{var{dirname:J0e}=Ie("path"),uG=(t,e,r)=>{e.recursive=!1;let s=J0e(t);return s===t?e.mkdirAsync(t,e).catch(a=>{if(a.code!=="EISDIR")throw a}):e.mkdirAsync(t,e).then(()=>r||t,a=>{if(a.code==="ENOENT")return uG(s,e).then(n=>uG(t,e,n));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;return e.statAsync(t).then(n=>{if(n.isDirectory())return r;throw a},()=>{throw a})})},fG=(t,e,r)=>{let s=J0e(t);if(e.recursive=!1,s===t)try{return e.mkdirSync(t,e)}catch(a){if(a.code!=="EISDIR")throw a;return}try{return e.mkdirSync(t,e),r||t}catch(a){if(a.code==="ENOENT")return fG(t,e,fG(s,e,r));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;try{if(!e.statSync(t).isDirectory())throw a}catch{throw a}}};K0e.exports={mkdirpManual:uG,mkdirpManualSync:fG}});var Z0e=_((x3t,X0e)=>{var{dirname:z0e}=Ie("path"),{findMade:qst,findMadeSync:Wst}=V0e(),{mkdirpManual:Yst,mkdirpManualSync:Vst}=AG(),Jst=(t,e)=>(e.recursive=!0,z0e(t)===t?e.mkdirAsync(t,e):qst(e,t).then(s=>e.mkdirAsync(t,e).then(()=>s).catch(a=>{if(a.code==="ENOENT")return Yst(t,e);throw a}))),Kst=(t,e)=>{if(e.recursive=!0,z0e(t)===t)return e.mkdirSync(t,e);let s=Wst(e,t);try{return e.mkdirSync(t,e),s}catch(a){if(a.code==="ENOENT")return Vst(t,e);throw a}};X0e.exports={mkdirpNative:Jst,mkdirpNativeSync:Kst}});var rge=_((k3t,tge)=>{var $0e=Ie("fs"),zst=process.version,pG=zst.replace(/^v/,"").split("."),ege=+pG[0]>10||+pG[0]==10&&+pG[1]>=12,Xst=ege?t=>t.mkdir===$0e.mkdir:()=>!1,Zst=ege?t=>t.mkdirSync===$0e.mkdirSync:()=>!1;tge.exports={useNative:Xst,useNativeSync:Zst}});var lge=_((Q3t,age)=>{var WI=_0e(),YI=j0e(),{mkdirpNative:nge,mkdirpNativeSync:ige}=Z0e(),{mkdirpManual:sge,mkdirpManualSync:oge}=AG(),{useNative:$st,useNativeSync:eot}=rge(),VI=(t,e)=>(t=YI(t),e=WI(e),$st(e)?nge(t,e):sge(t,e)),tot=(t,e)=>(t=YI(t),e=WI(e),eot(e)?ige(t,e):oge(t,e));VI.sync=tot;VI.native=(t,e)=>nge(YI(t),WI(e));VI.manual=(t,e)=>sge(YI(t),WI(e));VI.nativeSync=(t,e)=>ige(YI(t),WI(e));VI.manualSync=(t,e)=>oge(YI(t),WI(e));age.exports=VI});var gge=_((T3t,hge)=>{"use strict";var Uc=Ie("fs"),Cm=Ie("path"),rot=Uc.lchown?"lchown":"chown",not=Uc.lchownSync?"lchownSync":"chownSync",uge=Uc.lchown&&!process.version.match(/v1[1-9]+\./)&&!process.version.match(/v10\.[6-9]/),cge=(t,e,r)=>{try{return Uc[not](t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},iot=(t,e,r)=>{try{return Uc.chownSync(t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},sot=uge?(t,e,r,s)=>a=>{!a||a.code!=="EISDIR"?s(a):Uc.chown(t,e,r,s)}:(t,e,r,s)=>s,hG=uge?(t,e,r)=>{try{return cge(t,e,r)}catch(s){if(s.code!=="EISDIR")throw s;iot(t,e,r)}}:(t,e,r)=>cge(t,e,r),oot=process.version,fge=(t,e,r)=>Uc.readdir(t,e,r),aot=(t,e)=>Uc.readdirSync(t,e);/^v4\./.test(oot)&&(fge=(t,e,r)=>Uc.readdir(t,r));var bR=(t,e,r,s)=>{Uc[rot](t,e,r,sot(t,e,r,a=>{s(a&&a.code!=="ENOENT"?a:null)}))},Age=(t,e,r,s,a)=>{if(typeof e=="string")return Uc.lstat(Cm.resolve(t,e),(n,c)=>{if(n)return a(n.code!=="ENOENT"?n:null);c.name=e,Age(t,c,r,s,a)});if(e.isDirectory())gG(Cm.resolve(t,e.name),r,s,n=>{if(n)return a(n);let c=Cm.resolve(t,e.name);bR(c,r,s,a)});else{let n=Cm.resolve(t,e.name);bR(n,r,s,a)}},gG=(t,e,r,s)=>{fge(t,{withFileTypes:!0},(a,n)=>{if(a){if(a.code==="ENOENT")return s();if(a.code!=="ENOTDIR"&&a.code!=="ENOTSUP")return s(a)}if(a||!n.length)return bR(t,e,r,s);let c=n.length,f=null,p=h=>{if(!f){if(h)return s(f=h);if(--c===0)return bR(t,e,r,s)}};n.forEach(h=>Age(t,h,e,r,p))})},lot=(t,e,r,s)=>{if(typeof e=="string")try{let a=Uc.lstatSync(Cm.resolve(t,e));a.name=e,e=a}catch(a){if(a.code==="ENOENT")return;throw a}e.isDirectory()&&pge(Cm.resolve(t,e.name),r,s),hG(Cm.resolve(t,e.name),r,s)},pge=(t,e,r)=>{let s;try{s=aot(t,{withFileTypes:!0})}catch(a){if(a.code==="ENOENT")return;if(a.code==="ENOTDIR"||a.code==="ENOTSUP")return hG(t,e,r);throw a}return s&&s.length&&s.forEach(a=>lot(t,a,e,r)),hG(t,e,r)};hge.exports=gG;gG.sync=pge});var Ege=_((R3t,dG)=>{"use strict";var dge=lge(),_c=Ie("fs"),PR=Ie("path"),mge=gge(),Vu=QI(),xR=class extends Error{constructor(e,r){super("Cannot extract through symbolic link"),this.path=r,this.symlink=e}get name(){return"SylinkError"}},kR=class extends Error{constructor(e,r){super(r+": Cannot cd into '"+e+"'"),this.path=e,this.code=r}get name(){return"CwdError"}},QR=(t,e)=>t.get(Vu(e)),Qv=(t,e,r)=>t.set(Vu(e),r),cot=(t,e)=>{_c.stat(t,(r,s)=>{(r||!s.isDirectory())&&(r=new kR(t,r&&r.code||"ENOTDIR")),e(r)})};dG.exports=(t,e,r)=>{t=Vu(t);let s=e.umask,a=e.mode|448,n=(a&s)!==0,c=e.uid,f=e.gid,p=typeof c=="number"&&typeof f=="number"&&(c!==e.processUid||f!==e.processGid),h=e.preserve,E=e.unlink,C=e.cache,S=Vu(e.cwd),P=(N,U)=>{N?r(N):(Qv(C,t,!0),U&&p?mge(U,c,f,W=>P(W)):n?_c.chmod(t,a,r):r())};if(C&&QR(C,t)===!0)return P();if(t===S)return cot(t,P);if(h)return dge(t,{mode:a}).then(N=>P(null,N),P);let R=Vu(PR.relative(S,t)).split("/");TR(S,R,a,C,E,S,null,P)};var TR=(t,e,r,s,a,n,c,f)=>{if(!e.length)return f(null,c);let p=e.shift(),h=Vu(PR.resolve(t+"/"+p));if(QR(s,h))return TR(h,e,r,s,a,n,c,f);_c.mkdir(h,r,yge(h,e,r,s,a,n,c,f))},yge=(t,e,r,s,a,n,c,f)=>p=>{p?_c.lstat(t,(h,E)=>{if(h)h.path=h.path&&Vu(h.path),f(h);else if(E.isDirectory())TR(t,e,r,s,a,n,c,f);else if(a)_c.unlink(t,C=>{if(C)return f(C);_c.mkdir(t,r,yge(t,e,r,s,a,n,c,f))});else{if(E.isSymbolicLink())return f(new xR(t,t+"/"+e.join("/")));f(p)}}):(c=c||t,TR(t,e,r,s,a,n,c,f))},uot=t=>{let e=!1,r="ENOTDIR";try{e=_c.statSync(t).isDirectory()}catch(s){r=s.code}finally{if(!e)throw new kR(t,r)}};dG.exports.sync=(t,e)=>{t=Vu(t);let r=e.umask,s=e.mode|448,a=(s&r)!==0,n=e.uid,c=e.gid,f=typeof n=="number"&&typeof c=="number"&&(n!==e.processUid||c!==e.processGid),p=e.preserve,h=e.unlink,E=e.cache,C=Vu(e.cwd),S=N=>{Qv(E,t,!0),N&&f&&mge.sync(N,n,c),a&&_c.chmodSync(t,s)};if(E&&QR(E,t)===!0)return S();if(t===C)return uot(C),S();if(p)return S(dge.sync(t,s));let I=Vu(PR.relative(C,t)).split("/"),R=null;for(let N=I.shift(),U=C;N&&(U+="/"+N);N=I.shift())if(U=Vu(PR.resolve(U)),!QR(E,U))try{_c.mkdirSync(U,s),R=R||U,Qv(E,U,!0)}catch{let ee=_c.lstatSync(U);if(ee.isDirectory()){Qv(E,U,!0);continue}else if(h){_c.unlinkSync(U),_c.mkdirSync(U,s),R=R||U,Qv(E,U,!0);continue}else if(ee.isSymbolicLink())return new xR(U,U+"/"+I.join("/"))}return S(R)}});var yG=_((F3t,Ige)=>{var mG=Object.create(null),{hasOwnProperty:fot}=Object.prototype;Ige.exports=t=>(fot.call(mG,t)||(mG[t]=t.normalize("NFKD")),mG[t])});var vge=_((N3t,Bge)=>{var Cge=Ie("assert"),Aot=yG(),pot=FI(),{join:wge}=Ie("path"),hot=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,got=hot==="win32";Bge.exports=()=>{let t=new Map,e=new Map,r=h=>h.split("/").slice(0,-1).reduce((C,S)=>(C.length&&(S=wge(C[C.length-1],S)),C.push(S||"/"),C),[]),s=new Set,a=h=>{let E=e.get(h);if(!E)throw new Error("function does not have any path reservations");return{paths:E.paths.map(C=>t.get(C)),dirs:[...E.dirs].map(C=>t.get(C))}},n=h=>{let{paths:E,dirs:C}=a(h);return E.every(S=>S[0]===h)&&C.every(S=>S[0]instanceof Set&&S[0].has(h))},c=h=>s.has(h)||!n(h)?!1:(s.add(h),h(()=>f(h)),!0),f=h=>{if(!s.has(h))return!1;let{paths:E,dirs:C}=e.get(h),S=new Set;return E.forEach(P=>{let I=t.get(P);Cge.equal(I[0],h),I.length===1?t.delete(P):(I.shift(),typeof I[0]=="function"?S.add(I[0]):I[0].forEach(R=>S.add(R)))}),C.forEach(P=>{let I=t.get(P);Cge(I[0]instanceof Set),I[0].size===1&&I.length===1?t.delete(P):I[0].size===1?(I.shift(),S.add(I[0])):I[0].delete(h)}),s.delete(h),S.forEach(P=>c(P)),!0};return{check:n,reserve:(h,E)=>{h=got?["win32 parallelization disabled"]:h.map(S=>Aot(pot(wge(S))).toLowerCase());let C=new Set(h.map(S=>r(S)).reduce((S,P)=>S.concat(P)));return e.set(E,{dirs:C,paths:h}),h.forEach(S=>{let P=t.get(S);P?P.push(E):t.set(S,[E])}),C.forEach(S=>{let P=t.get(S);P?P[P.length-1]instanceof Set?P[P.length-1].add(E):P.push(new Set([E])):t.set(S,[new Set([E])])}),c(E)}}}});var bge=_((O3t,Dge)=>{var dot=process.platform,mot=dot==="win32",yot=global.__FAKE_TESTING_FS__||Ie("fs"),{O_CREAT:Eot,O_TRUNC:Iot,O_WRONLY:Cot,UV_FS_O_FILEMAP:Sge=0}=yot.constants,wot=mot&&!!Sge,Bot=512*1024,vot=Sge|Iot|Eot|Cot;Dge.exports=wot?t=>t"w"});var bG=_((L3t,Hge)=>{"use strict";var Sot=Ie("assert"),Dot=vR(),Mn=Ie("fs"),bot=GI(),Jp=Ie("path"),Mge=Ege(),Pge=b6(),Pot=vge(),xot=P6(),Zl=QI(),kot=FI(),Qot=yG(),xge=Symbol("onEntry"),CG=Symbol("checkFs"),kge=Symbol("checkFs2"),NR=Symbol("pruneCache"),wG=Symbol("isReusable"),Hc=Symbol("makeFs"),BG=Symbol("file"),vG=Symbol("directory"),OR=Symbol("link"),Qge=Symbol("symlink"),Tge=Symbol("hardlink"),Rge=Symbol("unsupported"),Fge=Symbol("checkPath"),_0=Symbol("mkdir"),Xo=Symbol("onError"),RR=Symbol("pending"),Nge=Symbol("pend"),JI=Symbol("unpend"),EG=Symbol("ended"),IG=Symbol("maybeClose"),SG=Symbol("skip"),Tv=Symbol("doChown"),Rv=Symbol("uid"),Fv=Symbol("gid"),Nv=Symbol("checkedCwd"),Uge=Ie("crypto"),_ge=bge(),Tot=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,Ov=Tot==="win32",Rot=(t,e)=>{if(!Ov)return Mn.unlink(t,e);let r=t+".DELETE."+Uge.randomBytes(16).toString("hex");Mn.rename(t,r,s=>{if(s)return e(s);Mn.unlink(r,e)})},Fot=t=>{if(!Ov)return Mn.unlinkSync(t);let e=t+".DELETE."+Uge.randomBytes(16).toString("hex");Mn.renameSync(t,e),Mn.unlinkSync(e)},Oge=(t,e,r)=>t===t>>>0?t:e===e>>>0?e:r,Lge=t=>Qot(kot(Zl(t))).toLowerCase(),Not=(t,e)=>{e=Lge(e);for(let r of t.keys()){let s=Lge(r);(s===e||s.indexOf(e+"/")===0)&&t.delete(r)}},Oot=t=>{for(let e of t.keys())t.delete(e)},Lv=class extends Dot{constructor(e){if(e||(e={}),e.ondone=r=>{this[EG]=!0,this[IG]()},super(e),this[Nv]=!1,this.reservations=Pot(),this.transform=typeof e.transform=="function"?e.transform:null,this.writable=!0,this.readable=!1,this[RR]=0,this[EG]=!1,this.dirCache=e.dirCache||new Map,typeof e.uid=="number"||typeof e.gid=="number"){if(typeof e.uid!="number"||typeof e.gid!="number")throw new TypeError("cannot set owner without number uid and gid");if(e.preserveOwner)throw new TypeError("cannot preserve owner in archive and also set owner explicitly");this.uid=e.uid,this.gid=e.gid,this.setOwner=!0}else this.uid=null,this.gid=null,this.setOwner=!1;e.preserveOwner===void 0&&typeof e.uid!="number"?this.preserveOwner=process.getuid&&process.getuid()===0:this.preserveOwner=!!e.preserveOwner,this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():null,this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():null,this.forceChown=e.forceChown===!0,this.win32=!!e.win32||Ov,this.newer=!!e.newer,this.keep=!!e.keep,this.noMtime=!!e.noMtime,this.preservePaths=!!e.preservePaths,this.unlink=!!e.unlink,this.cwd=Zl(Jp.resolve(e.cwd||process.cwd())),this.strip=+e.strip||0,this.processUmask=e.noChmod?0:process.umask(),this.umask=typeof e.umask=="number"?e.umask:this.processUmask,this.dmode=e.dmode||511&~this.umask,this.fmode=e.fmode||438&~this.umask,this.on("entry",r=>this[xge](r))}warn(e,r,s={}){return(e==="TAR_BAD_ARCHIVE"||e==="TAR_ABORT")&&(s.recoverable=!1),super.warn(e,r,s)}[IG](){this[EG]&&this[RR]===0&&(this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close"))}[Fge](e){if(this.strip){let r=Zl(e.path).split("/");if(r.length=this.strip)e.linkpath=s.slice(this.strip).join("/");else return!1}}if(!this.preservePaths){let r=Zl(e.path),s=r.split("/");if(s.includes("..")||Ov&&/^[a-z]:\.\.$/i.test(s[0]))return this.warn("TAR_ENTRY_ERROR","path contains '..'",{entry:e,path:r}),!1;let[a,n]=xot(r);a&&(e.path=n,this.warn("TAR_ENTRY_INFO",`stripping ${a} from absolute path`,{entry:e,path:r}))}if(Jp.isAbsolute(e.path)?e.absolute=Zl(Jp.resolve(e.path)):e.absolute=Zl(Jp.resolve(this.cwd,e.path)),!this.preservePaths&&e.absolute.indexOf(this.cwd+"/")!==0&&e.absolute!==this.cwd)return this.warn("TAR_ENTRY_ERROR","path escaped extraction target",{entry:e,path:Zl(e.path),resolvedPath:e.absolute,cwd:this.cwd}),!1;if(e.absolute===this.cwd&&e.type!=="Directory"&&e.type!=="GNUDumpDir")return!1;if(this.win32){let{root:r}=Jp.win32.parse(e.absolute);e.absolute=r+Pge.encode(e.absolute.substr(r.length));let{root:s}=Jp.win32.parse(e.path);e.path=s+Pge.encode(e.path.substr(s.length))}return!0}[xge](e){if(!this[Fge](e))return e.resume();switch(Sot.equal(typeof e.absolute,"string"),e.type){case"Directory":case"GNUDumpDir":e.mode&&(e.mode=e.mode|448);case"File":case"OldFile":case"ContiguousFile":case"Link":case"SymbolicLink":return this[CG](e);case"CharacterDevice":case"BlockDevice":case"FIFO":default:return this[Rge](e)}}[Xo](e,r){e.name==="CwdError"?this.emit("error",e):(this.warn("TAR_ENTRY_ERROR",e,{entry:r}),this[JI](),r.resume())}[_0](e,r,s){Mge(Zl(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r,noChmod:this.noChmod},s)}[Tv](e){return this.forceChown||this.preserveOwner&&(typeof e.uid=="number"&&e.uid!==this.processUid||typeof e.gid=="number"&&e.gid!==this.processGid)||typeof this.uid=="number"&&this.uid!==this.processUid||typeof this.gid=="number"&&this.gid!==this.processGid}[Rv](e){return Oge(this.uid,e.uid,this.processUid)}[Fv](e){return Oge(this.gid,e.gid,this.processGid)}[BG](e,r){let s=e.mode&4095||this.fmode,a=new bot.WriteStream(e.absolute,{flags:_ge(e.size),mode:s,autoClose:!1});a.on("error",p=>{a.fd&&Mn.close(a.fd,()=>{}),a.write=()=>!0,this[Xo](p,e),r()});let n=1,c=p=>{if(p){a.fd&&Mn.close(a.fd,()=>{}),this[Xo](p,e),r();return}--n===0&&Mn.close(a.fd,h=>{h?this[Xo](h,e):this[JI](),r()})};a.on("finish",p=>{let h=e.absolute,E=a.fd;if(e.mtime&&!this.noMtime){n++;let C=e.atime||new Date,S=e.mtime;Mn.futimes(E,C,S,P=>P?Mn.utimes(h,C,S,I=>c(I&&P)):c())}if(this[Tv](e)){n++;let C=this[Rv](e),S=this[Fv](e);Mn.fchown(E,C,S,P=>P?Mn.chown(h,C,S,I=>c(I&&P)):c())}c()});let f=this.transform&&this.transform(e)||e;f!==e&&(f.on("error",p=>{this[Xo](p,e),r()}),e.pipe(f)),f.pipe(a)}[vG](e,r){let s=e.mode&4095||this.dmode;this[_0](e.absolute,s,a=>{if(a){this[Xo](a,e),r();return}let n=1,c=f=>{--n===0&&(r(),this[JI](),e.resume())};e.mtime&&!this.noMtime&&(n++,Mn.utimes(e.absolute,e.atime||new Date,e.mtime,c)),this[Tv](e)&&(n++,Mn.chown(e.absolute,this[Rv](e),this[Fv](e),c)),c()})}[Rge](e){e.unsupported=!0,this.warn("TAR_ENTRY_UNSUPPORTED",`unsupported entry type: ${e.type}`,{entry:e}),e.resume()}[Qge](e,r){this[OR](e,e.linkpath,"symlink",r)}[Tge](e,r){let s=Zl(Jp.resolve(this.cwd,e.linkpath));this[OR](e,s,"link",r)}[Nge](){this[RR]++}[JI](){this[RR]--,this[IG]()}[SG](e){this[JI](),e.resume()}[wG](e,r){return e.type==="File"&&!this.unlink&&r.isFile()&&r.nlink<=1&&!Ov}[CG](e){this[Nge]();let r=[e.path];e.linkpath&&r.push(e.linkpath),this.reservations.reserve(r,s=>this[kge](e,s))}[NR](e){e.type==="SymbolicLink"?Oot(this.dirCache):e.type!=="Directory"&&Not(this.dirCache,e.absolute)}[kge](e,r){this[NR](e);let s=f=>{this[NR](e),r(f)},a=()=>{this[_0](this.cwd,this.dmode,f=>{if(f){this[Xo](f,e),s();return}this[Nv]=!0,n()})},n=()=>{if(e.absolute!==this.cwd){let f=Zl(Jp.dirname(e.absolute));if(f!==this.cwd)return this[_0](f,this.dmode,p=>{if(p){this[Xo](p,e),s();return}c()})}c()},c=()=>{Mn.lstat(e.absolute,(f,p)=>{if(p&&(this.keep||this.newer&&p.mtime>e.mtime)){this[SG](e),s();return}if(f||this[wG](e,p))return this[Hc](null,e,s);if(p.isDirectory()){if(e.type==="Directory"){let h=!this.noChmod&&e.mode&&(p.mode&4095)!==e.mode,E=C=>this[Hc](C,e,s);return h?Mn.chmod(e.absolute,e.mode,E):E()}if(e.absolute!==this.cwd)return Mn.rmdir(e.absolute,h=>this[Hc](h,e,s))}if(e.absolute===this.cwd)return this[Hc](null,e,s);Rot(e.absolute,h=>this[Hc](h,e,s))})};this[Nv]?n():a()}[Hc](e,r,s){if(e){this[Xo](e,r),s();return}switch(r.type){case"File":case"OldFile":case"ContiguousFile":return this[BG](r,s);case"Link":return this[Tge](r,s);case"SymbolicLink":return this[Qge](r,s);case"Directory":case"GNUDumpDir":return this[vG](r,s)}}[OR](e,r,s,a){Mn[s](r,e.absolute,n=>{n?this[Xo](n,e):(this[JI](),e.resume()),a()})}},FR=t=>{try{return[null,t()]}catch(e){return[e,null]}},DG=class extends Lv{[Hc](e,r){return super[Hc](e,r,()=>{})}[CG](e){if(this[NR](e),!this[Nv]){let n=this[_0](this.cwd,this.dmode);if(n)return this[Xo](n,e);this[Nv]=!0}if(e.absolute!==this.cwd){let n=Zl(Jp.dirname(e.absolute));if(n!==this.cwd){let c=this[_0](n,this.dmode);if(c)return this[Xo](c,e)}}let[r,s]=FR(()=>Mn.lstatSync(e.absolute));if(s&&(this.keep||this.newer&&s.mtime>e.mtime))return this[SG](e);if(r||this[wG](e,s))return this[Hc](null,e);if(s.isDirectory()){if(e.type==="Directory"){let c=!this.noChmod&&e.mode&&(s.mode&4095)!==e.mode,[f]=c?FR(()=>{Mn.chmodSync(e.absolute,e.mode)}):[];return this[Hc](f,e)}let[n]=FR(()=>Mn.rmdirSync(e.absolute));this[Hc](n,e)}let[a]=e.absolute===this.cwd?[]:FR(()=>Fot(e.absolute));this[Hc](a,e)}[BG](e,r){let s=e.mode&4095||this.fmode,a=f=>{let p;try{Mn.closeSync(n)}catch(h){p=h}(f||p)&&this[Xo](f||p,e),r()},n;try{n=Mn.openSync(e.absolute,_ge(e.size),s)}catch(f){return a(f)}let c=this.transform&&this.transform(e)||e;c!==e&&(c.on("error",f=>this[Xo](f,e)),e.pipe(c)),c.on("data",f=>{try{Mn.writeSync(n,f,0,f.length)}catch(p){a(p)}}),c.on("end",f=>{let p=null;if(e.mtime&&!this.noMtime){let h=e.atime||new Date,E=e.mtime;try{Mn.futimesSync(n,h,E)}catch(C){try{Mn.utimesSync(e.absolute,h,E)}catch{p=C}}}if(this[Tv](e)){let h=this[Rv](e),E=this[Fv](e);try{Mn.fchownSync(n,h,E)}catch(C){try{Mn.chownSync(e.absolute,h,E)}catch{p=p||C}}}a(p)})}[vG](e,r){let s=e.mode&4095||this.dmode,a=this[_0](e.absolute,s);if(a){this[Xo](a,e),r();return}if(e.mtime&&!this.noMtime)try{Mn.utimesSync(e.absolute,e.atime||new Date,e.mtime)}catch{}if(this[Tv](e))try{Mn.chownSync(e.absolute,this[Rv](e),this[Fv](e))}catch{}r(),e.resume()}[_0](e,r){try{return Mge.sync(Zl(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r})}catch(s){return s}}[OR](e,r,s,a){try{Mn[s+"Sync"](r,e.absolute),a(),e.resume()}catch(n){return this[Xo](n,e)}}};Lv.Sync=DG;Hge.exports=Lv});var Yge=_((M3t,Wge)=>{"use strict";var Lot=DI(),LR=bG(),Gge=Ie("fs"),qge=GI(),jge=Ie("path"),PG=FI();Wge.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=Lot(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&Mot(s,e),s.file&&s.sync?Uot(s):s.file?_ot(s,r):s.sync?Hot(s):jot(s)};var Mot=(t,e)=>{let r=new Map(e.map(n=>[PG(n),!0])),s=t.filter,a=(n,c)=>{let f=c||jge.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(jge.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(PG(n)):n=>a(PG(n))},Uot=t=>{let e=new LR.Sync(t),r=t.file,s=Gge.statSync(r),a=t.maxReadSize||16*1024*1024;new qge.ReadStreamSync(r,{readSize:a,size:s.size}).pipe(e)},_ot=(t,e)=>{let r=new LR(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("close",c),Gge.stat(a,(p,h)=>{if(p)f(p);else{let E=new qge.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},Hot=t=>new LR.Sync(t),jot=t=>new LR(t)});var Vge=_(Ps=>{"use strict";Ps.c=Ps.create=P0e();Ps.r=Ps.replace=cG();Ps.t=Ps.list=SR();Ps.u=Ps.update=L0e();Ps.x=Ps.extract=Yge();Ps.Pack=uR();Ps.Unpack=bG();Ps.Parse=vR();Ps.ReadEntry=VT();Ps.WriteEntry=M6();Ps.Header=RI();Ps.Pax=KT();Ps.types=I6()});var xG,Jge,H0,Mv,Uv,Kge=Xe(()=>{xG=ut(Ld()),Jge=Ie("worker_threads"),H0=Symbol("kTaskInfo"),Mv=class{constructor(e,r){this.fn=e;this.limit=(0,xG.default)(r.poolSize)}run(e){return this.limit(()=>this.fn(e))}},Uv=class{constructor(e,r){this.source=e;this.workers=[];this.limit=(0,xG.default)(r.poolSize),this.cleanupInterval=setInterval(()=>{if(this.limit.pendingCount===0&&this.limit.activeCount===0){let s=this.workers.pop();s?s.terminate():clearInterval(this.cleanupInterval)}},5e3).unref()}createWorker(){this.cleanupInterval.refresh();let e=new Jge.Worker(this.source,{eval:!0,execArgv:[...process.execArgv,"--unhandled-rejections=strict"]});return e.on("message",r=>{if(!e[H0])throw new Error("Assertion failed: Worker sent a result without having a task assigned");e[H0].resolve(r),e[H0]=null,e.unref(),this.workers.push(e)}),e.on("error",r=>{e[H0]?.reject(r),e[H0]=null}),e.on("exit",r=>{r!==0&&e[H0]?.reject(new Error(`Worker exited with code ${r}`)),e[H0]=null}),e}run(e){return this.limit(()=>{let r=this.workers.pop()??this.createWorker();return r.ref(),new Promise((s,a)=>{r[H0]={resolve:s,reject:a},r.postMessage(e)})})}}});var Xge=_((j3t,zge)=>{var kG;zge.exports.getContent=()=>(typeof kG>"u"&&(kG=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),kG)});var ps={};Vt(ps,{convertToZip:()=>Yot,convertToZipWorker:()=>RG,extractArchiveTo:()=>rde,getDefaultTaskPool:()=>ede,getTaskPoolForConfiguration:()=>tde,makeArchiveFromDirectory:()=>Wot});function Got(t,e){switch(t){case"async":return new Mv(RG,{poolSize:e});case"workers":return new Uv((0,TG.getContent)(),{poolSize:e});default:throw new Error(`Assertion failed: Unknown value ${t} for taskPoolMode`)}}function ede(){return typeof QG>"u"&&(QG=Got("workers",Ui.availableParallelism())),QG}function tde(t){return typeof t>"u"?ede():Yl(qot,t,()=>{let e=t.get("taskPoolMode"),r=t.get("taskPoolConcurrency");switch(e){case"async":return new Mv(RG,{poolSize:r});case"workers":return new Uv((0,TG.getContent)(),{poolSize:r});default:throw new Error(`Assertion failed: Unknown value ${e} for taskPoolMode`)}})}async function RG(t){let{tmpFile:e,tgz:r,compressionLevel:s,extractBufferOpts:a}=t,n=new As(e,{create:!0,level:s,stats:$a.makeDefaultStats()}),c=Buffer.from(r.buffer,r.byteOffset,r.byteLength);return await rde(c,n,a),n.saveAndClose(),e}async function Wot(t,{baseFs:e=new Yn,prefixPath:r=vt.root,compressionLevel:s,inMemory:a=!1}={}){let n;if(a)n=new As(null,{level:s});else{let f=await ce.mktempPromise(),p=J.join(f,"archive.zip");n=new As(p,{create:!0,level:s})}let c=J.resolve(vt.root,r);return await n.copyPromise(c,t,{baseFs:e,stableTime:!0,stableSort:!0}),n}async function Yot(t,e={}){let r=await ce.mktempPromise(),s=J.join(r,"archive.zip"),a=e.compressionLevel??e.configuration?.get("compressionLevel")??"mixed",n={prefixPath:e.prefixPath,stripComponents:e.stripComponents};return await(e.taskPool??tde(e.configuration)).run({tmpFile:s,tgz:t,compressionLevel:a,extractBufferOpts:n}),new As(s,{level:e.compressionLevel})}async function*Vot(t){let e=new $ge.default.Parse,r=new Zge.PassThrough({objectMode:!0,autoDestroy:!0,emitClose:!0});e.on("entry",s=>{r.write(s)}),e.on("error",s=>{r.destroy(s)}),e.on("close",()=>{r.destroyed||r.end()}),e.end(t);for await(let s of r){let a=s;yield a,a.resume()}}async function rde(t,e,{stripComponents:r=0,prefixPath:s=vt.dot}={}){function a(n){if(n.path[0]==="/")return!0;let c=n.path.split(/\//g);return!!(c.some(f=>f==="..")||c.length<=r)}for await(let n of Vot(t)){if(a(n))continue;let c=J.normalize(fe.toPortablePath(n.path)).replace(/\/$/,"").split(/\//g);if(c.length<=r)continue;let f=c.slice(r).join("/"),p=J.join(s,f),h=420;switch((n.type==="Directory"||(n.mode??0)&73)&&(h|=73),n.type){case"Directory":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.mkdirSync(p,{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case"OldFile":case"File":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.writeFileSync(p,await WE(n),{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case"SymbolicLink":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.symlinkSync(n.linkpath,p),e.lutimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break}}return e}var Zge,$ge,TG,QG,qot,nde=Xe(()=>{Ge();Dt();eA();Zge=Ie("stream"),$ge=ut(Vge());Kge();Pc();TG=ut(Xge());qot=new WeakMap});var sde=_((FG,ide)=>{(function(t,e){typeof FG=="object"?ide.exports=e():typeof define=="function"&&define.amd?define(e):t.treeify=e()})(FG,function(){function t(a,n){var c=n?"\u2514":"\u251C";return a?c+="\u2500 ":c+="\u2500\u2500\u2510",c}function e(a,n){var c=[];for(var f in a)a.hasOwnProperty(f)&&(n&&typeof a[f]=="function"||c.push(f));return c}function r(a,n,c,f,p,h,E){var C="",S=0,P,I,R=f.slice(0);if(R.push([n,c])&&f.length>0&&(f.forEach(function(U,W){W>0&&(C+=(U[1]?" ":"\u2502")+" "),!I&&U[0]===n&&(I=!0)}),C+=t(a,c)+a,p&&(typeof n!="object"||n instanceof Date)&&(C+=": "+n),I&&(C+=" (circular ref.)"),E(C)),!I&&typeof n=="object"){var N=e(n,h);N.forEach(function(U){P=++S===N.length,r(U,n[U],P,R,p,h,E)})}}var s={};return s.asLines=function(a,n,c,f){var p=typeof c!="function"?c:!1;r(".",a,!1,[],n,p,f||c)},s.asTree=function(a,n,c){var f="";return r(".",a,!1,[],n,c,function(p){f+=p+` +`}),f},s})});var xs={};Vt(xs,{emitList:()=>Jot,emitTree:()=>cde,treeNodeToJson:()=>lde,treeNodeToTreeify:()=>ade});function ade(t,{configuration:e}){let r={},s=0,a=(n,c)=>{let f=Array.isArray(n)?n.entries():Object.entries(n);for(let[p,h]of f){if(!h)continue;let{label:E,value:C,children:S}=h,P=[];typeof E<"u"&&P.push(zd(e,E,2)),typeof C<"u"&&P.push(Ht(e,C[0],C[1])),P.length===0&&P.push(zd(e,`${p}`,2));let I=P.join(": ").trim(),R=`\0${s++}\0`,N=c[`${R}${I}`]={};typeof S<"u"&&a(S,N)}};if(typeof t.children>"u")throw new Error("The root node must only contain children");return a(t.children,r),r}function lde(t){let e=r=>{if(typeof r.children>"u"){if(typeof r.value>"u")throw new Error("Assertion failed: Expected a value to be set if the children are missing");return Xd(r.value[0],r.value[1])}let s=Array.isArray(r.children)?r.children.entries():Object.entries(r.children??{}),a=Array.isArray(r.children)?[]:{};for(let[n,c]of s)c&&(a[Kot(n)]=e(c));return typeof r.value>"u"?a:{value:Xd(r.value[0],r.value[1]),children:a}};return e(t)}function Jot(t,{configuration:e,stdout:r,json:s}){let a=t.map(n=>({value:n}));cde({children:a},{configuration:e,stdout:r,json:s})}function cde(t,{configuration:e,stdout:r,json:s,separators:a=0}){if(s){let c=Array.isArray(t.children)?t.children.values():Object.values(t.children??{});for(let f of c)f&&r.write(`${JSON.stringify(lde(f))} +`);return}let n=(0,ode.asTree)(ade(t,{configuration:e}),!1,!1);if(n=n.replace(/\0[0-9]+\0/g,""),a>=1&&(n=n.replace(/^([├└]─)/gm,`\u2502 +$1`).replace(/^│\n/,"")),a>=2)for(let c=0;c<2;++c)n=n.replace(/^([│ ].{2}[├│ ].{2}[^\n]+\n)(([│ ]).{2}[├└].{2}[^\n]*\n[│ ].{2}[│ ].{2}[├└]─)/gm,`$1$3 \u2502 +$2`).replace(/^│\n/,"");if(a>=3)throw new Error("Only the first two levels are accepted by treeUtils.emitTree");r.write(n)}function Kot(t){return typeof t=="string"?t.replace(/^\0[0-9]+\0/,""):t}var ode,ude=Xe(()=>{ode=ut(sde());xc()});var MR,fde=Xe(()=>{MR=class{constructor(e){this.releaseFunction=e;this.map=new Map}addOrCreate(e,r){let s=this.map.get(e);if(typeof s<"u"){if(s.refCount<=0)throw new Error(`Race condition in RefCountedMap. While adding a new key the refCount is: ${s.refCount} for ${JSON.stringify(e)}`);return s.refCount++,{value:s.value,release:()=>this.release(e)}}else{let a=r();return this.map.set(e,{refCount:1,value:a}),{value:a,release:()=>this.release(e)}}}release(e){let r=this.map.get(e);if(!r)throw new Error(`Unbalanced calls to release. No known instances of: ${JSON.stringify(e)}`);let s=r.refCount;if(s<=0)throw new Error(`Unbalanced calls to release. Too many release vs alloc refcount would become: ${s-1} of ${JSON.stringify(e)}`);s==1?(this.map.delete(e),this.releaseFunction(r.value)):r.refCount--}}});function _v(t){let e=t.match(zot);if(!e?.groups)throw new Error("Assertion failed: Expected the checksum to match the requested pattern");let r=e.groups.cacheVersion?parseInt(e.groups.cacheVersion):null;return{cacheKey:e.groups.cacheKey??null,cacheVersion:r,cacheSpec:e.groups.cacheSpec??null,hash:e.groups.hash}}var Ade,NG,OG,UR,Kr,zot,LG=Xe(()=>{Ge();Dt();Dt();eA();Ade=Ie("crypto"),NG=ut(Ie("fs"));fde();Tc();I0();Pc();Wo();OG=YE(process.env.YARN_CACHE_CHECKPOINT_OVERRIDE??process.env.YARN_CACHE_VERSION_OVERRIDE??9),UR=YE(process.env.YARN_CACHE_VERSION_OVERRIDE??10),Kr=class t{constructor(e,{configuration:r,immutable:s=r.get("enableImmutableCache"),check:a=!1}){this.markedFiles=new Set;this.mutexes=new Map;this.refCountedZipFsCache=new MR(e=>{e.discardAndClose()});this.cacheId=`-${(0,Ade.randomBytes)(8).toString("hex")}.tmp`;this.configuration=r,this.cwd=e,this.immutable=s,this.check=a;let{cacheSpec:n,cacheKey:c}=t.getCacheKey(r);this.cacheSpec=n,this.cacheKey=c}static async find(e,{immutable:r,check:s}={}){let a=new t(e.get("cacheFolder"),{configuration:e,immutable:r,check:s});return await a.setup(),a}static getCacheKey(e){let r=e.get("compressionLevel"),s=r!=="mixed"?`c${r}`:"";return{cacheKey:[UR,s].join(""),cacheSpec:s}}get mirrorCwd(){if(!this.configuration.get("enableMirror"))return null;let e=`${this.configuration.get("globalFolder")}/cache`;return e!==this.cwd?e:null}getVersionFilename(e){return`${nI(e)}-${this.cacheKey}.zip`}getChecksumFilename(e,r){let a=_v(r).hash.slice(0,10);return`${nI(e)}-${a}.zip`}isChecksumCompatible(e){if(e===null)return!1;let{cacheVersion:r,cacheSpec:s}=_v(e);if(r===null||r{let pe=new As,Be=J.join(vt.root,x8(e));return pe.mkdirSync(Be,{recursive:!0}),pe.writeJsonSync(J.join(Be,Er.manifest),{name:un(e),mocked:!0}),pe},E=async(pe,{isColdHit:Be,controlPath:Ce=null})=>{if(Ce===null&&c.unstablePackages?.has(e.locatorHash))return{isValid:!0,hash:null};let g=r&&!Be?_v(r).cacheKey:this.cacheKey,we=!c.skipIntegrityCheck||!r?`${g}/${await SQ(pe)}`:r;if(Ce!==null){let Ae=!c.skipIntegrityCheck||!r?`${this.cacheKey}/${await SQ(Ce)}`:r;if(we!==Ae)throw new jt(18,"The remote archive doesn't match the local checksum - has the local cache been corrupted?")}let ye=null;switch(r!==null&&we!==r&&(this.check?ye="throw":_v(r).cacheKey!==_v(we).cacheKey?ye="update":ye=this.configuration.get("checksumBehavior")),ye){case null:case"update":return{isValid:!0,hash:we};case"ignore":return{isValid:!0,hash:r};case"reset":return{isValid:!1,hash:r};default:case"throw":throw new jt(18,"The remote archive doesn't match the expected checksum")}},C=async pe=>{if(!n)throw new Error(`Cache check required but no loader configured for ${Yr(this.configuration,e)}`);let Be=await n(),Ce=Be.getRealPath();Be.saveAndClose(),await ce.chmodPromise(Ce,420);let g=await E(pe,{controlPath:Ce,isColdHit:!1});if(!g.isValid)throw new Error("Assertion failed: Expected a valid checksum");return g.hash},S=async()=>{if(f===null||!await ce.existsPromise(f)){let pe=await n(),Be=pe.getRealPath();return pe.saveAndClose(),{source:"loader",path:Be}}return{source:"mirror",path:f}},P=async()=>{if(!n)throw new Error(`Cache entry required but missing for ${Yr(this.configuration,e)}`);if(this.immutable)throw new jt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}`);let{path:pe,source:Be}=await S(),{hash:Ce}=await E(pe,{isColdHit:!0}),g=this.getLocatorPath(e,Ce),we=[];Be!=="mirror"&&f!==null&&we.push(async()=>{let Ae=`${f}${this.cacheId}`;await ce.copyFilePromise(pe,Ae,NG.default.constants.COPYFILE_FICLONE),await ce.chmodPromise(Ae,420),await ce.renamePromise(Ae,f)}),(!c.mirrorWriteOnly||f===null)&&we.push(async()=>{let Ae=`${g}${this.cacheId}`;await ce.copyFilePromise(pe,Ae,NG.default.constants.COPYFILE_FICLONE),await ce.chmodPromise(Ae,420),await ce.renamePromise(Ae,g)});let ye=c.mirrorWriteOnly?f??g:g;return await Promise.all(we.map(Ae=>Ae())),[!1,ye,Ce]},I=async()=>{let Be=(async()=>{let Ce=c.unstablePackages?.has(e.locatorHash),g=Ce||!r||this.isChecksumCompatible(r)?this.getLocatorPath(e,r):null,we=g!==null?this.markedFiles.has(g)||await p.existsPromise(g):!1,ye=!!c.mockedPackages?.has(e.locatorHash)&&(!this.check||!we),Ae=ye||we,se=Ae?s:a;if(se&&se(),Ae){let Z=null,De=g;if(!ye)if(this.check)Z=await C(De);else{let Re=await E(De,{isColdHit:!1});if(Re.isValid)Z=Re.hash;else return P()}return[ye,De,Z]}else{if(this.immutable&&Ce)throw new jt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}; consider defining ${he.pretty(this.configuration,"supportedArchitectures",he.Type.CODE)} to cache packages for multiple systems`);return P()}})();this.mutexes.set(e.locatorHash,Be);try{return await Be}finally{this.mutexes.delete(e.locatorHash)}};for(let pe;pe=this.mutexes.get(e.locatorHash);)await pe;let[R,N,U]=await I();R||this.markedFiles.add(N);let W=()=>this.refCountedZipFsCache.addOrCreate(N,()=>R?h():new As(N,{baseFs:p,readOnly:!0})),ee,ie=new oE(()=>W4(()=>(ee=W(),ee.value),pe=>`Failed to open the cache entry for ${Yr(this.configuration,e)}: ${pe}`),J),ue=new _f(N,{baseFs:ie,pathUtils:J}),le=()=>{ee?.release()},me=c.unstablePackages?.has(e.locatorHash)?null:U;return[ue,le,me]}},zot=/^(?:(?(?[0-9]+)(?.*))\/)?(?.*)$/});var _R,pde=Xe(()=>{_R=(r=>(r[r.SCRIPT=0]="SCRIPT",r[r.SHELLCODE=1]="SHELLCODE",r))(_R||{})});var Xot,KI,MG=Xe(()=>{Dt();wc();Rp();Wo();Xot=[[/^(git(?:\+(?:https|ssh))?:\/\/.*(?:\.git)?)#(.*)$/,(t,e,r,s)=>`${r}#commit=${s}`],[/^https:\/\/((?:[^/]+?)@)?codeload\.github\.com\/([^/]+\/[^/]+)\/tar\.gz\/([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https:\/\/((?:[^/]+?)@)?github\.com\/([^/]+\/[^/]+?)(?:\.git)?#([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https?:\/\/[^/]+\/(?:[^/]+\/)*(?:@.+(?:\/|(?:%2f)))?([^/]+)\/(?:-|download)\/\1-[^/]+\.tgz(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.pkg\.github\.com\/download\/(?:@[^/]+)\/(?:[^/]+)\/(?:[^/]+)\/(?:[0-9a-f]+)(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.fontawesome\.com\/(?:@[^/]+)\/([^/]+)\/-\/([^/]+)\/\1-\2.tgz(?:#|$)/,t=>`npm:${t}`],[/^https?:\/\/[^/]+\/.*\/(@[^/]+)\/([^/]+)\/-\/\1\/\2-(?:[.\d\w-]+)\.tgz(?:#|$)/,(t,e)=>kQ({protocol:"npm:",source:null,selector:t,params:{__archiveUrl:e}})],[/^[^/]+\.tgz#[0-9a-f]+$/,t=>`npm:${t}`]],KI=class{constructor(e){this.resolver=e;this.resolutions=null}async setup(e,{report:r}){let s=J.join(e.cwd,Er.lockfile);if(!ce.existsSync(s))return;let a=await ce.readFilePromise(s,"utf8"),n=ls(a);if(Object.hasOwn(n,"__metadata"))return;let c=this.resolutions=new Map;for(let f of Object.keys(n)){let p=HB(f);if(!p){r.reportWarning(14,`Failed to parse the string "${f}" into a proper descriptor`);continue}let h=cl(p.range)?On(p,`npm:${p.range}`):p,{version:E,resolved:C}=n[f];if(!C)continue;let S;for(let[I,R]of Xot){let N=C.match(I);if(N){S=R(E,...N);break}}if(!S){r.reportWarning(14,`${ni(e.configuration,h)}: Only some patterns can be imported from legacy lockfiles (not "${C}")`);continue}let P=h;try{let I=em(h.range),R=HB(I.selector,!0);R&&(P=R)}catch{}c.set(h.descriptorHash,Ws(P,S))}}supportsDescriptor(e,r){return this.resolutions?this.resolutions.has(e.descriptorHash):!1}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!this.resolutions)throw new Error("Assertion failed: The resolution store should have been setup");let a=this.resolutions.get(e.descriptorHash);if(!a)throw new Error("Assertion failed: The resolution should have been registered");let n=S8(a),c=s.project.configuration.normalizeDependency(n);return await this.resolver.getCandidates(c,r,s)}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}}});var lA,hde=Xe(()=>{Tc();Ev();xc();lA=class extends Ao{constructor({configuration:r,stdout:s,suggestInstall:a=!0}){super();this.errorCount=0;RB(this,{configuration:r}),this.configuration=r,this.stdout=s,this.suggestInstall=a}static async start(r,s){let a=new this(r);try{await s(a)}catch(n){a.reportExceptionOnce(n)}finally{await a.finalize()}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(r){}reportCacheMiss(r){}startSectionSync(r,s){return s()}async startSectionPromise(r,s){return await s()}startTimerSync(r,s,a){return(typeof s=="function"?s:a)()}async startTimerPromise(r,s,a){return await(typeof s=="function"?s:a)()}reportSeparator(){}reportInfo(r,s){}reportWarning(r,s){}reportError(r,s){this.errorCount+=1,this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} ${this.formatNameWithHyperlink(r)}: ${s} +`)}reportProgress(r){return{...Promise.resolve().then(async()=>{for await(let{}of r);}),stop:()=>{}}}reportJson(r){}reportFold(r,s){}async finalize(){this.errorCount>0&&(this.stdout.write(` +`),this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} Errors happened when preparing the environment required to run this command. +`),this.suggestInstall&&this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} This might be caused by packages being missing from the lockfile, in which case running "yarn install" might help. +`))}formatNameWithHyperlink(r){return Wj(r,{configuration:this.configuration,json:!1})}}});var zI,UG=Xe(()=>{Wo();zI=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return!!(r.project.storedResolutions.get(e.descriptorHash)||r.project.originalPackages.has(bQ(e).locatorHash))}supportsLocator(e,r){return!!(r.project.originalPackages.has(e.locatorHash)&&!r.project.lockfileNeedsRefresh)}shouldPersistResolution(e,r){throw new Error("The shouldPersistResolution method shouldn't be called on the lockfile resolver, which would always answer yes")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){let a=s.project.storedResolutions.get(e.descriptorHash);if(a){let c=s.project.originalPackages.get(a);if(c)return[c]}let n=s.project.originalPackages.get(bQ(e).locatorHash);if(n)return[n];throw new Error("Resolution expected from the lockfile data")}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.originalPackages.get(e.locatorHash);if(!s)throw new Error("The lockfile resolver isn't meant to resolve packages - they should already have been stored into a cache");return s}}});function Kp(){}function Zot(t,e,r,s,a){for(var n=0,c=e.length,f=0,p=0;nP.length?R:P}),h.value=t.join(E)}else h.value=t.join(r.slice(f,f+h.count));f+=h.count,h.added||(p+=h.count)}}var S=e[c-1];return c>1&&typeof S.value=="string"&&(S.added||S.removed)&&t.equals("",S.value)&&(e[c-2].value+=S.value,e.pop()),e}function $ot(t){return{newPos:t.newPos,components:t.components.slice(0)}}function eat(t,e){if(typeof t=="function")e.callback=t;else if(t)for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}function mde(t,e,r){return r=eat(r,{ignoreWhitespace:!0}),qG.diff(t,e,r)}function tat(t,e,r){return WG.diff(t,e,r)}function HR(t){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?HR=function(e){return typeof e}:HR=function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},HR(t)}function _G(t){return iat(t)||sat(t)||oat(t)||aat()}function iat(t){if(Array.isArray(t))return HG(t)}function sat(t){if(typeof Symbol<"u"&&Symbol.iterator in Object(t))return Array.from(t)}function oat(t,e){if(t){if(typeof t=="string")return HG(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return HG(t,e)}}function HG(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,s=new Array(e);r"u"&&(c.context=4);var f=tat(r,s,c);if(!f)return;f.push({value:"",lines:[]});function p(U){return U.map(function(W){return" "+W})}for(var h=[],E=0,C=0,S=[],P=1,I=1,R=function(W){var ee=f[W],ie=ee.lines||ee.value.replace(/\n$/,"").split(` +`);if(ee.lines=ie,ee.added||ee.removed){var ue;if(!E){var le=f[W-1];E=P,C=I,le&&(S=c.context>0?p(le.lines.slice(-c.context)):[],E-=S.length,C-=S.length)}(ue=S).push.apply(ue,_G(ie.map(function(Ae){return(ee.added?"+":"-")+Ae}))),ee.added?I+=ie.length:P+=ie.length}else{if(E)if(ie.length<=c.context*2&&W=f.length-2&&ie.length<=c.context){var g=/\n$/.test(r),we=/\n$/.test(s),ye=ie.length==0&&S.length>Ce.oldLines;!g&&ye&&r.length>0&&S.splice(Ce.oldLines,0,"\\ No newline at end of file"),(!g&&!ye||!we)&&S.push("\\ No newline at end of file")}h.push(Ce),E=0,C=0,S=[]}P+=ie.length,I+=ie.length}},N=0;N{Kp.prototype={diff:function(e,r){var s=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},a=s.callback;typeof s=="function"&&(a=s,s={}),this.options=s;var n=this;function c(R){return a?(setTimeout(function(){a(void 0,R)},0),!0):R}e=this.castInput(e),r=this.castInput(r),e=this.removeEmpty(this.tokenize(e)),r=this.removeEmpty(this.tokenize(r));var f=r.length,p=e.length,h=1,E=f+p;s.maxEditLength&&(E=Math.min(E,s.maxEditLength));var C=[{newPos:-1,components:[]}],S=this.extractCommon(C[0],r,e,0);if(C[0].newPos+1>=f&&S+1>=p)return c([{value:this.join(r),count:r.length}]);function P(){for(var R=-1*h;R<=h;R+=2){var N=void 0,U=C[R-1],W=C[R+1],ee=(W?W.newPos:0)-R;U&&(C[R-1]=void 0);var ie=U&&U.newPos+1=f&&ee+1>=p)return c(Zot(n,N.components,r,e,n.useLongestToken));C[R]=N}h++}if(a)(function R(){setTimeout(function(){if(h>E)return a();P()||R()},0)})();else for(;h<=E;){var I=P();if(I)return I}},pushComponent:function(e,r,s){var a=e[e.length-1];a&&a.added===r&&a.removed===s?e[e.length-1]={count:a.count+1,added:r,removed:s}:e.push({count:1,added:r,removed:s})},extractCommon:function(e,r,s,a){for(var n=r.length,c=s.length,f=e.newPos,p=f-a,h=0;f+1"u"?r:c}:s;return typeof t=="string"?t:JSON.stringify(jG(t,null,null,a),a," ")};Hv.equals=function(t,e){return Kp.prototype.equals.call(Hv,t.replace(/,([\r\n])/g,"$1"),e.replace(/,([\r\n])/g,"$1"))};GG=new Kp;GG.tokenize=function(t){return t.slice()};GG.join=GG.removeEmpty=function(t){return t}});var jR,Ede=Xe(()=>{Tc();jR=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return this.resolver.supportsDescriptor(e,r)}supportsLocator(e,r){return this.resolver.supportsLocator(e,r)}shouldPersistResolution(e,r){return this.resolver.shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.resolver.bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async getSatisfying(e,r,s,a){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async resolve(e,r){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}}});var ki,VG=Xe(()=>{Tc();ki=class extends Ao{reportCacheHit(e){}reportCacheMiss(e){}startSectionSync(e,r){return r()}async startSectionPromise(e,r){return await r()}startTimerSync(e,r,s){return(typeof r=="function"?r:s)()}async startTimerPromise(e,r,s){return await(typeof r=="function"?r:s)()}reportSeparator(){}reportInfo(e,r){}reportWarning(e,r){}reportError(e,r){}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(let{}of e);}),stop:()=>{}}}reportJson(e){}reportFold(e,r){}async finalize(){}}});var Ide,XI,JG=Xe(()=>{Dt();Ide=ut(BQ());oI();tm();xc();I0();Rp();Wo();XI=class{constructor(e,{project:r}){this.workspacesCwds=new Set;this.project=r,this.cwd=e}async setup(){this.manifest=await Ut.tryFind(this.cwd)??new Ut,this.relativeCwd=J.relative(this.project.cwd,this.cwd)||vt.dot;let e=this.manifest.name?this.manifest.name:Da(null,`${this.computeCandidateName()}-${us(this.relativeCwd).substring(0,6)}`);this.anchoredDescriptor=On(e,`${Ei.protocol}${this.relativeCwd}`),this.anchoredLocator=Ws(e,`${Ei.protocol}${this.relativeCwd}`);let r=this.manifest.workspaceDefinitions.map(({pattern:a})=>a);if(r.length===0)return;let s=await(0,Ide.default)(r,{cwd:fe.fromPortablePath(this.cwd),onlyDirectories:!0,ignore:["**/node_modules","**/.git","**/.yarn"]});s.sort(),await s.reduce(async(a,n)=>{let c=J.resolve(this.cwd,fe.toPortablePath(n)),f=await ce.existsPromise(J.join(c,"package.json"));await a,f&&this.workspacesCwds.add(c)},Promise.resolve())}get anchoredPackage(){let e=this.project.storedPackages.get(this.anchoredLocator.locatorHash);if(!e)throw new Error(`Assertion failed: Expected workspace ${GB(this.project.configuration,this)} (${Ht(this.project.configuration,J.join(this.cwd,Er.manifest),ht.PATH)}) to have been resolved. Run "yarn install" to update the lockfile`);return e}accepts(e){let r=e.indexOf(":"),s=r!==-1?e.slice(0,r+1):null,a=r!==-1?e.slice(r+1):e;if(s===Ei.protocol&&J.normalize(a)===this.relativeCwd||s===Ei.protocol&&(a==="*"||a==="^"||a==="~"))return!0;let n=cl(a);return n?s===Ei.protocol?n.test(this.manifest.version??"0.0.0"):this.project.configuration.get("enableTransparentWorkspaces")&&this.manifest.version!==null?n.test(this.manifest.version):!1:!1}computeCandidateName(){return this.cwd===this.project.cwd?"root-workspace":`${J.basename(this.cwd)}`||"unnamed-workspace"}getRecursiveWorkspaceDependencies({dependencies:e=Ut.hardDependencies}={}){let r=new Set,s=a=>{for(let n of e)for(let c of a.manifest[n].values()){let f=this.project.tryWorkspaceByDescriptor(c);f===null||r.has(f)||(r.add(f),s(f))}};return s(this),r}getRecursiveWorkspaceDependents({dependencies:e=Ut.hardDependencies}={}){let r=new Set,s=a=>{for(let n of this.project.workspaces)e.some(f=>[...n.manifest[f].values()].some(p=>{let h=this.project.tryWorkspaceByDescriptor(p);return h!==null&&_B(h.anchoredLocator,a.anchoredLocator)}))&&!r.has(n)&&(r.add(n),s(n))};return s(this),r}getRecursiveWorkspaceChildren(){let e=new Set([this]);for(let r of e)for(let s of r.workspacesCwds){let a=this.project.workspacesByCwd.get(s);a&&e.add(a)}return e.delete(this),Array.from(e)}async persistManifest(){let e={};this.manifest.exportTo(e);let r=J.join(this.cwd,Ut.fileName),s=`${JSON.stringify(e,null,this.manifest.indent)} +`;await ce.changeFilePromise(r,s,{automaticNewlines:!0}),this.manifest.raw=e}}});function hat({project:t,allDescriptors:e,allResolutions:r,allPackages:s,accessibleLocators:a=new Set,optionalBuilds:n=new Set,peerRequirements:c=new Map,peerWarnings:f=[],peerRequirementNodes:p=new Map,volatileDescriptors:h=new Set}){let E=new Map,C=[],S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=new Map(t.workspaces.map(le=>{let me=le.anchoredLocator.locatorHash,pe=s.get(me);if(typeof pe>"u")throw new Error("Assertion failed: The workspace should have an associated package");return[me,LB(pe)]})),W=()=>{let le=ce.mktempSync(),me=J.join(le,"stacktrace.log"),pe=String(C.length+1).length,Be=C.map((Ce,g)=>`${`${g+1}.`.padStart(pe," ")} ${ll(Ce)} +`).join("");throw ce.writeFileSync(me,Be),ce.detachTemp(le),new jt(45,`Encountered a stack overflow when resolving peer dependencies; cf ${fe.fromPortablePath(me)}`)},ee=le=>{let me=r.get(le.descriptorHash);if(typeof me>"u")throw new Error("Assertion failed: The resolution should have been registered");let pe=s.get(me);if(!pe)throw new Error("Assertion failed: The package could not be found");return pe},ie=(le,me,pe,{top:Be,optional:Ce})=>{C.length>1e3&&W(),C.push(me);let g=ue(le,me,pe,{top:Be,optional:Ce});return C.pop(),g},ue=(le,me,pe,{top:Be,optional:Ce})=>{if(Ce||n.delete(me.locatorHash),a.has(me.locatorHash))return;a.add(me.locatorHash);let g=s.get(me.locatorHash);if(!g)throw new Error(`Assertion failed: The package (${Yr(t.configuration,me)}) should have been registered`);let we=new Set,ye=new Map,Ae=[],se=[],Z=[],De=[];for(let Re of Array.from(g.dependencies.values())){if(g.peerDependencies.has(Re.identHash)&&g.locatorHash!==Be)continue;if(kp(Re))throw new Error("Assertion failed: Virtual packages shouldn't be encountered when virtualizing a branch");h.delete(Re.descriptorHash);let mt=Ce;if(!mt){let ke=g.dependenciesMeta.get(un(Re));if(typeof ke<"u"){let it=ke.get(null);typeof it<"u"&&it.optional&&(mt=!0)}}let j=r.get(Re.descriptorHash);if(!j)throw new Error(`Assertion failed: The resolution (${ni(t.configuration,Re)}) should have been registered`);let rt=U.get(j)||s.get(j);if(!rt)throw new Error(`Assertion failed: The package (${j}, resolved from ${ni(t.configuration,Re)}) should have been registered`);if(rt.peerDependencies.size===0){ie(Re,rt,new Map,{top:Be,optional:mt});continue}let Fe,Ne,Pe=new Set,Ve=new Map;Ae.push(()=>{Fe=b8(Re,me.locatorHash),Ne=P8(rt,me.locatorHash),g.dependencies.set(Re.identHash,Fe),r.set(Fe.descriptorHash,Ne.locatorHash),e.set(Fe.descriptorHash,Fe),s.set(Ne.locatorHash,Ne),bp(R,Ne.locatorHash).add(Fe.descriptorHash),we.add(Ne.locatorHash)}),se.push(()=>{N.set(Ne.locatorHash,Ve);for(let ke of Ne.peerDependencies.values()){let Ue=Yl(ye,ke.identHash,()=>{let x=pe.get(ke.identHash)??null,w=g.dependencies.get(ke.identHash);return!w&&UB(me,ke)&&(le.identHash===me.identHash?w=le:(w=On(me,le.range),e.set(w.descriptorHash,w),r.set(w.descriptorHash,me.locatorHash),h.delete(w.descriptorHash),x=null)),w||(w=On(ke,"missing:")),{subject:me,ident:ke,provided:w,root:!x,requests:new Map,hash:`p${us(me.locatorHash,ke.identHash).slice(0,6)}`}}).provided;if(Ue.range==="missing:"&&Ne.dependencies.has(ke.identHash)){Ne.peerDependencies.delete(ke.identHash);continue}if(Ve.set(ke.identHash,{requester:Ne,descriptor:ke,meta:Ne.peerDependenciesMeta.get(un(ke)),children:new Map}),Ne.dependencies.set(ke.identHash,Ue),kp(Ue)){let x=r.get(Ue.descriptorHash);bp(I,x).add(Ne.locatorHash)}S.set(Ue.identHash,Ue),Ue.range==="missing:"&&Pe.add(Ue.identHash)}Ne.dependencies=new Map(qs(Ne.dependencies,([ke,it])=>un(it)))}),Z.push(()=>{if(!s.has(Ne.locatorHash))return;let ke=E.get(rt.locatorHash);typeof ke=="number"&&ke>=2&&W();let it=E.get(rt.locatorHash),Ue=typeof it<"u"?it+1:1;E.set(rt.locatorHash,Ue),ie(Fe,Ne,Ve,{top:Be,optional:mt}),E.set(rt.locatorHash,Ue-1)}),De.push(()=>{let ke=r.get(Fe.descriptorHash);if(typeof ke>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let it=N.get(ke);if(typeof it>"u")throw new Error("Assertion failed: Expected the peer requests to be registered");for(let Ue of ye.values()){let x=it.get(Ue.ident.identHash);x&&(Ue.requests.set(Fe.descriptorHash,x),p.set(Ue.hash,Ue),Ue.root||pe.get(Ue.ident.identHash)?.children.set(Fe.descriptorHash,x))}if(s.has(Ne.locatorHash))for(let Ue of Pe)Ne.dependencies.delete(Ue)})}for(let Re of[...Ae,...se])Re();for(let Re of we){we.delete(Re);let mt=s.get(Re),j=us(rI(mt).locatorHash,...Array.from(mt.dependencies.values(),Pe=>{let Ve=Pe.range!=="missing:"?r.get(Pe.descriptorHash):"missing:";if(typeof Ve>"u")throw new Error(`Assertion failed: Expected the resolution for ${ni(t.configuration,Pe)} to have been registered`);return Ve===Be?`${Ve} (top)`:Ve})),rt=P.get(j);if(typeof rt>"u"){P.set(j,mt);continue}let Fe=bp(R,rt.locatorHash);for(let Pe of R.get(mt.locatorHash)??[])r.set(Pe,rt.locatorHash),Fe.add(Pe);s.delete(mt.locatorHash),a.delete(mt.locatorHash),we.delete(mt.locatorHash);let Ne=I.get(mt.locatorHash);if(Ne!==void 0){let Pe=bp(I,rt.locatorHash);for(let Ve of Ne)Pe.add(Ve),we.add(Ve)}}for(let Re of[...Z,...De])Re()};for(let le of t.workspaces){let me=le.anchoredLocator;h.delete(le.anchoredDescriptor.descriptorHash),ie(le.anchoredDescriptor,me,new Map,{top:me.locatorHash,optional:!1})}for(let le of p.values()){if(!le.root)continue;let me=s.get(le.subject.locatorHash);if(typeof me>"u")continue;for(let Be of le.requests.values()){let Ce=`p${us(le.subject.locatorHash,un(le.ident),Be.requester.locatorHash).slice(0,6)}`;c.set(Ce,{subject:le.subject.locatorHash,requested:le.ident,rootRequester:Be.requester.locatorHash,allRequesters:Array.from(qB(Be),g=>g.requester.locatorHash)})}let pe=[...qB(le)];if(le.provided.range!=="missing:"){let Be=ee(le.provided),Ce=Be.version??"0.0.0",g=ye=>{if(ye.startsWith(Ei.protocol)){if(!t.tryWorkspaceByLocator(Be))return null;ye=ye.slice(Ei.protocol.length),(ye==="^"||ye==="~")&&(ye="*")}return ye},we=!0;for(let ye of pe){let Ae=g(ye.descriptor.range);if(Ae===null){we=!1;continue}if(!Zf(Ce,Ae)){we=!1;let se=`p${us(le.subject.locatorHash,un(le.ident),ye.requester.locatorHash).slice(0,6)}`;f.push({type:1,subject:me,requested:le.ident,requester:ye.requester,version:Ce,hash:se,requirementCount:pe.length})}}if(!we){let ye=pe.map(Ae=>g(Ae.descriptor.range));f.push({type:3,node:le,range:ye.includes(null)?null:Q8(ye),hash:le.hash})}}else{let Be=!0;for(let Ce of pe)if(!Ce.meta?.optional){Be=!1;let g=`p${us(le.subject.locatorHash,un(le.ident),Ce.requester.locatorHash).slice(0,6)}`;f.push({type:0,subject:me,requested:le.ident,requester:Ce.requester,hash:g})}Be||f.push({type:2,node:le,hash:le.hash})}}}function*gat(t){let e=new Map;if("children"in t)e.set(t,t);else for(let r of t.requests.values())e.set(r,r);for(let[r,s]of e){yield{request:r,root:s};for(let a of r.children.values())e.has(a)||e.set(a,s)}}function dat(t,e){let r=[],s=[],a=!1;for(let n of t.peerWarnings)if(!(n.type===1||n.type===0)){if(!t.tryWorkspaceByLocator(n.node.subject)){a=!0;continue}if(n.type===3){let c=t.storedResolutions.get(n.node.provided.descriptorHash);if(typeof c>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let f=t.storedPackages.get(c);if(typeof f>"u")throw new Error("Assertion failed: Expected the package to be registered");let p=p0(gat(n.node),({request:C,root:S})=>Zf(f.version??"0.0.0",C.descriptor.range)?p0.skip:C===S?$i(t.configuration,C.requester):`${$i(t.configuration,C.requester)} (via ${$i(t.configuration,S.requester)})`),h=[...qB(n.node)].length>1?"and other dependencies request":"requests",E=n.range?iI(t.configuration,n.range):Ht(t.configuration,"but they have non-overlapping ranges!","redBright");r.push(`${$i(t.configuration,n.node.ident)} is listed by your project with version ${jB(t.configuration,f.version??"0.0.0")} (${Ht(t.configuration,n.hash,ht.CODE)}), which doesn't satisfy what ${p} ${h} (${E}).`)}if(n.type===2){let c=n.node.requests.size>1?" and other dependencies":"";s.push(`${Yr(t.configuration,n.node.subject)} doesn't provide ${$i(t.configuration,n.node.ident)} (${Ht(t.configuration,n.hash,ht.CODE)}), requested by ${$i(t.configuration,n.node.requests.values().next().value.requester)}${c}.`)}}e.startSectionSync({reportFooter:()=>{e.reportWarning(86,`Some peer dependencies are incorrectly met by your project; run ${Ht(t.configuration,"yarn explain peer-requirements ",ht.CODE)} for details, where ${Ht(t.configuration,"",ht.CODE)} is the six-letter p-prefixed code.`)},skipIfEmpty:!0},()=>{for(let n of qs(r,c=>JE.default(c)))e.reportWarning(60,n);for(let n of qs(s,c=>JE.default(c)))e.reportWarning(2,n)}),a&&e.reportWarning(86,`Some peer dependencies are incorrectly met by dependencies; run ${Ht(t.configuration,"yarn explain peer-requirements",ht.CODE)} for details.`)}var GR,qR,Bde,XG,zG,ZG,WR,cat,uat,Cde,fat,Aat,pat,$l,KG,YR,wde,Tt,vde=Xe(()=>{Dt();Dt();wc();Yt();GR=Ie("crypto");YG();ql();qR=ut(Ld()),Bde=ut(Ai()),XG=Ie("util"),zG=ut(Ie("v8")),ZG=ut(Ie("zlib"));LG();av();MG();UG();oI();F8();Tc();Ede();Ev();VG();tm();JG();LQ();xc();I0();Pc();gT();zj();Rp();Wo();WR=YE(process.env.YARN_LOCKFILE_VERSION_OVERRIDE??8),cat=3,uat=/ *, */g,Cde=/\/$/,fat=32,Aat=(0,XG.promisify)(ZG.default.gzip),pat=(0,XG.promisify)(ZG.default.gunzip),$l=(r=>(r.UpdateLockfile="update-lockfile",r.SkipBuild="skip-build",r))($l||{}),KG={restoreLinkersCustomData:["linkersCustomData"],restoreResolutions:["accessibleLocators","conditionalLocators","disabledLocators","optionalBuilds","storedDescriptors","storedResolutions","storedPackages","lockFileChecksum"],restoreBuildState:["skippedBuilds","storedBuildState"]},YR=(a=>(a[a.NotProvided=0]="NotProvided",a[a.NotCompatible=1]="NotCompatible",a[a.NodeNotProvided=2]="NodeNotProvided",a[a.NodeNotCompatible=3]="NodeNotCompatible",a))(YR||{}),wde=t=>us(`${cat}`,t),Tt=class t{constructor(e,{configuration:r}){this.resolutionAliases=new Map;this.workspaces=[];this.workspacesByCwd=new Map;this.workspacesByIdent=new Map;this.storedResolutions=new Map;this.storedDescriptors=new Map;this.storedPackages=new Map;this.storedChecksums=new Map;this.storedBuildState=new Map;this.accessibleLocators=new Set;this.conditionalLocators=new Set;this.disabledLocators=new Set;this.originalPackages=new Map;this.optionalBuilds=new Set;this.skippedBuilds=new Set;this.lockfileLastVersion=null;this.lockfileNeedsRefresh=!1;this.peerRequirements=new Map;this.peerWarnings=[];this.peerRequirementNodes=new Map;this.linkersCustomData=new Map;this.lockFileChecksum=null;this.installStateChecksum=null;this.configuration=r,this.cwd=e}static async find(e,r){if(!e.projectCwd)throw new nt(`No project found in ${r}`);let s=e.projectCwd,a=r,n=null;for(;n!==e.projectCwd;){if(n=a,ce.existsSync(J.join(n,Er.manifest))){s=n;break}a=J.dirname(n)}let c=new t(e.projectCwd,{configuration:e});ze.telemetry?.reportProject(c.cwd),await c.setupResolutions(),await c.setupWorkspaces(),ze.telemetry?.reportWorkspaceCount(c.workspaces.length),ze.telemetry?.reportDependencyCount(c.workspaces.reduce((I,R)=>I+R.manifest.dependencies.size+R.manifest.devDependencies.size,0));let f=c.tryWorkspaceByCwd(s);if(f)return{project:c,workspace:f,locator:f.anchoredLocator};let p=await c.findLocatorForLocation(`${s}/`,{strict:!0});if(p)return{project:c,locator:p,workspace:null};let h=Ht(e,c.cwd,ht.PATH),E=Ht(e,J.relative(c.cwd,s),ht.PATH),C=`- If ${h} isn't intended to be a project, remove any yarn.lock and/or package.json file there.`,S=`- If ${h} is intended to be a project, it might be that you forgot to list ${E} in its workspace configuration.`,P=`- Finally, if ${h} is fine and you intend ${E} to be treated as a completely separate project (not even a workspace), create an empty yarn.lock file in it.`;throw new nt(`The nearest package directory (${Ht(e,s,ht.PATH)}) doesn't seem to be part of the project declared in ${Ht(e,c.cwd,ht.PATH)}. + +${[C,S,P].join(` +`)}`)}async setupResolutions(){this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.lockFileChecksum=null;let e=J.join(this.cwd,Er.lockfile),r=this.configuration.get("defaultLanguageName");if(ce.existsSync(e)){let s=await ce.readFilePromise(e,"utf8");this.lockFileChecksum=wde(s);let a=ls(s);if(a.__metadata){let n=a.__metadata.version,c=a.__metadata.cacheKey;this.lockfileLastVersion=n,this.lockfileNeedsRefresh=n"u")throw new Error(`Assertion failed: Expected the lockfile entry to have a resolution field (${f})`);let h=Qp(p.resolution,!0),E=new Ut;E.load(p,{yamlCompatibilityMode:!0});let C=E.version,S=E.languageName||r,P=p.linkType.toUpperCase(),I=p.conditions??null,R=E.dependencies,N=E.peerDependencies,U=E.dependenciesMeta,W=E.peerDependenciesMeta,ee=E.bin;if(p.checksum!=null){let ue=typeof c<"u"&&!p.checksum.includes("/")?`${c}/${p.checksum}`:p.checksum;this.storedChecksums.set(h.locatorHash,ue)}let ie={...h,version:C,languageName:S,linkType:P,conditions:I,dependencies:R,peerDependencies:N,dependenciesMeta:U,peerDependenciesMeta:W,bin:ee};this.originalPackages.set(ie.locatorHash,ie);for(let ue of f.split(uat)){let le=C0(ue);n<=6&&(le=this.configuration.normalizeDependency(le),le=On(le,le.range.replace(/^patch:[^@]+@(?!npm(:|%3A))/,"$1npm%3A"))),this.storedDescriptors.set(le.descriptorHash,le),this.storedResolutions.set(le.descriptorHash,h.locatorHash)}}}else s.includes("yarn lockfile v1")&&(this.lockfileLastVersion=-1)}}async setupWorkspaces(){this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map;let e=new Set,r=(0,qR.default)(4),s=async(a,n)=>{if(e.has(n))return a;e.add(n);let c=new XI(n,{project:this});await r(()=>c.setup());let f=a.then(()=>{this.addWorkspace(c)});return Array.from(c.workspacesCwds).reduce(s,f)};await s(Promise.resolve(),this.cwd)}addWorkspace(e){let r=this.workspacesByIdent.get(e.anchoredLocator.identHash);if(typeof r<"u")throw new Error(`Duplicate workspace name ${$i(this.configuration,e.anchoredLocator)}: ${fe.fromPortablePath(e.cwd)} conflicts with ${fe.fromPortablePath(r.cwd)}`);this.workspaces.push(e),this.workspacesByCwd.set(e.cwd,e),this.workspacesByIdent.set(e.anchoredLocator.identHash,e)}get topLevelWorkspace(){return this.getWorkspaceByCwd(this.cwd)}tryWorkspaceByCwd(e){J.isAbsolute(e)||(e=J.resolve(this.cwd,e)),e=J.normalize(e).replace(/\/+$/,"");let r=this.workspacesByCwd.get(e);return r||null}getWorkspaceByCwd(e){let r=this.tryWorkspaceByCwd(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByFilePath(e){let r=null;for(let s of this.workspaces)J.relative(s.cwd,e).startsWith("../")||r&&r.cwd.length>=s.cwd.length||(r=s);return r||null}getWorkspaceByFilePath(e){let r=this.tryWorkspaceByFilePath(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByIdent(e){let r=this.workspacesByIdent.get(e.identHash);return typeof r>"u"?null:r}getWorkspaceByIdent(e){let r=this.tryWorkspaceByIdent(e);if(!r)throw new Error(`Workspace not found (${$i(this.configuration,e)})`);return r}tryWorkspaceByDescriptor(e){if(e.range.startsWith(Ei.protocol)){let s=e.range.slice(Ei.protocol.length);if(s!=="^"&&s!=="~"&&s!=="*"&&!cl(s))return this.tryWorkspaceByCwd(s)}let r=this.tryWorkspaceByIdent(e);return r===null||(kp(e)&&(e=MB(e)),!r.accepts(e.range))?null:r}getWorkspaceByDescriptor(e){let r=this.tryWorkspaceByDescriptor(e);if(r===null)throw new Error(`Workspace not found (${ni(this.configuration,e)})`);return r}tryWorkspaceByLocator(e){let r=this.tryWorkspaceByIdent(e);return r===null||(Gu(e)&&(e=rI(e)),r.anchoredLocator.locatorHash!==e.locatorHash)?null:r}getWorkspaceByLocator(e){let r=this.tryWorkspaceByLocator(e);if(!r)throw new Error(`Workspace not found (${Yr(this.configuration,e)})`);return r}deleteDescriptor(e){this.storedResolutions.delete(e),this.storedDescriptors.delete(e)}deleteLocator(e){this.originalPackages.delete(e),this.storedPackages.delete(e),this.accessibleLocators.delete(e)}forgetResolution(e){if("descriptorHash"in e){let r=this.storedResolutions.get(e.descriptorHash);this.deleteDescriptor(e.descriptorHash);let s=new Set(this.storedResolutions.values());typeof r<"u"&&!s.has(r)&&this.deleteLocator(r)}if("locatorHash"in e){this.deleteLocator(e.locatorHash);for(let[r,s]of this.storedResolutions)s===e.locatorHash&&this.deleteDescriptor(r)}}forgetTransientResolutions(){let e=this.configuration.makeResolver(),r=new Map;for(let[s,a]of this.storedResolutions.entries()){let n=r.get(a);n||r.set(a,n=new Set),n.add(s)}for(let s of this.originalPackages.values()){let a;try{a=e.shouldPersistResolution(s,{project:this,resolver:e})}catch{a=!1}if(!a){this.deleteLocator(s.locatorHash);let n=r.get(s.locatorHash);if(n){r.delete(s.locatorHash);for(let c of n)this.deleteDescriptor(c)}}}}forgetVirtualResolutions(){for(let e of this.storedPackages.values())for(let[r,s]of e.dependencies)kp(s)&&e.dependencies.set(r,MB(s))}getDependencyMeta(e,r){let s={},n=this.topLevelWorkspace.manifest.dependenciesMeta.get(un(e));if(!n)return s;let c=n.get(null);if(c&&Object.assign(s,c),r===null||!Bde.default.valid(r))return s;for(let[f,p]of n)f!==null&&f===r&&Object.assign(s,p);return s}async findLocatorForLocation(e,{strict:r=!1}={}){let s=new ki,a=this.configuration.getLinkers(),n={project:this,report:s};for(let c of a){let f=await c.findPackageLocator(e,n);if(f){if(r&&(await c.findPackageLocation(f,n)).replace(Cde,"")!==e.replace(Cde,""))continue;return f}}return null}async loadUserConfig(){let e=J.join(this.cwd,".pnp.cjs");await ce.existsPromise(e)&&Pp(e).setup();let r=J.join(this.cwd,"yarn.config.cjs");return await ce.existsPromise(r)?Pp(r):null}async preparePackage(e,{resolver:r,resolveOptions:s}){let a=await this.configuration.getPackageExtensions(),n=this.configuration.normalizePackage(e,{packageExtensions:a});for(let[c,f]of n.dependencies){let p=await this.configuration.reduceHook(E=>E.reduceDependency,f,this,n,f,{resolver:r,resolveOptions:s});if(!UB(f,p))throw new Error("Assertion failed: The descriptor ident cannot be changed through aliases");let h=r.bindDescriptor(p,n,s);n.dependencies.set(c,h)}return n}async resolveEverything(e){if(!this.workspacesByCwd||!this.workspacesByIdent)throw new Error("Workspaces must have been setup before calling this function");this.forgetVirtualResolutions();let r=new Map(this.originalPackages),s=[];e.lockfileOnly||this.forgetTransientResolutions();let a=e.resolver||this.configuration.makeResolver(),n=new KI(a);await n.setup(this,{report:e.report});let c=e.lockfileOnly?[new jR(a)]:[n,a],f=new rm([new zI(a),...c]),p=new rm([...c]),h=this.configuration.makeFetcher(),E=e.lockfileOnly?{project:this,report:e.report,resolver:f}:{project:this,report:e.report,resolver:f,fetchOptions:{project:this,cache:e.cache,checksums:this.storedChecksums,report:e.report,fetcher:h,cacheOptions:{mirrorWriteOnly:!0}}},C=new Map,S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=this.topLevelWorkspace.anchoredLocator,W=new Set,ee=[],ie=uj(),ue=this.configuration.getSupportedArchitectures();await e.report.startProgressPromise(Ao.progressViaTitle(),async se=>{let Z=async rt=>{let Fe=await qE(async()=>await f.resolve(rt,E),ke=>`${Yr(this.configuration,rt)}: ${ke}`);if(!_B(rt,Fe))throw new Error(`Assertion failed: The locator cannot be changed by the resolver (went from ${Yr(this.configuration,rt)} to ${Yr(this.configuration,Fe)})`);I.set(Fe.locatorHash,Fe),!r.delete(Fe.locatorHash)&&!this.tryWorkspaceByLocator(Fe)&&s.push(Fe);let Pe=await this.preparePackage(Fe,{resolver:f,resolveOptions:E}),Ve=Uu([...Pe.dependencies.values()].map(ke=>j(ke)));return ee.push(Ve),Ve.catch(()=>{}),S.set(Pe.locatorHash,Pe),Pe},De=async rt=>{let Fe=R.get(rt.locatorHash);if(typeof Fe<"u")return Fe;let Ne=Promise.resolve().then(()=>Z(rt));return R.set(rt.locatorHash,Ne),Ne},Re=async(rt,Fe)=>{let Ne=await j(Fe);return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,Ne.locatorHash),Ne},mt=async rt=>{se.setTitle(ni(this.configuration,rt));let Fe=this.resolutionAliases.get(rt.descriptorHash);if(typeof Fe<"u")return Re(rt,this.storedDescriptors.get(Fe));let Ne=f.getResolutionDependencies(rt,E),Pe=Object.fromEntries(await Uu(Object.entries(Ne).map(async([it,Ue])=>{let x=f.bindDescriptor(Ue,U,E),w=await j(x);return W.add(w.locatorHash),[it,w]}))),ke=(await qE(async()=>await f.getCandidates(rt,Pe,E),it=>`${ni(this.configuration,rt)}: ${it}`))[0];if(typeof ke>"u")throw new jt(82,`${ni(this.configuration,rt)}: No candidates found`);if(e.checkResolutions){let{locators:it}=await p.getSatisfying(rt,Pe,[ke],{...E,resolver:p});if(!it.find(Ue=>Ue.locatorHash===ke.locatorHash))throw new jt(78,`Invalid resolution ${FB(this.configuration,rt,ke)}`)}return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,ke.locatorHash),De(ke)},j=rt=>{let Fe=N.get(rt.descriptorHash);if(typeof Fe<"u")return Fe;C.set(rt.descriptorHash,rt);let Ne=Promise.resolve().then(()=>mt(rt));return N.set(rt.descriptorHash,Ne),Ne};for(let rt of this.workspaces){let Fe=rt.anchoredDescriptor;ee.push(j(Fe))}for(;ee.length>0;){let rt=[...ee];ee.length=0,await Uu(rt)}});let le=Wl(r.values(),se=>this.tryWorkspaceByLocator(se)?Wl.skip:se);if(s.length>0||le.length>0){let se=new Set(this.workspaces.flatMap(rt=>{let Fe=S.get(rt.anchoredLocator.locatorHash);if(!Fe)throw new Error("Assertion failed: The workspace should have been resolved");return Array.from(Fe.dependencies.values(),Ne=>{let Pe=P.get(Ne.descriptorHash);if(!Pe)throw new Error("Assertion failed: The resolution should have been registered");return Pe})})),Z=rt=>se.has(rt.locatorHash)?"0":"1",De=rt=>ll(rt),Re=qs(s,[Z,De]),mt=qs(le,[Z,De]),j=e.report.getRecommendedLength();Re.length>0&&e.report.reportInfo(85,`${Ht(this.configuration,"+",ht.ADDED)} ${$k(this.configuration,Re,j)}`),mt.length>0&&e.report.reportInfo(85,`${Ht(this.configuration,"-",ht.REMOVED)} ${$k(this.configuration,mt,j)}`)}let me=new Set(this.resolutionAliases.values()),pe=new Set(S.keys()),Be=new Set,Ce=new Map,g=[],we=new Map;hat({project:this,accessibleLocators:Be,volatileDescriptors:me,optionalBuilds:pe,peerRequirements:Ce,peerWarnings:g,peerRequirementNodes:we,allDescriptors:C,allResolutions:P,allPackages:S});for(let se of W)pe.delete(se);for(let se of me)C.delete(se),P.delete(se);let ye=new Set,Ae=new Set;for(let se of S.values())se.conditions!=null&&pe.has(se.locatorHash)&&(TQ(se,ue)||(TQ(se,ie)&&e.report.reportWarningOnce(77,`${Yr(this.configuration,se)}: Your current architecture (${process.platform}-${process.arch}) is supported by this package, but is missing from the ${Ht(this.configuration,"supportedArchitectures",ht.SETTING)} setting`),Ae.add(se.locatorHash)),ye.add(se.locatorHash));this.storedResolutions=P,this.storedDescriptors=C,this.storedPackages=S,this.accessibleLocators=Be,this.conditionalLocators=ye,this.disabledLocators=Ae,this.originalPackages=I,this.optionalBuilds=pe,this.peerRequirements=Ce,this.peerWarnings=g,this.peerRequirementNodes=we}async fetchEverything({cache:e,report:r,fetcher:s,mode:a,persistProject:n=!0}){let c={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators},f=s||this.configuration.makeFetcher(),p={checksums:this.storedChecksums,project:this,cache:e,fetcher:f,report:r,cacheOptions:c},h=Array.from(new Set(qs(this.storedResolutions.values(),[I=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");return ll(R)}])));a==="update-lockfile"&&(h=h.filter(I=>!this.storedChecksums.has(I)));let E=!1,C=Ao.progressViaCounter(h.length);await r.reportProgress(C);let S=(0,qR.default)(fat);if(await Uu(h.map(I=>S(async()=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");if(Gu(R))return;let N;try{N=await f.fetch(R,p)}catch(U){U.message=`${Yr(this.configuration,R)}: ${U.message}`,r.reportExceptionOnce(U),E=U;return}N.checksum!=null?this.storedChecksums.set(R.locatorHash,N.checksum):this.storedChecksums.delete(R.locatorHash),N.releaseFs&&N.releaseFs()}).finally(()=>{C.tick()}))),E)throw E;let P=n&&a!=="update-lockfile"?await this.cacheCleanup({cache:e,report:r}):null;if(r.cacheMisses.size>0||P){let R=(await Promise.all([...r.cacheMisses].map(async le=>{let me=this.storedPackages.get(le),pe=this.storedChecksums.get(le)??null,Be=e.getLocatorPath(me,pe);return(await ce.statPromise(Be)).size}))).reduce((le,me)=>le+me,0)-(P?.size??0),N=r.cacheMisses.size,U=P?.count??0,W=`${Wk(N,{zero:"No new packages",one:"A package was",more:`${Ht(this.configuration,N,ht.NUMBER)} packages were`})} added to the project`,ee=`${Wk(U,{zero:"none were",one:"one was",more:`${Ht(this.configuration,U,ht.NUMBER)} were`})} removed`,ie=R!==0?` (${Ht(this.configuration,R,ht.SIZE_DIFF)})`:"",ue=U>0?N>0?`${W}, and ${ee}${ie}.`:`${W}, but ${ee}${ie}.`:`${W}${ie}.`;r.reportInfo(13,ue)}}async linkEverything({cache:e,report:r,fetcher:s,mode:a}){let n={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators,skipIntegrityCheck:!0},c=s||this.configuration.makeFetcher(),f={checksums:this.storedChecksums,project:this,cache:e,fetcher:c,report:r,cacheOptions:n},p=this.configuration.getLinkers(),h={project:this,report:r},E=new Map(p.map(ye=>{let Ae=ye.makeInstaller(h),se=ye.getCustomDataKey(),Z=this.linkersCustomData.get(se);return typeof Z<"u"&&Ae.attachCustomData(Z),[ye,Ae]})),C=new Map,S=new Map,P=new Map,I=new Map(await Uu([...this.accessibleLocators].map(async ye=>{let Ae=this.storedPackages.get(ye);if(!Ae)throw new Error("Assertion failed: The locator should have been registered");return[ye,await c.fetch(Ae,f)]}))),R=[],N=new Set,U=[];for(let ye of this.accessibleLocators){let Ae=this.storedPackages.get(ye);if(typeof Ae>"u")throw new Error("Assertion failed: The locator should have been registered");let se=I.get(Ae.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The fetch result should have been registered");let Z=[],De=mt=>{Z.push(mt)},Re=this.tryWorkspaceByLocator(Ae);if(Re!==null){let mt=[],{scripts:j}=Re.manifest;for(let Fe of["preinstall","install","postinstall"])j.has(Fe)&&mt.push({type:0,script:Fe});try{for(let[Fe,Ne]of E)if(Fe.supportsPackage(Ae,h)&&(await Ne.installPackage(Ae,se,{holdFetchResult:De})).buildRequest!==null)throw new Error("Assertion failed: Linkers can't return build directives for workspaces; this responsibility befalls to the Yarn core")}finally{Z.length===0?se.releaseFs?.():R.push(Uu(Z).catch(()=>{}).then(()=>{se.releaseFs?.()}))}let rt=J.join(se.packageFs.getRealPath(),se.prefixPath);S.set(Ae.locatorHash,rt),!Gu(Ae)&&mt.length>0&&P.set(Ae.locatorHash,{buildDirectives:mt,buildLocations:[rt]})}else{let mt=p.find(Fe=>Fe.supportsPackage(Ae,h));if(!mt)throw new jt(12,`${Yr(this.configuration,Ae)} isn't supported by any available linker`);let j=E.get(mt);if(!j)throw new Error("Assertion failed: The installer should have been registered");let rt;try{rt=await j.installPackage(Ae,se,{holdFetchResult:De})}finally{Z.length===0?se.releaseFs?.():R.push(Uu(Z).then(()=>{}).then(()=>{se.releaseFs?.()}))}C.set(Ae.locatorHash,mt),S.set(Ae.locatorHash,rt.packageLocation),rt.buildRequest&&rt.packageLocation&&(rt.buildRequest.skipped?(N.add(Ae.locatorHash),this.skippedBuilds.has(Ae.locatorHash)||U.push([Ae,rt.buildRequest.explain])):P.set(Ae.locatorHash,{buildDirectives:rt.buildRequest.directives,buildLocations:[rt.packageLocation]}))}}let W=new Map;for(let ye of this.accessibleLocators){let Ae=this.storedPackages.get(ye);if(!Ae)throw new Error("Assertion failed: The locator should have been registered");let se=this.tryWorkspaceByLocator(Ae)!==null,Z=async(De,Re)=>{let mt=S.get(Ae.locatorHash);if(typeof mt>"u")throw new Error(`Assertion failed: The package (${Yr(this.configuration,Ae)}) should have been registered`);let j=[];for(let rt of Ae.dependencies.values()){let Fe=this.storedResolutions.get(rt.descriptorHash);if(typeof Fe>"u")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,rt)}, from ${Yr(this.configuration,Ae)})should have been registered`);let Ne=this.storedPackages.get(Fe);if(typeof Ne>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);let Pe=this.tryWorkspaceByLocator(Ne)===null?C.get(Fe):null;if(typeof Pe>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);Pe===De||Pe===null?S.get(Ne.locatorHash)!==null&&j.push([rt,Ne]):!se&&mt!==null&&xB(W,Fe).push(mt)}mt!==null&&await Re.attachInternalDependencies(Ae,j)};if(se)for(let[De,Re]of E)De.supportsPackage(Ae,h)&&await Z(De,Re);else{let De=C.get(Ae.locatorHash);if(!De)throw new Error("Assertion failed: The linker should have been found");let Re=E.get(De);if(!Re)throw new Error("Assertion failed: The installer should have been registered");await Z(De,Re)}}for(let[ye,Ae]of W){let se=this.storedPackages.get(ye);if(!se)throw new Error("Assertion failed: The package should have been registered");let Z=C.get(se.locatorHash);if(!Z)throw new Error("Assertion failed: The linker should have been found");let De=E.get(Z);if(!De)throw new Error("Assertion failed: The installer should have been registered");await De.attachExternalDependents(se,Ae)}let ee=new Map;for(let[ye,Ae]of E){let se=await Ae.finalizeInstall();for(let Z of se?.records??[])Z.buildRequest.skipped?(N.add(Z.locator.locatorHash),this.skippedBuilds.has(Z.locator.locatorHash)||U.push([Z.locator,Z.buildRequest.explain])):P.set(Z.locator.locatorHash,{buildDirectives:Z.buildRequest.directives,buildLocations:Z.buildLocations});typeof se?.customData<"u"&&ee.set(ye.getCustomDataKey(),se.customData)}if(this.linkersCustomData=ee,await Uu(R),a==="skip-build")return;for(let[,ye]of qs(U,([Ae])=>ll(Ae)))ye(r);let ie=new Set(P.keys()),ue=(0,GR.createHash)("sha512");ue.update(process.versions.node),await this.configuration.triggerHook(ye=>ye.globalHashGeneration,this,ye=>{ue.update("\0"),ue.update(ye)});let le=ue.digest("hex"),me=new Map,pe=ye=>{let Ae=me.get(ye.locatorHash);if(typeof Ae<"u")return Ae;let se=this.storedPackages.get(ye.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The package should have been registered");let Z=(0,GR.createHash)("sha512");Z.update(ye.locatorHash),me.set(ye.locatorHash,"");for(let De of se.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(typeof Re>"u")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);let mt=this.storedPackages.get(Re);if(typeof mt>"u")throw new Error("Assertion failed: The package should have been registered");Z.update(pe(mt))}return Ae=Z.digest("hex"),me.set(ye.locatorHash,Ae),Ae},Be=(ye,Ae)=>{let se=(0,GR.createHash)("sha512");se.update(le),se.update(pe(ye));for(let Z of Ae)se.update(Z);return se.digest("hex")},Ce=new Map,g=!1,we=ye=>{let Ae=new Set([ye.locatorHash]);for(let se of Ae){let Z=this.storedPackages.get(se);if(!Z)throw new Error("Assertion failed: The package should have been registered");for(let De of Z.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(!Re)throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);if(Re!==ye.locatorHash&&ie.has(Re))return!1;let mt=this.storedPackages.get(Re);if(!mt)throw new Error("Assertion failed: The package should have been registered");let j=this.tryWorkspaceByLocator(mt);if(j){if(j.anchoredLocator.locatorHash!==ye.locatorHash&&ie.has(j.anchoredLocator.locatorHash))return!1;Ae.add(j.anchoredLocator.locatorHash)}Ae.add(Re)}}return!0};for(;ie.size>0;){let ye=ie.size,Ae=[];for(let se of ie){let Z=this.storedPackages.get(se);if(!Z)throw new Error("Assertion failed: The package should have been registered");if(!we(Z))continue;let De=P.get(Z.locatorHash);if(!De)throw new Error("Assertion failed: The build directive should have been registered");let Re=Be(Z,De.buildLocations);if(this.storedBuildState.get(Z.locatorHash)===Re){Ce.set(Z.locatorHash,Re),ie.delete(se);continue}g||(await this.persistInstallStateFile(),g=!0),this.storedBuildState.has(Z.locatorHash)?r.reportInfo(8,`${Yr(this.configuration,Z)} must be rebuilt because its dependency tree changed`):r.reportInfo(7,`${Yr(this.configuration,Z)} must be built because it never has been before or the last one failed`);let mt=De.buildLocations.map(async j=>{if(!J.isAbsolute(j))throw new Error(`Assertion failed: Expected the build location to be absolute (not ${j})`);for(let rt of De.buildDirectives){let Fe=`# This file contains the result of Yarn building a package (${ll(Z)}) +`;switch(rt.type){case 0:Fe+=`# Script name: ${rt.script} +`;break;case 1:Fe+=`# Script code: ${rt.script} +`;break}let Ne=null;if(!await ce.mktempPromise(async Ve=>{let ke=J.join(Ve,"build.log"),{stdout:it,stderr:Ue}=this.configuration.getSubprocessStreams(ke,{header:Fe,prefix:Yr(this.configuration,Z),report:r}),x;try{switch(rt.type){case 0:x=await LT(Z,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:Ue});break;case 1:x=await Yj(Z,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:Ue});break}}catch(y){Ue.write(y.stack),x=1}if(it.end(),Ue.end(),x===0)return!0;ce.detachTemp(Ve);let w=`${Yr(this.configuration,Z)} couldn't be built successfully (exit code ${Ht(this.configuration,x,ht.NUMBER)}, logs can be found here: ${Ht(this.configuration,ke,ht.PATH)})`,b=this.optionalBuilds.has(Z.locatorHash);return b?r.reportInfo(9,w):r.reportError(9,w),ehe&&r.reportFold(fe.fromPortablePath(ke),ce.readFileSync(ke,"utf8")),b}))return!1}return!0});Ae.push(...mt,Promise.allSettled(mt).then(j=>{ie.delete(se),j.every(rt=>rt.status==="fulfilled"&&rt.value===!0)&&Ce.set(Z.locatorHash,Re)}))}if(await Uu(Ae),ye===ie.size){let se=Array.from(ie).map(Z=>{let De=this.storedPackages.get(Z);if(!De)throw new Error("Assertion failed: The package should have been registered");return Yr(this.configuration,De)}).join(", ");r.reportError(3,`Some packages have circular dependencies that make their build order unsatisfiable - as a result they won't be built (affected packages are: ${se})`);break}}this.storedBuildState=Ce,this.skippedBuilds=N}async installWithNewReport(e,r){return(await Ot.start({configuration:this.configuration,json:e.json,stdout:e.stdout,forceSectionAlignment:!0,includeLogs:!e.json&&!e.quiet,includeVersion:!0},async a=>{await this.install({...r,report:a})})).exitCode()}async install(e){let r=this.configuration.get("nodeLinker");ze.telemetry?.reportInstall(r);let s=!1;if(await e.report.startTimerPromise("Project validation",{skipIfEmpty:!0},async()=>{this.configuration.get("enableOfflineMode")&&e.report.reportWarning(90,"Offline work is enabled; Yarn won't fetch packages from the remote registry if it can avoid it"),await this.configuration.triggerHook(E=>E.validateProject,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),s=!0}})}),s)return;let a=await this.configuration.getPackageExtensions();for(let E of a.values())for(let[,C]of E)for(let S of C)S.status="inactive";let n=J.join(this.cwd,Er.lockfile),c=null;if(e.immutable)try{c=await ce.readFilePromise(n,"utf8")}catch(E){throw E.code==="ENOENT"?new jt(28,"The lockfile would have been created by this install, which is explicitly forbidden."):E}await e.report.startTimerPromise("Resolution step",async()=>{await this.resolveEverything(e)}),await e.report.startTimerPromise("Post-resolution validation",{skipIfEmpty:!0},async()=>{dat(this,e.report);for(let[,E]of a)for(let[,C]of E)for(let S of C)if(S.userProvided){let P=Ht(this.configuration,S,ht.PACKAGE_EXTENSION);switch(S.status){case"inactive":e.report.reportWarning(68,`${P}: No matching package in the dependency tree; you may not need this rule anymore.`);break;case"redundant":e.report.reportWarning(69,`${P}: This rule seems redundant when applied on the original package; the extension may have been applied upstream.`);break}}if(c!==null){let E=Ed(c,this.generateLockfile());if(E!==c){let C=yde(n,n,c,E,void 0,void 0,{maxEditLength:100});if(C){e.report.reportSeparator();for(let S of C.hunks){e.report.reportInfo(null,`@@ -${S.oldStart},${S.oldLines} +${S.newStart},${S.newLines} @@`);for(let P of S.lines)P.startsWith("+")?e.report.reportError(28,Ht(this.configuration,P,ht.ADDED)):P.startsWith("-")?e.report.reportError(28,Ht(this.configuration,P,ht.REMOVED)):e.report.reportInfo(null,Ht(this.configuration,P,"grey"))}e.report.reportSeparator()}throw new jt(28,"The lockfile would have been modified by this install, which is explicitly forbidden.")}}});for(let E of a.values())for(let[,C]of E)for(let S of C)S.userProvided&&S.status==="active"&&ze.telemetry?.reportPackageExtension(Xd(S,ht.PACKAGE_EXTENSION));await e.report.startTimerPromise("Fetch step",async()=>{await this.fetchEverything(e)});let f=e.immutable?[...new Set(this.configuration.get("immutablePatterns"))].sort():[],p=await Promise.all(f.map(async E=>DQ(E,{cwd:this.cwd})));(typeof e.persistProject>"u"||e.persistProject)&&await this.persist(),await e.report.startTimerPromise("Link step",async()=>{if(e.mode==="update-lockfile"){e.report.reportWarning(73,`Skipped due to ${Ht(this.configuration,"mode=update-lockfile",ht.CODE)}`);return}await this.linkEverything(e);let E=await Promise.all(f.map(async C=>DQ(C,{cwd:this.cwd})));for(let C=0;C{await this.configuration.triggerHook(E=>E.validateProjectAfterInstall,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),h=!0}})}),!h&&await this.configuration.triggerHook(E=>E.afterAllInstalled,this,e)}generateLockfile(){let e=new Map;for(let[n,c]of this.storedResolutions.entries()){let f=e.get(c);f||e.set(c,f=new Set),f.add(n)}let r={},{cacheKey:s}=Kr.getCacheKey(this.configuration);r.__metadata={version:WR,cacheKey:s};for(let[n,c]of e.entries()){let f=this.originalPackages.get(n);if(!f)continue;let p=[];for(let C of c){let S=this.storedDescriptors.get(C);if(!S)throw new Error("Assertion failed: The descriptor should have been registered");p.push(S)}let h=p.map(C=>al(C)).sort().join(", "),E=new Ut;E.version=f.linkType==="HARD"?f.version:"0.0.0-use.local",E.languageName=f.languageName,E.dependencies=new Map(f.dependencies),E.peerDependencies=new Map(f.peerDependencies),E.dependenciesMeta=new Map(f.dependenciesMeta),E.peerDependenciesMeta=new Map(f.peerDependenciesMeta),E.bin=new Map(f.bin),r[h]={...E.exportTo({},{compatibilityMode:!1}),linkType:f.linkType.toLowerCase(),resolution:ll(f),checksum:this.storedChecksums.get(f.locatorHash),conditions:f.conditions||void 0}}return`${[`# This file is generated by running "yarn install" inside your project. +`,`# Manual changes might be lost - proceed with caution! +`].join("")} +`+nl(r)}async persistLockfile(){let e=J.join(this.cwd,Er.lockfile),r="";try{r=await ce.readFilePromise(e,"utf8")}catch{}let s=this.generateLockfile(),a=Ed(r,s);a!==r&&(await ce.writeFilePromise(e,a),this.lockFileChecksum=wde(a),this.lockfileNeedsRefresh=!1)}async persistInstallStateFile(){let e=[];for(let c of Object.values(KG))e.push(...c);let r=Kd(this,e),s=zG.default.serialize(r),a=us(s);if(this.installStateChecksum===a)return;let n=this.configuration.get("installStatePath");await ce.mkdirPromise(J.dirname(n),{recursive:!0}),await ce.writeFilePromise(n,await Aat(s)),this.installStateChecksum=a}async restoreInstallState({restoreLinkersCustomData:e=!0,restoreResolutions:r=!0,restoreBuildState:s=!0}={}){let a=this.configuration.get("installStatePath"),n;try{let c=await pat(await ce.readFilePromise(a));n=zG.default.deserialize(c),this.installStateChecksum=us(c)}catch{r&&await this.applyLightResolution();return}e&&typeof n.linkersCustomData<"u"&&(this.linkersCustomData=n.linkersCustomData),s&&Object.assign(this,Kd(n,KG.restoreBuildState)),r&&(n.lockFileChecksum===this.lockFileChecksum?Object.assign(this,Kd(n,KG.restoreResolutions)):await this.applyLightResolution())}async applyLightResolution(){await this.resolveEverything({lockfileOnly:!0,report:new ki}),await this.persistInstallStateFile()}async persist(){let e=(0,qR.default)(4);await Promise.all([this.persistLockfile(),...this.workspaces.map(r=>e(()=>r.persistManifest()))])}async cacheCleanup({cache:e,report:r}){if(this.configuration.get("enableGlobalCache"))return null;let s=new Set([".gitignore"]);if(!q8(e.cwd,this.cwd)||!await ce.existsPromise(e.cwd))return null;let a=[];for(let c of await ce.readdirPromise(e.cwd)){if(s.has(c))continue;let f=J.resolve(e.cwd,c);e.markedFiles.has(f)||(e.immutable?r.reportError(56,`${Ht(this.configuration,J.basename(f),"magenta")} appears to be unused and would be marked for deletion, but the cache is immutable`):a.push(ce.lstatPromise(f).then(async p=>(await ce.removePromise(f),p.size))))}if(a.length===0)return null;let n=await Promise.all(a);return{count:a.length,size:n.reduce((c,f)=>c+f,0)}}}});function mat(t){let s=Math.floor(t.timeNow/864e5),a=t.updateInterval*864e5,n=t.state.lastUpdate??t.timeNow+a+Math.floor(a*t.randomInitialInterval),c=n+a,f=t.state.lastTips??s*864e5,p=f+864e5+8*36e5-t.timeZone,h=c<=t.timeNow,E=p<=t.timeNow,C=null;return(h||E||!t.state.lastUpdate||!t.state.lastTips)&&(C={},C.lastUpdate=h?t.timeNow:n,C.lastTips=f,C.blocks=h?{}:t.state.blocks,C.displayedTips=t.state.displayedTips),{nextState:C,triggerUpdate:h,triggerTips:E,nextTips:E?s*864e5:f}}var ZI,Sde=Xe(()=>{Dt();yv();I0();pT();Pc();Rp();ZI=class{constructor(e,r){this.values=new Map;this.hits=new Map;this.enumerators=new Map;this.nextTips=0;this.displayedTips=[];this.shouldCommitTips=!1;this.configuration=e;let s=this.getRegistryPath();this.isNew=!ce.existsSync(s),this.shouldShowTips=!1,this.sendReport(r),this.startBuffer()}commitTips(){this.shouldShowTips&&(this.shouldCommitTips=!0)}selectTip(e){let r=new Set(this.displayedTips),s=f=>f&&fn?Zf(fn,f):!1,a=e.map((f,p)=>p).filter(f=>e[f]&&s(e[f]?.selector));if(a.length===0)return null;let n=a.filter(f=>!r.has(f));if(n.length===0){let f=Math.floor(a.length*.2);this.displayedTips=f>0?this.displayedTips.slice(-f):[],n=a.filter(p=>!r.has(p))}let c=n[Math.floor(Math.random()*n.length)];return this.displayedTips.push(c),this.commitTips(),e[c]}reportVersion(e){this.reportValue("version",e.replace(/-git\..*/,"-git"))}reportCommandName(e){this.reportValue("commandName",e||"")}reportPluginName(e){this.reportValue("pluginName",e)}reportProject(e){this.reportEnumerator("projectCount",e)}reportInstall(e){this.reportHit("installCount",e)}reportPackageExtension(e){this.reportValue("packageExtension",e)}reportWorkspaceCount(e){this.reportValue("workspaceCount",String(e))}reportDependencyCount(e){this.reportValue("dependencyCount",String(e))}reportValue(e,r){bp(this.values,e).add(r)}reportEnumerator(e,r){bp(this.enumerators,e).add(us(r))}reportHit(e,r="*"){let s=q4(this.hits,e),a=Yl(s,r,()=>0);s.set(r,a+1)}getRegistryPath(){let e=this.configuration.get("globalFolder");return J.join(e,"telemetry.json")}sendReport(e){let r=this.getRegistryPath(),s;try{s=ce.readJsonSync(r)}catch{s={}}let{nextState:a,triggerUpdate:n,triggerTips:c,nextTips:f}=mat({state:s,timeNow:Date.now(),timeZone:new Date().getTimezoneOffset()*60*1e3,randomInitialInterval:Math.random(),updateInterval:this.configuration.get("telemetryInterval")});if(this.nextTips=f,this.displayedTips=s.displayedTips??[],a!==null)try{ce.mkdirSync(J.dirname(r),{recursive:!0}),ce.writeJsonSync(r,a)}catch{return!1}if(c&&this.configuration.get("enableTips")&&(this.shouldShowTips=!0),n){let p=s.blocks??{};if(Object.keys(p).length===0){let h=`https://browser-http-intake.logs.datadoghq.eu/v1/input/${e}?ddsource=yarn`,E=C=>cj(h,C,{configuration:this.configuration}).catch(()=>{});for(let[C,S]of Object.entries(s.blocks??{})){if(Object.keys(S).length===0)continue;let P=S;P.userId=C,P.reportType="primary";for(let N of Object.keys(P.enumerators??{}))P.enumerators[N]=P.enumerators[N].length;E(P);let I=new Map,R=20;for(let[N,U]of Object.entries(P.values))U.length>0&&I.set(N,U.slice(0,R));for(;I.size>0;){let N={};N.userId=C,N.reportType="secondary",N.metrics={};for(let[U,W]of I)N.metrics[U]=W.shift(),W.length===0&&I.delete(U);E(N)}}}}return!0}applyChanges(){let e=this.getRegistryPath(),r;try{r=ce.readJsonSync(e)}catch{r={}}let s=this.configuration.get("telemetryUserId")??"*",a=r.blocks=r.blocks??{},n=a[s]=a[s]??{};for(let c of this.hits.keys()){let f=n.hits=n.hits??{},p=f[c]=f[c]??{};for(let[h,E]of this.hits.get(c))p[h]=(p[h]??0)+E}for(let c of["values","enumerators"])for(let f of this[c].keys()){let p=n[c]=n[c]??{};p[f]=[...new Set([...p[f]??[],...this[c].get(f)??[]])]}this.shouldCommitTips&&(r.lastTips=this.nextTips,r.displayedTips=this.displayedTips),ce.mkdirSync(J.dirname(e),{recursive:!0}),ce.writeJsonSync(e,r)}startBuffer(){process.on("exit",()=>{try{this.applyChanges()}catch{}})}}});var jv={};Vt(jv,{BuildDirectiveType:()=>_R,CACHE_CHECKPOINT:()=>OG,CACHE_VERSION:()=>UR,Cache:()=>Kr,Configuration:()=>ze,DEFAULT_RC_FILENAME:()=>dj,DurationUnit:()=>mj,FormatType:()=>upe,InstallMode:()=>$l,LEGACY_PLUGINS:()=>ov,LOCKFILE_VERSION:()=>WR,LegacyMigrationResolver:()=>KI,LightReport:()=>lA,LinkType:()=>VE,LockfileResolver:()=>zI,Manifest:()=>Ut,MessageName:()=>Br,MultiFetcher:()=>aI,PackageExtensionStatus:()=>J4,PackageExtensionType:()=>V4,PeerWarningType:()=>YR,Project:()=>Tt,Report:()=>Ao,ReportError:()=>jt,SettingsType:()=>wI,StreamReport:()=>Ot,TAG_REGEXP:()=>Mp,TelemetryManager:()=>ZI,ThrowReport:()=>ki,VirtualFetcher:()=>lI,WindowsLinkType:()=>IT,Workspace:()=>XI,WorkspaceFetcher:()=>cI,WorkspaceResolver:()=>Ei,YarnVersion:()=>fn,execUtils:()=>qr,folderUtils:()=>OQ,formatUtils:()=>he,hashUtils:()=>Nn,httpUtils:()=>nn,miscUtils:()=>je,nodeUtils:()=>Ui,parseMessageName:()=>jx,reportOptionDeprecations:()=>SI,scriptUtils:()=>In,semverUtils:()=>Fr,stringifyMessageName:()=>Yf,structUtils:()=>G,tgzUtils:()=>ps,treeUtils:()=>xs});var Ge=Xe(()=>{dT();LQ();xc();I0();pT();Pc();gT();zj();Rp();Wo();nde();ude();LG();av();av();pde();MG();hde();UG();oI();Gx();R8();vde();Tc();Ev();Sde();VG();N8();O8();tm();JG();yv();hle()});var Qde=_((WHt,qv)=>{"use strict";var Eat=process.env.TERM_PROGRAM==="Hyper",Iat=process.platform==="win32",Pde=process.platform==="linux",$G={ballotDisabled:"\u2612",ballotOff:"\u2610",ballotOn:"\u2611",bullet:"\u2022",bulletWhite:"\u25E6",fullBlock:"\u2588",heart:"\u2764",identicalTo:"\u2261",line:"\u2500",mark:"\u203B",middot:"\xB7",minus:"\uFF0D",multiplication:"\xD7",obelus:"\xF7",pencilDownRight:"\u270E",pencilRight:"\u270F",pencilUpRight:"\u2710",percent:"%",pilcrow2:"\u2761",pilcrow:"\xB6",plusMinus:"\xB1",section:"\xA7",starsOff:"\u2606",starsOn:"\u2605",upDownArrow:"\u2195"},xde=Object.assign({},$G,{check:"\u221A",cross:"\xD7",ellipsisLarge:"...",ellipsis:"...",info:"i",question:"?",questionSmall:"?",pointer:">",pointerSmall:"\xBB",radioOff:"( )",radioOn:"(*)",warning:"\u203C"}),kde=Object.assign({},$G,{ballotCross:"\u2718",check:"\u2714",cross:"\u2716",ellipsisLarge:"\u22EF",ellipsis:"\u2026",info:"\u2139",question:"?",questionFull:"\uFF1F",questionSmall:"\uFE56",pointer:Pde?"\u25B8":"\u276F",pointerSmall:Pde?"\u2023":"\u203A",radioOff:"\u25EF",radioOn:"\u25C9",warning:"\u26A0"});qv.exports=Iat&&!Eat?xde:kde;Reflect.defineProperty(qv.exports,"common",{enumerable:!1,value:$G});Reflect.defineProperty(qv.exports,"windows",{enumerable:!1,value:xde});Reflect.defineProperty(qv.exports,"other",{enumerable:!1,value:kde})});var Ju=_((YHt,e5)=>{"use strict";var Cat=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),wat=/[\u001b\u009b][[\]#;?()]*(?:(?:(?:[^\W_]*;?[^\W_]*)\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g,Tde=()=>{let t={enabled:!0,visible:!0,styles:{},keys:{}};"FORCE_COLOR"in process.env&&(t.enabled=process.env.FORCE_COLOR!=="0");let e=n=>{let c=n.open=`\x1B[${n.codes[0]}m`,f=n.close=`\x1B[${n.codes[1]}m`,p=n.regex=new RegExp(`\\u001b\\[${n.codes[1]}m`,"g");return n.wrap=(h,E)=>{h.includes(f)&&(h=h.replace(p,f+c));let C=c+h+f;return E?C.replace(/\r*\n/g,`${f}$&${c}`):C},n},r=(n,c,f)=>typeof n=="function"?n(c):n.wrap(c,f),s=(n,c)=>{if(n===""||n==null)return"";if(t.enabled===!1)return n;if(t.visible===!1)return"";let f=""+n,p=f.includes(` +`),h=c.length;for(h>0&&c.includes("unstyle")&&(c=[...new Set(["unstyle",...c])].reverse());h-- >0;)f=r(t.styles[c[h]],f,p);return f},a=(n,c,f)=>{t.styles[n]=e({name:n,codes:c}),(t.keys[f]||(t.keys[f]=[])).push(n),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(h){t.alias(n,h)},get(){let h=E=>s(E,h.stack);return Reflect.setPrototypeOf(h,t),h.stack=this.stack?this.stack.concat(n):[n],h}})};return a("reset",[0,0],"modifier"),a("bold",[1,22],"modifier"),a("dim",[2,22],"modifier"),a("italic",[3,23],"modifier"),a("underline",[4,24],"modifier"),a("inverse",[7,27],"modifier"),a("hidden",[8,28],"modifier"),a("strikethrough",[9,29],"modifier"),a("black",[30,39],"color"),a("red",[31,39],"color"),a("green",[32,39],"color"),a("yellow",[33,39],"color"),a("blue",[34,39],"color"),a("magenta",[35,39],"color"),a("cyan",[36,39],"color"),a("white",[37,39],"color"),a("gray",[90,39],"color"),a("grey",[90,39],"color"),a("bgBlack",[40,49],"bg"),a("bgRed",[41,49],"bg"),a("bgGreen",[42,49],"bg"),a("bgYellow",[43,49],"bg"),a("bgBlue",[44,49],"bg"),a("bgMagenta",[45,49],"bg"),a("bgCyan",[46,49],"bg"),a("bgWhite",[47,49],"bg"),a("blackBright",[90,39],"bright"),a("redBright",[91,39],"bright"),a("greenBright",[92,39],"bright"),a("yellowBright",[93,39],"bright"),a("blueBright",[94,39],"bright"),a("magentaBright",[95,39],"bright"),a("cyanBright",[96,39],"bright"),a("whiteBright",[97,39],"bright"),a("bgBlackBright",[100,49],"bgBright"),a("bgRedBright",[101,49],"bgBright"),a("bgGreenBright",[102,49],"bgBright"),a("bgYellowBright",[103,49],"bgBright"),a("bgBlueBright",[104,49],"bgBright"),a("bgMagentaBright",[105,49],"bgBright"),a("bgCyanBright",[106,49],"bgBright"),a("bgWhiteBright",[107,49],"bgBright"),t.ansiRegex=wat,t.hasColor=t.hasAnsi=n=>(t.ansiRegex.lastIndex=0,typeof n=="string"&&n!==""&&t.ansiRegex.test(n)),t.alias=(n,c)=>{let f=typeof c=="string"?t[c]:c;if(typeof f!="function")throw new TypeError("Expected alias to be the name of an existing color (string) or a function");f.stack||(Reflect.defineProperty(f,"name",{value:n}),t.styles[n]=f,f.stack=[n]),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(p){t.alias(n,p)},get(){let p=h=>s(h,p.stack);return Reflect.setPrototypeOf(p,t),p.stack=this.stack?this.stack.concat(f.stack):f.stack,p}})},t.theme=n=>{if(!Cat(n))throw new TypeError("Expected theme to be an object");for(let c of Object.keys(n))t.alias(c,n[c]);return t},t.alias("unstyle",n=>typeof n=="string"&&n!==""?(t.ansiRegex.lastIndex=0,n.replace(t.ansiRegex,"")):""),t.alias("noop",n=>n),t.none=t.clear=t.noop,t.stripColor=t.unstyle,t.symbols=Qde(),t.define=a,t};e5.exports=Tde();e5.exports.create=Tde});var Zo=_(pn=>{"use strict";var Bat=Object.prototype.toString,jc=Ju(),Rde=!1,t5=[],Fde={yellow:"blue",cyan:"red",green:"magenta",black:"white",blue:"yellow",red:"cyan",magenta:"green",white:"black"};pn.longest=(t,e)=>t.reduce((r,s)=>Math.max(r,e?s[e].length:s.length),0);pn.hasColor=t=>!!t&&jc.hasColor(t);var JR=pn.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);pn.nativeType=t=>Bat.call(t).slice(8,-1).toLowerCase().replace(/\s/g,"");pn.isAsyncFn=t=>pn.nativeType(t)==="asyncfunction";pn.isPrimitive=t=>t!=null&&typeof t!="object"&&typeof t!="function";pn.resolve=(t,e,...r)=>typeof e=="function"?e.call(t,...r):e;pn.scrollDown=(t=[])=>[...t.slice(1),t[0]];pn.scrollUp=(t=[])=>[t.pop(),...t];pn.reorder=(t=[])=>{let e=t.slice();return e.sort((r,s)=>r.index>s.index?1:r.index{let s=t.length,a=r===s?0:r<0?s-1:r,n=t[e];t[e]=t[a],t[a]=n};pn.width=(t,e=80)=>{let r=t&&t.columns?t.columns:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[0]),process.platform==="win32"?r-1:r};pn.height=(t,e=20)=>{let r=t&&t.rows?t.rows:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[1]),r};pn.wordWrap=(t,e={})=>{if(!t)return t;typeof e=="number"&&(e={width:e});let{indent:r="",newline:s=` +`+r,width:a=80}=e,n=(s+r).match(/[^\S\n]/g)||[];a-=n.length;let c=`.{1,${a}}([\\s\\u200B]+|$)|[^\\s\\u200B]+?([\\s\\u200B]+|$)`,f=t.trim(),p=new RegExp(c,"g"),h=f.match(p)||[];return h=h.map(E=>E.replace(/\n$/,"")),e.padEnd&&(h=h.map(E=>E.padEnd(a," "))),e.padStart&&(h=h.map(E=>E.padStart(a," "))),r+h.join(s)};pn.unmute=t=>{let e=t.stack.find(s=>jc.keys.color.includes(s));return e?jc[e]:t.stack.find(s=>s.slice(2)==="bg")?jc[e.slice(2)]:s=>s};pn.pascal=t=>t?t[0].toUpperCase()+t.slice(1):"";pn.inverse=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>jc.keys.color.includes(s));if(e){let s=jc["bg"+pn.pascal(e)];return s?s.black:t}let r=t.stack.find(s=>s.slice(0,2)==="bg");return r?jc[r.slice(2).toLowerCase()]||t:jc.none};pn.complement=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>jc.keys.color.includes(s)),r=t.stack.find(s=>s.slice(0,2)==="bg");if(e&&!r)return jc[Fde[e]||e];if(r){let s=r.slice(2).toLowerCase(),a=Fde[s];return a&&jc["bg"+pn.pascal(a)]||t}return jc.none};pn.meridiem=t=>{let e=t.getHours(),r=t.getMinutes(),s=e>=12?"pm":"am";e=e%12;let a=e===0?12:e,n=r<10?"0"+r:r;return a+":"+n+" "+s};pn.set=(t={},e="",r)=>e.split(".").reduce((s,a,n,c)=>{let f=c.length-1>n?s[a]||{}:r;return!pn.isObject(f)&&n{let s=t[e]==null?e.split(".").reduce((a,n)=>a&&a[n],t):t[e];return s??r};pn.mixin=(t,e)=>{if(!JR(t))return e;if(!JR(e))return t;for(let r of Object.keys(e)){let s=Object.getOwnPropertyDescriptor(e,r);if(s.hasOwnProperty("value"))if(t.hasOwnProperty(r)&&JR(s.value)){let a=Object.getOwnPropertyDescriptor(t,r);JR(a.value)?t[r]=pn.merge({},t[r],e[r]):Reflect.defineProperty(t,r,s)}else Reflect.defineProperty(t,r,s);else Reflect.defineProperty(t,r,s)}return t};pn.merge=(...t)=>{let e={};for(let r of t)pn.mixin(e,r);return e};pn.mixinEmitter=(t,e)=>{let r=e.constructor.prototype;for(let s of Object.keys(r)){let a=r[s];typeof a=="function"?pn.define(t,s,a.bind(e)):pn.define(t,s,a)}};pn.onExit=t=>{let e=(r,s)=>{Rde||(Rde=!0,t5.forEach(a=>a()),r===!0&&process.exit(128+s))};t5.length===0&&(process.once("SIGTERM",e.bind(null,!0,15)),process.once("SIGINT",e.bind(null,!0,2)),process.once("exit",e)),t5.push(t)};pn.define=(t,e,r)=>{Reflect.defineProperty(t,e,{value:r})};pn.defineExport=(t,e,r)=>{let s;Reflect.defineProperty(t,e,{enumerable:!0,configurable:!0,set(a){s=a},get(){return s?s():r()}})}});var Nde=_(rC=>{"use strict";rC.ctrl={a:"first",b:"backward",c:"cancel",d:"deleteForward",e:"last",f:"forward",g:"reset",i:"tab",k:"cutForward",l:"reset",n:"newItem",m:"cancel",j:"submit",p:"search",r:"remove",s:"save",u:"undo",w:"cutLeft",x:"toggleCursor",v:"paste"};rC.shift={up:"shiftUp",down:"shiftDown",left:"shiftLeft",right:"shiftRight",tab:"prev"};rC.fn={up:"pageUp",down:"pageDown",left:"pageLeft",right:"pageRight",delete:"deleteForward"};rC.option={b:"backward",f:"forward",d:"cutRight",left:"cutLeft",up:"altUp",down:"altDown"};rC.keys={pageup:"pageUp",pagedown:"pageDown",home:"home",end:"end",cancel:"cancel",delete:"deleteForward",backspace:"delete",down:"down",enter:"submit",escape:"cancel",left:"left",space:"space",number:"number",return:"submit",right:"right",tab:"next",up:"up"}});var Mde=_((KHt,Lde)=>{"use strict";var Ode=Ie("readline"),vat=Nde(),Sat=/^(?:\x1b)([a-zA-Z0-9])$/,Dat=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,bat={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"};function Pat(t){return["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(t)}function xat(t){return["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(t)}var KR=(t="",e={})=>{let r,s={name:e.name,ctrl:!1,meta:!1,shift:!1,option:!1,sequence:t,raw:t,...e};if(Buffer.isBuffer(t)?t[0]>127&&t[1]===void 0?(t[0]-=128,t="\x1B"+String(t)):t=String(t):t!==void 0&&typeof t!="string"?t=String(t):t||(t=s.sequence||""),s.sequence=s.sequence||t||s.name,t==="\r")s.raw=void 0,s.name="return";else if(t===` +`)s.name="enter";else if(t===" ")s.name="tab";else if(t==="\b"||t==="\x7F"||t==="\x1B\x7F"||t==="\x1B\b")s.name="backspace",s.meta=t.charAt(0)==="\x1B";else if(t==="\x1B"||t==="\x1B\x1B")s.name="escape",s.meta=t.length===2;else if(t===" "||t==="\x1B ")s.name="space",s.meta=t.length===2;else if(t<="")s.name=String.fromCharCode(t.charCodeAt(0)+97-1),s.ctrl=!0;else if(t.length===1&&t>="0"&&t<="9")s.name="number";else if(t.length===1&&t>="a"&&t<="z")s.name=t;else if(t.length===1&&t>="A"&&t<="Z")s.name=t.toLowerCase(),s.shift=!0;else if(r=Sat.exec(t))s.meta=!0,s.shift=/^[A-Z]$/.test(r[1]);else if(r=Dat.exec(t)){let a=[...t];a[0]==="\x1B"&&a[1]==="\x1B"&&(s.option=!0);let n=[r[1],r[2],r[4],r[6]].filter(Boolean).join(""),c=(r[3]||r[5]||1)-1;s.ctrl=!!(c&4),s.meta=!!(c&10),s.shift=!!(c&1),s.code=n,s.name=bat[n],s.shift=Pat(n)||s.shift,s.ctrl=xat(n)||s.ctrl}return s};KR.listen=(t={},e)=>{let{stdin:r}=t;if(!r||r!==process.stdin&&!r.isTTY)throw new Error("Invalid stream passed");let s=Ode.createInterface({terminal:!0,input:r});Ode.emitKeypressEvents(r,s);let a=(f,p)=>e(f,KR(f,p),s),n=r.isRaw;return r.isTTY&&r.setRawMode(!0),r.on("keypress",a),s.resume(),()=>{r.isTTY&&r.setRawMode(n),r.removeListener("keypress",a),s.pause(),s.close()}};KR.action=(t,e,r)=>{let s={...vat,...r};return e.ctrl?(e.action=s.ctrl[e.name],e):e.option&&s.option?(e.action=s.option[e.name],e):e.shift?(e.action=s.shift[e.name],e):(e.action=s.keys[e.name],e)};Lde.exports=KR});var _de=_((zHt,Ude)=>{"use strict";Ude.exports=t=>{t.timers=t.timers||{};let e=t.options.timers;if(e)for(let r of Object.keys(e)){let s=e[r];typeof s=="number"&&(s={interval:s}),kat(t,r,s)}};function kat(t,e,r={}){let s=t.timers[e]={name:e,start:Date.now(),ms:0,tick:0},a=r.interval||120;s.frames=r.frames||[],s.loading=!0;let n=setInterval(()=>{s.ms=Date.now()-s.start,s.tick++,t.render()},a);return s.stop=()=>{s.loading=!1,clearInterval(n)},Reflect.defineProperty(s,"interval",{value:n}),t.once("close",()=>s.stop()),s.stop}});var jde=_((XHt,Hde)=>{"use strict";var{define:Qat,width:Tat}=Zo(),r5=class{constructor(e){let r=e.options;Qat(this,"_prompt",e),this.type=e.type,this.name=e.name,this.message="",this.header="",this.footer="",this.error="",this.hint="",this.input="",this.cursor=0,this.index=0,this.lines=0,this.tick=0,this.prompt="",this.buffer="",this.width=Tat(r.stdout||process.stdout),Object.assign(this,r),this.name=this.name||this.message,this.message=this.message||this.name,this.symbols=e.symbols,this.styles=e.styles,this.required=new Set,this.cancelled=!1,this.submitted=!1}clone(){let e={...this};return e.status=this.status,e.buffer=Buffer.from(e.buffer),delete e.clone,e}set color(e){this._color=e}get color(){let e=this.prompt.styles;if(this.cancelled)return e.cancelled;if(this.submitted)return e.submitted;let r=this._color||e[this.status];return typeof r=="function"?r:e.pending}set loading(e){this._loading=e}get loading(){return typeof this._loading=="boolean"?this._loading:this.loadingChoices?"choices":!1}get status(){return this.cancelled?"cancelled":this.submitted?"submitted":"pending"}};Hde.exports=r5});var qde=_((ZHt,Gde)=>{"use strict";var n5=Zo(),ho=Ju(),i5={default:ho.noop,noop:ho.noop,set inverse(t){this._inverse=t},get inverse(){return this._inverse||n5.inverse(this.primary)},set complement(t){this._complement=t},get complement(){return this._complement||n5.complement(this.primary)},primary:ho.cyan,success:ho.green,danger:ho.magenta,strong:ho.bold,warning:ho.yellow,muted:ho.dim,disabled:ho.gray,dark:ho.dim.gray,underline:ho.underline,set info(t){this._info=t},get info(){return this._info||this.primary},set em(t){this._em=t},get em(){return this._em||this.primary.underline},set heading(t){this._heading=t},get heading(){return this._heading||this.muted.underline},set pending(t){this._pending=t},get pending(){return this._pending||this.primary},set submitted(t){this._submitted=t},get submitted(){return this._submitted||this.success},set cancelled(t){this._cancelled=t},get cancelled(){return this._cancelled||this.danger},set typing(t){this._typing=t},get typing(){return this._typing||this.dim},set placeholder(t){this._placeholder=t},get placeholder(){return this._placeholder||this.primary.dim},set highlight(t){this._highlight=t},get highlight(){return this._highlight||this.inverse}};i5.merge=(t={})=>{t.styles&&typeof t.styles.enabled=="boolean"&&(ho.enabled=t.styles.enabled),t.styles&&typeof t.styles.visible=="boolean"&&(ho.visible=t.styles.visible);let e=n5.merge({},i5,t.styles);delete e.merge;for(let r of Object.keys(ho))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>ho[r]});for(let r of Object.keys(ho.styles))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>ho[r]});return e};Gde.exports=i5});var Yde=_(($Ht,Wde)=>{"use strict";var s5=process.platform==="win32",zp=Ju(),Rat=Zo(),o5={...zp.symbols,upDownDoubleArrow:"\u21D5",upDownDoubleArrow2:"\u2B0D",upDownArrow:"\u2195",asterisk:"*",asterism:"\u2042",bulletWhite:"\u25E6",electricArrow:"\u2301",ellipsisLarge:"\u22EF",ellipsisSmall:"\u2026",fullBlock:"\u2588",identicalTo:"\u2261",indicator:zp.symbols.check,leftAngle:"\u2039",mark:"\u203B",minus:"\u2212",multiplication:"\xD7",obelus:"\xF7",percent:"%",pilcrow:"\xB6",pilcrow2:"\u2761",pencilUpRight:"\u2710",pencilDownRight:"\u270E",pencilRight:"\u270F",plus:"+",plusMinus:"\xB1",pointRight:"\u261E",rightAngle:"\u203A",section:"\xA7",hexagon:{off:"\u2B21",on:"\u2B22",disabled:"\u2B22"},ballot:{on:"\u2611",off:"\u2610",disabled:"\u2612"},stars:{on:"\u2605",off:"\u2606",disabled:"\u2606"},folder:{on:"\u25BC",off:"\u25B6",disabled:"\u25B6"},prefix:{pending:zp.symbols.question,submitted:zp.symbols.check,cancelled:zp.symbols.cross},separator:{pending:zp.symbols.pointerSmall,submitted:zp.symbols.middot,cancelled:zp.symbols.middot},radio:{off:s5?"( )":"\u25EF",on:s5?"(*)":"\u25C9",disabled:s5?"(|)":"\u24BE"},numbers:["\u24EA","\u2460","\u2461","\u2462","\u2463","\u2464","\u2465","\u2466","\u2467","\u2468","\u2469","\u246A","\u246B","\u246C","\u246D","\u246E","\u246F","\u2470","\u2471","\u2472","\u2473","\u3251","\u3252","\u3253","\u3254","\u3255","\u3256","\u3257","\u3258","\u3259","\u325A","\u325B","\u325C","\u325D","\u325E","\u325F","\u32B1","\u32B2","\u32B3","\u32B4","\u32B5","\u32B6","\u32B7","\u32B8","\u32B9","\u32BA","\u32BB","\u32BC","\u32BD","\u32BE","\u32BF"]};o5.merge=t=>{let e=Rat.merge({},zp.symbols,o5,t.symbols);return delete e.merge,e};Wde.exports=o5});var Jde=_((ejt,Vde)=>{"use strict";var Fat=qde(),Nat=Yde(),Oat=Zo();Vde.exports=t=>{t.options=Oat.merge({},t.options.theme,t.options),t.symbols=Nat.merge(t.options),t.styles=Fat.merge(t.options)}});var $de=_((Xde,Zde)=>{"use strict";var Kde=process.env.TERM_PROGRAM==="Apple_Terminal",Lat=Ju(),a5=Zo(),Ku=Zde.exports=Xde,_i="\x1B[",zde="\x07",l5=!1,j0=Ku.code={bell:zde,beep:zde,beginning:`${_i}G`,down:`${_i}J`,esc:_i,getPosition:`${_i}6n`,hide:`${_i}?25l`,line:`${_i}2K`,lineEnd:`${_i}K`,lineStart:`${_i}1K`,restorePosition:_i+(Kde?"8":"u"),savePosition:_i+(Kde?"7":"s"),screen:`${_i}2J`,show:`${_i}?25h`,up:`${_i}1J`},wm=Ku.cursor={get hidden(){return l5},hide(){return l5=!0,j0.hide},show(){return l5=!1,j0.show},forward:(t=1)=>`${_i}${t}C`,backward:(t=1)=>`${_i}${t}D`,nextLine:(t=1)=>`${_i}E`.repeat(t),prevLine:(t=1)=>`${_i}F`.repeat(t),up:(t=1)=>t?`${_i}${t}A`:"",down:(t=1)=>t?`${_i}${t}B`:"",right:(t=1)=>t?`${_i}${t}C`:"",left:(t=1)=>t?`${_i}${t}D`:"",to(t,e){return e?`${_i}${e+1};${t+1}H`:`${_i}${t+1}G`},move(t=0,e=0){let r="";return r+=t<0?wm.left(-t):t>0?wm.right(t):"",r+=e<0?wm.up(-e):e>0?wm.down(e):"",r},restore(t={}){let{after:e,cursor:r,initial:s,input:a,prompt:n,size:c,value:f}=t;if(s=a5.isPrimitive(s)?String(s):"",a=a5.isPrimitive(a)?String(a):"",f=a5.isPrimitive(f)?String(f):"",c){let p=Ku.cursor.up(c)+Ku.cursor.to(n.length),h=a.length-r;return h>0&&(p+=Ku.cursor.left(h)),p}if(f||e){let p=!a&&s?-s.length:-a.length+r;return e&&(p-=e.length),a===""&&s&&!n.includes(s)&&(p+=s.length),Ku.cursor.move(p)}}},c5=Ku.erase={screen:j0.screen,up:j0.up,down:j0.down,line:j0.line,lineEnd:j0.lineEnd,lineStart:j0.lineStart,lines(t){let e="";for(let r=0;r{if(!e)return c5.line+wm.to(0);let r=n=>[...Lat.unstyle(n)].length,s=t.split(/\r?\n/),a=0;for(let n of s)a+=1+Math.floor(Math.max(r(n)-1,0)/e);return(c5.line+wm.prevLine()).repeat(a-1)+c5.line+wm.to(0)}});var nC=_((tjt,tme)=>{"use strict";var Mat=Ie("events"),eme=Ju(),u5=Mde(),Uat=_de(),_at=jde(),Hat=Jde(),pl=Zo(),Bm=$de(),f5=class t extends Mat{constructor(e={}){super(),this.name=e.name,this.type=e.type,this.options=e,Hat(this),Uat(this),this.state=new _at(this),this.initial=[e.initial,e.default].find(r=>r!=null),this.stdout=e.stdout||process.stdout,this.stdin=e.stdin||process.stdin,this.scale=e.scale||1,this.term=this.options.term||process.env.TERM_PROGRAM,this.margin=Gat(this.options.margin),this.setMaxListeners(0),jat(this)}async keypress(e,r={}){this.keypressed=!0;let s=u5.action(e,u5(e,r),this.options.actions);this.state.keypress=s,this.emit("keypress",e,s),this.emit("state",this.state.clone());let a=this.options[s.action]||this[s.action]||this.dispatch;if(typeof a=="function")return await a.call(this,e,s);this.alert()}alert(){delete this.state.alert,this.options.show===!1?this.emit("alert"):this.stdout.write(Bm.code.beep)}cursorHide(){this.stdout.write(Bm.cursor.hide()),pl.onExit(()=>this.cursorShow())}cursorShow(){this.stdout.write(Bm.cursor.show())}write(e){e&&(this.stdout&&this.state.show!==!1&&this.stdout.write(e),this.state.buffer+=e)}clear(e=0){let r=this.state.buffer;this.state.buffer="",!(!r&&!e||this.options.show===!1)&&this.stdout.write(Bm.cursor.down(e)+Bm.clear(r,this.width))}restore(){if(this.state.closed||this.options.show===!1)return;let{prompt:e,after:r,rest:s}=this.sections(),{cursor:a,initial:n="",input:c="",value:f=""}=this,p=this.state.size=s.length,h={after:r,cursor:a,initial:n,input:c,prompt:e,size:p,value:f},E=Bm.cursor.restore(h);E&&this.stdout.write(E)}sections(){let{buffer:e,input:r,prompt:s}=this.state;s=eme.unstyle(s);let a=eme.unstyle(e),n=a.indexOf(s),c=a.slice(0,n),p=a.slice(n).split(` +`),h=p[0],E=p[p.length-1],S=(s+(r?" "+r:"")).length,P=Se.call(this,this.value),this.result=()=>s.call(this,this.value),typeof r.initial=="function"&&(this.initial=await r.initial.call(this,this)),typeof r.onRun=="function"&&await r.onRun.call(this,this),typeof r.onSubmit=="function"){let a=r.onSubmit.bind(this),n=this.submit.bind(this);delete this.options.onSubmit,this.submit=async()=>(await a(this.name,this.value,this),n())}await this.start(),await this.render()}render(){throw new Error("expected prompt to have a custom render method")}run(){return new Promise(async(e,r)=>{if(this.once("submit",e),this.once("cancel",r),await this.skip())return this.render=()=>{},this.submit();await this.initialize(),this.emit("run")})}async element(e,r,s){let{options:a,state:n,symbols:c,timers:f}=this,p=f&&f[e];n.timer=p;let h=a[e]||n[e]||c[e],E=r&&r[e]!=null?r[e]:await h;if(E==="")return E;let C=await this.resolve(E,n,r,s);return!C&&r&&r[e]?this.resolve(h,n,r,s):C}async prefix(){let e=await this.element("prefix")||this.symbols,r=this.timers&&this.timers.prefix,s=this.state;return s.timer=r,pl.isObject(e)&&(e=e[s.status]||e.pending),pl.hasColor(e)?e:(this.styles[s.status]||this.styles.pending)(e)}async message(){let e=await this.element("message");return pl.hasColor(e)?e:this.styles.strong(e)}async separator(){let e=await this.element("separator")||this.symbols,r=this.timers&&this.timers.separator,s=this.state;s.timer=r;let a=e[s.status]||e.pending||s.separator,n=await this.resolve(a,s);return pl.isObject(n)&&(n=n[s.status]||n.pending),pl.hasColor(n)?n:this.styles.muted(n)}async pointer(e,r){let s=await this.element("pointer",e,r);if(typeof s=="string"&&pl.hasColor(s))return s;if(s){let a=this.styles,n=this.index===r,c=n?a.primary:h=>h,f=await this.resolve(s[n?"on":"off"]||s,this.state),p=pl.hasColor(f)?f:c(f);return n?p:" ".repeat(f.length)}}async indicator(e,r){let s=await this.element("indicator",e,r);if(typeof s=="string"&&pl.hasColor(s))return s;if(s){let a=this.styles,n=e.enabled===!0,c=n?a.success:a.dark,f=s[n?"on":"off"]||s;return pl.hasColor(f)?f:c(f)}return""}body(){return null}footer(){if(this.state.status==="pending")return this.element("footer")}header(){if(this.state.status==="pending")return this.element("header")}async hint(){if(this.state.status==="pending"&&!this.isValue(this.state.input)){let e=await this.element("hint");return pl.hasColor(e)?e:this.styles.muted(e)}}error(e){return this.state.submitted?"":e||this.state.error}format(e){return e}result(e){return e}validate(e){return this.options.required===!0?this.isValue(e):!0}isValue(e){return e!=null&&e!==""}resolve(e,...r){return pl.resolve(this,e,...r)}get base(){return t.prototype}get style(){return this.styles[this.state.status]}get height(){return this.options.rows||pl.height(this.stdout,25)}get width(){return this.options.columns||pl.width(this.stdout,80)}get size(){return{width:this.width,height:this.height}}set cursor(e){this.state.cursor=e}get cursor(){return this.state.cursor}set input(e){this.state.input=e}get input(){return this.state.input}set value(e){this.state.value=e}get value(){let{input:e,value:r}=this.state,s=[r,e].find(this.isValue.bind(this));return this.isValue(s)?s:this.initial}static get prompt(){return e=>new this(e).run()}};function jat(t){let e=a=>t[a]===void 0||typeof t[a]=="function",r=["actions","choices","initial","margin","roles","styles","symbols","theme","timers","value"],s=["body","footer","error","header","hint","indicator","message","prefix","separator","skip"];for(let a of Object.keys(t.options)){if(r.includes(a)||/^on[A-Z]/.test(a))continue;let n=t.options[a];typeof n=="function"&&e(a)?s.includes(a)||(t[a]=n.bind(t)):typeof t[a]!="function"&&(t[a]=n)}}function Gat(t){typeof t=="number"&&(t=[t,t,t,t]);let e=[].concat(t||[]),r=a=>a%2===0?` +`:" ",s=[];for(let a=0;a<4;a++){let n=r(a);e[a]?s.push(n.repeat(e[a])):s.push("")}return s}tme.exports=f5});var ime=_((rjt,nme)=>{"use strict";var qat=Zo(),rme={default(t,e){return e},checkbox(t,e){throw new Error("checkbox role is not implemented yet")},editable(t,e){throw new Error("editable role is not implemented yet")},expandable(t,e){throw new Error("expandable role is not implemented yet")},heading(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||"",e},input(t,e){throw new Error("input role is not implemented yet")},option(t,e){return rme.default(t,e)},radio(t,e){throw new Error("radio role is not implemented yet")},separator(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||t.symbols.line.repeat(5),e},spacer(t,e){return e}};nme.exports=(t,e={})=>{let r=qat.merge({},rme,e.roles);return r[t]||r.default}});var Wv=_((njt,ame)=>{"use strict";var Wat=Ju(),Yat=nC(),Vat=ime(),zR=Zo(),{reorder:A5,scrollUp:Jat,scrollDown:Kat,isObject:sme,swap:zat}=zR,p5=class extends Yat{constructor(e){super(e),this.cursorHide(),this.maxSelected=e.maxSelected||1/0,this.multiple=e.multiple||!1,this.initial=e.initial||0,this.delay=e.delay||0,this.longest=0,this.num=""}async initialize(){typeof this.options.initial=="function"&&(this.initial=await this.options.initial.call(this)),await this.reset(!0),await super.initialize()}async reset(){let{choices:e,initial:r,autofocus:s,suggest:a}=this.options;if(this.state._choices=[],this.state.choices=[],this.choices=await Promise.all(await this.toChoices(e)),this.choices.forEach(n=>n.enabled=!1),typeof a!="function"&&this.selectable.length===0)throw new Error("At least one choice must be selectable");sme(r)&&(r=Object.keys(r)),Array.isArray(r)?(s!=null&&(this.index=this.findIndex(s)),r.forEach(n=>this.enable(this.find(n))),await this.render()):(s!=null&&(r=s),typeof r=="string"&&(r=this.findIndex(r)),typeof r=="number"&&r>-1&&(this.index=Math.max(0,Math.min(r,this.choices.length)),this.enable(this.find(this.index)))),this.isDisabled(this.focused)&&await this.down()}async toChoices(e,r){this.state.loadingChoices=!0;let s=[],a=0,n=async(c,f)=>{typeof c=="function"&&(c=await c.call(this)),c instanceof Promise&&(c=await c);for(let p=0;p(this.state.loadingChoices=!1,c))}async toChoice(e,r,s){if(typeof e=="function"&&(e=await e.call(this,this)),e instanceof Promise&&(e=await e),typeof e=="string"&&(e={name:e}),e.normalized)return e;e.normalized=!0;let a=e.value;if(e=Vat(e.role,this.options)(this,e),typeof e.disabled=="string"&&!e.hint&&(e.hint=e.disabled,e.disabled=!0),e.disabled===!0&&e.hint==null&&(e.hint="(disabled)"),e.index!=null)return e;e.name=e.name||e.key||e.title||e.value||e.message,e.message=e.message||e.name||"",e.value=[e.value,e.name].find(this.isValue.bind(this)),e.input="",e.index=r,e.cursor=0,zR.define(e,"parent",s),e.level=s?s.level+1:1,e.indent==null&&(e.indent=s?s.indent+" ":e.indent||""),e.path=s?s.path+"."+e.name:e.name,e.enabled=!!(this.multiple&&!this.isDisabled(e)&&(e.enabled||this.isSelected(e))),this.isDisabled(e)||(this.longest=Math.max(this.longest,Wat.unstyle(e.message).length));let c={...e};return e.reset=(f=c.input,p=c.value)=>{for(let h of Object.keys(c))e[h]=c[h];e.input=f,e.value=p},a==null&&typeof e.initial=="function"&&(e.input=await e.initial.call(this,this.state,e,r)),e}async onChoice(e,r){this.emit("choice",e,r,this),typeof e.onChoice=="function"&&await e.onChoice.call(this,this.state,e,r)}async addChoice(e,r,s){let a=await this.toChoice(e,r,s);return this.choices.push(a),this.index=this.choices.length-1,this.limit=this.choices.length,a}async newItem(e,r,s){let a={name:"New choice name?",editable:!0,newChoice:!0,...e},n=await this.addChoice(a,r,s);return n.updateChoice=()=>{delete n.newChoice,n.name=n.message=n.input,n.input="",n.cursor=0},this.render()}indent(e){return e.indent==null?e.level>1?" ".repeat(e.level-1):"":e.indent}dispatch(e,r){if(this.multiple&&this[r.name])return this[r.name]();this.alert()}focus(e,r){return typeof r!="boolean"&&(r=e.enabled),r&&!e.enabled&&this.selected.length>=this.maxSelected?this.alert():(this.index=e.index,e.enabled=r&&!this.isDisabled(e),e)}space(){return this.multiple?(this.toggle(this.focused),this.render()):this.alert()}a(){if(this.maxSelectedr.enabled);return this.choices.forEach(r=>r.enabled=!e),this.render()}i(){return this.choices.length-this.selected.length>this.maxSelected?this.alert():(this.choices.forEach(e=>e.enabled=!e.enabled),this.render())}g(e=this.focused){return this.choices.some(r=>!!r.parent)?(this.toggle(e.parent&&!e.choices?e.parent:e),this.render()):this.a()}toggle(e,r){if(!e.enabled&&this.selected.length>=this.maxSelected)return this.alert();typeof r!="boolean"&&(r=!e.enabled),e.enabled=r,e.choices&&e.choices.forEach(a=>this.toggle(a,r));let s=e.parent;for(;s;){let a=s.choices.filter(n=>this.isDisabled(n));s.enabled=a.every(n=>n.enabled===!0),s=s.parent}return ome(this,this.choices),this.emit("toggle",e,this),e}enable(e){return this.selected.length>=this.maxSelected?this.alert():(e.enabled=!this.isDisabled(e),e.choices&&e.choices.forEach(this.enable.bind(this)),e)}disable(e){return e.enabled=!1,e.choices&&e.choices.forEach(this.disable.bind(this)),e}number(e){this.num+=e;let r=s=>{let a=Number(s);if(a>this.choices.length-1)return this.alert();let n=this.focused,c=this.choices.find(f=>a===f.index);if(!c.enabled&&this.selected.length>=this.maxSelected)return this.alert();if(this.visible.indexOf(c)===-1){let f=A5(this.choices),p=f.indexOf(c);if(n.index>p){let h=f.slice(p,p+this.limit),E=f.filter(C=>!h.includes(C));this.choices=h.concat(E)}else{let h=p-this.limit+1;this.choices=f.slice(h).concat(f.slice(0,h))}}return this.index=this.choices.indexOf(c),this.toggle(this.focused),this.render()};return clearTimeout(this.numberTimeout),new Promise(s=>{let a=this.choices.length,n=this.num,c=(f=!1,p)=>{clearTimeout(this.numberTimeout),f&&(p=r(n)),this.num="",s(p)};if(n==="0"||n.length===1&&+(n+"0")>a)return c(!0);if(Number(n)>a)return c(!1,this.alert());this.numberTimeout=setTimeout(()=>c(!0),this.delay)})}home(){return this.choices=A5(this.choices),this.index=0,this.render()}end(){let e=this.choices.length-this.limit,r=A5(this.choices);return this.choices=r.slice(e).concat(r.slice(0,e)),this.index=this.limit-1,this.render()}first(){return this.index=0,this.render()}last(){return this.index=this.visible.length-1,this.render()}prev(){return this.visible.length<=1?this.alert():this.up()}next(){return this.visible.length<=1?this.alert():this.down()}right(){return this.cursor>=this.input.length?this.alert():(this.cursor++,this.render())}left(){return this.cursor<=0?this.alert():(this.cursor--,this.render())}up(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===0?this.alert():e>r&&s===0?this.scrollUp():(this.index=(s-1%e+e)%e,this.isDisabled()?this.up():this.render())}down(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===r-1?this.alert():e>r&&s===r-1?this.scrollDown():(this.index=(s+1)%e,this.isDisabled()?this.down():this.render())}scrollUp(e=0){return this.choices=Jat(this.choices),this.index=e,this.isDisabled()?this.up():this.render()}scrollDown(e=this.visible.length-1){return this.choices=Kat(this.choices),this.index=e,this.isDisabled()?this.down():this.render()}async shiftUp(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index-1),await this.up(),this.sorting=!1;return}return this.scrollUp(this.index)}async shiftDown(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index+1),await this.down(),this.sorting=!1;return}return this.scrollDown(this.index)}pageUp(){return this.visible.length<=1?this.alert():(this.limit=Math.max(this.limit-1,0),this.index=Math.min(this.limit-1,this.index),this._limit=this.limit,this.isDisabled()?this.up():this.render())}pageDown(){return this.visible.length>=this.choices.length?this.alert():(this.index=Math.max(0,this.index),this.limit=Math.min(this.limit+1,this.choices.length),this._limit=this.limit,this.isDisabled()?this.down():this.render())}swap(e){zat(this.choices,this.index,e)}isDisabled(e=this.focused){return e&&["disabled","collapsed","hidden","completing","readonly"].some(s=>e[s]===!0)?!0:e&&e.role==="heading"}isEnabled(e=this.focused){if(Array.isArray(e))return e.every(r=>this.isEnabled(r));if(e.choices){let r=e.choices.filter(s=>!this.isDisabled(s));return e.enabled&&r.every(s=>this.isEnabled(s))}return e.enabled&&!this.isDisabled(e)}isChoice(e,r){return e.name===r||e.index===Number(r)}isSelected(e){return Array.isArray(this.initial)?this.initial.some(r=>this.isChoice(e,r)):this.isChoice(e,this.initial)}map(e=[],r="value"){return[].concat(e||[]).reduce((s,a)=>(s[a]=this.find(a,r),s),{})}filter(e,r){let a=typeof e=="function"?e:(f,p)=>[f.name,p].includes(e),c=(this.options.multiple?this.state._choices:this.choices).filter(a);return r?c.map(f=>f[r]):c}find(e,r){if(sme(e))return r?e[r]:e;let a=typeof e=="function"?e:(c,f)=>[c.name,f].includes(e),n=this.choices.find(a);if(n)return r?n[r]:n}findIndex(e){return this.choices.indexOf(this.find(e))}async submit(){let e=this.focused;if(!e)return this.alert();if(e.newChoice)return e.input?(e.updateChoice(),this.render()):this.alert();if(this.choices.some(c=>c.newChoice))return this.alert();let{reorder:r,sort:s}=this.options,a=this.multiple===!0,n=this.selected;return n===void 0?this.alert():(Array.isArray(n)&&r!==!1&&s!==!0&&(n=zR.reorder(n)),this.value=a?n.map(c=>c.name):n.name,super.submit())}set choices(e=[]){this.state._choices=this.state._choices||[],this.state.choices=e;for(let r of e)this.state._choices.some(s=>s.name===r.name)||this.state._choices.push(r);if(!this._initial&&this.options.initial){this._initial=!0;let r=this.initial;if(typeof r=="string"||typeof r=="number"){let s=this.find(r);s&&(this.initial=s.index,this.focus(s,!0))}}}get choices(){return ome(this,this.state.choices||[])}set visible(e){this.state.visible=e}get visible(){return(this.state.visible||this.choices).slice(0,this.limit)}set limit(e){this.state.limit=e}get limit(){let{state:e,options:r,choices:s}=this,a=e.limit||this._limit||r.limit||s.length;return Math.min(a,this.height)}set value(e){super.value=e}get value(){return typeof super.value!="string"&&super.value===this.initial?this.input:super.value}set index(e){this.state.index=e}get index(){return Math.max(0,this.state?this.state.index:0)}get enabled(){return this.filter(this.isEnabled.bind(this))}get focused(){let e=this.choices[this.index];return e&&this.state.submitted&&this.multiple!==!0&&(e.enabled=!0),e}get selectable(){return this.choices.filter(e=>!this.isDisabled(e))}get selected(){return this.multiple?this.enabled:this.focused}};function ome(t,e){if(e instanceof Promise)return e;if(typeof e=="function"){if(zR.isAsyncFn(e))return e;e=e.call(t,t)}for(let r of e){if(Array.isArray(r.choices)){let s=r.choices.filter(a=>!t.isDisabled(a));r.enabled=s.every(a=>a.enabled===!0)}t.isDisabled(r)===!0&&delete r.enabled}return e}ame.exports=p5});var G0=_((ijt,lme)=>{"use strict";var Xat=Wv(),h5=Zo(),g5=class extends Xat{constructor(e){super(e),this.emptyError=this.options.emptyError||"No items were selected"}async dispatch(e,r){if(this.multiple)return this[r.name]?await this[r.name](e,r):await super.dispatch(e,r);this.alert()}separator(){if(this.options.separator)return super.separator();let e=this.styles.muted(this.symbols.ellipsis);return this.state.submitted?super.separator():e}pointer(e,r){return!this.multiple||this.options.pointer?super.pointer(e,r):""}indicator(e,r){return this.multiple?super.indicator(e,r):""}choiceMessage(e,r){let s=this.resolve(e.message,this.state,e,r);return e.role==="heading"&&!h5.hasColor(s)&&(s=this.styles.strong(s)),this.resolve(s,this.state,e,r)}choiceSeparator(){return":"}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await this.indicator(e,r)+(e.pad||""),c=await this.resolve(e.hint,this.state,e,r);c&&!h5.hasColor(c)&&(c=this.styles.muted(c));let f=this.indent(e),p=await this.choiceMessage(e,r),h=()=>[this.margin[3],f+a+n,p,this.margin[1],c].filter(Boolean).join(" ");return e.role==="heading"?h():e.disabled?(h5.hasColor(p)||(p=this.styles.disabled(p)),h()):(s&&(p=this.styles.em(p)),h())}async renderChoices(){if(this.state.loading==="choices")return this.styles.warning("Loading choices");if(this.state.submitted)return"";let e=this.visible.map(async(n,c)=>await this.renderChoice(n,c)),r=await Promise.all(e);r.length||r.push(this.styles.danger("No matching choices"));let s=this.margin[0]+r.join(` +`),a;return this.options.choicesHeader&&(a=await this.resolve(this.options.choicesHeader,this.state)),[a,s].filter(Boolean).join(` +`)}format(){return!this.state.submitted||this.state.cancelled?"":Array.isArray(this.selected)?this.selected.map(e=>this.styles.primary(e.name)).join(", "):this.styles.primary(this.selected.name)}async render(){let{submitted:e,size:r}=this.state,s="",a=await this.header(),n=await this.prefix(),c=await this.separator(),f=await this.message();this.options.promptLine!==!1&&(s=[n,f,c,""].join(" "),this.state.prompt=s);let p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();p&&(s+=p),h&&!s.includes(h)&&(s+=" "+h),e&&!p&&!E.trim()&&this.multiple&&this.emptyError!=null&&(s+=this.styles.danger(this.emptyError)),this.clear(r),this.write([a,s,E,C].filter(Boolean).join(` +`)),this.write(this.margin[2]),this.restore()}};lme.exports=g5});var ume=_((sjt,cme)=>{"use strict";var Zat=G0(),$at=(t,e)=>{let r=t.toLowerCase();return s=>{let n=s.toLowerCase().indexOf(r),c=e(s.slice(n,n+r.length));return n>=0?s.slice(0,n)+c+s.slice(n+r.length):s}},d5=class extends Zat{constructor(e){super(e),this.cursorShow()}moveCursor(e){this.state.cursor+=e}dispatch(e){return this.append(e)}space(e){return this.options.multiple?super.space(e):this.append(e)}append(e){let{cursor:r,input:s}=this.state;return this.input=s.slice(0,r)+e+s.slice(r),this.moveCursor(1),this.complete()}delete(){let{cursor:e,input:r}=this.state;return r?(this.input=r.slice(0,e-1)+r.slice(e),this.moveCursor(-1),this.complete()):this.alert()}deleteForward(){let{cursor:e,input:r}=this.state;return r[e]===void 0?this.alert():(this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.complete())}number(e){return this.append(e)}async complete(){this.completing=!0,this.choices=await this.suggest(this.input,this.state._choices),this.state.limit=void 0,this.index=Math.min(Math.max(this.visible.length-1,0),this.index),await this.render(),this.completing=!1}suggest(e=this.input,r=this.state._choices){if(typeof this.options.suggest=="function")return this.options.suggest.call(this,e,r);let s=e.toLowerCase();return r.filter(a=>a.message.toLowerCase().includes(s))}pointer(){return""}format(){if(!this.focused)return this.input;if(this.options.multiple&&this.state.submitted)return this.selected.map(e=>this.styles.primary(e.message)).join(", ");if(this.state.submitted){let e=this.value=this.input=this.focused.value;return this.styles.primary(e)}return this.input}async render(){if(this.state.status!=="pending")return super.render();let e=this.options.highlight?this.options.highlight.bind(this):this.styles.placeholder,r=$at(this.input,e),s=this.choices;this.choices=s.map(a=>({...a,message:r(a.message)})),await super.render(),this.choices=s}submit(){return this.options.multiple&&(this.value=this.selected.map(e=>e.name)),super.submit()}};cme.exports=d5});var y5=_((ojt,fme)=>{"use strict";var m5=Zo();fme.exports=(t,e={})=>{t.cursorHide();let{input:r="",initial:s="",pos:a,showCursor:n=!0,color:c}=e,f=c||t.styles.placeholder,p=m5.inverse(t.styles.primary),h=R=>p(t.styles.black(R)),E=r,C=" ",S=h(C);if(t.blink&&t.blink.off===!0&&(h=R=>R,S=""),n&&a===0&&s===""&&r==="")return h(C);if(n&&a===0&&(r===s||r===""))return h(s[0])+f(s.slice(1));s=m5.isPrimitive(s)?`${s}`:"",r=m5.isPrimitive(r)?`${r}`:"";let P=s&&s.startsWith(r)&&s!==r,I=P?h(s[r.length]):S;if(a!==r.length&&n===!0&&(E=r.slice(0,a)+h(r[a])+r.slice(a+1),I=""),n===!1&&(I=""),P){let R=t.styles.unstyle(E+I);return E+I+f(s.slice(R.length))}return E+I}});var XR=_((ajt,Ame)=>{"use strict";var elt=Ju(),tlt=G0(),rlt=y5(),E5=class extends tlt{constructor(e){super({...e,multiple:!0}),this.type="form",this.initial=this.options.initial,this.align=[this.options.align,"right"].find(r=>r!=null),this.emptyError="",this.values={}}async reset(e){return await super.reset(),e===!0&&(this._index=this.index),this.index=this._index,this.values={},this.choices.forEach(r=>r.reset&&r.reset()),this.render()}dispatch(e){return!!e&&this.append(e)}append(e){let r=this.focused;if(!r)return this.alert();let{cursor:s,input:a}=r;return r.value=r.input=a.slice(0,s)+e+a.slice(s),r.cursor++,this.render()}delete(){let e=this.focused;if(!e||e.cursor<=0)return this.alert();let{cursor:r,input:s}=e;return e.value=e.input=s.slice(0,r-1)+s.slice(r),e.cursor--,this.render()}deleteForward(){let e=this.focused;if(!e)return this.alert();let{cursor:r,input:s}=e;if(s[r]===void 0)return this.alert();let a=`${s}`.slice(0,r)+`${s}`.slice(r+1);return e.value=e.input=a,this.render()}right(){let e=this.focused;return e?e.cursor>=e.input.length?this.alert():(e.cursor++,this.render()):this.alert()}left(){let e=this.focused;return e?e.cursor<=0?this.alert():(e.cursor--,this.render()):this.alert()}space(e,r){return this.dispatch(e,r)}number(e,r){return this.dispatch(e,r)}next(){let e=this.focused;if(!e)return this.alert();let{initial:r,input:s}=e;return r&&r.startsWith(s)&&s!==r?(e.value=e.input=r,e.cursor=e.value.length,this.render()):super.next()}prev(){let e=this.focused;return e?e.cursor===0?super.prev():(e.value=e.input="",e.cursor=0,this.render()):this.alert()}separator(){return""}format(e){return this.state.submitted?"":super.format(e)}pointer(){return""}indicator(e){return e.input?"\u29BF":"\u2299"}async choiceSeparator(e,r){let s=await this.resolve(e.separator,this.state,e,r)||":";return s?" "+this.styles.disabled(s):""}async renderChoice(e,r){await this.onChoice(e,r);let{state:s,styles:a}=this,{cursor:n,initial:c="",name:f,hint:p,input:h=""}=e,{muted:E,submitted:C,primary:S,danger:P}=a,I=p,R=this.index===r,N=e.validate||(()=>!0),U=await this.choiceSeparator(e,r),W=e.message;this.align==="right"&&(W=W.padStart(this.longest+1," ")),this.align==="left"&&(W=W.padEnd(this.longest+1," "));let ee=this.values[f]=h||c,ie=h?"success":"dark";await N.call(e,ee,this.state)!==!0&&(ie="danger");let ue=a[ie],le=ue(await this.indicator(e,r))+(e.pad||""),me=this.indent(e),pe=()=>[me,le,W+U,h,I].filter(Boolean).join(" ");if(s.submitted)return W=elt.unstyle(W),h=C(h),I="",pe();if(e.format)h=await e.format.call(this,h,e,r);else{let Be=this.styles.muted;h=rlt(this,{input:h,initial:c,pos:n,showCursor:R,color:Be})}return this.isValue(h)||(h=this.styles.muted(this.symbols.ellipsis)),e.result&&(this.values[f]=await e.result.call(this,ee,e,r)),R&&(W=S(W)),e.error?h+=(h?" ":"")+P(e.error.trim()):e.hint&&(h+=(h?" ":"")+E(e.hint.trim())),pe()}async submit(){return this.value=this.values,super.base.submit.call(this)}};Ame.exports=E5});var I5=_((ljt,hme)=>{"use strict";var nlt=XR(),ilt=()=>{throw new Error("expected prompt to have a custom authenticate method")},pme=(t=ilt)=>{class e extends nlt{constructor(s){super(s)}async submit(){this.value=await t.call(this,this.values,this.state),super.base.submit.call(this)}static create(s){return pme(s)}}return e};hme.exports=pme()});var mme=_((cjt,dme)=>{"use strict";var slt=I5();function olt(t,e){return t.username===this.options.username&&t.password===this.options.password}var gme=(t=olt)=>{let e=[{name:"username",message:"username"},{name:"password",message:"password",format(s){return this.options.showPassword?s:(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(s.length))}}];class r extends slt.create(t){constructor(a){super({...a,choices:e})}static create(a){return gme(a)}}return r};dme.exports=gme()});var ZR=_((ujt,yme)=>{"use strict";var alt=nC(),{isPrimitive:llt,hasColor:clt}=Zo(),C5=class extends alt{constructor(e){super(e),this.cursorHide()}async initialize(){let e=await this.resolve(this.initial,this.state);this.input=await this.cast(e),await super.initialize()}dispatch(e){return this.isValue(e)?(this.input=e,this.submit()):this.alert()}format(e){let{styles:r,state:s}=this;return s.submitted?r.success(e):r.primary(e)}cast(e){return this.isTrue(e)}isTrue(e){return/^[ty1]/i.test(e)}isFalse(e){return/^[fn0]/i.test(e)}isValue(e){return llt(e)&&(this.isTrue(e)||this.isFalse(e))}async hint(){if(this.state.status==="pending"){let e=await this.element("hint");return clt(e)?e:this.styles.muted(e)}}async render(){let{input:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=this.styles.muted(this.default),f=[s,n,c,a].filter(Boolean).join(" ");this.state.prompt=f;let p=await this.header(),h=this.value=this.cast(e),E=await this.format(h),C=await this.error()||await this.hint(),S=await this.footer();C&&!f.includes(C)&&(E+=" "+C),f+=" "+E,this.clear(r),this.write([p,f,S].filter(Boolean).join(` +`)),this.restore()}set value(e){super.value=e}get value(){return this.cast(super.value)}};yme.exports=C5});var Ime=_((fjt,Eme)=>{"use strict";var ult=ZR(),w5=class extends ult{constructor(e){super(e),this.default=this.options.default||(this.initial?"(Y/n)":"(y/N)")}};Eme.exports=w5});var wme=_((Ajt,Cme)=>{"use strict";var flt=G0(),Alt=XR(),iC=Alt.prototype,B5=class extends flt{constructor(e){super({...e,multiple:!0}),this.align=[this.options.align,"left"].find(r=>r!=null),this.emptyError="",this.values={}}dispatch(e,r){let s=this.focused,a=s.parent||{};return!s.editable&&!a.editable&&(e==="a"||e==="i")?super[e]():iC.dispatch.call(this,e,r)}append(e,r){return iC.append.call(this,e,r)}delete(e,r){return iC.delete.call(this,e,r)}space(e){return this.focused.editable?this.append(e):super.space()}number(e){return this.focused.editable?this.append(e):super.number(e)}next(){return this.focused.editable?iC.next.call(this):super.next()}prev(){return this.focused.editable?iC.prev.call(this):super.prev()}async indicator(e,r){let s=e.indicator||"",a=e.editable?s:super.indicator(e,r);return await this.resolve(a,this.state,e,r)||""}indent(e){return e.role==="heading"?"":e.editable?" ":" "}async renderChoice(e,r){return e.indent="",e.editable?iC.renderChoice.call(this,e,r):super.renderChoice(e,r)}error(){return""}footer(){return this.state.error}async validate(){let e=!0;for(let r of this.choices){if(typeof r.validate!="function"||r.role==="heading")continue;let s=r.parent?this.value[r.parent.name]:this.value;if(r.editable?s=r.value===r.name?r.initial||"":r.value:this.isDisabled(r)||(s=r.enabled===!0),e=await r.validate(s,this.state),e!==!0)break}return e!==!0&&(this.state.error=typeof e=="string"?e:"Invalid Input"),e}submit(){if(this.focused.newChoice===!0)return super.submit();if(this.choices.some(e=>e.newChoice))return this.alert();this.value={};for(let e of this.choices){let r=e.parent?this.value[e.parent.name]:this.value;if(e.role==="heading"){this.value[e.name]={};continue}e.editable?r[e.name]=e.value===e.name?e.initial||"":e.value:this.isDisabled(e)||(r[e.name]=e.enabled===!0)}return this.base.submit.call(this)}};Cme.exports=B5});var vm=_((pjt,Bme)=>{"use strict";var plt=nC(),hlt=y5(),{isPrimitive:glt}=Zo(),v5=class extends plt{constructor(e){super(e),this.initial=glt(this.initial)?String(this.initial):"",this.initial&&this.cursorHide(),this.state.prevCursor=0,this.state.clipboard=[]}async keypress(e,r={}){let s=this.state.prevKeypress;return this.state.prevKeypress=r,this.options.multiline===!0&&r.name==="return"&&(!s||s.name!=="return")?this.append(` +`,r):super.keypress(e,r)}moveCursor(e){this.cursor+=e}reset(){return this.input=this.value="",this.cursor=0,this.render()}dispatch(e,r){if(!e||r.ctrl||r.code)return this.alert();this.append(e)}append(e){let{cursor:r,input:s}=this.state;this.input=`${s}`.slice(0,r)+e+`${s}`.slice(r),this.moveCursor(String(e).length),this.render()}insert(e){this.append(e)}delete(){let{cursor:e,input:r}=this.state;if(e<=0)return this.alert();this.input=`${r}`.slice(0,e-1)+`${r}`.slice(e),this.moveCursor(-1),this.render()}deleteForward(){let{cursor:e,input:r}=this.state;if(r[e]===void 0)return this.alert();this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.render()}cutForward(){let e=this.cursor;if(this.input.length<=e)return this.alert();this.state.clipboard.push(this.input.slice(e)),this.input=this.input.slice(0,e),this.render()}cutLeft(){let e=this.cursor;if(e===0)return this.alert();let r=this.input.slice(0,e),s=this.input.slice(e),a=r.split(" ");this.state.clipboard.push(a.pop()),this.input=a.join(" "),this.cursor=this.input.length,this.input+=s,this.render()}paste(){if(!this.state.clipboard.length)return this.alert();this.insert(this.state.clipboard.pop()),this.render()}toggleCursor(){this.state.prevCursor?(this.cursor=this.state.prevCursor,this.state.prevCursor=0):(this.state.prevCursor=this.cursor,this.cursor=0),this.render()}first(){this.cursor=0,this.render()}last(){this.cursor=this.input.length-1,this.render()}next(){let e=this.initial!=null?String(this.initial):"";if(!e||!e.startsWith(this.input))return this.alert();this.input=this.initial,this.cursor=this.initial.length,this.render()}prev(){if(!this.input)return this.alert();this.reset()}backward(){return this.left()}forward(){return this.right()}right(){return this.cursor>=this.input.length?this.alert():(this.moveCursor(1),this.render())}left(){return this.cursor<=0?this.alert():(this.moveCursor(-1),this.render())}isValue(e){return!!e}async format(e=this.value){let r=await this.resolve(this.initial,this.state);return this.state.submitted?this.styles.submitted(e||r):hlt(this,{input:e,initial:r,pos:this.cursor})}async render(){let e=this.state.size,r=await this.prefix(),s=await this.separator(),a=await this.message(),n=[r,a,s].filter(Boolean).join(" ");this.state.prompt=n;let c=await this.header(),f=await this.format(),p=await this.error()||await this.hint(),h=await this.footer();p&&!f.includes(p)&&(f+=" "+p),n+=" "+f,this.clear(e),this.write([c,n,h].filter(Boolean).join(` +`)),this.restore()}};Bme.exports=v5});var Sme=_((hjt,vme)=>{"use strict";var dlt=t=>t.filter((e,r)=>t.lastIndexOf(e)===r),$R=t=>dlt(t).filter(Boolean);vme.exports=(t,e={},r="")=>{let{past:s=[],present:a=""}=e,n,c;switch(t){case"prev":case"undo":return n=s.slice(0,s.length-1),c=s[s.length-1]||"",{past:$R([r,...n]),present:c};case"next":case"redo":return n=s.slice(1),c=s[0]||"",{past:$R([...n,r]),present:c};case"save":return{past:$R([...s,r]),present:""};case"remove":return c=$R(s.filter(f=>f!==r)),a="",c.length&&(a=c.pop()),{past:c,present:a};default:throw new Error(`Invalid action: "${t}"`)}}});var D5=_((gjt,bme)=>{"use strict";var mlt=vm(),Dme=Sme(),S5=class extends mlt{constructor(e){super(e);let r=this.options.history;if(r&&r.store){let s=r.values||this.initial;this.autosave=!!r.autosave,this.store=r.store,this.data=this.store.get("values")||{past:[],present:s},this.initial=this.data.present||this.data.past[this.data.past.length-1]}}completion(e){return this.store?(this.data=Dme(e,this.data,this.input),this.data.present?(this.input=this.data.present,this.cursor=this.input.length,this.render()):this.alert()):this.alert()}altUp(){return this.completion("prev")}altDown(){return this.completion("next")}prev(){return this.save(),super.prev()}save(){this.store&&(this.data=Dme("save",this.data,this.input),this.store.set("values",this.data))}submit(){return this.store&&this.autosave===!0&&this.save(),super.submit()}};bme.exports=S5});var xme=_((djt,Pme)=>{"use strict";var ylt=vm(),b5=class extends ylt{format(){return""}};Pme.exports=b5});var Qme=_((mjt,kme)=>{"use strict";var Elt=vm(),P5=class extends Elt{constructor(e={}){super(e),this.sep=this.options.separator||/, */,this.initial=e.initial||""}split(e=this.value){return e?String(e).split(this.sep):[]}format(){let e=this.state.submitted?this.styles.primary:r=>r;return this.list.map(e).join(", ")}async submit(e){let r=this.state.error||await this.validate(this.list,this.state);return r!==!0?(this.state.error=r,super.submit()):(this.value=this.list,super.submit())}get list(){return this.split()}};kme.exports=P5});var Rme=_((yjt,Tme)=>{"use strict";var Ilt=G0(),x5=class extends Ilt{constructor(e){super({...e,multiple:!0})}};Tme.exports=x5});var Q5=_((Ejt,Fme)=>{"use strict";var Clt=vm(),k5=class extends Clt{constructor(e={}){super({style:"number",...e}),this.min=this.isValue(e.min)?this.toNumber(e.min):-1/0,this.max=this.isValue(e.max)?this.toNumber(e.max):1/0,this.delay=e.delay!=null?e.delay:1e3,this.float=e.float!==!1,this.round=e.round===!0||e.float===!1,this.major=e.major||10,this.minor=e.minor||1,this.initial=e.initial!=null?e.initial:"",this.input=String(this.initial),this.cursor=this.input.length,this.cursorShow()}append(e){return!/[-+.]/.test(e)||e==="."&&this.input.includes(".")?this.alert("invalid number"):super.append(e)}number(e){return super.append(e)}next(){return this.input&&this.input!==this.initial?this.alert():this.isValue(this.initial)?(this.input=this.initial,this.cursor=String(this.initial).length,this.render()):this.alert()}up(e){let r=e||this.minor,s=this.toNumber(this.input);return s>this.max+r?this.alert():(this.input=`${s+r}`,this.render())}down(e){let r=e||this.minor,s=this.toNumber(this.input);return sthis.isValue(r));return this.value=this.toNumber(e||0),super.submit()}};Fme.exports=k5});var Ome=_((Ijt,Nme)=>{Nme.exports=Q5()});var Mme=_((Cjt,Lme)=>{"use strict";var wlt=vm(),T5=class extends wlt{constructor(e){super(e),this.cursorShow()}format(e=this.input){return this.keypressed?(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length)):""}};Lme.exports=T5});var Hme=_((wjt,_me)=>{"use strict";var Blt=Ju(),vlt=Wv(),Ume=Zo(),R5=class extends vlt{constructor(e={}){super(e),this.widths=[].concat(e.messageWidth||50),this.align=[].concat(e.align||"left"),this.linebreak=e.linebreak||!1,this.edgeLength=e.edgeLength||3,this.newline=e.newline||` + `;let r=e.startNumber||1;typeof this.scale=="number"&&(this.scaleKey=!1,this.scale=Array(this.scale).fill(0).map((s,a)=>({name:a+r})))}async reset(){return this.tableized=!1,await super.reset(),this.render()}tableize(){if(this.tableized===!0)return;this.tableized=!0;let e=0;for(let r of this.choices){e=Math.max(e,r.message.length),r.scaleIndex=r.initial||2,r.scale=[];for(let s=0;s=this.scale.length-1?this.alert():(e.scaleIndex++,this.render())}left(){let e=this.focused;return e.scaleIndex<=0?this.alert():(e.scaleIndex--,this.render())}indent(){return""}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.index)).join(", "):""}pointer(){return""}renderScaleKey(){return this.scaleKey===!1||this.state.submitted?"":["",...this.scale.map(s=>` ${s.name} - ${s.message}`)].map(s=>this.styles.muted(s)).join(` +`)}renderScaleHeading(e){let r=this.scale.map(p=>p.name);typeof this.options.renderScaleHeading=="function"&&(r=this.options.renderScaleHeading.call(this,e));let s=this.scaleLength-r.join("").length,a=Math.round(s/(r.length-1)),c=r.map(p=>this.styles.strong(p)).join(" ".repeat(a)),f=" ".repeat(this.widths[0]);return this.margin[3]+f+this.margin[1]+c}scaleIndicator(e,r,s){if(typeof this.options.scaleIndicator=="function")return this.options.scaleIndicator.call(this,e,r,s);let a=e.scaleIndex===r.index;return r.disabled?this.styles.hint(this.symbols.radio.disabled):a?this.styles.success(this.symbols.radio.on):this.symbols.radio.off}renderScale(e,r){let s=e.scale.map(n=>this.scaleIndicator(e,n,r)),a=this.term==="Hyper"?"":" ";return s.join(a+this.symbols.line.repeat(this.edgeLength))}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await e.hint;n&&!Ume.hasColor(n)&&(n=this.styles.muted(n));let c=I=>this.margin[3]+I.replace(/\s+$/,"").padEnd(this.widths[0]," "),f=this.newline,p=this.indent(e),h=await this.resolve(e.message,this.state,e,r),E=await this.renderScale(e,r),C=this.margin[1]+this.margin[3];this.scaleLength=Blt.unstyle(E).length,this.widths[0]=Math.min(this.widths[0],this.width-this.scaleLength-C.length);let P=Ume.wordWrap(h,{width:this.widths[0],newline:f}).split(` +`).map(I=>c(I)+this.margin[1]);return s&&(E=this.styles.info(E),P=P.map(I=>this.styles.info(I))),P[0]+=E,this.linebreak&&P.push(""),[p+a,P.join(` +`)].filter(Boolean)}async renderChoices(){if(this.state.submitted)return"";this.tableize();let e=this.visible.map(async(a,n)=>await this.renderChoice(a,n)),r=await Promise.all(e),s=await this.renderScaleHeading();return this.margin[0]+[s,...r.map(a=>a.join(" "))].join(` +`)}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c="";this.options.promptLine!==!1&&(c=[s,n,a,""].join(" "),this.state.prompt=c);let f=await this.header(),p=await this.format(),h=await this.renderScaleKey(),E=await this.error()||await this.hint(),C=await this.renderChoices(),S=await this.footer(),P=this.emptyError;p&&(c+=p),E&&!c.includes(E)&&(c+=" "+E),e&&!p&&!C.trim()&&this.multiple&&P!=null&&(c+=this.styles.danger(P)),this.clear(r),this.write([f,c,h,C,S].filter(Boolean).join(` +`)),this.state.submitted||this.write(this.margin[2]),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIndex;return this.base.submit.call(this)}};_me.exports=R5});var qme=_((Bjt,Gme)=>{"use strict";var jme=Ju(),Slt=(t="")=>typeof t=="string"?t.replace(/^['"]|['"]$/g,""):"",N5=class{constructor(e){this.name=e.key,this.field=e.field||{},this.value=Slt(e.initial||this.field.initial||""),this.message=e.message||this.name,this.cursor=0,this.input="",this.lines=[]}},Dlt=async(t={},e={},r=s=>s)=>{let s=new Set,a=t.fields||[],n=t.template,c=[],f=[],p=[],h=1;typeof n=="function"&&(n=await n());let E=-1,C=()=>n[++E],S=()=>n[E+1],P=I=>{I.line=h,c.push(I)};for(P({type:"bos",value:""});Eie.name===U.key);U.field=a.find(ie=>ie.name===U.key),ee||(ee=new N5(U),f.push(ee)),ee.lines.push(U.line-1);continue}let R=c[c.length-1];R.type==="text"&&R.line===h?R.value+=I:P({type:"text",value:I})}return P({type:"eos",value:""}),{input:n,tabstops:c,unique:s,keys:p,items:f}};Gme.exports=async t=>{let e=t.options,r=new Set(e.required===!0?[]:e.required||[]),s={...e.values,...e.initial},{tabstops:a,items:n,keys:c}=await Dlt(e,s),f=F5("result",t,e),p=F5("format",t,e),h=F5("validate",t,e,!0),E=t.isValue.bind(t);return async(C={},S=!1)=>{let P=0;C.required=r,C.items=n,C.keys=c,C.output="";let I=async(W,ee,ie,ue)=>{let le=await h(W,ee,ie,ue);return le===!1?"Invalid field "+ie.name:le};for(let W of a){let ee=W.value,ie=W.key;if(W.type!=="template"){ee&&(C.output+=ee);continue}if(W.type==="template"){let ue=n.find(Ce=>Ce.name===ie);e.required===!0&&C.required.add(ue.name);let le=[ue.input,C.values[ue.value],ue.value,ee].find(E),pe=(ue.field||{}).message||W.inner;if(S){let Ce=await I(C.values[ie],C,ue,P);if(Ce&&typeof Ce=="string"||Ce===!1){C.invalid.set(ie,Ce);continue}C.invalid.delete(ie);let g=await f(C.values[ie],C,ue,P);C.output+=jme.unstyle(g);continue}ue.placeholder=!1;let Be=ee;ee=await p(ee,C,ue,P),le!==ee?(C.values[ie]=le,ee=t.styles.typing(le),C.missing.delete(pe)):(C.values[ie]=void 0,le=`<${pe}>`,ee=t.styles.primary(le),ue.placeholder=!0,C.required.has(ie)&&C.missing.add(pe)),C.missing.has(pe)&&C.validating&&(ee=t.styles.warning(le)),C.invalid.has(ie)&&C.validating&&(ee=t.styles.danger(le)),P===C.index&&(Be!==ee?ee=t.styles.underline(ee):ee=t.styles.heading(jme.unstyle(ee))),P++}ee&&(C.output+=ee)}let R=C.output.split(` +`).map(W=>" "+W),N=n.length,U=0;for(let W of n)C.invalid.has(W.name)&&W.lines.forEach(ee=>{R[ee][0]===" "&&(R[ee]=C.styles.danger(C.symbols.bullet)+R[ee].slice(1))}),t.isValue(C.values[W.name])&&U++;return C.completed=(U/N*100).toFixed(0),C.output=R.join(` +`),C.output}};function F5(t,e,r,s){return(a,n,c,f)=>typeof c.field[t]=="function"?c.field[t].call(e,a,n,c,f):[s,a].find(p=>e.isValue(p))}});var Yme=_((vjt,Wme)=>{"use strict";var blt=Ju(),Plt=qme(),xlt=nC(),O5=class extends xlt{constructor(e){super(e),this.cursorHide(),this.reset(!0)}async initialize(){this.interpolate=await Plt(this),await super.initialize()}async reset(e){this.state.keys=[],this.state.invalid=new Map,this.state.missing=new Set,this.state.completed=0,this.state.values={},e!==!0&&(await this.initialize(),await this.render())}moveCursor(e){let r=this.getItem();this.cursor+=e,r.cursor+=e}dispatch(e,r){if(!r.code&&!r.ctrl&&e!=null&&this.getItem()){this.append(e,r);return}this.alert()}append(e,r){let s=this.getItem(),a=s.input.slice(0,this.cursor),n=s.input.slice(this.cursor);this.input=s.input=`${a}${e}${n}`,this.moveCursor(1),this.render()}delete(){let e=this.getItem();if(this.cursor<=0||!e.input)return this.alert();let r=e.input.slice(this.cursor),s=e.input.slice(0,this.cursor-1);this.input=e.input=`${s}${r}`,this.moveCursor(-1),this.render()}increment(e){return e>=this.state.keys.length-1?0:e+1}decrement(e){return e<=0?this.state.keys.length-1:e-1}first(){this.state.index=0,this.render()}last(){this.state.index=this.state.keys.length-1,this.render()}right(){if(this.cursor>=this.input.length)return this.alert();this.moveCursor(1),this.render()}left(){if(this.cursor<=0)return this.alert();this.moveCursor(-1),this.render()}prev(){this.state.index=this.decrement(this.state.index),this.getItem(),this.render()}next(){this.state.index=this.increment(this.state.index),this.getItem(),this.render()}up(){this.prev()}down(){this.next()}format(e){let r=this.state.completed<100?this.styles.warning:this.styles.success;return this.state.submitted===!0&&this.state.completed!==100&&(r=this.styles.danger),r(`${this.state.completed}% completed`)}async render(){let{index:e,keys:r=[],submitted:s,size:a}=this.state,n=[this.options.newline,` +`].find(W=>W!=null),c=await this.prefix(),f=await this.separator(),p=await this.message(),h=[c,p,f].filter(Boolean).join(" ");this.state.prompt=h;let E=await this.header(),C=await this.error()||"",S=await this.hint()||"",P=s?"":await this.interpolate(this.state),I=this.state.key=r[e]||"",R=await this.format(I),N=await this.footer();R&&(h+=" "+R),S&&!R&&this.state.completed===0&&(h+=" "+S),this.clear(a);let U=[E,h,P,N,C.trim()];this.write(U.filter(Boolean).join(n)),this.restore()}getItem(e){let{items:r,keys:s,index:a}=this.state,n=r.find(c=>c.name===s[a]);return n&&n.input!=null&&(this.input=n.input,this.cursor=n.cursor),n}async submit(){typeof this.interpolate!="function"&&await this.initialize(),await this.interpolate(this.state,!0);let{invalid:e,missing:r,output:s,values:a}=this.state;if(e.size){let f="";for(let[p,h]of e)f+=`Invalid ${p}: ${h} +`;return this.state.error=f,super.submit()}if(r.size)return this.state.error="Required: "+[...r.keys()].join(", "),super.submit();let c=blt.unstyle(s).split(` +`).map(f=>f.slice(1)).join(` +`);return this.value={values:a,result:c},super.submit()}};Wme.exports=O5});var Jme=_((Sjt,Vme)=>{"use strict";var klt="(Use + to sort)",Qlt=G0(),L5=class extends Qlt{constructor(e){super({...e,reorder:!1,sort:!0,multiple:!0}),this.state.hint=[this.options.hint,klt].find(this.isValue.bind(this))}indicator(){return""}async renderChoice(e,r){let s=await super.renderChoice(e,r),a=this.symbols.identicalTo+" ",n=this.index===r&&this.sorting?this.styles.muted(a):" ";return this.options.drag===!1&&(n=""),this.options.numbered===!0?n+`${r+1} - `+s:n+s}get selected(){return this.choices}submit(){return this.value=this.choices.map(e=>e.value),super.submit()}};Vme.exports=L5});var zme=_((Djt,Kme)=>{"use strict";var Tlt=Wv(),M5=class extends Tlt{constructor(e={}){if(super(e),this.emptyError=e.emptyError||"No items were selected",this.term=process.env.TERM_PROGRAM,!this.options.header){let r=["","4 - Strongly Agree","3 - Agree","2 - Neutral","1 - Disagree","0 - Strongly Disagree",""];r=r.map(s=>this.styles.muted(s)),this.state.header=r.join(` + `)}}async toChoices(...e){if(this.createdScales)return!1;this.createdScales=!0;let r=await super.toChoices(...e);for(let s of r)s.scale=Rlt(5,this.options),s.scaleIdx=2;return r}dispatch(){this.alert()}space(){let e=this.focused,r=e.scale[e.scaleIdx],s=r.selected;return e.scale.forEach(a=>a.selected=!1),r.selected=!s,this.render()}indicator(){return""}pointer(){return""}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIdx>=e.scale.length-1?this.alert():(e.scaleIdx++,this.render())}left(){let e=this.focused;return e.scaleIdx<=0?this.alert():(e.scaleIdx--,this.render())}indent(){return" "}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=this.term==="Hyper",n=a?9:8,c=a?"":" ",f=this.symbols.line.repeat(n),p=" ".repeat(n+(a?0:1)),h=ee=>(ee?this.styles.success("\u25C9"):"\u25EF")+c,E=r+1+".",C=s?this.styles.heading:this.styles.noop,S=await this.resolve(e.message,this.state,e,r),P=this.indent(e),I=P+e.scale.map((ee,ie)=>h(ie===e.scaleIdx)).join(f),R=ee=>ee===e.scaleIdx?C(ee):ee,N=P+e.scale.map((ee,ie)=>R(ie)).join(p),U=()=>[E,S].filter(Boolean).join(" "),W=()=>[U(),I,N," "].filter(Boolean).join(` +`);return s&&(I=this.styles.cyan(I),N=this.styles.cyan(N)),W()}async renderChoices(){if(this.state.submitted)return"";let e=this.visible.map(async(s,a)=>await this.renderChoice(s,a)),r=await Promise.all(e);return r.length||r.push(this.styles.danger("No matching choices")),r.join(` +`)}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.scaleIdx)).join(", "):""}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=[s,n,a].filter(Boolean).join(" ");this.state.prompt=c;let f=await this.header(),p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();(p||!h)&&(c+=" "+p),h&&!c.includes(h)&&(c+=" "+h),e&&!p&&!E&&this.multiple&&this.type!=="form"&&(c+=this.styles.danger(this.emptyError)),this.clear(r),this.write([c,f,E,C].filter(Boolean).join(` +`)),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIdx;return this.base.submit.call(this)}};function Rlt(t,e={}){if(Array.isArray(e.scale))return e.scale.map(s=>({...s}));let r=[];for(let s=1;s{Xme.exports=D5()});var eye=_((Pjt,$me)=>{"use strict";var Flt=ZR(),U5=class extends Flt{async initialize(){await super.initialize(),this.value=this.initial=!!this.options.initial,this.disabled=this.options.disabled||"no",this.enabled=this.options.enabled||"yes",await this.render()}reset(){this.value=this.initial,this.render()}delete(){this.alert()}toggle(){this.value=!this.value,this.render()}enable(){if(this.value===!0)return this.alert();this.value=!0,this.render()}disable(){if(this.value===!1)return this.alert();this.value=!1,this.render()}up(){this.toggle()}down(){this.toggle()}right(){this.toggle()}left(){this.toggle()}next(){this.toggle()}prev(){this.toggle()}dispatch(e="",r){switch(e.toLowerCase()){case" ":return this.toggle();case"1":case"y":case"t":return this.enable();case"0":case"n":case"f":return this.disable();default:return this.alert()}}format(){let e=s=>this.styles.primary.underline(s);return[this.value?this.disabled:e(this.disabled),this.value?e(this.enabled):this.enabled].join(this.styles.muted(" / "))}async render(){let{size:e}=this.state,r=await this.header(),s=await this.prefix(),a=await this.separator(),n=await this.message(),c=await this.format(),f=await this.error()||await this.hint(),p=await this.footer(),h=[s,n,a,c].join(" ");this.state.prompt=h,f&&!h.includes(f)&&(h+=" "+f),this.clear(e),this.write([r,h,p].filter(Boolean).join(` +`)),this.write(this.margin[2]),this.restore()}};$me.exports=U5});var rye=_((xjt,tye)=>{"use strict";var Nlt=G0(),_5=class extends Nlt{constructor(e){if(super(e),typeof this.options.correctChoice!="number"||this.options.correctChoice<0)throw new Error("Please specify the index of the correct answer from the list of choices")}async toChoices(e,r){let s=await super.toChoices(e,r);if(s.length<2)throw new Error("Please give at least two choices to the user");if(this.options.correctChoice>s.length)throw new Error("Please specify the index of the correct answer from the list of choices");return s}check(e){return e.index===this.options.correctChoice}async result(e){return{selectedAnswer:e,correctAnswer:this.options.choices[this.options.correctChoice].value,correct:await this.check(this.state)}}};tye.exports=_5});var iye=_(H5=>{"use strict";var nye=Zo(),ks=(t,e)=>{nye.defineExport(H5,t,e),nye.defineExport(H5,t.toLowerCase(),e)};ks("AutoComplete",()=>ume());ks("BasicAuth",()=>mme());ks("Confirm",()=>Ime());ks("Editable",()=>wme());ks("Form",()=>XR());ks("Input",()=>D5());ks("Invisible",()=>xme());ks("List",()=>Qme());ks("MultiSelect",()=>Rme());ks("Numeral",()=>Ome());ks("Password",()=>Mme());ks("Scale",()=>Hme());ks("Select",()=>G0());ks("Snippet",()=>Yme());ks("Sort",()=>Jme());ks("Survey",()=>zme());ks("Text",()=>Zme());ks("Toggle",()=>eye());ks("Quiz",()=>rye())});var oye=_((Qjt,sye)=>{sye.exports={ArrayPrompt:Wv(),AuthPrompt:I5(),BooleanPrompt:ZR(),NumberPrompt:Q5(),StringPrompt:vm()}});var Vv=_((Tjt,lye)=>{"use strict";var aye=Ie("assert"),G5=Ie("events"),q0=Zo(),zu=class extends G5{constructor(e,r){super(),this.options=q0.merge({},e),this.answers={...r}}register(e,r){if(q0.isObject(e)){for(let a of Object.keys(e))this.register(a,e[a]);return this}aye.equal(typeof r,"function","expected a function");let s=e.toLowerCase();return r.prototype instanceof this.Prompt?this.prompts[s]=r:this.prompts[s]=r(this.Prompt,this),this}async prompt(e=[]){for(let r of[].concat(e))try{typeof r=="function"&&(r=await r.call(this)),await this.ask(q0.merge({},this.options,r))}catch(s){return Promise.reject(s)}return this.answers}async ask(e){typeof e=="function"&&(e=await e.call(this));let r=q0.merge({},this.options,e),{type:s,name:a}=e,{set:n,get:c}=q0;if(typeof s=="function"&&(s=await s.call(this,e,this.answers)),!s)return this.answers[a];aye(this.prompts[s],`Prompt "${s}" is not registered`);let f=new this.prompts[s](r),p=c(this.answers,a);f.state.answers=this.answers,f.enquirer=this,a&&f.on("submit",E=>{this.emit("answer",a,E,f),n(this.answers,a,E)});let h=f.emit.bind(f);return f.emit=(...E)=>(this.emit.call(this,...E),h(...E)),this.emit("prompt",f,this),r.autofill&&p!=null?(f.value=f.input=p,r.autofill==="show"&&await f.submit()):p=f.value=await f.run(),p}use(e){return e.call(this,this),this}set Prompt(e){this._Prompt=e}get Prompt(){return this._Prompt||this.constructor.Prompt}get prompts(){return this.constructor.prompts}static set Prompt(e){this._Prompt=e}static get Prompt(){return this._Prompt||nC()}static get prompts(){return iye()}static get types(){return oye()}static get prompt(){let e=(r,...s)=>{let a=new this(...s),n=a.emit.bind(a);return a.emit=(...c)=>(e.emit(...c),n(...c)),a.prompt(r)};return q0.mixinEmitter(e,new G5),e}};q0.mixinEmitter(zu,new G5);var j5=zu.prompts;for(let t of Object.keys(j5)){let e=t.toLowerCase(),r=s=>new j5[t](s).run();zu.prompt[e]=r,zu[e]=r,zu[t]||Reflect.defineProperty(zu,t,{get:()=>j5[t]})}var Yv=t=>{q0.defineExport(zu,t,()=>zu.types[t])};Yv("ArrayPrompt");Yv("AuthPrompt");Yv("BooleanPrompt");Yv("NumberPrompt");Yv("StringPrompt");lye.exports=zu});var dye=_((tGt,qlt)=>{qlt.exports={name:"@yarnpkg/cli",version:"4.12.0",license:"BSD-2-Clause",main:"./sources/index.ts",exports:{".":"./sources/index.ts","./polyfills":"./sources/polyfills.ts","./package.json":"./package.json"},dependencies:{"@yarnpkg/core":"workspace:^","@yarnpkg/fslib":"workspace:^","@yarnpkg/libzip":"workspace:^","@yarnpkg/parsers":"workspace:^","@yarnpkg/plugin-catalog":"workspace:^","@yarnpkg/plugin-compat":"workspace:^","@yarnpkg/plugin-constraints":"workspace:^","@yarnpkg/plugin-dlx":"workspace:^","@yarnpkg/plugin-essentials":"workspace:^","@yarnpkg/plugin-exec":"workspace:^","@yarnpkg/plugin-file":"workspace:^","@yarnpkg/plugin-git":"workspace:^","@yarnpkg/plugin-github":"workspace:^","@yarnpkg/plugin-http":"workspace:^","@yarnpkg/plugin-init":"workspace:^","@yarnpkg/plugin-interactive-tools":"workspace:^","@yarnpkg/plugin-jsr":"workspace:^","@yarnpkg/plugin-link":"workspace:^","@yarnpkg/plugin-nm":"workspace:^","@yarnpkg/plugin-npm":"workspace:^","@yarnpkg/plugin-npm-cli":"workspace:^","@yarnpkg/plugin-pack":"workspace:^","@yarnpkg/plugin-patch":"workspace:^","@yarnpkg/plugin-pnp":"workspace:^","@yarnpkg/plugin-pnpm":"workspace:^","@yarnpkg/plugin-stage":"workspace:^","@yarnpkg/plugin-typescript":"workspace:^","@yarnpkg/plugin-version":"workspace:^","@yarnpkg/plugin-workspace-tools":"workspace:^","@yarnpkg/shell":"workspace:^","ci-info":"^4.0.0",clipanion:"^4.0.0-rc.2",semver:"^7.1.2",tslib:"^2.4.0",typanion:"^3.14.0"},devDependencies:{"@types/semver":"^7.1.0","@yarnpkg/builder":"workspace:^","@yarnpkg/monorepo":"workspace:^","@yarnpkg/pnpify":"workspace:^"},peerDependencies:{"@yarnpkg/core":"workspace:^"},scripts:{postpack:"rm -rf lib",prepack:'run build:compile "$(pwd)"',"build:cli+hook":"run build:pnp:hook && builder build bundle","build:cli":"builder build bundle","run:cli":"builder run","update-local":"run build:cli --no-git-hash && rsync -a --delete bundles/ bin/"},publishConfig:{main:"./lib/index.js",bin:null,exports:{".":"./lib/index.js","./package.json":"./package.json"}},files:["/lib/**/*","!/lib/pluginConfiguration.*","!/lib/cli.*"],"@yarnpkg/builder":{bundles:{standard:["@yarnpkg/plugin-essentials","@yarnpkg/plugin-catalog","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]}},repository:{type:"git",url:"git+https://github.com/yarnpkg/berry.git",directory:"packages/yarnpkg-cli"},engines:{node:">=18.12.0"}}});var iq=_((R9t,Pye)=>{"use strict";Pye.exports=function(e,r){r===!0&&(r=0);var s="";if(typeof e=="string")try{s=new URL(e).protocol}catch{}else e&&e.constructor===URL&&(s=e.protocol);var a=s.split(/\:|\+/).filter(Boolean);return typeof r=="number"?a[r]:a}});var kye=_((F9t,xye)=>{"use strict";var uct=iq();function fct(t){var e={protocols:[],protocol:null,port:null,resource:"",host:"",user:"",password:"",pathname:"",hash:"",search:"",href:t,query:{},parse_failed:!1};try{var r=new URL(t);e.protocols=uct(r),e.protocol=e.protocols[0],e.port=r.port,e.resource=r.hostname,e.host=r.host,e.user=r.username||"",e.password=r.password||"",e.pathname=r.pathname,e.hash=r.hash.slice(1),e.search=r.search.slice(1),e.href=r.href,e.query=Object.fromEntries(r.searchParams)}catch{e.protocols=["file"],e.protocol=e.protocols[0],e.port="",e.resource="",e.user="",e.pathname="",e.hash="",e.search="",e.href=t,e.query={},e.parse_failed=!0}return e}xye.exports=fct});var Rye=_((N9t,Tye)=>{"use strict";var Act=kye();function pct(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}var hct=pct(Act),gct="text/plain",dct="us-ascii",Qye=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),mct=(t,{stripHash:e})=>{let r=/^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(t);if(!r)throw new Error(`Invalid URL: ${t}`);let{type:s,data:a,hash:n}=r.groups,c=s.split(";");n=e?"":n;let f=!1;c[c.length-1]==="base64"&&(c.pop(),f=!0);let p=(c.shift()||"").toLowerCase(),E=[...c.map(C=>{let[S,P=""]=C.split("=").map(I=>I.trim());return S==="charset"&&(P=P.toLowerCase(),P===dct)?"":`${S}${P?`=${P}`:""}`}).filter(Boolean)];return f&&E.push("base64"),(E.length>0||p&&p!==gct)&&E.unshift(p),`data:${E.join(";")},${f?a.trim():a}${n?`#${n}`:""}`};function yct(t,e){if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripTextFragment:!0,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeSingleSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},t=t.trim(),/^data:/i.test(t))return mct(t,e);if(/^view-source:/i.test(t))throw new Error("`view-source:` is not supported as it is a non-standard protocol");let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new URL(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash?a.hash="":e.stripTextFragment&&(a.hash=a.hash.replace(/#?:~:text.*?$/i,"")),a.pathname){let c=/\b[a-z][a-z\d+\-.]{1,50}:\/\//g,f=0,p="";for(;;){let E=c.exec(a.pathname);if(!E)break;let C=E[0],S=E.index,P=a.pathname.slice(f,S);p+=P.replace(/\/{2,}/g,"/"),p+=C,f=S+C.length}let h=a.pathname.slice(f,a.pathname.length);p+=h.replace(/\/{2,}/g,"/"),a.pathname=p}if(a.pathname)try{a.pathname=decodeURI(a.pathname)}catch{}if(e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let c=a.pathname.split("/"),f=c[c.length-1];Qye(f,e.removeDirectoryIndex)&&(c=c.slice(0,-1),a.pathname=c.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let c of[...a.searchParams.keys()])Qye(c,e.removeQueryParameters)&&a.searchParams.delete(c);if(e.removeQueryParameters===!0&&(a.search=""),e.sortQueryParameters){a.searchParams.sort();try{a.search=decodeURIComponent(a.search)}catch{}}e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,""));let n=t;return t=a.toString(),!e.removeSingleSlash&&a.pathname==="/"&&!n.endsWith("/")&&a.hash===""&&(t=t.replace(/\/$/,"")),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&e.removeSingleSlash&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t}var sq=(t,e=!1)=>{let r=/^(?:([a-z_][a-z0-9_-]{0,31})@|https?:\/\/)([\w\.\-@]+)[\/:]([\~,\.\w,\-,\_,\/]+?(?:\.git|\/)?)$/,s=n=>{let c=new Error(n);throw c.subject_url=t,c};(typeof t!="string"||!t.trim())&&s("Invalid url."),t.length>sq.MAX_INPUT_LENGTH&&s("Input exceeds maximum length. If needed, change the value of parseUrl.MAX_INPUT_LENGTH."),e&&(typeof e!="object"&&(e={stripHash:!1}),t=yct(t,e));let a=hct.default(t);if(a.parse_failed){let n=a.href.match(r);n?(a.protocols=["ssh"],a.protocol="ssh",a.resource=n[2],a.host=n[2],a.user=n[1],a.pathname=`/${n[3]}`,a.parse_failed=!1):s("URL parsing failed.")}return a};sq.MAX_INPUT_LENGTH=2048;Tye.exports=sq});var Oye=_((O9t,Nye)=>{"use strict";var Ect=iq();function Fye(t){if(Array.isArray(t))return t.indexOf("ssh")!==-1||t.indexOf("rsync")!==-1;if(typeof t!="string")return!1;var e=Ect(t);if(t=t.substring(t.indexOf("://")+3),Fye(e))return!0;var r=new RegExp(".([a-zA-Z\\d]+):(\\d+)/");return!t.match(r)&&t.indexOf("@"){"use strict";var Ict=Rye(),Lye=Oye();function Cct(t){var e=Ict(t);return e.token="",e.password==="x-oauth-basic"?e.token=e.user:e.user==="x-token-auth"&&(e.token=e.password),Lye(e.protocols)||e.protocols.length===0&&Lye(t)?e.protocol="ssh":e.protocols.length?e.protocol=e.protocols[0]:(e.protocol="file",e.protocols=["file"]),e.href=e.href.replace(/\/$/,""),e}Mye.exports=Cct});var Hye=_((M9t,_ye)=>{"use strict";var wct=Uye();function oq(t){if(typeof t!="string")throw new Error("The url must be a string.");var e=/^([a-z\d-]{1,39})\/([-\.\w]{1,100})$/i;e.test(t)&&(t="https://github.com/"+t);var r=wct(t),s=r.resource.split("."),a=null;switch(r.toString=function(N){return oq.stringify(this,N)},r.source=s.length>2?s.slice(1-s.length).join("."):r.source=r.resource,r.git_suffix=/\.git$/.test(r.pathname),r.name=decodeURIComponent((r.pathname||r.href).replace(/(^\/)|(\/$)/g,"").replace(/\.git$/,"")),r.owner=decodeURIComponent(r.user),r.source){case"git.cloudforge.com":r.owner=r.user,r.organization=s[0],r.source="cloudforge.com";break;case"visualstudio.com":if(r.resource==="vs-ssh.visualstudio.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3],r.full_name=a[2]+"/"+a[3]);break}else{a=r.name.split("/"),a.length===2?(r.owner=a[1],r.name=a[1],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name);break}case"dev.azure.com":case"azure.com":if(r.resource==="ssh.dev.azure.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3]);break}else{a=r.name.split("/"),a.length===5?(r.organization=a[0],r.owner=a[1],r.name=a[4],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name),r.query&&r.query.path&&(r.filepath=r.query.path.replace(/^\/+/g,"")),r.query&&r.query.version&&(r.ref=r.query.version.replace(/^GB/,""));break}default:a=r.name.split("/");var n=a.length-1;if(a.length>=2){var c=a.indexOf("-",2),f=a.indexOf("blob",2),p=a.indexOf("tree",2),h=a.indexOf("commit",2),E=a.indexOf("src",2),C=a.indexOf("raw",2),S=a.indexOf("edit",2);n=c>0?c-1:f>0?f-1:p>0?p-1:h>0?h-1:E>0?E-1:C>0?C-1:S>0?S-1:n,r.owner=a.slice(0,n).join("/"),r.name=a[n],h&&(r.commit=a[n+2])}r.ref="",r.filepathtype="",r.filepath="";var P=a.length>n&&a[n+1]==="-"?n+1:n;a.length>P+2&&["raw","src","blob","tree","edit"].indexOf(a[P+1])>=0&&(r.filepathtype=a[P+1],r.ref=a[P+2],a.length>P+3&&(r.filepath=a.slice(P+3).join("/"))),r.organization=r.owner;break}r.full_name||(r.full_name=r.owner,r.name&&(r.full_name&&(r.full_name+="/"),r.full_name+=r.name)),r.owner.startsWith("scm/")&&(r.source="bitbucket-server",r.owner=r.owner.replace("scm/",""),r.organization=r.owner,r.full_name=r.owner+"/"+r.name);var I=/(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/,R=I.exec(r.pathname);return R!=null&&(r.source="bitbucket-server",R[1]==="users"?r.owner="~"+R[2]:r.owner=R[2],r.organization=r.owner,r.name=R[3],a=R[4].split("/"),a.length>1&&(["raw","browse"].indexOf(a[1])>=0?(r.filepathtype=a[1],a.length>2&&(r.filepath=a.slice(2).join("/"))):a[1]==="commits"&&a.length>2&&(r.commit=a[2])),r.full_name=r.owner+"/"+r.name,r.query.at?r.ref=r.query.at:r.ref=""),r}oq.stringify=function(t,e){e=e||(t.protocols&&t.protocols.length?t.protocols.join("+"):t.protocol);var r=t.port?":"+t.port:"",s=t.user||"git",a=t.git_suffix?".git":"";switch(e){case"ssh":return r?"ssh://"+s+"@"+t.resource+r+"/"+t.full_name+a:s+"@"+t.resource+":"+t.full_name+a;case"git+ssh":case"ssh+git":case"ftp":case"ftps":return e+"://"+s+"@"+t.resource+r+"/"+t.full_name+a;case"http":case"https":var n=t.token?Bct(t):t.user&&(t.protocols.includes("http")||t.protocols.includes("https"))?t.user+"@":"";return e+"://"+n+t.resource+r+"/"+vct(t)+a;default:return t.href}};function Bct(t){switch(t.source){case"bitbucket.org":return"x-token-auth:"+t.token+"@";default:return t.token+"@"}}function vct(t){switch(t.source){case"bitbucket-server":return"scm/"+t.full_name;default:return""+t.full_name}}_ye.exports=oq});function jct(t,e){return e===1&&Hct.has(t[0])}function nS(t){let e=Array.isArray(t)?t:Mu(t);return e.map((s,a)=>Uct.test(s)?`[${s}]`:_ct.test(s)&&!jct(e,a)?`.${s}`:`[${JSON.stringify(s)}]`).join("").replace(/^\./,"")}function Gct(t,e){let r=[];if(e.methodName!==null&&r.push(he.pretty(t,e.methodName,he.Type.CODE)),e.file!==null){let s=[];s.push(he.pretty(t,e.file,he.Type.PATH)),e.line!==null&&(s.push(he.pretty(t,e.line,he.Type.NUMBER)),e.column!==null&&s.push(he.pretty(t,e.column,he.Type.NUMBER))),r.push(`(${s.join(he.pretty(t,":","grey"))})`)}return r.join(" ")}function iF(t,{manifestUpdates:e,reportedErrors:r},{fix:s}={}){let a=new Map,n=new Map,c=[...r.keys()].map(f=>[f,new Map]);for(let[f,p]of[...c,...e]){let h=r.get(f)?.map(P=>({text:P,fixable:!1}))??[],E=!1,C=t.getWorkspaceByCwd(f),S=C.manifest.exportTo({});for(let[P,I]of p){if(I.size>1){let R=[...I].map(([N,U])=>{let W=he.pretty(t.configuration,N,he.Type.INSPECT),ee=U.size>0?Gct(t.configuration,U.values().next().value):null;return ee!==null?` +${W} at ${ee}`:` +${W}`}).join("");h.push({text:`Conflict detected in constraint targeting ${he.pretty(t.configuration,P,he.Type.CODE)}; conflicting values are:${R}`,fixable:!1})}else{let[[R]]=I,N=va(S,P);if(JSON.stringify(N)===JSON.stringify(R))continue;if(!s){let U=typeof N>"u"?`Missing field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}`:typeof R>"u"?`Extraneous field ${he.pretty(t.configuration,P,he.Type.CODE)} currently set to ${he.pretty(t.configuration,N,he.Type.INSPECT)}`:`Invalid field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}, found ${he.pretty(t.configuration,N,he.Type.INSPECT)}`;h.push({text:U,fixable:!0});continue}typeof R>"u"?A0(S,P):Jd(S,P,R),E=!0}E&&a.set(C,S)}h.length>0&&n.set(C,h)}return{changedWorkspaces:a,remainingErrors:n}}function rEe(t,{configuration:e}){let r={children:[]};for(let[s,a]of t){let n=[];for(let f of a){let p=f.text.split(/\n/);f.fixable&&(p[0]=`${he.pretty(e,"\u2699","gray")} ${p[0]}`),n.push({value:he.tuple(he.Type.NO_HINT,p[0]),children:p.slice(1).map(h=>({value:he.tuple(he.Type.NO_HINT,h)}))})}let c={value:he.tuple(he.Type.LOCATOR,s.anchoredLocator),children:je.sortMap(n,f=>f.value[1])};r.children.push(c)}return r.children=je.sortMap(r.children,s=>s.value[1]),r}var WC,Uct,_ct,Hct,iS=Xe(()=>{Ge();ql();WC=class{constructor(e){this.indexedFields=e;this.items=[];this.indexes={};this.clear()}clear(){this.items=[];for(let e of this.indexedFields)this.indexes[e]=new Map}insert(e){this.items.push(e);for(let r of this.indexedFields){let s=Object.hasOwn(e,r)?e[r]:void 0;if(typeof s>"u")continue;je.getArrayWithDefault(this.indexes[r],s).push(e)}return e}find(e){if(typeof e>"u")return this.items;let r=Object.entries(e);if(r.length===0)return this.items;let s=[],a;for(let[c,f]of r){let p=c,h=Object.hasOwn(this.indexes,p)?this.indexes[p]:void 0;if(typeof h>"u"){s.push([p,f]);continue}let E=new Set(h.get(f)??[]);if(E.size===0)return[];if(typeof a>"u")a=E;else for(let C of a)E.has(C)||a.delete(C);if(a.size===0)break}let n=[...a??[]];return s.length>0&&(n=n.filter(c=>{for(let[f,p]of s)if(!(typeof p<"u"?Object.hasOwn(c,f)&&c[f]===p:Object.hasOwn(c,f)===!1))return!1;return!0})),n}},Uct=/^[0-9]+$/,_ct=/^[a-zA-Z0-9_]+$/,Hct=new Set(["scripts",...Ut.allDependencies])});var nEe=_((_Yt,vq)=>{var qct;(function(t){var e=function(){return{"append/2":[new t.type.Rule(new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("L")]),new t.type.Term("foldl",[new t.type.Term("append",[]),new t.type.Var("X"),new t.type.Term("[]",[]),new t.type.Var("L")]))],"append/3":[new t.type.Rule(new t.type.Term("append",[new t.type.Term("[]",[]),new t.type.Var("X"),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("append",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("append",[new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("S")]))],"member/2":[new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("_")])]),null),new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")])]),new t.type.Term("member",[new t.type.Var("X"),new t.type.Var("Xs")]))],"permutation/2":[new t.type.Rule(new t.type.Term("permutation",[new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("permutation",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("permutation",[new t.type.Var("T"),new t.type.Var("P")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("P")]),new t.type.Term("append",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("Y")]),new t.type.Var("S")])])]))],"maplist/2":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("X")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("Xs")])]))],"maplist/3":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs")])]))],"maplist/4":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs")])]))],"maplist/5":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds")])]))],"maplist/6":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es")])]))],"maplist/7":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs")])]))],"maplist/8":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")]),new t.type.Term(".",[new t.type.Var("G"),new t.type.Var("Gs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F"),new t.type.Var("G")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs"),new t.type.Var("Gs")])]))],"include/3":[new t.type.Rule(new t.type.Term("include",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("include",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("A")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("A"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("F"),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("F")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("L"),new t.type.Var("S")])]),new t.type.Term("include",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("S")])])])])]))],"exclude/3":[new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("E")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("Q")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("R"),new t.type.Var("Q")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("!",[]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("E")])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("E")])])])])])])]))],"foldl/4":[new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Var("I"),new t.type.Var("I")]),null),new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("I"),new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("I"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])])])]),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P2"),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P2")]),new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("R")])])])])]))],"select/3":[new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Xs")]),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term("select",[new t.type.Var("E"),new t.type.Var("Xs"),new t.type.Var("Ys")]))],"sum_list/2":[new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term("[]",[]),new t.type.Num(0,!1)]),null),new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("sum_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("+",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"max_list/2":[new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("max_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"min_list/2":[new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("min_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("=<",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"prod_list/2":[new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term("[]",[]),new t.type.Num(1,!1)]),null),new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("prod_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("*",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"last/2":[new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")]),new t.type.Var("X")]),new t.type.Term("last",[new t.type.Var("Xs"),new t.type.Var("X")]))],"prefix/2":[new t.type.Rule(new t.type.Term("prefix",[new t.type.Var("Part"),new t.type.Var("Whole")]),new t.type.Term("append",[new t.type.Var("Part"),new t.type.Var("_"),new t.type.Var("Whole")]))],"nth0/3":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth1/3":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth0/4":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth1/4":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth/5":[new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("N"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("X"),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("O"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("Y"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term(",",[new t.type.Term("is",[new t.type.Var("M"),new t.type.Term("+",[new t.type.Var("N"),new t.type.Num(1,!1)])]),new t.type.Term("nth",[new t.type.Var("M"),new t.type.Var("O"),new t.type.Var("Xs"),new t.type.Var("Y"),new t.type.Var("Ys")])]))],"length/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(!t.type.is_variable(f)&&!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(t.type.is_integer(f)&&f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else{var p=new t.type.Term("length",[c,new t.type.Num(0,!1),f]);t.type.is_integer(f)&&(p=new t.type.Term(",",[p,new t.type.Term("!",[])])),s.prepend([new t.type.State(a.goal.replace(p),a.substitution,a)])}},"length/3":[new t.type.Rule(new t.type.Term("length",[new t.type.Term("[]",[]),new t.type.Var("N"),new t.type.Var("N")]),null),new t.type.Rule(new t.type.Term("length",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("X")]),new t.type.Var("A"),new t.type.Var("N")]),new t.type.Term(",",[new t.type.Term("succ",[new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("length",[new t.type.Var("X"),new t.type.Var("B"),new t.type.Var("N")])]))],"replicate/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=new t.type.Term("[]"),E=0;E0;C--)E[C].equals(E[C-1])&&E.splice(C,1);for(var S=new t.type.Term("[]"),C=E.length-1;C>=0;C--)S=new t.type.Term(".",[E[C],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"msort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h=c;h.indicator==="./2";)p.push(h.args[0]),h=h.args[1];if(t.type.is_variable(h))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(h))s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=p.sort(t.compare),C=new t.type.Term("[]"),S=E.length-1;S>=0;S--)C=new t.type.Term(".",[E[S],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,f])),a.substitution,a)])}}},"keysort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h,E=c;E.indicator==="./2";){if(h=E.args[0],t.type.is_variable(h)){s.throw_error(t.error.instantiation(n.indicator));return}else if(!t.type.is_term(h)||h.indicator!=="-/2"){s.throw_error(t.error.type("pair",h,n.indicator));return}h.args[0].pair=h.args[1],p.push(h.args[0]),E=E.args[1]}if(t.type.is_variable(E))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(E))s.throw_error(t.error.type("list",c,n.indicator));else{for(var C=p.sort(t.compare),S=new t.type.Term("[]"),P=C.length-1;P>=0;P--)S=new t.type.Term(".",[new t.type.Term("-",[C[P],C[P].pair]),S]),delete C[P].pair;s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"take/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;if(h===0){for(var S=new t.type.Term("[]"),h=E.length-1;h>=0;h--)S=new t.type.Term(".",[E[h],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,p])),a.substitution,a)])}}},"drop/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;h===0&&s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p])),a.substitution,a)])}},"reverse/2":function(s,a,n){var c=n.args[0],f=n.args[1],p=t.type.is_instantiated_list(c),h=t.type.is_instantiated_list(f);if(t.type.is_variable(c)&&t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(c)&&!t.type.is_fully_list(c))s.throw_error(t.error.type("list",c,n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!p&&!h)s.throw_error(t.error.instantiation(n.indicator));else{for(var E=p?c:f,C=new t.type.Term("[]",[]);E.indicator==="./2";)C=new t.type.Term(".",[E.args[0],C]),E=E.args[1];s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p?f:c])),a.substitution,a)])}},"list_to_set/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else{for(var p=c,h=[];p.indicator==="./2";)h.push(p.args[0]),p=p.args[1];if(t.type.is_variable(p))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_term(p)||p.indicator!=="[]/0")s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=[],C=new t.type.Term("[]",[]),S,P=0;P=0;P--)C=new t.type.Term(".",[E[P],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[f,C])),a.substitution,a)])}}}}},r=["append/2","append/3","member/2","permutation/2","maplist/2","maplist/3","maplist/4","maplist/5","maplist/6","maplist/7","maplist/8","include/3","exclude/3","foldl/4","sum_list/2","max_list/2","min_list/2","prod_list/2","last/2","prefix/2","nth0/3","nth1/3","nth0/4","nth1/4","length/2","replicate/3","select/3","sort/2","msort/2","keysort/2","take/3","drop/3","reverse/2","list_to_set/2"];typeof vq<"u"?vq.exports=function(s){t=s,new t.type.Module("lists",e(),r)}:new t.type.Module("lists",e(),r)})(qct)});var yEe=_($r=>{"use strict";var bm=process.platform==="win32",Sq="aes-256-cbc",Wct="sha256",oEe="The current environment doesn't support interactive reading from TTY.",si=Ie("fs"),iEe=process.binding("tty_wrap").TTY,bq=Ie("child_process"),V0=Ie("path"),Pq={prompt:"> ",hideEchoBack:!1,mask:"*",limit:[],limitMessage:"Input another, please.$<( [)limit(])>",defaultInput:"",trueValue:[],falseValue:[],caseSensitive:!1,keepWhitespace:!1,encoding:"utf8",bufferSize:1024,print:void 0,history:!0,cd:!1,phContent:void 0,preCheck:void 0},Xp="none",Zu,VC,sEe=!1,Y0,oF,Dq,Yct=0,Rq="",Dm=[],aF,aEe=!1,xq=!1,sS=!1;function lEe(t){function e(r){return r.replace(/[^\w\u0080-\uFFFF]/g,function(s){return"#"+s.charCodeAt(0)+";"})}return oF.concat(function(r){var s=[];return Object.keys(r).forEach(function(a){r[a]==="boolean"?t[a]&&s.push("--"+a):r[a]==="string"&&t[a]&&s.push("--"+a,e(t[a]))}),s}({display:"string",displayOnly:"boolean",keyIn:"boolean",hideEchoBack:"boolean",mask:"string",limit:"string",caseSensitive:"boolean"}))}function Vct(t,e){function r(U){var W,ee="",ie;for(Dq=Dq||Ie("os").tmpdir();;){W=V0.join(Dq,U+ee);try{ie=si.openSync(W,"wx")}catch(ue){if(ue.code==="EEXIST"){ee++;continue}else throw ue}si.closeSync(ie);break}return W}var s,a,n,c={},f,p,h=r("readline-sync.stdout"),E=r("readline-sync.stderr"),C=r("readline-sync.exit"),S=r("readline-sync.done"),P=Ie("crypto"),I,R,N;I=P.createHash(Wct),I.update(""+process.pid+Yct+++Math.random()),N=I.digest("hex"),R=P.createDecipher(Sq,N),s=lEe(t),bm?(a=process.env.ComSpec||"cmd.exe",process.env.Q='"',n=["/V:ON","/S","/C","(%Q%"+a+"%Q% /V:ON /S /C %Q%%Q%"+Y0+"%Q%"+s.map(function(U){return" %Q%"+U+"%Q%"}).join("")+" & (echo !ERRORLEVEL!)>%Q%"+C+"%Q%%Q%) 2>%Q%"+E+"%Q% |%Q%"+process.execPath+"%Q% %Q%"+__dirname+"\\encrypt.js%Q% %Q%"+Sq+"%Q% %Q%"+N+"%Q% >%Q%"+h+"%Q% & (echo 1)>%Q%"+S+"%Q%"]):(a="/bin/sh",n=["-c",'("'+Y0+'"'+s.map(function(U){return" '"+U.replace(/'/g,"'\\''")+"'"}).join("")+'; echo $?>"'+C+'") 2>"'+E+'" |"'+process.execPath+'" "'+__dirname+'/encrypt.js" "'+Sq+'" "'+N+'" >"'+h+'"; echo 1 >"'+S+'"']),sS&&sS("_execFileSync",s);try{bq.spawn(a,n,e)}catch(U){c.error=new Error(U.message),c.error.method="_execFileSync - spawn",c.error.program=a,c.error.args=n}for(;si.readFileSync(S,{encoding:t.encoding}).trim()!=="1";);return(f=si.readFileSync(C,{encoding:t.encoding}).trim())==="0"?c.input=R.update(si.readFileSync(h,{encoding:"binary"}),"hex",t.encoding)+R.final(t.encoding):(p=si.readFileSync(E,{encoding:t.encoding}).trim(),c.error=new Error(oEe+(p?` +`+p:"")),c.error.method="_execFileSync",c.error.program=a,c.error.args=n,c.error.extMessage=p,c.error.exitCode=+f),si.unlinkSync(h),si.unlinkSync(E),si.unlinkSync(C),si.unlinkSync(S),c}function Jct(t){var e,r={},s,a={env:process.env,encoding:t.encoding};if(Y0||(bm?process.env.PSModulePath?(Y0="powershell.exe",oF=["-ExecutionPolicy","Bypass","-File",__dirname+"\\read.ps1"]):(Y0="cscript.exe",oF=["//nologo",__dirname+"\\read.cs.js"]):(Y0="/bin/sh",oF=[__dirname+"/read.sh"])),bm&&!process.env.PSModulePath&&(a.stdio=[process.stdin]),bq.execFileSync){e=lEe(t),sS&&sS("execFileSync",e);try{r.input=bq.execFileSync(Y0,e,a)}catch(n){s=n.stderr?(n.stderr+"").trim():"",r.error=new Error(oEe+(s?` +`+s:"")),r.error.method="execFileSync",r.error.program=Y0,r.error.args=e,r.error.extMessage=s,r.error.exitCode=n.status,r.error.code=n.code,r.error.signal=n.signal}}else r=Vct(t,a);return r.error||(r.input=r.input.replace(/^\s*'|'\s*$/g,""),t.display=""),r}function kq(t){var e="",r=t.display,s=!t.display&&t.keyIn&&t.hideEchoBack&&!t.mask;function a(){var n=Jct(t);if(n.error)throw n.error;return n.input}return xq&&xq(t),function(){var n,c,f;function p(){return n||(n=process.binding("fs"),c=process.binding("constants")),n}if(typeof Xp=="string")if(Xp=null,bm){if(f=function(h){var E=h.replace(/^\D+/,"").split("."),C=0;return(E[0]=+E[0])&&(C+=E[0]*1e4),(E[1]=+E[1])&&(C+=E[1]*100),(E[2]=+E[2])&&(C+=E[2]),C}(process.version),!(f>=20302&&f<40204||f>=5e4&&f<50100||f>=50600&&f<60200)&&process.stdin.isTTY)process.stdin.pause(),Xp=process.stdin.fd,VC=process.stdin._handle;else try{Xp=p().open("CONIN$",c.O_RDWR,parseInt("0666",8)),VC=new iEe(Xp,!0)}catch{}if(process.stdout.isTTY)Zu=process.stdout.fd;else{try{Zu=si.openSync("\\\\.\\CON","w")}catch{}if(typeof Zu!="number")try{Zu=p().open("CONOUT$",c.O_RDWR,parseInt("0666",8))}catch{}}}else{if(process.stdin.isTTY){process.stdin.pause();try{Xp=si.openSync("/dev/tty","r"),VC=process.stdin._handle}catch{}}else try{Xp=si.openSync("/dev/tty","r"),VC=new iEe(Xp,!1)}catch{}if(process.stdout.isTTY)Zu=process.stdout.fd;else try{Zu=si.openSync("/dev/tty","w")}catch{}}}(),function(){var n,c,f=!t.hideEchoBack&&!t.keyIn,p,h,E,C,S;aF="";function P(I){return I===sEe?!0:VC.setRawMode(I)!==0?!1:(sEe=I,!0)}if(aEe||!VC||typeof Zu!="number"&&(t.display||!f)){e=a();return}if(t.display&&(si.writeSync(Zu,t.display),t.display=""),!t.displayOnly){if(!P(!f)){e=a();return}for(h=t.keyIn?1:t.bufferSize,p=Buffer.allocUnsafe&&Buffer.alloc?Buffer.alloc(h):new Buffer(h),t.keyIn&&t.limit&&(c=new RegExp("[^"+t.limit+"]","g"+(t.caseSensitive?"":"i")));;){E=0;try{E=si.readSync(Xp,p,0,h)}catch(I){if(I.code!=="EOF"){P(!1),e+=a();return}}if(E>0?(C=p.toString(t.encoding,0,E),aF+=C):(C=` +`,aF+="\0"),C&&typeof(S=(C.match(/^(.*?)[\r\n]/)||[])[1])=="string"&&(C=S,n=!0),C&&(C=C.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")),C&&c&&(C=C.replace(c,"")),C&&(f||(t.hideEchoBack?t.mask&&si.writeSync(Zu,new Array(C.length+1).join(t.mask)):si.writeSync(Zu,C)),e+=C),!t.keyIn&&n||t.keyIn&&e.length>=h)break}!f&&!s&&si.writeSync(Zu,` +`),P(!1)}}(),t.print&&!s&&t.print(r+(t.displayOnly?"":(t.hideEchoBack?new Array(e.length+1).join(t.mask):e)+` +`),t.encoding),t.displayOnly?"":Rq=t.keepWhitespace||t.keyIn?e:e.trim()}function Kct(t,e){var r=[];function s(a){a!=null&&(Array.isArray(a)?a.forEach(s):(!e||e(a))&&r.push(a))}return s(t),r}function Fq(t){return t.replace(/[\x00-\x7f]/g,function(e){return"\\x"+("00"+e.charCodeAt().toString(16)).substr(-2)})}function Vs(){var t=Array.prototype.slice.call(arguments),e,r;return t.length&&typeof t[0]=="boolean"&&(r=t.shift(),r&&(e=Object.keys(Pq),t.unshift(Pq))),t.reduce(function(s,a){return a==null||(a.hasOwnProperty("noEchoBack")&&!a.hasOwnProperty("hideEchoBack")&&(a.hideEchoBack=a.noEchoBack,delete a.noEchoBack),a.hasOwnProperty("noTrim")&&!a.hasOwnProperty("keepWhitespace")&&(a.keepWhitespace=a.noTrim,delete a.noTrim),r||(e=Object.keys(a)),e.forEach(function(n){var c;if(a.hasOwnProperty(n))switch(c=a[n],n){case"mask":case"limitMessage":case"defaultInput":case"encoding":c=c!=null?c+"":"",c&&n!=="limitMessage"&&(c=c.replace(/[\r\n]/g,"")),s[n]=c;break;case"bufferSize":!isNaN(c=parseInt(c,10))&&typeof c=="number"&&(s[n]=c);break;case"displayOnly":case"keyIn":case"hideEchoBack":case"caseSensitive":case"keepWhitespace":case"history":case"cd":s[n]=!!c;break;case"limit":case"trueValue":case"falseValue":s[n]=Kct(c,function(f){var p=typeof f;return p==="string"||p==="number"||p==="function"||f instanceof RegExp}).map(function(f){return typeof f=="string"?f.replace(/[\r\n]/g,""):f});break;case"print":case"phContent":case"preCheck":s[n]=typeof c=="function"?c:void 0;break;case"prompt":case"display":s[n]=c??"";break}})),s},{})}function Qq(t,e,r){return e.some(function(s){var a=typeof s;return a==="string"?r?t===s:t.toLowerCase()===s.toLowerCase():a==="number"?parseFloat(t)===s:a==="function"?s(t):s instanceof RegExp?s.test(t):!1})}function Nq(t,e){var r=V0.normalize(bm?(process.env.HOMEDRIVE||"")+(process.env.HOMEPATH||""):process.env.HOME||"").replace(/[\/\\]+$/,"");return t=V0.normalize(t),e?t.replace(/^~(?=\/|\\|$)/,r):t.replace(new RegExp("^"+Fq(r)+"(?=\\/|\\\\|$)",bm?"i":""),"~")}function JC(t,e){var r="(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?",s=new RegExp("(\\$)?(\\$<"+r+">)","g"),a=new RegExp("(\\$)?(\\$\\{"+r+"\\})","g");function n(c,f,p,h,E,C){var S;return f||typeof(S=e(E))!="string"?p:S?(h||"")+S+(C||""):""}return t.replace(s,n).replace(a,n)}function cEe(t,e,r){var s,a=[],n=-1,c=0,f="",p;function h(E,C){return C.length>3?(E.push(C[0]+"..."+C[C.length-1]),p=!0):C.length&&(E=E.concat(C)),E}return s=t.reduce(function(E,C){return E.concat((C+"").split(""))},[]).reduce(function(E,C){var S,P;return e||(C=C.toLowerCase()),S=/^\d$/.test(C)?1:/^[A-Z]$/.test(C)?2:/^[a-z]$/.test(C)?3:0,r&&S===0?f+=C:(P=C.charCodeAt(0),S&&S===n&&P===c+1?a.push(C):(E=h(E,a),a=[C],n=S),c=P),E},[]),s=h(s,a),f&&(s.push(f),p=!0),{values:s,suppressed:p}}function uEe(t,e){return t.join(t.length>2?", ":e?" / ":"/")}function fEe(t,e){var r,s,a={},n;if(e.phContent&&(r=e.phContent(t,e)),typeof r!="string")switch(t){case"hideEchoBack":case"mask":case"defaultInput":case"caseSensitive":case"keepWhitespace":case"encoding":case"bufferSize":case"history":case"cd":r=e.hasOwnProperty(t)?typeof e[t]=="boolean"?e[t]?"on":"off":e[t]+"":"";break;case"limit":case"trueValue":case"falseValue":s=e[e.hasOwnProperty(t+"Src")?t+"Src":t],e.keyIn?(a=cEe(s,e.caseSensitive),s=a.values):s=s.filter(function(c){var f=typeof c;return f==="string"||f==="number"}),r=uEe(s,a.suppressed);break;case"limitCount":case"limitCountNotZero":r=e[e.hasOwnProperty("limitSrc")?"limitSrc":"limit"].length,r=r||t!=="limitCountNotZero"?r+"":"";break;case"lastInput":r=Rq;break;case"cwd":case"CWD":case"cwdHome":r=process.cwd(),t==="CWD"?r=V0.basename(r):t==="cwdHome"&&(r=Nq(r));break;case"date":case"time":case"localeDate":case"localeTime":r=new Date()["to"+t.replace(/^./,function(c){return c.toUpperCase()})+"String"]();break;default:typeof(n=(t.match(/^history_m(\d+)$/)||[])[1])=="string"&&(r=Dm[Dm.length-n]||"")}return r}function AEe(t){var e=/^(.)-(.)$/.exec(t),r="",s,a,n,c;if(!e)return null;for(s=e[1].charCodeAt(0),a=e[2].charCodeAt(0),c=s +And the length must be: $`,trueValue:null,falseValue:null,caseSensitive:!0},e,{history:!1,cd:!1,phContent:function(P){return P==="charlist"?r.text:P==="length"?s+"..."+a:null}}),c,f,p,h,E,C,S;for(e=e||{},c=JC(e.charlist?e.charlist+"":"$",AEe),(isNaN(s=parseInt(e.min,10))||typeof s!="number")&&(s=12),(isNaN(a=parseInt(e.max,10))||typeof a!="number")&&(a=24),h=new RegExp("^["+Fq(c)+"]{"+s+","+a+"}$"),r=cEe([c],n.caseSensitive,!0),r.text=uEe(r.values,r.suppressed),f=e.confirmMessage!=null?e.confirmMessage:"Reinput a same one to confirm it: ",p=e.unmatchMessage!=null?e.unmatchMessage:"It differs from first one. Hit only the Enter key if you want to retry from first one.",t==null&&(t="Input new password: "),E=n.limitMessage;!S;)n.limit=h,n.limitMessage=E,C=$r.question(t,n),n.limit=[C,""],n.limitMessage=p,S=$r.question(f,n);return C};function gEe(t,e,r){var s;function a(n){return s=r(n),!isNaN(s)&&typeof s=="number"}return $r.question(t,Vs({limitMessage:"Input valid number, please."},e,{limit:a,cd:!1})),s}$r.questionInt=function(t,e){return gEe(t,e,function(r){return parseInt(r,10)})};$r.questionFloat=function(t,e){return gEe(t,e,parseFloat)};$r.questionPath=function(t,e){var r,s="",a=Vs({hideEchoBack:!1,limitMessage:`$Input valid path, please.$<( Min:)min>$<( Max:)max>`,history:!0,cd:!0},e,{keepWhitespace:!1,limit:function(n){var c,f,p;n=Nq(n,!0),s="";function h(E){E.split(/\/|\\/).reduce(function(C,S){var P=V0.resolve(C+=S+V0.sep);if(!si.existsSync(P))si.mkdirSync(P);else if(!si.statSync(P).isDirectory())throw new Error("Non directory already exists: "+P);return C},"")}try{if(c=si.existsSync(n),r=c?si.realpathSync(n):V0.resolve(n),!e.hasOwnProperty("exists")&&!c||typeof e.exists=="boolean"&&e.exists!==c)return s=(c?"Already exists":"No such file or directory")+": "+r,!1;if(!c&&e.create&&(e.isDirectory?h(r):(h(V0.dirname(r)),si.closeSync(si.openSync(r,"w"))),r=si.realpathSync(r)),c&&(e.min||e.max||e.isFile||e.isDirectory)){if(f=si.statSync(r),e.isFile&&!f.isFile())return s="Not file: "+r,!1;if(e.isDirectory&&!f.isDirectory())return s="Not directory: "+r,!1;if(e.min&&f.size<+e.min||e.max&&f.size>+e.max)return s="Size "+f.size+" is out of range: "+r,!1}if(typeof e.validate=="function"&&(p=e.validate(r))!==!0)return typeof p=="string"&&(s=p),!1}catch(E){return s=E+"",!1}return!0},phContent:function(n){return n==="error"?s:n!=="min"&&n!=="max"?null:e.hasOwnProperty(n)?e[n]+"":""}});return e=e||{},t==null&&(t='Input path (you can "cd" and "pwd"): '),$r.question(t,a),r};function dEe(t,e){var r={},s={};return typeof t=="object"?(Object.keys(t).forEach(function(a){typeof t[a]=="function"&&(s[e.caseSensitive?a:a.toLowerCase()]=t[a])}),r.preCheck=function(a){var n;return r.args=Tq(a),n=r.args[0]||"",e.caseSensitive||(n=n.toLowerCase()),r.hRes=n!=="_"&&s.hasOwnProperty(n)?s[n].apply(a,r.args.slice(1)):s.hasOwnProperty("_")?s._.apply(a,r.args):null,{res:a,forceNext:!1}},s.hasOwnProperty("_")||(r.limit=function(){var a=r.args[0]||"";return e.caseSensitive||(a=a.toLowerCase()),s.hasOwnProperty(a)})):r.preCheck=function(a){return r.args=Tq(a),r.hRes=typeof t=="function"?t.apply(a,r.args):!0,{res:a,forceNext:!1}},r}$r.promptCL=function(t,e){var r=Vs({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=dEe(t,r);return r.limit=s.limit,r.preCheck=s.preCheck,$r.prompt(r),s.args};$r.promptLoop=function(t,e){for(var r=Vs({hideEchoBack:!1,trueValue:null,falseValue:null,caseSensitive:!1,history:!0},e);!t($r.prompt(r)););};$r.promptCLLoop=function(t,e){var r=Vs({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=dEe(t,r);for(r.limit=s.limit,r.preCheck=s.preCheck;$r.prompt(r),!s.hRes;);};$r.promptSimShell=function(t){return $r.prompt(Vs({hideEchoBack:!1,history:!0},t,{prompt:function(){return bm?"$>":(process.env.USER||"")+(process.env.HOSTNAME?"@"+process.env.HOSTNAME.replace(/\..*$/,""):"")+":$$ "}()}))};function mEe(t,e,r){var s;return t==null&&(t="Are you sure? "),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s*:?\s*$/,"")+" [y/n]: "),s=$r.keyIn(t,Vs(e,{hideEchoBack:!1,limit:r,trueValue:"y",falseValue:"n",caseSensitive:!1})),typeof s=="boolean"?s:""}$r.keyInYN=function(t,e){return mEe(t,e)};$r.keyInYNStrict=function(t,e){return mEe(t,e,"yn")};$r.keyInPause=function(t,e){t==null&&(t="Continue..."),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s+$/,"")+" (Hit any key)"),$r.keyIn(t,Vs({limit:null},e,{hideEchoBack:!0,mask:""}))};$r.keyInSelect=function(t,e,r){var s=Vs({hideEchoBack:!1},r,{trueValue:null,falseValue:null,caseSensitive:!1,phContent:function(p){return p==="itemsCount"?t.length+"":p==="firstItem"?(t[0]+"").trim():p==="lastItem"?(t[t.length-1]+"").trim():null}}),a="",n={},c=49,f=` +`;if(!Array.isArray(t)||!t.length||t.length>35)throw"`items` must be Array (max length: 35).";return t.forEach(function(p,h){var E=String.fromCharCode(c);a+=E,n[E]=h,f+="["+E+"] "+(p+"").trim()+` +`,c=c===57?97:c+1}),(!r||r.cancel!==!1)&&(a+="0",n[0]=-1,f+="[0] "+(r&&r.cancel!=null&&typeof r.cancel!="boolean"?(r.cancel+"").trim():"CANCEL")+` +`),s.limit=a,f+=` +`,e==null&&(e="Choose one from list: "),(e+="")&&((!r||r.guide!==!1)&&(e=e.replace(/\s*:?\s*$/,"")+" [$]: "),f+=e),n[$r.keyIn(f,s).toLowerCase()]};$r.getRawInput=function(){return aF};function oS(t,e){var r;return e.length&&(r={},r[t]=e[0]),$r.setDefaultOptions(r)[t]}$r.setPrint=function(){return oS("print",arguments)};$r.setPrompt=function(){return oS("prompt",arguments)};$r.setEncoding=function(){return oS("encoding",arguments)};$r.setMask=function(){return oS("mask",arguments)};$r.setBufferSize=function(){return oS("bufferSize",arguments)}});var Oq=_((jYt,ec)=>{(function(){var t={major:0,minor:2,patch:66,status:"beta"};tau_file_system={files:{},open:function(w,b,y){var F=tau_file_system.files[w];if(!F){if(y==="read")return null;F={path:w,text:"",type:b,get:function(z,X){return X===this.text.length||X>this.text.length?"end_of_file":this.text.substring(X,X+z)},put:function(z,X){return X==="end_of_file"?(this.text+=z,!0):X==="past_end_of_file"?null:(this.text=this.text.substring(0,X)+z+this.text.substring(X+z.length),!0)},get_byte:function(z){if(z==="end_of_stream")return-1;var X=Math.floor(z/2);if(this.text.length<=X)return-1;var $=n(this.text[Math.floor(z/2)],0);return z%2===0?$&255:$/256>>>0},put_byte:function(z,X){var $=X==="end_of_stream"?this.text.length:Math.floor(X/2);if(this.text.length<$)return null;var oe=this.text.length===$?-1:n(this.text[Math.floor(X/2)],0);return X%2===0?(oe=oe/256>>>0,oe=(oe&255)<<8|z&255):(oe=oe&255,oe=(z&255)<<8|oe&255),this.text.length===$?this.text+=c(oe):this.text=this.text.substring(0,$)+c(oe)+this.text.substring($+1),!0},flush:function(){return!0},close:function(){var z=tau_file_system.files[this.path];return z?!0:null}},tau_file_system.files[w]=F}return y==="write"&&(F.text=""),F}},tau_user_input={buffer:"",get:function(w,b){for(var y;tau_user_input.buffer.length\?\@\^\~\\]+|'(?:[^']*?(?:\\(?:x?\d+)?\\)*(?:'')*(?:\\')*)*')/,number:/^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\[abfnrtv\\'"`]|\\x?\d+\\|[^\\])|\d+(?:\.\d+(?:[eE][+-]?\d+)?)?)/,string:/^(?:"([^"]|""|\\")*"|`([^`]|``|\\`)*`)/,l_brace:/^(?:\[)/,r_brace:/^(?:\])/,l_bracket:/^(?:\{)/,r_bracket:/^(?:\})/,bar:/^(?:\|)/,l_paren:/^(?:\()/,r_paren:/^(?:\))/};function N(w,b){return w.get_flag("char_conversion").id==="on"?b.replace(/./g,function(y){return w.get_char_conversion(y)}):b}function U(w){this.thread=w,this.text="",this.tokens=[]}U.prototype.set_last_tokens=function(w){return this.tokens=w},U.prototype.new_text=function(w){this.text=w,this.tokens=[]},U.prototype.get_tokens=function(w){var b,y=0,F=0,z=0,X=[],$=!1;if(w){var oe=this.tokens[w-1];y=oe.len,b=N(this.thread,this.text.substr(oe.len)),F=oe.line,z=oe.start}else b=this.text;if(/^\s*$/.test(b))return null;for(;b!=="";){var xe=[],Te=!1;if(/^\n/.exec(b)!==null){F++,z=0,y++,b=b.replace(/\n/,""),$=!0;continue}for(var lt in R)if(R.hasOwnProperty(lt)){var Ct=R[lt].exec(b);Ct&&xe.push({value:Ct[0],name:lt,matches:Ct})}if(!xe.length)return this.set_last_tokens([{value:b,matches:[],name:"lexical",line:F,start:z}]);var oe=r(xe,function(Pr,Ir){return Pr.value.length>=Ir.value.length?Pr:Ir});switch(oe.start=z,oe.line=F,b=b.replace(oe.value,""),z+=oe.value.length,y+=oe.value.length,oe.name){case"atom":oe.raw=oe.value,oe.value.charAt(0)==="'"&&(oe.value=S(oe.value.substr(1,oe.value.length-2),"'"),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence"));break;case"number":oe.float=oe.value.substring(0,2)!=="0x"&&oe.value.match(/[.eE]/)!==null&&oe.value!=="0'.",oe.value=I(oe.value),oe.blank=Te;break;case"string":var qt=oe.value.charAt(0);oe.value=S(oe.value.substr(1,oe.value.length-2),qt),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence");break;case"whitespace":var ir=X[X.length-1];ir&&(ir.space=!0),Te=!0;continue;case"r_bracket":X.length>0&&X[X.length-1].name==="l_bracket"&&(oe=X.pop(),oe.name="atom",oe.value="{}",oe.raw="{}",oe.space=!1);break;case"r_brace":X.length>0&&X[X.length-1].name==="l_brace"&&(oe=X.pop(),oe.name="atom",oe.value="[]",oe.raw="[]",oe.space=!1);break}oe.len=y,X.push(oe),Te=!1}var Pt=this.set_last_tokens(X);return Pt.length===0?null:Pt};function W(w,b,y,F,z){if(!b[y])return{type:f,value:x.error.syntax(b[y-1],"expression expected",!0)};var X;if(F==="0"){var $=b[y];switch($.name){case"number":return{type:p,len:y+1,value:new x.type.Num($.value,$.float)};case"variable":return{type:p,len:y+1,value:new x.type.Var($.value)};case"string":var oe;switch(w.get_flag("double_quotes").id){case"atom":oe=new j($.value,[]);break;case"codes":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Num(n($.value,xe),!1),oe]);break;case"chars":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Term($.value.charAt(xe),[]),oe]);break}return{type:p,len:y+1,value:oe};case"l_paren":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_paren"?(Pt.len++,Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],") or operator expected",!b[Pt.len])};case"l_bracket":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_bracket"?(Pt.len++,Pt.value=new j("{}",[Pt.value]),Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],"} or operator expected",!b[Pt.len])}}var Te=ee(w,b,y,z);return Te.type===p||Te.derived||(Te=ie(w,b,y),Te.type===p||Te.derived)?Te:{type:f,derived:!1,value:x.error.syntax(b[y],"unexpected token")}}var lt=w.__get_max_priority(),Ct=w.__get_next_priority(F),qt=y;if(b[y].name==="atom"&&b[y+1]&&(b[y].space||b[y+1].name!=="l_paren")){var $=b[y++],ir=w.__lookup_operator_classes(F,$.value);if(ir&&ir.indexOf("fy")>-1){var Pt=W(w,b,y,F,z);if(Pt.type!==f)return $.value==="-"&&!$.space&&x.type.is_number(Pt.value)?{value:new x.type.Num(-Pt.value.value,Pt.value.is_float),len:Pt.len,type:p}:{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};X=Pt}else if(ir&&ir.indexOf("fx")>-1){var Pt=W(w,b,y,Ct,z);if(Pt.type!==f)return{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};X=Pt}}y=qt;var Pt=W(w,b,y,Ct,z);if(Pt.type===p){y=Pt.len;var $=b[y];if(b[y]&&(b[y].name==="atom"&&w.__lookup_operator_classes(F,$.value)||b[y].name==="bar"&&w.__lookup_operator_classes(F,"|"))){var gn=Ct,Pr=F,ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("xf")>-1)return{value:new x.type.Term($.value,[Pt.value]),len:++Pt.len,type:p};if(ir.indexOf("xfx")>-1){var Ir=W(w,b,y+1,gn,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(ir.indexOf("xfy")>-1){var Ir=W(w,b,y+1,Pr,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(Pt.type!==f)for(;;){y=Pt.len;var $=b[y];if($&&$.name==="atom"&&w.__lookup_operator_classes(F,$.value)){var ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("yf")>-1)Pt={value:new x.type.Term($.value,[Pt.value]),len:++y,type:p};else if(ir.indexOf("yfx")>-1){var Ir=W(w,b,++y,gn,z);if(Ir.type===f)return Ir.derived=!0,Ir;y=Ir.len,Pt={value:new x.type.Term($.value,[Pt.value,Ir.value]),len:y,type:p}}else break}else break}}else X={type:f,value:x.error.syntax(b[Pt.len-1],"operator expected")};return Pt}return Pt}function ee(w,b,y,F){if(!b[y]||b[y].name==="atom"&&b[y].raw==="."&&!F&&(b[y].space||!b[y+1]||b[y+1].name!=="l_paren"))return{type:f,derived:!1,value:x.error.syntax(b[y-1],"unfounded token")};var z=b[y],X=[];if(b[y].name==="atom"&&b[y].raw!==","){if(y++,b[y-1].space)return{type:p,len:y,value:new x.type.Term(z.value,X)};if(b[y]&&b[y].name==="l_paren"){if(b[y+1]&&b[y+1].name==="r_paren")return{type:f,derived:!0,value:x.error.syntax(b[y+1],"argument expected")};var $=W(w,b,++y,"999",!0);if($.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],"argument expected",!b[y])};for(X.push($.value),y=$.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if($=W(w,b,y+1,"999",!0),$.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};X.push($.value),y=$.len}if(b[y]&&b[y].name==="r_paren")y++;else return{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],", or ) expected",!b[y])}}return{type:p,len:y,value:new x.type.Term(z.value,X)}}return{type:f,derived:!1,value:x.error.syntax(b[y],"term expected")}}function ie(w,b,y){if(!b[y])return{type:f,derived:!1,value:x.error.syntax(b[y-1],"[ expected")};if(b[y]&&b[y].name==="l_brace"){var F=W(w,b,++y,"999",!0),z=[F.value],X=void 0;if(F.type===f)return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:new x.type.Term("[]",[])}:{type:f,derived:!0,value:x.error.syntax(b[y],"] expected")};for(y=F.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if(F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};z.push(F.value),y=F.len}var $=!1;if(b[y]&&b[y].name==="bar"){if($=!0,F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};X=F.value,y=F.len}return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:g(z,X)}:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],$?"] expected":", or | or ] expected",!b[y])}}return{type:f,derived:!1,value:x.error.syntax(b[y],"list expected")}}function ue(w,b,y){var F=b[y].line,z=W(w,b,y,w.__get_max_priority(),!1),X=null,$;if(z.type!==f)if(y=z.len,b[y]&&b[y].name==="atom"&&b[y].raw===".")if(y++,x.type.is_term(z.value)){if(z.value.indicator===":-/2"?(X=new x.type.Rule(z.value.args[0],Ce(z.value.args[1])),$={value:X,len:y,type:p}):z.value.indicator==="-->/2"?(X=pe(new x.type.Rule(z.value.args[0],z.value.args[1]),w),X.body=Ce(X.body),$={value:X,len:y,type:x.type.is_rule(X)?p:f}):(X=new x.type.Rule(z.value,null),$={value:X,len:y,type:p}),X){var oe=X.singleton_variables();oe.length>0&&w.throw_warning(x.warning.singleton(oe,X.head.indicator,F))}return $}else return{type:f,value:x.error.syntax(b[y],"callable expected")};else return{type:f,value:x.error.syntax(b[y]?b[y]:b[y-1],". or operator expected")};return z}function le(w,b,y){y=y||{},y.from=y.from?y.from:"$tau-js",y.reconsult=y.reconsult!==void 0?y.reconsult:!0;var F=new U(w),z={},X;F.new_text(b);var $=0,oe=F.get_tokens($);do{if(oe===null||!oe[$])break;var xe=ue(w,oe,$);if(xe.type===f)return new j("throw",[xe.value]);if(xe.value.body===null&&xe.value.head.indicator==="?-/1"){var Te=new it(w.session);Te.add_goal(xe.value.head.args[0]),Te.answer(function(Ct){x.type.is_error(Ct)?w.throw_warning(Ct.args[0]):(Ct===!1||Ct===null)&&w.throw_warning(x.warning.failed_goal(xe.value.head.args[0],xe.len))}),$=xe.len;var lt=!0}else if(xe.value.body===null&&xe.value.head.indicator===":-/1"){var lt=w.run_directive(xe.value.head.args[0]);$=xe.len,xe.value.head.args[0].indicator==="char_conversion/2"&&(oe=F.get_tokens($),$=0)}else{X=xe.value.head.indicator,y.reconsult!==!1&&z[X]!==!0&&!w.is_multifile_predicate(X)&&(w.session.rules[X]=a(w.session.rules[X]||[],function(qt){return qt.dynamic}),z[X]=!0);var lt=w.add_rule(xe.value,y);$=xe.len}if(!lt)return lt}while(!0);return!0}function me(w,b){var y=new U(w);y.new_text(b);var F=0;do{var z=y.get_tokens(F);if(z===null)break;var X=W(w,z,0,w.__get_max_priority(),!1);if(X.type!==f){var $=X.len,oe=$;if(z[$]&&z[$].name==="atom"&&z[$].raw===".")w.add_goal(Ce(X.value));else{var xe=z[$];return new j("throw",[x.error.syntax(xe||z[$-1],". or operator expected",!xe)])}F=X.len+1}else return new j("throw",[X.value])}while(!0);return!0}function pe(w,b){w=w.rename(b);var y=b.next_free_variable(),F=Be(w.body,y,b);return F.error?F.value:(w.body=F.value,w.head.args=w.head.args.concat([y,F.variable]),w.head=new j(w.head.id,w.head.args),w)}function Be(w,b,y){var F;if(x.type.is_term(w)&&w.indicator==="!/0")return{value:w,variable:b,error:!1};if(x.type.is_term(w)&&w.indicator===",/2"){var z=Be(w.args[0],b,y);if(z.error)return z;var X=Be(w.args[1],z.variable,y);return X.error?X:{value:new j(",",[z.value,X.value]),variable:X.variable,error:!1}}else{if(x.type.is_term(w)&&w.indicator==="{}/1")return{value:w.args[0],variable:b,error:!1};if(x.type.is_empty_list(w))return{value:new j("true",[]),variable:b,error:!1};if(x.type.is_list(w)){F=y.next_free_variable();for(var $=w,oe;$.indicator==="./2";)oe=$,$=$.args[1];return x.type.is_variable($)?{value:x.error.instantiation("DCG"),variable:b,error:!0}:x.type.is_empty_list($)?(oe.args[1]=F,{value:new j("=",[b,w]),variable:F,error:!1}):{value:x.error.type("list",w,"DCG"),variable:b,error:!0}}else return x.type.is_callable(w)?(F=y.next_free_variable(),w.args=w.args.concat([b,F]),w=new j(w.id,w.args),{value:w,variable:F,error:!1}):{value:x.error.type("callable",w,"DCG"),variable:b,error:!0}}}function Ce(w){return x.type.is_variable(w)?new j("call",[w]):x.type.is_term(w)&&[",/2",";/2","->/2"].indexOf(w.indicator)!==-1?new j(w.id,[Ce(w.args[0]),Ce(w.args[1])]):w}function g(w,b){for(var y=b||new x.type.Term("[]",[]),F=w.length-1;F>=0;F--)y=new x.type.Term(".",[w[F],y]);return y}function we(w,b){for(var y=w.length-1;y>=0;y--)w[y]===b&&w.splice(y,1)}function ye(w){for(var b={},y=[],F=0;F=0;b--)if(w.charAt(b)==="/")return new j("/",[new j(w.substring(0,b)),new Re(parseInt(w.substring(b+1)),!1)])}function De(w){this.id=w}function Re(w,b){this.is_float=b!==void 0?b:parseInt(w)!==w,this.value=this.is_float?w:parseInt(w)}var mt=0;function j(w,b,y){this.ref=y||++mt,this.id=w,this.args=b||[],this.indicator=w+"/"+this.args.length}var rt=0;function Fe(w,b,y,F,z,X){this.id=rt++,this.stream=w,this.mode=b,this.alias=y,this.type=F!==void 0?F:"text",this.reposition=z!==void 0?z:!0,this.eof_action=X!==void 0?X:"eof_code",this.position=this.mode==="append"?"end_of_stream":0,this.output=this.mode==="write"||this.mode==="append",this.input=this.mode==="read"}function Ne(w){w=w||{},this.links=w}function Pe(w,b,y){b=b||new Ne,y=y||null,this.goal=w,this.substitution=b,this.parent=y}function Ve(w,b,y){this.head=w,this.body=b,this.dynamic=y||!1}function ke(w){w=w===void 0||w<=0?1e3:w,this.rules={},this.src_predicates={},this.rename=0,this.modules=[],this.thread=new it(this),this.total_threads=1,this.renamed_variables={},this.public_predicates={},this.multifile_predicates={},this.limit=w,this.streams={user_input:new Fe(typeof ec<"u"&&ec.exports?nodejs_user_input:tau_user_input,"read","user_input","text",!1,"reset"),user_output:new Fe(typeof ec<"u"&&ec.exports?nodejs_user_output:tau_user_output,"write","user_output","text",!1,"eof_code")},this.file_system=typeof ec<"u"&&ec.exports?nodejs_file_system:tau_file_system,this.standard_input=this.streams.user_input,this.standard_output=this.streams.user_output,this.current_input=this.streams.user_input,this.current_output=this.streams.user_output,this.format_success=function(b){return b.substitution},this.format_error=function(b){return b.goal},this.flag={bounded:x.flag.bounded.value,max_integer:x.flag.max_integer.value,min_integer:x.flag.min_integer.value,integer_rounding_function:x.flag.integer_rounding_function.value,char_conversion:x.flag.char_conversion.value,debug:x.flag.debug.value,max_arity:x.flag.max_arity.value,unknown:x.flag.unknown.value,double_quotes:x.flag.double_quotes.value,occurs_check:x.flag.occurs_check.value,dialect:x.flag.dialect.value,version_data:x.flag.version_data.value,nodejs:x.flag.nodejs.value},this.__loaded_modules=[],this.__char_conversion={},this.__operators={1200:{":-":["fx","xfx"],"-->":["xfx"],"?-":["fx"]},1100:{";":["xfy"]},1050:{"->":["xfy"]},1e3:{",":["xfy"]},900:{"\\+":["fy"]},700:{"=":["xfx"],"\\=":["xfx"],"==":["xfx"],"\\==":["xfx"],"@<":["xfx"],"@=<":["xfx"],"@>":["xfx"],"@>=":["xfx"],"=..":["xfx"],is:["xfx"],"=:=":["xfx"],"=\\=":["xfx"],"<":["xfx"],"=<":["xfx"],">":["xfx"],">=":["xfx"]},600:{":":["xfy"]},500:{"+":["yfx"],"-":["yfx"],"/\\":["yfx"],"\\/":["yfx"]},400:{"*":["yfx"],"/":["yfx"],"//":["yfx"],rem:["yfx"],mod:["yfx"],"<<":["yfx"],">>":["yfx"]},200:{"**":["xfx"],"^":["xfy"],"-":["fy"],"+":["fy"],"\\":["fy"]}}}function it(w){this.epoch=Date.now(),this.session=w,this.session.total_threads++,this.total_steps=0,this.cpu_time=0,this.cpu_time_last=0,this.points=[],this.debugger=!1,this.debugger_states=[],this.level="top_level/0",this.__calls=[],this.current_limit=this.session.limit,this.warnings=[]}function Ue(w,b,y){this.id=w,this.rules=b,this.exports=y,x.module[w]=this}Ue.prototype.exports_predicate=function(w){return this.exports.indexOf(w)!==-1},De.prototype.unify=function(w,b){if(b&&e(w.variables(),this.id)!==-1&&!x.type.is_variable(w))return null;var y={};return y[this.id]=w,new Ne(y)},Re.prototype.unify=function(w,b){return x.type.is_number(w)&&this.value===w.value&&this.is_float===w.is_float?new Ne:null},j.prototype.unify=function(w,b){if(x.type.is_term(w)&&this.indicator===w.indicator){for(var y=new Ne,F=0;F=0){var F=this.args[0].value,z=Math.floor(F/26),X=F%26;return"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[X]+(z!==0?z:"")}switch(this.indicator){case"[]/0":case"{}/0":case"!/0":return this.id;case"{}/1":return"{"+this.args[0].toString(w)+"}";case"./2":for(var $="["+this.args[0].toString(w),oe=this.args[1];oe.indicator==="./2";)$+=", "+oe.args[0].toString(w),oe=oe.args[1];return oe.indicator!=="[]/0"&&($+="|"+oe.toString(w)),$+="]",$;case",/2":return"("+this.args[0].toString(w)+", "+this.args[1].toString(w)+")";default:var xe=this.id,Te=w.session?w.session.lookup_operator(this.id,this.args.length):null;if(w.session===void 0||w.ignore_ops||Te===null)return w.quoted&&!/^(!|,|;|[a-z][0-9a-zA-Z_]*)$/.test(xe)&&xe!=="{}"&&xe!=="[]"&&(xe="'"+P(xe)+"'"),xe+(this.args.length?"("+s(this.args,function(ir){return ir.toString(w)}).join(", ")+")":"");var lt=Te.priority>b.priority||Te.priority===b.priority&&(Te.class==="xfy"&&this.indicator!==b.indicator||Te.class==="yfx"&&this.indicator!==b.indicator||this.indicator===b.indicator&&Te.class==="yfx"&&y==="right"||this.indicator===b.indicator&&Te.class==="xfy"&&y==="left");Te.indicator=this.indicator;var Ct=lt?"(":"",qt=lt?")":"";return this.args.length===0?"("+this.id+")":["fy","fx"].indexOf(Te.class)!==-1?Ct+xe+" "+this.args[0].toString(w,Te)+qt:["yf","xf"].indexOf(Te.class)!==-1?Ct+this.args[0].toString(w,Te)+" "+xe+qt:Ct+this.args[0].toString(w,Te,"left")+" "+this.id+" "+this.args[1].toString(w,Te,"right")+qt}},Fe.prototype.toString=function(w){return"("+this.id+")"},Ne.prototype.toString=function(w){var b="{";for(var y in this.links)this.links.hasOwnProperty(y)&&(b!=="{"&&(b+=", "),b+=y+"/"+this.links[y].toString(w));return b+="}",b},Pe.prototype.toString=function(w){return this.goal===null?"<"+this.substitution.toString(w)+">":"<"+this.goal.toString(w)+", "+this.substitution.toString(w)+">"},Ve.prototype.toString=function(w){return this.body?this.head.toString(w)+" :- "+this.body.toString(w)+".":this.head.toString(w)+"."},ke.prototype.toString=function(w){for(var b="",y=0;y=0;z--)F=new j(".",[b[z],F]);return F}return new j(this.id,s(this.args,function(X){return X.apply(w)}),this.ref)},Fe.prototype.apply=function(w){return this},Ve.prototype.apply=function(w){return new Ve(this.head.apply(w),this.body!==null?this.body.apply(w):null)},Ne.prototype.apply=function(w){var b,y={};for(b in this.links)this.links.hasOwnProperty(b)&&(y[b]=this.links[b].apply(w));return new Ne(y)},j.prototype.select=function(){for(var w=this;w.indicator===",/2";)w=w.args[0];return w},j.prototype.replace=function(w){return this.indicator===",/2"?this.args[0].indicator===",/2"?new j(",",[this.args[0].replace(w),this.args[1]]):w===null?this.args[1]:new j(",",[w,this.args[1]]):w},j.prototype.search=function(w){if(x.type.is_term(w)&&w.ref!==void 0&&this.ref===w.ref)return!0;for(var b=0;bb&&F0&&(b=this.head_point().substitution.domain());e(b,x.format_variable(this.session.rename))!==-1;)this.session.rename++;if(w.id==="_")return new De(x.format_variable(this.session.rename));this.session.renamed_variables[w.id]=x.format_variable(this.session.rename)}return new De(this.session.renamed_variables[w.id])},ke.prototype.next_free_variable=function(){return this.thread.next_free_variable()},it.prototype.next_free_variable=function(){this.session.rename++;var w=[];for(this.points.length>0&&(w=this.head_point().substitution.domain());e(w,x.format_variable(this.session.rename))!==-1;)this.session.rename++;return new De(x.format_variable(this.session.rename))},ke.prototype.is_public_predicate=function(w){return!this.public_predicates.hasOwnProperty(w)||this.public_predicates[w]===!0},it.prototype.is_public_predicate=function(w){return this.session.is_public_predicate(w)},ke.prototype.is_multifile_predicate=function(w){return this.multifile_predicates.hasOwnProperty(w)&&this.multifile_predicates[w]===!0},it.prototype.is_multifile_predicate=function(w){return this.session.is_multifile_predicate(w)},ke.prototype.prepend=function(w){return this.thread.prepend(w)},it.prototype.prepend=function(w){for(var b=w.length-1;b>=0;b--)this.points.push(w[b])},ke.prototype.success=function(w,b){return this.thread.success(w,b)},it.prototype.success=function(w,y){var y=typeof y>"u"?w:y;this.prepend([new Pe(w.goal.replace(null),w.substitution,y)])},ke.prototype.throw_error=function(w){return this.thread.throw_error(w)},it.prototype.throw_error=function(w){this.prepend([new Pe(new j("throw",[w]),new Ne,null,null)])},ke.prototype.step_rule=function(w,b){return this.thread.step_rule(w,b)},it.prototype.step_rule=function(w,b){var y=b.indicator;if(w==="user"&&(w=null),w===null&&this.session.rules.hasOwnProperty(y))return this.session.rules[y];for(var F=w===null?this.session.modules:e(this.session.modules,w)===-1?[]:[w],z=0;z1)&&this.again()},ke.prototype.answers=function(w,b,y){return this.thread.answers(w,b,y)},it.prototype.answers=function(w,b,y){var F=b||1e3,z=this;if(b<=0){y&&y();return}this.answer(function(X){w(X),X!==!1?setTimeout(function(){z.answers(w,b-1,y)},1):y&&y()})},ke.prototype.again=function(w){return this.thread.again(w)},it.prototype.again=function(w){for(var b,y=Date.now();this.__calls.length>0;){for(this.warnings=[],w!==!1&&(this.current_limit=this.session.limit);this.current_limit>0&&this.points.length>0&&this.head_point().goal!==null&&!x.type.is_error(this.head_point().goal);)if(this.current_limit--,this.step()===!0)return;var F=Date.now();this.cpu_time_last=F-y,this.cpu_time+=this.cpu_time_last;var z=this.__calls.shift();this.current_limit<=0?z(null):this.points.length===0?z(!1):x.type.is_error(this.head_point().goal)?(b=this.session.format_error(this.points.pop()),this.points=[],z(b)):(this.debugger&&this.debugger_states.push(this.head_point()),b=this.session.format_success(this.points.pop()),z(b))}},ke.prototype.unfold=function(w){if(w.body===null)return!1;var b=w.head,y=w.body,F=y.select(),z=new it(this),X=[];z.add_goal(F),z.step();for(var $=z.points.length-1;$>=0;$--){var oe=z.points[$],xe=b.apply(oe.substitution),Te=y.replace(oe.goal);Te!==null&&(Te=Te.apply(oe.substitution)),X.push(new Ve(xe,Te))}var lt=this.rules[b.indicator],Ct=e(lt,w);return X.length>0&&Ct!==-1?(lt.splice.apply(lt,[Ct,1].concat(X)),!0):!1},it.prototype.unfold=function(w){return this.session.unfold(w)},De.prototype.interpret=function(w){return x.error.instantiation(w.level)},Re.prototype.interpret=function(w){return this},j.prototype.interpret=function(w){return x.type.is_unitary_list(this)?this.args[0].interpret(w):x.operate(w,this)},De.prototype.compare=function(w){return this.idw.id?1:0},Re.prototype.compare=function(w){if(this.value===w.value&&this.is_float===w.is_float)return 0;if(this.valuew.value)return 1},j.prototype.compare=function(w){if(this.args.lengthw.args.length||this.args.length===w.args.length&&this.id>w.id)return 1;for(var b=0;bF)return 1;if(w.constructor===Re){if(w.is_float&&b.is_float)return 0;if(w.is_float)return-1;if(b.is_float)return 1}return 0},is_substitution:function(w){return w instanceof Ne},is_state:function(w){return w instanceof Pe},is_rule:function(w){return w instanceof Ve},is_variable:function(w){return w instanceof De},is_stream:function(w){return w instanceof Fe},is_anonymous_var:function(w){return w instanceof De&&w.id==="_"},is_callable:function(w){return w instanceof j},is_number:function(w){return w instanceof Re},is_integer:function(w){return w instanceof Re&&!w.is_float},is_float:function(w){return w instanceof Re&&w.is_float},is_term:function(w){return w instanceof j},is_atom:function(w){return w instanceof j&&w.args.length===0},is_ground:function(w){if(w instanceof De)return!1;if(w instanceof j){for(var b=0;b0},is_list:function(w){return w instanceof j&&(w.indicator==="[]/0"||w.indicator==="./2")},is_empty_list:function(w){return w instanceof j&&w.indicator==="[]/0"},is_non_empty_list:function(w){return w instanceof j&&w.indicator==="./2"},is_fully_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof De||w instanceof j&&w.indicator==="[]/0"},is_instantiated_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof j&&w.indicator==="[]/0"},is_unitary_list:function(w){return w instanceof j&&w.indicator==="./2"&&w.args[1]instanceof j&&w.args[1].indicator==="[]/0"},is_character:function(w){return w instanceof j&&(w.id.length===1||w.id.length>0&&w.id.length<=2&&n(w.id,0)>=65536)},is_character_code:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=1114111},is_byte:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=255},is_operator:function(w){return w instanceof j&&x.arithmetic.evaluation[w.indicator]},is_directive:function(w){return w instanceof j&&x.directive[w.indicator]!==void 0},is_builtin:function(w){return w instanceof j&&x.predicate[w.indicator]!==void 0},is_error:function(w){return w instanceof j&&w.indicator==="throw/1"},is_predicate_indicator:function(w){return w instanceof j&&w.indicator==="//2"&&w.args[0]instanceof j&&w.args[0].args.length===0&&w.args[1]instanceof Re&&w.args[1].is_float===!1},is_flag:function(w){return w instanceof j&&w.args.length===0&&x.flag[w.id]!==void 0},is_value_flag:function(w,b){if(!x.type.is_flag(w))return!1;for(var y in x.flag[w.id].allowed)if(x.flag[w.id].allowed.hasOwnProperty(y)&&x.flag[w.id].allowed[y].equals(b))return!0;return!1},is_io_mode:function(w){return x.type.is_atom(w)&&["read","write","append"].indexOf(w.id)!==-1},is_stream_option:function(w){return x.type.is_term(w)&&(w.indicator==="alias/1"&&x.type.is_atom(w.args[0])||w.indicator==="reposition/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="type/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary")||w.indicator==="eof_action/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))},is_stream_position:function(w){return x.type.is_integer(w)&&w.value>=0||x.type.is_atom(w)&&(w.id==="end_of_stream"||w.id==="past_end_of_stream")},is_stream_property:function(w){return x.type.is_term(w)&&(w.indicator==="input/0"||w.indicator==="output/0"||w.indicator==="alias/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="file_name/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="position/1"&&(x.type.is_variable(w.args[0])||x.type.is_stream_position(w.args[0]))||w.indicator==="reposition/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))||w.indicator==="type/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary"))||w.indicator==="mode/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="read"||w.args[0].id==="write"||w.args[0].id==="append"))||w.indicator==="eof_action/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))||w.indicator==="end_of_stream/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="at"||w.args[0].id==="past"||w.args[0].id==="not")))},is_streamable:function(w){return w.__proto__.stream!==void 0},is_read_option:function(w){return x.type.is_term(w)&&["variables/1","variable_names/1","singletons/1"].indexOf(w.indicator)!==-1},is_write_option:function(w){return x.type.is_term(w)&&(w.indicator==="quoted/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="ignore_ops/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="numbervars/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))},is_close_option:function(w){return x.type.is_term(w)&&w.indicator==="force/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")},is_modifiable_flag:function(w){return x.type.is_flag(w)&&x.flag[w.id].changeable},is_module:function(w){return w instanceof j&&w.indicator==="library/1"&&w.args[0]instanceof j&&w.args[0].args.length===0&&x.module[w.args[0].id]!==void 0}},arithmetic:{evaluation:{"e/0":{type_args:null,type_result:!0,fn:function(w){return Math.E}},"pi/0":{type_args:null,type_result:!0,fn:function(w){return Math.PI}},"tau/0":{type_args:null,type_result:!0,fn:function(w){return 2*Math.PI}},"epsilon/0":{type_args:null,type_result:!0,fn:function(w){return Number.EPSILON}},"+/1":{type_args:null,type_result:null,fn:function(w,b){return w}},"-/1":{type_args:null,type_result:null,fn:function(w,b){return-w}},"\\/1":{type_args:!1,type_result:!1,fn:function(w,b){return~w}},"abs/1":{type_args:null,type_result:null,fn:function(w,b){return Math.abs(w)}},"sign/1":{type_args:null,type_result:null,fn:function(w,b){return Math.sign(w)}},"float_integer_part/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"float_fractional_part/1":{type_args:!0,type_result:!0,fn:function(w,b){return w-parseInt(w)}},"float/1":{type_args:null,type_result:!0,fn:function(w,b){return parseFloat(w)}},"floor/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.floor(w)}},"truncate/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"round/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.round(w)}},"ceiling/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.ceil(w)}},"sin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sin(w)}},"cos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.cos(w)}},"tan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.tan(w)}},"asin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.asin(w)}},"acos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.acos(w)}},"atan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.atan(w)}},"atan2/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.atan2(w,b)}},"exp/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.exp(w)}},"sqrt/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sqrt(w)}},"log/1":{type_args:null,type_result:!0,fn:function(w,b){return w>0?Math.log(w):x.error.evaluation("undefined",b.__call_indicator)}},"+/2":{type_args:null,type_result:null,fn:function(w,b,y){return w+b}},"-/2":{type_args:null,type_result:null,fn:function(w,b,y){return w-b}},"*/2":{type_args:null,type_result:null,fn:function(w,b,y){return w*b}},"//2":{type_args:null,type_result:!0,fn:function(w,b,y){return b?w/b:x.error.evaluation("zero_division",y.__call_indicator)}},"///2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?parseInt(w/b):x.error.evaluation("zero_division",y.__call_indicator)}},"**/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.pow(w,b)}},"^/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.pow(w,b)}},"<>/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w>>b}},"/\\/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w&b}},"\\//2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w|b}},"xor/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w^b}},"rem/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w%b:x.error.evaluation("zero_division",y.__call_indicator)}},"mod/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w-parseInt(w/b)*b:x.error.evaluation("zero_division",y.__call_indicator)}},"max/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.max(w,b)}},"min/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.min(w,b)}}}},directive:{"dynamic/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_compound(y)||y.indicator!=="//2")w.throw_error(x.error.type("predicate_indicator",y,b.indicator));else if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],b.indicator));else if(!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],b.indicator));else{var F=b.args[0].args[0].id+"/"+b.args[0].args[1].value;w.session.public_predicates[F]=!0,w.session.rules[F]||(w.session.rules[F]=[])}},"multifile/1":function(w,b){var y=b.args[0];x.type.is_variable(y)?w.throw_error(x.error.instantiation(b.indicator)):!x.type.is_compound(y)||y.indicator!=="//2"?w.throw_error(x.error.type("predicate_indicator",y,b.indicator)):x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1])?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y.args[0])?x.type.is_integer(y.args[1])?w.session.multifile_predicates[b.args[0].args[0].id+"/"+b.args[0].args[1].value]=!0:w.throw_error(x.error.type("integer",y.args[1],b.indicator)):w.throw_error(x.error.type("atom",y.args[0],b.indicator))},"set_prolog_flag/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y)?x.type.is_flag(y)?x.type.is_value_flag(y,F)?x.type.is_modifiable_flag(y)?w.session.flag[y.id]=F:w.throw_error(x.error.permission("modify","flag",y)):w.throw_error(x.error.domain("flag_value",new j("+",[y,F]),b.indicator)):w.throw_error(x.error.domain("prolog_flag",y,b.indicator)):w.throw_error(x.error.type("atom",y,b.indicator))},"use_module/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_term(y))w.throw_error(x.error.type("term",y,b.indicator));else if(x.type.is_module(y)){var F=y.args[0].id;e(w.session.modules,F)===-1&&w.session.modules.push(F)}},"char_conversion/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_character(y)?x.type.is_character(F)?y.id===F.id?delete w.session.__char_conversion[y.id]:w.session.__char_conversion[y.id]=F.id:w.throw_error(x.error.type("character",F,b.indicator)):w.throw_error(x.error.type("character",y,b.indicator))},"op/3":function(w,b){var y=b.args[0],F=b.args[1],z=b.args[2];if(x.type.is_variable(y)||x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_integer(y))w.throw_error(x.error.type("integer",y,b.indicator));else if(!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,b.indicator));else if(!x.type.is_atom(z))w.throw_error(x.error.type("atom",z,b.indicator));else if(y.value<0||y.value>1200)w.throw_error(x.error.domain("operator_priority",y,b.indicator));else if(z.id===",")w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(z.id==="|"&&(y.value<1001||F.id.length!==3))w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(["fy","fx","yf","xf","xfx","yfx","xfy"].indexOf(F.id)===-1)w.throw_error(x.error.domain("operator_specifier",F,b.indicator));else{var X={prefix:null,infix:null,postfix:null};for(var $ in w.session.__operators)if(w.session.__operators.hasOwnProperty($)){var oe=w.session.__operators[$][z.id];oe&&(e(oe,"fx")!==-1&&(X.prefix={priority:$,type:"fx"}),e(oe,"fy")!==-1&&(X.prefix={priority:$,type:"fy"}),e(oe,"xf")!==-1&&(X.postfix={priority:$,type:"xf"}),e(oe,"yf")!==-1&&(X.postfix={priority:$,type:"yf"}),e(oe,"xfx")!==-1&&(X.infix={priority:$,type:"xfx"}),e(oe,"xfy")!==-1&&(X.infix={priority:$,type:"xfy"}),e(oe,"yfx")!==-1&&(X.infix={priority:$,type:"yfx"}))}var xe;switch(F.id){case"fy":case"fx":xe="prefix";break;case"yf":case"xf":xe="postfix";break;default:xe="infix";break}if(((X.prefix&&xe==="prefix"||X.postfix&&xe==="postfix"||X.infix&&xe==="infix")&&X[xe].type!==F.id||X.infix&&xe==="postfix"||X.postfix&&xe==="infix")&&y.value!==0)w.throw_error(x.error.permission("create","operator",z,b.indicator));else return X[xe]&&(we(w.session.__operators[X[xe].priority][z.id],F.id),w.session.__operators[X[xe].priority][z.id].length===0&&delete w.session.__operators[X[xe].priority][z.id]),y.value>0&&(w.session.__operators[y.value]||(w.session.__operators[y.value.toString()]={}),w.session.__operators[y.value][z.id]||(w.session.__operators[y.value][z.id]=[]),w.session.__operators[y.value][z.id].push(F.id)),!0}}},predicate:{"op/3":function(w,b,y){x.directive["op/3"](w,y)&&w.success(b)},"current_op/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2],$=[];for(var oe in w.session.__operators)for(var xe in w.session.__operators[oe])for(var Te=0;Te/2"){var F=w.points,z=w.session.format_success,X=w.session.format_error;w.session.format_success=function(Te){return Te.substitution},w.session.format_error=function(Te){return Te.goal},w.points=[new Pe(y.args[0].args[0],b.substitution,b)];var $=function(Te){w.points=F,w.session.format_success=z,w.session.format_error=X,Te===!1?w.prepend([new Pe(b.goal.replace(y.args[1]),b.substitution,b)]):x.type.is_error(Te)?w.throw_error(Te.args[0]):Te===null?(w.prepend([b]),w.__calls.shift()(null)):w.prepend([new Pe(b.goal.replace(y.args[0].args[1]).apply(Te),b.substitution.apply(Te),b)])};w.__calls.unshift($)}else{var oe=new Pe(b.goal.replace(y.args[0]),b.substitution,b),xe=new Pe(b.goal.replace(y.args[1]),b.substitution,b);w.prepend([oe,xe])}},"!/0":function(w,b,y){var F,z,X=[];for(F=b,z=null;F.parent!==null&&F.parent.goal.search(y);)if(z=F,F=F.parent,F.goal!==null){var $=F.goal.select();if($&&$.id==="call"&&$.search(y)){F=z;break}}for(var oe=w.points.length-1;oe>=0;oe--){for(var xe=w.points[oe],Te=xe.parent;Te!==null&&Te!==F.parent;)Te=Te.parent;Te===null&&Te!==F.parent&&X.push(xe)}w.points=X.reverse(),w.success(b)},"\\+/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(w.level)):x.type.is_callable(F)?w.prepend([new Pe(b.goal.replace(new j(",",[new j(",",[new j("call",[F]),new j("!",[])]),new j("fail",[])])),b.substitution,b),new Pe(b.goal.replace(null),b.substitution,b)]):w.throw_error(x.error.type("callable",F,w.level))},"->/2":function(w,b,y){var F=b.goal.replace(new j(",",[y.args[0],new j(",",[new j("!"),y.args[1]])]));w.prepend([new Pe(F,b.substitution,b)])},"fail/0":function(w,b,y){},"false/0":function(w,b,y){},"true/0":function(w,b,y){w.success(b)},"call/1":se(1),"call/2":se(2),"call/3":se(3),"call/4":se(4),"call/5":se(5),"call/6":se(6),"call/7":se(7),"call/8":se(8),"once/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("call",[F]),new j("!",[])])),b.substitution,b)])},"forall/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("\\+",[new j(",",[new j("call",[F]),new j("\\+",[new j("call",[z])])])])),b.substitution,b)])},"repeat/0":function(w,b,y){w.prepend([new Pe(b.goal.replace(null),b.substitution,b),b])},"throw/1":function(w,b,y){x.type.is_variable(y.args[0])?w.throw_error(x.error.instantiation(w.level)):w.throw_error(y.args[0])},"catch/3":function(w,b,y){var F=w.points;w.points=[],w.prepend([new Pe(y.args[0],b.substitution,b)]);var z=w.session.format_success,X=w.session.format_error;w.session.format_success=function(oe){return oe.substitution},w.session.format_error=function(oe){return oe.goal};var $=function(oe){var xe=w.points;if(w.points=F,w.session.format_success=z,w.session.format_error=X,x.type.is_error(oe)){for(var Te=[],lt=w.points.length-1;lt>=0;lt--){for(var ir=w.points[lt],Ct=ir.parent;Ct!==null&&Ct!==b.parent;)Ct=Ct.parent;Ct===null&&Ct!==b.parent&&Te.push(ir)}w.points=Te;var qt=w.get_flag("occurs_check").indicator==="true/0",ir=new Pe,Pt=x.unify(oe.args[0],y.args[1],qt);Pt!==null?(ir.substitution=b.substitution.apply(Pt),ir.goal=b.goal.replace(y.args[2]).apply(Pt),ir.parent=b,w.prepend([ir])):w.throw_error(oe.args[0])}else if(oe!==!1){for(var gn=oe===null?[]:[new Pe(b.goal.apply(oe).replace(null),b.substitution.apply(oe),b)],Pr=[],lt=xe.length-1;lt>=0;lt--){Pr.push(xe[lt]);var Ir=xe[lt].goal!==null?xe[lt].goal.select():null;if(x.type.is_term(Ir)&&Ir.indicator==="!/0")break}var Or=s(Pr,function(on){return on.goal===null&&(on.goal=new j("true",[])),on=new Pe(b.goal.replace(new j("catch",[on.goal,y.args[1],y.args[2]])),b.substitution.apply(on.substitution),on.parent),on.exclude=y.args[0].variables(),on}).reverse();w.prepend(Or),w.prepend(gn),oe===null&&(this.current_limit=0,w.__calls.shift()(null))}};w.__calls.unshift($)},"=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=new Pe,X=x.unify(y.args[0],y.args[1],F);X!==null&&(z.goal=b.goal.apply(X).replace(null),z.substitution=b.substitution.apply(X),z.parent=b,w.prepend([z]))},"unify_with_occurs_check/2":function(w,b,y){var F=new Pe,z=x.unify(y.args[0],y.args[1],!0);z!==null&&(F.goal=b.goal.apply(z).replace(null),F.substitution=b.substitution.apply(z),F.parent=b,w.prepend([F]))},"\\=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[0],y.args[1],F);z===null&&w.success(b)},"subsumes_term/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[1],y.args[0],F);z!==null&&y.args[1].apply(z).equals(y.args[1])&&w.success(b)},"findall/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(z))w.throw_error(x.error.type("callable",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=w.next_free_variable(),oe=new j(",",[z,new j("=",[$,F])]),xe=w.points,Te=w.session.limit,lt=w.session.format_success;w.session.format_success=function(ir){return ir.substitution},w.add_goal(oe,!0,b);var Ct=[],qt=function(ir){if(ir!==!1&&ir!==null&&!x.type.is_error(ir))w.__calls.unshift(qt),Ct.push(ir.links[$.id]),w.session.limit=w.current_limit;else if(w.points=xe,w.session.limit=Te,w.session.format_success=lt,x.type.is_error(ir))w.throw_error(ir.args[0]);else if(w.current_limit>0){for(var Pt=new j("[]"),gn=Ct.length-1;gn>=0;gn--)Pt=new j(".",[Ct[gn],Pt]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Pt])),b.substitution,b)])}};w.__calls.unshift(qt)}},"bagof/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(X))w.throw_error(x.error.type("callable",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;X.indicator==="^/2"?(xe=X.args[0].variables(),X=X.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=X.variables().filter(function(Or){return e(xe,Or)===-1}),lt=new j("[]"),Ct=Te.length-1;Ct>=0;Ct--)lt=new j(".",[new De(Te[Ct]),lt]);var qt=new j(",",[X,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Or){return Or.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Or){if(Or!==!1&&Or!==null&&!x.type.is_error(Or)){w.__calls.unshift(Ir);var on=!1,ai=Or.links[oe.id].args[0],Io=Or.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var $s=Pr[rs];if($s.variables.equals(ai)){$s.answers.push(Io),on=!0;break}}on||Pr.push({variables:ai,answers:[Io]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Or))w.throw_error(Or.args[0]);else if(w.current_limit>0){for(var Co=[],ji=0;ji=0;wo--)eo=new j(".",[Or[wo],eo]);Co.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[ji].variables]),new j("=",[$,eo])])),b.substitution,b))}w.prepend(Co)}};w.__calls.unshift(Ir)}},"setof/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(X))w.throw_error(x.error.type("callable",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;X.indicator==="^/2"?(xe=X.args[0].variables(),X=X.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=X.variables().filter(function(Or){return e(xe,Or)===-1}),lt=new j("[]"),Ct=Te.length-1;Ct>=0;Ct--)lt=new j(".",[new De(Te[Ct]),lt]);var qt=new j(",",[X,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Or){return Or.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Or){if(Or!==!1&&Or!==null&&!x.type.is_error(Or)){w.__calls.unshift(Ir);var on=!1,ai=Or.links[oe.id].args[0],Io=Or.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var $s=Pr[rs];if($s.variables.equals(ai)){$s.answers.push(Io),on=!0;break}}on||Pr.push({variables:ai,answers:[Io]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Or))w.throw_error(Or.args[0]);else if(w.current_limit>0){for(var Co=[],ji=0;ji=0;wo--)eo=new j(".",[Or[wo],eo]);Co.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[ji].variables]),new j("=",[$,eo])])),b.substitution,b))}w.prepend(Co)}};w.__calls.unshift(Ir)}},"functor/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(z)&&(x.type.is_variable(X)||x.type.is_variable($)))w.throw_error(x.error.instantiation("functor/3"));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",y.args[2],"functor/3"));else if(!x.type.is_variable(X)&&!x.type.is_atomic(X))w.throw_error(x.error.type("atomic",y.args[1],"functor/3"));else if(x.type.is_integer(X)&&x.type.is_integer($)&&$.value!==0)w.throw_error(x.error.type("atom",y.args[1],"functor/3"));else if(x.type.is_variable(z)){if(y.args[2].value>=0){for(var oe=[],xe=0;xe<$.value;xe++)oe.push(w.next_free_variable());var Te=x.type.is_integer(X)?X:new j(X.id,oe);w.prepend([new Pe(b.goal.replace(new j("=",[z,Te])),b.substitution,b)])}}else{var lt=x.type.is_integer(z)?z:new j(z.id,[]),Ct=x.type.is_integer(z)?new Re(0,!1):new Re(z.args.length,!1),qt=new j(",",[new j("=",[lt,X]),new j("=",[Ct,$])]);w.prepend([new Pe(b.goal.replace(qt),b.substitution,b)])}},"arg/3":function(w,b,y){if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(y.indicator));else if(y.args[0].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[0],y.indicator));else if(!x.type.is_compound(y.args[1]))w.throw_error(x.error.type("compound",y.args[1],y.indicator));else{var F=y.args[0].value;if(F>0&&F<=y.args[1].args.length){var z=new j("=",[y.args[1].args[F-1],y.args[2]]);w.prepend([new Pe(b.goal.replace(z),b.substitution,b)])}}},"=../2":function(w,b,y){var F;if(x.type.is_variable(y.args[0])&&(x.type.is_variable(y.args[1])||x.type.is_non_empty_list(y.args[1])&&x.type.is_variable(y.args[1].args[0])))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_fully_list(y.args[1]))w.throw_error(x.error.type("list",y.args[1],y.indicator));else if(x.type.is_variable(y.args[0])){if(!x.type.is_variable(y.args[1])){var X=[];for(F=y.args[1].args[1];F.indicator==="./2";)X.push(F.args[0]),F=F.args[1];x.type.is_variable(y.args[0])&&x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):X.length===0&&x.type.is_compound(y.args[1].args[0])?w.throw_error(x.error.type("atomic",y.args[1].args[0],y.indicator)):X.length>0&&(x.type.is_compound(y.args[1].args[0])||x.type.is_number(y.args[1].args[0]))?w.throw_error(x.error.type("atom",y.args[1].args[0],y.indicator)):X.length===0?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[1].args[0],y.args[0]],b)),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[new j(y.args[1].args[0].id,X),y.args[0]])),b.substitution,b)])}}else{if(x.type.is_atomic(y.args[0]))F=new j(".",[y.args[0],new j("[]")]);else{F=new j("[]");for(var z=y.args[0].args.length-1;z>=0;z--)F=new j(".",[y.args[0].args[z],F]);F=new j(".",[new j(y.args[0].id),F])}w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"copy_term/2":function(w,b,y){var F=y.args[0].rename(w);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b.parent)])},"term_variables/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_fully_list(z))w.throw_error(x.error.type("list",z,y.indicator));else{var X=g(s(ye(F.variables()),function($){return new De($)}));w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"clause/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_callable(y.args[1]))w.throw_error(x.error.type("callable",y.args[1],y.indicator));else if(w.session.rules[y.args[0].indicator]!==void 0)if(w.is_public_predicate(y.args[0].indicator)){var F=[];for(var z in w.session.rules[y.args[0].indicator])if(w.session.rules[y.args[0].indicator].hasOwnProperty(z)){var X=w.session.rules[y.args[0].indicator][z];w.session.renamed_variables={},X=X.rename(w),X.body===null&&(X.body=new j("true"));var $=new j(",",[new j("=",[X.head,y.args[0]]),new j("=",[X.body,y.args[1]])]);F.push(new Pe(b.goal.replace($),b.substitution,b))}w.prepend(F)}else w.throw_error(x.error.permission("access","private_procedure",y.args[0].indicator,y.indicator))},"current_predicate/1":function(w,b,y){var F=y.args[0];if(!x.type.is_variable(F)&&(!x.type.is_compound(F)||F.indicator!=="//2"))w.throw_error(x.error.type("predicate_indicator",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[0])&&!x.type.is_atom(F.args[0]))w.throw_error(x.error.type("atom",F.args[0],y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[1])&&!x.type.is_integer(F.args[1]))w.throw_error(x.error.type("integer",F.args[1],y.indicator));else{var z=[];for(var X in w.session.rules)if(w.session.rules.hasOwnProperty(X)){var $=X.lastIndexOf("/"),oe=X.substr(0,$),xe=parseInt(X.substr($+1,X.length-($+1))),Te=new j("/",[new j(oe),new Re(xe,!1)]),lt=new j("=",[Te,F]);z.push(new Pe(b.goal.replace(lt),b.substitution,b))}w.prepend(z)}},"asserta/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator]=[new Ve(F,z,!0)].concat(w.session.rules[F.indicator]),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"assertz/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator].push(new Ve(F,z,!0)),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"retract/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;if(y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=y.args[0].args[1]):(F=y.args[0],z=new j("true")),typeof b.retract>"u")if(w.is_public_predicate(F.indicator)){if(w.session.rules[F.indicator]!==void 0){for(var X=[],$=0;$w.get_flag("max_arity").value)w.throw_error(x.error.representation("max_arity",y.indicator));else{var F=y.args[0].args[0].id+"/"+y.args[0].args[1].value;w.is_public_predicate(F)?(delete w.session.rules[F],w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F,y.indicator))}},"atom_length/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],y.indicator));else if(x.type.is_integer(y.args[1])&&y.args[1].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[1],y.indicator));else{var F=new Re(y.args[0].id.length,!1);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"atom_concat/3":function(w,b,y){var F,z,X=y.args[0],$=y.args[1],oe=y.args[2];if(x.type.is_variable(oe)&&(x.type.is_variable(X)||x.type.is_variable($)))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_atom(X))w.throw_error(x.error.type("atom",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_atom($))w.throw_error(x.error.type("atom",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_atom(oe))w.throw_error(x.error.type("atom",oe,y.indicator));else{var xe=x.type.is_variable(X),Te=x.type.is_variable($);if(!xe&&!Te)z=new j("=",[oe,new j(X.id+$.id)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]);else if(xe&&!Te)F=oe.id.substr(0,oe.id.length-$.id.length),F+$.id===oe.id&&(z=new j("=",[X,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else if(Te&&!xe)F=oe.id.substr(X.id.length),X.id+F===oe.id&&(z=new j("=",[$,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else{for(var lt=[],Ct=0;Ct<=oe.id.length;Ct++){var qt=new j(oe.id.substr(0,Ct)),ir=new j(oe.id.substr(Ct));z=new j(",",[new j("=",[qt,X]),new j("=",[ir,$])]),lt.push(new Pe(b.goal.replace(z),b.substitution,b))}w.prepend(lt)}}},"sub_atom/5":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2],oe=y.args[3],xe=y.args[4];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_integer(X))w.throw_error(x.error.type("integer",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_integer(oe))w.throw_error(x.error.type("integer",oe,y.indicator));else if(x.type.is_integer(X)&&X.value<0)w.throw_error(x.error.domain("not_less_than_zero",X,y.indicator));else if(x.type.is_integer($)&&$.value<0)w.throw_error(x.error.domain("not_less_than_zero",$,y.indicator));else if(x.type.is_integer(oe)&&oe.value<0)w.throw_error(x.error.domain("not_less_than_zero",oe,y.indicator));else{var Te=[],lt=[],Ct=[];if(x.type.is_variable(X))for(F=0;F<=z.id.length;F++)Te.push(F);else Te.push(X.value);if(x.type.is_variable($))for(F=0;F<=z.id.length;F++)lt.push(F);else lt.push($.value);if(x.type.is_variable(oe))for(F=0;F<=z.id.length;F++)Ct.push(F);else Ct.push(oe.value);var qt=[];for(var ir in Te)if(Te.hasOwnProperty(ir)){F=Te[ir];for(var Pt in lt)if(lt.hasOwnProperty(Pt)){var gn=lt[Pt],Pr=z.id.length-F-gn;if(e(Ct,Pr)!==-1&&F+gn+Pr===z.id.length){var Ir=z.id.substr(F,gn);if(z.id===z.id.substr(0,F)+Ir+z.id.substr(F+gn,Pr)){var Or=new j("=",[new j(Ir),xe]),on=new j("=",[X,new Re(F)]),ai=new j("=",[$,new Re(gn)]),Io=new j("=",[oe,new Re(Pr)]),rs=new j(",",[new j(",",[new j(",",[on,ai]),Io]),Or]);qt.push(new Pe(b.goal.replace(rs),b.substitution,b))}}}}w.prepend(qt)}},"atom_chars/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))Te+=oe.args[0].id;else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var X=new j("[]"),$=F.id.length-1;$>=0;$--)X=new j(".",[new j(F.id.charAt($)),X]);w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"atom_codes/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))Te+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.representation("character_code",y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var X=new j("[]"),$=F.id.length-1;$>=0;$--)X=new j(".",[new Re(n(F.id,$),!1),X]);w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"char_code/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_character(F))w.throw_error(x.error.type("character",F,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character_code(z))w.throw_error(x.error.representation("character_code",y.indicator));else if(x.type.is_variable(z)){var X=new Re(n(F.id,0),!1);w.prepend([new Pe(b.goal.replace(new j("=",[X,z])),b.substitution,b)])}else{var $=new j(c(z.value));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"number_chars/2":function(w,b,y){var F,z=y.args[0],X=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(X)){var oe=X,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))F+=oe.args[0].id;else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",X,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Ct=new j("[]"),qt=F.length-1;qt>=0;qt--)Ct=new j(".",[new j(F.charAt(qt)),Ct]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Ct])),b.substitution,b)])}}},"number_codes/2":function(w,b,y){var F,z=y.args[0],X=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(X)){var oe=X,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))F+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character_code",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",X,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Ct=new j("[]"),qt=F.length-1;qt>=0;qt--)Ct=new j(".",[new Re(n(F,qt),!1),Ct]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Ct])),b.substitution,b)])}}},"upcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toUpperCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"downcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toLowerCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"atomic_list_concat/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("atomic_list_concat",[F,new j("",[]),z])),b.substitution,b)])},"atomic_list_concat/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(z)||x.type.is_variable(F)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_list(F))w.throw_error(x.error.type("list",F,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_atom(X))w.throw_error(x.error.type("atom",X,y.indicator));else if(x.type.is_variable(X)){for(var oe="",xe=F;x.type.is_term(xe)&&xe.indicator==="./2";){if(!x.type.is_atom(xe.args[0])&&!x.type.is_number(xe.args[0])){w.throw_error(x.error.type("atomic",xe.args[0],y.indicator));return}oe!==""&&(oe+=z.id),x.type.is_atom(xe.args[0])?oe+=xe.args[0].id:oe+=""+xe.args[0].value,xe=xe.args[1]}oe=new j(oe,[]),x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_term(xe)||xe.indicator!=="[]/0"?w.throw_error(x.error.type("list",F,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[oe,X])),b.substitution,b)])}else{var $=g(s(X.id.split(z.id),function(Te){return new j(Te,[])}));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"@=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>0&&w.success(b)},"@>=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>=0&&w.success(b)},"compare/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_atom(F)&&["<",">","="].indexOf(F.id)===-1)w.throw_error(x.type.domain("order",F,y.indicator));else{var $=x.compare(z,X);$=$===0?"=":$===-1?"<":">",w.prepend([new Pe(b.goal.replace(new j("=",[F,new j($,[])])),b.substitution,b)])}},"is/2":function(w,b,y){var F=y.args[1].interpret(w);x.type.is_number(F)?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[0],F],w.level)),b.substitution,b)]):w.throw_error(F)},"between/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_integer(F))w.throw_error(x.error.type("integer",F,y.indicator));else if(!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_integer(X))w.throw_error(x.error.type("integer",X,y.indicator));else if(x.type.is_variable(X)){var $=[new Pe(b.goal.replace(new j("=",[X,F])),b.substitution,b)];F.value=X.value&&w.success(b)},"succ/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)&&x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_integer(F)?w.throw_error(x.error.type("integer",F,y.indicator)):!x.type.is_variable(z)&&!x.type.is_integer(z)?w.throw_error(x.error.type("integer",z,y.indicator)):!x.type.is_variable(F)&&F.value<0?w.throw_error(x.error.domain("not_less_than_zero",F,y.indicator)):!x.type.is_variable(z)&&z.value<0?w.throw_error(x.error.domain("not_less_than_zero",z,y.indicator)):(x.type.is_variable(z)||z.value>0)&&(x.type.is_variable(F)?w.prepend([new Pe(b.goal.replace(new j("=",[F,new Re(z.value-1,!1)])),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[z,new Re(F.value+1,!1)])),b.substitution,b)]))},"=:=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F===0&&w.success(b)},"=\\=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F!==0&&w.success(b)},"/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>0&&w.success(b)},">=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>=0&&w.success(b)},"var/1":function(w,b,y){x.type.is_variable(y.args[0])&&w.success(b)},"atom/1":function(w,b,y){x.type.is_atom(y.args[0])&&w.success(b)},"atomic/1":function(w,b,y){x.type.is_atomic(y.args[0])&&w.success(b)},"compound/1":function(w,b,y){x.type.is_compound(y.args[0])&&w.success(b)},"integer/1":function(w,b,y){x.type.is_integer(y.args[0])&&w.success(b)},"float/1":function(w,b,y){x.type.is_float(y.args[0])&&w.success(b)},"number/1":function(w,b,y){x.type.is_number(y.args[0])&&w.success(b)},"nonvar/1":function(w,b,y){x.type.is_variable(y.args[0])||w.success(b)},"ground/1":function(w,b,y){y.variables().length===0&&w.success(b)},"acyclic_term/1":function(w,b,y){for(var F=b.substitution.apply(b.substitution),z=y.args[0].variables(),X=0;X0?Pt[Pt.length-1]:null,Pt!==null&&(qt=W(w,Pt,0,w.__get_max_priority(),!1))}if(qt.type===p&&qt.len===Pt.length-1&&gn.value==="."){qt=qt.value.rename(w);var Pr=new j("=",[z,qt]);if(oe.variables){var Ir=g(s(ye(qt.variables()),function(Or){return new De(Or)}));Pr=new j(",",[Pr,new j("=",[oe.variables,Ir])])}if(oe.variable_names){var Ir=g(s(ye(qt.variables()),function(on){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===on)break;return new j("=",[new j(ai,[]),new De(on)])}));Pr=new j(",",[Pr,new j("=",[oe.variable_names,Ir])])}if(oe.singletons){var Ir=g(s(new Ve(qt,null).singleton_variables(),function(on){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===on)break;return new j("=",[new j(ai,[]),new De(on)])}));Pr=new j(",",[Pr,new j("=",[oe.singletons,Ir])])}w.prepend([new Pe(b.goal.replace(Pr),b.substitution,b)])}else qt.type===p?w.throw_error(x.error.syntax(Pt[qt.len],"unexpected token",!1)):w.throw_error(qt.value)}}},"write/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write",[new De("S"),F])])),b.substitution,b)])},"write/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("false",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"writeq/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("writeq",[new De("S"),F])])),b.substitution,b)])},"writeq/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"write_canonical/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_canonical",[new De("S"),F])])),b.substitution,b)])},"write_canonical/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("true")]),new j(".",[new j("numbervars",[new j("false")]),new j("[]",[])])])])])),b.substitution,b)])},"write_term/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_term",[new De("S"),F,z])])),b.substitution,b)])},"write_term/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2],$=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain("stream_or_alias",F,y.indicator));else if(!x.type.is_stream($)||$.stream===null)w.throw_error(x.error.existence("stream",F,y.indicator));else if($.input)w.throw_error(x.error.permission("output","stream",F,y.indicator));else if($.type==="binary")w.throw_error(x.error.permission("output","binary_stream",F,y.indicator));else if($.position==="past_end_of_stream"&&$.eof_action==="error")w.throw_error(x.error.permission("output","past_end_of_stream",F,y.indicator));else{for(var oe={},xe=X,Te;x.type.is_term(xe)&&xe.indicator==="./2";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_write_option(Te)){w.throw_error(x.error.domain("write_option",Te,y.indicator));return}oe[Te.id]=Te.args[0].id==="true",xe=xe.args[1]}if(xe.indicator!=="[]/0"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type("list",X,y.indicator));return}else{oe.session=w.session;var lt=z.toString(oe);$.stream.put(lt,$.position),typeof $.position=="number"&&($.position+=lt.length),w.success(b)}}},"halt/0":function(w,b,y){w.points=[]},"halt/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_integer(F)?w.points=[]:w.throw_error(x.error.type("integer",F,y.indicator))},"current_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_flag(F))w.throw_error(x.error.domain("prolog_flag",F,y.indicator));else{var X=[];for(var $ in x.flag)if(x.flag.hasOwnProperty($)){var oe=new j(",",[new j("=",[new j($),F]),new j("=",[w.get_flag($),z])]);X.push(new Pe(b.goal.replace(oe),b.substitution,b))}w.prepend(X)}},"set_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?x.type.is_flag(F)?x.type.is_value_flag(F,z)?x.type.is_modifiable_flag(F)?(w.session.flag[F.id]=z,w.success(b)):w.throw_error(x.error.permission("modify","flag",F)):w.throw_error(x.error.domain("flag_value",new j("+",[F,z]),y.indicator)):w.throw_error(x.error.domain("prolog_flag",F,y.indicator)):w.throw_error(x.error.type("atom",F,y.indicator))}},flag:{bounded:{allowed:[new j("true"),new j("false")],value:new j("true"),changeable:!1},max_integer:{allowed:[new Re(Number.MAX_SAFE_INTEGER)],value:new Re(Number.MAX_SAFE_INTEGER),changeable:!1},min_integer:{allowed:[new Re(Number.MIN_SAFE_INTEGER)],value:new Re(Number.MIN_SAFE_INTEGER),changeable:!1},integer_rounding_function:{allowed:[new j("down"),new j("toward_zero")],value:new j("toward_zero"),changeable:!1},char_conversion:{allowed:[new j("on"),new j("off")],value:new j("on"),changeable:!0},debug:{allowed:[new j("on"),new j("off")],value:new j("off"),changeable:!0},max_arity:{allowed:[new j("unbounded")],value:new j("unbounded"),changeable:!1},unknown:{allowed:[new j("error"),new j("fail"),new j("warning")],value:new j("error"),changeable:!0},double_quotes:{allowed:[new j("chars"),new j("codes"),new j("atom")],value:new j("codes"),changeable:!0},occurs_check:{allowed:[new j("false"),new j("true")],value:new j("false"),changeable:!0},dialect:{allowed:[new j("tau")],value:new j("tau"),changeable:!1},version_data:{allowed:[new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)])],value:new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)]),changeable:!1},nodejs:{allowed:[new j("yes"),new j("no")],value:new j(typeof ec<"u"&&ec.exports?"yes":"no"),changeable:!1}},unify:function(w,b,y){y=y===void 0?!1:y;for(var F=[{left:w,right:b}],z={};F.length!==0;){var X=F.pop();if(w=X.left,b=X.right,x.type.is_term(w)&&x.type.is_term(b)){if(w.indicator!==b.indicator)return null;for(var $=0;$z.value?1:0:z}else return F},operate:function(w,b){if(x.type.is_operator(b)){for(var y=x.type.is_operator(b),F=[],z,X=!1,$=0;$w.get_flag("max_integer").value||z0?w.start+w.matches[0].length:w.start,z=y?new j("token_not_found"):new j("found",[new j(w.value.toString())]),X=new j(".",[new j("line",[new Re(w.line+1)]),new j(".",[new j("column",[new Re(F+1)]),new j(".",[z,new j("[]",[])])])]);return new j("error",[new j("syntax_error",[new j(b)]),X])},syntax_by_predicate:function(w,b){return new j("error",[new j("syntax_error",[new j(w)]),Z(b)])}},warning:{singleton:function(w,b,y){for(var F=new j("[]"),z=w.length-1;z>=0;z--)F=new j(".",[new De(w[z]),F]);return new j("warning",[new j("singleton_variables",[F,Z(b)]),new j(".",[new j("line",[new Re(y,!1)]),new j("[]")])])},failed_goal:function(w,b){return new j("warning",[new j("failed_goal",[w]),new j(".",[new j("line",[new Re(b,!1)]),new j("[]")])])}},format_variable:function(w){return"_"+w},format_answer:function(w,b,F){b instanceof ke&&(b=b.thread);var F=F||{};if(F.session=b?b.session:void 0,x.type.is_error(w))return"uncaught exception: "+w.args[0].toString();if(w===!1)return"false.";if(w===null)return"limit exceeded ;";var z=0,X="";if(x.type.is_substitution(w)){var $=w.domain(!0);w=w.filter(function(Te,lt){return!x.type.is_variable(lt)||$.indexOf(lt.id)!==-1&&Te!==lt.id})}for(var oe in w.links)w.links.hasOwnProperty(oe)&&(z++,X!==""&&(X+=", "),X+=oe.toString(F)+" = "+w.links[oe].toString(F));var xe=typeof b>"u"||b.points.length>0?" ;":".";return z===0?"true"+xe:X+xe},flatten_error:function(w){if(!x.type.is_error(w))return null;w=w.args[0];var b={};return b.type=w.args[0].id,b.thrown=b.type==="syntax_error"?null:w.args[1].id,b.expected=null,b.found=null,b.representation=null,b.existence=null,b.existence_type=null,b.line=null,b.column=null,b.permission_operation=null,b.permission_type=null,b.evaluation_type=null,b.type==="type_error"||b.type==="domain_error"?(b.expected=w.args[0].args[0].id,b.found=w.args[0].args[1].toString()):b.type==="syntax_error"?w.args[1].indicator==="./2"?(b.expected=w.args[0].args[0].id,b.found=w.args[1].args[1].args[1].args[0],b.found=b.found.id==="token_not_found"?b.found.id:b.found.args[0].id,b.line=w.args[1].args[0].args[0].value,b.column=w.args[1].args[1].args[0].args[0].value):b.thrown=w.args[1].id:b.type==="permission_error"?(b.found=w.args[0].args[2].toString(),b.permission_operation=w.args[0].args[0].id,b.permission_type=w.args[0].args[1].id):b.type==="evaluation_error"?b.evaluation_type=w.args[0].args[0].id:b.type==="representation_error"?b.representation=w.args[0].args[0].id:b.type==="existence_error"&&(b.existence=w.args[0].args[1].toString(),b.existence_type=w.args[0].args[0].id),b},create:function(w){return new x.type.Session(w)}};typeof ec<"u"?ec.exports=x:window.pl=x})()});function EEe(t,e,r){t.prepend(r.map(s=>new hl.default.type.State(e.goal.replace(s),e.substitution,e)))}function Lq(t){let e=CEe.get(t.session);if(e==null)throw new Error("Assertion failed: A project should have been registered for the active session");return e}function wEe(t,e){CEe.set(t,e),t.consult(`:- use_module(library(${Zct.id})).`)}var hl,IEe,J0,zct,Xct,CEe,Zct,BEe=Xe(()=>{Ge();ql();hl=ut(Oq()),IEe=ut(Ie("vm")),{is_atom:J0,is_variable:zct,is_instantiated_list:Xct}=hl.default.type;CEe=new WeakMap;Zct=new hl.default.type.Module("constraints",{"project_workspaces_by_descriptor/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let c=G.parseIdent(s.id),f=G.makeDescriptor(c,a.id),h=Lq(t).tryWorkspaceByDescriptor(f);zct(n)&&h!==null&&EEe(t,e,[new hl.default.type.Term("=",[n,new hl.default.type.Term(String(h.relativeCwd))])]),J0(n)&&h!==null&&h.relativeCwd===n.id&&t.success(e)},"workspace_field/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let f=Lq(t).tryWorkspaceByCwd(s.id);if(f==null)return;let p=va(f.manifest.raw,a.id);typeof p>"u"||EEe(t,e,[new hl.default.type.Term("=",[n,new hl.default.type.Term(typeof p=="object"?JSON.stringify(p):p)])])},"workspace_field_test/3":(t,e,r)=>{let[s,a,n]=r.args;t.prepend([new hl.default.type.State(e.goal.replace(new hl.default.type.Term("workspace_field_test",[s,a,n,new hl.default.type.Term("[]",[])])),e.substitution,e)])},"workspace_field_test/4":(t,e,r)=>{let[s,a,n,c]=r.args;if(!J0(s)||!J0(a)||!J0(n)||!Xct(c)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let p=Lq(t).tryWorkspaceByCwd(s.id);if(p==null)return;let h=va(p.manifest.raw,a.id);if(typeof h>"u")return;let E={$$:h};for(let[S,P]of c.toJavaScript().entries())E[`$${S}`]=P;IEe.default.runInNewContext(n.id,E)&&t.success(e)}},["project_workspaces_by_descriptor/3","workspace_field/3","workspace_field_test/3","workspace_field_test/4"])});var aS={};Vt(aS,{Constraints:()=>Uq,DependencyType:()=>bEe});function go(t){if(t instanceof KC.default.type.Num)return t.value;if(t instanceof KC.default.type.Term)switch(t.indicator){case"throw/1":return go(t.args[0]);case"error/1":return go(t.args[0]);case"error/2":if(t.args[0]instanceof KC.default.type.Term&&t.args[0].indicator==="syntax_error/1")return Object.assign(go(t.args[0]),...go(t.args[1]));{let e=go(t.args[0]);return e.message+=` (in ${go(t.args[1])})`,e}case"syntax_error/1":return new jt(43,`Syntax error: ${go(t.args[0])}`);case"existence_error/2":return new jt(44,`Existence error: ${go(t.args[0])} ${go(t.args[1])} not found`);case"instantiation_error/0":return new jt(75,"Instantiation error: an argument is variable when an instantiated argument was expected");case"line/1":return{line:go(t.args[0])};case"column/1":return{column:go(t.args[0])};case"found/1":return{found:go(t.args[0])};case"./2":return[go(t.args[0])].concat(go(t.args[1]));case"//2":return`${go(t.args[0])}/${go(t.args[1])}`;default:return t.id}throw`couldn't pretty print because of unsupported node ${t}`}function SEe(t){let e;try{e=go(t)}catch(r){throw typeof r=="string"?new jt(42,`Unknown error: ${t} (note: ${r})`):r}return typeof e.line<"u"&&typeof e.column<"u"&&(e.message+=` at line ${e.line}, column ${e.column}`),e}function Pm(t){return t.id==="null"?null:`${t.toJavaScript()}`}function $ct(t){if(t.id==="null")return null;{let e=t.toJavaScript();if(typeof e!="string")return JSON.stringify(e);try{return JSON.stringify(JSON.parse(e))}catch{return JSON.stringify(e)}}}function K0(t){return typeof t=="string"?`'${t}'`:"[]"}var DEe,KC,bEe,vEe,Mq,Uq,lS=Xe(()=>{Ge();Ge();Dt();DEe=ut(nEe()),KC=ut(Oq());iS();BEe();(0,DEe.default)(KC.default);bEe=(s=>(s.Dependencies="dependencies",s.DevDependencies="devDependencies",s.PeerDependencies="peerDependencies",s))(bEe||{}),vEe=["dependencies","devDependencies","peerDependencies"];Mq=class{constructor(e,r){let s=1e3*e.workspaces.length;this.session=KC.default.create(s),wEe(this.session,e),this.session.consult(":- use_module(library(lists))."),this.session.consult(r)}fetchNextAnswer(){return new Promise(e=>{this.session.answer(r=>{e(r)})})}async*makeQuery(e){let r=this.session.query(e);if(r!==!0)throw SEe(r);for(;;){let s=await this.fetchNextAnswer();if(s===null)throw new jt(79,"Resolution limit exceeded");if(!s)break;if(s.id==="throw")throw SEe(s);yield s}}};Uq=class t{constructor(e){this.source="";this.project=e;let r=e.configuration.get("constraintsPath");ce.existsSync(r)&&(this.source=ce.readFileSync(r,"utf8"))}static async find(e){return new t(e)}getProjectDatabase(){let e="";for(let r of vEe)e+=`dependency_type(${r}). +`;for(let r of this.project.workspacesByCwd.values()){let s=r.relativeCwd;e+=`workspace(${K0(s)}). +`,e+=`workspace_ident(${K0(s)}, ${K0(G.stringifyIdent(r.anchoredLocator))}). +`,e+=`workspace_version(${K0(s)}, ${K0(r.manifest.version)}). +`;for(let a of vEe)for(let n of r.manifest[a].values())e+=`workspace_has_dependency(${K0(s)}, ${K0(G.stringifyIdent(n))}, ${K0(n.range)}, ${a}). +`}return e+=`workspace(_) :- false. +`,e+=`workspace_ident(_, _) :- false. +`,e+=`workspace_version(_, _) :- false. +`,e+=`workspace_has_dependency(_, _, _, _) :- false. +`,e}getDeclarations(){let e="";return e+=`gen_enforced_dependency(_, _, _, _) :- false. +`,e+=`gen_enforced_field(_, _, _) :- false. +`,e}get fullSource(){return`${this.getProjectDatabase()} +${this.source} +${this.getDeclarations()}`}createSession(){return new Mq(this.project,this.fullSource)}async processClassic(){let e=this.createSession();return{enforcedDependencies:await this.genEnforcedDependencies(e),enforcedFields:await this.genEnforcedFields(e)}}async process(){let{enforcedDependencies:e,enforcedFields:r}=await this.processClassic(),s=new Map;for(let{workspace:a,dependencyIdent:n,dependencyRange:c,dependencyType:f}of e){let p=nS([f,G.stringifyIdent(n)]),h=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(h,p).set(c??void 0,new Set)}for(let{workspace:a,fieldPath:n,fieldValue:c}of r){let f=nS(n),p=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(p,f).set(JSON.parse(c)??void 0,new Set)}return{manifestUpdates:s,reportedErrors:new Map}}async genEnforcedDependencies(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), dependency_type(DependencyType), gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType).")){let a=J.resolve(this.project.cwd,Pm(s.links.WorkspaceCwd)),n=Pm(s.links.DependencyIdent),c=Pm(s.links.DependencyRange),f=Pm(s.links.DependencyType);if(a===null||n===null)throw new Error("Invalid rule");let p=this.project.getWorkspaceByCwd(a),h=G.parseIdent(n);r.push({workspace:p,dependencyIdent:h,dependencyRange:c,dependencyType:f})}return je.sortMap(r,[({dependencyRange:s})=>s!==null?"0":"1",({workspace:s})=>G.stringifyIdent(s.anchoredLocator),({dependencyIdent:s})=>G.stringifyIdent(s)])}async genEnforcedFields(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), gen_enforced_field(WorkspaceCwd, FieldPath, FieldValue).")){let a=J.resolve(this.project.cwd,Pm(s.links.WorkspaceCwd)),n=Pm(s.links.FieldPath),c=$ct(s.links.FieldValue);if(a===null||n===null)throw new Error("Invalid rule");let f=this.project.getWorkspaceByCwd(a);r.push({workspace:f,fieldPath:n,fieldValue:c})}return je.sortMap(r,[({workspace:s})=>G.stringifyIdent(s.anchoredLocator),({fieldPath:s})=>s])}async*query(e){let r=this.createSession();for await(let s of r.makeQuery(e)){let a={};for(let[n,c]of Object.entries(s.links))n!=="_"&&(a[n]=Pm(c));yield a}}}});var OEe=_(fF=>{"use strict";Object.defineProperty(fF,"__esModule",{value:!0});function BS(t){let e=[...t.caches],r=e.shift();return r===void 0?NEe():{get(s,a,n={miss:()=>Promise.resolve()}){return r.get(s,a,n).catch(()=>BS({caches:e}).get(s,a,n))},set(s,a){return r.set(s,a).catch(()=>BS({caches:e}).set(s,a))},delete(s){return r.delete(s).catch(()=>BS({caches:e}).delete(s))},clear(){return r.clear().catch(()=>BS({caches:e}).clear())}}}function NEe(){return{get(t,e,r={miss:()=>Promise.resolve()}){return e().then(a=>Promise.all([a,r.miss(a)])).then(([a])=>a)},set(t,e){return Promise.resolve(e)},delete(t){return Promise.resolve()},clear(){return Promise.resolve()}}}fF.createFallbackableCache=BS;fF.createNullCache=NEe});var MEe=_((BJt,LEe)=>{LEe.exports=OEe()});var UEe=_($q=>{"use strict";Object.defineProperty($q,"__esModule",{value:!0});function yut(t={serializable:!0}){let e={};return{get(r,s,a={miss:()=>Promise.resolve()}){let n=JSON.stringify(r);if(n in e)return Promise.resolve(t.serializable?JSON.parse(e[n]):e[n]);let c=s(),f=a&&a.miss||(()=>Promise.resolve());return c.then(p=>f(p)).then(()=>c)},set(r,s){return e[JSON.stringify(r)]=t.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete e[JSON.stringify(r)],Promise.resolve()},clear(){return e={},Promise.resolve()}}}$q.createInMemoryCache=yut});var HEe=_((SJt,_Ee)=>{_Ee.exports=UEe()});var GEe=_($u=>{"use strict";Object.defineProperty($u,"__esModule",{value:!0});function Eut(t,e,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":e};return{headers(){return t===e9.WithinHeaders?s:{}},queryParameters(){return t===e9.WithinQueryParameters?s:{}}}}function Iut(t){let e=0,r=()=>(e++,new Promise(s=>{setTimeout(()=>{s(t(r))},Math.min(100*e,1e3))}));return t(r)}function jEe(t,e=(r,s)=>Promise.resolve()){return Object.assign(t,{wait(r){return jEe(t.then(s=>Promise.all([e(s,r),s])).then(s=>s[1]))}})}function Cut(t){let e=t.length-1;for(e;e>0;e--){let r=Math.floor(Math.random()*(e+1)),s=t[e];t[e]=t[r],t[r]=s}return t}function wut(t,e){return e&&Object.keys(e).forEach(r=>{t[r]=e[r](t)}),t}function But(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}var vut="4.22.1",Sut=t=>()=>t.transporter.requester.destroy(),e9={WithinQueryParameters:0,WithinHeaders:1};$u.AuthMode=e9;$u.addMethods=wut;$u.createAuth=Eut;$u.createRetryablePromise=Iut;$u.createWaitablePromise=jEe;$u.destroy=Sut;$u.encode=But;$u.shuffle=Cut;$u.version=vut});var vS=_((bJt,qEe)=>{qEe.exports=GEe()});var WEe=_(t9=>{"use strict";Object.defineProperty(t9,"__esModule",{value:!0});var Dut={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};t9.MethodEnum=Dut});var SS=_((xJt,YEe)=>{YEe.exports=WEe()});var aIe=_(Yi=>{"use strict";Object.defineProperty(Yi,"__esModule",{value:!0});var JEe=SS();function r9(t,e){let r=t||{},s=r.data||{};return Object.keys(r).forEach(a=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(a)===-1&&(s[a]=r[a])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||e,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var DS={Read:1,Write:2,Any:3},sw={Up:1,Down:2,Timeouted:3},KEe=2*60*1e3;function i9(t,e=sw.Up){return{...t,status:e,lastUpdate:Date.now()}}function zEe(t){return t.status===sw.Up||Date.now()-t.lastUpdate>KEe}function XEe(t){return t.status===sw.Timeouted&&Date.now()-t.lastUpdate<=KEe}function s9(t){return typeof t=="string"?{protocol:"https",url:t,accept:DS.Any}:{protocol:t.protocol||"https",url:t.url,accept:t.accept||DS.Any}}function but(t,e){return Promise.all(e.map(r=>t.get(r,()=>Promise.resolve(i9(r))))).then(r=>{let s=r.filter(f=>zEe(f)),a=r.filter(f=>XEe(f)),n=[...s,...a],c=n.length>0?n.map(f=>s9(f)):e;return{getTimeout(f,p){return(a.length===0&&f===0?1:a.length+3+f)*p},statelessHosts:c}})}var Put=({isTimedOut:t,status:e})=>!t&&~~e===0,xut=t=>{let e=t.status;return t.isTimedOut||Put(t)||~~(e/100)!==2&&~~(e/100)!==4},kut=({status:t})=>~~(t/100)===2,Qut=(t,e)=>xut(t)?e.onRetry(t):kut(t)?e.onSuccess(t):e.onFail(t);function VEe(t,e,r,s){let a=[],n=rIe(r,s),c=nIe(t,s),f=r.method,p=r.method!==JEe.MethodEnum.Get?{}:{...r.data,...s.data},h={"x-algolia-agent":t.userAgent.value,...t.queryParameters,...p,...s.queryParameters},E=0,C=(S,P)=>{let I=S.pop();if(I===void 0)throw oIe(n9(a));let R={data:n,headers:c,method:f,url:eIe(I,r.path,h),connectTimeout:P(E,t.timeouts.connect),responseTimeout:P(E,s.timeout)},N=W=>{let ee={request:R,response:W,host:I,triesLeft:S.length};return a.push(ee),ee},U={onSuccess:W=>ZEe(W),onRetry(W){let ee=N(W);return W.isTimedOut&&E++,Promise.all([t.logger.info("Retryable failure",o9(ee)),t.hostsCache.set(I,i9(I,W.isTimedOut?sw.Timeouted:sw.Down))]).then(()=>C(S,P))},onFail(W){throw N(W),$Ee(W,n9(a))}};return t.requester.send(R).then(W=>Qut(W,U))};return but(t.hostsCache,e).then(S=>C([...S.statelessHosts].reverse(),S.getTimeout))}function Tut(t){let{hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,hosts:p,queryParameters:h,headers:E}=t,C={hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,headers:E,queryParameters:h,hosts:p.map(S=>s9(S)),read(S,P){let I=r9(P,C.timeouts.read),R=()=>VEe(C,C.hosts.filter(W=>(W.accept&DS.Read)!==0),S,I);if((I.cacheable!==void 0?I.cacheable:S.cacheable)!==!0)return R();let U={request:S,mappedRequestOptions:I,transporter:{queryParameters:C.queryParameters,headers:C.headers}};return C.responsesCache.get(U,()=>C.requestsCache.get(U,()=>C.requestsCache.set(U,R()).then(W=>Promise.all([C.requestsCache.delete(U),W]),W=>Promise.all([C.requestsCache.delete(U),Promise.reject(W)])).then(([W,ee])=>ee)),{miss:W=>C.responsesCache.set(U,W)})},write(S,P){return VEe(C,C.hosts.filter(I=>(I.accept&DS.Write)!==0),S,r9(P,C.timeouts.write))}};return C}function Rut(t){let e={value:`Algolia for JavaScript (${t})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return e.value.indexOf(s)===-1&&(e.value=`${e.value}${s}`),e}};return e}function ZEe(t){try{return JSON.parse(t.content)}catch(e){throw sIe(e.message,t)}}function $Ee({content:t,status:e},r){let s=t;try{s=JSON.parse(t).message}catch{}return iIe(s,e,r)}function Fut(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}function eIe(t,e,r){let s=tIe(r),a=`${t.protocol}://${t.url}/${e.charAt(0)==="/"?e.substr(1):e}`;return s.length&&(a+=`?${s}`),a}function tIe(t){let e=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(t).map(r=>Fut("%s=%s",r,e(t[r])?JSON.stringify(t[r]):t[r])).join("&")}function rIe(t,e){if(t.method===JEe.MethodEnum.Get||t.data===void 0&&e.data===void 0)return;let r=Array.isArray(t.data)?t.data:{...t.data,...e.data};return JSON.stringify(r)}function nIe(t,e){let r={...t.headers,...e.headers},s={};return Object.keys(r).forEach(a=>{let n=r[a];s[a.toLowerCase()]=n}),s}function n9(t){return t.map(e=>o9(e))}function o9(t){let e=t.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...t,request:{...t.request,headers:{...t.request.headers,...e}}}}function iIe(t,e,r){return{name:"ApiError",message:t,status:e,transporterStackTrace:r}}function sIe(t,e){return{name:"DeserializationError",message:t,response:e}}function oIe(t){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:t}}Yi.CallEnum=DS;Yi.HostStatusEnum=sw;Yi.createApiError=iIe;Yi.createDeserializationError=sIe;Yi.createMappedRequestOptions=r9;Yi.createRetryError=oIe;Yi.createStatefulHost=i9;Yi.createStatelessHost=s9;Yi.createTransporter=Tut;Yi.createUserAgent=Rut;Yi.deserializeFailure=$Ee;Yi.deserializeSuccess=ZEe;Yi.isStatefulHostTimeouted=XEe;Yi.isStatefulHostUp=zEe;Yi.serializeData=rIe;Yi.serializeHeaders=nIe;Yi.serializeQueryParameters=tIe;Yi.serializeUrl=eIe;Yi.stackFrameWithoutCredentials=o9;Yi.stackTraceWithoutCredentials=n9});var bS=_((QJt,lIe)=>{lIe.exports=aIe()});var cIe=_(X0=>{"use strict";Object.defineProperty(X0,"__esModule",{value:!0});var ow=vS(),Nut=bS(),PS=SS(),Out=t=>{let e=t.region||"us",r=ow.createAuth(ow.AuthMode.WithinHeaders,t.appId,t.apiKey),s=Nut.createTransporter({hosts:[{url:`analytics.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a=t.appId;return ow.addMethods({appId:a,transporter:s},t.methods)},Lut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Post,path:"2/abtests",data:e},r),Mut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Delete,path:ow.encode("2/abtests/%s",e)},r),Uut=t=>(e,r)=>t.transporter.read({method:PS.MethodEnum.Get,path:ow.encode("2/abtests/%s",e)},r),_ut=t=>e=>t.transporter.read({method:PS.MethodEnum.Get,path:"2/abtests"},e),Hut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Post,path:ow.encode("2/abtests/%s/stop",e)},r);X0.addABTest=Lut;X0.createAnalyticsClient=Out;X0.deleteABTest=Mut;X0.getABTest=Uut;X0.getABTests=_ut;X0.stopABTest=Hut});var fIe=_((RJt,uIe)=>{uIe.exports=cIe()});var pIe=_(xS=>{"use strict";Object.defineProperty(xS,"__esModule",{value:!0});var a9=vS(),jut=bS(),AIe=SS(),Gut=t=>{let e=t.region||"us",r=a9.createAuth(a9.AuthMode.WithinHeaders,t.appId,t.apiKey),s=jut.createTransporter({hosts:[{url:`personalization.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}});return a9.addMethods({appId:t.appId,transporter:s},t.methods)},qut=t=>e=>t.transporter.read({method:AIe.MethodEnum.Get,path:"1/strategies/personalization"},e),Wut=t=>(e,r)=>t.transporter.write({method:AIe.MethodEnum.Post,path:"1/strategies/personalization",data:e},r);xS.createPersonalizationClient=Gut;xS.getPersonalizationStrategy=qut;xS.setPersonalizationStrategy=Wut});var gIe=_((NJt,hIe)=>{hIe.exports=pIe()});var xIe=_(Ft=>{"use strict";Object.defineProperty(Ft,"__esModule",{value:!0});var Jt=vS(),gl=bS(),br=SS(),Yut=Ie("crypto");function AF(t){let e=r=>t.request(r).then(s=>{if(t.batch!==void 0&&t.batch(s.hits),!t.shouldStop(s))return s.cursor?e({cursor:s.cursor}):e({page:(r.page||0)+1})});return e({})}var Vut=t=>{let e=t.appId,r=Jt.createAuth(t.authMode!==void 0?t.authMode:Jt.AuthMode.WithinHeaders,e,t.apiKey),s=gl.createTransporter({hosts:[{url:`${e}-dsn.algolia.net`,accept:gl.CallEnum.Read},{url:`${e}.algolia.net`,accept:gl.CallEnum.Write}].concat(Jt.shuffle([{url:`${e}-1.algolianet.com`},{url:`${e}-2.algolianet.com`},{url:`${e}-3.algolianet.com`}])),...t,headers:{...r.headers(),"content-type":"application/x-www-form-urlencoded",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a={transporter:s,appId:e,addAlgoliaAgent(n,c){s.userAgent.add({segment:n,version:c})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return Jt.addMethods(a,t.methods)};function dIe(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function mIe(){return{name:"ObjectNotFoundError",message:"Object not found."}}function yIe(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Jut=t=>(e,r)=>{let{queryParameters:s,...a}=r||{},n={acl:e,...s!==void 0?{queryParameters:s}:{}},c=(f,p)=>Jt.createRetryablePromise(h=>kS(t)(f.key,p).catch(E=>{if(E.status!==404)throw E;return h()}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/keys",data:n},a),c)},Kut=t=>(e,r,s)=>{let a=gl.createMappedRequestOptions(s);return a.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},a)},zut=t=>(e,r,s)=>t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:e,cluster:r}},s),Xut=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:{action:"addEntry",body:[]}}},r),(s,a)=>aw(t)(s.taskID,a)),pF=t=>(e,r,s)=>{let a=(n,c)=>QS(t)(e,{methods:{waitTask:hs}}).waitTask(n.taskID,c);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",e),data:{operation:"copy",destination:r}},s),a)},Zut=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Rules]}),$ut=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Settings]}),eft=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Synonyms]}),tft=t=>(e,r)=>e.method===br.MethodEnum.Get?t.transporter.read(e,r):t.transporter.write(e,r),rft=t=>(e,r)=>{let s=(a,n)=>Jt.createRetryablePromise(c=>kS(t)(e,n).then(c).catch(f=>{if(f.status!==404)throw f}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/keys/%s",e)},r),s)},nft=t=>(e,r,s)=>{let a=r.map(n=>({action:"deleteEntry",body:{objectID:n}}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},ift=()=>(t,e)=>{let r=gl.serializeQueryParameters(e),s=Yut.createHmac("sha256",t).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},kS=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/keys/%s",e)},r),EIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/task/%s",e.toString())},r),sft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"/1/dictionaries/*/settings"},e),oft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/logs"},e),aft=()=>t=>{let e=Buffer.from(t,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=e.match(r);if(s===null)throw yIe();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},lft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/top"},e),cft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/clusters/mapping/%s",e)},r),uft=t=>e=>{let{retrieveMappings:r,...s}=e||{};return r===!0&&(s.getClusters=!0),t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},QS=t=>(e,r={})=>{let s={transporter:t.transporter,appId:t.appId,indexName:e};return Jt.addMethods(s,r.methods)},fft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/keys"},e),Aft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters"},e),pft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/indexes"},e),hft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping"},e),gft=t=>(e,r,s)=>{let a=(n,c)=>QS(t)(e,{methods:{waitTask:hs}}).waitTask(n.taskID,c);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",e),data:{operation:"move",destination:r}},s),a)},dft=t=>(e,r)=>{let s=(a,n)=>Promise.all(Object.keys(a.taskID).map(c=>QS(t)(c,{methods:{waitTask:hs}}).waitTask(a.taskID[c],n)));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:e}},r),s)},mft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:e}},r),yft=t=>(e,r)=>{let s=e.map(a=>({...a,params:gl.serializeQueryParameters(a.params||{})}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Eft=t=>(e,r)=>Promise.all(e.map(s=>{let{facetName:a,facetQuery:n,...c}=s.params;return QS(t)(s.indexName,{methods:{searchForFacetValues:DIe}}).searchForFacetValues(a,n,{...r,...c})})),Ift=t=>(e,r)=>{let s=gl.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Cft=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},wft=t=>(e,r)=>{let s=(a,n)=>Jt.createRetryablePromise(c=>kS(t)(e,n).catch(f=>{if(f.status!==404)throw f;return c()}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/keys/%s/restore",e)},r),s)},Bft=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},vft=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/search",e),data:{query:r},cacheable:!0},s),Sft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:e}},r),Dft=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:"/1/dictionaries/*/settings",data:e},r),(s,a)=>aw(t)(s.taskID,a)),bft=t=>(e,r)=>{let s=Object.assign({},r),{queryParameters:a,...n}=r||{},c=a?{queryParameters:a}:{},f=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],p=E=>Object.keys(s).filter(C=>f.indexOf(C)!==-1).every(C=>{if(Array.isArray(E[C])&&Array.isArray(s[C])){let S=E[C];return S.length===s[C].length&&S.every((P,I)=>P===s[C][I])}else return E[C]===s[C]}),h=(E,C)=>Jt.createRetryablePromise(S=>kS(t)(e,C).then(P=>p(P)?Promise.resolve():S()));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Jt.encode("1/keys/%s",e),data:c},n),h)},aw=t=>(e,r)=>Jt.createRetryablePromise(s=>EIe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),IIe=t=>(e,r)=>{let s=(a,n)=>hs(t)(a.taskID,n);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/batch",t.indexName),data:{requests:e}},r),s)},Pft=t=>e=>AF({shouldStop:r=>r.cursor===void 0,...e,request:r=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/browse",t.indexName),data:r},e)}),xft=t=>e=>{let r={hitsPerPage:1e3,...e};return AF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},kft=t=>e=>{let r={hitsPerPage:1e3,...e};return AF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},hF=t=>(e,r,s)=>{let{batchSize:a,...n}=s||{},c={taskIDs:[],objectIDs:[]},f=(p=0)=>{let h=[],E;for(E=p;E({action:r,body:C})),n).then(C=>(c.objectIDs=c.objectIDs.concat(C.objectIDs),c.taskIDs.push(C.taskID),E++,f(E)))};return Jt.createWaitablePromise(f(),(p,h)=>Promise.all(p.taskIDs.map(E=>hs(t)(E,h))))},Qft=t=>e=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/clear",t.indexName)},e),(r,s)=>hs(t)(r.taskID,s)),Tft=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=gl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/clear",t.indexName)},a),(n,c)=>hs(t)(n.taskID,c))},Rft=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=gl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/clear",t.indexName)},a),(n,c)=>hs(t)(n.taskID,c))},Fft=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/deleteByQuery",t.indexName),data:e},r),(s,a)=>hs(t)(s.taskID,a)),Nft=t=>e=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s",t.indexName)},e),(r,s)=>hs(t)(r.taskID,s)),Oft=t=>(e,r)=>Jt.createWaitablePromise(CIe(t)([e],r).then(s=>({taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),CIe=t=>(e,r)=>{let s=e.map(a=>({objectID:a}));return hF(t)(s,km.DeleteObject,r)},Lft=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s/rules/%s",t.indexName,e)},n),(c,f)=>hs(t)(c.taskID,f))},Mft=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},n),(c,f)=>hs(t)(c.taskID,f))},Uft=t=>e=>wIe(t)(e).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),_ft=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/answers/%s/prediction",t.indexName),data:{query:e,queryLanguages:r},cacheable:!0},s),Hft=t=>(e,r)=>{let{query:s,paginate:a,...n}=r||{},c=0,f=()=>SIe(t)(s||"",{...n,page:c}).then(p=>{for(let[h,E]of Object.entries(p.hits))if(e(E))return{object:E,position:parseInt(h,10),page:c};if(c++,a===!1||c>=p.nbPages)throw mIe();return f()});return f()},jft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/%s",t.indexName,e)},r),Gft=()=>(t,e)=>{for(let[r,s]of Object.entries(t.hits))if(s.objectID===e)return parseInt(r,10);return-1},qft=t=>(e,r)=>{let{attributesToRetrieve:s,...a}=r||{},n=e.map(c=>({indexName:t.indexName,objectID:c,...s?{attributesToRetrieve:s}:{}}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:n}},a)},Wft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/rules/%s",t.indexName,e)},r),wIe=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/settings",t.indexName),data:{getVersion:2}},e),Yft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},r),BIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/task/%s",t.indexName,e.toString())},r),Vft=t=>(e,r)=>Jt.createWaitablePromise(vIe(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),vIe=t=>(e,r)=>{let{createIfNotExists:s,...a}=r||{},n=s?km.PartialUpdateObject:km.PartialUpdateObjectNoCreate;return hF(t)(e,n,a)},Jft=t=>(e,r)=>{let{safe:s,autoGenerateObjectIDIfNotExist:a,batchSize:n,...c}=r||{},f=(I,R,N,U)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",I),data:{operation:N,destination:R}},U),(W,ee)=>hs(t)(W.taskID,ee)),p=Math.random().toString(36).substring(7),h=`${t.indexName}_tmp_${p}`,E=l9({appId:t.appId,transporter:t.transporter,indexName:h}),C=[],S=f(t.indexName,h,"copy",{...c,scope:["settings","synonyms","rules"]});C.push(S);let P=(s?S.wait(c):S).then(()=>{let I=E(e,{...c,autoGenerateObjectIDIfNotExist:a,batchSize:n});return C.push(I),s?I.wait(c):I}).then(()=>{let I=f(h,t.indexName,"move",c);return C.push(I),s?I.wait(c):I}).then(()=>Promise.all(C)).then(([I,R,N])=>({objectIDs:R.objectIDs,taskIDs:[I.taskID,...R.taskIDs,N.taskID]}));return Jt.createWaitablePromise(P,(I,R)=>Promise.all(C.map(N=>N.wait(R))))},Kft=t=>(e,r)=>c9(t)(e,{...r,clearExistingRules:!0}),zft=t=>(e,r)=>u9(t)(e,{...r,clearExistingSynonyms:!0}),Xft=t=>(e,r)=>Jt.createWaitablePromise(l9(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),l9=t=>(e,r)=>{let{autoGenerateObjectIDIfNotExist:s,...a}=r||{},n=s?km.AddObject:km.UpdateObject;if(n===km.UpdateObject){for(let c of e)if(c.objectID===void 0)return Jt.createWaitablePromise(Promise.reject(dIe()))}return hF(t)(e,n,a)},Zft=t=>(e,r)=>c9(t)([e],r),c9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingRules:a,...n}=r||{},c=gl.createMappedRequestOptions(n);return s&&(c.queryParameters.forwardToReplicas=1),a&&(c.queryParameters.clearExistingRules=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/batch",t.indexName),data:e},c),(f,p)=>hs(t)(f.taskID,p))},$ft=t=>(e,r)=>u9(t)([e],r),u9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingSynonyms:a,replaceExistingSynonyms:n,...c}=r||{},f=gl.createMappedRequestOptions(c);return s&&(f.queryParameters.forwardToReplicas=1),(n||a)&&(f.queryParameters.replaceExistingSynonyms=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/batch",t.indexName),data:e},f),(p,h)=>hs(t)(p.taskID,h))},SIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/query",t.indexName),data:{query:e},cacheable:!0},r),DIe=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/facets/%s/query",t.indexName,e),data:{facetQuery:r},cacheable:!0},s),bIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/search",t.indexName),data:{query:e}},r),PIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/search",t.indexName),data:{query:e}},r),eAt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Jt.encode("1/indexes/%s/settings",t.indexName),data:e},n),(c,f)=>hs(t)(c.taskID,f))},hs=t=>(e,r)=>Jt.createRetryablePromise(s=>BIe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),tAt={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",Inference:"inference",ListIndexes:"listIndexes",Logs:"logs",Personalization:"personalization",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},km={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject",DeleteIndex:"delete",ClearIndex:"clear"},gF={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},rAt={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},nAt={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};Ft.ApiKeyACLEnum=tAt;Ft.BatchActionEnum=km;Ft.ScopeEnum=gF;Ft.StrategyEnum=rAt;Ft.SynonymEnum=nAt;Ft.addApiKey=Jut;Ft.assignUserID=Kut;Ft.assignUserIDs=zut;Ft.batch=IIe;Ft.browseObjects=Pft;Ft.browseRules=xft;Ft.browseSynonyms=kft;Ft.chunkedBatch=hF;Ft.clearDictionaryEntries=Xut;Ft.clearObjects=Qft;Ft.clearRules=Tft;Ft.clearSynonyms=Rft;Ft.copyIndex=pF;Ft.copyRules=Zut;Ft.copySettings=$ut;Ft.copySynonyms=eft;Ft.createBrowsablePromise=AF;Ft.createMissingObjectIDError=dIe;Ft.createObjectNotFoundError=mIe;Ft.createSearchClient=Vut;Ft.createValidUntilNotFoundError=yIe;Ft.customRequest=tft;Ft.deleteApiKey=rft;Ft.deleteBy=Fft;Ft.deleteDictionaryEntries=nft;Ft.deleteIndex=Nft;Ft.deleteObject=Oft;Ft.deleteObjects=CIe;Ft.deleteRule=Lft;Ft.deleteSynonym=Mft;Ft.exists=Uft;Ft.findAnswers=_ft;Ft.findObject=Hft;Ft.generateSecuredApiKey=ift;Ft.getApiKey=kS;Ft.getAppTask=EIe;Ft.getDictionarySettings=sft;Ft.getLogs=oft;Ft.getObject=jft;Ft.getObjectPosition=Gft;Ft.getObjects=qft;Ft.getRule=Wft;Ft.getSecuredApiKeyRemainingValidity=aft;Ft.getSettings=wIe;Ft.getSynonym=Yft;Ft.getTask=BIe;Ft.getTopUserIDs=lft;Ft.getUserID=cft;Ft.hasPendingMappings=uft;Ft.initIndex=QS;Ft.listApiKeys=fft;Ft.listClusters=Aft;Ft.listIndices=pft;Ft.listUserIDs=hft;Ft.moveIndex=gft;Ft.multipleBatch=dft;Ft.multipleGetObjects=mft;Ft.multipleQueries=yft;Ft.multipleSearchForFacetValues=Eft;Ft.partialUpdateObject=Vft;Ft.partialUpdateObjects=vIe;Ft.removeUserID=Ift;Ft.replaceAllObjects=Jft;Ft.replaceAllRules=Kft;Ft.replaceAllSynonyms=zft;Ft.replaceDictionaryEntries=Cft;Ft.restoreApiKey=wft;Ft.saveDictionaryEntries=Bft;Ft.saveObject=Xft;Ft.saveObjects=l9;Ft.saveRule=Zft;Ft.saveRules=c9;Ft.saveSynonym=$ft;Ft.saveSynonyms=u9;Ft.search=SIe;Ft.searchDictionaryEntries=vft;Ft.searchForFacetValues=DIe;Ft.searchRules=bIe;Ft.searchSynonyms=PIe;Ft.searchUserIDs=Sft;Ft.setDictionarySettings=Dft;Ft.setSettings=eAt;Ft.updateApiKey=bft;Ft.waitAppTask=aw;Ft.waitTask=hs});var QIe=_((LJt,kIe)=>{kIe.exports=xIe()});var TIe=_(dF=>{"use strict";Object.defineProperty(dF,"__esModule",{value:!0});function iAt(){return{debug(t,e){return Promise.resolve()},info(t,e){return Promise.resolve()},error(t,e){return Promise.resolve()}}}var sAt={Debug:1,Info:2,Error:3};dF.LogLevelEnum=sAt;dF.createNullLogger=iAt});var FIe=_((UJt,RIe)=>{RIe.exports=TIe()});var MIe=_(f9=>{"use strict";Object.defineProperty(f9,"__esModule",{value:!0});var NIe=Ie("http"),OIe=Ie("https"),oAt=Ie("url"),LIe={keepAlive:!0},aAt=new NIe.Agent(LIe),lAt=new OIe.Agent(LIe);function cAt({agent:t,httpAgent:e,httpsAgent:r,requesterOptions:s={}}={}){let a=e||t||aAt,n=r||t||lAt;return{send(c){return new Promise(f=>{let p=oAt.parse(c.url),h=p.query===null?p.pathname:`${p.pathname}?${p.query}`,E={...s,agent:p.protocol==="https:"?n:a,hostname:p.hostname,path:h,method:c.method,headers:{...s&&s.headers?s.headers:{},...c.headers},...p.port!==void 0?{port:p.port||""}:{}},C=(p.protocol==="https:"?OIe:NIe).request(E,R=>{let N=[];R.on("data",U=>{N=N.concat(U)}),R.on("end",()=>{clearTimeout(P),clearTimeout(I),f({status:R.statusCode||0,content:Buffer.concat(N).toString(),isTimedOut:!1})})}),S=(R,N)=>setTimeout(()=>{C.abort(),f({status:0,content:N,isTimedOut:!0})},R*1e3),P=S(c.connectTimeout,"Connection timeout"),I;C.on("error",R=>{clearTimeout(P),clearTimeout(I),f({status:0,content:R.message,isTimedOut:!1})}),C.once("response",()=>{clearTimeout(P),I=S(c.responseTimeout,"Socket timeout")}),c.data!==void 0&&C.write(c.data),C.end()})},destroy(){return a.destroy(),n.destroy(),Promise.resolve()}}}f9.createNodeHttpRequester=cAt});var _Ie=_((HJt,UIe)=>{UIe.exports=MIe()});var qIe=_((jJt,GIe)=>{"use strict";var HIe=MEe(),uAt=HEe(),lw=fIe(),p9=vS(),A9=gIe(),Gt=QIe(),fAt=FIe(),AAt=_Ie(),pAt=bS();function jIe(t,e,r){let s={appId:t,apiKey:e,timeouts:{connect:2,read:5,write:30},requester:AAt.createNodeHttpRequester(),logger:fAt.createNullLogger(),responsesCache:HIe.createNullCache(),requestsCache:HIe.createNullCache(),hostsCache:uAt.createInMemoryCache(),userAgent:pAt.createUserAgent(p9.version).add({segment:"Node.js",version:process.versions.node})},a={...s,...r},n=()=>c=>A9.createPersonalizationClient({...s,...c,methods:{getPersonalizationStrategy:A9.getPersonalizationStrategy,setPersonalizationStrategy:A9.setPersonalizationStrategy}});return Gt.createSearchClient({...a,methods:{search:Gt.multipleQueries,searchForFacetValues:Gt.multipleSearchForFacetValues,multipleBatch:Gt.multipleBatch,multipleGetObjects:Gt.multipleGetObjects,multipleQueries:Gt.multipleQueries,copyIndex:Gt.copyIndex,copySettings:Gt.copySettings,copyRules:Gt.copyRules,copySynonyms:Gt.copySynonyms,moveIndex:Gt.moveIndex,listIndices:Gt.listIndices,getLogs:Gt.getLogs,listClusters:Gt.listClusters,multipleSearchForFacetValues:Gt.multipleSearchForFacetValues,getApiKey:Gt.getApiKey,addApiKey:Gt.addApiKey,listApiKeys:Gt.listApiKeys,updateApiKey:Gt.updateApiKey,deleteApiKey:Gt.deleteApiKey,restoreApiKey:Gt.restoreApiKey,assignUserID:Gt.assignUserID,assignUserIDs:Gt.assignUserIDs,getUserID:Gt.getUserID,searchUserIDs:Gt.searchUserIDs,listUserIDs:Gt.listUserIDs,getTopUserIDs:Gt.getTopUserIDs,removeUserID:Gt.removeUserID,hasPendingMappings:Gt.hasPendingMappings,generateSecuredApiKey:Gt.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:Gt.getSecuredApiKeyRemainingValidity,destroy:p9.destroy,clearDictionaryEntries:Gt.clearDictionaryEntries,deleteDictionaryEntries:Gt.deleteDictionaryEntries,getDictionarySettings:Gt.getDictionarySettings,getAppTask:Gt.getAppTask,replaceDictionaryEntries:Gt.replaceDictionaryEntries,saveDictionaryEntries:Gt.saveDictionaryEntries,searchDictionaryEntries:Gt.searchDictionaryEntries,setDictionarySettings:Gt.setDictionarySettings,waitAppTask:Gt.waitAppTask,customRequest:Gt.customRequest,initIndex:c=>f=>Gt.initIndex(c)(f,{methods:{batch:Gt.batch,delete:Gt.deleteIndex,findAnswers:Gt.findAnswers,getObject:Gt.getObject,getObjects:Gt.getObjects,saveObject:Gt.saveObject,saveObjects:Gt.saveObjects,search:Gt.search,searchForFacetValues:Gt.searchForFacetValues,waitTask:Gt.waitTask,setSettings:Gt.setSettings,getSettings:Gt.getSettings,partialUpdateObject:Gt.partialUpdateObject,partialUpdateObjects:Gt.partialUpdateObjects,deleteObject:Gt.deleteObject,deleteObjects:Gt.deleteObjects,deleteBy:Gt.deleteBy,clearObjects:Gt.clearObjects,browseObjects:Gt.browseObjects,getObjectPosition:Gt.getObjectPosition,findObject:Gt.findObject,exists:Gt.exists,saveSynonym:Gt.saveSynonym,saveSynonyms:Gt.saveSynonyms,getSynonym:Gt.getSynonym,searchSynonyms:Gt.searchSynonyms,browseSynonyms:Gt.browseSynonyms,deleteSynonym:Gt.deleteSynonym,clearSynonyms:Gt.clearSynonyms,replaceAllObjects:Gt.replaceAllObjects,replaceAllSynonyms:Gt.replaceAllSynonyms,searchRules:Gt.searchRules,getRule:Gt.getRule,deleteRule:Gt.deleteRule,saveRule:Gt.saveRule,saveRules:Gt.saveRules,replaceAllRules:Gt.replaceAllRules,browseRules:Gt.browseRules,clearRules:Gt.clearRules}}),initAnalytics:()=>c=>lw.createAnalyticsClient({...s,...c,methods:{addABTest:lw.addABTest,getABTest:lw.getABTest,getABTests:lw.getABTests,stopABTest:lw.stopABTest,deleteABTest:lw.deleteABTest}}),initPersonalization:n,initRecommendation:()=>c=>(a.logger.info("The `initRecommendation` method is deprecated. Use `initPersonalization` instead."),n()(c))}})}jIe.version=p9.version;GIe.exports=jIe});var g9=_((GJt,h9)=>{var WIe=qIe();h9.exports=WIe;h9.exports.default=WIe});var y9=_((WJt,JIe)=>{"use strict";var VIe=Object.getOwnPropertySymbols,gAt=Object.prototype.hasOwnProperty,dAt=Object.prototype.propertyIsEnumerable;function mAt(t){if(t==null)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}function yAt(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de",Object.getOwnPropertyNames(t)[0]==="5")return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;var s=Object.getOwnPropertyNames(e).map(function(n){return e[n]});if(s.join("")!=="0123456789")return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach(function(n){a[n]=n}),Object.keys(Object.assign({},a)).join("")==="abcdefghijklmnopqrst"}catch{return!1}}JIe.exports=yAt()?Object.assign:function(t,e){for(var r,s=mAt(t),a,n=1;n{"use strict";var I9=y9(),cw=60103,XIe=60106;Dn.Fragment=60107;Dn.StrictMode=60108;Dn.Profiler=60114;var ZIe=60109,$Ie=60110,eCe=60112;Dn.Suspense=60113;var tCe=60115,rCe=60116;typeof Symbol=="function"&&Symbol.for&&(Gc=Symbol.for,cw=Gc("react.element"),XIe=Gc("react.portal"),Dn.Fragment=Gc("react.fragment"),Dn.StrictMode=Gc("react.strict_mode"),Dn.Profiler=Gc("react.profiler"),ZIe=Gc("react.provider"),$Ie=Gc("react.context"),eCe=Gc("react.forward_ref"),Dn.Suspense=Gc("react.suspense"),tCe=Gc("react.memo"),rCe=Gc("react.lazy"));var Gc,KIe=typeof Symbol=="function"&&Symbol.iterator;function EAt(t){return t===null||typeof t!="object"?null:(t=KIe&&t[KIe]||t["@@iterator"],typeof t=="function"?t:null)}function TS(t){for(var e="https://reactjs.org/docs/error-decoder.html?invariant="+t,r=1;r{"use strict";fCe.exports=uCe()});var EF=_((JJt,ACe)=>{function vAt(t){var e=typeof t;return t!=null&&(e=="object"||e=="function")}ACe.exports=vAt});var hCe=_((KJt,pCe)=>{var SAt=typeof global=="object"&&global&&global.Object===Object&&global;pCe.exports=SAt});var S9=_((zJt,gCe)=>{var DAt=hCe(),bAt=typeof self=="object"&&self&&self.Object===Object&&self,PAt=DAt||bAt||Function("return this")();gCe.exports=PAt});var mCe=_((XJt,dCe)=>{var xAt=S9(),kAt=function(){return xAt.Date.now()};dCe.exports=kAt});var ECe=_((ZJt,yCe)=>{var QAt=/\s/;function TAt(t){for(var e=t.length;e--&&QAt.test(t.charAt(e)););return e}yCe.exports=TAt});var CCe=_(($Jt,ICe)=>{var RAt=ECe(),FAt=/^\s+/;function NAt(t){return t&&t.slice(0,RAt(t)+1).replace(FAt,"")}ICe.exports=NAt});var D9=_((eKt,wCe)=>{var OAt=S9(),LAt=OAt.Symbol;wCe.exports=LAt});var DCe=_((tKt,SCe)=>{var BCe=D9(),vCe=Object.prototype,MAt=vCe.hasOwnProperty,UAt=vCe.toString,RS=BCe?BCe.toStringTag:void 0;function _At(t){var e=MAt.call(t,RS),r=t[RS];try{t[RS]=void 0;var s=!0}catch{}var a=UAt.call(t);return s&&(e?t[RS]=r:delete t[RS]),a}SCe.exports=_At});var PCe=_((rKt,bCe)=>{var HAt=Object.prototype,jAt=HAt.toString;function GAt(t){return jAt.call(t)}bCe.exports=GAt});var TCe=_((nKt,QCe)=>{var xCe=D9(),qAt=DCe(),WAt=PCe(),YAt="[object Null]",VAt="[object Undefined]",kCe=xCe?xCe.toStringTag:void 0;function JAt(t){return t==null?t===void 0?VAt:YAt:kCe&&kCe in Object(t)?qAt(t):WAt(t)}QCe.exports=JAt});var FCe=_((iKt,RCe)=>{function KAt(t){return t!=null&&typeof t=="object"}RCe.exports=KAt});var OCe=_((sKt,NCe)=>{var zAt=TCe(),XAt=FCe(),ZAt="[object Symbol]";function $At(t){return typeof t=="symbol"||XAt(t)&&zAt(t)==ZAt}NCe.exports=$At});var _Ce=_((oKt,UCe)=>{var ept=CCe(),LCe=EF(),tpt=OCe(),MCe=NaN,rpt=/^[-+]0x[0-9a-f]+$/i,npt=/^0b[01]+$/i,ipt=/^0o[0-7]+$/i,spt=parseInt;function opt(t){if(typeof t=="number")return t;if(tpt(t))return MCe;if(LCe(t)){var e=typeof t.valueOf=="function"?t.valueOf():t;t=LCe(e)?e+"":e}if(typeof t!="string")return t===0?t:+t;t=ept(t);var r=npt.test(t);return r||ipt.test(t)?spt(t.slice(2),r?2:8):rpt.test(t)?MCe:+t}UCe.exports=opt});var GCe=_((aKt,jCe)=>{var apt=EF(),b9=mCe(),HCe=_Ce(),lpt="Expected a function",cpt=Math.max,upt=Math.min;function fpt(t,e,r){var s,a,n,c,f,p,h=0,E=!1,C=!1,S=!0;if(typeof t!="function")throw new TypeError(lpt);e=HCe(e)||0,apt(r)&&(E=!!r.leading,C="maxWait"in r,n=C?cpt(HCe(r.maxWait)||0,e):n,S="trailing"in r?!!r.trailing:S);function P(le){var me=s,pe=a;return s=a=void 0,h=le,c=t.apply(pe,me),c}function I(le){return h=le,f=setTimeout(U,e),E?P(le):c}function R(le){var me=le-p,pe=le-h,Be=e-me;return C?upt(Be,n-pe):Be}function N(le){var me=le-p,pe=le-h;return p===void 0||me>=e||me<0||C&&pe>=n}function U(){var le=b9();if(N(le))return W(le);f=setTimeout(U,R(le))}function W(le){return f=void 0,S&&s?P(le):(s=a=void 0,c)}function ee(){f!==void 0&&clearTimeout(f),h=0,s=p=a=f=void 0}function ie(){return f===void 0?c:W(b9())}function ue(){var le=b9(),me=N(le);if(s=arguments,a=this,p=le,me){if(f===void 0)return I(p);if(C)return clearTimeout(f),f=setTimeout(U,e),P(p)}return f===void 0&&(f=setTimeout(U,e)),c}return ue.cancel=ee,ue.flush=ie,ue}jCe.exports=fpt});var WCe=_((lKt,qCe)=>{var Apt=GCe(),ppt=EF(),hpt="Expected a function";function gpt(t,e,r){var s=!0,a=!0;if(typeof t!="function")throw new TypeError(hpt);return ppt(r)&&(s="leading"in r?!!r.leading:s,a="trailing"in r?!!r.trailing:a),Apt(t,e,{leading:s,maxWait:e,trailing:a})}qCe.exports=gpt});var x9=_((cKt,P9)=>{"use strict";var Cn=P9.exports;P9.exports.default=Cn;var Xn="\x1B[",NS="\x1B]",fw="\x07",IF=";",YCe=process.env.TERM_PROGRAM==="Apple_Terminal";Cn.cursorTo=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");return typeof e!="number"?Xn+(t+1)+"G":Xn+(e+1)+";"+(t+1)+"H"};Cn.cursorMove=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");let r="";return t<0?r+=Xn+-t+"D":t>0&&(r+=Xn+t+"C"),e<0?r+=Xn+-e+"A":e>0&&(r+=Xn+e+"B"),r};Cn.cursorUp=(t=1)=>Xn+t+"A";Cn.cursorDown=(t=1)=>Xn+t+"B";Cn.cursorForward=(t=1)=>Xn+t+"C";Cn.cursorBackward=(t=1)=>Xn+t+"D";Cn.cursorLeft=Xn+"G";Cn.cursorSavePosition=YCe?"\x1B7":Xn+"s";Cn.cursorRestorePosition=YCe?"\x1B8":Xn+"u";Cn.cursorGetPosition=Xn+"6n";Cn.cursorNextLine=Xn+"E";Cn.cursorPrevLine=Xn+"F";Cn.cursorHide=Xn+"?25l";Cn.cursorShow=Xn+"?25h";Cn.eraseLines=t=>{let e="";for(let r=0;r[NS,"8",IF,IF,e,fw,t,NS,"8",IF,IF,fw].join("");Cn.image=(t,e={})=>{let r=`${NS}1337;File=inline=1`;return e.width&&(r+=`;width=${e.width}`),e.height&&(r+=`;height=${e.height}`),e.preserveAspectRatio===!1&&(r+=";preserveAspectRatio=0"),r+":"+t.toString("base64")+fw};Cn.iTerm={setCwd:(t=process.cwd())=>`${NS}50;CurrentDir=${t}${fw}`,annotation:(t,e={})=>{let r=`${NS}1337;`,s=typeof e.x<"u",a=typeof e.y<"u";if((s||a)&&!(s&&a&&typeof e.length<"u"))throw new Error("`x`, `y` and `length` must be defined when `x` or `y` is defined");return t=t.replace(/\|/g,""),r+=e.isHidden?"AddHiddenAnnotation=":"AddAnnotation=",e.length>0?r+=(s?[t,e.length,e.x,e.y]:[e.length,t]).join("|"):r+=t,r+fw}}});var JCe=_((uKt,k9)=>{"use strict";var VCe=(t,e)=>{for(let r of Reflect.ownKeys(e))Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r));return t};k9.exports=VCe;k9.exports.default=VCe});var zCe=_((fKt,wF)=>{"use strict";var dpt=JCe(),CF=new WeakMap,KCe=(t,e={})=>{if(typeof t!="function")throw new TypeError("Expected a function");let r,s=0,a=t.displayName||t.name||"",n=function(...c){if(CF.set(n,++s),s===1)r=t.apply(this,c),t=null;else if(e.throw===!0)throw new Error(`Function \`${a}\` can only be called once`);return r};return dpt(n,t),CF.set(n,s),n};wF.exports=KCe;wF.exports.default=KCe;wF.exports.callCount=t=>{if(!CF.has(t))throw new Error(`The given function \`${t.name}\` is not wrapped by the \`onetime\` package`);return CF.get(t)}});var XCe=_((AKt,BF)=>{BF.exports=["SIGABRT","SIGALRM","SIGHUP","SIGINT","SIGTERM"];process.platform!=="win32"&&BF.exports.push("SIGVTALRM","SIGXCPU","SIGXFSZ","SIGUSR2","SIGTRAP","SIGSYS","SIGQUIT","SIGIOT");process.platform==="linux"&&BF.exports.push("SIGIO","SIGPOLL","SIGPWR","SIGSTKFLT","SIGUNUSED")});var R9=_((pKt,hw)=>{var Qi=global.process,Qm=function(t){return t&&typeof t=="object"&&typeof t.removeListener=="function"&&typeof t.emit=="function"&&typeof t.reallyExit=="function"&&typeof t.listeners=="function"&&typeof t.kill=="function"&&typeof t.pid=="number"&&typeof t.on=="function"};Qm(Qi)?(ZCe=Ie("assert"),Aw=XCe(),$Ce=/^win/i.test(Qi.platform),OS=Ie("events"),typeof OS!="function"&&(OS=OS.EventEmitter),Qi.__signal_exit_emitter__?Js=Qi.__signal_exit_emitter__:(Js=Qi.__signal_exit_emitter__=new OS,Js.count=0,Js.emitted={}),Js.infinite||(Js.setMaxListeners(1/0),Js.infinite=!0),hw.exports=function(t,e){if(!Qm(global.process))return function(){};ZCe.equal(typeof t,"function","a callback must be provided for exit handler"),pw===!1&&Q9();var r="exit";e&&e.alwaysLast&&(r="afterexit");var s=function(){Js.removeListener(r,t),Js.listeners("exit").length===0&&Js.listeners("afterexit").length===0&&vF()};return Js.on(r,t),s},vF=function(){!pw||!Qm(global.process)||(pw=!1,Aw.forEach(function(e){try{Qi.removeListener(e,SF[e])}catch{}}),Qi.emit=DF,Qi.reallyExit=T9,Js.count-=1)},hw.exports.unload=vF,Tm=function(e,r,s){Js.emitted[e]||(Js.emitted[e]=!0,Js.emit(e,r,s))},SF={},Aw.forEach(function(t){SF[t]=function(){if(Qm(global.process)){var r=Qi.listeners(t);r.length===Js.count&&(vF(),Tm("exit",null,t),Tm("afterexit",null,t),$Ce&&t==="SIGHUP"&&(t="SIGINT"),Qi.kill(Qi.pid,t))}}}),hw.exports.signals=function(){return Aw},pw=!1,Q9=function(){pw||!Qm(global.process)||(pw=!0,Js.count+=1,Aw=Aw.filter(function(e){try{return Qi.on(e,SF[e]),!0}catch{return!1}}),Qi.emit=twe,Qi.reallyExit=ewe)},hw.exports.load=Q9,T9=Qi.reallyExit,ewe=function(e){Qm(global.process)&&(Qi.exitCode=e||0,Tm("exit",Qi.exitCode,null),Tm("afterexit",Qi.exitCode,null),T9.call(Qi,Qi.exitCode))},DF=Qi.emit,twe=function(e,r){if(e==="exit"&&Qm(global.process)){r!==void 0&&(Qi.exitCode=r);var s=DF.apply(this,arguments);return Tm("exit",Qi.exitCode,null),Tm("afterexit",Qi.exitCode,null),s}else return DF.apply(this,arguments)}):hw.exports=function(){return function(){}};var ZCe,Aw,$Ce,OS,Js,vF,Tm,SF,pw,Q9,T9,ewe,DF,twe});var nwe=_((hKt,rwe)=>{"use strict";var mpt=zCe(),ypt=R9();rwe.exports=mpt(()=>{ypt(()=>{process.stderr.write("\x1B[?25h")},{alwaysLast:!0})})});var F9=_(gw=>{"use strict";var Ept=nwe(),bF=!1;gw.show=(t=process.stderr)=>{t.isTTY&&(bF=!1,t.write("\x1B[?25h"))};gw.hide=(t=process.stderr)=>{t.isTTY&&(Ept(),bF=!0,t.write("\x1B[?25l"))};gw.toggle=(t,e)=>{t!==void 0&&(bF=t),bF?gw.show(e):gw.hide(e)}});var awe=_(LS=>{"use strict";var owe=LS&&LS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(LS,"__esModule",{value:!0});var iwe=owe(x9()),swe=owe(F9()),Ipt=(t,{showCursor:e=!1}={})=>{let r=0,s="",a=!1,n=c=>{!e&&!a&&(swe.default.hide(),a=!0);let f=c+` +`;f!==s&&(s=f,t.write(iwe.default.eraseLines(r)+f),r=f.split(` +`).length)};return n.clear=()=>{t.write(iwe.default.eraseLines(r)),s="",r=0},n.done=()=>{s="",r=0,e||(swe.default.show(),a=!1)},n};LS.default={create:Ipt}});var lwe=_((mKt,Cpt)=>{Cpt.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY_BUILD_BASE",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}}]});var fwe=_(tc=>{"use strict";var uwe=lwe(),uA=process.env;Object.defineProperty(tc,"_vendors",{value:uwe.map(function(t){return t.constant})});tc.name=null;tc.isPR=null;uwe.forEach(function(t){var e=Array.isArray(t.env)?t.env:[t.env],r=e.every(function(s){return cwe(s)});if(tc[t.constant]=r,r)switch(tc.name=t.name,typeof t.pr){case"string":tc.isPR=!!uA[t.pr];break;case"object":"env"in t.pr?tc.isPR=t.pr.env in uA&&uA[t.pr.env]!==t.pr.ne:"any"in t.pr?tc.isPR=t.pr.any.some(function(s){return!!uA[s]}):tc.isPR=cwe(t.pr);break;default:tc.isPR=null}});tc.isCI=!!(uA.CI||uA.CONTINUOUS_INTEGRATION||uA.BUILD_NUMBER||uA.RUN_ID||tc.name);function cwe(t){return typeof t=="string"?!!uA[t]:Object.keys(t).every(function(e){return uA[e]===t[e]})}});var pwe=_((EKt,Awe)=>{"use strict";Awe.exports=fwe().isCI});var gwe=_((IKt,hwe)=>{"use strict";var wpt=t=>{let e=new Set;do for(let r of Reflect.ownKeys(t))e.add([t,r]);while((t=Reflect.getPrototypeOf(t))&&t!==Object.prototype);return e};hwe.exports=(t,{include:e,exclude:r}={})=>{let s=a=>{let n=c=>typeof c=="string"?a===c:c.test(a);return e?e.some(n):r?!r.some(n):!0};for(let[a,n]of wpt(t.constructor.prototype)){if(n==="constructor"||!s(n))continue;let c=Reflect.getOwnPropertyDescriptor(a,n);c&&typeof c.value=="function"&&(t[n]=t[n].bind(t))}return t}});var Cwe=_(Vn=>{"use strict";var mw,_S,QF,H9;typeof performance=="object"&&typeof performance.now=="function"?(dwe=performance,Vn.unstable_now=function(){return dwe.now()}):(N9=Date,mwe=N9.now(),Vn.unstable_now=function(){return N9.now()-mwe});var dwe,N9,mwe;typeof window>"u"||typeof MessageChannel!="function"?(dw=null,O9=null,L9=function(){if(dw!==null)try{var t=Vn.unstable_now();dw(!0,t),dw=null}catch(e){throw setTimeout(L9,0),e}},mw=function(t){dw!==null?setTimeout(mw,0,t):(dw=t,setTimeout(L9,0))},_S=function(t,e){O9=setTimeout(t,e)},QF=function(){clearTimeout(O9)},Vn.unstable_shouldYield=function(){return!1},H9=Vn.unstable_forceFrameRate=function(){}):(ywe=window.setTimeout,Ewe=window.clearTimeout,typeof console<"u"&&(Iwe=window.cancelAnimationFrame,typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof Iwe!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")),MS=!1,US=null,PF=-1,M9=5,U9=0,Vn.unstable_shouldYield=function(){return Vn.unstable_now()>=U9},H9=function(){},Vn.unstable_forceFrameRate=function(t){0>t||125>>1,a=t[s];if(a!==void 0&&0kF(c,r))p!==void 0&&0>kF(p,c)?(t[s]=p,t[f]=r,s=f):(t[s]=c,t[n]=r,s=n);else if(p!==void 0&&0>kF(p,r))t[s]=p,t[f]=r,s=f;else break e}}return e}return null}function kF(t,e){var r=t.sortIndex-e.sortIndex;return r!==0?r:t.id-e.id}var fA=[],Z0=[],Bpt=1,qc=null,$o=3,RF=!1,Rm=!1,HS=!1;function G9(t){for(var e=ef(Z0);e!==null;){if(e.callback===null)TF(Z0);else if(e.startTime<=t)TF(Z0),e.sortIndex=e.expirationTime,j9(fA,e);else break;e=ef(Z0)}}function q9(t){if(HS=!1,G9(t),!Rm)if(ef(fA)!==null)Rm=!0,mw(W9);else{var e=ef(Z0);e!==null&&_S(q9,e.startTime-t)}}function W9(t,e){Rm=!1,HS&&(HS=!1,QF()),RF=!0;var r=$o;try{for(G9(e),qc=ef(fA);qc!==null&&(!(qc.expirationTime>e)||t&&!Vn.unstable_shouldYield());){var s=qc.callback;if(typeof s=="function"){qc.callback=null,$o=qc.priorityLevel;var a=s(qc.expirationTime<=e);e=Vn.unstable_now(),typeof a=="function"?qc.callback=a:qc===ef(fA)&&TF(fA),G9(e)}else TF(fA);qc=ef(fA)}if(qc!==null)var n=!0;else{var c=ef(Z0);c!==null&&_S(q9,c.startTime-e),n=!1}return n}finally{qc=null,$o=r,RF=!1}}var vpt=H9;Vn.unstable_IdlePriority=5;Vn.unstable_ImmediatePriority=1;Vn.unstable_LowPriority=4;Vn.unstable_NormalPriority=3;Vn.unstable_Profiling=null;Vn.unstable_UserBlockingPriority=2;Vn.unstable_cancelCallback=function(t){t.callback=null};Vn.unstable_continueExecution=function(){Rm||RF||(Rm=!0,mw(W9))};Vn.unstable_getCurrentPriorityLevel=function(){return $o};Vn.unstable_getFirstCallbackNode=function(){return ef(fA)};Vn.unstable_next=function(t){switch($o){case 1:case 2:case 3:var e=3;break;default:e=$o}var r=$o;$o=e;try{return t()}finally{$o=r}};Vn.unstable_pauseExecution=function(){};Vn.unstable_requestPaint=vpt;Vn.unstable_runWithPriority=function(t,e){switch(t){case 1:case 2:case 3:case 4:case 5:break;default:t=3}var r=$o;$o=t;try{return e()}finally{$o=r}};Vn.unstable_scheduleCallback=function(t,e,r){var s=Vn.unstable_now();switch(typeof r=="object"&&r!==null?(r=r.delay,r=typeof r=="number"&&0s?(t.sortIndex=r,j9(Z0,t),ef(fA)===null&&t===ef(Z0)&&(HS?QF():HS=!0,_S(q9,r-s))):(t.sortIndex=a,j9(fA,t),Rm||RF||(Rm=!0,mw(W9))),t};Vn.unstable_wrapCallback=function(t){var e=$o;return function(){var r=$o;$o=e;try{return t.apply(this,arguments)}finally{$o=r}}}});var Y9=_((wKt,wwe)=>{"use strict";wwe.exports=Cwe()});var Bwe=_((BKt,jS)=>{jS.exports=function(e){var r={},s=y9(),a=hn(),n=Y9();function c(v){for(var D="https://reactjs.org/docs/error-decoder.html?invariant="+v,Q=1;Q_e||V[Se]!==ne[_e])return` +`+V[Se].replace(" at new "," at ");while(1<=Se&&0<=_e);break}}}finally{ve=!1,Error.prepareStackTrace=Q}return(v=v?v.displayName||v.name:"")?oc(v):""}var ac=[],Oi=-1;function no(v){return{current:v}}function Rt(v){0>Oi||(v.current=ac[Oi],ac[Oi]=null,Oi--)}function xn(v,D){Oi++,ac[Oi]=v.current,v.current=D}var la={},Gi=no(la),Li=no(!1),Na=la;function dn(v,D){var Q=v.type.contextTypes;if(!Q)return la;var H=v.stateNode;if(H&&H.__reactInternalMemoizedUnmaskedChildContext===D)return H.__reactInternalMemoizedMaskedChildContext;var V={},ne;for(ne in Q)V[ne]=D[ne];return H&&(v=v.stateNode,v.__reactInternalMemoizedUnmaskedChildContext=D,v.__reactInternalMemoizedMaskedChildContext=V),V}function Kn(v){return v=v.childContextTypes,v!=null}function Au(){Rt(Li),Rt(Gi)}function yh(v,D,Q){if(Gi.current!==la)throw Error(c(168));xn(Gi,D),xn(Li,Q)}function Oa(v,D,Q){var H=v.stateNode;if(v=D.childContextTypes,typeof H.getChildContext!="function")return Q;H=H.getChildContext();for(var V in H)if(!(V in v))throw Error(c(108,g(D)||"Unknown",V));return s({},Q,H)}function La(v){return v=(v=v.stateNode)&&v.__reactInternalMemoizedMergedChildContext||la,Na=Gi.current,xn(Gi,v),xn(Li,Li.current),!0}function Ma(v,D,Q){var H=v.stateNode;if(!H)throw Error(c(169));Q?(v=Oa(v,D,Na),H.__reactInternalMemoizedMergedChildContext=v,Rt(Li),Rt(Gi),xn(Gi,v)):Rt(Li),xn(Li,Q)}var $e=null,Ua=null,hf=n.unstable_now;hf();var lc=0,wn=8;function ca(v){if(1&v)return wn=15,1;if(2&v)return wn=14,2;if(4&v)return wn=13,4;var D=24&v;return D!==0?(wn=12,D):v&32?(wn=11,32):(D=192&v,D!==0?(wn=10,D):v&256?(wn=9,256):(D=3584&v,D!==0?(wn=8,D):v&4096?(wn=7,4096):(D=4186112&v,D!==0?(wn=6,D):(D=62914560&v,D!==0?(wn=5,D):v&67108864?(wn=4,67108864):v&134217728?(wn=3,134217728):(D=805306368&v,D!==0?(wn=2,D):1073741824&v?(wn=1,1073741824):(wn=8,v))))))}function LA(v){switch(v){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}function MA(v){switch(v){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(c(358,v))}}function ua(v,D){var Q=v.pendingLanes;if(Q===0)return wn=0;var H=0,V=0,ne=v.expiredLanes,Se=v.suspendedLanes,_e=v.pingedLanes;if(ne!==0)H=ne,V=wn=15;else if(ne=Q&134217727,ne!==0){var pt=ne&~Se;pt!==0?(H=ca(pt),V=wn):(_e&=ne,_e!==0&&(H=ca(_e),V=wn))}else ne=Q&~Se,ne!==0?(H=ca(ne),V=wn):_e!==0&&(H=ca(_e),V=wn);if(H===0)return 0;if(H=31-ns(H),H=Q&((0>H?0:1<Q;Q++)D.push(v);return D}function Ha(v,D,Q){v.pendingLanes|=D;var H=D-1;v.suspendedLanes&=H,v.pingedLanes&=H,v=v.eventTimes,D=31-ns(D),v[D]=Q}var ns=Math.clz32?Math.clz32:uc,cc=Math.log,pu=Math.LN2;function uc(v){return v===0?32:31-(cc(v)/pu|0)|0}var ja=n.unstable_runWithPriority,Mi=n.unstable_scheduleCallback,Is=n.unstable_cancelCallback,vl=n.unstable_shouldYield,gf=n.unstable_requestPaint,fc=n.unstable_now,wi=n.unstable_getCurrentPriorityLevel,Qn=n.unstable_ImmediatePriority,Ac=n.unstable_UserBlockingPriority,Ke=n.unstable_NormalPriority,st=n.unstable_LowPriority,St=n.unstable_IdlePriority,lr={},te=gf!==void 0?gf:function(){},Ee=null,Oe=null,dt=!1,Et=fc(),bt=1e4>Et?fc:function(){return fc()-Et};function tr(){switch(wi()){case Qn:return 99;case Ac:return 98;case Ke:return 97;case st:return 96;case St:return 95;default:throw Error(c(332))}}function An(v){switch(v){case 99:return Qn;case 98:return Ac;case 97:return Ke;case 96:return st;case 95:return St;default:throw Error(c(332))}}function li(v,D){return v=An(v),ja(v,D)}function qi(v,D,Q){return v=An(v),Mi(v,D,Q)}function Tn(){if(Oe!==null){var v=Oe;Oe=null,Is(v)}Ga()}function Ga(){if(!dt&&Ee!==null){dt=!0;var v=0;try{var D=Ee;li(99,function(){for(;vRn?(_n=kr,kr=null):_n=kr.sibling;var zr=Zt(et,kr,gt[Rn],Xt);if(zr===null){kr===null&&(kr=_n);break}v&&kr&&zr.alternate===null&&D(et,kr),qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr,kr=_n}if(Rn===gt.length)return Q(et,kr),Dr;if(kr===null){for(;RnRn?(_n=kr,kr=null):_n=kr.sibling;var ci=Zt(et,kr,zr.value,Xt);if(ci===null){kr===null&&(kr=_n);break}v&&kr&&ci.alternate===null&&D(et,kr),qe=ne(ci,qe,Rn),Zn===null?Dr=ci:Zn.sibling=ci,Zn=ci,kr=_n}if(zr.done)return Q(et,kr),Dr;if(kr===null){for(;!zr.done;Rn++,zr=gt.next())zr=Lr(et,zr.value,Xt),zr!==null&&(qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr);return Dr}for(kr=H(et,kr);!zr.done;Rn++,zr=gt.next())zr=zn(kr,et,Rn,zr.value,Xt),zr!==null&&(v&&zr.alternate!==null&&kr.delete(zr.key===null?Rn:zr.key),qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr);return v&&kr.forEach(function(Du){return D(et,Du)}),Dr}return function(et,qe,gt,Xt){var Dr=typeof gt=="object"&>!==null&>.type===E&>.key===null;Dr&&(gt=gt.props.children);var Zn=typeof gt=="object"&>!==null;if(Zn)switch(gt.$$typeof){case p:e:{for(Zn=gt.key,Dr=qe;Dr!==null;){if(Dr.key===Zn){switch(Dr.tag){case 7:if(gt.type===E){Q(et,Dr.sibling),qe=V(Dr,gt.props.children),qe.return=et,et=qe;break e}break;default:if(Dr.elementType===gt.type){Q(et,Dr.sibling),qe=V(Dr,gt.props),qe.ref=yt(et,Dr,gt),qe.return=et,et=qe;break e}}Q(et,Dr);break}else D(et,Dr);Dr=Dr.sibling}gt.type===E?(qe=kf(gt.props.children,et.mode,Xt,gt.key),qe.return=et,et=qe):(Xt=sd(gt.type,gt.key,gt.props,null,et.mode,Xt),Xt.ref=yt(et,qe,gt),Xt.return=et,et=Xt)}return Se(et);case h:e:{for(Dr=gt.key;qe!==null;){if(qe.key===Dr)if(qe.tag===4&&qe.stateNode.containerInfo===gt.containerInfo&&qe.stateNode.implementation===gt.implementation){Q(et,qe.sibling),qe=V(qe,gt.children||[]),qe.return=et,et=qe;break e}else{Q(et,qe);break}else D(et,qe);qe=qe.sibling}qe=Qo(gt,et.mode,Xt),qe.return=et,et=qe}return Se(et)}if(typeof gt=="string"||typeof gt=="number")return gt=""+gt,qe!==null&&qe.tag===6?(Q(et,qe.sibling),qe=V(qe,gt),qe.return=et,et=qe):(Q(et,qe),qe=b2(gt,et.mode,Xt),qe.return=et,et=qe),Se(et);if(mf(gt))return yi(et,qe,gt,Xt);if(Ce(gt))return za(et,qe,gt,Xt);if(Zn&&gu(et,gt),typeof gt>"u"&&!Dr)switch(et.tag){case 1:case 22:case 0:case 11:case 15:throw Error(c(152,g(et.type)||"Component"))}return Q(et,qe)}}var Mg=By(!0),e2=By(!1),vh={},ur=no(vh),zi=no(vh),yf=no(vh);function qa(v){if(v===vh)throw Error(c(174));return v}function Ug(v,D){xn(yf,D),xn(zi,v),xn(ur,vh),v=mt(D),Rt(ur),xn(ur,v)}function du(){Rt(ur),Rt(zi),Rt(yf)}function Ef(v){var D=qa(yf.current),Q=qa(ur.current);D=j(Q,v.type,D),Q!==D&&(xn(zi,v),xn(ur,D))}function wt(v){zi.current===v&&(Rt(ur),Rt(zi))}var di=no(0);function GA(v){for(var D=v;D!==null;){if(D.tag===13){var Q=D.memoizedState;if(Q!==null&&(Q=Q.dehydrated,Q===null||gr(Q)||Bo(Q)))return D}else if(D.tag===19&&D.memoizedProps.revealOrder!==void 0){if(D.flags&64)return D}else if(D.child!==null){D.child.return=D,D=D.child;continue}if(D===v)break;for(;D.sibling===null;){if(D.return===null||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}return null}var Wa=null,Aa=null,Ya=!1;function _g(v,D){var Q=Ka(5,null,null,0);Q.elementType="DELETED",Q.type="DELETED",Q.stateNode=D,Q.return=v,Q.flags=8,v.lastEffect!==null?(v.lastEffect.nextEffect=Q,v.lastEffect=Q):v.firstEffect=v.lastEffect=Q}function Sh(v,D){switch(v.tag){case 5:return D=aa(D,v.type,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 6:return D=FA(D,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 13:return!1;default:return!1}}function Hg(v){if(Ya){var D=Aa;if(D){var Q=D;if(!Sh(v,D)){if(D=Me(Q),!D||!Sh(v,D)){v.flags=v.flags&-1025|2,Ya=!1,Wa=v;return}_g(Wa,Q)}Wa=v,Aa=cu(D)}else v.flags=v.flags&-1025|2,Ya=!1,Wa=v}}function vy(v){for(v=v.return;v!==null&&v.tag!==5&&v.tag!==3&&v.tag!==13;)v=v.return;Wa=v}function qA(v){if(!X||v!==Wa)return!1;if(!Ya)return vy(v),Ya=!0,!1;var D=v.type;if(v.tag!==5||D!=="head"&&D!=="body"&&!it(D,v.memoizedProps))for(D=Aa;D;)_g(v,D),D=Me(D);if(vy(v),v.tag===13){if(!X)throw Error(c(316));if(v=v.memoizedState,v=v!==null?v.dehydrated:null,!v)throw Error(c(317));Aa=NA(v)}else Aa=Wa?Me(v.stateNode):null;return!0}function jg(){X&&(Aa=Wa=null,Ya=!1)}var mu=[];function yu(){for(var v=0;vne))throw Error(c(301));ne+=1,Pi=is=null,D.updateQueue=null,If.current=re,v=Q(H,V)}while(Cf)}if(If.current=kt,D=is!==null&&is.next!==null,Eu=0,Pi=is=Gn=null,WA=!1,D)throw Error(c(300));return v}function ss(){var v={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return Pi===null?Gn.memoizedState=Pi=v:Pi=Pi.next=v,Pi}function Pl(){if(is===null){var v=Gn.alternate;v=v!==null?v.memoizedState:null}else v=is.next;var D=Pi===null?Gn.memoizedState:Pi.next;if(D!==null)Pi=D,is=v;else{if(v===null)throw Error(c(310));is=v,v={memoizedState:is.memoizedState,baseState:is.baseState,baseQueue:is.baseQueue,queue:is.queue,next:null},Pi===null?Gn.memoizedState=Pi=v:Pi=Pi.next=v}return Pi}function Po(v,D){return typeof D=="function"?D(v):D}function wf(v){var D=Pl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=is,V=H.baseQueue,ne=Q.pending;if(ne!==null){if(V!==null){var Se=V.next;V.next=ne.next,ne.next=Se}H.baseQueue=V=ne,Q.pending=null}if(V!==null){V=V.next,H=H.baseState;var _e=Se=ne=null,pt=V;do{var Wt=pt.lane;if((Eu&Wt)===Wt)_e!==null&&(_e=_e.next={lane:0,action:pt.action,eagerReducer:pt.eagerReducer,eagerState:pt.eagerState,next:null}),H=pt.eagerReducer===v?pt.eagerState:v(H,pt.action);else{var Sr={lane:Wt,action:pt.action,eagerReducer:pt.eagerReducer,eagerState:pt.eagerState,next:null};_e===null?(Se=_e=Sr,ne=H):_e=_e.next=Sr,Gn.lanes|=Wt,Zg|=Wt}pt=pt.next}while(pt!==null&&pt!==V);_e===null?ne=H:_e.next=Se,vo(H,D.memoizedState)||(Je=!0),D.memoizedState=H,D.baseState=ne,D.baseQueue=_e,Q.lastRenderedState=H}return[D.memoizedState,Q.dispatch]}function Bf(v){var D=Pl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=Q.dispatch,V=Q.pending,ne=D.memoizedState;if(V!==null){Q.pending=null;var Se=V=V.next;do ne=v(ne,Se.action),Se=Se.next;while(Se!==V);vo(ne,D.memoizedState)||(Je=!0),D.memoizedState=ne,D.baseQueue===null&&(D.baseState=ne),Q.lastRenderedState=ne}return[ne,H]}function xl(v,D,Q){var H=D._getVersion;H=H(D._source);var V=y?D._workInProgressVersionPrimary:D._workInProgressVersionSecondary;if(V!==null?v=V===H:(v=v.mutableReadLanes,(v=(Eu&v)===v)&&(y?D._workInProgressVersionPrimary=H:D._workInProgressVersionSecondary=H,mu.push(D))),v)return Q(D._source);throw mu.push(D),Error(c(350))}function yn(v,D,Q,H){var V=so;if(V===null)throw Error(c(349));var ne=D._getVersion,Se=ne(D._source),_e=If.current,pt=_e.useState(function(){return xl(V,D,Q)}),Wt=pt[1],Sr=pt[0];pt=Pi;var Lr=v.memoizedState,Zt=Lr.refs,zn=Zt.getSnapshot,yi=Lr.source;Lr=Lr.subscribe;var za=Gn;return v.memoizedState={refs:Zt,source:D,subscribe:H},_e.useEffect(function(){Zt.getSnapshot=Q,Zt.setSnapshot=Wt;var et=ne(D._source);if(!vo(Se,et)){et=Q(D._source),vo(Sr,et)||(Wt(et),et=Bs(za),V.mutableReadLanes|=et&V.pendingLanes),et=V.mutableReadLanes,V.entangledLanes|=et;for(var qe=V.entanglements,gt=et;0Q?98:Q,function(){v(!0)}),li(97m2&&(D.flags|=64,V=!0,XA(H,!1),D.lanes=33554432)}else{if(!V)if(v=GA(ne),v!==null){if(D.flags|=64,V=!0,v=v.updateQueue,v!==null&&(D.updateQueue=v,D.flags|=4),XA(H,!0),H.tail===null&&H.tailMode==="hidden"&&!ne.alternate&&!Ya)return D=D.lastEffect=H.lastEffect,D!==null&&(D.nextEffect=null),null}else 2*bt()-H.renderingStartTime>m2&&Q!==1073741824&&(D.flags|=64,V=!0,XA(H,!1),D.lanes=33554432);H.isBackwards?(ne.sibling=D.child,D.child=ne):(v=H.last,v!==null?v.sibling=ne:D.child=ne,H.last=ne)}return H.tail!==null?(v=H.tail,H.rendering=v,H.tail=v.sibling,H.lastEffect=D.lastEffect,H.renderingStartTime=bt(),v.sibling=null,D=di.current,xn(di,V?D&1|2:D&1),v):null;case 23:case 24:return B2(),v!==null&&v.memoizedState!==null!=(D.memoizedState!==null)&&H.mode!=="unstable-defer-without-hiding"&&(D.flags|=4),null}throw Error(c(156,D.tag))}function qL(v){switch(v.tag){case 1:Kn(v.type)&&Au();var D=v.flags;return D&4096?(v.flags=D&-4097|64,v):null;case 3:if(du(),Rt(Li),Rt(Gi),yu(),D=v.flags,D&64)throw Error(c(285));return v.flags=D&-4097|64,v;case 5:return wt(v),null;case 13:return Rt(di),D=v.flags,D&4096?(v.flags=D&-4097|64,v):null;case 19:return Rt(di),null;case 4:return du(),null;case 10:return Og(v),null;case 23:case 24:return B2(),null;default:return null}}function Yg(v,D){try{var Q="",H=D;do Q+=$1(H),H=H.return;while(H);var V=Q}catch(ne){V=` +Error generating stack: `+ne.message+` +`+ne.stack}return{value:v,source:D,stack:V}}function Vg(v,D){try{console.error(D.value)}catch(Q){setTimeout(function(){throw Q})}}var WL=typeof WeakMap=="function"?WeakMap:Map;function i2(v,D,Q){Q=Dl(-1,Q),Q.tag=3,Q.payload={element:null};var H=D.value;return Q.callback=function(){_y||(_y=!0,y2=H),Vg(v,D)},Q}function Jg(v,D,Q){Q=Dl(-1,Q),Q.tag=3;var H=v.type.getDerivedStateFromError;if(typeof H=="function"){var V=D.value;Q.payload=function(){return Vg(v,D),H(V)}}var ne=v.stateNode;return ne!==null&&typeof ne.componentDidCatch=="function"&&(Q.callback=function(){typeof H!="function"&&(hc===null?hc=new Set([this]):hc.add(this),Vg(v,D));var Se=D.stack;this.componentDidCatch(D.value,{componentStack:Se!==null?Se:""})}),Q}var YL=typeof WeakSet=="function"?WeakSet:Set;function s2(v){var D=v.ref;if(D!==null)if(typeof D=="function")try{D(null)}catch(Q){xf(v,Q)}else D.current=null}function xy(v,D){switch(D.tag){case 0:case 11:case 15:case 22:return;case 1:if(D.flags&256&&v!==null){var Q=v.memoizedProps,H=v.memoizedState;v=D.stateNode,D=v.getSnapshotBeforeUpdate(D.elementType===D.type?Q:So(D.type,Q),H),v.__reactInternalSnapshotBeforeUpdate=D}return;case 3:F&&D.flags&256&&Ts(D.stateNode.containerInfo);return;case 5:case 6:case 4:case 17:return}throw Error(c(163))}function Th(v,D){if(D=D.updateQueue,D=D!==null?D.lastEffect:null,D!==null){var Q=D=D.next;do{if((Q.tag&v)===v){var H=Q.destroy;Q.destroy=void 0,H!==void 0&&H()}Q=Q.next}while(Q!==D)}}function uP(v,D,Q){switch(Q.tag){case 0:case 11:case 15:case 22:if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{if((v.tag&3)===3){var H=v.create;v.destroy=H()}v=v.next}while(v!==D)}if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{var V=v;H=V.next,V=V.tag,V&4&&V&1&&(vP(Q,v),tM(Q,v)),v=H}while(v!==D)}return;case 1:v=Q.stateNode,Q.flags&4&&(D===null?v.componentDidMount():(H=Q.elementType===Q.type?D.memoizedProps:So(Q.type,D.memoizedProps),v.componentDidUpdate(H,D.memoizedState,v.__reactInternalSnapshotBeforeUpdate))),D=Q.updateQueue,D!==null&&Cy(Q,D,v);return;case 3:if(D=Q.updateQueue,D!==null){if(v=null,Q.child!==null)switch(Q.child.tag){case 5:v=Re(Q.child.stateNode);break;case 1:v=Q.child.stateNode}Cy(Q,D,v)}return;case 5:v=Q.stateNode,D===null&&Q.flags&4&&$s(v,Q.type,Q.memoizedProps,Q);return;case 6:return;case 4:return;case 12:return;case 13:X&&Q.memoizedState===null&&(Q=Q.alternate,Q!==null&&(Q=Q.memoizedState,Q!==null&&(Q=Q.dehydrated,Q!==null&&uu(Q))));return;case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(c(163))}function fP(v,D){if(F)for(var Q=v;;){if(Q.tag===5){var H=Q.stateNode;D?dh(H):to(Q.stateNode,Q.memoizedProps)}else if(Q.tag===6)H=Q.stateNode,D?mh(H):jn(H,Q.memoizedProps);else if((Q.tag!==23&&Q.tag!==24||Q.memoizedState===null||Q===v)&&Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===v)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===v)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}}function ky(v,D){if(Ua&&typeof Ua.onCommitFiberUnmount=="function")try{Ua.onCommitFiberUnmount($e,D)}catch{}switch(D.tag){case 0:case 11:case 14:case 15:case 22:if(v=D.updateQueue,v!==null&&(v=v.lastEffect,v!==null)){var Q=v=v.next;do{var H=Q,V=H.destroy;if(H=H.tag,V!==void 0)if(H&4)vP(D,Q);else{H=D;try{V()}catch(ne){xf(H,ne)}}Q=Q.next}while(Q!==v)}break;case 1:if(s2(D),v=D.stateNode,typeof v.componentWillUnmount=="function")try{v.props=D.memoizedProps,v.state=D.memoizedState,v.componentWillUnmount()}catch(ne){xf(D,ne)}break;case 5:s2(D);break;case 4:F?gP(v,D):z&&z&&(D=D.stateNode.containerInfo,v=ou(D),TA(D,v))}}function AP(v,D){for(var Q=D;;)if(ky(v,Q),Q.child===null||F&&Q.tag===4){if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}else Q.child.return=Q,Q=Q.child}function Qy(v){v.alternate=null,v.child=null,v.dependencies=null,v.firstEffect=null,v.lastEffect=null,v.memoizedProps=null,v.memoizedState=null,v.pendingProps=null,v.return=null,v.updateQueue=null}function pP(v){return v.tag===5||v.tag===3||v.tag===4}function hP(v){if(F){e:{for(var D=v.return;D!==null;){if(pP(D))break e;D=D.return}throw Error(c(160))}var Q=D;switch(D=Q.stateNode,Q.tag){case 5:var H=!1;break;case 3:D=D.containerInfo,H=!0;break;case 4:D=D.containerInfo,H=!0;break;default:throw Error(c(161))}Q.flags&16&&(Af(D),Q.flags&=-17);e:t:for(Q=v;;){for(;Q.sibling===null;){if(Q.return===null||pP(Q.return)){Q=null;break e}Q=Q.return}for(Q.sibling.return=Q.return,Q=Q.sibling;Q.tag!==5&&Q.tag!==6&&Q.tag!==18;){if(Q.flags&2||Q.child===null||Q.tag===4)continue t;Q.child.return=Q,Q=Q.child}if(!(Q.flags&2)){Q=Q.stateNode;break e}}H?o2(v,Q,D):a2(v,Q,D)}}function o2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?eo(Q,v,D):Io(Q,v);else if(H!==4&&(v=v.child,v!==null))for(o2(v,D,Q),v=v.sibling;v!==null;)o2(v,D,Q),v=v.sibling}function a2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?ji(Q,v,D):ai(Q,v);else if(H!==4&&(v=v.child,v!==null))for(a2(v,D,Q),v=v.sibling;v!==null;)a2(v,D,Q),v=v.sibling}function gP(v,D){for(var Q=D,H=!1,V,ne;;){if(!H){H=Q.return;e:for(;;){if(H===null)throw Error(c(160));switch(V=H.stateNode,H.tag){case 5:ne=!1;break e;case 3:V=V.containerInfo,ne=!0;break e;case 4:V=V.containerInfo,ne=!0;break e}H=H.return}H=!0}if(Q.tag===5||Q.tag===6)AP(v,Q),ne?QA(V,Q.stateNode):wo(V,Q.stateNode);else if(Q.tag===4){if(Q.child!==null){V=Q.stateNode.containerInfo,ne=!0,Q.child.return=Q,Q=Q.child;continue}}else if(ky(v,Q),Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return,Q.tag===4&&(H=!1)}Q.sibling.return=Q.return,Q=Q.sibling}}function l2(v,D){if(F){switch(D.tag){case 0:case 11:case 14:case 15:case 22:Th(3,D);return;case 1:return;case 5:var Q=D.stateNode;if(Q!=null){var H=D.memoizedProps;v=v!==null?v.memoizedProps:H;var V=D.type,ne=D.updateQueue;D.updateQueue=null,ne!==null&&Co(Q,ne,V,v,H,D)}return;case 6:if(D.stateNode===null)throw Error(c(162));Q=D.memoizedProps,rs(D.stateNode,v!==null?v.memoizedProps:Q,Q);return;case 3:X&&(D=D.stateNode,D.hydrate&&(D.hydrate=!1,OA(D.containerInfo)));return;case 12:return;case 13:dP(D),Kg(D);return;case 19:Kg(D);return;case 17:return;case 23:case 24:fP(D,D.memoizedState!==null);return}throw Error(c(163))}switch(D.tag){case 0:case 11:case 14:case 15:case 22:Th(3,D);return;case 12:return;case 13:dP(D),Kg(D);return;case 19:Kg(D);return;case 3:X&&(Q=D.stateNode,Q.hydrate&&(Q.hydrate=!1,OA(Q.containerInfo)));break;case 23:case 24:return}e:if(z){switch(D.tag){case 1:case 5:case 6:case 20:break e;case 3:case 4:D=D.stateNode,TA(D.containerInfo,D.pendingChildren);break e}throw Error(c(163))}}function dP(v){v.memoizedState!==null&&(d2=bt(),F&&fP(v.child,!0))}function Kg(v){var D=v.updateQueue;if(D!==null){v.updateQueue=null;var Q=v.stateNode;Q===null&&(Q=v.stateNode=new YL),D.forEach(function(H){var V=nM.bind(null,v,H);Q.has(H)||(Q.add(H),H.then(V,V))})}}function VL(v,D){return v!==null&&(v=v.memoizedState,v===null||v.dehydrated!==null)?(D=D.memoizedState,D!==null&&D.dehydrated===null):!1}var Ty=0,Ry=1,Fy=2,zg=3,Ny=4;if(typeof Symbol=="function"&&Symbol.for){var Xg=Symbol.for;Ty=Xg("selector.component"),Ry=Xg("selector.has_pseudo_class"),Fy=Xg("selector.role"),zg=Xg("selector.test_id"),Ny=Xg("selector.text")}function Oy(v){var D=$(v);if(D!=null){if(typeof D.memoizedProps["data-testname"]!="string")throw Error(c(364));return D}if(v=ir(v),v===null)throw Error(c(362));return v.stateNode.current}function Sf(v,D){switch(D.$$typeof){case Ty:if(v.type===D.value)return!0;break;case Ry:e:{D=D.value,v=[v,0];for(var Q=0;Q";case Ry:return":has("+(Df(v)||"")+")";case Fy:return'[role="'+v.value+'"]';case Ny:return'"'+v.value+'"';case zg:return'[data-testname="'+v.value+'"]';default:throw Error(c(365,v))}}function c2(v,D){var Q=[];v=[v,0];for(var H=0;HV&&(V=Se),Q&=~ne}if(Q=V,Q=bt()-Q,Q=(120>Q?120:480>Q?480:1080>Q?1080:1920>Q?1920:3e3>Q?3e3:4320>Q?4320:1960*KL(Q/1960))-Q,10 component higher in the tree to provide a loading indicator or placeholder to display.`)}ws!==5&&(ws=2),pt=Yg(pt,_e),Zt=Se;do{switch(Zt.tag){case 3:ne=pt,Zt.flags|=4096,D&=-D,Zt.lanes|=D;var Zn=i2(Zt,ne,D);Iy(Zt,Zn);break e;case 1:ne=pt;var kr=Zt.type,Rn=Zt.stateNode;if(!(Zt.flags&64)&&(typeof kr.getDerivedStateFromError=="function"||Rn!==null&&typeof Rn.componentDidCatch=="function"&&(hc===null||!hc.has(Rn)))){Zt.flags|=4096,D&=-D,Zt.lanes|=D;var _n=Jg(Zt,ne,D);Iy(Zt,_n);break e}}Zt=Zt.return}while(Zt!==null)}BP(Q)}catch(zr){D=zr,Xi===Q&&Q!==null&&(Xi=Q=Q.return);continue}break}while(!0)}function CP(){var v=My.current;return My.current=kt,v===null?kt:v}function id(v,D){var Q=xr;xr|=16;var H=CP();so===v&&Ns===D||Oh(v,D);do try{XL();break}catch(V){IP(v,V)}while(!0);if(Fg(),xr=Q,My.current=H,Xi!==null)throw Error(c(261));return so=null,Ns=0,ws}function XL(){for(;Xi!==null;)wP(Xi)}function ZL(){for(;Xi!==null&&!vl();)wP(Xi)}function wP(v){var D=bP(v.alternate,v,ZA);v.memoizedProps=v.pendingProps,D===null?BP(v):Xi=D,f2.current=null}function BP(v){var D=v;do{var Q=D.alternate;if(v=D.return,D.flags&2048){if(Q=qL(D),Q!==null){Q.flags&=2047,Xi=Q;return}v!==null&&(v.firstEffect=v.lastEffect=null,v.flags|=2048)}else{if(Q=jL(Q,D,ZA),Q!==null){Xi=Q;return}if(Q=D,Q.tag!==24&&Q.tag!==23||Q.memoizedState===null||ZA&1073741824||!(Q.mode&4)){for(var H=0,V=Q.child;V!==null;)H|=V.lanes|V.childLanes,V=V.sibling;Q.childLanes=H}v!==null&&!(v.flags&2048)&&(v.firstEffect===null&&(v.firstEffect=D.firstEffect),D.lastEffect!==null&&(v.lastEffect!==null&&(v.lastEffect.nextEffect=D.firstEffect),v.lastEffect=D.lastEffect),1bt()-d2?Oh(v,0):h2|=Q),ga(v,D)}function nM(v,D){var Q=v.stateNode;Q!==null&&Q.delete(D),D=0,D===0&&(D=v.mode,D&2?D&4?(Bu===0&&(Bu=Rh),D=kn(62914560&~Bu),D===0&&(D=4194304)):D=tr()===99?1:2:D=1),Q=ko(),v=Gy(v,D),v!==null&&(Ha(v,D,Q),ga(v,Q))}var bP;bP=function(v,D,Q){var H=D.lanes;if(v!==null)if(v.memoizedProps!==D.pendingProps||Li.current)Je=!0;else if(Q&H)Je=!!(v.flags&16384);else{switch(Je=!1,D.tag){case 3:by(D),jg();break;case 5:Ef(D);break;case 1:Kn(D.type)&&La(D);break;case 4:Ug(D,D.stateNode.containerInfo);break;case 10:Ng(D,D.memoizedProps.value);break;case 13:if(D.memoizedState!==null)return Q&D.child.childLanes?r2(v,D,Q):(xn(di,di.current&1),D=qn(v,D,Q),D!==null?D.sibling:null);xn(di,di.current&1);break;case 19:if(H=(Q&D.childLanes)!==0,v.flags&64){if(H)return cP(v,D,Q);D.flags|=64}var V=D.memoizedState;if(V!==null&&(V.rendering=null,V.tail=null,V.lastEffect=null),xn(di,di.current),H)break;return null;case 23:case 24:return D.lanes=0,mi(v,D,Q)}return qn(v,D,Q)}else Je=!1;switch(D.lanes=0,D.tag){case 2:if(H=D.type,v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,V=dn(D,Gi.current),df(D,Q),V=qg(null,D,H,v,V,Q),D.flags|=1,typeof V=="object"&&V!==null&&typeof V.render=="function"&&V.$$typeof===void 0){if(D.tag=1,D.memoizedState=null,D.updateQueue=null,Kn(H)){var ne=!0;La(D)}else ne=!1;D.memoizedState=V.state!==null&&V.state!==void 0?V.state:null,Bh(D);var Se=H.getDerivedStateFromProps;typeof Se=="function"&&_A(D,H,Se,v),V.updater=HA,D.stateNode=V,V._reactInternals=D,bo(D,H,v,Q),D=t2(null,D,H,!0,ne,Q)}else D.tag=0,At(null,D,V,Q),D=D.child;return D;case 16:V=D.elementType;e:{switch(v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,ne=V._init,V=ne(V._payload),D.type=V,ne=D.tag=sM(V),v=So(V,v),ne){case 0:D=JA(null,D,V,v,Q);break e;case 1:D=lP(null,D,V,v,Q);break e;case 11:D=dr(null,D,V,v,Q);break e;case 14:D=vr(null,D,V,So(V.type,v),H,Q);break e}throw Error(c(306,V,""))}return D;case 0:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:So(H,V),JA(v,D,H,V,Q);case 1:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:So(H,V),lP(v,D,H,V,Q);case 3:if(by(D),H=D.updateQueue,v===null||H===null)throw Error(c(282));if(H=D.pendingProps,V=D.memoizedState,V=V!==null?V.element:null,Lg(v,D),UA(D,H,null,Q),H=D.memoizedState.element,H===V)jg(),D=qn(v,D,Q);else{if(V=D.stateNode,(ne=V.hydrate)&&(X?(Aa=cu(D.stateNode.containerInfo),Wa=D,ne=Ya=!0):ne=!1),ne){if(X&&(v=V.mutableSourceEagerHydrationData,v!=null))for(V=0;V=Wt&&ne>=Lr&&V<=Sr&&Se<=Zt){v.splice(D,1);break}else if(H!==Wt||Q.width!==pt.width||ZtSe){if(!(ne!==Lr||Q.height!==pt.height||SrV)){Wt>H&&(pt.width+=Wt-H,pt.x=H),Srne&&(pt.height+=Lr-ne,pt.y=ne),ZtQ&&(Q=Se)),Se ")+` + +No matching component was found for: + `)+v.join(" > ")}return null},r.getPublicRootInstance=function(v){if(v=v.current,!v.child)return null;switch(v.child.tag){case 5:return Re(v.child.stateNode);default:return v.child.stateNode}},r.injectIntoDevTools=function(v){if(v={bundleType:v.bundleType,version:v.version,rendererPackageName:v.rendererPackageName,rendererConfig:v.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:f.ReactCurrentDispatcher,findHostInstanceByFiber:aM,findFiberByHostInstance:v.findFiberByHostInstance||lM,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u")v=!1;else{var D=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!D.isDisabled&&D.supportsFiber)try{$e=D.inject(v),Ua=D}catch{}v=!0}return v},r.observeVisibleRects=function(v,D,Q,H){if(!qt)throw Error(c(363));v=u2(v,D);var V=on(v,Q,H).disconnect;return{disconnect:function(){V()}}},r.registerMutableSourceForHydration=function(v,D){var Q=D._getVersion;Q=Q(D._source),v.mutableSourceEagerHydrationData==null?v.mutableSourceEagerHydrationData=[D,Q]:v.mutableSourceEagerHydrationData.push(D,Q)},r.runWithPriority=function(v,D){var Q=lc;try{return lc=v,D()}finally{lc=Q}},r.shouldSuspend=function(){return!1},r.unbatchedUpdates=function(v,D){var Q=xr;xr&=-2,xr|=8;try{return v(D)}finally{xr=Q,xr===0&&(bf(),Tn())}},r.updateContainer=function(v,D,Q,H){var V=D.current,ne=ko(),Se=Bs(V);e:if(Q){Q=Q._reactInternals;t:{if(we(Q)!==Q||Q.tag!==1)throw Error(c(170));var _e=Q;do{switch(_e.tag){case 3:_e=_e.stateNode.context;break t;case 1:if(Kn(_e.type)){_e=_e.stateNode.__reactInternalMemoizedMergedChildContext;break t}}_e=_e.return}while(_e!==null);throw Error(c(171))}if(Q.tag===1){var pt=Q.type;if(Kn(pt)){Q=Oa(Q,pt,_e);break e}}Q=_e}else Q=la;return D.context===null?D.context=Q:D.pendingContext=Q,D=Dl(ne,Se),D.payload={element:v},H=H===void 0?null:H,H!==null&&(D.callback=H),bl(V,D),Tl(V,Se,ne),Se},r}});var Swe=_((vKt,vwe)=>{"use strict";vwe.exports=Bwe()});var bwe=_((SKt,Dwe)=>{"use strict";var Spt={ALIGN_COUNT:8,ALIGN_AUTO:0,ALIGN_FLEX_START:1,ALIGN_CENTER:2,ALIGN_FLEX_END:3,ALIGN_STRETCH:4,ALIGN_BASELINE:5,ALIGN_SPACE_BETWEEN:6,ALIGN_SPACE_AROUND:7,DIMENSION_COUNT:2,DIMENSION_WIDTH:0,DIMENSION_HEIGHT:1,DIRECTION_COUNT:3,DIRECTION_INHERIT:0,DIRECTION_LTR:1,DIRECTION_RTL:2,DISPLAY_COUNT:2,DISPLAY_FLEX:0,DISPLAY_NONE:1,EDGE_COUNT:9,EDGE_LEFT:0,EDGE_TOP:1,EDGE_RIGHT:2,EDGE_BOTTOM:3,EDGE_START:4,EDGE_END:5,EDGE_HORIZONTAL:6,EDGE_VERTICAL:7,EDGE_ALL:8,EXPERIMENTAL_FEATURE_COUNT:1,EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS:0,FLEX_DIRECTION_COUNT:4,FLEX_DIRECTION_COLUMN:0,FLEX_DIRECTION_COLUMN_REVERSE:1,FLEX_DIRECTION_ROW:2,FLEX_DIRECTION_ROW_REVERSE:3,JUSTIFY_COUNT:6,JUSTIFY_FLEX_START:0,JUSTIFY_CENTER:1,JUSTIFY_FLEX_END:2,JUSTIFY_SPACE_BETWEEN:3,JUSTIFY_SPACE_AROUND:4,JUSTIFY_SPACE_EVENLY:5,LOG_LEVEL_COUNT:6,LOG_LEVEL_ERROR:0,LOG_LEVEL_WARN:1,LOG_LEVEL_INFO:2,LOG_LEVEL_DEBUG:3,LOG_LEVEL_VERBOSE:4,LOG_LEVEL_FATAL:5,MEASURE_MODE_COUNT:3,MEASURE_MODE_UNDEFINED:0,MEASURE_MODE_EXACTLY:1,MEASURE_MODE_AT_MOST:2,NODE_TYPE_COUNT:2,NODE_TYPE_DEFAULT:0,NODE_TYPE_TEXT:1,OVERFLOW_COUNT:3,OVERFLOW_VISIBLE:0,OVERFLOW_HIDDEN:1,OVERFLOW_SCROLL:2,POSITION_TYPE_COUNT:2,POSITION_TYPE_RELATIVE:0,POSITION_TYPE_ABSOLUTE:1,PRINT_OPTIONS_COUNT:3,PRINT_OPTIONS_LAYOUT:1,PRINT_OPTIONS_STYLE:2,PRINT_OPTIONS_CHILDREN:4,UNIT_COUNT:4,UNIT_UNDEFINED:0,UNIT_POINT:1,UNIT_PERCENT:2,UNIT_AUTO:3,WRAP_COUNT:3,WRAP_NO_WRAP:0,WRAP_WRAP:1,WRAP_WRAP_REVERSE:2};Dwe.exports=Spt});var Qwe=_((DKt,kwe)=>{"use strict";var Dpt=Object.assign||function(t){for(var e=1;e"}}]),t}(),Pwe=function(){FF(t,null,[{key:"fromJS",value:function(r){var s=r.width,a=r.height;return new t(s,a)}}]);function t(e,r){J9(this,t),this.width=e,this.height=r}return FF(t,[{key:"fromJS",value:function(r){r(this.width,this.height)}},{key:"toString",value:function(){return""}}]),t}(),xwe=function(){function t(e,r){J9(this,t),this.unit=e,this.value=r}return FF(t,[{key:"fromJS",value:function(r){r(this.unit,this.value)}},{key:"toString",value:function(){switch(this.unit){case tf.UNIT_POINT:return String(this.value);case tf.UNIT_PERCENT:return this.value+"%";case tf.UNIT_AUTO:return"auto";default:return this.value+"?"}}},{key:"valueOf",value:function(){return this.value}}]),t}();kwe.exports=function(t,e){function r(c,f,p){var h=c[f];c[f]=function(){for(var E=arguments.length,C=Array(E),S=0;S1?C-1:0),P=1;P1&&arguments[1]!==void 0?arguments[1]:NaN,p=arguments.length>2&&arguments[2]!==void 0?arguments[2]:NaN,h=arguments.length>3&&arguments[3]!==void 0?arguments[3]:tf.DIRECTION_LTR;return c.call(this,f,p,h)}),Dpt({Config:e.Config,Node:e.Node,Layout:t("Layout",bpt),Size:t("Size",Pwe),Value:t("Value",xwe),getInstanceCount:function(){return e.getInstanceCount.apply(e,arguments)}},tf)}});var Twe=_((exports,module)=>{(function(t,e){typeof define=="function"&&define.amd?define([],function(){return e}):typeof module=="object"&&module.exports?module.exports=e:(t.nbind=t.nbind||{}).init=e})(exports,function(Module,cb){typeof Module=="function"&&(cb=Module,Module={}),Module.onRuntimeInitialized=function(t,e){return function(){t&&t.apply(this,arguments);try{Module.ccall("nbind_init")}catch(r){e(r);return}e(null,{bind:Module._nbind_value,reflect:Module.NBind.reflect,queryType:Module.NBind.queryType,toggleLightGC:Module.toggleLightGC,lib:Module})}}(Module.onRuntimeInitialized,cb);var Module;Module||(Module=(typeof Module<"u"?Module:null)||{});var moduleOverrides={};for(var key in Module)Module.hasOwnProperty(key)&&(moduleOverrides[key]=Module[key]);var ENVIRONMENT_IS_WEB=!1,ENVIRONMENT_IS_WORKER=!1,ENVIRONMENT_IS_NODE=!1,ENVIRONMENT_IS_SHELL=!1;if(Module.ENVIRONMENT)if(Module.ENVIRONMENT==="WEB")ENVIRONMENT_IS_WEB=!0;else if(Module.ENVIRONMENT==="WORKER")ENVIRONMENT_IS_WORKER=!0;else if(Module.ENVIRONMENT==="NODE")ENVIRONMENT_IS_NODE=!0;else if(Module.ENVIRONMENT==="SHELL")ENVIRONMENT_IS_SHELL=!0;else throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");else ENVIRONMENT_IS_WEB=typeof window=="object",ENVIRONMENT_IS_WORKER=typeof importScripts=="function",ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof Ie=="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER,ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){Module.print||(Module.print=console.log),Module.printErr||(Module.printErr=console.warn);var nodeFS,nodePath;Module.read=function(e,r){nodeFS||(nodeFS={}("")),nodePath||(nodePath={}("")),e=nodePath.normalize(e);var s=nodeFS.readFileSync(e);return r?s:s.toString()},Module.readBinary=function(e){var r=Module.read(e,!0);return r.buffer||(r=new Uint8Array(r)),assert(r.buffer),r},Module.load=function(e){globalEval(read(e))},Module.thisProgram||(process.argv.length>1?Module.thisProgram=process.argv[1].replace(/\\/g,"/"):Module.thisProgram="unknown-program"),Module.arguments=process.argv.slice(2),typeof module<"u"&&(module.exports=Module),Module.inspect=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL)Module.print||(Module.print=print),typeof printErr<"u"&&(Module.printErr=printErr),typeof read<"u"?Module.read=read:Module.read=function(){throw"no read() available"},Module.readBinary=function(e){if(typeof readbuffer=="function")return new Uint8Array(readbuffer(e));var r=read(e,"binary");return assert(typeof r=="object"),r},typeof scriptArgs<"u"?Module.arguments=scriptArgs:typeof arguments<"u"&&(Module.arguments=arguments),typeof quit=="function"&&(Module.quit=function(t,e){quit(t)});else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(Module.read=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},ENVIRONMENT_IS_WORKER&&(Module.readBinary=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),Module.readAsync=function(e,r,s){var a=new XMLHttpRequest;a.open("GET",e,!0),a.responseType="arraybuffer",a.onload=function(){a.status==200||a.status==0&&a.response?r(a.response):s()},a.onerror=s,a.send(null)},typeof arguments<"u"&&(Module.arguments=arguments),typeof console<"u")Module.print||(Module.print=function(e){console.log(e)}),Module.printErr||(Module.printErr=function(e){console.warn(e)});else{var TRY_USE_DUMP=!1;Module.print||(Module.print=TRY_USE_DUMP&&typeof dump<"u"?function(t){dump(t)}:function(t){})}ENVIRONMENT_IS_WORKER&&(Module.load=importScripts),typeof Module.setWindowTitle>"u"&&(Module.setWindowTitle=function(t){document.title=t})}else throw"Unknown runtime environment. Where are we?";function globalEval(t){eval.call(null,t)}!Module.load&&Module.read&&(Module.load=function(e){globalEval(Module.read(e))}),Module.print||(Module.print=function(){}),Module.printErr||(Module.printErr=Module.print),Module.arguments||(Module.arguments=[]),Module.thisProgram||(Module.thisProgram="./this.program"),Module.quit||(Module.quit=function(t,e){throw e}),Module.print=Module.print,Module.printErr=Module.printErr,Module.preRun=[],Module.postRun=[];for(var key in moduleOverrides)moduleOverrides.hasOwnProperty(key)&&(Module[key]=moduleOverrides[key]);moduleOverrides=void 0;var Runtime={setTempRet0:function(t){return tempRet0=t,t},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(t){STACKTOP=t},getNativeTypeSize:function(t){switch(t){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(t[t.length-1]==="*")return Runtime.QUANTUM_SIZE;if(t[0]==="i"){var e=parseInt(t.substr(1));return assert(e%8===0),e/8}else return 0}}},getNativeFieldSize:function(t){return Math.max(Runtime.getNativeTypeSize(t),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(t,e){return e==="double"||e==="i64"?t&7&&(assert((t&7)===4),t+=4):assert((t&3)===0),t},getAlignSize:function(t,e,r){return!r&&(t=="i64"||t=="double")?8:t?Math.min(e||(t?Runtime.getNativeFieldSize(t):0),Runtime.QUANTUM_SIZE):Math.min(e,8)},dynCall:function(t,e,r){return r&&r.length?Module["dynCall_"+t].apply(null,[e].concat(r)):Module["dynCall_"+t].call(null,e)},functionPointers:[],addFunction:function(t){for(var e=0;e>2],r=(e+t+15|0)&-16;if(HEAP32[DYNAMICTOP_PTR>>2]=r,r>=TOTAL_MEMORY){var s=enlargeMemory();if(!s)return HEAP32[DYNAMICTOP_PTR>>2]=e,0}return e},alignMemory:function(t,e){var r=t=Math.ceil(t/(e||16))*(e||16);return r},makeBigInt:function(t,e,r){var s=r?+(t>>>0)+ +(e>>>0)*4294967296:+(t>>>0)+ +(e|0)*4294967296;return s},GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module.Runtime=Runtime;var ABORT=0,EXITSTATUS=0;function assert(t,e){t||abort("Assertion failed: "+e)}function getCFunc(ident){var func=Module["_"+ident];if(!func)try{func=eval("_"+ident)}catch(t){}return assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)"),func}var cwrap,ccall;(function(){var JSfuncs={stackSave:function(){Runtime.stackSave()},stackRestore:function(){Runtime.stackRestore()},arrayToC:function(t){var e=Runtime.stackAlloc(t.length);return writeArrayToMemory(t,e),e},stringToC:function(t){var e=0;if(t!=null&&t!==0){var r=(t.length<<2)+1;e=Runtime.stackAlloc(r),stringToUTF8(t,e,r)}return e}},toC={string:JSfuncs.stringToC,array:JSfuncs.arrayToC};ccall=function(e,r,s,a,n){var c=getCFunc(e),f=[],p=0;if(a)for(var h=0;h>0]=e;break;case"i8":HEAP8[t>>0]=e;break;case"i16":HEAP16[t>>1]=e;break;case"i32":HEAP32[t>>2]=e;break;case"i64":tempI64=[e>>>0,(tempDouble=e,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[t>>2]=tempI64[0],HEAP32[t+4>>2]=tempI64[1];break;case"float":HEAPF32[t>>2]=e;break;case"double":HEAPF64[t>>3]=e;break;default:abort("invalid type for setValue: "+r)}}Module.setValue=setValue;function getValue(t,e,r){switch(e=e||"i8",e.charAt(e.length-1)==="*"&&(e="i32"),e){case"i1":return HEAP8[t>>0];case"i8":return HEAP8[t>>0];case"i16":return HEAP16[t>>1];case"i32":return HEAP32[t>>2];case"i64":return HEAP32[t>>2];case"float":return HEAPF32[t>>2];case"double":return HEAPF64[t>>3];default:abort("invalid type for setValue: "+e)}return null}Module.getValue=getValue;var ALLOC_NORMAL=0,ALLOC_STACK=1,ALLOC_STATIC=2,ALLOC_DYNAMIC=3,ALLOC_NONE=4;Module.ALLOC_NORMAL=ALLOC_NORMAL,Module.ALLOC_STACK=ALLOC_STACK,Module.ALLOC_STATIC=ALLOC_STATIC,Module.ALLOC_DYNAMIC=ALLOC_DYNAMIC,Module.ALLOC_NONE=ALLOC_NONE;function allocate(t,e,r,s){var a,n;typeof t=="number"?(a=!0,n=t):(a=!1,n=t.length);var c=typeof e=="string"?e:null,f;if(r==ALLOC_NONE?f=s:f=[typeof _malloc=="function"?_malloc:Runtime.staticAlloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][r===void 0?ALLOC_STATIC:r](Math.max(n,c?1:e.length)),a){var s=f,p;for(assert((f&3)==0),p=f+(n&-4);s>2]=0;for(p=f+n;s>0]=0;return f}if(c==="i8")return t.subarray||t.slice?HEAPU8.set(t,f):HEAPU8.set(new Uint8Array(t),f),f;for(var h=0,E,C,S;h>0],r|=s,!(s==0&&!e||(a++,e&&a==e)););e||(e=a);var n="";if(r<128){for(var c=1024,f;e>0;)f=String.fromCharCode.apply(String,HEAPU8.subarray(t,t+Math.min(e,c))),n=n?n+f:f,t+=c,e-=c;return n}return Module.UTF8ToString(t)}Module.Pointer_stringify=Pointer_stringify;function AsciiToString(t){for(var e="";;){var r=HEAP8[t++>>0];if(!r)return e;e+=String.fromCharCode(r)}}Module.AsciiToString=AsciiToString;function stringToAscii(t,e){return writeAsciiToMemory(t,e,!1)}Module.stringToAscii=stringToAscii;var UTF8Decoder=typeof TextDecoder<"u"?new TextDecoder("utf8"):void 0;function UTF8ArrayToString(t,e){for(var r=e;t[r];)++r;if(r-e>16&&t.subarray&&UTF8Decoder)return UTF8Decoder.decode(t.subarray(e,r));for(var s,a,n,c,f,p,h="";;){if(s=t[e++],!s)return h;if(!(s&128)){h+=String.fromCharCode(s);continue}if(a=t[e++]&63,(s&224)==192){h+=String.fromCharCode((s&31)<<6|a);continue}if(n=t[e++]&63,(s&240)==224?s=(s&15)<<12|a<<6|n:(c=t[e++]&63,(s&248)==240?s=(s&7)<<18|a<<12|n<<6|c:(f=t[e++]&63,(s&252)==248?s=(s&3)<<24|a<<18|n<<12|c<<6|f:(p=t[e++]&63,s=(s&1)<<30|a<<24|n<<18|c<<12|f<<6|p))),s<65536)h+=String.fromCharCode(s);else{var E=s-65536;h+=String.fromCharCode(55296|E>>10,56320|E&1023)}}}Module.UTF8ArrayToString=UTF8ArrayToString;function UTF8ToString(t){return UTF8ArrayToString(HEAPU8,t)}Module.UTF8ToString=UTF8ToString;function stringToUTF8Array(t,e,r,s){if(!(s>0))return 0;for(var a=r,n=r+s-1,c=0;c=55296&&f<=57343&&(f=65536+((f&1023)<<10)|t.charCodeAt(++c)&1023),f<=127){if(r>=n)break;e[r++]=f}else if(f<=2047){if(r+1>=n)break;e[r++]=192|f>>6,e[r++]=128|f&63}else if(f<=65535){if(r+2>=n)break;e[r++]=224|f>>12,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=2097151){if(r+3>=n)break;e[r++]=240|f>>18,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=67108863){if(r+4>=n)break;e[r++]=248|f>>24,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else{if(r+5>=n)break;e[r++]=252|f>>30,e[r++]=128|f>>24&63,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}}return e[r]=0,r-a}Module.stringToUTF8Array=stringToUTF8Array;function stringToUTF8(t,e,r){return stringToUTF8Array(t,HEAPU8,e,r)}Module.stringToUTF8=stringToUTF8;function lengthBytesUTF8(t){for(var e=0,r=0;r=55296&&s<=57343&&(s=65536+((s&1023)<<10)|t.charCodeAt(++r)&1023),s<=127?++e:s<=2047?e+=2:s<=65535?e+=3:s<=2097151?e+=4:s<=67108863?e+=5:e+=6}return e}Module.lengthBytesUTF8=lengthBytesUTF8;var UTF16Decoder=typeof TextDecoder<"u"?new TextDecoder("utf-16le"):void 0;function demangle(t){var e=Module.___cxa_demangle||Module.__cxa_demangle;if(e){try{var r=t.substr(1),s=lengthBytesUTF8(r)+1,a=_malloc(s);stringToUTF8(r,a,s);var n=_malloc(4),c=e(a,0,0,n);if(getValue(n,"i32")===0&&c)return Pointer_stringify(c)}catch{}finally{a&&_free(a),n&&_free(n),c&&_free(c)}return t}return Runtime.warnOnce("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling"),t}function demangleAll(t){var e=/__Z[\w\d_]+/g;return t.replace(e,function(r){var s=demangle(r);return r===s?r:r+" ["+s+"]"})}function jsStackTrace(){var t=new Error;if(!t.stack){try{throw new Error(0)}catch(e){t=e}if(!t.stack)return"(no stack trace available)"}return t.stack.toString()}function stackTrace(){var t=jsStackTrace();return Module.extraStackTrace&&(t+=` +`+Module.extraStackTrace()),demangleAll(t)}Module.stackTrace=stackTrace;var HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferViews(){Module.HEAP8=HEAP8=new Int8Array(buffer),Module.HEAP16=HEAP16=new Int16Array(buffer),Module.HEAP32=HEAP32=new Int32Array(buffer),Module.HEAPU8=HEAPU8=new Uint8Array(buffer),Module.HEAPU16=HEAPU16=new Uint16Array(buffer),Module.HEAPU32=HEAPU32=new Uint32Array(buffer),Module.HEAPF32=HEAPF32=new Float32Array(buffer),Module.HEAPF64=HEAPF64=new Float64Array(buffer)}var STATIC_BASE,STATICTOP,staticSealed,STACK_BASE,STACKTOP,STACK_MAX,DYNAMIC_BASE,DYNAMICTOP_PTR;STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0,staticSealed=!1;function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module.TOTAL_STACK||5242880,TOTAL_MEMORY=Module.TOTAL_MEMORY||134217728;TOTAL_MEMORY0;){var e=t.shift();if(typeof e=="function"){e();continue}var r=e.func;typeof r=="number"?e.arg===void 0?Module.dynCall_v(r):Module.dynCall_vi(r,e.arg):r(e.arg===void 0?null:e.arg)}}var __ATPRERUN__=[],__ATINIT__=[],__ATMAIN__=[],__ATEXIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1,runtimeExited=!1;function preRun(){if(Module.preRun)for(typeof Module.preRun=="function"&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){runtimeInitialized||(runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__))}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__),runtimeExited=!0}function postRun(){if(Module.postRun)for(typeof Module.postRun=="function"&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(t){__ATPRERUN__.unshift(t)}Module.addOnPreRun=addOnPreRun;function addOnInit(t){__ATINIT__.unshift(t)}Module.addOnInit=addOnInit;function addOnPreMain(t){__ATMAIN__.unshift(t)}Module.addOnPreMain=addOnPreMain;function addOnExit(t){__ATEXIT__.unshift(t)}Module.addOnExit=addOnExit;function addOnPostRun(t){__ATPOSTRUN__.unshift(t)}Module.addOnPostRun=addOnPostRun;function intArrayFromString(t,e,r){var s=r>0?r:lengthBytesUTF8(t)+1,a=new Array(s),n=stringToUTF8Array(t,a,0,a.length);return e&&(a.length=n),a}Module.intArrayFromString=intArrayFromString;function intArrayToString(t){for(var e=[],r=0;r255&&(s&=255),e.push(String.fromCharCode(s))}return e.join("")}Module.intArrayToString=intArrayToString;function writeStringToMemory(t,e,r){Runtime.warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!");var s,a;r&&(a=e+lengthBytesUTF8(t),s=HEAP8[a]),stringToUTF8(t,e,1/0),r&&(HEAP8[a]=s)}Module.writeStringToMemory=writeStringToMemory;function writeArrayToMemory(t,e){HEAP8.set(t,e)}Module.writeArrayToMemory=writeArrayToMemory;function writeAsciiToMemory(t,e,r){for(var s=0;s>0]=t.charCodeAt(s);r||(HEAP8[e>>0]=0)}if(Module.writeAsciiToMemory=writeAsciiToMemory,(!Math.imul||Math.imul(4294967295,5)!==-5)&&(Math.imul=function t(e,r){var s=e>>>16,a=e&65535,n=r>>>16,c=r&65535;return a*c+(s*c+a*n<<16)|0}),Math.imul=Math.imul,!Math.fround){var froundBuffer=new Float32Array(1);Math.fround=function(t){return froundBuffer[0]=t,froundBuffer[0]}}Math.fround=Math.fround,Math.clz32||(Math.clz32=function(t){t=t>>>0;for(var e=0;e<32;e++)if(t&1<<31-e)return e;return 32}),Math.clz32=Math.clz32,Math.trunc||(Math.trunc=function(t){return t<0?Math.ceil(t):Math.floor(t)}),Math.trunc=Math.trunc;var Math_abs=Math.abs,Math_cos=Math.cos,Math_sin=Math.sin,Math_tan=Math.tan,Math_acos=Math.acos,Math_asin=Math.asin,Math_atan=Math.atan,Math_atan2=Math.atan2,Math_exp=Math.exp,Math_log=Math.log,Math_sqrt=Math.sqrt,Math_ceil=Math.ceil,Math_floor=Math.floor,Math_pow=Math.pow,Math_imul=Math.imul,Math_fround=Math.fround,Math_round=Math.round,Math_min=Math.min,Math_clz32=Math.clz32,Math_trunc=Math.trunc,runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function getUniqueRunDependency(t){return t}function addRunDependency(t){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}Module.addRunDependency=addRunDependency;function removeRunDependency(t){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),runDependencies==0&&(runDependencyWatcher!==null&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var e=dependenciesFulfilled;dependenciesFulfilled=null,e()}}Module.removeRunDependency=removeRunDependency,Module.preloadedImages={},Module.preloadedAudios={};var ASM_CONSTS=[function(t,e,r,s,a,n,c,f){return _nbind.callbackSignatureList[t].apply(this,arguments)}];function _emscripten_asm_const_iiiiiiii(t,e,r,s,a,n,c,f){return ASM_CONSTS[t](e,r,s,a,n,c,f)}function _emscripten_asm_const_iiiii(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiidddddd(t,e,r,s,a,n,c,f,p){return ASM_CONSTS[t](e,r,s,a,n,c,f,p)}function _emscripten_asm_const_iiididi(t,e,r,s,a,n,c){return ASM_CONSTS[t](e,r,s,a,n,c)}function _emscripten_asm_const_iiii(t,e,r,s){return ASM_CONSTS[t](e,r,s)}function _emscripten_asm_const_iiiid(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiiiii(t,e,r,s,a,n){return ASM_CONSTS[t](e,r,s,a,n)}STATIC_BASE=Runtime.GLOBAL_BASE,STATICTOP=STATIC_BASE+12800,__ATINIT__.push({func:function(){__GLOBAL__sub_I_Yoga_cpp()}},{func:function(){__GLOBAL__sub_I_nbind_cc()}},{func:function(){__GLOBAL__sub_I_common_cc()}},{func:function(){__GLOBAL__sub_I_Binding_cc()}}),allocate([0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,192,127,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,0,128,191,0,0,128,191,0,0,192,127,0,0,0,0,0,0,0,0,0,0,128,63,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,190,12,0,0,200,12,0,0,208,12,0,0,216,12,0,0,230,12,0,0,242,12,0,0,1,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,192,127,3,0,0,0,180,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,182,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,183,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,184,45,0,0,185,45,0,0,181,45,0,0,181,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,148,4,0,0,3,0,0,0,187,45,0,0,164,4,0,0,188,45,0,0,2,0,0,0,189,45,0,0,164,4,0,0,188,45,0,0,185,45,0,0,164,4,0,0,185,45,0,0,164,4,0,0,188,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,6,0,0,0,1,0,0,0,7,0,0,0,183,45,0,0,182,45,0,0,181,45,0,0,190,45,0,0,190,45,0,0,182,45,0,0,182,45,0,0,185,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,185,45,0,0,48,5,0,0,3,0,0,0,56,5,0,0,1,0,0,0,189,45,0,0,185,45,0,0,164,4,0,0,76,5,0,0,2,0,0,0,191,45,0,0,186,45,0,0,182,45,0,0,185,45,0,0,192,45,0,0,185,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,76,5,0,0,76,5,0,0,136,5,0,0,182,45,0,0,181,45,0,0,2,0,0,0,190,45,0,0,136,5,0,0,56,19,0,0,156,5,0,0,2,0,0,0,184,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,8,0,0,0,9,0,0,0,1,0,0,0,10,0,0,0,204,5,0,0,181,45,0,0,181,45,0,0,2,0,0,0,180,45,0,0,204,5,0,0,2,0,0,0,195,45,0,0,236,5,0,0,97,19,0,0,198,45,0,0,211,45,0,0,212,45,0,0,213,45,0,0,214,45,0,0,215,45,0,0,188,45,0,0,182,45,0,0,216,45,0,0,217,45,0,0,218,45,0,0,219,45,0,0,192,45,0,0,181,45,0,0,0,0,0,0,185,45,0,0,110,19,0,0,186,45,0,0,115,19,0,0,221,45,0,0,120,19,0,0,148,4,0,0,132,19,0,0,96,6,0,0,145,19,0,0,222,45,0,0,164,19,0,0,223,45,0,0,173,19,0,0,0,0,0,0,3,0,0,0,104,6,0,0,1,0,0,0,187,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,11,0,0,0,12,0,0,0,1,0,0,0,13,0,0,0,185,45,0,0,224,45,0,0,164,6,0,0,188,45,0,0,172,6,0,0,180,6,0,0,2,0,0,0,188,6,0,0,7,0,0,0,224,45,0,0,7,0,0,0,164,6,0,0,1,0,0,0,213,45,0,0,185,45,0,0,224,45,0,0,172,6,0,0,185,45,0,0,224,45,0,0,164,6,0,0,185,45,0,0,224,45,0,0,211,45,0,0,211,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,172,6,0,0,222,45,0,0,211,45,0,0,224,45,0,0,188,45,0,0,222,45,0,0,211,45,0,0,40,7,0,0,188,45,0,0,2,0,0,0,224,45,0,0,185,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,222,45,0,0,224,45,0,0,148,4,0,0,185,45,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,185,45,0,0,164,6,0,0,148,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,14,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,148,7,0,0,2,0,0,0,225,45,0,0,183,45,0,0,188,45,0,0,168,7,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,234,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,9,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,2,0,0,0,242,45,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,110,111,100,101,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,119,104,105,99,104,32,115,116,105,108,108,32,104,97,115,32,99,104,105,108,100,114,101,110,32,97,116,116,97,99,104,101,100,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,115,116,105,108,108,32,97,116,116,97,99,104,101,100,32,116,111,32,97,32,112,97,114,101,110,116,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,99,111,110,102,105,103,0,67,97,110,110,111,116,32,115,101,116,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,67,104,105,108,100,32,97,108,114,101,97,100,121,32,104,97,115,32,97,32,112,97,114,101,110,116,44,32,105,116,32,109,117,115,116,32,98,101,32,114,101,109,111,118,101,100,32,102,105,114,115,116,46,0,67,97,110,110,111,116,32,97,100,100,32,99,104,105,108,100,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,79,110,108,121,32,108,101,97,102,32,110,111,100,101,115,32,119,105,116,104,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,115,104,111,117,108,100,32,109,97,110,117,97,108,108,121,32,109,97,114,107,32,116,104,101,109,115,101,108,118,101,115,32,97,115,32,100,105,114,116,121,0,67,97,110,110,111,116,32,103,101,116,32,108,97,121,111,117,116,32,112,114,111,112,101,114,116,105,101,115,32,111,102,32,109,117,108,116,105,45,101,100,103,101,32,115,104,111,114,116,104,97,110,100,115,0,37,115,37,100,46,123,91,115,107,105,112,112,101,100,93,32,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,61,62,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,37,115,37,100,46,123,37,115,0,42,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,37,115,10,0,37,115,37,100,46,125,37,115,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,79,117,116,32,111,102,32,99,97,99,104,101,32,101,110,116,114,105,101,115,33,10,0,83,99,97,108,101,32,102,97,99,116,111,114,32,115,104,111,117,108,100,32,110,111,116,32,98,101,32,108,101,115,115,32,116,104,97,110,32,122,101,114,111,0,105,110,105,116,105,97,108,0,37,115,10,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,0,85,78,68,69,70,73,78,69,68,0,69,88,65,67,84,76,89,0,65,84,95,77,79,83,84,0,76,65,89,95,85,78,68,69,70,73,78,69,68,0,76,65,89,95,69,88,65,67,84,76,89,0,76,65,89,95,65,84,95,77,79,83,84,0,97,118,97,105,108,97,98,108,101,87,105,100,116,104,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,119,105,100,116,104,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,97,118,97,105,108,97,98,108,101,72,101,105,103,104,116,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,104,101,105,103,104,116,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,102,108,101,120,0,115,116,114,101,116,99,104,0,109,117,108,116,105,108,105,110,101,45,115,116,114,101,116,99,104,0,69,120,112,101,99,116,101,100,32,110,111,100,101,32,116,111,32,104,97,118,101,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,0,109,101,97,115,117,114,101,0,69,120,112,101,99,116,32,99,117,115,116,111,109,32,98,97,115,101,108,105,110,101,32,102,117,110,99,116,105,111,110,32,116,111,32,110,111,116,32,114,101,116,117,114,110,32,78,97,78,0,97,98,115,45,109,101,97,115,117,114,101,0,97,98,115,45,108,97,121,111,117,116,0,78,111,100,101,0,99,114,101,97,116,101,68,101,102,97,117,108,116,0,99,114,101,97,116,101,87,105,116,104,67,111,110,102,105,103,0,100,101,115,116,114,111,121,0,114,101,115,101,116,0,99,111,112,121,83,116,121,108,101,0,115,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,115,101,116,80,111,115,105,116,105,111,110,0,115,101,116,80,111,115,105,116,105,111,110,80,101,114,99,101,110,116,0,115,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,115,101,116,65,108,105,103,110,73,116,101,109,115,0,115,101,116,65,108,105,103,110,83,101,108,102,0,115,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,115,101,116,70,108,101,120,87,114,97,112,0,115,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,115,101,116,77,97,114,103,105,110,0,115,101,116,77,97,114,103,105,110,80,101,114,99,101,110,116,0,115,101,116,77,97,114,103,105,110,65,117,116,111,0,115,101,116,79,118,101,114,102,108,111,119,0,115,101,116,68,105,115,112,108,97,121,0,115,101,116,70,108,101,120,0,115,101,116,70,108,101,120,66,97,115,105,115,0,115,101,116,70,108,101,120,66,97,115,105,115,80,101,114,99,101,110,116,0,115,101,116,70,108,101,120,71,114,111,119,0,115,101,116,70,108,101,120,83,104,114,105,110,107,0,115,101,116,87,105,100,116,104,0,115,101,116,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,87,105,100,116,104,65,117,116,111,0,115,101,116,72,101,105,103,104,116,0,115,101,116,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,72,101,105,103,104,116,65,117,116,111,0,115,101,116,77,105,110,87,105,100,116,104,0,115,101,116,77,105,110,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,105,110,72,101,105,103,104,116,0,115,101,116,77,105,110,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,77,97,120,87,105,100,116,104,0,115,101,116,77,97,120,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,97,120,72,101,105,103,104,116,0,115,101,116,77,97,120,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,65,115,112,101,99,116,82,97,116,105,111,0,115,101,116,66,111,114,100,101,114,0,115,101,116,80,97,100,100,105,110,103,0,115,101,116,80,97,100,100,105,110,103,80,101,114,99,101,110,116,0,103,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,103,101,116,80,111,115,105,116,105,111,110,0,103,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,103,101,116,65,108,105,103,110,73,116,101,109,115,0,103,101,116,65,108,105,103,110,83,101,108,102,0,103,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,103,101,116,70,108,101,120,87,114,97,112,0,103,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,103,101,116,77,97,114,103,105,110,0,103,101,116,70,108,101,120,66,97,115,105,115,0,103,101,116,70,108,101,120,71,114,111,119,0,103,101,116,70,108,101,120,83,104,114,105,110,107,0,103,101,116,87,105,100,116,104,0,103,101,116,72,101,105,103,104,116,0,103,101,116,77,105,110,87,105,100,116,104,0,103,101,116,77,105,110,72,101,105,103,104,116,0,103,101,116,77,97,120,87,105,100,116,104,0,103,101,116,77,97,120,72,101,105,103,104,116,0,103,101,116,65,115,112,101,99,116,82,97,116,105,111,0,103,101,116,66,111,114,100,101,114,0,103,101,116,79,118,101,114,102,108,111,119,0,103,101,116,68,105,115,112,108,97,121,0,103,101,116,80,97,100,100,105,110,103,0,105,110,115,101,114,116,67,104,105,108,100,0,114,101,109,111,118,101,67,104,105,108,100,0,103,101,116,67,104,105,108,100,67,111,117,110,116,0,103,101,116,80,97,114,101,110,116,0,103,101,116,67,104,105,108,100,0,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,117,110,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,109,97,114,107,68,105,114,116,121,0,105,115,68,105,114,116,121,0,99,97,108,99,117,108,97,116,101,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,76,101,102,116,0,103,101,116,67,111,109,112,117,116,101,100,82,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,84,111,112,0,103,101,116,67,111,109,112,117,116,101,100,66,111,116,116,111,109,0,103,101,116,67,111,109,112,117,116,101,100,87,105,100,116,104,0,103,101,116,67,111,109,112,117,116,101,100,72,101,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,77,97,114,103,105,110,0,103,101,116,67,111,109,112,117,116,101,100,66,111,114,100,101,114,0,103,101,116,67,111,109,112,117,116,101,100,80,97,100,100,105,110,103,0,67,111,110,102,105,103,0,99,114,101,97,116,101,0,115,101,116,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,115,101,116,80,111,105,110,116,83,99,97,108,101,70,97,99,116,111,114,0,105,115,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,86,97,108,117,101,0,76,97,121,111,117,116,0,83,105,122,101,0,103,101,116,73,110,115,116,97,110,99,101,67,111,117,110,116,0,73,110,116,54,52,0,1,1,1,2,2,4,4,4,4,8,8,4,8,118,111,105,100,0,98,111,111,108,0,115,116,100,58,58,115,116,114,105,110,103,0,99,98,70,117,110,99,116,105,111,110,32,38,0,99,111,110,115,116,32,99,98,70,117,110,99,116,105,111,110,32,38,0,69,120,116,101,114,110,97,108,0,66,117,102,102,101,114,0,78,66,105,110,100,73,68,0,78,66,105,110,100,0,98,105,110,100,95,118,97,108,117,101,0,114,101,102,108,101,99,116,0,113,117,101,114,121,84,121,112,101,0,108,97,108,108,111,99,0,108,114,101,115,101,116,0,123,114,101,116,117,114,110,40,95,110,98,105,110,100,46,99,97,108,108,98,97,99,107,83,105,103,110,97,116,117,114,101,76,105,115,116,91,36,48,93,46,97,112,112,108,121,40,116,104,105,115,44,97,114,103,117,109,101,110,116,115,41,41,59,125,0,95,110,98,105,110,100,95,110,101,119,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,46,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=STATICTOP;STATICTOP+=16;function _atexit(t,e){__ATEXIT__.unshift({func:t,arg:e})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}function _abort(){Module.abort()}function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj(){Module.printErr("missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj"),abort(-1)}function __decorate(t,e,r,s){var a=arguments.length,n=a<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,r):s,c;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(t,e,r,s);else for(var f=t.length-1;f>=0;f--)(c=t[f])&&(n=(a<3?c(n):a>3?c(e,r,n):c(e,r))||n);return a>3&&n&&Object.defineProperty(e,r,n),n}function _defineHidden(t){return function(e,r){Object.defineProperty(e,r,{configurable:!1,enumerable:!1,value:t,writable:!0})}}var _nbind={};function __nbind_free_external(t){_nbind.externalList[t].dereference(t)}function __nbind_reference_external(t){_nbind.externalList[t].reference()}function _llvm_stackrestore(t){var e=_llvm_stacksave,r=e.LLVM_SAVEDSTACKS[t];e.LLVM_SAVEDSTACKS.splice(t,1),Runtime.stackRestore(r)}function __nbind_register_pool(t,e,r,s){_nbind.Pool.pageSize=t,_nbind.Pool.usedPtr=e/4,_nbind.Pool.rootPtr=r,_nbind.Pool.pagePtr=s/4,HEAP32[e/4]=16909060,HEAP8[e]==1&&(_nbind.bigEndian=!0),HEAP32[e/4]=0,_nbind.makeTypeKindTbl=(n={},n[1024]=_nbind.PrimitiveType,n[64]=_nbind.Int64Type,n[2048]=_nbind.BindClass,n[3072]=_nbind.BindClassPtr,n[4096]=_nbind.SharedClassPtr,n[5120]=_nbind.ArrayType,n[6144]=_nbind.ArrayType,n[7168]=_nbind.CStringType,n[9216]=_nbind.CallbackType,n[10240]=_nbind.BindType,n),_nbind.makeTypeNameTbl={Buffer:_nbind.BufferType,External:_nbind.ExternalType,Int64:_nbind.Int64Type,_nbind_new:_nbind.CreateValueType,bool:_nbind.BooleanType,"cbFunction &":_nbind.CallbackType,"const cbFunction &":_nbind.CallbackType,"const std::string &":_nbind.StringType,"std::string":_nbind.StringType},Module.toggleLightGC=_nbind.toggleLightGC,_nbind.callUpcast=Module.dynCall_ii;var a=_nbind.makeType(_nbind.constructType,{flags:2048,id:0,name:""});a.proto=Module,_nbind.BindClass.list.push(a);var n}function _emscripten_set_main_loop_timing(t,e){if(Browser.mainLoop.timingMode=t,Browser.mainLoop.timingValue=e,!Browser.mainLoop.func)return 1;if(t==0)Browser.mainLoop.scheduler=function(){var c=Math.max(0,Browser.mainLoop.tickStartTime+e-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,c)},Browser.mainLoop.method="timeout";else if(t==1)Browser.mainLoop.scheduler=function(){Browser.requestAnimationFrame(Browser.mainLoop.runner)},Browser.mainLoop.method="rAF";else if(t==2){if(!window.setImmediate){let n=function(c){c.source===window&&c.data===s&&(c.stopPropagation(),r.shift()())};var a=n,r=[],s="setimmediate";window.addEventListener("message",n,!0),window.setImmediate=function(f){r.push(f),ENVIRONMENT_IS_WORKER?(Module.setImmediates===void 0&&(Module.setImmediates=[]),Module.setImmediates.push(f),window.postMessage({target:s})):window.postMessage(s,"*")}}Browser.mainLoop.scheduler=function(){window.setImmediate(Browser.mainLoop.runner)},Browser.mainLoop.method="immediate"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(t,e,r,s,a){Module.noExitRuntime=!0,assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters."),Browser.mainLoop.func=t,Browser.mainLoop.arg=s;var n;typeof s<"u"?n=function(){Module.dynCall_vi(t,s)}:n=function(){Module.dynCall_v(t)};var c=Browser.mainLoop.currentlyRunningMainloop;if(Browser.mainLoop.runner=function(){if(!ABORT){if(Browser.mainLoop.queue.length>0){var p=Date.now(),h=Browser.mainLoop.queue.shift();if(h.func(h.arg),Browser.mainLoop.remainingBlockers){var E=Browser.mainLoop.remainingBlockers,C=E%1==0?E-1:Math.floor(E);h.counted?Browser.mainLoop.remainingBlockers=C:(C=C+.5,Browser.mainLoop.remainingBlockers=(8*E+C)/9)}if(console.log('main loop blocker "'+h.name+'" took '+(Date.now()-p)+" ms"),Browser.mainLoop.updateStatus(),c1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else Browser.mainLoop.timingMode==0&&(Browser.mainLoop.tickStartTime=_emscripten_get_now());Browser.mainLoop.method==="timeout"&&Module.ctx&&(Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!"),Browser.mainLoop.method=""),Browser.mainLoop.runIter(n),!(c0?_emscripten_set_main_loop_timing(0,1e3/e):_emscripten_set_main_loop_timing(1,1),Browser.mainLoop.scheduler()),r)throw"SimulateInfiniteLoop"}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null,Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var t=Browser.mainLoop.timingMode,e=Browser.mainLoop.timingValue,r=Browser.mainLoop.func;Browser.mainLoop.func=null,_emscripten_set_main_loop(r,0,!1,Browser.mainLoop.arg,!0),_emscripten_set_main_loop_timing(t,e),Browser.mainLoop.scheduler()},updateStatus:function(){if(Module.setStatus){var t=Module.statusMessage||"Please wait...",e=Browser.mainLoop.remainingBlockers,r=Browser.mainLoop.expectedBlockers;e?e"u"&&(console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available."),Module.noImageDecoding=!0);var t={};t.canHandle=function(n){return!Module.noImageDecoding&&/\.(jpg|jpeg|png|bmp)$/i.test(n)},t.handle=function(n,c,f,p){var h=null;if(Browser.hasBlobConstructor)try{h=new Blob([n],{type:Browser.getMimetype(c)}),h.size!==n.length&&(h=new Blob([new Uint8Array(n).buffer],{type:Browser.getMimetype(c)}))}catch(P){Runtime.warnOnce("Blob constructor present but fails: "+P+"; falling back to blob builder")}if(!h){var E=new Browser.BlobBuilder;E.append(new Uint8Array(n).buffer),h=E.getBlob()}var C=Browser.URLObject.createObjectURL(h),S=new Image;S.onload=function(){assert(S.complete,"Image "+c+" could not be decoded");var I=document.createElement("canvas");I.width=S.width,I.height=S.height;var R=I.getContext("2d");R.drawImage(S,0,0),Module.preloadedImages[c]=I,Browser.URLObject.revokeObjectURL(C),f&&f(n)},S.onerror=function(I){console.log("Image "+C+" could not be decoded"),p&&p()},S.src=C},Module.preloadPlugins.push(t);var e={};e.canHandle=function(n){return!Module.noAudioDecoding&&n.substr(-4)in{".ogg":1,".wav":1,".mp3":1}},e.handle=function(n,c,f,p){var h=!1;function E(R){h||(h=!0,Module.preloadedAudios[c]=R,f&&f(n))}function C(){h||(h=!0,Module.preloadedAudios[c]=new Audio,p&&p())}if(Browser.hasBlobConstructor){try{var S=new Blob([n],{type:Browser.getMimetype(c)})}catch{return C()}var P=Browser.URLObject.createObjectURL(S),I=new Audio;I.addEventListener("canplaythrough",function(){E(I)},!1),I.onerror=function(N){if(h)return;console.log("warning: browser could not fully decode audio "+c+", trying slower base64 approach");function U(W){for(var ee="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",ie="=",ue="",le=0,me=0,pe=0;pe=6;){var Be=le>>me-6&63;me-=6,ue+=ee[Be]}return me==2?(ue+=ee[(le&3)<<4],ue+=ie+ie):me==4&&(ue+=ee[(le&15)<<2],ue+=ie),ue}I.src="data:audio/x-"+c.substr(-3)+";base64,"+U(n),E(I)},I.src=P,Browser.safeSetTimeout(function(){E(I)},1e4)}else return C()},Module.preloadPlugins.push(e);function r(){Browser.pointerLock=document.pointerLockElement===Module.canvas||document.mozPointerLockElement===Module.canvas||document.webkitPointerLockElement===Module.canvas||document.msPointerLockElement===Module.canvas}var s=Module.canvas;s&&(s.requestPointerLock=s.requestPointerLock||s.mozRequestPointerLock||s.webkitRequestPointerLock||s.msRequestPointerLock||function(){},s.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||document.webkitExitPointerLock||document.msExitPointerLock||function(){},s.exitPointerLock=s.exitPointerLock.bind(document),document.addEventListener("pointerlockchange",r,!1),document.addEventListener("mozpointerlockchange",r,!1),document.addEventListener("webkitpointerlockchange",r,!1),document.addEventListener("mspointerlockchange",r,!1),Module.elementPointerLock&&s.addEventListener("click",function(a){!Browser.pointerLock&&Module.canvas.requestPointerLock&&(Module.canvas.requestPointerLock(),a.preventDefault())},!1))},createContext:function(t,e,r,s){if(e&&Module.ctx&&t==Module.canvas)return Module.ctx;var a,n;if(e){var c={antialias:!1,alpha:!1};if(s)for(var f in s)c[f]=s[f];n=GL.createContext(t,c),n&&(a=GL.getContext(n).GLctx)}else a=t.getContext("2d");return a?(r&&(e||assert(typeof GLctx>"u","cannot set in module if GLctx is used, but we are a non-GL context that would replace it"),Module.ctx=a,e&&GL.makeContextCurrent(n),Module.useWebGL=e,Browser.moduleContextCreatedCallbacks.forEach(function(p){p()}),Browser.init()),a):null},destroyContext:function(t,e,r){},fullscreenHandlersInstalled:!1,lockPointer:void 0,resizeCanvas:void 0,requestFullscreen:function(t,e,r){Browser.lockPointer=t,Browser.resizeCanvas=e,Browser.vrDevice=r,typeof Browser.lockPointer>"u"&&(Browser.lockPointer=!0),typeof Browser.resizeCanvas>"u"&&(Browser.resizeCanvas=!1),typeof Browser.vrDevice>"u"&&(Browser.vrDevice=null);var s=Module.canvas;function a(){Browser.isFullscreen=!1;var c=s.parentNode;(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===c?(s.exitFullscreen=document.exitFullscreen||document.cancelFullScreen||document.mozCancelFullScreen||document.msExitFullscreen||document.webkitCancelFullScreen||function(){},s.exitFullscreen=s.exitFullscreen.bind(document),Browser.lockPointer&&s.requestPointerLock(),Browser.isFullscreen=!0,Browser.resizeCanvas&&Browser.setFullscreenCanvasSize()):(c.parentNode.insertBefore(s,c),c.parentNode.removeChild(c),Browser.resizeCanvas&&Browser.setWindowedCanvasSize()),Module.onFullScreen&&Module.onFullScreen(Browser.isFullscreen),Module.onFullscreen&&Module.onFullscreen(Browser.isFullscreen),Browser.updateCanvasDimensions(s)}Browser.fullscreenHandlersInstalled||(Browser.fullscreenHandlersInstalled=!0,document.addEventListener("fullscreenchange",a,!1),document.addEventListener("mozfullscreenchange",a,!1),document.addEventListener("webkitfullscreenchange",a,!1),document.addEventListener("MSFullscreenChange",a,!1));var n=document.createElement("div");s.parentNode.insertBefore(n,s),n.appendChild(s),n.requestFullscreen=n.requestFullscreen||n.mozRequestFullScreen||n.msRequestFullscreen||(n.webkitRequestFullscreen?function(){n.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}:null)||(n.webkitRequestFullScreen?function(){n.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}:null),r?n.requestFullscreen({vrDisplay:r}):n.requestFullscreen()},requestFullScreen:function(t,e,r){return Module.printErr("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead."),Browser.requestFullScreen=function(s,a,n){return Browser.requestFullscreen(s,a,n)},Browser.requestFullscreen(t,e,r)},nextRAF:0,fakeRequestAnimationFrame:function(t){var e=Date.now();if(Browser.nextRAF===0)Browser.nextRAF=e+1e3/60;else for(;e+2>=Browser.nextRAF;)Browser.nextRAF+=1e3/60;var r=Math.max(Browser.nextRAF-e,0);setTimeout(t,r)},requestAnimationFrame:function t(e){typeof window>"u"?Browser.fakeRequestAnimationFrame(e):(window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||Browser.fakeRequestAnimationFrame),window.requestAnimationFrame(e))},safeCallback:function(t){return function(){if(!ABORT)return t.apply(null,arguments)}},allowAsyncCallbacks:!0,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=!1},resumeAsyncCallbacks:function(){if(Browser.allowAsyncCallbacks=!0,Browser.queuedAsyncCallbacks.length>0){var t=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[],t.forEach(function(e){e()})}},safeRequestAnimationFrame:function(t){return Browser.requestAnimationFrame(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))})},safeSetTimeout:function(t,e){return Module.noExitRuntime=!0,setTimeout(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))},e)},safeSetInterval:function(t,e){return Module.noExitRuntime=!0,setInterval(function(){ABORT||Browser.allowAsyncCallbacks&&t()},e)},getMimetype:function(t){return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",bmp:"image/bmp",ogg:"audio/ogg",wav:"audio/wav",mp3:"audio/mpeg"}[t.substr(t.lastIndexOf(".")+1)]},getUserMedia:function(t){window.getUserMedia||(window.getUserMedia=navigator.getUserMedia||navigator.mozGetUserMedia),window.getUserMedia(t)},getMovementX:function(t){return t.movementX||t.mozMovementX||t.webkitMovementX||0},getMovementY:function(t){return t.movementY||t.mozMovementY||t.webkitMovementY||0},getMouseWheelDelta:function(t){var e=0;switch(t.type){case"DOMMouseScroll":e=t.detail;break;case"mousewheel":e=t.wheelDelta;break;case"wheel":e=t.deltaY;break;default:throw"unrecognized mouse wheel event: "+t.type}return e},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(t){if(Browser.pointerLock)t.type!="mousemove"&&"mozMovementX"in t?Browser.mouseMovementX=Browser.mouseMovementY=0:(Browser.mouseMovementX=Browser.getMovementX(t),Browser.mouseMovementY=Browser.getMovementY(t)),typeof SDL<"u"?(Browser.mouseX=SDL.mouseX+Browser.mouseMovementX,Browser.mouseY=SDL.mouseY+Browser.mouseMovementY):(Browser.mouseX+=Browser.mouseMovementX,Browser.mouseY+=Browser.mouseMovementY);else{var e=Module.canvas.getBoundingClientRect(),r=Module.canvas.width,s=Module.canvas.height,a=typeof window.scrollX<"u"?window.scrollX:window.pageXOffset,n=typeof window.scrollY<"u"?window.scrollY:window.pageYOffset;if(t.type==="touchstart"||t.type==="touchend"||t.type==="touchmove"){var c=t.touch;if(c===void 0)return;var f=c.pageX-(a+e.left),p=c.pageY-(n+e.top);f=f*(r/e.width),p=p*(s/e.height);var h={x:f,y:p};if(t.type==="touchstart")Browser.lastTouches[c.identifier]=h,Browser.touches[c.identifier]=h;else if(t.type==="touchend"||t.type==="touchmove"){var E=Browser.touches[c.identifier];E||(E=h),Browser.lastTouches[c.identifier]=E,Browser.touches[c.identifier]=h}return}var C=t.pageX-(a+e.left),S=t.pageY-(n+e.top);C=C*(r/e.width),S=S*(s/e.height),Browser.mouseMovementX=C-Browser.mouseX,Browser.mouseMovementY=S-Browser.mouseY,Browser.mouseX=C,Browser.mouseY=S}},asyncLoad:function(t,e,r,s){var a=s?"":"al "+t;Module.readAsync(t,function(n){assert(n,'Loading data file "'+t+'" failed (no arrayBuffer).'),e(new Uint8Array(n)),a&&removeRunDependency(a)},function(n){if(r)r();else throw'Loading data file "'+t+'" failed.'}),a&&addRunDependency(a)},resizeListeners:[],updateResizeListeners:function(){var t=Module.canvas;Browser.resizeListeners.forEach(function(e){e(t.width,t.height)})},setCanvasSize:function(t,e,r){var s=Module.canvas;Browser.updateCanvasDimensions(s,t,e),r||Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t|8388608,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t&-8388609,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},updateCanvasDimensions:function(t,e,r){e&&r?(t.widthNative=e,t.heightNative=r):(e=t.widthNative,r=t.heightNative);var s=e,a=r;if(Module.forcedAspectRatio&&Module.forcedAspectRatio>0&&(s/a>2];return e},getStr:function(){var t=Pointer_stringify(SYSCALLS.get());return t},get64:function(){var t=SYSCALLS.get(),e=SYSCALLS.get();return t>=0?assert(e===0):assert(e===-1),t},getZero:function(){assert(SYSCALLS.get()===0)}};function ___syscall6(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD();return FS.close(r),0}catch(s){return(typeof FS>"u"||!(s instanceof FS.ErrnoError))&&abort(s),-s.errno}}function ___syscall54(t,e){SYSCALLS.varargs=e;try{return 0}catch(r){return(typeof FS>"u"||!(r instanceof FS.ErrnoError))&&abort(r),-r.errno}}function _typeModule(t){var e=[[0,1,"X"],[1,1,"const X"],[128,1,"X *"],[256,1,"X &"],[384,1,"X &&"],[512,1,"std::shared_ptr"],[640,1,"std::unique_ptr"],[5120,1,"std::vector"],[6144,2,"std::array"],[9216,-1,"std::function"]];function r(p,h,E,C,S,P){if(h==1){var I=C&896;(I==128||I==256||I==384)&&(p="X const")}var R;return P?R=E.replace("X",p).replace("Y",S):R=p.replace("X",E).replace("Y",S),R.replace(/([*&]) (?=[*&])/g,"$1")}function s(p,h,E,C,S){throw new Error(p+" type "+E.replace("X",h+"?")+(C?" with flag "+C:"")+" in "+S)}function a(p,h,E,C,S,P,I,R){P===void 0&&(P="X"),R===void 0&&(R=1);var N=E(p);if(N)return N;var U=C(p),W=U.placeholderFlag,ee=e[W];I&&ee&&(P=r(I[2],I[0],P,ee[0],"?",!0));var ie;W==0&&(ie="Unbound"),W>=10&&(ie="Corrupt"),R>20&&(ie="Deeply nested"),ie&&s(ie,p,P,W,S||"?");var ue=U.paramList[0],le=a(ue,h,E,C,S,P,ee,R+1),me,pe={flags:ee[0],id:p,name:"",paramList:[le]},Be=[],Ce="?";switch(U.placeholderFlag){case 1:me=le.spec;break;case 2:if((le.flags&15360)==1024&&le.spec.ptrSize==1){pe.flags=7168;break}case 3:case 6:case 5:me=le.spec,le.flags&15360;break;case 8:Ce=""+U.paramList[1],pe.paramList.push(U.paramList[1]);break;case 9:for(var g=0,we=U.paramList[1];g>2]=t),t}function _llvm_stacksave(){var t=_llvm_stacksave;return t.LLVM_SAVEDSTACKS||(t.LLVM_SAVEDSTACKS=[]),t.LLVM_SAVEDSTACKS.push(Runtime.stackSave()),t.LLVM_SAVEDSTACKS.length-1}function ___syscall140(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=SYSCALLS.get(),c=SYSCALLS.get(),f=a;return FS.llseek(r,f,c),HEAP32[n>>2]=r.position,r.getdents&&f===0&&c===0&&(r.getdents=null),0}catch(p){return(typeof FS>"u"||!(p instanceof FS.ErrnoError))&&abort(p),-p.errno}}function ___syscall146(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.get(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=0;___syscall146.buffer||(___syscall146.buffers=[null,[],[]],___syscall146.printChar=function(E,C){var S=___syscall146.buffers[E];assert(S),C===0||C===10?((E===1?Module.print:Module.printErr)(UTF8ArrayToString(S,0)),S.length=0):S.push(C)});for(var c=0;c>2],p=HEAP32[s+(c*8+4)>>2],h=0;h"u"||!(E instanceof FS.ErrnoError))&&abort(E),-E.errno}}function __nbind_finish(){for(var t=0,e=_nbind.BindClass.list;tt.pageSize/2||e>t.pageSize-r){var s=_nbind.typeNameTbl.NBind.proto;return s.lalloc(e)}else return HEAPU32[t.usedPtr]=r+e,t.rootPtr+r},t.lreset=function(e,r){var s=HEAPU32[t.pagePtr];if(s){var a=_nbind.typeNameTbl.NBind.proto;a.lreset(e,r)}else HEAPU32[t.usedPtr]=e},t}();_nbind.Pool=Pool;function constructType(t,e){var r=t==10240?_nbind.makeTypeNameTbl[e.name]||_nbind.BindType:_nbind.makeTypeKindTbl[t],s=new r(e);return typeIdTbl[e.id]=s,_nbind.typeNameTbl[e.name]=s,s}_nbind.constructType=constructType;function getType(t){return typeIdTbl[t]}_nbind.getType=getType;function queryType(t){var e=HEAPU8[t],r=_nbind.structureList[e][1];t/=4,r<0&&(++t,r=HEAPU32[t]+1);var s=Array.prototype.slice.call(HEAPU32.subarray(t+1,t+1+r));return e==9&&(s=[s[0],s.slice(1)]),{paramList:s,placeholderFlag:e}}_nbind.queryType=queryType;function getTypes(t,e){return t.map(function(r){return typeof r=="number"?_nbind.getComplexType(r,constructType,getType,queryType,e):_nbind.typeNameTbl[r]})}_nbind.getTypes=getTypes;function readTypeIdList(t,e){return Array.prototype.slice.call(HEAPU32,t/4,t/4+e)}_nbind.readTypeIdList=readTypeIdList;function readAsciiString(t){for(var e=t;HEAPU8[e++];);return String.fromCharCode.apply("",HEAPU8.subarray(t,e-1))}_nbind.readAsciiString=readAsciiString;function readPolicyList(t){var e={};if(t)for(;;){var r=HEAPU32[t/4];if(!r)break;e[readAsciiString(r)]=!0,t+=4}return e}_nbind.readPolicyList=readPolicyList;function getDynCall(t,e){var r={float32_t:"d",float64_t:"d",int64_t:"d",uint64_t:"d",void:"v"},s=t.map(function(n){return r[n.name]||"i"}).join(""),a=Module["dynCall_"+s];if(!a)throw new Error("dynCall_"+s+" not found for "+e+"("+t.map(function(n){return n.name}).join(", ")+")");return a}_nbind.getDynCall=getDynCall;function addMethod(t,e,r,s){var a=t[e];t.hasOwnProperty(e)&&a?((a.arity||a.arity===0)&&(a=_nbind.makeOverloader(a,a.arity),t[e]=a),a.addMethod(r,s)):(r.arity=s,t[e]=r)}_nbind.addMethod=addMethod;function throwError(t){throw new Error(t)}_nbind.throwError=throwError,_nbind.bigEndian=!1,_a=_typeModule(_typeModule),_nbind.Type=_a.Type,_nbind.makeType=_a.makeType,_nbind.getComplexType=_a.getComplexType,_nbind.structureList=_a.structureList;var BindType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.heap=HEAPU32,r.ptrSize=4,r}return e.prototype.needsWireRead=function(r){return!!this.wireRead||!!this.makeWireRead},e.prototype.needsWireWrite=function(r){return!!this.wireWrite||!!this.makeWireWrite},e}(_nbind.Type);_nbind.BindType=BindType;var PrimitiveType=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this,a=r.flags&32?{32:HEAPF32,64:HEAPF64}:r.flags&8?{8:HEAPU8,16:HEAPU16,32:HEAPU32}:{8:HEAP8,16:HEAP16,32:HEAP32};return s.heap=a[r.ptrSize*8],s.ptrSize=r.ptrSize,s}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="number")return a;throw new Error("Type mismatch")}},e}(BindType);_nbind.PrimitiveType=PrimitiveType;function pushCString(t,e){if(t==null){if(e&&e.Nullable)return 0;throw new Error("Type mismatch")}if(e&&e.Strict){if(typeof t!="string")throw new Error("Type mismatch")}else t=t.toString();var r=Module.lengthBytesUTF8(t)+1,s=_nbind.Pool.lalloc(r);return Module.stringToUTF8Array(t,HEAPU8,s,r),s}_nbind.pushCString=pushCString;function popCString(t){return t===0?null:Module.Pointer_stringify(t)}_nbind.popCString=popCString;var CStringType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popCString,r.wireWrite=pushCString,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushCString(a,s)}},e}(BindType);_nbind.CStringType=CStringType;var BooleanType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=function(s){return!!s},r}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireRead=function(r){return"!!("+r+")"},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="boolean")return a;throw new Error("Type mismatch")}||r},e}(BindType);_nbind.BooleanType=BooleanType;var Wrapper=function(){function t(){}return t.prototype.persist=function(){this.__nbindState|=1},t}();_nbind.Wrapper=Wrapper;function makeBound(t,e){var r=function(s){__extends(a,s);function a(n,c,f,p){var h=s.call(this)||this;if(!(h instanceof a))return new(Function.prototype.bind.apply(a,Array.prototype.concat.apply([null],arguments)));var E=c,C=f,S=p;if(n!==_nbind.ptrMarker){var P=h.__nbindConstructor.apply(h,arguments);E=4608,S=HEAPU32[P/4],C=HEAPU32[P/4+1]}var I={configurable:!0,enumerable:!1,value:null,writable:!1},R={__nbindFlags:E,__nbindPtr:C};S&&(R.__nbindShared=S,_nbind.mark(h));for(var N=0,U=Object.keys(R);N>=1;var r=_nbind.valueList[t];return _nbind.valueList[t]=firstFreeValue,firstFreeValue=t,r}else{if(e)return _nbind.popShared(t,e);throw new Error("Invalid value slot "+t)}}_nbind.popValue=popValue;var valueBase=18446744073709552e3;function push64(t){return typeof t=="number"?t:pushValue(t)*4096+valueBase}function pop64(t){return t=3?c=Buffer.from(n):c=new Buffer(n),c.copy(s)}else getBuffer(s).set(n)}}_nbind.commitBuffer=commitBuffer;var dirtyList=[],gcTimer=0;function sweep(){for(var t=0,e=dirtyList;t>2]=DYNAMIC_BASE,staticSealed=!0;function invoke_viiiii(t,e,r,s,a,n){try{Module.dynCall_viiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_vif(t,e,r){try{Module.dynCall_vif(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_vid(t,e,r){try{Module.dynCall_vid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_fiff(t,e,r,s){try{return Module.dynCall_fiff(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_vi(t,e){try{Module.dynCall_vi(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_vii(t,e,r){try{Module.dynCall_vii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_ii(t,e){try{return Module.dynCall_ii(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_viddi(t,e,r,s,a){try{Module.dynCall_viddi(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_vidd(t,e,r,s){try{Module.dynCall_vidd(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_iiii(t,e,r,s){try{return Module.dynCall_iiii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_diii(t,e,r,s){try{return Module.dynCall_diii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_di(t,e){try{return Module.dynCall_di(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_iid(t,e,r){try{return Module.dynCall_iid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_iii(t,e,r){try{return Module.dynCall_iii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiddi(t,e,r,s,a,n){try{Module.dynCall_viiddi(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiiiii(t,e,r,s,a,n,c){try{Module.dynCall_viiiiii(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_dii(t,e,r){try{return Module.dynCall_dii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_i(t){try{return Module.dynCall_i(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_iiiiii(t,e,r,s,a,n){try{return Module.dynCall_iiiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiid(t,e,r,s,a){try{Module.dynCall_viiid(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_viififi(t,e,r,s,a,n,c){try{Module.dynCall_viififi(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_viii(t,e,r,s){try{Module.dynCall_viii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_v(t){try{Module.dynCall_v(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_viid(t,e,r,s){try{Module.dynCall_viid(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_idd(t,e,r){try{return Module.dynCall_idd(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiii(t,e,r,s,a){try{Module.dynCall_viiii(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}Module.asmGlobalArg={Math,Int8Array,Int16Array,Int32Array,Uint8Array,Uint16Array,Uint32Array,Float32Array,Float64Array,NaN:NaN,Infinity:1/0},Module.asmLibraryArg={abort,assert,enlargeMemory,getTotalMemory,abortOnCannotGrowMemory,invoke_viiiii,invoke_vif,invoke_vid,invoke_fiff,invoke_vi,invoke_vii,invoke_ii,invoke_viddi,invoke_vidd,invoke_iiii,invoke_diii,invoke_di,invoke_iid,invoke_iii,invoke_viiddi,invoke_viiiiii,invoke_dii,invoke_i,invoke_iiiiii,invoke_viiid,invoke_viififi,invoke_viii,invoke_v,invoke_viid,invoke_idd,invoke_viiii,_emscripten_asm_const_iiiii,_emscripten_asm_const_iiidddddd,_emscripten_asm_const_iiiid,__nbind_reference_external,_emscripten_asm_const_iiiiiiii,_removeAccessorPrefix,_typeModule,__nbind_register_pool,__decorate,_llvm_stackrestore,___cxa_atexit,__extends,__nbind_get_value_object,__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,_emscripten_set_main_loop_timing,__nbind_register_primitive,__nbind_register_type,_emscripten_memcpy_big,__nbind_register_function,___setErrNo,__nbind_register_class,__nbind_finish,_abort,_nbind_value,_llvm_stacksave,___syscall54,_defineHidden,_emscripten_set_main_loop,_emscripten_get_now,__nbind_register_callback_signature,_emscripten_asm_const_iiiiii,__nbind_free_external,_emscripten_asm_const_iiii,_emscripten_asm_const_iiididi,___syscall6,_atexit,___syscall140,___syscall146,DYNAMICTOP_PTR,tempDoublePtr,ABORT,STACKTOP,STACK_MAX,cttz_i8,___dso_handle};var asm=function(t,e,r){var s=new t.Int8Array(r),a=new t.Int16Array(r),n=new t.Int32Array(r),c=new t.Uint8Array(r),f=new t.Uint16Array(r),p=new t.Uint32Array(r),h=new t.Float32Array(r),E=new t.Float64Array(r),C=e.DYNAMICTOP_PTR|0,S=e.tempDoublePtr|0,P=e.ABORT|0,I=e.STACKTOP|0,R=e.STACK_MAX|0,N=e.cttz_i8|0,U=e.___dso_handle|0,W=0,ee=0,ie=0,ue=0,le=t.NaN,me=t.Infinity,pe=0,Be=0,Ce=0,g=0,we=0,ye=0,Ae=t.Math.floor,se=t.Math.abs,Z=t.Math.sqrt,De=t.Math.pow,Re=t.Math.cos,mt=t.Math.sin,j=t.Math.tan,rt=t.Math.acos,Fe=t.Math.asin,Ne=t.Math.atan,Pe=t.Math.atan2,Ve=t.Math.exp,ke=t.Math.log,it=t.Math.ceil,Ue=t.Math.imul,x=t.Math.min,w=t.Math.max,b=t.Math.clz32,y=t.Math.fround,F=e.abort,z=e.assert,X=e.enlargeMemory,$=e.getTotalMemory,oe=e.abortOnCannotGrowMemory,xe=e.invoke_viiiii,Te=e.invoke_vif,lt=e.invoke_vid,Ct=e.invoke_fiff,qt=e.invoke_vi,ir=e.invoke_vii,Pt=e.invoke_ii,gn=e.invoke_viddi,Pr=e.invoke_vidd,Ir=e.invoke_iiii,Or=e.invoke_diii,on=e.invoke_di,ai=e.invoke_iid,Io=e.invoke_iii,rs=e.invoke_viiddi,$s=e.invoke_viiiiii,Co=e.invoke_dii,ji=e.invoke_i,eo=e.invoke_iiiiii,wo=e.invoke_viiid,QA=e.invoke_viififi,Af=e.invoke_viii,dh=e.invoke_v,mh=e.invoke_viid,to=e.invoke_idd,jn=e.invoke_viiii,Ts=e._emscripten_asm_const_iiiii,ro=e._emscripten_asm_const_iiidddddd,ou=e._emscripten_asm_const_iiiid,au=e.__nbind_reference_external,lu=e._emscripten_asm_const_iiiiiiii,TA=e._removeAccessorPrefix,RA=e._typeModule,oa=e.__nbind_register_pool,aa=e.__decorate,FA=e._llvm_stackrestore,gr=e.___cxa_atexit,Bo=e.__extends,Me=e.__nbind_get_value_object,cu=e.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,Cr=e._emscripten_set_main_loop_timing,pf=e.__nbind_register_primitive,NA=e.__nbind_register_type,OA=e._emscripten_memcpy_big,uu=e.__nbind_register_function,fu=e.___setErrNo,oc=e.__nbind_register_class,ve=e.__nbind_finish,Nt=e._abort,ac=e._nbind_value,Oi=e._llvm_stacksave,no=e.___syscall54,Rt=e._defineHidden,xn=e._emscripten_set_main_loop,la=e._emscripten_get_now,Gi=e.__nbind_register_callback_signature,Li=e._emscripten_asm_const_iiiiii,Na=e.__nbind_free_external,dn=e._emscripten_asm_const_iiii,Kn=e._emscripten_asm_const_iiididi,Au=e.___syscall6,yh=e._atexit,Oa=e.___syscall140,La=e.___syscall146,Ma=y(0);let $e=y(0);function Ua(o){o=o|0;var l=0;return l=I,I=I+o|0,I=I+15&-16,l|0}function hf(){return I|0}function lc(o){o=o|0,I=o}function wn(o,l){o=o|0,l=l|0,I=o,R=l}function ca(o,l){o=o|0,l=l|0,W||(W=o,ee=l)}function LA(o){o=o|0,ye=o}function MA(){return ye|0}function ua(){var o=0,l=0;Qr(8104,8,400)|0,Qr(8504,408,540)|0,o=9044,l=o+44|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));s[9088]=0,s[9089]=1,n[2273]=0,n[2274]=948,n[2275]=948,gr(17,8104,U|0)|0}function Bl(o){o=o|0,dt(o+948|0)}function Mt(o){return o=y(o),((fP(o)|0)&2147483647)>>>0>2139095040|0}function kn(o,l,u){o=o|0,l=l|0,u=u|0;e:do if(n[o+(l<<3)+4>>2]|0)o=o+(l<<3)|0;else{if((l|2|0)==3&&n[o+60>>2]|0){o=o+56|0;break}switch(l|0){case 0:case 2:case 4:case 5:{if(n[o+52>>2]|0){o=o+48|0;break e}break}default:}if(n[o+68>>2]|0){o=o+64|0;break}else{o=(l|1|0)==5?948:u;break}}while(!1);return o|0}function fa(o){o=o|0;var l=0;return l=_P(1e3)|0,Ha(o,(l|0)!=0,2456),n[2276]=(n[2276]|0)+1,Qr(l|0,8104,1e3)|0,s[o+2>>0]|0&&(n[l+4>>2]=2,n[l+12>>2]=4),n[l+976>>2]=o,l|0}function Ha(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Wg(o,5,3197,A)),I=d}function ns(){return fa(956)|0}function cc(o){o=o|0;var l=0;return l=Kt(1e3)|0,pu(l,o),Ha(n[o+976>>2]|0,1,2456),n[2276]=(n[2276]|0)+1,n[l+944>>2]=0,l|0}function pu(o,l){o=o|0,l=l|0;var u=0;Qr(o|0,l|0,948)|0,Dy(o+948|0,l+948|0),u=o+960|0,o=l+960|0,l=u+40|0;do n[u>>2]=n[o>>2],u=u+4|0,o=o+4|0;while((u|0)<(l|0))}function uc(o){o=o|0;var l=0,u=0,A=0,d=0;if(l=o+944|0,u=n[l>>2]|0,u|0&&(ja(u+948|0,o)|0,n[l>>2]=0),u=Mi(o)|0,u|0){l=0;do n[(Is(o,l)|0)+944>>2]=0,l=l+1|0;while((l|0)!=(u|0))}u=o+948|0,A=n[u>>2]|0,d=o+952|0,l=n[d>>2]|0,(l|0)!=(A|0)&&(n[d>>2]=l+(~((l+-4-A|0)>>>2)<<2)),vl(u),HP(o),n[2276]=(n[2276]|0)+-1}function ja(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0;A=n[o>>2]|0,k=o+4|0,u=n[k>>2]|0,m=u;e:do if((A|0)==(u|0))d=A,B=4;else for(o=A;;){if((n[o>>2]|0)==(l|0)){d=o,B=4;break e}if(o=o+4|0,(o|0)==(u|0)){o=0;break}}while(!1);return(B|0)==4&&((d|0)!=(u|0)?(A=d+4|0,o=m-A|0,l=o>>2,l&&(Q2(d|0,A|0,o|0)|0,u=n[k>>2]|0),o=d+(l<<2)|0,(u|0)==(o|0)||(n[k>>2]=u+(~((u+-4-o|0)>>>2)<<2)),o=1):o=0),o|0}function Mi(o){return o=o|0,(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2|0}function Is(o,l){o=o|0,l=l|0;var u=0;return u=n[o+948>>2]|0,(n[o+952>>2]|0)-u>>2>>>0>l>>>0?o=n[u+(l<<2)>>2]|0:o=0,o|0}function vl(o){o=o|0;var l=0,u=0,A=0,d=0;A=I,I=I+32|0,l=A,d=n[o>>2]|0,u=(n[o+4>>2]|0)-d|0,((n[o+8>>2]|0)-d|0)>>>0>u>>>0&&(d=u>>2,ky(l,d,d,o+8|0),AP(o,l),Qy(l)),I=A}function gf(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;M=Mi(o)|0;do if(M|0){if((n[(Is(o,0)|0)+944>>2]|0)==(o|0)){if(!(ja(o+948|0,l)|0))break;Qr(l+400|0,8504,540)|0,n[l+944>>2]=0,Oe(o);break}B=n[(n[o+976>>2]|0)+12>>2]|0,k=o+948|0,T=(B|0)==0,u=0,m=0;do A=n[(n[k>>2]|0)+(m<<2)>>2]|0,(A|0)==(l|0)?Oe(o):(d=cc(A)|0,n[(n[k>>2]|0)+(u<<2)>>2]=d,n[d+944>>2]=o,T||dU[B&15](A,d,o,u),u=u+1|0),m=m+1|0;while((m|0)!=(M|0));if(u>>>0>>0){T=o+948|0,k=o+952|0,B=u,u=n[k>>2]|0;do m=(n[T>>2]|0)+(B<<2)|0,A=m+4|0,d=u-A|0,l=d>>2,l&&(Q2(m|0,A|0,d|0)|0,u=n[k>>2]|0),d=u,A=m+(l<<2)|0,(d|0)!=(A|0)&&(u=d+(~((d+-4-A|0)>>>2)<<2)|0,n[k>>2]=u),B=B+1|0;while((B|0)!=(M|0))}}while(!1)}function fc(o){o=o|0;var l=0,u=0,A=0,d=0;wi(o,(Mi(o)|0)==0,2491),wi(o,(n[o+944>>2]|0)==0,2545),l=o+948|0,u=n[l>>2]|0,A=o+952|0,d=n[A>>2]|0,(d|0)!=(u|0)&&(n[A>>2]=d+(~((d+-4-u|0)>>>2)<<2)),vl(l),l=o+976|0,u=n[l>>2]|0,Qr(o|0,8104,1e3)|0,s[u+2>>0]|0&&(n[o+4>>2]=2,n[o+12>>2]=4),n[l>>2]=u}function wi(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,xo(o,5,3197,A)),I=d}function Qn(){return n[2276]|0}function Ac(){var o=0;return o=_P(20)|0,Ke((o|0)!=0,2592),n[2277]=(n[2277]|0)+1,n[o>>2]=n[239],n[o+4>>2]=n[240],n[o+8>>2]=n[241],n[o+12>>2]=n[242],n[o+16>>2]=n[243],o|0}function Ke(o,l){o=o|0,l=l|0;var u=0,A=0;A=I,I=I+16|0,u=A,o||(n[u>>2]=l,xo(0,5,3197,u)),I=A}function st(o){o=o|0,HP(o),n[2277]=(n[2277]|0)+-1}function St(o,l){o=o|0,l=l|0;var u=0;l?(wi(o,(Mi(o)|0)==0,2629),u=1):(u=0,l=0),n[o+964>>2]=l,n[o+988>>2]=u}function lr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+8|0,d=A+4|0,B=A,n[d>>2]=l,wi(o,(n[l+944>>2]|0)==0,2709),wi(o,(n[o+964>>2]|0)==0,2763),te(o),l=o+948|0,n[B>>2]=(n[l>>2]|0)+(u<<2),n[m>>2]=n[B>>2],Ee(l,m,d)|0,n[(n[d>>2]|0)+944>>2]=o,Oe(o),I=A}function te(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;if(u=Mi(o)|0,u|0&&(n[(Is(o,0)|0)+944>>2]|0)!=(o|0)){A=n[(n[o+976>>2]|0)+12>>2]|0,d=o+948|0,m=(A|0)==0,l=0;do B=n[(n[d>>2]|0)+(l<<2)>>2]|0,k=cc(B)|0,n[(n[d>>2]|0)+(l<<2)>>2]=k,n[k+944>>2]=o,m||dU[A&15](B,k,o,l),l=l+1|0;while((l|0)!=(u|0))}}function Ee(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0;tt=I,I=I+64|0,q=tt+52|0,k=tt+48|0,ae=tt+28|0,Ye=tt+24|0,Le=tt+20|0,Qe=tt,A=n[o>>2]|0,m=A,l=A+((n[l>>2]|0)-m>>2<<2)|0,A=o+4|0,d=n[A>>2]|0,B=o+8|0;do if(d>>>0<(n[B>>2]|0)>>>0){if((l|0)==(d|0)){n[l>>2]=n[u>>2],n[A>>2]=(n[A>>2]|0)+4;break}pP(o,l,d,l+4|0),l>>>0<=u>>>0&&(u=(n[A>>2]|0)>>>0>u>>>0?u+4|0:u),n[l>>2]=n[u>>2]}else{A=(d-m>>2)+1|0,d=O(o)|0,d>>>0>>0&&an(o),L=n[o>>2]|0,M=(n[B>>2]|0)-L|0,m=M>>1,ky(Qe,M>>2>>>0>>1>>>0?m>>>0>>0?A:m:d,l-L>>2,o+8|0),L=Qe+8|0,A=n[L>>2]|0,m=Qe+12|0,M=n[m>>2]|0,B=M,T=A;do if((A|0)==(M|0)){if(M=Qe+4|0,A=n[M>>2]|0,Ze=n[Qe>>2]|0,d=Ze,A>>>0<=Ze>>>0){A=B-d>>1,A=A|0?A:1,ky(ae,A,A>>>2,n[Qe+16>>2]|0),n[Ye>>2]=n[M>>2],n[Le>>2]=n[L>>2],n[k>>2]=n[Ye>>2],n[q>>2]=n[Le>>2],o2(ae,k,q),A=n[Qe>>2]|0,n[Qe>>2]=n[ae>>2],n[ae>>2]=A,A=ae+4|0,Ze=n[M>>2]|0,n[M>>2]=n[A>>2],n[A>>2]=Ze,A=ae+8|0,Ze=n[L>>2]|0,n[L>>2]=n[A>>2],n[A>>2]=Ze,A=ae+12|0,Ze=n[m>>2]|0,n[m>>2]=n[A>>2],n[A>>2]=Ze,Qy(ae),A=n[L>>2]|0;break}m=A,B=((m-d>>2)+1|0)/-2|0,k=A+(B<<2)|0,d=T-m|0,m=d>>2,m&&(Q2(k|0,A|0,d|0)|0,A=n[M>>2]|0),Ze=k+(m<<2)|0,n[L>>2]=Ze,n[M>>2]=A+(B<<2),A=Ze}while(!1);n[A>>2]=n[u>>2],n[L>>2]=(n[L>>2]|0)+4,l=hP(o,Qe,l)|0,Qy(Qe)}while(!1);return I=tt,l|0}function Oe(o){o=o|0;var l=0;do{if(l=o+984|0,s[l>>0]|0)break;s[l>>0]=1,h[o+504>>2]=y(le),o=n[o+944>>2]|0}while(o|0)}function dt(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function Et(o){return o=o|0,n[o+944>>2]|0}function bt(o){o=o|0,wi(o,(n[o+964>>2]|0)!=0,2832),Oe(o)}function tr(o){return o=o|0,(s[o+984>>0]|0)!=0|0}function An(o,l){o=o|0,l=l|0,l6e(o,l,400)|0&&(Qr(o|0,l|0,400)|0,Oe(o))}function li(o){o=o|0;var l=$e;return l=y(h[o+44>>2]),o=Mt(l)|0,y(o?y(0):l)}function qi(o){o=o|0;var l=$e;return l=y(h[o+48>>2]),Mt(l)|0&&(l=s[(n[o+976>>2]|0)+2>>0]|0?y(1):y(0)),y(l)}function Tn(o,l){o=o|0,l=l|0,n[o+980>>2]=l}function Ga(o){return o=o|0,n[o+980>>2]|0}function my(o,l){o=o|0,l=l|0;var u=0;u=o+4|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Z1(o){return o=o|0,n[o+4>>2]|0}function vo(o,l){o=o|0,l=l|0;var u=0;u=o+8|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function yy(o){return o=o|0,n[o+8>>2]|0}function Eh(o,l){o=o|0,l=l|0;var u=0;u=o+12|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function $1(o){return o=o|0,n[o+12>>2]|0}function So(o,l){o=o|0,l=l|0;var u=0;u=o+16|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Ih(o){return o=o|0,n[o+16>>2]|0}function Ch(o,l){o=o|0,l=l|0;var u=0;u=o+20|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function hu(o){return o=o|0,n[o+20>>2]|0}function wh(o,l){o=o|0,l=l|0;var u=0;u=o+24|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Fg(o){return o=o|0,n[o+24>>2]|0}function Ng(o,l){o=o|0,l=l|0;var u=0;u=o+28|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Og(o){return o=o|0,n[o+28>>2]|0}function Ey(o,l){o=o|0,l=l|0;var u=0;u=o+32|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function df(o){return o=o|0,n[o+32>>2]|0}function Do(o,l){o=o|0,l=l|0;var u=0;u=o+36|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Sl(o){return o=o|0,n[o+36>>2]|0}function Bh(o,l){o=o|0,l=y(l);var u=0;u=o+40|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Lg(o,l){o=o|0,l=y(l);var u=0;u=o+44|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Dl(o,l){o=o|0,l=y(l);var u=0;u=o+48|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function bl(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+52|0,d=o+56|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Iy(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+52|0,u=o+56|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function UA(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+52|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Cy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function wy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function _A(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+132+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function HA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Y(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function xt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+60+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function jA(o,l){o=o|0,l=l|0;var u=0;u=o+60+(l<<3)+4|0,(n[u>>2]|0)!=3&&(h[o+60+(l<<3)>>2]=y(le),n[u>>2]=3,Oe(o))}function bo(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function mf(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function yt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+204+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function gu(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+276+(l<<3)|0,l=o+276+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function By(o,l){return o=o|0,l=l|0,y(h[o+276+(l<<3)>>2])}function Mg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+348|0,d=o+352|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function e2(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+348|0,u=o+352|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function vh(o){o=o|0;var l=0;l=o+352|0,(n[l>>2]|0)!=3&&(h[o+348>>2]=y(le),n[l>>2]=3,Oe(o))}function ur(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+348|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function zi(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+356|0,d=o+360|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function yf(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+356|0,u=o+360|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function qa(o){o=o|0;var l=0;l=o+360|0,(n[l>>2]|0)!=3&&(h[o+356>>2]=y(le),n[l>>2]=3,Oe(o))}function Ug(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+356|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function du(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ef(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function wt(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+364|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function di(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function GA(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Wa(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+372|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Aa(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ya(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function _g(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+380|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Sh(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Hg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function vy(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+388|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function qA(o,l){o=o|0,l=y(l);var u=0;u=o+396|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function jg(o){return o=o|0,y(h[o+396>>2])}function mu(o){return o=o|0,y(h[o+400>>2])}function yu(o){return o=o|0,y(h[o+404>>2])}function If(o){return o=o|0,y(h[o+408>>2])}function Rs(o){return o=o|0,y(h[o+412>>2])}function Eu(o){return o=o|0,y(h[o+416>>2])}function Gn(o){return o=o|0,y(h[o+420>>2])}function is(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+424+(l<<2)>>2])}function Pi(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+448+(l<<2)>>2])}function WA(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+472+(l<<2)>>2])}function Cf(o,l){o=o|0,l=l|0;var u=0,A=$e;return u=n[o+4>>2]|0,(u|0)==(n[l+4>>2]|0)?u?(A=y(h[o>>2]),o=y(se(y(A-y(h[l>>2]))))>2]=0,n[A+4>>2]=0,n[A+8>>2]=0,cu(A|0,o|0,l|0,0),xo(o,3,(s[A+11>>0]|0)<0?n[A>>2]|0:A,u),Q6e(A),I=u}function ss(o,l,u,A){o=y(o),l=y(l),u=u|0,A=A|0;var d=$e;o=y(o*l),d=y(uU(o,y(1)));do if(mn(d,y(0))|0)o=y(o-d);else{if(o=y(o-d),mn(d,y(1))|0){o=y(o+y(1));break}if(u){o=y(o+y(1));break}A||(d>y(.5)?d=y(1):(A=mn(d,y(.5))|0,d=y(A?1:0)),o=y(o+d))}while(!1);return y(o/l)}function Pl(o,l,u,A,d,m,B,k,T,M,L,q,ae){o=o|0,l=y(l),u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,k=y(k),T=y(T),M=y(M),L=y(L),q=y(q),ae=ae|0;var Ye=0,Le=$e,Qe=$e,tt=$e,Ze=$e,ct=$e,He=$e;return T>2]),Le!=y(0))?(tt=y(ss(l,Le,0,0)),Ze=y(ss(A,Le,0,0)),Qe=y(ss(m,Le,0,0)),Le=y(ss(k,Le,0,0))):(Qe=m,tt=l,Le=k,Ze=A),(d|0)==(o|0)?Ye=mn(Qe,tt)|0:Ye=0,(B|0)==(u|0)?ae=mn(Le,Ze)|0:ae=0,!Ye&&(ct=y(l-L),!(Po(o,ct,T)|0))&&!(wf(o,ct,d,T)|0)?Ye=Bf(o,ct,d,m,T)|0:Ye=1,!ae&&(He=y(A-q),!(Po(u,He,M)|0))&&!(wf(u,He,B,M)|0)?ae=Bf(u,He,B,k,M)|0:ae=1,ae=Ye&ae),ae|0}function Po(o,l,u){return o=o|0,l=y(l),u=y(u),(o|0)==1?o=mn(l,u)|0:o=0,o|0}function wf(o,l,u,A){return o=o|0,l=y(l),u=u|0,A=y(A),(o|0)==2&(u|0)==0?l>=A?o=1:o=mn(l,A)|0:o=0,o|0}function Bf(o,l,u,A,d){return o=o|0,l=y(l),u=u|0,A=y(A),d=y(d),(o|0)==2&(u|0)==2&A>l?d<=l?o=1:o=mn(l,d)|0:o=0,o|0}function xl(o,l,u,A,d,m,B,k,T,M,L){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,M=M|0,L=L|0;var q=0,ae=0,Ye=0,Le=0,Qe=$e,tt=$e,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=$e,To=$e,Ro=$e,Fo=0,Za=0;cr=I,I=I+160|0,$t=cr+152|0,fr=cr+120|0,Gr=cr+104|0,He=cr+72|0,Le=cr+56|0,Lt=cr+8|0,ct=cr,We=(n[2279]|0)+1|0,n[2279]=We,Tr=o+984|0,s[Tr>>0]|0&&(n[o+512>>2]|0)!=(n[2278]|0)?Ze=4:(n[o+516>>2]|0)==(A|0)?Hr=0:Ze=4,(Ze|0)==4&&(n[o+520>>2]=0,n[o+924>>2]=-1,n[o+928>>2]=-1,h[o+932>>2]=y(-1),h[o+936>>2]=y(-1),Hr=1);e:do if(n[o+964>>2]|0)if(Qe=y(yn(o,2,B)),tt=y(yn(o,0,B)),q=o+916|0,Ro=y(h[q>>2]),To=y(h[o+920>>2]),Hn=y(h[o+932>>2]),Pl(d,l,m,u,n[o+924>>2]|0,Ro,n[o+928>>2]|0,To,Hn,y(h[o+936>>2]),Qe,tt,L)|0)Ze=22;else if(Ye=n[o+520>>2]|0,!Ye)Ze=21;else for(ae=0;;){if(q=o+524+(ae*24|0)|0,Hn=y(h[q>>2]),To=y(h[o+524+(ae*24|0)+4>>2]),Ro=y(h[o+524+(ae*24|0)+16>>2]),Pl(d,l,m,u,n[o+524+(ae*24|0)+8>>2]|0,Hn,n[o+524+(ae*24|0)+12>>2]|0,To,Ro,y(h[o+524+(ae*24|0)+20>>2]),Qe,tt,L)|0){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=Ye>>>0){Ze=21;break}}else{if(T){if(q=o+916|0,!(mn(y(h[q>>2]),l)|0)){Ze=21;break}if(!(mn(y(h[o+920>>2]),u)|0)){Ze=21;break}if((n[o+924>>2]|0)!=(d|0)){Ze=21;break}q=(n[o+928>>2]|0)==(m|0)?q:0,Ze=22;break}if(Ye=n[o+520>>2]|0,!Ye)Ze=21;else for(ae=0;;){if(q=o+524+(ae*24|0)|0,mn(y(h[q>>2]),l)|0&&mn(y(h[o+524+(ae*24|0)+4>>2]),u)|0&&(n[o+524+(ae*24|0)+8>>2]|0)==(d|0)&&(n[o+524+(ae*24|0)+12>>2]|0)==(m|0)){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=Ye>>>0){Ze=21;break}}}while(!1);do if((Ze|0)==21)s[11697]|0?(q=0,Ze=28):(q=0,Ze=31);else if((Ze|0)==22){if(ae=(s[11697]|0)!=0,!((q|0)!=0&(Hr^1)))if(ae){Ze=28;break}else{Ze=31;break}Le=q+16|0,n[o+908>>2]=n[Le>>2],Ye=q+20|0,n[o+912>>2]=n[Ye>>2],(s[11698]|0)==0|ae^1||(n[ct>>2]=Iu(We)|0,n[ct+4>>2]=We,xo(o,4,2972,ct),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),d=pa(d,T)|0,m=pa(m,T)|0,Za=+y(h[Le>>2]),Fo=+y(h[Ye>>2]),n[Lt>>2]=d,n[Lt+4>>2]=m,E[Lt+8>>3]=+l,E[Lt+16>>3]=+u,E[Lt+24>>3]=Za,E[Lt+32>>3]=Fo,n[Lt+40>>2]=M,xo(o,4,2989,Lt))}while(!1);return(Ze|0)==28&&(ae=Iu(We)|0,n[Le>>2]=ae,n[Le+4>>2]=We,n[Le+8>>2]=Hr?3047:11699,xo(o,4,3038,Le),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),Lt=pa(d,T)|0,Ze=pa(m,T)|0,n[He>>2]=Lt,n[He+4>>2]=Ze,E[He+8>>3]=+l,E[He+16>>3]=+u,n[He+24>>2]=M,xo(o,4,3049,He),Ze=31),(Ze|0)==31&&(Fs(o,l,u,A,d,m,B,k,T,L),s[11697]|0&&(ae=n[2279]|0,Lt=Iu(ae)|0,n[Gr>>2]=Lt,n[Gr+4>>2]=ae,n[Gr+8>>2]=Hr?3047:11699,xo(o,4,3083,Gr),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),Lt=pa(d,T)|0,Gr=pa(m,T)|0,Fo=+y(h[o+908>>2]),Za=+y(h[o+912>>2]),n[fr>>2]=Lt,n[fr+4>>2]=Gr,E[fr+8>>3]=Fo,E[fr+16>>3]=Za,n[fr+24>>2]=M,xo(o,4,3092,fr)),n[o+516>>2]=A,q||(ae=o+520|0,q=n[ae>>2]|0,(q|0)==16&&(s[11697]|0&&xo(o,4,3124,$t),n[ae>>2]=0,q=0),T?q=o+916|0:(n[ae>>2]=q+1,q=o+524+(q*24|0)|0),h[q>>2]=l,h[q+4>>2]=u,n[q+8>>2]=d,n[q+12>>2]=m,n[q+16>>2]=n[o+908>>2],n[q+20>>2]=n[o+912>>2],q=0)),T&&(n[o+416>>2]=n[o+908>>2],n[o+420>>2]=n[o+912>>2],s[o+985>>0]=1,s[Tr>>0]=0),n[2279]=(n[2279]|0)+-1,n[o+512>>2]=n[2278],I=cr,Hr|(q|0)==0|0}function yn(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(K(o,l,u)),y(A+y(re(o,l,u)))}function xo(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=I,I=I+16|0,d=m,n[d>>2]=A,o?A=n[o+976>>2]|0:A=0,Ph(A,o,l,u,d),I=m}function Iu(o){return o=o|0,(o>>>0>60?3201:3201+(60-o)|0)|0}function pa(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+32|0,u=d+12|0,A=d,n[u>>2]=n[254],n[u+4>>2]=n[255],n[u+8>>2]=n[256],n[A>>2]=n[257],n[A+4>>2]=n[258],n[A+8>>2]=n[259],(o|0)>2?o=11699:o=n[(l?A:u)+(o<<2)>>2]|0,I=d,o|0}function Fs(o,l,u,A,d,m,B,k,T,M){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,M=M|0;var L=0,q=0,ae=0,Ye=0,Le=$e,Qe=$e,tt=$e,Ze=$e,ct=$e,He=$e,We=$e,Lt=0,Gr=0,fr=0,$t=$e,Tr=$e,Hr=0,cr=$e,Hn=0,To=0,Ro=0,Fo=0,Za=0,Wh=0,Yh=0,gc=0,Vh=0,Rf=0,Ff=0,Jh=0,Kh=0,zh=0,ln=0,dc=0,Xh=0,Pu=0,Zh=$e,$h=$e,Nf=$e,Of=$e,xu=$e,oo=0,Ll=0,ma=0,mc=0,op=0,ap=$e,Lf=$e,lp=$e,cp=$e,ao=$e,Ms=$e,yc=0,Wn=$e,up=$e,No=$e,ku=$e,Oo=$e,Qu=$e,fp=0,Ap=0,Tu=$e,lo=$e,Ec=0,pp=0,hp=0,gp=0,Nr=$e,ui=0,Us=0,Lo=0,co=0,Mr=0,Ar=0,Ic=0,zt=$e,dp=0,Bi=0;Ic=I,I=I+16|0,oo=Ic+12|0,Ll=Ic+8|0,ma=Ic+4|0,mc=Ic,wi(o,(d|0)==0|(Mt(l)|0)^1,3326),wi(o,(m|0)==0|(Mt(u)|0)^1,3406),Us=At(o,A)|0,n[o+496>>2]=Us,Mr=dr(2,Us)|0,Ar=dr(0,Us)|0,h[o+440>>2]=y(K(o,Mr,B)),h[o+444>>2]=y(re(o,Mr,B)),h[o+428>>2]=y(K(o,Ar,B)),h[o+436>>2]=y(re(o,Ar,B)),h[o+464>>2]=y(vr(o,Mr)),h[o+468>>2]=y(Un(o,Mr)),h[o+452>>2]=y(vr(o,Ar)),h[o+460>>2]=y(Un(o,Ar)),h[o+488>>2]=y(mi(o,Mr,B)),h[o+492>>2]=y(Cs(o,Mr,B)),h[o+476>>2]=y(mi(o,Ar,B)),h[o+484>>2]=y(Cs(o,Ar,B));do if(n[o+964>>2]|0)JA(o,l,u,d,m,B,k);else{if(Lo=o+948|0,co=(n[o+952>>2]|0)-(n[Lo>>2]|0)>>2,!co){lP(o,l,u,d,m,B,k);break}if(!T&&t2(o,l,u,d,m,B,k)|0)break;te(o),dc=o+508|0,s[dc>>0]=0,Mr=dr(n[o+4>>2]|0,Us)|0,Ar=by(Mr,Us)|0,ui=de(Mr)|0,Xh=n[o+8>>2]|0,pp=o+28|0,Pu=(n[pp>>2]|0)!=0,Oo=ui?B:k,Tu=ui?k:B,Zh=y(kh(o,Mr,B)),$h=y(r2(o,Mr,B)),Le=y(kh(o,Ar,B)),Qu=y(Va(o,Mr,B)),lo=y(Va(o,Ar,B)),fr=ui?d:m,Ec=ui?m:d,Nr=ui?Qu:lo,ct=ui?lo:Qu,ku=y(yn(o,2,B)),Ze=y(yn(o,0,B)),Qe=y(y(Zr(o+364|0,B))-Nr),tt=y(y(Zr(o+380|0,B))-Nr),He=y(y(Zr(o+372|0,k))-ct),We=y(y(Zr(o+388|0,k))-ct),Nf=ui?Qe:He,Of=ui?tt:We,ku=y(l-ku),l=y(ku-Nr),Mt(l)|0?Nr=l:Nr=y($n(y(pd(l,tt)),Qe)),up=y(u-Ze),l=y(up-ct),Mt(l)|0?No=l:No=y($n(y(pd(l,We)),He)),Qe=ui?Nr:No,Wn=ui?No:Nr;e:do if((fr|0)==1)for(A=0,q=0;;){if(L=Is(o,q)|0,!A)y(KA(L))>y(0)&&y(Qh(L))>y(0)?A=L:A=0;else if(n2(L)|0){Ye=0;break e}if(q=q+1|0,q>>>0>=co>>>0){Ye=A;break}}else Ye=0;while(!1);Lt=Ye+500|0,Gr=Ye+504|0,A=0,L=0,l=y(0),ae=0;do{if(q=n[(n[Lo>>2]|0)+(ae<<2)>>2]|0,(n[q+36>>2]|0)==1)Py(q),s[q+985>>0]=1,s[q+984>>0]=0;else{vf(q),T&&bh(q,At(q,Us)|0,Qe,Wn,Nr);do if((n[q+24>>2]|0)!=1)if((q|0)==(Ye|0)){n[Lt>>2]=n[2278],h[Gr>>2]=y(0);break}else{cP(o,q,Nr,d,No,Nr,No,m,Us,M);break}else L|0&&(n[L+960>>2]=q),n[q+960>>2]=0,L=q,A=A|0?A:q;while(!1);Ms=y(h[q+504>>2]),l=y(l+y(Ms+y(yn(q,Mr,Nr))))}ae=ae+1|0}while((ae|0)!=(co|0));for(Ro=l>Qe,yc=Pu&((fr|0)==2&Ro)?1:fr,Hn=(Ec|0)==1,Za=Hn&(T^1),Wh=(yc|0)==1,Yh=(yc|0)==2,gc=976+(Mr<<2)|0,Vh=(Ec|2|0)==2,zh=Hn&(Pu^1),Rf=1040+(Ar<<2)|0,Ff=1040+(Mr<<2)|0,Jh=976+(Ar<<2)|0,Kh=(Ec|0)!=1,Ro=Pu&((fr|0)!=0&Ro),To=o+976|0,Hn=Hn^1,l=Qe,Hr=0,Fo=0,Ms=y(0),xu=y(0);;){e:do if(Hr>>>0>>0)for(Gr=n[Lo>>2]|0,ae=0,We=y(0),He=y(0),tt=y(0),Qe=y(0),q=0,L=0,Ye=Hr;;){if(Lt=n[Gr+(Ye<<2)>>2]|0,(n[Lt+36>>2]|0)!=1&&(n[Lt+940>>2]=Fo,(n[Lt+24>>2]|0)!=1)){if(Ze=y(yn(Lt,Mr,Nr)),ln=n[gc>>2]|0,u=y(Zr(Lt+380+(ln<<3)|0,Oo)),ct=y(h[Lt+504>>2]),u=y(pd(u,ct)),u=y($n(y(Zr(Lt+364+(ln<<3)|0,Oo)),u)),Pu&(ae|0)!=0&y(Ze+y(He+u))>l){m=ae,Ze=We,fr=Ye;break e}Ze=y(Ze+u),u=y(He+Ze),Ze=y(We+Ze),n2(Lt)|0&&(tt=y(tt+y(KA(Lt))),Qe=y(Qe-y(ct*y(Qh(Lt))))),L|0&&(n[L+960>>2]=Lt),n[Lt+960>>2]=0,ae=ae+1|0,L=Lt,q=q|0?q:Lt}else Ze=We,u=He;if(Ye=Ye+1|0,Ye>>>0>>0)We=Ze,He=u;else{m=ae,fr=Ye;break}}else m=0,Ze=y(0),tt=y(0),Qe=y(0),q=0,fr=Hr;while(!1);ln=tt>y(0)&tty(0)&QeOf&((Mt(Of)|0)^1))l=Of,ln=51;else if(s[(n[To>>2]|0)+3>>0]|0)ln=51;else{if($t!=y(0)&&y(KA(o))!=y(0)){ln=53;break}l=Ze,ln=53}while(!1);if((ln|0)==51&&(ln=0,Mt(l)|0?ln=53:(Tr=y(l-Ze),cr=l)),(ln|0)==53&&(ln=0,Ze>2]|0,Ye=Try(0),He=y(Tr/$t),tt=y(0),Ze=y(0),l=y(0),L=q;do u=y(Zr(L+380+(ae<<3)|0,Oo)),Qe=y(Zr(L+364+(ae<<3)|0,Oo)),Qe=y(pd(u,y($n(Qe,y(h[L+504>>2]))))),Ye?(u=y(Qe*y(Qh(L))),u!=y(-0)&&(zt=y(Qe-y(ct*u)),ap=y(qn(L,Mr,zt,cr,Nr)),zt!=ap)&&(tt=y(tt-y(ap-Qe)),l=y(l+u))):Lt&&(Lf=y(KA(L)),Lf!=y(0))&&(zt=y(Qe+y(He*Lf)),lp=y(qn(L,Mr,zt,cr,Nr)),zt!=lp)&&(tt=y(tt-y(lp-Qe)),Ze=y(Ze-Lf)),L=n[L+960>>2]|0;while(L|0);if(l=y(We+l),Qe=y(Tr+tt),op)l=y(0);else{ct=y($t+Ze),Ye=n[gc>>2]|0,Lt=Qey(0),ct=y(Qe/ct),l=y(0);do{zt=y(Zr(q+380+(Ye<<3)|0,Oo)),tt=y(Zr(q+364+(Ye<<3)|0,Oo)),tt=y(pd(zt,y($n(tt,y(h[q+504>>2]))))),Lt?(zt=y(tt*y(Qh(q))),Qe=y(-zt),zt!=y(-0)?(zt=y(He*Qe),Qe=y(qn(q,Mr,y(tt+(Gr?Qe:zt)),cr,Nr))):Qe=tt):ae&&(cp=y(KA(q)),cp!=y(0))?Qe=y(qn(q,Mr,y(tt+y(ct*cp)),cr,Nr)):Qe=tt,l=y(l-y(Qe-tt)),Ze=y(yn(q,Mr,Nr)),u=y(yn(q,Ar,Nr)),Qe=y(Qe+Ze),h[Ll>>2]=Qe,n[mc>>2]=1,tt=y(h[q+396>>2]);e:do if(Mt(tt)|0){L=Mt(Wn)|0;do if(!L){if(Ro|(io(q,Ar,Wn)|0|Hn)||(os(o,q)|0)!=4||(n[(kl(q,Ar)|0)+4>>2]|0)==3||(n[(Ql(q,Ar)|0)+4>>2]|0)==3)break;h[oo>>2]=Wn,n[ma>>2]=1;break e}while(!1);if(io(q,Ar,Wn)|0){L=n[q+992+(n[Jh>>2]<<2)>>2]|0,zt=y(u+y(Zr(L,Wn))),h[oo>>2]=zt,L=Kh&(n[L+4>>2]|0)==2,n[ma>>2]=((Mt(zt)|0|L)^1)&1;break}else{h[oo>>2]=Wn,n[ma>>2]=L?0:2;break}}else zt=y(Qe-Ze),$t=y(zt/tt),zt=y(tt*zt),n[ma>>2]=1,h[oo>>2]=y(u+(ui?$t:zt));while(!1);Cu(q,Mr,cr,Nr,mc,Ll),Cu(q,Ar,Wn,Nr,ma,oo);do if(!(io(q,Ar,Wn)|0)&&(os(o,q)|0)==4){if((n[(kl(q,Ar)|0)+4>>2]|0)==3){L=0;break}L=(n[(Ql(q,Ar)|0)+4>>2]|0)!=3}else L=0;while(!1);zt=y(h[Ll>>2]),$t=y(h[oo>>2]),dp=n[mc>>2]|0,Bi=n[ma>>2]|0,xl(q,ui?zt:$t,ui?$t:zt,Us,ui?dp:Bi,ui?Bi:dp,Nr,No,T&(L^1),3488,M)|0,s[dc>>0]=s[dc>>0]|s[q+508>>0],q=n[q+960>>2]|0}while(q|0)}}else l=y(0);if(l=y(Tr+l),Bi=l>0]=Bi|c[dc>>0],Yh&l>y(0)?(L=n[gc>>2]|0,n[o+364+(L<<3)+4>>2]|0&&(ao=y(Zr(o+364+(L<<3)|0,Oo)),ao>=y(0))?Qe=y($n(y(0),y(ao-y(cr-l)))):Qe=y(0)):Qe=l,Lt=Hr>>>0>>0,Lt){Ye=n[Lo>>2]|0,ae=Hr,L=0;do q=n[Ye+(ae<<2)>>2]|0,n[q+24>>2]|0||(L=((n[(kl(q,Mr)|0)+4>>2]|0)==3&1)+L|0,L=L+((n[(Ql(q,Mr)|0)+4>>2]|0)==3&1)|0),ae=ae+1|0;while((ae|0)!=(fr|0));L?(Ze=y(0),u=y(0)):ln=101}else ln=101;e:do if((ln|0)==101)switch(ln=0,Xh|0){case 1:{L=0,Ze=y(Qe*y(.5)),u=y(0);break e}case 2:{L=0,Ze=Qe,u=y(0);break e}case 3:{if(m>>>0<=1){L=0,Ze=y(0),u=y(0);break e}u=y((m+-1|0)>>>0),L=0,Ze=y(0),u=y(y($n(Qe,y(0)))/u);break e}case 5:{u=y(Qe/y((m+1|0)>>>0)),L=0,Ze=u;break e}case 4:{u=y(Qe/y(m>>>0)),L=0,Ze=y(u*y(.5));break e}default:{L=0,Ze=y(0),u=y(0);break e}}while(!1);if(l=y(Zh+Ze),Lt){tt=y(Qe/y(L|0)),ae=n[Lo>>2]|0,q=Hr,Qe=y(0);do{L=n[ae+(q<<2)>>2]|0;e:do if((n[L+36>>2]|0)!=1){switch(n[L+24>>2]|0){case 1:{if(ha(L,Mr)|0){if(!T)break e;zt=y(zA(L,Mr,cr)),zt=y(zt+y(vr(o,Mr))),zt=y(zt+y(K(L,Mr,Nr))),h[L+400+(n[Ff>>2]<<2)>>2]=zt;break e}break}case 0:if(Bi=(n[(kl(L,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,T&&(Bi=L+400+(n[Ff>>2]<<2)|0,h[Bi>>2]=y(l+y(h[Bi>>2]))),Bi=(n[(Ql(L,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,Za){zt=y(u+y(yn(L,Mr,Nr))),Qe=Wn,l=y(l+y(zt+y(h[L+504>>2])));break e}else{l=y(l+y(u+y(XA(L,Mr,Nr)))),Qe=y($n(Qe,y(XA(L,Ar,Nr))));break e}default:}T&&(zt=y(Ze+y(vr(o,Mr))),Bi=L+400+(n[Ff>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2])))}while(!1);q=q+1|0}while((q|0)!=(fr|0))}else Qe=y(0);if(u=y($h+l),Vh?Ze=y(y(qn(o,Ar,y(lo+Qe),Tu,B))-lo):Ze=Wn,tt=y(y(qn(o,Ar,y(lo+(zh?Wn:Qe)),Tu,B))-lo),Lt&T){q=Hr;do{ae=n[(n[Lo>>2]|0)+(q<<2)>>2]|0;do if((n[ae+36>>2]|0)!=1){if((n[ae+24>>2]|0)==1){if(ha(ae,Ar)|0){if(zt=y(zA(ae,Ar,Wn)),zt=y(zt+y(vr(o,Ar))),zt=y(zt+y(K(ae,Ar,Nr))),L=n[Rf>>2]|0,h[ae+400+(L<<2)>>2]=zt,!(Mt(zt)|0))break}else L=n[Rf>>2]|0;zt=y(vr(o,Ar)),h[ae+400+(L<<2)>>2]=y(zt+y(K(ae,Ar,Nr)));break}L=os(o,ae)|0;do if((L|0)==4){if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){ln=139;break}if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){ln=139;break}if(io(ae,Ar,Wn)|0){l=Le;break}dp=n[ae+908+(n[gc>>2]<<2)>>2]|0,n[oo>>2]=dp,l=y(h[ae+396>>2]),Bi=Mt(l)|0,Qe=(n[S>>2]=dp,y(h[S>>2])),Bi?l=tt:(Tr=y(yn(ae,Ar,Nr)),zt=y(Qe/l),l=y(l*Qe),l=y(Tr+(ui?zt:l))),h[Ll>>2]=l,h[oo>>2]=y(y(yn(ae,Mr,Nr))+Qe),n[ma>>2]=1,n[mc>>2]=1,Cu(ae,Mr,cr,Nr,ma,oo),Cu(ae,Ar,Wn,Nr,mc,Ll),l=y(h[oo>>2]),Tr=y(h[Ll>>2]),zt=ui?l:Tr,l=ui?Tr:l,Bi=((Mt(zt)|0)^1)&1,xl(ae,zt,l,Us,Bi,((Mt(l)|0)^1)&1,Nr,No,1,3493,M)|0,l=Le}else ln=139;while(!1);e:do if((ln|0)==139){ln=0,l=y(Ze-y(XA(ae,Ar,Nr)));do if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){if((n[(Ql(ae,Ar)|0)+4>>2]|0)!=3)break;l=y(Le+y($n(y(0),y(l*y(.5)))));break e}while(!1);if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){l=Le;break}if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){l=y(Le+y($n(y(0),l)));break}switch(L|0){case 1:{l=Le;break e}case 2:{l=y(Le+y(l*y(.5)));break e}default:{l=y(Le+l);break e}}}while(!1);zt=y(Ms+l),Bi=ae+400+(n[Rf>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2]))}while(!1);q=q+1|0}while((q|0)!=(fr|0))}if(Ms=y(Ms+tt),xu=y($n(xu,u)),m=Fo+1|0,fr>>>0>=co>>>0)break;l=cr,Hr=fr,Fo=m}do if(T){if(L=m>>>0>1,!L&&!(jL(o)|0))break;if(!(Mt(Wn)|0)){l=y(Wn-Ms);e:do switch(n[o+12>>2]|0){case 3:{Le=y(Le+l),He=y(0);break}case 2:{Le=y(Le+y(l*y(.5))),He=y(0);break}case 4:{Wn>Ms?He=y(l/y(m>>>0)):He=y(0);break}case 7:if(Wn>Ms){Le=y(Le+y(l/y(m<<1>>>0))),He=y(l/y(m>>>0)),He=L?He:y(0);break e}else{Le=y(Le+y(l*y(.5))),He=y(0);break e}case 6:{He=y(l/y(Fo>>>0)),He=Wn>Ms&L?He:y(0);break}default:He=y(0)}while(!1);if(m|0)for(Lt=1040+(Ar<<2)|0,Gr=976+(Ar<<2)|0,Ye=0,q=0;;){e:do if(q>>>0>>0)for(Qe=y(0),tt=y(0),l=y(0),ae=q;;){L=n[(n[Lo>>2]|0)+(ae<<2)>>2]|0;do if((n[L+36>>2]|0)!=1&&!(n[L+24>>2]|0)){if((n[L+940>>2]|0)!=(Ye|0))break e;if(qL(L,Ar)|0&&(zt=y(h[L+908+(n[Gr>>2]<<2)>>2]),l=y($n(l,y(zt+y(yn(L,Ar,Nr)))))),(os(o,L)|0)!=5)break;ao=y(Yg(L)),ao=y(ao+y(K(L,0,Nr))),zt=y(h[L+912>>2]),zt=y(y(zt+y(yn(L,0,Nr)))-ao),ao=y($n(tt,ao)),zt=y($n(Qe,zt)),Qe=zt,tt=ao,l=y($n(l,y(ao+zt)))}while(!1);if(L=ae+1|0,L>>>0>>0)ae=L;else{ae=L;break}}else tt=y(0),l=y(0),ae=q;while(!1);if(ct=y(He+l),u=Le,Le=y(Le+ct),q>>>0>>0){Ze=y(u+tt),L=q;do{q=n[(n[Lo>>2]|0)+(L<<2)>>2]|0;e:do if((n[q+36>>2]|0)!=1&&!(n[q+24>>2]|0))switch(os(o,q)|0){case 1:{zt=y(u+y(K(q,Ar,Nr))),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 3:{zt=y(y(Le-y(re(q,Ar,Nr)))-y(h[q+908+(n[Gr>>2]<<2)>>2])),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 2:{zt=y(u+y(y(ct-y(h[q+908+(n[Gr>>2]<<2)>>2]))*y(.5))),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 4:{if(zt=y(u+y(K(q,Ar,Nr))),h[q+400+(n[Lt>>2]<<2)>>2]=zt,io(q,Ar,Wn)|0||(ui?(Qe=y(h[q+908>>2]),l=y(Qe+y(yn(q,Mr,Nr))),tt=ct):(tt=y(h[q+912>>2]),tt=y(tt+y(yn(q,Ar,Nr))),l=ct,Qe=y(h[q+908>>2])),mn(l,Qe)|0&&mn(tt,y(h[q+912>>2]))|0))break e;xl(q,l,tt,Us,1,1,Nr,No,1,3501,M)|0;break e}case 5:{h[q+404>>2]=y(y(Ze-y(Yg(q)))+y(zA(q,0,Wn)));break e}default:break e}while(!1);L=L+1|0}while((L|0)!=(ae|0))}if(Ye=Ye+1|0,(Ye|0)==(m|0))break;q=ae}}}while(!1);if(h[o+908>>2]=y(qn(o,2,ku,B,B)),h[o+912>>2]=y(qn(o,0,up,k,B)),yc|0&&(fp=n[o+32>>2]|0,Ap=(yc|0)==2,!(Ap&(fp|0)!=2))?Ap&(fp|0)==2&&(l=y(Qu+cr),l=y($n(y(pd(l,y(Vg(o,Mr,xu,Oo)))),Qu)),ln=198):(l=y(qn(o,Mr,xu,Oo,B)),ln=198),(ln|0)==198&&(h[o+908+(n[976+(Mr<<2)>>2]<<2)>>2]=l),Ec|0&&(hp=n[o+32>>2]|0,gp=(Ec|0)==2,!(gp&(hp|0)!=2))?gp&(hp|0)==2&&(l=y(lo+Wn),l=y($n(y(pd(l,y(Vg(o,Ar,y(lo+Ms),Tu)))),lo)),ln=204):(l=y(qn(o,Ar,y(lo+Ms),Tu,B)),ln=204),(ln|0)==204&&(h[o+908+(n[976+(Ar<<2)>>2]<<2)>>2]=l),T){if((n[pp>>2]|0)==2){q=976+(Ar<<2)|0,ae=1040+(Ar<<2)|0,L=0;do Ye=Is(o,L)|0,n[Ye+24>>2]|0||(dp=n[q>>2]|0,zt=y(h[o+908+(dp<<2)>>2]),Bi=Ye+400+(n[ae>>2]<<2)|0,zt=y(zt-y(h[Bi>>2])),h[Bi>>2]=y(zt-y(h[Ye+908+(dp<<2)>>2]))),L=L+1|0;while((L|0)!=(co|0))}if(A|0){L=ui?yc:d;do WL(o,A,Nr,L,No,Us,M),A=n[A+960>>2]|0;while(A|0)}if(L=(Mr|2|0)==3,q=(Ar|2|0)==3,L|q){A=0;do ae=n[(n[Lo>>2]|0)+(A<<2)>>2]|0,(n[ae+36>>2]|0)!=1&&(L&&i2(o,ae,Mr),q&&i2(o,ae,Ar)),A=A+1|0;while((A|0)!=(co|0))}}}while(!1);I=Ic}function Dh(o,l){o=o|0,l=y(l);var u=0;Ha(o,l>=y(0),3147),u=l==y(0),h[o+4>>2]=u?y(0):l}function YA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=A|0;var d=$e,m=$e,B=0,k=0,T=0;n[2278]=(n[2278]|0)+1,vf(o),io(o,2,l)|0?(d=y(Zr(n[o+992>>2]|0,l)),T=1,d=y(d+y(yn(o,2,l)))):(d=y(Zr(o+380|0,l)),d>=y(0)?T=2:(T=((Mt(l)|0)^1)&1,d=l)),io(o,0,u)|0?(m=y(Zr(n[o+996>>2]|0,u)),k=1,m=y(m+y(yn(o,0,l)))):(m=y(Zr(o+388|0,u)),m>=y(0)?k=2:(k=((Mt(u)|0)^1)&1,m=u)),B=o+976|0,xl(o,d,m,A,T,k,l,u,1,3189,n[B>>2]|0)|0&&(bh(o,n[o+496>>2]|0,l,u,l),VA(o,y(h[(n[B>>2]|0)+4>>2]),y(0),y(0)),s[11696]|0)&&Gg(o,7)}function vf(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;k=I,I=I+32|0,B=k+24|0,m=k+16|0,A=k+8|0,d=k,u=0;do l=o+380+(u<<3)|0,n[o+380+(u<<3)+4>>2]|0&&(T=l,M=n[T+4>>2]|0,L=A,n[L>>2]=n[T>>2],n[L+4>>2]=M,L=o+364+(u<<3)|0,M=n[L+4>>2]|0,T=d,n[T>>2]=n[L>>2],n[T+4>>2]=M,n[m>>2]=n[A>>2],n[m+4>>2]=n[A+4>>2],n[B>>2]=n[d>>2],n[B+4>>2]=n[d+4>>2],Cf(m,B)|0)||(l=o+348+(u<<3)|0),n[o+992+(u<<2)>>2]=l,u=u+1|0;while((u|0)!=2);I=k}function io(o,l,u){o=o|0,l=l|0,u=y(u);var A=0;switch(o=n[o+992+(n[976+(l<<2)>>2]<<2)>>2]|0,n[o+4>>2]|0){case 0:case 3:{o=0;break}case 1:{y(h[o>>2])>2])>2]|0){case 2:{l=y(y(y(h[o>>2])*l)/y(100));break}case 1:{l=y(h[o>>2]);break}default:l=y(le)}return y(l)}function bh(o,l,u,A,d){o=o|0,l=l|0,u=y(u),A=y(A),d=y(d);var m=0,B=$e;l=n[o+944>>2]|0?l:1,m=dr(n[o+4>>2]|0,l)|0,l=by(m,l)|0,u=y(uP(o,m,u)),A=y(uP(o,l,A)),B=y(u+y(K(o,m,d))),h[o+400+(n[1040+(m<<2)>>2]<<2)>>2]=B,u=y(u+y(re(o,m,d))),h[o+400+(n[1e3+(m<<2)>>2]<<2)>>2]=u,u=y(A+y(K(o,l,d))),h[o+400+(n[1040+(l<<2)>>2]<<2)>>2]=u,d=y(A+y(re(o,l,d))),h[o+400+(n[1e3+(l<<2)>>2]<<2)>>2]=d}function VA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=y(A);var d=0,m=0,B=$e,k=$e,T=0,M=0,L=$e,q=0,ae=$e,Ye=$e,Le=$e,Qe=$e;if(l!=y(0)&&(d=o+400|0,Qe=y(h[d>>2]),m=o+404|0,Le=y(h[m>>2]),q=o+416|0,Ye=y(h[q>>2]),M=o+420|0,B=y(h[M>>2]),ae=y(Qe+u),L=y(Le+A),A=y(ae+Ye),k=y(L+B),T=(n[o+988>>2]|0)==1,h[d>>2]=y(ss(Qe,l,0,T)),h[m>>2]=y(ss(Le,l,0,T)),u=y(uU(y(Ye*l),y(1))),mn(u,y(0))|0?m=0:m=(mn(u,y(1))|0)^1,u=y(uU(y(B*l),y(1))),mn(u,y(0))|0?d=0:d=(mn(u,y(1))|0)^1,Qe=y(ss(A,l,T&m,T&(m^1))),h[q>>2]=y(Qe-y(ss(ae,l,0,T))),Qe=y(ss(k,l,T&d,T&(d^1))),h[M>>2]=y(Qe-y(ss(L,l,0,T))),m=(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2,m|0)){d=0;do VA(Is(o,d)|0,l,ae,L),d=d+1|0;while((d|0)!=(m|0))}}function Sy(o,l,u,A,d){switch(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,u|0){case 5:case 0:{o=IZ(n[489]|0,A,d)|0;break}default:o=b6e(A,d)|0}return o|0}function Wg(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;d=I,I=I+16|0,m=d,n[m>>2]=A,Ph(o,0,l,u,m),I=d}function Ph(o,l,u,A,d){if(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,o=o|0?o:956,HZ[n[o+8>>2]&1](o,l,u,A,d)|0,(u|0)==5)Nt();else return}function pc(o,l,u){o=o|0,l=l|0,u=u|0,s[o+l>>0]=u&1}function Dy(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(xh(o,A),kt(o,n[l>>2]|0,n[u>>2]|0,A))}function xh(o,l){o=o|0,l=l|0;var u=0;if((O(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function kt(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function O(o){return o=o|0,1073741823}function K(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+96>>2]|0?o=o+92|0:o=kn(o+60|0,n[1040+(l<<2)>>2]|0,992)|0,y(Je(o,u))}function re(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+104>>2]|0?o=o+100|0:o=kn(o+60|0,n[1e3+(l<<2)>>2]|0,992)|0,y(Je(o,u))}function de(o){return o=o|0,(o|1|0)==3|0}function Je(o,l){return o=o|0,l=y(l),(n[o+4>>2]|0)==3?l=y(0):l=y(Zr(o,l)),y(l)}function At(o,l){return o=o|0,l=l|0,o=n[o>>2]|0,(o|0?o:(l|0)>1?l:1)|0}function dr(o,l){o=o|0,l=l|0;var u=0;e:do if((l|0)==2){switch(o|0){case 2:{o=3;break e}case 3:break;default:{u=4;break e}}o=2}else u=4;while(!1);return o|0}function vr(o,l){o=o|0,l=l|0;var u=$e;return de(l)|0&&n[o+312>>2]|0&&(u=y(h[o+308>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1040+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function Un(o,l){o=o|0,l=l|0;var u=$e;return de(l)|0&&n[o+320>>2]|0&&(u=y(h[o+316>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1e3+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function mi(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return de(l)|0&&n[o+240>>2]|0&&(A=y(Zr(o+236|0,u)),A>=y(0))||(A=y($n(y(Zr(kn(o+204|0,n[1040+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function Cs(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return de(l)|0&&n[o+248>>2]|0&&(A=y(Zr(o+244|0,u)),A>=y(0))||(A=y($n(y(Zr(kn(o+204|0,n[1e3+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function JA(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=$e,T=$e,M=$e,L=$e,q=$e,ae=$e,Ye=0,Le=0,Qe=0;Qe=I,I=I+16|0,Ye=Qe,Le=o+964|0,wi(o,(n[Le>>2]|0)!=0,3519),k=y(Va(o,2,l)),T=y(Va(o,0,l)),M=y(yn(o,2,l)),L=y(yn(o,0,l)),Mt(l)|0?q=l:q=y($n(y(0),y(y(l-M)-k))),Mt(u)|0?ae=u:ae=y($n(y(0),y(y(u-L)-T))),(A|0)==1&(d|0)==1?(h[o+908>>2]=y(qn(o,2,y(l-M),m,m)),l=y(qn(o,0,y(u-L),B,m))):(jZ[n[Le>>2]&1](Ye,o,q,A,ae,d),q=y(k+y(h[Ye>>2])),ae=y(l-M),h[o+908>>2]=y(qn(o,2,(A|2|0)==2?q:ae,m,m)),ae=y(T+y(h[Ye+4>>2])),l=y(u-L),l=y(qn(o,0,(d|2|0)==2?ae:l,B,m))),h[o+912>>2]=l,I=Qe}function lP(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=$e,T=$e,M=$e,L=$e;M=y(Va(o,2,m)),k=y(Va(o,0,m)),L=y(yn(o,2,m)),T=y(yn(o,0,m)),l=y(l-L),h[o+908>>2]=y(qn(o,2,(A|2|0)==2?M:l,m,m)),u=y(u-T),h[o+912>>2]=y(qn(o,0,(d|2|0)==2?k:u,B,m))}function t2(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=0,T=$e,M=$e;return k=(A|0)==2,!(l<=y(0)&k)&&!(u<=y(0)&(d|0)==2)&&!((A|0)==1&(d|0)==1)?o=0:(T=y(yn(o,0,m)),M=y(yn(o,2,m)),k=l>2]=y(qn(o,2,k?y(0):l,m,m)),l=y(u-T),k=u>2]=y(qn(o,0,k?y(0):l,B,m)),o=1),o|0}function by(o,l){return o=o|0,l=l|0,Jg(o)|0?o=dr(2,l)|0:o=0,o|0}function kh(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(mi(o,l,u)),y(u+y(vr(o,l)))}function r2(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(Cs(o,l,u)),y(u+y(Un(o,l)))}function Va(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(kh(o,l,u)),y(A+y(r2(o,l,u)))}function n2(o){return o=o|0,n[o+24>>2]|0?o=0:y(KA(o))!=y(0)?o=1:o=y(Qh(o))!=y(0),o|0}function KA(o){o=o|0;var l=$e;if(n[o+944>>2]|0){if(l=y(h[o+44>>2]),Mt(l)|0)return l=y(h[o+40>>2]),o=l>y(0)&((Mt(l)|0)^1),y(o?l:y(0))}else l=y(0);return y(l)}function Qh(o){o=o|0;var l=$e,u=0,A=$e;do if(n[o+944>>2]|0){if(l=y(h[o+48>>2]),Mt(l)|0){if(u=s[(n[o+976>>2]|0)+2>>0]|0,!(u<<24>>24)&&(A=y(h[o+40>>2]),A>24?y(1):y(0)}}else l=y(0);while(!1);return y(l)}function Py(o){o=o|0;var l=0,u=0;if(eE(o+400|0,0,540)|0,s[o+985>>0]=1,te(o),u=Mi(o)|0,u|0){l=o+948|0,o=0;do Py(n[(n[l>>2]|0)+(o<<2)>>2]|0),o=o+1|0;while((o|0)!=(u|0))}}function cP(o,l,u,A,d,m,B,k,T,M){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=y(m),B=y(B),k=k|0,T=T|0,M=M|0;var L=0,q=$e,ae=0,Ye=0,Le=$e,Qe=$e,tt=0,Ze=$e,ct=0,He=$e,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,To=0;Hn=I,I=I+16|0,Gr=Hn+12|0,fr=Hn+8|0,$t=Hn+4|0,Tr=Hn,cr=dr(n[o+4>>2]|0,T)|0,We=de(cr)|0,q=y(Zr(YL(l)|0,We?m:B)),Lt=io(l,2,m)|0,Hr=io(l,0,B)|0;do if(!(Mt(q)|0)&&!(Mt(We?u:d)|0)){if(L=l+504|0,!(Mt(y(h[L>>2]))|0)&&(!(s2(n[l+976>>2]|0,0)|0)||(n[l+500>>2]|0)==(n[2278]|0)))break;h[L>>2]=y($n(q,y(Va(l,cr,m))))}else ae=7;while(!1);do if((ae|0)==7){if(ct=We^1,!(ct|Lt^1)){B=y(Zr(n[l+992>>2]|0,m)),h[l+504>>2]=y($n(B,y(Va(l,2,m))));break}if(!(We|Hr^1)){B=y(Zr(n[l+996>>2]|0,B)),h[l+504>>2]=y($n(B,y(Va(l,0,m))));break}h[Gr>>2]=y(le),h[fr>>2]=y(le),n[$t>>2]=0,n[Tr>>2]=0,Ze=y(yn(l,2,m)),He=y(yn(l,0,m)),Lt?(Le=y(Ze+y(Zr(n[l+992>>2]|0,m))),h[Gr>>2]=Le,n[$t>>2]=1,Ye=1):(Ye=0,Le=y(le)),Hr?(q=y(He+y(Zr(n[l+996>>2]|0,B))),h[fr>>2]=q,n[Tr>>2]=1,L=1):(L=0,q=y(le)),ae=n[o+32>>2]|0,We&(ae|0)==2?ae=2:Mt(Le)|0&&!(Mt(u)|0)&&(h[Gr>>2]=u,n[$t>>2]=2,Ye=2,Le=u),!((ae|0)==2&ct)&&Mt(q)|0&&!(Mt(d)|0)&&(h[fr>>2]=d,n[Tr>>2]=2,L=2,q=d),Qe=y(h[l+396>>2]),tt=Mt(Qe)|0;do if(tt)ae=Ye;else{if((Ye|0)==1&ct){h[fr>>2]=y(y(Le-Ze)/Qe),n[Tr>>2]=1,L=1,ae=1;break}We&(L|0)==1?(h[Gr>>2]=y(Qe*y(q-He)),n[$t>>2]=1,L=1,ae=1):ae=Ye}while(!1);To=Mt(u)|0,Ye=(os(o,l)|0)!=4,!(We|Lt|((A|0)!=1|To)|(Ye|(ae|0)==1))&&(h[Gr>>2]=u,n[$t>>2]=1,!tt)&&(h[fr>>2]=y(y(u-Ze)/Qe),n[Tr>>2]=1,L=1),!(Hr|ct|((k|0)!=1|(Mt(d)|0))|(Ye|(L|0)==1))&&(h[fr>>2]=d,n[Tr>>2]=1,!tt)&&(h[Gr>>2]=y(Qe*y(d-He)),n[$t>>2]=1),Cu(l,2,m,m,$t,Gr),Cu(l,0,B,m,Tr,fr),u=y(h[Gr>>2]),d=y(h[fr>>2]),xl(l,u,d,T,n[$t>>2]|0,n[Tr>>2]|0,m,B,0,3565,M)|0,B=y(h[l+908+(n[976+(cr<<2)>>2]<<2)>>2]),h[l+504>>2]=y($n(B,y(Va(l,cr,m))))}while(!1);n[l+500>>2]=n[2278],I=Hn}function qn(o,l,u,A,d){return o=o|0,l=l|0,u=y(u),A=y(A),d=y(d),A=y(Vg(o,l,u,A)),y($n(A,y(Va(o,l,d))))}function os(o,l){return o=o|0,l=l|0,l=l+20|0,l=n[(n[l>>2]|0?l:o+16|0)>>2]|0,(l|0)==5&&Jg(n[o+4>>2]|0)|0&&(l=1),l|0}function kl(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+96>>2]|0?l=4:l=n[1040+(l<<2)>>2]|0,o+60+(l<<3)|0}function Ql(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+104>>2]|0?l=5:l=n[1e3+(l<<2)>>2]|0,o+60+(l<<3)|0}function Cu(o,l,u,A,d,m){switch(o=o|0,l=l|0,u=y(u),A=y(A),d=d|0,m=m|0,u=y(Zr(o+380+(n[976+(l<<2)>>2]<<3)|0,u)),u=y(u+y(yn(o,l,A))),n[d>>2]|0){case 2:case 1:{d=Mt(u)|0,A=y(h[m>>2]),h[m>>2]=d|A>2]=2,h[m>>2]=u);break}default:}}function ha(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,4,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1040+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function zA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,4,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1040+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Zr(A,u))),y(u)}function XA(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),A=y(A+y(K(o,l,u))),y(A+y(re(o,l,u)))}function jL(o){o=o|0;var l=0,u=0,A=0;e:do if(Jg(n[o+4>>2]|0)|0)l=0;else if((n[o+16>>2]|0)!=5)if(u=Mi(o)|0,!u)l=0;else for(l=0;;){if(A=Is(o,l)|0,!(n[A+24>>2]|0)&&(n[A+20>>2]|0)==5){l=1;break e}if(l=l+1|0,l>>>0>=u>>>0){l=0;break}}else l=1;while(!1);return l|0}function qL(o,l){o=o|0,l=l|0;var u=$e;return u=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),u>=y(0)&((Mt(u)|0)^1)|0}function Yg(o){o=o|0;var l=$e,u=0,A=0,d=0,m=0,B=0,k=0,T=$e;if(u=n[o+968>>2]|0,u)T=y(h[o+908>>2]),l=y(h[o+912>>2]),l=y(LZ[u&0](o,T,l)),wi(o,(Mt(l)|0)^1,3573);else{m=Mi(o)|0;do if(m|0){for(u=0,d=0;;){if(A=Is(o,d)|0,n[A+940>>2]|0){B=8;break}if((n[A+24>>2]|0)!=1)if(k=(os(o,A)|0)==5,k){u=A;break}else u=u|0?u:A;if(d=d+1|0,d>>>0>=m>>>0){B=8;break}}if((B|0)==8&&!u)break;return l=y(Yg(u)),y(l+y(h[u+404>>2]))}while(!1);l=y(h[o+912>>2])}return y(l)}function Vg(o,l,u,A){o=o|0,l=l|0,u=y(u),A=y(A);var d=$e,m=0;return Jg(l)|0?(l=1,m=3):de(l)|0?(l=0,m=3):(A=y(le),d=y(le)),(m|0)==3&&(d=y(Zr(o+364+(l<<3)|0,A)),A=y(Zr(o+380+(l<<3)|0,A))),m=A=y(0)&((Mt(A)|0)^1)),u=m?A:u,m=d>=y(0)&((Mt(d)|0)^1)&u>2]|0,m)|0,Le=by(tt,m)|0,Qe=de(tt)|0,q=y(yn(l,2,u)),ae=y(yn(l,0,u)),io(l,2,u)|0?k=y(q+y(Zr(n[l+992>>2]|0,u))):ha(l,2)|0&&xy(l,2)|0?(k=y(h[o+908>>2]),T=y(vr(o,2)),T=y(k-y(T+y(Un(o,2)))),k=y(zA(l,2,u)),k=y(qn(l,2,y(T-y(k+y(Th(l,2,u)))),u,u))):k=y(le),io(l,0,d)|0?T=y(ae+y(Zr(n[l+996>>2]|0,d))):ha(l,0)|0&&xy(l,0)|0?(T=y(h[o+912>>2]),ct=y(vr(o,0)),ct=y(T-y(ct+y(Un(o,0)))),T=y(zA(l,0,d)),T=y(qn(l,0,y(ct-y(T+y(Th(l,0,d)))),d,u))):T=y(le),M=Mt(k)|0,L=Mt(T)|0;do if(M^L&&(Ye=y(h[l+396>>2]),!(Mt(Ye)|0)))if(M){k=y(q+y(y(T-ae)*Ye));break}else{ct=y(ae+y(y(k-q)/Ye)),T=L?ct:T;break}while(!1);L=Mt(k)|0,M=Mt(T)|0,L|M&&(He=(L^1)&1,A=u>y(0)&((A|0)!=0&L),k=Qe?k:A?u:k,xl(l,k,T,m,Qe?He:A?2:He,L&(M^1)&1,k,T,0,3623,B)|0,k=y(h[l+908>>2]),k=y(k+y(yn(l,2,u))),T=y(h[l+912>>2]),T=y(T+y(yn(l,0,u)))),xl(l,k,T,m,1,1,k,T,1,3635,B)|0,xy(l,tt)|0&&!(ha(l,tt)|0)?(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(Un(o,tt))),ct=y(ct-y(re(l,tt,u))),ct=y(ct-y(Th(l,tt,Qe?u:d))),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct):Ze=21;do if((Ze|0)==21){if(!(ha(l,tt)|0)&&(n[o+8>>2]|0)==1){He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct;break}!(ha(l,tt)|0)&&(n[o+8>>2]|0)==2&&(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct)}while(!1);xy(l,Le)|0&&!(ha(l,Le)|0)?(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(Un(o,Le))),ct=y(ct-y(re(l,Le,u))),ct=y(ct-y(Th(l,Le,Qe?d:u))),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct):Ze=30;do if((Ze|0)==30&&!(ha(l,Le)|0)){if((os(o,l)|0)==2){He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct;break}He=(os(o,l)|0)==3,He^(n[o+28>>2]|0)==2&&(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct)}while(!1)}function i2(o,l,u){o=o|0,l=l|0,u=u|0;var A=$e,d=0;d=n[976+(u<<2)>>2]|0,A=y(h[l+908+(d<<2)>>2]),A=y(y(h[o+908+(d<<2)>>2])-A),A=y(A-y(h[l+400+(n[1040+(u<<2)>>2]<<2)>>2])),h[l+400+(n[1e3+(u<<2)>>2]<<2)>>2]=A}function Jg(o){return o=o|0,(o|1|0)==1|0}function YL(o){o=o|0;var l=$e;switch(n[o+56>>2]|0){case 0:case 3:{l=y(h[o+40>>2]),l>y(0)&((Mt(l)|0)^1)?o=s[(n[o+976>>2]|0)+2>>0]|0?1056:992:o=1056;break}default:o=o+52|0}return o|0}function s2(o,l){return o=o|0,l=l|0,(s[o+l>>0]|0)!=0|0}function xy(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,5,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1e3+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function Th(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,5,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1e3+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Zr(A,u))),y(u)}function uP(o,l,u){return o=o|0,l=l|0,u=y(u),ha(o,l)|0?u=y(zA(o,l,u)):u=y(-y(Th(o,l,u))),y(u)}function fP(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function ky(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function AP(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Qy(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function pP(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;if(B=o+4|0,k=n[B>>2]|0,d=k-A|0,m=d>>2,o=l+(m<<2)|0,o>>>0>>0){A=k;do n[A>>2]=n[o>>2],o=o+4|0,A=(n[B>>2]|0)+4|0,n[B>>2]=A;while(o>>>0>>0)}m|0&&Q2(k+(0-m<<2)|0,l|0,d|0)|0}function hP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return k=l+4|0,T=n[k>>2]|0,d=n[o>>2]|0,B=u,m=B-d|0,A=T+(0-(m>>2)<<2)|0,n[k>>2]=A,(m|0)>0&&Qr(A|0,d|0,m|0)|0,d=o+4|0,m=l+8|0,A=(n[d>>2]|0)-B|0,(A|0)>0&&(Qr(n[m>>2]|0,u|0,A|0)|0,n[m>>2]=(n[m>>2]|0)+(A>>>2<<2)),B=n[o>>2]|0,n[o>>2]=n[k>>2],n[k>>2]=B,B=n[d>>2]|0,n[d>>2]=n[m>>2],n[m>>2]=B,B=o+8|0,u=l+12|0,o=n[B>>2]|0,n[B>>2]=n[u>>2],n[u>>2]=o,n[l>>2]=n[k>>2],T|0}function o2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(B=n[l>>2]|0,m=n[u>>2]|0,(B|0)!=(m|0)){d=o+8|0,u=((m+-4-B|0)>>>2)+1|0,o=B,A=n[d>>2]|0;do n[A>>2]=n[o>>2],A=(n[d>>2]|0)+4|0,n[d>>2]=A,o=o+4|0;while((o|0)!=(m|0));n[l>>2]=B+(u<<2)}}function a2(){ua()}function gP(){var o=0;return o=Kt(4)|0,l2(o),o|0}function l2(o){o=o|0,n[o>>2]=Ac()|0}function dP(o){o=o|0,o|0&&(Kg(o),It(o))}function Kg(o){o=o|0,st(n[o>>2]|0)}function VL(o,l,u){o=o|0,l=l|0,u=u|0,pc(n[o>>2]|0,l,u)}function Ty(o,l){o=o|0,l=y(l),Dh(n[o>>2]|0,l)}function Ry(o,l){return o=o|0,l=l|0,s2(n[o>>2]|0,l)|0}function Fy(){var o=0;return o=Kt(8)|0,zg(o,0),o|0}function zg(o,l){o=o|0,l=l|0,l?l=fa(n[l>>2]|0)|0:l=ns()|0,n[o>>2]=l,n[o+4>>2]=0,Tn(l,o)}function Ny(o){o=o|0;var l=0;return l=Kt(8)|0,zg(l,o),l|0}function Xg(o){o=o|0,o|0&&(Oy(o),It(o))}function Oy(o){o=o|0;var l=0;uc(n[o>>2]|0),l=o+4|0,o=n[l>>2]|0,n[l>>2]=0,o|0&&(Sf(o),It(o))}function Sf(o){o=o|0,Df(o)}function Df(o){o=o|0,o=n[o>>2]|0,o|0&&Na(o|0)}function c2(o){return o=o|0,Ga(o)|0}function u2(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Sf(l),It(l)),fc(n[o>>2]|0)}function Ly(o,l){o=o|0,l=l|0,An(n[o>>2]|0,n[l>>2]|0)}function JL(o,l){o=o|0,l=l|0,wh(n[o>>2]|0,l)}function KL(o,l,u){o=o|0,l=l|0,u=+u,Cy(n[o>>2]|0,l,y(u))}function My(o,l,u){o=o|0,l=l|0,u=+u,wy(n[o>>2]|0,l,y(u))}function f2(o,l){o=o|0,l=l|0,Eh(n[o>>2]|0,l)}function A2(o,l){o=o|0,l=l|0,So(n[o>>2]|0,l)}function xr(o,l){o=o|0,l=l|0,Ch(n[o>>2]|0,l)}function so(o,l){o=o|0,l=l|0,my(n[o>>2]|0,l)}function Xi(o,l){o=o|0,l=l|0,Ng(n[o>>2]|0,l)}function Ns(o,l){o=o|0,l=l|0,vo(n[o>>2]|0,l)}function ZA(o,l,u){o=o|0,l=l|0,u=+u,HA(n[o>>2]|0,l,y(u))}function p2(o,l,u){o=o|0,l=l|0,u=+u,Y(n[o>>2]|0,l,y(u))}function ws(o,l){o=o|0,l=l|0,jA(n[o>>2]|0,l)}function Uy(o,l){o=o|0,l=l|0,Ey(n[o>>2]|0,l)}function Rh(o,l){o=o|0,l=l|0,Do(n[o>>2]|0,l)}function Zg(o,l){o=o|0,l=+l,Bh(n[o>>2]|0,y(l))}function Fh(o,l){o=o|0,l=+l,bl(n[o>>2]|0,y(l))}function h2(o,l){o=o|0,l=+l,Iy(n[o>>2]|0,y(l))}function g2(o,l){o=o|0,l=+l,Lg(n[o>>2]|0,y(l))}function d2(o,l){o=o|0,l=+l,Dl(n[o>>2]|0,y(l))}function m2(o,l){o=o|0,l=+l,Mg(n[o>>2]|0,y(l))}function bf(o,l){o=o|0,l=+l,e2(n[o>>2]|0,y(l))}function sr(o){o=o|0,vh(n[o>>2]|0)}function _y(o,l){o=o|0,l=+l,zi(n[o>>2]|0,y(l))}function y2(o,l){o=o|0,l=+l,yf(n[o>>2]|0,y(l))}function hc(o){o=o|0,qa(n[o>>2]|0)}function Pf(o,l){o=o|0,l=+l,du(n[o>>2]|0,y(l))}function $g(o,l){o=o|0,l=+l,Ef(n[o>>2]|0,y(l))}function ed(o,l){o=o|0,l=+l,di(n[o>>2]|0,y(l))}function E2(o,l){o=o|0,l=+l,GA(n[o>>2]|0,y(l))}function I2(o,l){o=o|0,l=+l,Aa(n[o>>2]|0,y(l))}function wu(o,l){o=o|0,l=+l,Ya(n[o>>2]|0,y(l))}function td(o,l){o=o|0,l=+l,Sh(n[o>>2]|0,y(l))}function C2(o,l){o=o|0,l=+l,Hg(n[o>>2]|0,y(l))}function Hy(o,l){o=o|0,l=+l,qA(n[o>>2]|0,y(l))}function Bu(o,l,u){o=o|0,l=l|0,u=+u,gu(n[o>>2]|0,l,y(u))}function jy(o,l,u){o=o|0,l=l|0,u=+u,bo(n[o>>2]|0,l,y(u))}function rd(o,l,u){o=o|0,l=l|0,u=+u,mf(n[o>>2]|0,l,y(u))}function nd(o){return o=o|0,Fg(n[o>>2]|0)|0}function ko(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,_A(d,n[l>>2]|0,u),Bs(o,d),I=A}function Bs(o,l){o=o|0,l=l|0,Tl(o,n[l+4>>2]|0,+y(h[l>>2]))}function Tl(o,l,u){o=o|0,l=l|0,u=+u,n[o>>2]=l,E[o+8>>3]=u}function Gy(o){return o=o|0,$1(n[o>>2]|0)|0}function ga(o){return o=o|0,Ih(n[o>>2]|0)|0}function mP(o){return o=o|0,hu(n[o>>2]|0)|0}function Nh(o){return o=o|0,Z1(n[o>>2]|0)|0}function w2(o){return o=o|0,Og(n[o>>2]|0)|0}function zL(o){return o=o|0,yy(n[o>>2]|0)|0}function yP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,xt(d,n[l>>2]|0,u),Bs(o,d),I=A}function EP(o){return o=o|0,df(n[o>>2]|0)|0}function qy(o){return o=o|0,Sl(n[o>>2]|0)|0}function B2(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,UA(A,n[l>>2]|0),Bs(o,A),I=u}function Oh(o){return o=o|0,+ +y(li(n[o>>2]|0))}function IP(o){return o=o|0,+ +y(qi(n[o>>2]|0))}function CP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,ur(A,n[l>>2]|0),Bs(o,A),I=u}function id(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Ug(A,n[l>>2]|0),Bs(o,A),I=u}function XL(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,wt(A,n[l>>2]|0),Bs(o,A),I=u}function ZL(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Wa(A,n[l>>2]|0),Bs(o,A),I=u}function wP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,_g(A,n[l>>2]|0),Bs(o,A),I=u}function BP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,vy(A,n[l>>2]|0),Bs(o,A),I=u}function $A(o){return o=o|0,+ +y(jg(n[o>>2]|0))}function $L(o,l){return o=o|0,l=l|0,+ +y(By(n[o>>2]|0,l))}function eM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,yt(d,n[l>>2]|0,u),Bs(o,d),I=A}function vu(o,l,u){o=o|0,l=l|0,u=u|0,lr(n[o>>2]|0,n[l>>2]|0,u)}function tM(o,l){o=o|0,l=l|0,gf(n[o>>2]|0,n[l>>2]|0)}function vP(o){return o=o|0,Mi(n[o>>2]|0)|0}function rM(o){return o=o|0,o=Et(n[o>>2]|0)|0,o?o=c2(o)|0:o=0,o|0}function SP(o,l){return o=o|0,l=l|0,o=Is(n[o>>2]|0,l)|0,o?o=c2(o)|0:o=0,o|0}function xf(o,l){o=o|0,l=l|0;var u=0,A=0;A=Kt(4)|0,DP(A,l),u=o+4|0,l=n[u>>2]|0,n[u>>2]=A,l|0&&(Sf(l),It(l)),St(n[o>>2]|0,1)}function DP(o,l){o=o|0,l=l|0,oM(o,l)}function nM(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,bP(k,Ga(l)|0,+u,A,+d,m),h[o>>2]=y(+E[k>>3]),h[o+4>>2]=y(+E[k+8>>3]),I=B}function bP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0,k=0,T=0,M=0,L=0;B=I,I=I+32|0,L=B+8|0,M=B+20|0,T=B,k=B+16|0,E[L>>3]=u,n[M>>2]=A,E[T>>3]=d,n[k>>2]=m,Wy(o,n[l+4>>2]|0,L,M,T,k),I=B}function Wy(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,Fl(k),l=Os(l)|0,PP(o,l,+E[u>>3],n[A>>2]|0,+E[d>>3],n[m>>2]|0),Nl(k),I=B}function Os(o){return o=o|0,n[o>>2]|0}function PP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0;B=da(v2()|0)|0,u=+Ja(u),A=Yy(A)|0,d=+Ja(d),iM(o,Kn(0,B|0,l|0,+u,A|0,+d,Yy(m)|0)|0)}function v2(){var o=0;return s[7608]|0||(D2(9120),o=7608,n[o>>2]=1,n[o+4>>2]=0),9120}function da(o){return o=o|0,n[o+8>>2]|0}function Ja(o){return o=+o,+ +kf(o)}function Yy(o){return o=o|0,sd(o)|0}function iM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=l,A&1?(Ka(u,0),Me(A|0,u|0)|0,S2(o,u),sM(u)):(n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]),I=d}function Ka(o,l){o=o|0,l=l|0,Su(o,l),n[o+8>>2]=0,s[o+24>>0]=0}function S2(o,l){o=o|0,l=l|0,l=l+8|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]}function sM(o){o=o|0,s[o+24>>0]=0}function Su(o,l){o=o|0,l=l|0,n[o>>2]=l}function sd(o){return o=o|0,o|0}function kf(o){return o=+o,+o}function D2(o){o=o|0,Qo(o,b2()|0,4)}function b2(){return 1064}function Qo(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=Gi(l|0,u+1|0)|0}function oM(o,l){o=o|0,l=l|0,l=n[l>>2]|0,n[o>>2]=l,au(l|0)}function xP(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Sf(l),It(l)),St(n[o>>2]|0,0)}function kP(o){o=o|0,bt(n[o>>2]|0)}function Vy(o){return o=o|0,tr(n[o>>2]|0)|0}function aM(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,YA(n[o>>2]|0,y(l),y(u),A)}function lM(o){return o=o|0,+ +y(mu(n[o>>2]|0))}function v(o){return o=o|0,+ +y(If(n[o>>2]|0))}function D(o){return o=o|0,+ +y(yu(n[o>>2]|0))}function Q(o){return o=o|0,+ +y(Rs(n[o>>2]|0))}function H(o){return o=o|0,+ +y(Eu(n[o>>2]|0))}function V(o){return o=o|0,+ +y(Gn(n[o>>2]|0))}function ne(o,l){o=o|0,l=l|0,E[o>>3]=+y(mu(n[l>>2]|0)),E[o+8>>3]=+y(If(n[l>>2]|0)),E[o+16>>3]=+y(yu(n[l>>2]|0)),E[o+24>>3]=+y(Rs(n[l>>2]|0)),E[o+32>>3]=+y(Eu(n[l>>2]|0)),E[o+40>>3]=+y(Gn(n[l>>2]|0))}function Se(o,l){return o=o|0,l=l|0,+ +y(is(n[o>>2]|0,l))}function _e(o,l){return o=o|0,l=l|0,+ +y(Pi(n[o>>2]|0,l))}function pt(o,l){return o=o|0,l=l|0,+ +y(WA(n[o>>2]|0,l))}function Wt(){return Qn()|0}function Sr(){Lr(),Zt(),zn(),yi(),za(),et()}function Lr(){p4e(11713,4938,1)}function Zt(){T_e(10448)}function zn(){p_e(10408)}function yi(){OUe(10324)}function za(){qLe(10096)}function et(){qe(9132)}function qe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,To=0,Ro=0,Fo=0,Za=0,Wh=0,Yh=0,gc=0,Vh=0,Rf=0,Ff=0,Jh=0,Kh=0,zh=0,ln=0,dc=0,Xh=0,Pu=0,Zh=0,$h=0,Nf=0,Of=0,xu=0,oo=0,Ll=0,ma=0,mc=0,op=0,ap=0,Lf=0,lp=0,cp=0,ao=0,Ms=0,yc=0,Wn=0,up=0,No=0,ku=0,Oo=0,Qu=0,fp=0,Ap=0,Tu=0,lo=0,Ec=0,pp=0,hp=0,gp=0,Nr=0,ui=0,Us=0,Lo=0,co=0,Mr=0,Ar=0,Ic=0;l=I,I=I+672|0,u=l+656|0,Ic=l+648|0,Ar=l+640|0,Mr=l+632|0,co=l+624|0,Lo=l+616|0,Us=l+608|0,ui=l+600|0,Nr=l+592|0,gp=l+584|0,hp=l+576|0,pp=l+568|0,Ec=l+560|0,lo=l+552|0,Tu=l+544|0,Ap=l+536|0,fp=l+528|0,Qu=l+520|0,Oo=l+512|0,ku=l+504|0,No=l+496|0,up=l+488|0,Wn=l+480|0,yc=l+472|0,Ms=l+464|0,ao=l+456|0,cp=l+448|0,lp=l+440|0,Lf=l+432|0,ap=l+424|0,op=l+416|0,mc=l+408|0,ma=l+400|0,Ll=l+392|0,oo=l+384|0,xu=l+376|0,Of=l+368|0,Nf=l+360|0,$h=l+352|0,Zh=l+344|0,Pu=l+336|0,Xh=l+328|0,dc=l+320|0,ln=l+312|0,zh=l+304|0,Kh=l+296|0,Jh=l+288|0,Ff=l+280|0,Rf=l+272|0,Vh=l+264|0,gc=l+256|0,Yh=l+248|0,Wh=l+240|0,Za=l+232|0,Fo=l+224|0,Ro=l+216|0,To=l+208|0,Hn=l+200|0,cr=l+192|0,Hr=l+184|0,Tr=l+176|0,$t=l+168|0,fr=l+160|0,Gr=l+152|0,Lt=l+144|0,We=l+136|0,He=l+128|0,ct=l+120|0,Ze=l+112|0,tt=l+104|0,Qe=l+96|0,Le=l+88|0,Ye=l+80|0,ae=l+72|0,q=l+64|0,L=l+56|0,M=l+48|0,T=l+40|0,k=l+32|0,B=l+24|0,m=l+16|0,d=l+8|0,A=l,gt(o,3646),Xt(o,3651,2)|0,Dr(o,3665,2)|0,Zn(o,3682,18)|0,n[Ic>>2]=19,n[Ic+4>>2]=0,n[u>>2]=n[Ic>>2],n[u+4>>2]=n[Ic+4>>2],kr(o,3690,u)|0,n[Ar>>2]=1,n[Ar+4>>2]=0,n[u>>2]=n[Ar>>2],n[u+4>>2]=n[Ar+4>>2],Rn(o,3696,u)|0,n[Mr>>2]=2,n[Mr+4>>2]=0,n[u>>2]=n[Mr>>2],n[u+4>>2]=n[Mr+4>>2],_n(o,3706,u)|0,n[co>>2]=1,n[co+4>>2]=0,n[u>>2]=n[co>>2],n[u+4>>2]=n[co+4>>2],zr(o,3722,u)|0,n[Lo>>2]=2,n[Lo+4>>2]=0,n[u>>2]=n[Lo>>2],n[u+4>>2]=n[Lo+4>>2],zr(o,3734,u)|0,n[Us>>2]=3,n[Us+4>>2]=0,n[u>>2]=n[Us>>2],n[u+4>>2]=n[Us+4>>2],_n(o,3753,u)|0,n[ui>>2]=4,n[ui+4>>2]=0,n[u>>2]=n[ui>>2],n[u+4>>2]=n[ui+4>>2],_n(o,3769,u)|0,n[Nr>>2]=5,n[Nr+4>>2]=0,n[u>>2]=n[Nr>>2],n[u+4>>2]=n[Nr+4>>2],_n(o,3783,u)|0,n[gp>>2]=6,n[gp+4>>2]=0,n[u>>2]=n[gp>>2],n[u+4>>2]=n[gp+4>>2],_n(o,3796,u)|0,n[hp>>2]=7,n[hp+4>>2]=0,n[u>>2]=n[hp>>2],n[u+4>>2]=n[hp+4>>2],_n(o,3813,u)|0,n[pp>>2]=8,n[pp+4>>2]=0,n[u>>2]=n[pp>>2],n[u+4>>2]=n[pp+4>>2],_n(o,3825,u)|0,n[Ec>>2]=3,n[Ec+4>>2]=0,n[u>>2]=n[Ec>>2],n[u+4>>2]=n[Ec+4>>2],zr(o,3843,u)|0,n[lo>>2]=4,n[lo+4>>2]=0,n[u>>2]=n[lo>>2],n[u+4>>2]=n[lo+4>>2],zr(o,3853,u)|0,n[Tu>>2]=9,n[Tu+4>>2]=0,n[u>>2]=n[Tu>>2],n[u+4>>2]=n[Tu+4>>2],_n(o,3870,u)|0,n[Ap>>2]=10,n[Ap+4>>2]=0,n[u>>2]=n[Ap>>2],n[u+4>>2]=n[Ap+4>>2],_n(o,3884,u)|0,n[fp>>2]=11,n[fp+4>>2]=0,n[u>>2]=n[fp>>2],n[u+4>>2]=n[fp+4>>2],_n(o,3896,u)|0,n[Qu>>2]=1,n[Qu+4>>2]=0,n[u>>2]=n[Qu>>2],n[u+4>>2]=n[Qu+4>>2],ci(o,3907,u)|0,n[Oo>>2]=2,n[Oo+4>>2]=0,n[u>>2]=n[Oo>>2],n[u+4>>2]=n[Oo+4>>2],ci(o,3915,u)|0,n[ku>>2]=3,n[ku+4>>2]=0,n[u>>2]=n[ku>>2],n[u+4>>2]=n[ku+4>>2],ci(o,3928,u)|0,n[No>>2]=4,n[No+4>>2]=0,n[u>>2]=n[No>>2],n[u+4>>2]=n[No+4>>2],ci(o,3948,u)|0,n[up>>2]=5,n[up+4>>2]=0,n[u>>2]=n[up>>2],n[u+4>>2]=n[up+4>>2],ci(o,3960,u)|0,n[Wn>>2]=6,n[Wn+4>>2]=0,n[u>>2]=n[Wn>>2],n[u+4>>2]=n[Wn+4>>2],ci(o,3974,u)|0,n[yc>>2]=7,n[yc+4>>2]=0,n[u>>2]=n[yc>>2],n[u+4>>2]=n[yc+4>>2],ci(o,3983,u)|0,n[Ms>>2]=20,n[Ms+4>>2]=0,n[u>>2]=n[Ms>>2],n[u+4>>2]=n[Ms+4>>2],kr(o,3999,u)|0,n[ao>>2]=8,n[ao+4>>2]=0,n[u>>2]=n[ao>>2],n[u+4>>2]=n[ao+4>>2],ci(o,4012,u)|0,n[cp>>2]=9,n[cp+4>>2]=0,n[u>>2]=n[cp>>2],n[u+4>>2]=n[cp+4>>2],ci(o,4022,u)|0,n[lp>>2]=21,n[lp+4>>2]=0,n[u>>2]=n[lp>>2],n[u+4>>2]=n[lp+4>>2],kr(o,4039,u)|0,n[Lf>>2]=10,n[Lf+4>>2]=0,n[u>>2]=n[Lf>>2],n[u+4>>2]=n[Lf+4>>2],ci(o,4053,u)|0,n[ap>>2]=11,n[ap+4>>2]=0,n[u>>2]=n[ap>>2],n[u+4>>2]=n[ap+4>>2],ci(o,4065,u)|0,n[op>>2]=12,n[op+4>>2]=0,n[u>>2]=n[op>>2],n[u+4>>2]=n[op+4>>2],ci(o,4084,u)|0,n[mc>>2]=13,n[mc+4>>2]=0,n[u>>2]=n[mc>>2],n[u+4>>2]=n[mc+4>>2],ci(o,4097,u)|0,n[ma>>2]=14,n[ma+4>>2]=0,n[u>>2]=n[ma>>2],n[u+4>>2]=n[ma+4>>2],ci(o,4117,u)|0,n[Ll>>2]=15,n[Ll+4>>2]=0,n[u>>2]=n[Ll>>2],n[u+4>>2]=n[Ll+4>>2],ci(o,4129,u)|0,n[oo>>2]=16,n[oo+4>>2]=0,n[u>>2]=n[oo>>2],n[u+4>>2]=n[oo+4>>2],ci(o,4148,u)|0,n[xu>>2]=17,n[xu+4>>2]=0,n[u>>2]=n[xu>>2],n[u+4>>2]=n[xu+4>>2],ci(o,4161,u)|0,n[Of>>2]=18,n[Of+4>>2]=0,n[u>>2]=n[Of>>2],n[u+4>>2]=n[Of+4>>2],ci(o,4181,u)|0,n[Nf>>2]=5,n[Nf+4>>2]=0,n[u>>2]=n[Nf>>2],n[u+4>>2]=n[Nf+4>>2],zr(o,4196,u)|0,n[$h>>2]=6,n[$h+4>>2]=0,n[u>>2]=n[$h>>2],n[u+4>>2]=n[$h+4>>2],zr(o,4206,u)|0,n[Zh>>2]=7,n[Zh+4>>2]=0,n[u>>2]=n[Zh>>2],n[u+4>>2]=n[Zh+4>>2],zr(o,4217,u)|0,n[Pu>>2]=3,n[Pu+4>>2]=0,n[u>>2]=n[Pu>>2],n[u+4>>2]=n[Pu+4>>2],Du(o,4235,u)|0,n[Xh>>2]=1,n[Xh+4>>2]=0,n[u>>2]=n[Xh>>2],n[u+4>>2]=n[Xh+4>>2],cM(o,4251,u)|0,n[dc>>2]=4,n[dc+4>>2]=0,n[u>>2]=n[dc>>2],n[u+4>>2]=n[dc+4>>2],Du(o,4263,u)|0,n[ln>>2]=5,n[ln+4>>2]=0,n[u>>2]=n[ln>>2],n[u+4>>2]=n[ln+4>>2],Du(o,4279,u)|0,n[zh>>2]=6,n[zh+4>>2]=0,n[u>>2]=n[zh>>2],n[u+4>>2]=n[zh+4>>2],Du(o,4293,u)|0,n[Kh>>2]=7,n[Kh+4>>2]=0,n[u>>2]=n[Kh>>2],n[u+4>>2]=n[Kh+4>>2],Du(o,4306,u)|0,n[Jh>>2]=8,n[Jh+4>>2]=0,n[u>>2]=n[Jh>>2],n[u+4>>2]=n[Jh+4>>2],Du(o,4323,u)|0,n[Ff>>2]=9,n[Ff+4>>2]=0,n[u>>2]=n[Ff>>2],n[u+4>>2]=n[Ff+4>>2],Du(o,4335,u)|0,n[Rf>>2]=2,n[Rf+4>>2]=0,n[u>>2]=n[Rf>>2],n[u+4>>2]=n[Rf+4>>2],cM(o,4353,u)|0,n[Vh>>2]=12,n[Vh+4>>2]=0,n[u>>2]=n[Vh>>2],n[u+4>>2]=n[Vh+4>>2],od(o,4363,u)|0,n[gc>>2]=1,n[gc+4>>2]=0,n[u>>2]=n[gc>>2],n[u+4>>2]=n[gc+4>>2],ep(o,4376,u)|0,n[Yh>>2]=2,n[Yh+4>>2]=0,n[u>>2]=n[Yh>>2],n[u+4>>2]=n[Yh+4>>2],ep(o,4388,u)|0,n[Wh>>2]=13,n[Wh+4>>2]=0,n[u>>2]=n[Wh>>2],n[u+4>>2]=n[Wh+4>>2],od(o,4402,u)|0,n[Za>>2]=14,n[Za+4>>2]=0,n[u>>2]=n[Za>>2],n[u+4>>2]=n[Za+4>>2],od(o,4411,u)|0,n[Fo>>2]=15,n[Fo+4>>2]=0,n[u>>2]=n[Fo>>2],n[u+4>>2]=n[Fo+4>>2],od(o,4421,u)|0,n[Ro>>2]=16,n[Ro+4>>2]=0,n[u>>2]=n[Ro>>2],n[u+4>>2]=n[Ro+4>>2],od(o,4433,u)|0,n[To>>2]=17,n[To+4>>2]=0,n[u>>2]=n[To>>2],n[u+4>>2]=n[To+4>>2],od(o,4446,u)|0,n[Hn>>2]=18,n[Hn+4>>2]=0,n[u>>2]=n[Hn>>2],n[u+4>>2]=n[Hn+4>>2],od(o,4458,u)|0,n[cr>>2]=3,n[cr+4>>2]=0,n[u>>2]=n[cr>>2],n[u+4>>2]=n[cr+4>>2],ep(o,4471,u)|0,n[Hr>>2]=1,n[Hr+4>>2]=0,n[u>>2]=n[Hr>>2],n[u+4>>2]=n[Hr+4>>2],QP(o,4486,u)|0,n[Tr>>2]=10,n[Tr+4>>2]=0,n[u>>2]=n[Tr>>2],n[u+4>>2]=n[Tr+4>>2],Du(o,4496,u)|0,n[$t>>2]=11,n[$t+4>>2]=0,n[u>>2]=n[$t>>2],n[u+4>>2]=n[$t+4>>2],Du(o,4508,u)|0,n[fr>>2]=3,n[fr+4>>2]=0,n[u>>2]=n[fr>>2],n[u+4>>2]=n[fr+4>>2],cM(o,4519,u)|0,n[Gr>>2]=4,n[Gr+4>>2]=0,n[u>>2]=n[Gr>>2],n[u+4>>2]=n[Gr+4>>2],Cke(o,4530,u)|0,n[Lt>>2]=19,n[Lt+4>>2]=0,n[u>>2]=n[Lt>>2],n[u+4>>2]=n[Lt+4>>2],wke(o,4542,u)|0,n[We>>2]=12,n[We+4>>2]=0,n[u>>2]=n[We>>2],n[u+4>>2]=n[We+4>>2],Bke(o,4554,u)|0,n[He>>2]=13,n[He+4>>2]=0,n[u>>2]=n[He>>2],n[u+4>>2]=n[He+4>>2],vke(o,4568,u)|0,n[ct>>2]=2,n[ct+4>>2]=0,n[u>>2]=n[ct>>2],n[u+4>>2]=n[ct+4>>2],Ske(o,4578,u)|0,n[Ze>>2]=20,n[Ze+4>>2]=0,n[u>>2]=n[Ze>>2],n[u+4>>2]=n[Ze+4>>2],Dke(o,4587,u)|0,n[tt>>2]=22,n[tt+4>>2]=0,n[u>>2]=n[tt>>2],n[u+4>>2]=n[tt+4>>2],kr(o,4602,u)|0,n[Qe>>2]=23,n[Qe+4>>2]=0,n[u>>2]=n[Qe>>2],n[u+4>>2]=n[Qe+4>>2],kr(o,4619,u)|0,n[Le>>2]=14,n[Le+4>>2]=0,n[u>>2]=n[Le>>2],n[u+4>>2]=n[Le+4>>2],bke(o,4629,u)|0,n[Ye>>2]=1,n[Ye+4>>2]=0,n[u>>2]=n[Ye>>2],n[u+4>>2]=n[Ye+4>>2],Pke(o,4637,u)|0,n[ae>>2]=4,n[ae+4>>2]=0,n[u>>2]=n[ae>>2],n[u+4>>2]=n[ae+4>>2],ep(o,4653,u)|0,n[q>>2]=5,n[q+4>>2]=0,n[u>>2]=n[q>>2],n[u+4>>2]=n[q+4>>2],ep(o,4669,u)|0,n[L>>2]=6,n[L+4>>2]=0,n[u>>2]=n[L>>2],n[u+4>>2]=n[L+4>>2],ep(o,4686,u)|0,n[M>>2]=7,n[M+4>>2]=0,n[u>>2]=n[M>>2],n[u+4>>2]=n[M+4>>2],ep(o,4701,u)|0,n[T>>2]=8,n[T+4>>2]=0,n[u>>2]=n[T>>2],n[u+4>>2]=n[T+4>>2],ep(o,4719,u)|0,n[k>>2]=9,n[k+4>>2]=0,n[u>>2]=n[k>>2],n[u+4>>2]=n[k+4>>2],ep(o,4736,u)|0,n[B>>2]=21,n[B+4>>2]=0,n[u>>2]=n[B>>2],n[u+4>>2]=n[B+4>>2],xke(o,4754,u)|0,n[m>>2]=2,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],QP(o,4772,u)|0,n[d>>2]=3,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],QP(o,4790,u)|0,n[A>>2]=4,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],QP(o,4808,u)|0,I=l}function gt(o,l){o=o|0,l=l|0;var u=0;u=NLe()|0,n[o>>2]=u,OLe(u,l),jh(n[o>>2]|0)}function Xt(o,l,u){return o=o|0,l=l|0,u=u|0,CLe(o,Bn(l)|0,u,0),o|0}function Dr(o,l,u){return o=o|0,l=l|0,u=u|0,sLe(o,Bn(l)|0,u,0),o|0}function Zn(o,l,u){return o=o|0,l=l|0,u=u|0,WOe(o,Bn(l)|0,u,0),o|0}function kr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],xOe(o,l,d),I=A,o|0}function Rn(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uOe(o,l,d),I=A,o|0}function _n(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JNe(o,l,d),I=A,o|0}function zr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TNe(o,l,d),I=A,o|0}function ci(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],dNe(o,l,d),I=A,o|0}function Du(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],eNe(o,l,d),I=A,o|0}function cM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],MFe(o,l,d),I=A,o|0}function od(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uFe(o,l,d),I=A,o|0}function ep(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JRe(o,l,d),I=A,o|0}function QP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TRe(o,l,d),I=A,o|0}function Cke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],dRe(o,l,d),I=A,o|0}function wke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],eRe(o,l,d),I=A,o|0}function Bke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],UTe(o,l,d),I=A,o|0}function vke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],vTe(o,l,d),I=A,o|0}function Ske(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],aTe(o,l,d),I=A,o|0}function Dke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],qQe(o,l,d),I=A,o|0}function bke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],PQe(o,l,d),I=A,o|0}function Pke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uQe(o,l,d),I=A,o|0}function xke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],kke(o,l,d),I=A,o|0}function kke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],Qke(o,u,d,1),I=A}function Bn(o){return o=o|0,o|0}function Qke(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=uM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=Tke(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,Rke(m,A)|0,A),I=d}function uM(){var o=0,l=0;if(s[7616]|0||(mz(9136),gr(24,9136,U|0)|0,l=7616,n[l>>2]=1,n[l+4>>2]=0),!(_r(9136)|0)){o=9136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));mz(9136)}return 9136}function Tke(o){return o=o|0,0}function Rke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=uM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],dz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(Oke(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function vn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0;B=I,I=I+32|0,ae=B+24|0,q=B+20|0,T=B+16|0,L=B+12|0,M=B+8|0,k=B+4|0,Ye=B,n[q>>2]=l,n[T>>2]=u,n[L>>2]=A,n[M>>2]=d,n[k>>2]=m,m=o+28|0,n[Ye>>2]=n[m>>2],n[ae>>2]=n[Ye>>2],Fke(o+24|0,ae,q,L,M,T,k)|0,n[m>>2]=n[n[m>>2]>>2],I=B}function Fke(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,o=Nke(l)|0,l=Kt(24)|0,gz(l+4|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0,n[B>>2]|0),n[l>>2]=n[o>>2],n[o>>2]=l,l|0}function Nke(o){return o=o|0,n[o>>2]|0}function gz(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function yr(o,l){return o=o|0,l=l|0,l|o|0}function dz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function Oke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=Lke(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,Mke(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],dz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,Uke(o,k),_ke(k),I=M;return}}function Lke(o){return o=o|0,357913941}function Mke(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function Uke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _ke(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function mz(o){o=o|0,Gke(o)}function Hke(o){o=o|0,jke(o+24|0)}function _r(o){return o=o|0,n[o>>2]|0}function jke(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function Gke(o){o=o|0;var l=0;l=tn()|0,rn(o,2,3,l,qke()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function tn(){return 9228}function qke(){return 1140}function Wke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=Yke(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=Vke(l,A)|0,I=u,l|0}function rn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function Yke(o){return o=o|0,(n[(uM()|0)+24>>2]|0)+(o*12|0)|0}function Vke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+48|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sp[u&31](A,o),A=Jke(A)|0,I=d,A|0}function Jke(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(yz()|0)|0,A?(AM(l,A),pM(u,l),Kke(o,u),o=hM(l)|0):o=zke(o)|0,I=d,o|0}function yz(){var o=0;return s[7632]|0||(oQe(9184),gr(25,9184,U|0)|0,o=7632,n[o>>2]=1,n[o+4>>2]=0),9184}function fM(o){return o=o|0,n[o+36>>2]|0}function AM(o,l){o=o|0,l=l|0,n[o>>2]=l,n[o+4>>2]=o,n[o+8>>2]=0}function pM(o,l){o=o|0,l=l|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=0}function Kke(o,l){o=o|0,l=l|0,eQe(l,o,o+8|0,o+16|0,o+24|0,o+32|0,o+40|0)|0}function hM(o){return o=o|0,n[(n[o+4>>2]|0)+8>>2]|0}function zke(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;T=I,I=I+16|0,u=T+4|0,A=T,d=Rl(8)|0,m=d,B=Kt(48)|0,k=B,l=k+48|0;do n[k>>2]=n[o>>2],k=k+4|0,o=o+4|0;while((k|0)<(l|0));return l=m+4|0,n[l>>2]=B,k=Kt(8)|0,B=n[l>>2]|0,n[A>>2]=0,n[u>>2]=n[A>>2],Ez(k,B,u),n[d>>2]=k,I=T,m|0}function Ez(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1092,n[u+12>>2]=l,n[o+4>>2]=u}function Xke(o){o=o|0,$y(o),It(o)}function Zke(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function $ke(o){o=o|0,It(o)}function eQe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,m=tQe(n[o>>2]|0,l,u,A,d,m,B)|0,B=o+4|0,n[(n[B>>2]|0)+8>>2]=m,n[(n[B>>2]|0)+8>>2]|0}function tQe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0;var k=0,T=0;return k=I,I=I+16|0,T=k,Fl(T),o=Os(o)|0,B=rQe(o,+E[l>>3],+E[u>>3],+E[A>>3],+E[d>>3],+E[m>>3],+E[B>>3])|0,Nl(T),I=k,B|0}function rQe(o,l,u,A,d,m,B){o=o|0,l=+l,u=+u,A=+A,d=+d,m=+m,B=+B;var k=0;return k=da(nQe()|0)|0,l=+Ja(l),u=+Ja(u),A=+Ja(A),d=+Ja(d),m=+Ja(m),ro(0,k|0,o|0,+l,+u,+A,+d,+m,+ +Ja(B))|0}function nQe(){var o=0;return s[7624]|0||(iQe(9172),o=7624,n[o>>2]=1,n[o+4>>2]=0),9172}function iQe(o){o=o|0,Qo(o,sQe()|0,6)}function sQe(){return 1112}function oQe(o){o=o|0,Lh(o)}function aQe(o){o=o|0,Iz(o+24|0),Cz(o+16|0)}function Iz(o){o=o|0,cQe(o)}function Cz(o){o=o|0,lQe(o)}function lQe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function cQe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function Lh(o){o=o|0;var l=0;n[o+16>>2]=0,n[o+20>>2]=0,l=o+24|0,n[l>>2]=0,n[o+28>>2]=l,n[o+36>>2]=0,s[o+40>>0]=0,s[o+41>>0]=0}function uQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fQe(o,u,d,0),I=A}function fQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=gM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pQe(m,A)|0,A),I=d}function gM(){var o=0,l=0;if(s[7640]|0||(Bz(9232),gr(26,9232,U|0)|0,l=7640,n[l>>2]=1,n[l+4>>2]=0),!(_r(9232)|0)){o=9232,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Bz(9232)}return 9232}function AQe(o){return o=o|0,0}function pQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=gM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],wz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function wz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],wz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mQe(o,k),yQe(k),I=M;return}}function gQe(o){return o=o|0,357913941}function dQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Bz(o){o=o|0,CQe(o)}function EQe(o){o=o|0,IQe(o+24|0)}function IQe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function CQe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,wQe()|0,3),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wQe(){return 1144}function BQe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,B=m+8|0,k=m,T=vQe(o)|0,o=n[T+4>>2]|0,n[k>>2]=n[T>>2],n[k+4>>2]=o,n[B>>2]=n[k>>2],n[B+4>>2]=n[k+4>>2],SQe(l,B,u,A,d),I=m}function vQe(o){return o=o|0,(n[(gM()|0)+24>>2]|0)+(o*12|0)|0}function SQe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0,M=0;M=I,I=I+16|0,B=M+2|0,k=M+1|0,T=M,m=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(m=n[(n[o>>2]|0)+m>>2]|0),Qf(B,u),u=+Tf(B,u),Qf(k,A),A=+Tf(k,A),tp(T,d),T=rp(T,d)|0,MZ[m&1](o,u,A,T),I=M}function Qf(o,l){o=o|0,l=+l}function Tf(o,l){return o=o|0,l=+l,+ +bQe(l)}function tp(o,l){o=o|0,l=l|0}function rp(o,l){return o=o|0,l=l|0,DQe(l)|0}function DQe(o){return o=o|0,o|0}function bQe(o){return o=+o,+o}function PQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],xQe(o,u,d,1),I=A}function xQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=dM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=kQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,QQe(m,A)|0,A),I=d}function dM(){var o=0,l=0;if(s[7648]|0||(Sz(9268),gr(27,9268,U|0)|0,l=7648,n[l>>2]=1,n[l+4>>2]=0),!(_r(9268)|0)){o=9268,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Sz(9268)}return 9268}function kQe(o){return o=o|0,0}function QQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=dM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],vz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(TQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function vz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function TQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=RQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,FQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],vz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,NQe(o,k),OQe(k),I=M;return}}function RQe(o){return o=o|0,357913941}function FQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function NQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function OQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Sz(o){o=o|0,UQe(o)}function LQe(o){o=o|0,MQe(o+24|0)}function MQe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function UQe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,4,l,_Qe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function _Qe(){return 1160}function HQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=jQe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=GQe(l,A)|0,I=u,l|0}function jQe(o){return o=o|0,(n[(dM()|0)+24>>2]|0)+(o*12|0)|0}function GQe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),Dz(gd[u&31](o)|0)|0}function Dz(o){return o=o|0,o&1|0}function qQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],WQe(o,u,d,0),I=A}function WQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=mM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=YQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,VQe(m,A)|0,A),I=d}function mM(){var o=0,l=0;if(s[7656]|0||(Pz(9304),gr(28,9304,U|0)|0,l=7656,n[l>>2]=1,n[l+4>>2]=0),!(_r(9304)|0)){o=9304,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Pz(9304)}return 9304}function YQe(o){return o=o|0,0}function VQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=mM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],bz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(JQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function bz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function JQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=KQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,zQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],bz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,XQe(o,k),ZQe(k),I=M;return}}function KQe(o){return o=o|0,357913941}function zQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function XQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Pz(o){o=o|0,tTe(o)}function $Qe(o){o=o|0,eTe(o+24|0)}function eTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function tTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,rTe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function rTe(){return 1164}function nTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=iTe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],sTe(l,d,u),I=A}function iTe(o){return o=o|0,(n[(mM()|0)+24>>2]|0)+(o*12|0)|0}function sTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Mh(d,u),u=Uh(d,u)|0,sp[A&31](o,u),_h(d),I=m}function Mh(o,l){o=o|0,l=l|0,oTe(o,l)}function Uh(o,l){return o=o|0,l=l|0,o|0}function _h(o){o=o|0,Sf(o)}function oTe(o,l){o=o|0,l=l|0,yM(o,l)}function yM(o,l){o=o|0,l=l|0,n[o>>2]=l}function aTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],lTe(o,u,d,0),I=A}function lTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=EM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=cTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,uTe(m,A)|0,A),I=d}function EM(){var o=0,l=0;if(s[7664]|0||(kz(9340),gr(29,9340,U|0)|0,l=7664,n[l>>2]=1,n[l+4>>2]=0),!(_r(9340)|0)){o=9340,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));kz(9340)}return 9340}function cTe(o){return o=o|0,0}function uTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=EM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],xz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(fTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function xz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function fTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=ATe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,pTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],xz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,hTe(o,k),gTe(k),I=M;return}}function ATe(o){return o=o|0,357913941}function pTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function hTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function gTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function kz(o){o=o|0,yTe(o)}function dTe(o){o=o|0,mTe(o+24|0)}function mTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function yTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,4,l,ETe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function ETe(){return 1180}function ITe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=CTe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=wTe(l,d,u)|0,I=A,u|0}function CTe(o){return o=o|0,(n[(EM()|0)+24>>2]|0)+(o*12|0)|0}function wTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),ad(d,u),d=ld(d,u)|0,d=TP(gU[A&15](o,d)|0)|0,I=m,d|0}function ad(o,l){o=o|0,l=l|0}function ld(o,l){return o=o|0,l=l|0,BTe(l)|0}function TP(o){return o=o|0,o|0}function BTe(o){return o=o|0,o|0}function vTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],STe(o,u,d,0),I=A}function STe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=IM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=DTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,bTe(m,A)|0,A),I=d}function IM(){var o=0,l=0;if(s[7672]|0||(Tz(9376),gr(30,9376,U|0)|0,l=7672,n[l>>2]=1,n[l+4>>2]=0),!(_r(9376)|0)){o=9376,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Tz(9376)}return 9376}function DTe(o){return o=o|0,0}function bTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=IM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Qz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(PTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Qz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function PTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=xTe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,kTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Qz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,QTe(o,k),TTe(k),I=M;return}}function xTe(o){return o=o|0,357913941}function kTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function QTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function TTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Tz(o){o=o|0,NTe(o)}function RTe(o){o=o|0,FTe(o+24|0)}function FTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function NTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,Rz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Rz(){return 1196}function OTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=LTe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=MTe(l,A)|0,I=u,l|0}function LTe(o){return o=o|0,(n[(IM()|0)+24>>2]|0)+(o*12|0)|0}function MTe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),TP(gd[u&31](o)|0)|0}function UTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],_Te(o,u,d,1),I=A}function _Te(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=CM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=HTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,jTe(m,A)|0,A),I=d}function CM(){var o=0,l=0;if(s[7680]|0||(Nz(9412),gr(31,9412,U|0)|0,l=7680,n[l>>2]=1,n[l+4>>2]=0),!(_r(9412)|0)){o=9412,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Nz(9412)}return 9412}function HTe(o){return o=o|0,0}function jTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=CM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Fz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(GTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Fz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function GTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=qTe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,WTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Fz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,YTe(o,k),VTe(k),I=M;return}}function qTe(o){return o=o|0,357913941}function WTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function YTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function VTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Nz(o){o=o|0,zTe(o)}function JTe(o){o=o|0,KTe(o+24|0)}function KTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function zTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,Oz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Oz(){return 1200}function XTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=ZTe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=$Te(l,A)|0,I=u,l|0}function ZTe(o){return o=o|0,(n[(CM()|0)+24>>2]|0)+(o*12|0)|0}function $Te(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),RP(gd[u&31](o)|0)|0}function RP(o){return o=o|0,o|0}function eRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],tRe(o,u,d,0),I=A}function tRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=wM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=rRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,nRe(m,A)|0,A),I=d}function wM(){var o=0,l=0;if(s[7688]|0||(Mz(9448),gr(32,9448,U|0)|0,l=7688,n[l>>2]=1,n[l+4>>2]=0),!(_r(9448)|0)){o=9448,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Mz(9448)}return 9448}function rRe(o){return o=o|0,0}function nRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=wM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Lz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(iRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Lz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function iRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=sRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,oRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Lz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,aRe(o,k),lRe(k),I=M;return}}function sRe(o){return o=o|0,357913941}function oRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function aRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function lRe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Mz(o){o=o|0,fRe(o)}function cRe(o){o=o|0,uRe(o+24|0)}function uRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function fRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,Uz()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Uz(){return 1204}function ARe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=pRe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hRe(l,d,u),I=A}function pRe(o){return o=o|0,(n[(wM()|0)+24>>2]|0)+(o*12|0)|0}function hRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),BM(d,u),d=vM(d,u)|0,sp[A&31](o,d),I=m}function BM(o,l){o=o|0,l=l|0}function vM(o,l){return o=o|0,l=l|0,gRe(l)|0}function gRe(o){return o=o|0,o|0}function dRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],mRe(o,u,d,0),I=A}function mRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=SM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=yRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,ERe(m,A)|0,A),I=d}function SM(){var o=0,l=0;if(s[7696]|0||(Hz(9484),gr(33,9484,U|0)|0,l=7696,n[l>>2]=1,n[l+4>>2]=0),!(_r(9484)|0)){o=9484,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Hz(9484)}return 9484}function yRe(o){return o=o|0,0}function ERe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=SM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],_z(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(IRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function _z(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function IRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=CRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,wRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],_z(m,A,u),n[T>>2]=(n[T>>2]|0)+12,BRe(o,k),vRe(k),I=M;return}}function CRe(o){return o=o|0,357913941}function wRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function BRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function vRe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Hz(o){o=o|0,bRe(o)}function SRe(o){o=o|0,DRe(o+24|0)}function DRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function bRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,PRe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function PRe(){return 1212}function xRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=kRe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],QRe(l,m,u,A),I=d}function kRe(o){return o=o|0,(n[(SM()|0)+24>>2]|0)+(o*12|0)|0}function QRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),BM(m,u),m=vM(m,u)|0,ad(B,A),B=ld(B,A)|0,F2[d&15](o,m,B),I=k}function TRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RRe(o,u,d,1),I=A}function RRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=DM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NRe(m,A)|0,A),I=d}function DM(){var o=0,l=0;if(s[7704]|0||(Gz(9520),gr(34,9520,U|0)|0,l=7704,n[l>>2]=1,n[l+4>>2]=0),!(_r(9520)|0)){o=9520,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Gz(9520)}return 9520}function FRe(o){return o=o|0,0}function NRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=DM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],jz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ORe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function jz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ORe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],jz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,URe(o,k),_Re(k),I=M;return}}function LRe(o){return o=o|0,357913941}function MRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function URe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Re(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Gz(o){o=o|0,GRe(o)}function HRe(o){o=o|0,jRe(o+24|0)}function jRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,qRe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qRe(){return 1224}function WRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;return d=I,I=I+16|0,m=d+8|0,B=d,k=YRe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],A=+VRe(l,m,u),I=d,+A}function YRe(o){return o=o|0,(n[(DM()|0)+24>>2]|0)+(o*12|0)|0}function VRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,B=+kf(+_Z[A&7](o,d)),I=m,+B}function JRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],KRe(o,u,d,1),I=A}function KRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=bM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=zRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,XRe(m,A)|0,A),I=d}function bM(){var o=0,l=0;if(s[7712]|0||(Wz(9556),gr(35,9556,U|0)|0,l=7712,n[l>>2]=1,n[l+4>>2]=0),!(_r(9556)|0)){o=9556,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Wz(9556)}return 9556}function zRe(o){return o=o|0,0}function XRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=bM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],qz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ZRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function qz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ZRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=$Re(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,eFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],qz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,tFe(o,k),rFe(k),I=M;return}}function $Re(o){return o=o|0,357913941}function eFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function tFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Wz(o){o=o|0,sFe(o)}function nFe(o){o=o|0,iFe(o+24|0)}function iFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function sFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,oFe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oFe(){return 1232}function aFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=lFe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=+cFe(l,d),I=A,+u}function lFe(o){return o=o|0,(n[(bM()|0)+24>>2]|0)+(o*12|0)|0}function cFe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),+ +kf(+UZ[u&15](o))}function uFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fFe(o,u,d,1),I=A}function fFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=PM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AFe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pFe(m,A)|0,A),I=d}function PM(){var o=0,l=0;if(s[7720]|0||(Vz(9592),gr(36,9592,U|0)|0,l=7720,n[l>>2]=1,n[l+4>>2]=0),!(_r(9592)|0)){o=9592,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Vz(9592)}return 9592}function AFe(o){return o=o|0,0}function pFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=PM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Yz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hFe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Yz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gFe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Yz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mFe(o,k),yFe(k),I=M;return}}function gFe(o){return o=o|0,357913941}function dFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Vz(o){o=o|0,CFe(o)}function EFe(o){o=o|0,IFe(o+24|0)}function IFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function CFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,7,l,wFe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wFe(){return 1276}function BFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=vFe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=SFe(l,A)|0,I=u,l|0}function vFe(o){return o=o|0,(n[(PM()|0)+24>>2]|0)+(o*12|0)|0}function SFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+16|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sp[u&31](A,o),A=Jz(A)|0,I=d,A|0}function Jz(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(Kz()|0)|0,A?(AM(l,A),pM(u,l),DFe(o,u),o=hM(l)|0):o=bFe(o)|0,I=d,o|0}function Kz(){var o=0;return s[7736]|0||(LFe(9640),gr(25,9640,U|0)|0,o=7736,n[o>>2]=1,n[o+4>>2]=0),9640}function DFe(o,l){o=o|0,l=l|0,QFe(l,o,o+8|0)|0}function bFe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(16)|0,n[k>>2]=n[o>>2],n[k+4>>2]=n[o+4>>2],n[k+8>>2]=n[o+8>>2],n[k+12>>2]=n[o+12>>2],m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xM(o,m,d),n[A>>2]=o,I=u,l|0}function xM(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1244,n[u+12>>2]=l,n[o+4>>2]=u}function PFe(o){o=o|0,$y(o),It(o)}function xFe(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function kFe(o){o=o|0,It(o)}function QFe(o,l,u){return o=o|0,l=l|0,u=u|0,l=TFe(n[o>>2]|0,l,u)|0,u=o+4|0,n[(n[u>>2]|0)+8>>2]=l,n[(n[u>>2]|0)+8>>2]|0}function TFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return A=I,I=I+16|0,d=A,Fl(d),o=Os(o)|0,u=RFe(o,n[l>>2]|0,+E[u>>3])|0,Nl(d),I=A,u|0}function RFe(o,l,u){o=o|0,l=l|0,u=+u;var A=0;return A=da(FFe()|0)|0,l=Yy(l)|0,ou(0,A|0,o|0,l|0,+ +Ja(u))|0}function FFe(){var o=0;return s[7728]|0||(NFe(9628),o=7728,n[o>>2]=1,n[o+4>>2]=0),9628}function NFe(o){o=o|0,Qo(o,OFe()|0,2)}function OFe(){return 1264}function LFe(o){o=o|0,Lh(o)}function MFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],UFe(o,u,d,1),I=A}function UFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=kM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=_Fe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,HFe(m,A)|0,A),I=d}function kM(){var o=0,l=0;if(s[7744]|0||(Xz(9684),gr(37,9684,U|0)|0,l=7744,n[l>>2]=1,n[l+4>>2]=0),!(_r(9684)|0)){o=9684,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Xz(9684)}return 9684}function _Fe(o){return o=o|0,0}function HFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=kM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],zz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(jFe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function zz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function jFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=GFe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,qFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],zz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,WFe(o,k),YFe(k),I=M;return}}function GFe(o){return o=o|0,357913941}function qFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function WFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function YFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Xz(o){o=o|0,KFe(o)}function VFe(o){o=o|0,JFe(o+24|0)}function JFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function KFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,zFe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function zFe(){return 1280}function XFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=ZFe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=$Fe(l,d,u)|0,I=A,u|0}function ZFe(o){return o=o|0,(n[(kM()|0)+24>>2]|0)+(o*12|0)|0}function $Fe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return B=I,I=I+32|0,d=B,m=B+16|0,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(m,u),m=rp(m,u)|0,F2[A&15](d,o,m),m=Jz(d)|0,I=B,m|0}function eNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],tNe(o,u,d,1),I=A}function tNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=QM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=rNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,nNe(m,A)|0,A),I=d}function QM(){var o=0,l=0;if(s[7752]|0||($z(9720),gr(38,9720,U|0)|0,l=7752,n[l>>2]=1,n[l+4>>2]=0),!(_r(9720)|0)){o=9720,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));$z(9720)}return 9720}function rNe(o){return o=o|0,0}function nNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=QM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Zz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(iNe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Zz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function iNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=sNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,oNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Zz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,aNe(o,k),lNe(k),I=M;return}}function sNe(o){return o=o|0,357913941}function oNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function aNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function lNe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function $z(o){o=o|0,fNe(o)}function cNe(o){o=o|0,uNe(o+24|0)}function uNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function fNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,ANe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function ANe(){return 1288}function pNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=hNe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=gNe(l,A)|0,I=u,l|0}function hNe(o){return o=o|0,(n[(QM()|0)+24>>2]|0)+(o*12|0)|0}function gNe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sd(gd[u&31](o)|0)|0}function dNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],mNe(o,u,d,0),I=A}function mNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=TM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=yNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,ENe(m,A)|0,A),I=d}function TM(){var o=0,l=0;if(s[7760]|0||(tX(9756),gr(39,9756,U|0)|0,l=7760,n[l>>2]=1,n[l+4>>2]=0),!(_r(9756)|0)){o=9756,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));tX(9756)}return 9756}function yNe(o){return o=o|0,0}function ENe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=TM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],eX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(INe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function eX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function INe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=CNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,wNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],eX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,BNe(o,k),vNe(k),I=M;return}}function CNe(o){return o=o|0,357913941}function wNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function BNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function vNe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function tX(o){o=o|0,bNe(o)}function SNe(o){o=o|0,DNe(o+24|0)}function DNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function bNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,PNe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function PNe(){return 1292}function xNe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=kNe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],QNe(l,d,u),I=A}function kNe(o){return o=o|0,(n[(TM()|0)+24>>2]|0)+(o*12|0)|0}function QNe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Qf(d,u),u=+Tf(d,u),OZ[A&31](o,u),I=m}function TNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RNe(o,u,d,0),I=A}function RNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=RM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NNe(m,A)|0,A),I=d}function RM(){var o=0,l=0;if(s[7768]|0||(nX(9792),gr(40,9792,U|0)|0,l=7768,n[l>>2]=1,n[l+4>>2]=0),!(_r(9792)|0)){o=9792,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));nX(9792)}return 9792}function FNe(o){return o=o|0,0}function NNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=RM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],rX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ONe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function rX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ONe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],rX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,UNe(o,k),_Ne(k),I=M;return}}function LNe(o){return o=o|0,357913941}function MNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function UNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Ne(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function nX(o){o=o|0,GNe(o)}function HNe(o){o=o|0,jNe(o+24|0)}function jNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,qNe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qNe(){return 1300}function WNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=YNe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],VNe(l,m,u,A),I=d}function YNe(o){return o=o|0,(n[(RM()|0)+24>>2]|0)+(o*12|0)|0}function VNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),tp(m,u),m=rp(m,u)|0,Qf(B,A),A=+Tf(B,A),qZ[d&15](o,m,A),I=k}function JNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],KNe(o,u,d,0),I=A}function KNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=FM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=zNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,XNe(m,A)|0,A),I=d}function FM(){var o=0,l=0;if(s[7776]|0||(sX(9828),gr(41,9828,U|0)|0,l=7776,n[l>>2]=1,n[l+4>>2]=0),!(_r(9828)|0)){o=9828,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));sX(9828)}return 9828}function zNe(o){return o=o|0,0}function XNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=FM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],iX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ZNe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function iX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ZNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=$Ne(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,eOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],iX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,tOe(o,k),rOe(k),I=M;return}}function $Ne(o){return o=o|0,357913941}function eOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function tOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function sX(o){o=o|0,sOe(o)}function nOe(o){o=o|0,iOe(o+24|0)}function iOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function sOe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,7,l,oOe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oOe(){return 1312}function aOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=lOe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],cOe(l,d,u),I=A}function lOe(o){return o=o|0,(n[(FM()|0)+24>>2]|0)+(o*12|0)|0}function cOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,sp[A&31](o,d),I=m}function uOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fOe(o,u,d,0),I=A}function fOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=NM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AOe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pOe(m,A)|0,A),I=d}function NM(){var o=0,l=0;if(s[7784]|0||(aX(9864),gr(42,9864,U|0)|0,l=7784,n[l>>2]=1,n[l+4>>2]=0),!(_r(9864)|0)){o=9864,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));aX(9864)}return 9864}function AOe(o){return o=o|0,0}function pOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=NM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],oX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hOe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function oX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gOe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],oX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mOe(o,k),yOe(k),I=M;return}}function gOe(o){return o=o|0,357913941}function dOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function aX(o){o=o|0,COe(o)}function EOe(o){o=o|0,IOe(o+24|0)}function IOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function COe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,wOe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wOe(){return 1320}function BOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=vOe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],SOe(l,d,u),I=A}function vOe(o){return o=o|0,(n[(NM()|0)+24>>2]|0)+(o*12|0)|0}function SOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),DOe(d,u),d=bOe(d,u)|0,sp[A&31](o,d),I=m}function DOe(o,l){o=o|0,l=l|0}function bOe(o,l){return o=o|0,l=l|0,POe(l)|0}function POe(o){return o=o|0,o|0}function xOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],kOe(o,u,d,0),I=A}function kOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=OM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=QOe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,TOe(m,A)|0,A),I=d}function OM(){var o=0,l=0;if(s[7792]|0||(cX(9900),gr(43,9900,U|0)|0,l=7792,n[l>>2]=1,n[l+4>>2]=0),!(_r(9900)|0)){o=9900,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));cX(9900)}return 9900}function QOe(o){return o=o|0,0}function TOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=OM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],lX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ROe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function lX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ROe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=FOe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,NOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],lX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,OOe(o,k),LOe(k),I=M;return}}function FOe(o){return o=o|0,357913941}function NOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function OOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function LOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function cX(o){o=o|0,_Oe(o)}function MOe(o){o=o|0,UOe(o+24|0)}function UOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function _Oe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,22,l,HOe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function HOe(){return 1344}function jOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;u=I,I=I+16|0,A=u+8|0,d=u,m=GOe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],qOe(l,A),I=u}function GOe(o){return o=o|0,(n[(OM()|0)+24>>2]|0)+(o*12|0)|0}function qOe(o,l){o=o|0,l=l|0;var u=0;u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ip[u&127](o)}function WOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=LM()|0,o=YOe(u)|0,vn(m,l,d,o,VOe(u,A)|0,A)}function LM(){var o=0,l=0;if(s[7800]|0||(fX(9936),gr(44,9936,U|0)|0,l=7800,n[l>>2]=1,n[l+4>>2]=0),!(_r(9936)|0)){o=9936,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));fX(9936)}return 9936}function YOe(o){return o=o|0,o|0}function VOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=LM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(uX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(JOe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function uX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function JOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=KOe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,zOe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,uX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,XOe(o,d),ZOe(d),I=k;return}}function KOe(o){return o=o|0,536870911}function zOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function XOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function fX(o){o=o|0,tLe(o)}function $Oe(o){o=o|0,eLe(o+24|0)}function eLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function tLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,23,l,Uz()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function rLe(o,l){o=o|0,l=l|0,iLe(n[(nLe(o)|0)>>2]|0,l)}function nLe(o){return o=o|0,(n[(LM()|0)+24>>2]|0)+(o<<3)|0}function iLe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,BM(A,l),l=vM(A,l)|0,ip[o&127](l),I=u}function sLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=MM()|0,o=oLe(u)|0,vn(m,l,d,o,aLe(u,A)|0,A)}function MM(){var o=0,l=0;if(s[7808]|0||(pX(9972),gr(45,9972,U|0)|0,l=7808,n[l>>2]=1,n[l+4>>2]=0),!(_r(9972)|0)){o=9972,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));pX(9972)}return 9972}function oLe(o){return o=o|0,o|0}function aLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=MM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(AX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(lLe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function AX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function lLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=cLe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,uLe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,AX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,fLe(o,d),ALe(d),I=k;return}}function cLe(o){return o=o|0,536870911}function uLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function fLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ALe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function pX(o){o=o|0,gLe(o)}function pLe(o){o=o|0,hLe(o+24|0)}function hLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function gLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,9,l,dLe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function dLe(){return 1348}function mLe(o,l){return o=o|0,l=l|0,ELe(n[(yLe(o)|0)>>2]|0,l)|0}function yLe(o){return o=o|0,(n[(MM()|0)+24>>2]|0)+(o<<3)|0}function ELe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,hX(A,l),l=gX(A,l)|0,l=TP(gd[o&31](l)|0)|0,I=u,l|0}function hX(o,l){o=o|0,l=l|0}function gX(o,l){return o=o|0,l=l|0,ILe(l)|0}function ILe(o){return o=o|0,o|0}function CLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=UM()|0,o=wLe(u)|0,vn(m,l,d,o,BLe(u,A)|0,A)}function UM(){var o=0,l=0;if(s[7816]|0||(mX(10008),gr(46,10008,U|0)|0,l=7816,n[l>>2]=1,n[l+4>>2]=0),!(_r(10008)|0)){o=10008,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));mX(10008)}return 10008}function wLe(o){return o=o|0,o|0}function BLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=UM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(dX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(vLe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function dX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function vLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=SLe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,DLe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,dX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,bLe(o,d),PLe(d),I=k;return}}function SLe(o){return o=o|0,536870911}function DLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function bLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function PLe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function mX(o){o=o|0,QLe(o)}function xLe(o){o=o|0,kLe(o+24|0)}function kLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function QLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,15,l,Rz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function TLe(o){return o=o|0,FLe(n[(RLe(o)|0)>>2]|0)|0}function RLe(o){return o=o|0,(n[(UM()|0)+24>>2]|0)+(o<<3)|0}function FLe(o){return o=o|0,TP(VP[o&7]()|0)|0}function NLe(){var o=0;return s[7832]|0||(GLe(10052),gr(25,10052,U|0)|0,o=7832,n[o>>2]=1,n[o+4>>2]=0),10052}function OLe(o,l){o=o|0,l=l|0,n[o>>2]=LLe()|0,n[o+4>>2]=MLe()|0,n[o+12>>2]=l,n[o+8>>2]=ULe()|0,n[o+32>>2]=2}function LLe(){return 11709}function MLe(){return 1188}function ULe(){return FP()|0}function _Le(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(HLe(u),It(u)):l|0&&(Oy(l),It(l))}function Hh(o,l){return o=o|0,l=l|0,l&o|0}function HLe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function FP(){var o=0;return s[7824]|0||(n[2511]=jLe()|0,n[2512]=0,o=7824,n[o>>2]=1,n[o+4>>2]=0),10044}function jLe(){return 0}function GLe(o){o=o|0,Lh(o)}function qLe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0;l=I,I=I+32|0,u=l+24|0,m=l+16|0,d=l+8|0,A=l,WLe(o,4827),YLe(o,4834,3)|0,VLe(o,3682,47)|0,n[m>>2]=9,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],JLe(o,4841,u)|0,n[d>>2]=1,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],KLe(o,4871,u)|0,n[A>>2]=10,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],zLe(o,4891,u)|0,I=l}function WLe(o,l){o=o|0,l=l|0;var u=0;u=PUe()|0,n[o>>2]=u,xUe(u,l),jh(n[o>>2]|0)}function YLe(o,l,u){return o=o|0,l=l|0,u=u|0,AUe(o,Bn(l)|0,u,0),o|0}function VLe(o,l,u){return o=o|0,l=l|0,u=u|0,XMe(o,Bn(l)|0,u,0),o|0}function JLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TMe(o,l,d),I=A,o|0}function KLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],pMe(o,l,d),I=A,o|0}function zLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],XLe(o,l,d),I=A,o|0}function XLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],ZLe(o,u,d,1),I=A}function ZLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=_M()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=$Le(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,eMe(m,A)|0,A),I=d}function _M(){var o=0,l=0;if(s[7840]|0||(EX(10100),gr(48,10100,U|0)|0,l=7840,n[l>>2]=1,n[l+4>>2]=0),!(_r(10100)|0)){o=10100,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));EX(10100)}return 10100}function $Le(o){return o=o|0,0}function eMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=_M()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],yX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(tMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function yX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function tMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=rMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,nMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],yX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,iMe(o,k),sMe(k),I=M;return}}function rMe(o){return o=o|0,357913941}function nMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function iMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function sMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function EX(o){o=o|0,lMe(o)}function oMe(o){o=o|0,aMe(o+24|0)}function aMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function lMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,cMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function cMe(){return 1364}function uMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=fMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=AMe(l,d,u)|0,I=A,u|0}function fMe(o){return o=o|0,(n[(_M()|0)+24>>2]|0)+(o*12|0)|0}function AMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,d=Dz(gU[A&15](o,d)|0)|0,I=m,d|0}function pMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hMe(o,u,d,0),I=A}function hMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=HM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=gMe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,dMe(m,A)|0,A),I=d}function HM(){var o=0,l=0;if(s[7848]|0||(CX(10136),gr(49,10136,U|0)|0,l=7848,n[l>>2]=1,n[l+4>>2]=0),!(_r(10136)|0)){o=10136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));CX(10136)}return 10136}function gMe(o){return o=o|0,0}function dMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=HM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],IX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(mMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function IX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function mMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=yMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,EMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],IX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,IMe(o,k),CMe(k),I=M;return}}function yMe(o){return o=o|0,357913941}function EMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function IMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function CX(o){o=o|0,vMe(o)}function wMe(o){o=o|0,BMe(o+24|0)}function BMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function vMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,9,l,SMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function SMe(){return 1372}function DMe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=bMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],PMe(l,d,u),I=A}function bMe(o){return o=o|0,(n[(HM()|0)+24>>2]|0)+(o*12|0)|0}function PMe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=$e;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),xMe(d,u),B=y(kMe(d,u)),NZ[A&1](o,B),I=m}function xMe(o,l){o=o|0,l=+l}function kMe(o,l){return o=o|0,l=+l,y(QMe(l))}function QMe(o){return o=+o,y(o)}function TMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RMe(o,u,d,0),I=A}function RMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=jM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FMe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NMe(m,A)|0,A),I=d}function jM(){var o=0,l=0;if(s[7856]|0||(BX(10172),gr(50,10172,U|0)|0,l=7856,n[l>>2]=1,n[l+4>>2]=0),!(_r(10172)|0)){o=10172,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));BX(10172)}return 10172}function FMe(o){return o=o|0,0}function NMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=jM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],wX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(OMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function wX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function OMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],wX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,UMe(o,k),_Me(k),I=M;return}}function LMe(o){return o=o|0,357913941}function MMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function UMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Me(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function BX(o){o=o|0,GMe(o)}function HMe(o){o=o|0,jMe(o+24|0)}function jMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,3,l,qMe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qMe(){return 1380}function WMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=YMe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],VMe(l,m,u,A),I=d}function YMe(o){return o=o|0,(n[(jM()|0)+24>>2]|0)+(o*12|0)|0}function VMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),tp(m,u),m=rp(m,u)|0,JMe(B,A),B=KMe(B,A)|0,F2[d&15](o,m,B),I=k}function JMe(o,l){o=o|0,l=l|0}function KMe(o,l){return o=o|0,l=l|0,zMe(l)|0}function zMe(o){return o=o|0,(o|0)!=0|0}function XMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=GM()|0,o=ZMe(u)|0,vn(m,l,d,o,$Me(u,A)|0,A)}function GM(){var o=0,l=0;if(s[7864]|0||(SX(10208),gr(51,10208,U|0)|0,l=7864,n[l>>2]=1,n[l+4>>2]=0),!(_r(10208)|0)){o=10208,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));SX(10208)}return 10208}function ZMe(o){return o=o|0,o|0}function $Me(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=GM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(vX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(eUe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function vX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function eUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=tUe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,rUe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,vX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,nUe(o,d),iUe(d),I=k;return}}function tUe(o){return o=o|0,536870911}function rUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function nUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function iUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function SX(o){o=o|0,aUe(o)}function sUe(o){o=o|0,oUe(o+24|0)}function oUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function aUe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,24,l,lUe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function lUe(){return 1392}function cUe(o,l){o=o|0,l=l|0,fUe(n[(uUe(o)|0)>>2]|0,l)}function uUe(o){return o=o|0,(n[(GM()|0)+24>>2]|0)+(o<<3)|0}function fUe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,hX(A,l),l=gX(A,l)|0,ip[o&127](l),I=u}function AUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=qM()|0,o=pUe(u)|0,vn(m,l,d,o,hUe(u,A)|0,A)}function qM(){var o=0,l=0;if(s[7872]|0||(bX(10244),gr(52,10244,U|0)|0,l=7872,n[l>>2]=1,n[l+4>>2]=0),!(_r(10244)|0)){o=10244,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));bX(10244)}return 10244}function pUe(o){return o=o|0,o|0}function hUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=qM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(DX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(gUe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function DX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function gUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=dUe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,mUe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,DX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,yUe(o,d),EUe(d),I=k;return}}function dUe(o){return o=o|0,536870911}function mUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function yUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function EUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function bX(o){o=o|0,wUe(o)}function IUe(o){o=o|0,CUe(o+24|0)}function CUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function wUe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,16,l,BUe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function BUe(){return 1400}function vUe(o){return o=o|0,DUe(n[(SUe(o)|0)>>2]|0)|0}function SUe(o){return o=o|0,(n[(qM()|0)+24>>2]|0)+(o<<3)|0}function DUe(o){return o=o|0,bUe(VP[o&7]()|0)|0}function bUe(o){return o=o|0,o|0}function PUe(){var o=0;return s[7880]|0||(NUe(10280),gr(25,10280,U|0)|0,o=7880,n[o>>2]=1,n[o+4>>2]=0),10280}function xUe(o,l){o=o|0,l=l|0,n[o>>2]=kUe()|0,n[o+4>>2]=QUe()|0,n[o+12>>2]=l,n[o+8>>2]=TUe()|0,n[o+32>>2]=4}function kUe(){return 11711}function QUe(){return 1356}function TUe(){return FP()|0}function RUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(FUe(u),It(u)):l|0&&(Kg(l),It(l))}function FUe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function NUe(o){o=o|0,Lh(o)}function OUe(o){o=o|0,LUe(o,4920),MUe(o)|0,UUe(o)|0}function LUe(o,l){o=o|0,l=l|0;var u=0;u=Kz()|0,n[o>>2]=u,o_e(u,l),jh(n[o>>2]|0)}function MUe(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,zUe()|0),o|0}function UUe(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,_Ue()|0),o|0}function _Ue(){var o=0;return s[7888]|0||(PX(10328),gr(53,10328,U|0)|0,o=7888,n[o>>2]=1,n[o+4>>2]=0),_r(10328)|0||PX(10328),10328}function cd(o,l){o=o|0,l=l|0,vn(o,0,l,0,0,0)}function PX(o){o=o|0,GUe(o),ud(o,10)}function HUe(o){o=o|0,jUe(o+24|0)}function jUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function GUe(o){o=o|0;var l=0;l=tn()|0,rn(o,5,1,l,VUe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qUe(o,l,u){o=o|0,l=l|0,u=+u,WUe(o,l,u)}function ud(o,l){o=o|0,l=l|0,n[o+20>>2]=l}function WUe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,m=A+8|0,k=A+13|0,d=A,B=A+12|0,tp(k,l),n[m>>2]=rp(k,l)|0,Qf(B,u),E[d>>3]=+Tf(B,u),YUe(o,m,d),I=A}function YUe(o,l,u){o=o|0,l=l|0,u=u|0,Tl(o+8|0,n[l>>2]|0,+E[u>>3]),s[o+24>>0]=1}function VUe(){return 1404}function JUe(o,l){return o=o|0,l=+l,KUe(o,l)|0}function KUe(o,l){o=o|0,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,m=A+4|0,B=A+8|0,k=A,d=Rl(8)|0,u=d,T=Kt(16)|0,tp(m,o),o=rp(m,o)|0,Qf(B,l),Tl(T,o,+Tf(B,l)),B=u+4|0,n[B>>2]=T,o=Kt(8)|0,B=n[B>>2]|0,n[k>>2]=0,n[m>>2]=n[k>>2],xM(o,B,m),n[d>>2]=o,I=A,u|0}function zUe(){var o=0;return s[7896]|0||(xX(10364),gr(54,10364,U|0)|0,o=7896,n[o>>2]=1,n[o+4>>2]=0),_r(10364)|0||xX(10364),10364}function xX(o){o=o|0,$Ue(o),ud(o,55)}function XUe(o){o=o|0,ZUe(o+24|0)}function ZUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function $Ue(o){o=o|0;var l=0;l=tn()|0,rn(o,5,4,l,n_e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function e_e(o){o=o|0,t_e(o)}function t_e(o){o=o|0,r_e(o)}function r_e(o){o=o|0,kX(o+8|0),s[o+24>>0]=1}function kX(o){o=o|0,n[o>>2]=0,E[o+8>>3]=0}function n_e(){return 1424}function i_e(){return s_e()|0}function s_e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,A=Kt(16)|0,kX(A),m=o+4|0,n[m>>2]=A,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xM(A,m,d),n[u>>2]=A,I=l,o|0}function o_e(o,l){o=o|0,l=l|0,n[o>>2]=a_e()|0,n[o+4>>2]=l_e()|0,n[o+12>>2]=l,n[o+8>>2]=c_e()|0,n[o+32>>2]=5}function a_e(){return 11710}function l_e(){return 1416}function c_e(){return NP()|0}function u_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(f_e(u),It(u)):l|0&&It(l)}function f_e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function NP(){var o=0;return s[7904]|0||(n[2600]=A_e()|0,n[2601]=0,o=7904,n[o>>2]=1,n[o+4>>2]=0),10400}function A_e(){return n[357]|0}function p_e(o){o=o|0,h_e(o,4926),g_e(o)|0}function h_e(o,l){o=o|0,l=l|0;var u=0;u=yz()|0,n[o>>2]=u,D_e(u,l),jh(n[o>>2]|0)}function g_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,d_e()|0),o|0}function d_e(){var o=0;return s[7912]|0||(QX(10412),gr(56,10412,U|0)|0,o=7912,n[o>>2]=1,n[o+4>>2]=0),_r(10412)|0||QX(10412),10412}function QX(o){o=o|0,E_e(o),ud(o,57)}function m_e(o){o=o|0,y_e(o+24|0)}function y_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function E_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,5,l,B_e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function I_e(o){o=o|0,C_e(o)}function C_e(o){o=o|0,w_e(o)}function w_e(o){o=o|0;var l=0,u=0;l=o+8|0,u=l+48|0;do n[l>>2]=0,l=l+4|0;while((l|0)<(u|0));s[o+56>>0]=1}function B_e(){return 1432}function v_e(){return S_e()|0}function S_e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0;B=I,I=I+16|0,o=B+4|0,l=B,u=Rl(8)|0,A=u,d=Kt(48)|0,m=d,k=m+48|0;do n[m>>2]=0,m=m+4|0;while((m|0)<(k|0));return m=A+4|0,n[m>>2]=d,k=Kt(8)|0,m=n[m>>2]|0,n[l>>2]=0,n[o>>2]=n[l>>2],Ez(k,m,o),n[u>>2]=k,I=B,A|0}function D_e(o,l){o=o|0,l=l|0,n[o>>2]=b_e()|0,n[o+4>>2]=P_e()|0,n[o+12>>2]=l,n[o+8>>2]=x_e()|0,n[o+32>>2]=6}function b_e(){return 11704}function P_e(){return 1436}function x_e(){return NP()|0}function k_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(Q_e(u),It(u)):l|0&&It(l)}function Q_e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function T_e(o){o=o|0,R_e(o,4933),F_e(o)|0,N_e(o)|0}function R_e(o,l){o=o|0,l=l|0;var u=0;u=s4e()|0,n[o>>2]=u,o4e(u,l),jh(n[o>>2]|0)}function F_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,K_e()|0),o|0}function N_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,O_e()|0),o|0}function O_e(){var o=0;return s[7920]|0||(TX(10452),gr(58,10452,U|0)|0,o=7920,n[o>>2]=1,n[o+4>>2]=0),_r(10452)|0||TX(10452),10452}function TX(o){o=o|0,U_e(o),ud(o,1)}function L_e(o){o=o|0,M_e(o+24|0)}function M_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function U_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,1,l,G_e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function __e(o,l,u){o=o|0,l=+l,u=+u,H_e(o,l,u)}function H_e(o,l,u){o=o|0,l=+l,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,m=A+8|0,k=A+17|0,d=A,B=A+16|0,Qf(k,l),E[m>>3]=+Tf(k,l),Qf(B,u),E[d>>3]=+Tf(B,u),j_e(o,m,d),I=A}function j_e(o,l,u){o=o|0,l=l|0,u=u|0,RX(o+8|0,+E[l>>3],+E[u>>3]),s[o+24>>0]=1}function RX(o,l,u){o=o|0,l=+l,u=+u,E[o>>3]=l,E[o+8>>3]=u}function G_e(){return 1472}function q_e(o,l){return o=+o,l=+l,W_e(o,l)|0}function W_e(o,l){o=+o,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,B=A+4|0,k=A+8|0,T=A,d=Rl(8)|0,u=d,m=Kt(16)|0,Qf(B,o),o=+Tf(B,o),Qf(k,l),RX(m,o,+Tf(k,l)),k=u+4|0,n[k>>2]=m,m=Kt(8)|0,k=n[k>>2]|0,n[T>>2]=0,n[B>>2]=n[T>>2],FX(m,k,B),n[d>>2]=m,I=A,u|0}function FX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1452,n[u+12>>2]=l,n[o+4>>2]=u}function Y_e(o){o=o|0,$y(o),It(o)}function V_e(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function J_e(o){o=o|0,It(o)}function K_e(){var o=0;return s[7928]|0||(NX(10488),gr(59,10488,U|0)|0,o=7928,n[o>>2]=1,n[o+4>>2]=0),_r(10488)|0||NX(10488),10488}function NX(o){o=o|0,Z_e(o),ud(o,60)}function z_e(o){o=o|0,X_e(o+24|0)}function X_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function Z_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,6,l,r4e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function $_e(o){o=o|0,e4e(o)}function e4e(o){o=o|0,t4e(o)}function t4e(o){o=o|0,OX(o+8|0),s[o+24>>0]=1}function OX(o){o=o|0,n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,n[o+12>>2]=0}function r4e(){return 1492}function n4e(){return i4e()|0}function i4e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,A=Kt(16)|0,OX(A),m=o+4|0,n[m>>2]=A,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],FX(A,m,d),n[u>>2]=A,I=l,o|0}function s4e(){var o=0;return s[7936]|0||(A4e(10524),gr(25,10524,U|0)|0,o=7936,n[o>>2]=1,n[o+4>>2]=0),10524}function o4e(o,l){o=o|0,l=l|0,n[o>>2]=a4e()|0,n[o+4>>2]=l4e()|0,n[o+12>>2]=l,n[o+8>>2]=c4e()|0,n[o+32>>2]=7}function a4e(){return 11700}function l4e(){return 1484}function c4e(){return NP()|0}function u4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(f4e(u),It(u)):l|0&&It(l)}function f4e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function A4e(o){o=o|0,Lh(o)}function p4e(o,l,u){o=o|0,l=l|0,u=u|0,o=Bn(l)|0,l=h4e(u)|0,u=g4e(u,0)|0,W4e(o,l,u,WM()|0,0)}function h4e(o){return o=o|0,o|0}function g4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=WM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(MX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(w4e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function WM(){var o=0,l=0;if(s[7944]|0||(LX(10568),gr(61,10568,U|0)|0,l=7944,n[l>>2]=1,n[l+4>>2]=0),!(_r(10568)|0)){o=10568,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));LX(10568)}return 10568}function LX(o){o=o|0,y4e(o)}function d4e(o){o=o|0,m4e(o+24|0)}function m4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function y4e(o){o=o|0;var l=0;l=tn()|0,rn(o,1,17,l,Oz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function E4e(o){return o=o|0,C4e(n[(I4e(o)|0)>>2]|0)|0}function I4e(o){return o=o|0,(n[(WM()|0)+24>>2]|0)+(o<<3)|0}function C4e(o){return o=o|0,RP(VP[o&7]()|0)|0}function MX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function w4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=B4e(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,v4e(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,MX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,S4e(o,d),D4e(d),I=k;return}}function B4e(o){return o=o|0,536870911}function v4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function S4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function D4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function b4e(){P4e()}function P4e(){x4e(10604)}function x4e(o){o=o|0,k4e(o,4955)}function k4e(o,l){o=o|0,l=l|0;var u=0;u=Q4e()|0,n[o>>2]=u,T4e(u,l),jh(n[o>>2]|0)}function Q4e(){var o=0;return s[7952]|0||(H4e(10612),gr(25,10612,U|0)|0,o=7952,n[o>>2]=1,n[o+4>>2]=0),10612}function T4e(o,l){o=o|0,l=l|0,n[o>>2]=O4e()|0,n[o+4>>2]=L4e()|0,n[o+12>>2]=l,n[o+8>>2]=M4e()|0,n[o+32>>2]=8}function jh(o){o=o|0;var l=0,u=0;l=I,I=I+16|0,u=l,Jy()|0,n[u>>2]=o,R4e(10608,u),I=l}function Jy(){return s[11714]|0||(n[2652]=0,gr(62,10608,U|0)|0,s[11714]=1),10608}function R4e(o,l){o=o|0,l=l|0;var u=0;u=Kt(8)|0,n[u+4>>2]=n[l>>2],n[u>>2]=n[o>>2],n[o>>2]=u}function F4e(o){o=o|0,N4e(o)}function N4e(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function O4e(){return 11715}function L4e(){return 1496}function M4e(){return FP()|0}function U4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(_4e(u),It(u)):l|0&&It(l)}function _4e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function H4e(o){o=o|0,Lh(o)}function j4e(o,l){o=o|0,l=l|0;var u=0,A=0;Jy()|0,u=n[2652]|0;e:do if(u|0){for(;A=n[u+4>>2]|0,!(A|0&&!(EZ(YM(A)|0,o)|0));)if(u=n[u>>2]|0,!u)break e;G4e(A,l)}while(!1)}function YM(o){return o=o|0,n[o+12>>2]|0}function G4e(o,l){o=o|0,l=l|0;var u=0;o=o+36|0,u=n[o>>2]|0,u|0&&(Sf(u),It(u)),u=Kt(4)|0,DP(u,l),n[o>>2]=u}function VM(){return s[11716]|0||(n[2664]=0,gr(63,10656,U|0)|0,s[11716]=1),10656}function UX(){var o=0;return s[11717]|0?o=n[2665]|0:(q4e(),n[2665]=1504,s[11717]=1,o=1504),o|0}function q4e(){s[11740]|0||(s[11718]=yr(yr(8,0)|0,0)|0,s[11719]=yr(yr(0,0)|0,0)|0,s[11720]=yr(yr(0,16)|0,0)|0,s[11721]=yr(yr(8,0)|0,0)|0,s[11722]=yr(yr(0,0)|0,0)|0,s[11723]=yr(yr(8,0)|0,0)|0,s[11724]=yr(yr(0,0)|0,0)|0,s[11725]=yr(yr(8,0)|0,0)|0,s[11726]=yr(yr(0,0)|0,0)|0,s[11727]=yr(yr(8,0)|0,0)|0,s[11728]=yr(yr(0,0)|0,0)|0,s[11729]=yr(yr(0,0)|0,32)|0,s[11730]=yr(yr(0,0)|0,32)|0,s[11740]=1)}function _X(){return 1572}function W4e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0;m=I,I=I+32|0,L=m+16|0,M=m+12|0,T=m+8|0,k=m+4|0,B=m,n[L>>2]=o,n[M>>2]=l,n[T>>2]=u,n[k>>2]=A,n[B>>2]=d,VM()|0,Y4e(10656,L,M,T,k,B),I=m}function Y4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0;B=Kt(24)|0,gz(B+4|0,n[l>>2]|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0),n[B>>2]=n[o>>2],n[o>>2]=B}function HX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0;if(ct=I,I=I+32|0,Le=ct+20|0,Qe=ct+8|0,tt=ct+4|0,Ze=ct,l=n[l>>2]|0,l|0){Ye=Le+4|0,T=Le+8|0,M=Qe+4|0,L=Qe+8|0,q=Qe+8|0,ae=Le+8|0;do{if(B=l+4|0,k=JM(B)|0,k|0){if(d=P2(k)|0,n[Le>>2]=0,n[Ye>>2]=0,n[T>>2]=0,A=(x2(k)|0)+1|0,V4e(Le,A),A|0)for(;A=A+-1|0,bu(Qe,n[d>>2]|0),m=n[Ye>>2]|0,m>>>0<(n[ae>>2]|0)>>>0?(n[m>>2]=n[Qe>>2],n[Ye>>2]=(n[Ye>>2]|0)+4):KM(Le,Qe),A;)d=d+4|0;A=k2(k)|0,n[Qe>>2]=0,n[M>>2]=0,n[L>>2]=0;e:do if(n[A>>2]|0)for(d=0,m=0;;){if((d|0)==(m|0)?J4e(Qe,A):(n[d>>2]=n[A>>2],n[M>>2]=(n[M>>2]|0)+4),A=A+4|0,!(n[A>>2]|0))break e;d=n[M>>2]|0,m=n[q>>2]|0}while(!1);n[tt>>2]=OP(B)|0,n[Ze>>2]=_r(k)|0,K4e(u,o,tt,Ze,Le,Qe),zM(Qe),np(Le)}l=n[l>>2]|0}while(l|0)}I=ct}function JM(o){return o=o|0,n[o+12>>2]|0}function P2(o){return o=o|0,n[o+12>>2]|0}function x2(o){return o=o|0,n[o+16>>2]|0}function V4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=n[o>>2]|0,(n[o+8>>2]|0)-A>>2>>>0>>0&&(KX(u,l,(n[o+4>>2]|0)-A>>2,o+8|0),zX(o,u),XX(u)),I=d}function KM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=JX(o)|0,m>>>0>>0)an(o);else{k=n[o>>2]|0,M=(n[o+8>>2]|0)-k|0,T=M>>1,KX(u,M>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,zX(o,u),XX(u),I=B;return}}function k2(o){return o=o|0,n[o+8>>2]|0}function J4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=VX(o)|0,m>>>0>>0)an(o);else{k=n[o>>2]|0,M=(n[o+8>>2]|0)-k|0,T=M>>1,h3e(u,M>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,g3e(o,u),d3e(u),I=B;return}}function OP(o){return o=o|0,n[o>>2]|0}function K4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,z4e(o,l,u,A,d,m)}function zM(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function np(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function z4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+48|0,L=B+40|0,k=B+32|0,q=B+24|0,T=B+12|0,M=B,Fl(k),o=Os(o)|0,n[q>>2]=n[l>>2],u=n[u>>2]|0,A=n[A>>2]|0,XM(T,d),X4e(M,m),n[L>>2]=n[q>>2],Z4e(o,L,u,A,T,M),zM(M),np(T),Nl(k),I=B}function XM(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(A3e(o,A),p3e(o,n[l>>2]|0,n[u>>2]|0,A))}function X4e(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(u3e(o,A),f3e(o,n[l>>2]|0,n[u>>2]|0,A))}function Z4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+32|0,L=B+28|0,q=B+24|0,k=B+12|0,T=B,M=da($4e()|0)|0,n[q>>2]=n[l>>2],n[L>>2]=n[q>>2],l=fd(L)|0,u=jX(u)|0,A=ZM(A)|0,n[k>>2]=n[d>>2],L=d+4|0,n[k+4>>2]=n[L>>2],q=d+8|0,n[k+8>>2]=n[q>>2],n[q>>2]=0,n[L>>2]=0,n[d>>2]=0,d=$M(k)|0,n[T>>2]=n[m>>2],L=m+4|0,n[T+4>>2]=n[L>>2],q=m+8|0,n[T+8>>2]=n[q>>2],n[q>>2]=0,n[L>>2]=0,n[m>>2]=0,lu(0,M|0,o|0,l|0,u|0,A|0,d|0,e3e(T)|0)|0,zM(T),np(k),I=B}function $4e(){var o=0;return s[7968]|0||(l3e(10708),o=7968,n[o>>2]=1,n[o+4>>2]=0),10708}function fd(o){return o=o|0,qX(o)|0}function jX(o){return o=o|0,GX(o)|0}function ZM(o){return o=o|0,RP(o)|0}function $M(o){return o=o|0,r3e(o)|0}function e3e(o){return o=o|0,t3e(o)|0}function t3e(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Rl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=GX(n[(n[o>>2]|0)+(l<<2)>>2]|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function GX(o){return o=o|0,o|0}function r3e(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Rl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=qX((n[o>>2]|0)+(l<<2)|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function qX(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(WX()|0)|0,A?(AM(l,A),pM(u,l),Mje(o,u),o=hM(l)|0):o=n3e(o)|0,I=d,o|0}function WX(){var o=0;return s[7960]|0||(a3e(10664),gr(25,10664,U|0)|0,o=7960,n[o>>2]=1,n[o+4>>2]=0),10664}function n3e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(4)|0,n[k>>2]=n[o>>2],m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],YX(o,m,d),n[A>>2]=o,I=u,l|0}function YX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1656,n[u+12>>2]=l,n[o+4>>2]=u}function i3e(o){o=o|0,$y(o),It(o)}function s3e(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function o3e(o){o=o|0,It(o)}function a3e(o){o=o|0,Lh(o)}function l3e(o){o=o|0,Qo(o,c3e()|0,5)}function c3e(){return 1676}function u3e(o,l){o=o|0,l=l|0;var u=0;if((VX(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function f3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function VX(o){return o=o|0,1073741823}function A3e(o,l){o=o|0,l=l|0;var u=0;if((JX(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function p3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function JX(o){return o=o|0,1073741823}function h3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function g3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function d3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function KX(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function zX(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function XX(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function m3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;if(Qe=I,I=I+32|0,L=Qe+20|0,q=Qe+12|0,M=Qe+16|0,ae=Qe+4|0,Ye=Qe,Le=Qe+8|0,k=UX()|0,m=n[k>>2]|0,B=n[m>>2]|0,B|0)for(T=n[k+8>>2]|0,k=n[k+4>>2]|0;bu(L,B),y3e(o,L,k,T),m=m+4|0,B=n[m>>2]|0,B;)T=T+1|0,k=k+1|0;if(m=_X()|0,B=n[m>>2]|0,B|0)do bu(L,B),n[q>>2]=n[m+4>>2],E3e(l,L,q),m=m+8|0,B=n[m>>2]|0;while(B|0);if(m=n[(Jy()|0)>>2]|0,m|0)do l=n[m+4>>2]|0,bu(L,n[(Ky(l)|0)>>2]|0),n[q>>2]=YM(l)|0,I3e(u,L,q),m=n[m>>2]|0;while(m|0);if(bu(M,0),m=VM()|0,n[L>>2]=n[M>>2],HX(L,m,d),m=n[(Jy()|0)>>2]|0,m|0){o=L+4|0,l=L+8|0,u=L+8|0;do{if(T=n[m+4>>2]|0,bu(q,n[(Ky(T)|0)>>2]|0),C3e(ae,ZX(T)|0),B=n[ae>>2]|0,B|0){n[L>>2]=0,n[o>>2]=0,n[l>>2]=0;do bu(Ye,n[(Ky(n[B+4>>2]|0)|0)>>2]|0),k=n[o>>2]|0,k>>>0<(n[u>>2]|0)>>>0?(n[k>>2]=n[Ye>>2],n[o>>2]=(n[o>>2]|0)+4):KM(L,Ye),B=n[B>>2]|0;while(B|0);w3e(A,q,L),np(L)}n[Le>>2]=n[q>>2],M=$X(T)|0,n[L>>2]=n[Le>>2],HX(L,M,d),Cz(ae),m=n[m>>2]|0}while(m|0)}I=Qe}function y3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F3e(o,l,u,A)}function E3e(o,l,u){o=o|0,l=l|0,u=u|0,R3e(o,l,u)}function Ky(o){return o=o|0,o|0}function I3e(o,l,u){o=o|0,l=l|0,u=u|0,x3e(o,l,u)}function ZX(o){return o=o|0,o+16|0}function C3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(m=I,I=I+16|0,d=m+8|0,u=m,n[o>>2]=0,A=n[l>>2]|0,n[d>>2]=A,n[u>>2]=o,u=P3e(u)|0,A|0){if(A=Kt(12)|0,B=(eZ(d)|0)+4|0,o=n[B+4>>2]|0,l=A+4|0,n[l>>2]=n[B>>2],n[l+4>>2]=o,l=n[n[d>>2]>>2]|0,n[d>>2]=l,!l)o=A;else for(l=A;o=Kt(12)|0,T=(eZ(d)|0)+4|0,k=n[T+4>>2]|0,B=o+4|0,n[B>>2]=n[T>>2],n[B+4>>2]=k,n[l>>2]=o,B=n[n[d>>2]>>2]|0,n[d>>2]=B,B;)l=o;n[o>>2]=n[u>>2],n[u>>2]=A}I=m}function w3e(o,l,u){o=o|0,l=l|0,u=u|0,B3e(o,l,u)}function $X(o){return o=o|0,o+24|0}function B3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+24|0,d=A+16|0,k=A+12|0,m=A,Fl(d),o=Os(o)|0,n[k>>2]=n[l>>2],XM(m,u),n[B>>2]=n[k>>2],v3e(o,B,m),np(m),Nl(d),I=A}function v3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+16|0,k=A+12|0,d=A,m=da(S3e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=fd(B)|0,n[d>>2]=n[u>>2],B=u+4|0,n[d+4>>2]=n[B>>2],k=u+8|0,n[d+8>>2]=n[k>>2],n[k>>2]=0,n[B>>2]=0,n[u>>2]=0,Ts(0,m|0,o|0,l|0,$M(d)|0)|0,np(d),I=A}function S3e(){var o=0;return s[7976]|0||(D3e(10720),o=7976,n[o>>2]=1,n[o+4>>2]=0),10720}function D3e(o){o=o|0,Qo(o,b3e()|0,2)}function b3e(){return 1732}function P3e(o){return o=o|0,n[o>>2]|0}function eZ(o){return o=o|0,n[o>>2]|0}function x3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Fl(d),o=Os(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],tZ(o,m,u),Nl(d),I=A}function tZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+4|0,B=A,d=da(k3e()|0)|0,n[B>>2]=n[l>>2],n[m>>2]=n[B>>2],l=fd(m)|0,Ts(0,d|0,o|0,l|0,jX(u)|0)|0,I=A}function k3e(){var o=0;return s[7984]|0||(Q3e(10732),o=7984,n[o>>2]=1,n[o+4>>2]=0),10732}function Q3e(o){o=o|0,Qo(o,T3e()|0,2)}function T3e(){return 1744}function R3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Fl(d),o=Os(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],tZ(o,m,u),Nl(d),I=A}function F3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Fl(m),o=Os(o)|0,n[k>>2]=n[l>>2],u=s[u>>0]|0,A=s[A>>0]|0,n[B>>2]=n[k>>2],N3e(o,B,u,A),Nl(m),I=d}function N3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,B=d+4|0,k=d,m=da(O3e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=fd(B)|0,u=zy(u)|0,Li(0,m|0,o|0,l|0,u|0,zy(A)|0)|0,I=d}function O3e(){var o=0;return s[7992]|0||(M3e(10744),o=7992,n[o>>2]=1,n[o+4>>2]=0),10744}function zy(o){return o=o|0,L3e(o)|0}function L3e(o){return o=o|0,o&255|0}function M3e(o){o=o|0,Qo(o,U3e()|0,3)}function U3e(){return 1756}function _3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;switch(ae=I,I=I+32|0,k=ae+8|0,T=ae+4|0,M=ae+20|0,L=ae,yM(o,0),A=Lje(l)|0,n[k>>2]=0,q=k+4|0,n[q>>2]=0,n[k+8>>2]=0,A<<24>>24){case 0:{s[M>>0]=0,H3e(T,u,M),LP(o,T)|0,Df(T);break}case 8:{q=sU(l)|0,s[M>>0]=8,bu(L,n[q+4>>2]|0),j3e(T,u,M,L,q+8|0),LP(o,T)|0,Df(T);break}case 9:{if(m=sU(l)|0,l=n[m+4>>2]|0,l|0)for(B=k+8|0,d=m+12|0;l=l+-1|0,bu(T,n[d>>2]|0),A=n[q>>2]|0,A>>>0<(n[B>>2]|0)>>>0?(n[A>>2]=n[T>>2],n[q>>2]=(n[q>>2]|0)+4):KM(k,T),l;)d=d+4|0;s[M>>0]=9,bu(L,n[m+8>>2]|0),G3e(T,u,M,L,k),LP(o,T)|0,Df(T);break}default:q=sU(l)|0,s[M>>0]=A,bu(L,n[q+4>>2]|0),q3e(T,u,M,L),LP(o,T)|0,Df(T)}np(k),I=ae}function H3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,Fl(d),l=Os(l)|0,n8e(o,l,s[u>>0]|0),Nl(d),I=A}function LP(o,l){o=o|0,l=l|0;var u=0;return u=n[o>>2]|0,u|0&&Na(u|0),n[o>>2]=n[l>>2],n[l>>2]=0,o|0}function j3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+32|0,k=m+16|0,B=m+8|0,T=m,Fl(B),l=Os(l)|0,u=s[u>>0]|0,n[T>>2]=n[A>>2],d=n[d>>2]|0,n[k>>2]=n[T>>2],$3e(o,l,u,k,d),Nl(B),I=m}function G3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0;m=I,I=I+32|0,T=m+24|0,B=m+16|0,M=m+12|0,k=m,Fl(B),l=Os(l)|0,u=s[u>>0]|0,n[M>>2]=n[A>>2],XM(k,d),n[T>>2]=n[M>>2],K3e(o,l,u,T,k),np(k),Nl(B),I=m}function q3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Fl(m),l=Os(l)|0,u=s[u>>0]|0,n[k>>2]=n[A>>2],n[B>>2]=n[k>>2],W3e(o,l,u,B),Nl(m),I=d}function W3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+4|0,k=d,B=da(Y3e()|0)|0,u=zy(u)|0,n[k>>2]=n[A>>2],n[m>>2]=n[k>>2],MP(o,Ts(0,B|0,l|0,u|0,fd(m)|0)|0),I=d}function Y3e(){var o=0;return s[8e3]|0||(V3e(10756),o=8e3,n[o>>2]=1,n[o+4>>2]=0),10756}function MP(o,l){o=o|0,l=l|0,yM(o,l)}function V3e(o){o=o|0,Qo(o,J3e()|0,2)}function J3e(){return 1772}function K3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0;m=I,I=I+32|0,T=m+16|0,M=m+12|0,B=m,k=da(z3e()|0)|0,u=zy(u)|0,n[M>>2]=n[A>>2],n[T>>2]=n[M>>2],A=fd(T)|0,n[B>>2]=n[d>>2],T=d+4|0,n[B+4>>2]=n[T>>2],M=d+8|0,n[B+8>>2]=n[M>>2],n[M>>2]=0,n[T>>2]=0,n[d>>2]=0,MP(o,Li(0,k|0,l|0,u|0,A|0,$M(B)|0)|0),np(B),I=m}function z3e(){var o=0;return s[8008]|0||(X3e(10768),o=8008,n[o>>2]=1,n[o+4>>2]=0),10768}function X3e(o){o=o|0,Qo(o,Z3e()|0,3)}function Z3e(){return 1784}function $3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,k=m+4|0,T=m,B=da(e8e()|0)|0,u=zy(u)|0,n[T>>2]=n[A>>2],n[k>>2]=n[T>>2],A=fd(k)|0,MP(o,Li(0,B|0,l|0,u|0,A|0,ZM(d)|0)|0),I=m}function e8e(){var o=0;return s[8016]|0||(t8e(10780),o=8016,n[o>>2]=1,n[o+4>>2]=0),10780}function t8e(o){o=o|0,Qo(o,r8e()|0,3)}function r8e(){return 1800}function n8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=da(i8e()|0)|0,MP(o,dn(0,A|0,l|0,zy(u)|0)|0)}function i8e(){var o=0;return s[8024]|0||(s8e(10792),o=8024,n[o>>2]=1,n[o+4>>2]=0),10792}function s8e(o){o=o|0,Qo(o,o8e()|0,1)}function o8e(){return 1816}function a8e(){l8e(),c8e(),u8e()}function l8e(){n[2702]=xZ(65536)|0}function c8e(){k8e(10856)}function u8e(){f8e(10816)}function f8e(o){o=o|0,A8e(o,5044),p8e(o)|0}function A8e(o,l){o=o|0,l=l|0;var u=0;u=WX()|0,n[o>>2]=u,v8e(u,l),jh(n[o>>2]|0)}function p8e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,h8e()|0),o|0}function h8e(){var o=0;return s[8032]|0||(rZ(10820),gr(64,10820,U|0)|0,o=8032,n[o>>2]=1,n[o+4>>2]=0),_r(10820)|0||rZ(10820),10820}function rZ(o){o=o|0,m8e(o),ud(o,25)}function g8e(o){o=o|0,d8e(o+24|0)}function d8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function m8e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,18,l,C8e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function y8e(o,l){o=o|0,l=l|0,E8e(o,l)}function E8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;u=I,I=I+16|0,A=u,d=u+4|0,ad(d,l),n[A>>2]=ld(d,l)|0,I8e(o,A),I=u}function I8e(o,l){o=o|0,l=l|0,nZ(o+4|0,n[l>>2]|0),s[o+8>>0]=1}function nZ(o,l){o=o|0,l=l|0,n[o>>2]=l}function C8e(){return 1824}function w8e(o){return o=o|0,B8e(o)|0}function B8e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(4)|0,ad(d,o),nZ(k,ld(d,o)|0),m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],YX(o,m,d),n[A>>2]=o,I=u,l|0}function Rl(o){o=o|0;var l=0,u=0;return o=o+7&-8,o>>>0<=32768&&(l=n[2701]|0,o>>>0<=(65536-l|0)>>>0)?(u=(n[2702]|0)+l|0,n[2701]=l+o,o=u):(o=xZ(o+8|0)|0,n[o>>2]=n[2703],n[2703]=o,o=o+8|0),o|0}function v8e(o,l){o=o|0,l=l|0,n[o>>2]=S8e()|0,n[o+4>>2]=D8e()|0,n[o+12>>2]=l,n[o+8>>2]=b8e()|0,n[o+32>>2]=9}function S8e(){return 11744}function D8e(){return 1832}function b8e(){return NP()|0}function P8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(x8e(u),It(u)):l|0&&It(l)}function x8e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function k8e(o){o=o|0,Q8e(o,5052),T8e(o)|0,R8e(o,5058,26)|0,F8e(o,5069,1)|0,N8e(o,5077,10)|0,O8e(o,5087,19)|0,L8e(o,5094,27)|0}function Q8e(o,l){o=o|0,l=l|0;var u=0;u=xje()|0,n[o>>2]=u,kje(u,l),jh(n[o>>2]|0)}function T8e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,gje()|0),o|0}function R8e(o,l,u){return o=o|0,l=l|0,u=u|0,XHe(o,Bn(l)|0,u,0),o|0}function F8e(o,l,u){return o=o|0,l=l|0,u=u|0,OHe(o,Bn(l)|0,u,0),o|0}function N8e(o,l,u){return o=o|0,l=l|0,u=u|0,hHe(o,Bn(l)|0,u,0),o|0}function O8e(o,l,u){return o=o|0,l=l|0,u=u|0,$8e(o,Bn(l)|0,u,0),o|0}function iZ(o,l){o=o|0,l=l|0;var u=0,A=0;e:for(;;){for(u=n[2703]|0;;){if((u|0)==(l|0))break e;if(A=n[u>>2]|0,n[2703]=A,!u)u=A;else break}It(u)}n[2701]=o}function L8e(o,l,u){return o=o|0,l=l|0,u=u|0,M8e(o,Bn(l)|0,u,0),o|0}function M8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=eU()|0,o=U8e(u)|0,vn(m,l,d,o,_8e(u,A)|0,A)}function eU(){var o=0,l=0;if(s[8040]|0||(oZ(10860),gr(65,10860,U|0)|0,l=8040,n[l>>2]=1,n[l+4>>2]=0),!(_r(10860)|0)){o=10860,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));oZ(10860)}return 10860}function U8e(o){return o=o|0,o|0}function _8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=eU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(sZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(H8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function sZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function H8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=j8e(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,G8e(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,sZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,q8e(o,d),W8e(d),I=k;return}}function j8e(o){return o=o|0,536870911}function G8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function q8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function W8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function oZ(o){o=o|0,J8e(o)}function Y8e(o){o=o|0,V8e(o+24|0)}function V8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function J8e(o){o=o|0;var l=0;l=tn()|0,rn(o,1,11,l,K8e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function K8e(){return 1840}function z8e(o,l,u){o=o|0,l=l|0,u=u|0,Z8e(n[(X8e(o)|0)>>2]|0,l,u)}function X8e(o){return o=o|0,(n[(eU()|0)+24>>2]|0)+(o<<3)|0}function Z8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+1|0,d=A,ad(m,l),l=ld(m,l)|0,ad(d,u),u=ld(d,u)|0,sp[o&31](l,u),I=A}function $8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=tU()|0,o=eHe(u)|0,vn(m,l,d,o,tHe(u,A)|0,A)}function tU(){var o=0,l=0;if(s[8048]|0||(lZ(10896),gr(66,10896,U|0)|0,l=8048,n[l>>2]=1,n[l+4>>2]=0),!(_r(10896)|0)){o=10896,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));lZ(10896)}return 10896}function eHe(o){return o=o|0,o|0}function tHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=tU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(aZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(rHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function aZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function rHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=nHe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,iHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,aZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,sHe(o,d),oHe(d),I=k;return}}function nHe(o){return o=o|0,536870911}function iHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function sHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function oHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function lZ(o){o=o|0,cHe(o)}function aHe(o){o=o|0,lHe(o+24|0)}function lHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function cHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,11,l,uHe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function uHe(){return 1852}function fHe(o,l){return o=o|0,l=l|0,pHe(n[(AHe(o)|0)>>2]|0,l)|0}function AHe(o){return o=o|0,(n[(tU()|0)+24>>2]|0)+(o<<3)|0}function pHe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,ad(A,l),l=ld(A,l)|0,l=RP(gd[o&31](l)|0)|0,I=u,l|0}function hHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=rU()|0,o=gHe(u)|0,vn(m,l,d,o,dHe(u,A)|0,A)}function rU(){var o=0,l=0;if(s[8056]|0||(uZ(10932),gr(67,10932,U|0)|0,l=8056,n[l>>2]=1,n[l+4>>2]=0),!(_r(10932)|0)){o=10932,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));uZ(10932)}return 10932}function gHe(o){return o=o|0,o|0}function dHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=rU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(cZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(mHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function cZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function mHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=yHe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,EHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,cZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,IHe(o,d),CHe(d),I=k;return}}function yHe(o){return o=o|0,536870911}function EHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function IHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function uZ(o){o=o|0,vHe(o)}function wHe(o){o=o|0,BHe(o+24|0)}function BHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function vHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,7,l,SHe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function SHe(){return 1860}function DHe(o,l,u){return o=o|0,l=l|0,u=u|0,PHe(n[(bHe(o)|0)>>2]|0,l,u)|0}function bHe(o){return o=o|0,(n[(rU()|0)+24>>2]|0)+(o<<3)|0}function PHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+32|0,B=A+12|0,m=A+8|0,k=A,T=A+16|0,d=A+4|0,xHe(T,l),kHe(k,T,l),Mh(d,u),u=Uh(d,u)|0,n[B>>2]=n[k>>2],F2[o&15](m,B,u),u=QHe(m)|0,Df(m),_h(d),I=A,u|0}function xHe(o,l){o=o|0,l=l|0}function kHe(o,l,u){o=o|0,l=l|0,u=u|0,THe(o,u)}function QHe(o){return o=o|0,Os(o)|0}function THe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+16|0,u=d,A=l,A&1?(RHe(u,0),Me(A|0,u|0)|0,FHe(o,u),NHe(u)):n[o>>2]=n[l>>2],I=d}function RHe(o,l){o=o|0,l=l|0,Su(o,l),n[o+4>>2]=0,s[o+8>>0]=0}function FHe(o,l){o=o|0,l=l|0,n[o>>2]=n[l+4>>2]}function NHe(o){o=o|0,s[o+8>>0]=0}function OHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=nU()|0,o=LHe(u)|0,vn(m,l,d,o,MHe(u,A)|0,A)}function nU(){var o=0,l=0;if(s[8064]|0||(AZ(10968),gr(68,10968,U|0)|0,l=8064,n[l>>2]=1,n[l+4>>2]=0),!(_r(10968)|0)){o=10968,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));AZ(10968)}return 10968}function LHe(o){return o=o|0,o|0}function MHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=nU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(fZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(UHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function fZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function UHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=_He(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,HHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,fZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,jHe(o,d),GHe(d),I=k;return}}function _He(o){return o=o|0,536870911}function HHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function jHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function GHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function AZ(o){o=o|0,YHe(o)}function qHe(o){o=o|0,WHe(o+24|0)}function WHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function YHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,1,l,VHe()|0,5),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function VHe(){return 1872}function JHe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,zHe(n[(KHe(o)|0)>>2]|0,l,u,A,d,m)}function KHe(o){return o=o|0,(n[(nU()|0)+24>>2]|0)+(o<<3)|0}function zHe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+32|0,k=B+16|0,T=B+12|0,M=B+8|0,L=B+4|0,q=B,Mh(k,l),l=Uh(k,l)|0,Mh(T,u),u=Uh(T,u)|0,Mh(M,A),A=Uh(M,A)|0,Mh(L,d),d=Uh(L,d)|0,Mh(q,m),m=Uh(q,m)|0,FZ[o&1](l,u,A,d,m),_h(q),_h(L),_h(M),_h(T),_h(k),I=B}function XHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=iU()|0,o=ZHe(u)|0,vn(m,l,d,o,$He(u,A)|0,A)}function iU(){var o=0,l=0;if(s[8072]|0||(hZ(11004),gr(69,11004,U|0)|0,l=8072,n[l>>2]=1,n[l+4>>2]=0),!(_r(11004)|0)){o=11004,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));hZ(11004)}return 11004}function ZHe(o){return o=o|0,o|0}function $He(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=iU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(pZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(eje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function pZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function eje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=tje(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,rje(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,pZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,nje(o,d),ije(d),I=k;return}}function tje(o){return o=o|0,536870911}function rje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function nje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ije(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function hZ(o){o=o|0,aje(o)}function sje(o){o=o|0,oje(o+24|0)}function oje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function aje(o){o=o|0;var l=0;l=tn()|0,rn(o,1,12,l,lje()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function lje(){return 1896}function cje(o,l,u){o=o|0,l=l|0,u=u|0,fje(n[(uje(o)|0)>>2]|0,l,u)}function uje(o){return o=o|0,(n[(iU()|0)+24>>2]|0)+(o<<3)|0}function fje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+4|0,d=A,Aje(m,l),l=pje(m,l)|0,Mh(d,u),u=Uh(d,u)|0,sp[o&31](l,u),_h(d),I=A}function Aje(o,l){o=o|0,l=l|0}function pje(o,l){return o=o|0,l=l|0,hje(l)|0}function hje(o){return o=o|0,o|0}function gje(){var o=0;return s[8080]|0||(gZ(11040),gr(70,11040,U|0)|0,o=8080,n[o>>2]=1,n[o+4>>2]=0),_r(11040)|0||gZ(11040),11040}function gZ(o){o=o|0,yje(o),ud(o,71)}function dje(o){o=o|0,mje(o+24|0)}function mje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function yje(o){o=o|0;var l=0;l=tn()|0,rn(o,5,7,l,wje()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Eje(o){o=o|0,Ije(o)}function Ije(o){o=o|0,Cje(o)}function Cje(o){o=o|0,s[o+8>>0]=1}function wje(){return 1936}function Bje(){return vje()|0}function vje(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,m=o+4|0,n[m>>2]=Kt(1)|0,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],Sje(A,m,d),n[u>>2]=A,I=l,o|0}function Sje(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1916,n[u+12>>2]=l,n[o+4>>2]=u}function Dje(o){o=o|0,$y(o),It(o)}function bje(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function Pje(o){o=o|0,It(o)}function xje(){var o=0;return s[8088]|0||(Oje(11076),gr(25,11076,U|0)|0,o=8088,n[o>>2]=1,n[o+4>>2]=0),11076}function kje(o,l){o=o|0,l=l|0,n[o>>2]=Qje()|0,n[o+4>>2]=Tje()|0,n[o+12>>2]=l,n[o+8>>2]=Rje()|0,n[o+32>>2]=10}function Qje(){return 11745}function Tje(){return 1940}function Rje(){return FP()|0}function Fje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(Nje(u),It(u)):l|0&&It(l)}function Nje(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function Oje(o){o=o|0,Lh(o)}function bu(o,l){o=o|0,l=l|0,n[o>>2]=l}function sU(o){return o=o|0,n[o>>2]|0}function Lje(o){return o=o|0,s[n[o>>2]>>0]|0}function Mje(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,n[A>>2]=n[o>>2],Uje(l,A)|0,I=u}function Uje(o,l){o=o|0,l=l|0;var u=0;return u=_je(n[o>>2]|0,l)|0,l=o+4|0,n[(n[l>>2]|0)+8>>2]=u,n[(n[l>>2]|0)+8>>2]|0}function _je(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,Fl(A),o=Os(o)|0,l=Hje(o,n[l>>2]|0)|0,Nl(A),I=u,l|0}function Fl(o){o=o|0,n[o>>2]=n[2701],n[o+4>>2]=n[2703]}function Hje(o,l){o=o|0,l=l|0;var u=0;return u=da(jje()|0)|0,dn(0,u|0,o|0,ZM(l)|0)|0}function Nl(o){o=o|0,iZ(n[o>>2]|0,n[o+4>>2]|0)}function jje(){var o=0;return s[8096]|0||(Gje(11120),o=8096,n[o>>2]=1,n[o+4>>2]=0),11120}function Gje(o){o=o|0,Qo(o,qje()|0,1)}function qje(){return 1948}function Wje(){Yje()}function Yje(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;if(Le=I,I=I+16|0,L=Le+4|0,q=Le,oa(65536,10804,n[2702]|0,10812),u=UX()|0,l=n[u>>2]|0,o=n[l>>2]|0,o|0)for(A=n[u+8>>2]|0,u=n[u+4>>2]|0;pf(o|0,c[u>>0]|0|0,s[A>>0]|0),l=l+4|0,o=n[l>>2]|0,o;)A=A+1|0,u=u+1|0;if(o=_X()|0,l=n[o>>2]|0,l|0)do NA(l|0,n[o+4>>2]|0),o=o+8|0,l=n[o>>2]|0;while(l|0);NA(Vje()|0,5167),M=Jy()|0,o=n[M>>2]|0;e:do if(o|0){do Jje(n[o+4>>2]|0),o=n[o>>2]|0;while(o|0);if(o=n[M>>2]|0,o|0){T=M;do{for(;d=o,o=n[o>>2]|0,d=n[d+4>>2]|0,!!(Kje(d)|0);)if(n[q>>2]=T,n[L>>2]=n[q>>2],zje(M,L)|0,!o)break e;if(Xje(d),T=n[T>>2]|0,l=dZ(d)|0,m=Oi()|0,B=I,I=I+((1*(l<<2)|0)+15&-16)|0,k=I,I=I+((1*(l<<2)|0)+15&-16)|0,l=n[(ZX(d)|0)>>2]|0,l|0)for(u=B,A=k;n[u>>2]=n[(Ky(n[l+4>>2]|0)|0)>>2],n[A>>2]=n[l+8>>2],l=n[l>>2]|0,l;)u=u+4|0,A=A+4|0;Qe=Ky(d)|0,l=Zje(d)|0,u=dZ(d)|0,A=$je(d)|0,oc(Qe|0,l|0,B|0,k|0,u|0,A|0,YM(d)|0),FA(m|0)}while(o|0)}}while(!1);if(o=n[(VM()|0)>>2]|0,o|0)do Qe=o+4|0,M=JM(Qe)|0,d=k2(M)|0,m=P2(M)|0,B=(x2(M)|0)+1|0,k=UP(M)|0,T=mZ(Qe)|0,M=_r(M)|0,L=OP(Qe)|0,q=oU(Qe)|0,uu(0,d|0,m|0,B|0,k|0,T|0,M|0,L|0,q|0,aU(Qe)|0),o=n[o>>2]|0;while(o|0);o=n[(Jy()|0)>>2]|0;e:do if(o|0){t:for(;;){if(l=n[o+4>>2]|0,l|0&&(ae=n[(Ky(l)|0)>>2]|0,Ye=n[($X(l)|0)>>2]|0,Ye|0)){u=Ye;do{l=u+4|0,A=JM(l)|0;r:do if(A|0)switch(_r(A)|0){case 0:break t;case 4:case 3:case 2:{k=k2(A)|0,T=P2(A)|0,M=(x2(A)|0)+1|0,L=UP(A)|0,q=_r(A)|0,Qe=OP(l)|0,uu(ae|0,k|0,T|0,M|0,L|0,0,q|0,Qe|0,oU(l)|0,aU(l)|0);break r}case 1:{B=k2(A)|0,k=P2(A)|0,T=(x2(A)|0)+1|0,M=UP(A)|0,L=mZ(l)|0,q=_r(A)|0,Qe=OP(l)|0,uu(ae|0,B|0,k|0,T|0,M|0,L|0,q|0,Qe|0,oU(l)|0,aU(l)|0);break r}case 5:{M=k2(A)|0,L=P2(A)|0,q=(x2(A)|0)+1|0,Qe=UP(A)|0,uu(ae|0,M|0,L|0,q|0,Qe|0,e6e(A)|0,_r(A)|0,0,0,0);break r}default:break r}while(!1);u=n[u>>2]|0}while(u|0)}if(o=n[o>>2]|0,!o)break e}Nt()}while(!1);ve(),I=Le}function Vje(){return 11703}function Jje(o){o=o|0,s[o+40>>0]=0}function Kje(o){return o=o|0,(s[o+40>>0]|0)!=0|0}function zje(o,l){return o=o|0,l=l|0,l=t6e(l)|0,o=n[l>>2]|0,n[l>>2]=n[o>>2],It(o),n[l>>2]|0}function Xje(o){o=o|0,s[o+40>>0]=1}function dZ(o){return o=o|0,n[o+20>>2]|0}function Zje(o){return o=o|0,n[o+8>>2]|0}function $je(o){return o=o|0,n[o+32>>2]|0}function UP(o){return o=o|0,n[o+4>>2]|0}function mZ(o){return o=o|0,n[o+4>>2]|0}function oU(o){return o=o|0,n[o+8>>2]|0}function aU(o){return o=o|0,n[o+16>>2]|0}function e6e(o){return o=o|0,n[o+20>>2]|0}function t6e(o){return o=o|0,n[o>>2]|0}function _P(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0;Lt=I,I=I+16|0,ae=Lt;do if(o>>>0<245){if(M=o>>>0<11?16:o+11&-8,o=M>>>3,q=n[2783]|0,u=q>>>o,u&3|0)return l=(u&1^1)+o|0,o=11172+(l<<1<<2)|0,u=o+8|0,A=n[u>>2]|0,d=A+8|0,m=n[d>>2]|0,(o|0)==(m|0)?n[2783]=q&~(1<>2]=o,n[u>>2]=m),We=l<<3,n[A+4>>2]=We|3,We=A+We+4|0,n[We>>2]=n[We>>2]|1,We=d,I=Lt,We|0;if(L=n[2785]|0,M>>>0>L>>>0){if(u|0)return l=2<>>12&16,l=l>>>B,u=l>>>5&8,l=l>>>u,d=l>>>2&4,l=l>>>d,o=l>>>1&2,l=l>>>o,A=l>>>1&1,A=(u|B|d|o|A)+(l>>>A)|0,l=11172+(A<<1<<2)|0,o=l+8|0,d=n[o>>2]|0,B=d+8|0,u=n[B>>2]|0,(l|0)==(u|0)?(o=q&~(1<>2]=l,n[o>>2]=u,o=q),m=(A<<3)-M|0,n[d+4>>2]=M|3,A=d+M|0,n[A+4>>2]=m|1,n[A+m>>2]=m,L|0&&(d=n[2788]|0,l=L>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=d,n[l+12>>2]=d,n[d+8>>2]=l,n[d+12>>2]=u),n[2785]=m,n[2788]=A,We=B,I=Lt,We|0;if(k=n[2784]|0,k){if(u=(k&0-k)+-1|0,B=u>>>12&16,u=u>>>B,m=u>>>5&8,u=u>>>m,T=u>>>2&4,u=u>>>T,A=u>>>1&2,u=u>>>A,o=u>>>1&1,o=n[11436+((m|B|T|A|o)+(u>>>o)<<2)>>2]|0,u=(n[o+4>>2]&-8)-M|0,A=n[o+16+(((n[o+16>>2]|0)==0&1)<<2)>>2]|0,!A)T=o,m=u;else{do B=(n[A+4>>2]&-8)-M|0,T=B>>>0>>0,u=T?B:u,o=T?A:o,A=n[A+16+(((n[A+16>>2]|0)==0&1)<<2)>>2]|0;while(A|0);T=o,m=u}if(B=T+M|0,T>>>0>>0){d=n[T+24>>2]|0,l=n[T+12>>2]|0;do if((l|0)==(T|0)){if(o=T+20|0,l=n[o>>2]|0,!l&&(o=T+16|0,l=n[o>>2]|0,!l)){u=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0,u=l}else u=n[T+8>>2]|0,n[u+12>>2]=l,n[l+8>>2]=u,u=l;while(!1);do if(d|0){if(l=n[T+28>>2]|0,o=11436+(l<<2)|0,(T|0)==(n[o>>2]|0)){if(n[o>>2]=u,!u){n[2784]=k&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=d,l=n[T+16>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),l=n[T+20>>2]|0,l|0&&(n[u+20>>2]=l,n[l+24>>2]=u)}while(!1);return m>>>0<16?(We=m+M|0,n[T+4>>2]=We|3,We=T+We+4|0,n[We>>2]=n[We>>2]|1):(n[T+4>>2]=M|3,n[B+4>>2]=m|1,n[B+m>>2]=m,L|0&&(A=n[2788]|0,l=L>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=q|l,l=u,o=u+8|0),n[o>>2]=A,n[l+12>>2]=A,n[A+8>>2]=l,n[A+12>>2]=u),n[2785]=m,n[2788]=B),We=T+8|0,I=Lt,We|0}else q=M}else q=M}else q=M}else if(o>>>0<=4294967231)if(o=o+11|0,M=o&-8,T=n[2784]|0,T){A=0-M|0,o=o>>>8,o?M>>>0>16777215?k=31:(q=(o+1048320|0)>>>16&8,He=o<>>16&4,He=He<>>16&2,k=14-(L|q|k)+(He<>>15)|0,k=M>>>(k+7|0)&1|k<<1):k=0,u=n[11436+(k<<2)>>2]|0;e:do if(!u)u=0,o=0,He=57;else for(o=0,B=M<<((k|0)==31?0:25-(k>>>1)|0),m=0;;){if(d=(n[u+4>>2]&-8)-M|0,d>>>0>>0)if(d)o=u,A=d;else{o=u,A=0,d=u,He=61;break e}if(d=n[u+20>>2]|0,u=n[u+16+(B>>>31<<2)>>2]|0,m=(d|0)==0|(d|0)==(u|0)?m:d,d=(u|0)==0,d){u=m,He=57;break}else B=B<<((d^1)&1)}while(!1);if((He|0)==57){if((u|0)==0&(o|0)==0){if(o=2<>>12&16,q=q>>>B,m=q>>>5&8,q=q>>>m,k=q>>>2&4,q=q>>>k,L=q>>>1&2,q=q>>>L,u=q>>>1&1,o=0,u=n[11436+((m|B|k|L|u)+(q>>>u)<<2)>>2]|0}u?(d=u,He=61):(k=o,B=A)}if((He|0)==61)for(;;)if(He=0,u=(n[d+4>>2]&-8)-M|0,q=u>>>0>>0,u=q?u:A,o=q?d:o,d=n[d+16+(((n[d+16>>2]|0)==0&1)<<2)>>2]|0,d)A=u,He=61;else{k=o,B=u;break}if(k|0&&B>>>0<((n[2785]|0)-M|0)>>>0){if(m=k+M|0,k>>>0>=m>>>0)return We=0,I=Lt,We|0;d=n[k+24>>2]|0,l=n[k+12>>2]|0;do if((l|0)==(k|0)){if(o=k+20|0,l=n[o>>2]|0,!l&&(o=k+16|0,l=n[o>>2]|0,!l)){l=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0}else We=n[k+8>>2]|0,n[We+12>>2]=l,n[l+8>>2]=We;while(!1);do if(d){if(o=n[k+28>>2]|0,u=11436+(o<<2)|0,(k|0)==(n[u>>2]|0)){if(n[u>>2]=l,!l){A=T&~(1<>2]|0)!=(k|0)&1)<<2)>>2]=l,!l){A=T;break}n[l+24>>2]=d,o=n[k+16>>2]|0,o|0&&(n[l+16>>2]=o,n[o+24>>2]=l),o=n[k+20>>2]|0,o&&(n[l+20>>2]=o,n[o+24>>2]=l),A=T}else A=T;while(!1);do if(B>>>0>=16){if(n[k+4>>2]=M|3,n[m+4>>2]=B|1,n[m+B>>2]=B,l=B>>>3,B>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=m,n[l+12>>2]=m,n[m+8>>2]=l,n[m+12>>2]=u;break}if(l=B>>>8,l?B>>>0>16777215?l=31:(He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,l=14-(ct|He|l)+(We<>>15)|0,l=B>>>(l+7|0)&1|l<<1):l=0,u=11436+(l<<2)|0,n[m+28>>2]=l,o=m+16|0,n[o+4>>2]=0,n[o>>2]=0,o=1<>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}for(o=B<<((l|0)==31?0:25-(l>>>1)|0),u=n[u>>2]|0;;){if((n[u+4>>2]&-8|0)==(B|0)){He=97;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=96;break}}if((He|0)==96){n[A>>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}else if((He|0)==97){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=m,n[He>>2]=m,n[m+8>>2]=We,n[m+12>>2]=u,n[m+24>>2]=0;break}}else We=B+M|0,n[k+4>>2]=We|3,We=k+We+4|0,n[We>>2]=n[We>>2]|1;while(!1);return We=k+8|0,I=Lt,We|0}else q=M}else q=M;else q=-1;while(!1);if(u=n[2785]|0,u>>>0>=q>>>0)return l=u-q|0,o=n[2788]|0,l>>>0>15?(We=o+q|0,n[2788]=We,n[2785]=l,n[We+4>>2]=l|1,n[We+l>>2]=l,n[o+4>>2]=q|3):(n[2785]=0,n[2788]=0,n[o+4>>2]=u|3,We=o+u+4|0,n[We>>2]=n[We>>2]|1),We=o+8|0,I=Lt,We|0;if(B=n[2786]|0,B>>>0>q>>>0)return ct=B-q|0,n[2786]=ct,We=n[2789]|0,He=We+q|0,n[2789]=He,n[He+4>>2]=ct|1,n[We+4>>2]=q|3,We=We+8|0,I=Lt,We|0;if(n[2901]|0?o=n[2903]|0:(n[2903]=4096,n[2902]=4096,n[2904]=-1,n[2905]=-1,n[2906]=0,n[2894]=0,o=ae&-16^1431655768,n[ae>>2]=o,n[2901]=o,o=4096),k=q+48|0,T=q+47|0,m=o+T|0,d=0-o|0,M=m&d,M>>>0<=q>>>0||(o=n[2893]|0,o|0&&(L=n[2891]|0,ae=L+M|0,ae>>>0<=L>>>0|ae>>>0>o>>>0)))return We=0,I=Lt,We|0;e:do if(n[2894]&4)l=0,He=133;else{u=n[2789]|0;t:do if(u){for(A=11580;o=n[A>>2]|0,!(o>>>0<=u>>>0&&(Qe=A+4|0,(o+(n[Qe>>2]|0)|0)>>>0>u>>>0));)if(o=n[A+8>>2]|0,o)A=o;else{He=118;break t}if(l=m-B&d,l>>>0<2147483647)if(o=qh(l|0)|0,(o|0)==((n[A>>2]|0)+(n[Qe>>2]|0)|0)){if((o|0)!=-1){B=l,m=o,He=135;break e}}else A=o,He=126;else l=0}else He=118;while(!1);do if((He|0)==118)if(u=qh(0)|0,(u|0)!=-1&&(l=u,Ye=n[2902]|0,Le=Ye+-1|0,l=(Le&l|0?(Le+l&0-Ye)-l|0:0)+M|0,Ye=n[2891]|0,Le=l+Ye|0,l>>>0>q>>>0&l>>>0<2147483647)){if(Qe=n[2893]|0,Qe|0&&Le>>>0<=Ye>>>0|Le>>>0>Qe>>>0){l=0;break}if(o=qh(l|0)|0,(o|0)==(u|0)){B=l,m=u,He=135;break e}else A=o,He=126}else l=0;while(!1);do if((He|0)==126){if(u=0-l|0,!(k>>>0>l>>>0&(l>>>0<2147483647&(A|0)!=-1)))if((A|0)==-1){l=0;break}else{B=l,m=A,He=135;break e}if(o=n[2903]|0,o=T-l+o&0-o,o>>>0>=2147483647){B=l,m=A,He=135;break e}if((qh(o|0)|0)==-1){qh(u|0)|0,l=0;break}else{B=o+l|0,m=A,He=135;break e}}while(!1);n[2894]=n[2894]|4,He=133}while(!1);if((He|0)==133&&M>>>0<2147483647&&(ct=qh(M|0)|0,Qe=qh(0)|0,tt=Qe-ct|0,Ze=tt>>>0>(q+40|0)>>>0,!((ct|0)==-1|Ze^1|ct>>>0>>0&((ct|0)!=-1&(Qe|0)!=-1)^1))&&(B=Ze?tt:l,m=ct,He=135),(He|0)==135){l=(n[2891]|0)+B|0,n[2891]=l,l>>>0>(n[2892]|0)>>>0&&(n[2892]=l),T=n[2789]|0;do if(T){for(l=11580;;){if(o=n[l>>2]|0,u=l+4|0,A=n[u>>2]|0,(m|0)==(o+A|0)){He=145;break}if(d=n[l+8>>2]|0,d)l=d;else break}if((He|0)==145&&!(n[l+12>>2]&8|0)&&T>>>0>>0&T>>>0>=o>>>0){n[u>>2]=A+B,We=T+8|0,We=We&7|0?0-We&7:0,He=T+We|0,We=(n[2786]|0)+(B-We)|0,n[2789]=He,n[2786]=We,n[He+4>>2]=We|1,n[He+We+4>>2]=40,n[2790]=n[2905];break}for(m>>>0<(n[2787]|0)>>>0&&(n[2787]=m),u=m+B|0,l=11580;;){if((n[l>>2]|0)==(u|0)){He=153;break}if(o=n[l+8>>2]|0,o)l=o;else break}if((He|0)==153&&!(n[l+12>>2]&8|0)){n[l>>2]=m,L=l+4|0,n[L>>2]=(n[L>>2]|0)+B,L=m+8|0,L=m+(L&7|0?0-L&7:0)|0,l=u+8|0,l=u+(l&7|0?0-l&7:0)|0,M=L+q|0,k=l-L-q|0,n[L+4>>2]=q|3;do if((l|0)!=(T|0)){if((l|0)==(n[2788]|0)){We=(n[2785]|0)+k|0,n[2785]=We,n[2788]=M,n[M+4>>2]=We|1,n[M+We>>2]=We;break}if(o=n[l+4>>2]|0,(o&3|0)==1){B=o&-8,A=o>>>3;e:do if(o>>>0<256)if(o=n[l+8>>2]|0,u=n[l+12>>2]|0,(u|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=u,n[u+8>>2]=o;break}else{m=n[l+24>>2]|0,o=n[l+12>>2]|0;do if((o|0)==(l|0)){if(A=l+16|0,u=A+4|0,o=n[u>>2]|0,!o)if(o=n[A>>2]|0,o)u=A;else{o=0;break}for(;;){if(A=o+20|0,d=n[A>>2]|0,d|0){o=d,u=A;continue}if(A=o+16|0,d=n[A>>2]|0,d)o=d,u=A;else break}n[u>>2]=0}else We=n[l+8>>2]|0,n[We+12>>2]=o,n[o+8>>2]=We;while(!1);if(!m)break;u=n[l+28>>2]|0,A=11436+(u<<2)|0;do if((l|0)!=(n[A>>2]|0)){if(n[m+16+(((n[m+16>>2]|0)!=(l|0)&1)<<2)>>2]=o,!o)break e}else{if(n[A>>2]=o,o|0)break;n[2784]=n[2784]&~(1<>2]=m,u=l+16|0,A=n[u>>2]|0,A|0&&(n[o+16>>2]=A,n[A+24>>2]=o),u=n[u+4>>2]|0,!u)break;n[o+20>>2]=u,n[u+24>>2]=o}while(!1);l=l+B|0,d=B+k|0}else d=k;if(l=l+4|0,n[l>>2]=n[l>>2]&-2,n[M+4>>2]=d|1,n[M+d>>2]=d,l=d>>>3,d>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=M,n[l+12>>2]=M,n[M+8>>2]=l,n[M+12>>2]=u;break}l=d>>>8;do if(!l)l=0;else{if(d>>>0>16777215){l=31;break}He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,l=14-(ct|He|l)+(We<>>15)|0,l=d>>>(l+7|0)&1|l<<1}while(!1);if(A=11436+(l<<2)|0,n[M+28>>2]=l,o=M+16|0,n[o+4>>2]=0,n[o>>2]=0,o=n[2784]|0,u=1<>2]=M,n[M+24>>2]=A,n[M+12>>2]=M,n[M+8>>2]=M;break}for(o=d<<((l|0)==31?0:25-(l>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){He=194;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=193;break}}if((He|0)==193){n[A>>2]=M,n[M+24>>2]=u,n[M+12>>2]=M,n[M+8>>2]=M;break}else if((He|0)==194){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=M,n[He>>2]=M,n[M+8>>2]=We,n[M+12>>2]=u,n[M+24>>2]=0;break}}else We=(n[2786]|0)+k|0,n[2786]=We,n[2789]=M,n[M+4>>2]=We|1;while(!1);return We=L+8|0,I=Lt,We|0}for(l=11580;o=n[l>>2]|0,!(o>>>0<=T>>>0&&(We=o+(n[l+4>>2]|0)|0,We>>>0>T>>>0));)l=n[l+8>>2]|0;d=We+-47|0,o=d+8|0,o=d+(o&7|0?0-o&7:0)|0,d=T+16|0,o=o>>>0>>0?T:o,l=o+8|0,u=m+8|0,u=u&7|0?0-u&7:0,He=m+u|0,u=B+-40-u|0,n[2789]=He,n[2786]=u,n[He+4>>2]=u|1,n[He+u+4>>2]=40,n[2790]=n[2905],u=o+4|0,n[u>>2]=27,n[l>>2]=n[2895],n[l+4>>2]=n[2896],n[l+8>>2]=n[2897],n[l+12>>2]=n[2898],n[2895]=m,n[2896]=B,n[2898]=0,n[2897]=l,l=o+24|0;do He=l,l=l+4|0,n[l>>2]=7;while((He+8|0)>>>0>>0);if((o|0)!=(T|0)){if(m=o-T|0,n[u>>2]=n[u>>2]&-2,n[T+4>>2]=m|1,n[o>>2]=m,l=m>>>3,m>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=T,n[l+12>>2]=T,n[T+8>>2]=l,n[T+12>>2]=u;break}if(l=m>>>8,l?m>>>0>16777215?u=31:(He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,u=14-(ct|He|u)+(We<>>15)|0,u=m>>>(u+7|0)&1|u<<1):u=0,A=11436+(u<<2)|0,n[T+28>>2]=u,n[T+20>>2]=0,n[d>>2]=0,l=n[2784]|0,o=1<>2]=T,n[T+24>>2]=A,n[T+12>>2]=T,n[T+8>>2]=T;break}for(o=m<<((u|0)==31?0:25-(u>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(m|0)){He=216;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=215;break}}if((He|0)==215){n[A>>2]=T,n[T+24>>2]=u,n[T+12>>2]=T,n[T+8>>2]=T;break}else if((He|0)==216){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=T,n[He>>2]=T,n[T+8>>2]=We,n[T+12>>2]=u,n[T+24>>2]=0;break}}}else{We=n[2787]|0,(We|0)==0|m>>>0>>0&&(n[2787]=m),n[2895]=m,n[2896]=B,n[2898]=0,n[2792]=n[2901],n[2791]=-1,l=0;do We=11172+(l<<1<<2)|0,n[We+12>>2]=We,n[We+8>>2]=We,l=l+1|0;while((l|0)!=32);We=m+8|0,We=We&7|0?0-We&7:0,He=m+We|0,We=B+-40-We|0,n[2789]=He,n[2786]=We,n[He+4>>2]=We|1,n[He+We+4>>2]=40,n[2790]=n[2905]}while(!1);if(l=n[2786]|0,l>>>0>q>>>0)return ct=l-q|0,n[2786]=ct,We=n[2789]|0,He=We+q|0,n[2789]=He,n[He+4>>2]=ct|1,n[We+4>>2]=q|3,We=We+8|0,I=Lt,We|0}return n[(Xy()|0)>>2]=12,We=0,I=Lt,We|0}function HP(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(o){u=o+-8|0,d=n[2787]|0,o=n[o+-4>>2]|0,l=o&-8,T=u+l|0;do if(o&1)k=u,B=u;else{if(A=n[u>>2]|0,!(o&3)||(B=u+(0-A)|0,m=A+l|0,B>>>0>>0))return;if((B|0)==(n[2788]|0)){if(o=T+4|0,l=n[o>>2]|0,(l&3|0)!=3){k=B,l=m;break}n[2785]=m,n[o>>2]=l&-2,n[B+4>>2]=m|1,n[B+m>>2]=m;return}if(u=A>>>3,A>>>0<256)if(o=n[B+8>>2]|0,l=n[B+12>>2]|0,(l|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=l,n[l+8>>2]=o,k=B,l=m;break}d=n[B+24>>2]|0,o=n[B+12>>2]|0;do if((o|0)==(B|0)){if(u=B+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{o=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0}else k=n[B+8>>2]|0,n[k+12>>2]=o,n[o+8>>2]=k;while(!1);if(d){if(l=n[B+28>>2]|0,u=11436+(l<<2)|0,(B|0)==(n[u>>2]|0)){if(n[u>>2]=o,!o){n[2784]=n[2784]&~(1<>2]|0)!=(B|0)&1)<<2)>>2]=o,!o){k=B,l=m;break}n[o+24>>2]=d,l=B+16|0,u=n[l>>2]|0,u|0&&(n[o+16>>2]=u,n[u+24>>2]=o),l=n[l+4>>2]|0,l?(n[o+20>>2]=l,n[l+24>>2]=o,k=B,l=m):(k=B,l=m)}else k=B,l=m}while(!1);if(!(B>>>0>=T>>>0)&&(o=T+4|0,A=n[o>>2]|0,!!(A&1))){if(A&2)n[o>>2]=A&-2,n[k+4>>2]=l|1,n[B+l>>2]=l,d=l;else{if(o=n[2788]|0,(T|0)==(n[2789]|0)){if(T=(n[2786]|0)+l|0,n[2786]=T,n[2789]=k,n[k+4>>2]=T|1,(k|0)!=(o|0))return;n[2788]=0,n[2785]=0;return}if((T|0)==(o|0)){T=(n[2785]|0)+l|0,n[2785]=T,n[2788]=B,n[k+4>>2]=T|1,n[B+T>>2]=T;return}d=(A&-8)+l|0,u=A>>>3;do if(A>>>0<256)if(l=n[T+8>>2]|0,o=n[T+12>>2]|0,(o|0)==(l|0)){n[2783]=n[2783]&~(1<>2]=o,n[o+8>>2]=l;break}else{m=n[T+24>>2]|0,o=n[T+12>>2]|0;do if((o|0)==(T|0)){if(u=T+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{u=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0,u=o}else u=n[T+8>>2]|0,n[u+12>>2]=o,n[o+8>>2]=u,u=o;while(!1);if(m|0){if(o=n[T+28>>2]|0,l=11436+(o<<2)|0,(T|0)==(n[l>>2]|0)){if(n[l>>2]=u,!u){n[2784]=n[2784]&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=m,o=T+16|0,l=n[o>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),o=n[o+4>>2]|0,o|0&&(n[u+20>>2]=o,n[o+24>>2]=u)}}while(!1);if(n[k+4>>2]=d|1,n[B+d>>2]=d,(k|0)==(n[2788]|0)){n[2785]=d;return}}if(o=d>>>3,d>>>0<256){u=11172+(o<<1<<2)|0,l=n[2783]|0,o=1<>2]|0):(n[2783]=l|o,o=u,l=u+8|0),n[l>>2]=k,n[o+12>>2]=k,n[k+8>>2]=o,n[k+12>>2]=u;return}o=d>>>8,o?d>>>0>16777215?o=31:(B=(o+1048320|0)>>>16&8,T=o<>>16&4,T=T<>>16&2,o=14-(m|B|o)+(T<>>15)|0,o=d>>>(o+7|0)&1|o<<1):o=0,A=11436+(o<<2)|0,n[k+28>>2]=o,n[k+20>>2]=0,n[k+16>>2]=0,l=n[2784]|0,u=1<>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){o=73;break}if(A=u+16+(l>>>31<<2)|0,o=n[A>>2]|0,o)l=l<<1,u=o;else{o=72;break}}if((o|0)==72){n[A>>2]=k,n[k+24>>2]=u,n[k+12>>2]=k,n[k+8>>2]=k;break}else if((o|0)==73){B=u+8|0,T=n[B>>2]|0,n[T+12>>2]=k,n[B>>2]=k,n[k+8>>2]=T,n[k+12>>2]=u,n[k+24>>2]=0;break}}else n[2784]=l|u,n[A>>2]=k,n[k+24>>2]=A,n[k+12>>2]=k,n[k+8>>2]=k;while(!1);if(T=(n[2791]|0)+-1|0,n[2791]=T,!T)o=11588;else return;for(;o=n[o>>2]|0,o;)o=o+8|0;n[2791]=-1}}}function r6e(){return 11628}function n6e(o){o=o|0;var l=0,u=0;return l=I,I=I+16|0,u=l,n[u>>2]=o6e(n[o+60>>2]|0)|0,o=jP(Au(6,u|0)|0)|0,I=l,o|0}function yZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0;q=I,I=I+48|0,M=q+16|0,m=q,d=q+32|0,k=o+28|0,A=n[k>>2]|0,n[d>>2]=A,T=o+20|0,A=(n[T>>2]|0)-A|0,n[d+4>>2]=A,n[d+8>>2]=l,n[d+12>>2]=u,A=A+u|0,B=o+60|0,n[m>>2]=n[B>>2],n[m+4>>2]=d,n[m+8>>2]=2,m=jP(La(146,m|0)|0)|0;e:do if((A|0)!=(m|0)){for(l=2;!((m|0)<0);)if(A=A-m|0,Ye=n[d+4>>2]|0,ae=m>>>0>Ye>>>0,d=ae?d+8|0:d,l=(ae<<31>>31)+l|0,Ye=m-(ae?Ye:0)|0,n[d>>2]=(n[d>>2]|0)+Ye,ae=d+4|0,n[ae>>2]=(n[ae>>2]|0)-Ye,n[M>>2]=n[B>>2],n[M+4>>2]=d,n[M+8>>2]=l,m=jP(La(146,M|0)|0)|0,(A|0)==(m|0)){L=3;break e}n[o+16>>2]=0,n[k>>2]=0,n[T>>2]=0,n[o>>2]=n[o>>2]|32,(l|0)==2?u=0:u=u-(n[d+4>>2]|0)|0}else L=3;while(!1);return(L|0)==3&&(Ye=n[o+44>>2]|0,n[o+16>>2]=Ye+(n[o+48>>2]|0),n[k>>2]=Ye,n[T>>2]=Ye),I=q,u|0}function i6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return d=I,I=I+32|0,m=d,A=d+20|0,n[m>>2]=n[o+60>>2],n[m+4>>2]=0,n[m+8>>2]=l,n[m+12>>2]=A,n[m+16>>2]=u,(jP(Oa(140,m|0)|0)|0)<0?(n[A>>2]=-1,o=-1):o=n[A>>2]|0,I=d,o|0}function jP(o){return o=o|0,o>>>0>4294963200&&(n[(Xy()|0)>>2]=0-o,o=-1),o|0}function Xy(){return(s6e()|0)+64|0}function s6e(){return lU()|0}function lU(){return 2084}function o6e(o){return o=o|0,o|0}function a6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return d=I,I=I+32|0,A=d,n[o+36>>2]=1,!(n[o>>2]&64|0)&&(n[A>>2]=n[o+60>>2],n[A+4>>2]=21523,n[A+8>>2]=d+16,no(54,A|0)|0)&&(s[o+75>>0]=-1),A=yZ(o,l,u)|0,I=d,A|0}function EZ(o,l){o=o|0,l=l|0;var u=0,A=0;if(u=s[o>>0]|0,A=s[l>>0]|0,!(u<<24>>24)||u<<24>>24!=A<<24>>24)o=A;else{do o=o+1|0,l=l+1|0,u=s[o>>0]|0,A=s[l>>0]|0;while(!(!(u<<24>>24)||u<<24>>24!=A<<24>>24));o=A}return(u&255)-(o&255)|0}function l6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;e:do if(!u)o=0;else{for(;A=s[o>>0]|0,d=s[l>>0]|0,A<<24>>24==d<<24>>24;)if(u=u+-1|0,u)o=o+1|0,l=l+1|0;else{o=0;break e}o=(A&255)-(d&255)|0}while(!1);return o|0}function IZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;Qe=I,I=I+224|0,L=Qe+120|0,q=Qe+80|0,Ye=Qe,Le=Qe+136|0,A=q,d=A+40|0;do n[A>>2]=0,A=A+4|0;while((A|0)<(d|0));return n[L>>2]=n[u>>2],(cU(0,l,L,Ye,q)|0)<0?u=-1:((n[o+76>>2]|0)>-1?ae=c6e(o)|0:ae=0,u=n[o>>2]|0,M=u&32,(s[o+74>>0]|0)<1&&(n[o>>2]=u&-33),A=o+48|0,n[A>>2]|0?u=cU(o,l,L,Ye,q)|0:(d=o+44|0,m=n[d>>2]|0,n[d>>2]=Le,B=o+28|0,n[B>>2]=Le,k=o+20|0,n[k>>2]=Le,n[A>>2]=80,T=o+16|0,n[T>>2]=Le+80,u=cU(o,l,L,Ye,q)|0,m&&(YP[n[o+36>>2]&7](o,0,0)|0,u=n[k>>2]|0?u:-1,n[d>>2]=m,n[A>>2]=0,n[T>>2]=0,n[B>>2]=0,n[k>>2]=0)),A=n[o>>2]|0,n[o>>2]=A|M,ae|0&&u6e(o),u=A&32|0?-1:u),I=Qe,u|0}function cU(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0;cr=I,I=I+64|0,fr=cr+16|0,$t=cr,Lt=cr+24|0,Tr=cr+8|0,Hr=cr+20|0,n[fr>>2]=l,ct=(o|0)!=0,He=Lt+40|0,We=He,Lt=Lt+39|0,Gr=Tr+4|0,B=0,m=0,L=0;e:for(;;){do if((m|0)>-1)if((B|0)>(2147483647-m|0)){n[(Xy()|0)>>2]=75,m=-1;break}else{m=B+m|0;break}while(!1);if(B=s[l>>0]|0,B<<24>>24)k=l;else{Ze=87;break}t:for(;;){switch(B<<24>>24){case 37:{B=k,Ze=9;break t}case 0:{B=k;break t}default:}tt=k+1|0,n[fr>>2]=tt,B=s[tt>>0]|0,k=tt}t:do if((Ze|0)==9)for(;;){if(Ze=0,(s[k+1>>0]|0)!=37)break t;if(B=B+1|0,k=k+2|0,n[fr>>2]=k,(s[k>>0]|0)==37)Ze=9;else break}while(!1);if(B=B-l|0,ct&&vs(o,l,B),B|0){l=k;continue}T=k+1|0,B=(s[T>>0]|0)+-48|0,B>>>0<10?(tt=(s[k+2>>0]|0)==36,Qe=tt?B:-1,L=tt?1:L,T=tt?k+3|0:T):Qe=-1,n[fr>>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0;t:do if(k>>>0<32)for(M=0,q=B;;){if(B=1<>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0,k>>>0>=32)break;q=B}else M=0;while(!1);if(B<<24>>24==42){if(k=T+1|0,B=(s[k>>0]|0)+-48|0,B>>>0<10&&(s[T+2>>0]|0)==36)n[d+(B<<2)>>2]=10,B=n[A+((s[k>>0]|0)+-48<<3)>>2]|0,L=1,T=T+3|0;else{if(L|0){m=-1;break}ct?(L=(n[u>>2]|0)+3&-4,B=n[L>>2]|0,n[u>>2]=L+4,L=0,T=k):(B=0,L=0,T=k)}n[fr>>2]=T,tt=(B|0)<0,B=tt?0-B|0:B,M=tt?M|8192:M}else{if(B=CZ(fr)|0,(B|0)<0){m=-1;break}T=n[fr>>2]|0}do if((s[T>>0]|0)==46){if((s[T+1>>0]|0)!=42){n[fr>>2]=T+1,k=CZ(fr)|0,T=n[fr>>2]|0;break}if(q=T+2|0,k=(s[q>>0]|0)+-48|0,k>>>0<10&&(s[T+3>>0]|0)==36){n[d+(k<<2)>>2]=10,k=n[A+((s[q>>0]|0)+-48<<3)>>2]|0,T=T+4|0,n[fr>>2]=T;break}if(L|0){m=-1;break e}ct?(tt=(n[u>>2]|0)+3&-4,k=n[tt>>2]|0,n[u>>2]=tt+4):k=0,n[fr>>2]=q,T=q}else k=-1;while(!1);for(Le=0;;){if(((s[T>>0]|0)+-65|0)>>>0>57){m=-1;break e}if(tt=T+1|0,n[fr>>2]=tt,q=s[(s[T>>0]|0)+-65+(5178+(Le*58|0))>>0]|0,ae=q&255,(ae+-1|0)>>>0<8)Le=ae,T=tt;else break}if(!(q<<24>>24)){m=-1;break}Ye=(Qe|0)>-1;do if(q<<24>>24==19)if(Ye){m=-1;break e}else Ze=49;else{if(Ye){n[d+(Qe<<2)>>2]=ae,Ye=A+(Qe<<3)|0,Qe=n[Ye+4>>2]|0,Ze=$t,n[Ze>>2]=n[Ye>>2],n[Ze+4>>2]=Qe,Ze=49;break}if(!ct){m=0;break e}wZ($t,ae,u)}while(!1);if((Ze|0)==49&&(Ze=0,!ct)){B=0,l=tt;continue}T=s[T>>0]|0,T=(Le|0)!=0&(T&15|0)==3?T&-33:T,Ye=M&-65537,Qe=M&8192|0?Ye:M;t:do switch(T|0){case 110:switch((Le&255)<<24>>24){case 0:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 1:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 2:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}case 3:{a[n[$t>>2]>>1]=m,B=0,l=tt;continue e}case 4:{s[n[$t>>2]>>0]=m,B=0,l=tt;continue e}case 6:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 7:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}default:{B=0,l=tt;continue e}}case 112:{T=120,k=k>>>0>8?k:8,l=Qe|8,Ze=61;break}case 88:case 120:{l=Qe,Ze=61;break}case 111:{T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,ae=A6e(l,T,He)|0,Ye=We-ae|0,M=0,q=5642,k=(Qe&8|0)==0|(k|0)>(Ye|0)?k:Ye+1|0,Ye=Qe,Ze=67;break}case 105:case 100:if(T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,(T|0)<0){l=GP(0,0,l|0,T|0)|0,T=ye,M=$t,n[M>>2]=l,n[M+4>>2]=T,M=1,q=5642,Ze=66;break t}else{M=(Qe&2049|0)!=0&1,q=Qe&2048|0?5643:Qe&1|0?5644:5642,Ze=66;break t}case 117:{T=$t,M=0,q=5642,l=n[T>>2]|0,T=n[T+4>>2]|0,Ze=66;break}case 99:{s[Lt>>0]=n[$t>>2],l=Lt,M=0,q=5642,ae=He,T=1,k=Ye;break}case 109:{T=p6e(n[(Xy()|0)>>2]|0)|0,Ze=71;break}case 115:{T=n[$t>>2]|0,T=T|0?T:5652,Ze=71;break}case 67:{n[Tr>>2]=n[$t>>2],n[Gr>>2]=0,n[$t>>2]=Tr,ae=-1,T=Tr,Ze=75;break}case 83:{l=n[$t>>2]|0,k?(ae=k,T=l,Ze=75):(Ls(o,32,B,0,Qe),l=0,Ze=84);break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{B=g6e(o,+E[$t>>3],B,k,Qe,T)|0,l=tt;continue e}default:M=0,q=5642,ae=He,T=k,k=Qe}while(!1);t:do if((Ze|0)==61)Qe=$t,Le=n[Qe>>2]|0,Qe=n[Qe+4>>2]|0,ae=f6e(Le,Qe,He,T&32)|0,q=(l&8|0)==0|(Le|0)==0&(Qe|0)==0,M=q?0:2,q=q?5642:5642+(T>>4)|0,Ye=l,l=Le,T=Qe,Ze=67;else if((Ze|0)==66)ae=Zy(l,T,He)|0,Ye=Qe,Ze=67;else if((Ze|0)==71)Ze=0,Qe=h6e(T,0,k)|0,Le=(Qe|0)==0,l=T,M=0,q=5642,ae=Le?T+k|0:Qe,T=Le?k:Qe-T|0,k=Ye;else if((Ze|0)==75){for(Ze=0,q=T,l=0,k=0;M=n[q>>2]|0,!(!M||(k=BZ(Hr,M)|0,(k|0)<0|k>>>0>(ae-l|0)>>>0));)if(l=k+l|0,ae>>>0>l>>>0)q=q+4|0;else break;if((k|0)<0){m=-1;break e}if(Ls(o,32,B,l,Qe),!l)l=0,Ze=84;else for(M=0;;){if(k=n[T>>2]|0,!k){Ze=84;break t}if(k=BZ(Hr,k)|0,M=k+M|0,(M|0)>(l|0)){Ze=84;break t}if(vs(o,Hr,k),M>>>0>=l>>>0){Ze=84;break}else T=T+4|0}}while(!1);if((Ze|0)==67)Ze=0,T=(l|0)!=0|(T|0)!=0,Qe=(k|0)!=0|T,T=((T^1)&1)+(We-ae)|0,l=Qe?ae:He,ae=He,T=Qe?(k|0)>(T|0)?k:T:k,k=(k|0)>-1?Ye&-65537:Ye;else if((Ze|0)==84){Ze=0,Ls(o,32,B,l,Qe^8192),B=(B|0)>(l|0)?B:l,l=tt;continue}Le=ae-l|0,Ye=(T|0)<(Le|0)?Le:T,Qe=Ye+M|0,B=(B|0)<(Qe|0)?Qe:B,Ls(o,32,B,Qe,k),vs(o,q,M),Ls(o,48,B,Qe,k^65536),Ls(o,48,Ye,Le,0),vs(o,l,Le),Ls(o,32,B,Qe,k^8192),l=tt}e:do if((Ze|0)==87&&!o)if(!L)m=0;else{for(m=1;l=n[d+(m<<2)>>2]|0,!!l;)if(wZ(A+(m<<3)|0,l,u),m=m+1|0,(m|0)>=10){m=1;break e}for(;;){if(n[d+(m<<2)>>2]|0){m=-1;break e}if(m=m+1|0,(m|0)>=10){m=1;break}}}while(!1);return I=cr,m|0}function c6e(o){return o=o|0,0}function u6e(o){o=o|0}function vs(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]&32||v6e(l,u,o)|0}function CZ(o){o=o|0;var l=0,u=0,A=0;if(u=n[o>>2]|0,A=(s[u>>0]|0)+-48|0,A>>>0<10){l=0;do l=A+(l*10|0)|0,u=u+1|0,n[o>>2]=u,A=(s[u>>0]|0)+-48|0;while(A>>>0<10)}else l=0;return l|0}function wZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;e:do if(l>>>0<=20)do switch(l|0){case 9:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,n[o>>2]=l;break e}case 10:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=((l|0)<0)<<31>>31;break e}case 11:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=0;break e}case 12:{A=(n[u>>2]|0)+7&-8,l=A,d=n[l>>2]|0,l=n[l+4>>2]|0,n[u>>2]=A+8,A=o,n[A>>2]=d,n[A+4>>2]=l;break e}case 13:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&65535)<<16>>16,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 14:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&65535,n[d+4>>2]=0;break e}case 15:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&255)<<24>>24,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 16:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&255,n[d+4>>2]=0;break e}case 17:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}case 18:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}default:break e}while(!1);while(!1)}function f6e(o,l,u,A){if(o=o|0,l=l|0,u=u|0,A=A|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=c[5694+(o&15)>>0]|0|A,o=qP(o|0,l|0,4)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function A6e(o,l,u){if(o=o|0,l=l|0,u=u|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=o&7|48,o=qP(o|0,l|0,3)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function Zy(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if(l>>>0>0|(l|0)==0&o>>>0>4294967295){for(;A=pU(o|0,l|0,10,0)|0,u=u+-1|0,s[u>>0]=A&255|48,A=o,o=AU(o|0,l|0,10,0)|0,l>>>0>9|(l|0)==9&A>>>0>4294967295;)l=ye;l=o}else l=o;if(l)for(;u=u+-1|0,s[u>>0]=(l>>>0)%10|0|48,!(l>>>0<10);)l=(l>>>0)/10|0;return u|0}function p6e(o){return o=o|0,I6e(o,n[(E6e()|0)+188>>2]|0)|0}function h6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;m=l&255,A=(u|0)!=0;e:do if(A&(o&3|0)!=0)for(d=l&255;;){if((s[o>>0]|0)==d<<24>>24){B=6;break e}if(o=o+1|0,u=u+-1|0,A=(u|0)!=0,!(A&(o&3|0)!=0)){B=5;break}}else B=5;while(!1);(B|0)==5&&(A?B=6:u=0);e:do if((B|0)==6&&(d=l&255,(s[o>>0]|0)!=d<<24>>24)){A=Ue(m,16843009)|0;t:do if(u>>>0>3){for(;m=n[o>>2]^A,!((m&-2139062144^-2139062144)&m+-16843009|0);)if(o=o+4|0,u=u+-4|0,u>>>0<=3){B=11;break t}}else B=11;while(!1);if((B|0)==11&&!u){u=0;break}for(;;){if((s[o>>0]|0)==d<<24>>24)break e;if(o=o+1|0,u=u+-1|0,!u){u=0;break}}}while(!1);return(u|0?o:0)|0}function Ls(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0;if(B=I,I=I+256|0,m=B,(u|0)>(A|0)&(d&73728|0)==0){if(d=u-A|0,eE(m|0,l|0,(d>>>0<256?d:256)|0)|0,d>>>0>255){l=u-A|0;do vs(o,m,256),d=d+-256|0;while(d>>>0>255);d=l&255}vs(o,m,d)}I=B}function BZ(o,l){return o=o|0,l=l|0,o?o=m6e(o,l,0)|0:o=0,o|0}function g6e(o,l,u,A,d,m){o=o|0,l=+l,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0;Hn=I,I=I+560|0,T=Hn+8|0,tt=Hn,cr=Hn+524|0,Hr=cr,M=Hn+512|0,n[tt>>2]=0,Tr=M+12|0,vZ(l)|0,(ye|0)<0?(l=-l,fr=1,Gr=5659):(fr=(d&2049|0)!=0&1,Gr=d&2048|0?5662:d&1|0?5665:5660),vZ(l)|0,$t=ye&2146435072;do if($t>>>0<2146435072|($t|0)==2146435072&!1){if(Ye=+d6e(l,tt)*2,B=Ye!=0,B&&(n[tt>>2]=(n[tt>>2]|0)+-1),ct=m|32,(ct|0)==97){Le=m&32,ae=Le|0?Gr+9|0:Gr,q=fr|2,B=12-A|0;do if(A>>>0>11|(B|0)==0)l=Ye;else{l=8;do B=B+-1|0,l=l*16;while(B|0);if((s[ae>>0]|0)==45){l=-(l+(-Ye-l));break}else{l=Ye+l-l;break}}while(!1);k=n[tt>>2]|0,B=(k|0)<0?0-k|0:k,B=Zy(B,((B|0)<0)<<31>>31,Tr)|0,(B|0)==(Tr|0)&&(B=M+11|0,s[B>>0]=48),s[B+-1>>0]=(k>>31&2)+43,L=B+-2|0,s[L>>0]=m+15,M=(A|0)<1,T=(d&8|0)==0,B=cr;do $t=~~l,k=B+1|0,s[B>>0]=c[5694+$t>>0]|Le,l=(l-+($t|0))*16,(k-Hr|0)==1&&!(T&(M&l==0))?(s[k>>0]=46,B=B+2|0):B=k;while(l!=0);$t=B-Hr|0,Hr=Tr-L|0,Tr=(A|0)!=0&($t+-2|0)<(A|0)?A+2|0:$t,B=Hr+q+Tr|0,Ls(o,32,u,B,d),vs(o,ae,q),Ls(o,48,u,B,d^65536),vs(o,cr,$t),Ls(o,48,Tr-$t|0,0,0),vs(o,L,Hr),Ls(o,32,u,B,d^8192);break}k=(A|0)<0?6:A,B?(B=(n[tt>>2]|0)+-28|0,n[tt>>2]=B,l=Ye*268435456):(l=Ye,B=n[tt>>2]|0),$t=(B|0)<0?T:T+288|0,T=$t;do We=~~l>>>0,n[T>>2]=We,T=T+4|0,l=(l-+(We>>>0))*1e9;while(l!=0);if((B|0)>0)for(M=$t,q=T;;){if(L=(B|0)<29?B:29,B=q+-4|0,B>>>0>=M>>>0){T=0;do He=kZ(n[B>>2]|0,0,L|0)|0,He=fU(He|0,ye|0,T|0,0)|0,We=ye,Ze=pU(He|0,We|0,1e9,0)|0,n[B>>2]=Ze,T=AU(He|0,We|0,1e9,0)|0,B=B+-4|0;while(B>>>0>=M>>>0);T&&(M=M+-4|0,n[M>>2]=T)}for(T=q;!(T>>>0<=M>>>0);)if(B=T+-4|0,!(n[B>>2]|0))T=B;else break;if(B=(n[tt>>2]|0)-L|0,n[tt>>2]=B,(B|0)>0)q=T;else break}else M=$t;if((B|0)<0){A=((k+25|0)/9|0)+1|0,Qe=(ct|0)==102;do{if(Le=0-B|0,Le=(Le|0)<9?Le:9,M>>>0>>0){L=(1<>>Le,ae=0,B=M;do We=n[B>>2]|0,n[B>>2]=(We>>>Le)+ae,ae=Ue(We&L,q)|0,B=B+4|0;while(B>>>0>>0);B=n[M>>2]|0?M:M+4|0,ae?(n[T>>2]=ae,M=B,B=T+4|0):(M=B,B=T)}else M=n[M>>2]|0?M:M+4|0,B=T;T=Qe?$t:M,T=(B-T>>2|0)>(A|0)?T+(A<<2)|0:B,B=(n[tt>>2]|0)+Le|0,n[tt>>2]=B}while((B|0)<0);B=M,A=T}else B=M,A=T;if(We=$t,B>>>0>>0){if(T=(We-B>>2)*9|0,L=n[B>>2]|0,L>>>0>=10){M=10;do M=M*10|0,T=T+1|0;while(L>>>0>=M>>>0)}}else T=0;if(Qe=(ct|0)==103,Ze=(k|0)!=0,M=k-((ct|0)!=102?T:0)+((Ze&Qe)<<31>>31)|0,(M|0)<(((A-We>>2)*9|0)+-9|0)){if(M=M+9216|0,Le=$t+4+(((M|0)/9|0)+-1024<<2)|0,M=((M|0)%9|0)+1|0,(M|0)<9){L=10;do L=L*10|0,M=M+1|0;while((M|0)!=9)}else L=10;if(q=n[Le>>2]|0,ae=(q>>>0)%(L>>>0)|0,M=(Le+4|0)==(A|0),M&(ae|0)==0)M=Le;else if(Ye=((q>>>0)/(L>>>0)|0)&1|0?9007199254740994:9007199254740992,He=(L|0)/2|0,l=ae>>>0>>0?.5:M&(ae|0)==(He|0)?1:1.5,fr&&(He=(s[Gr>>0]|0)==45,l=He?-l:l,Ye=He?-Ye:Ye),M=q-ae|0,n[Le>>2]=M,Ye+l!=Ye){if(He=M+L|0,n[Le>>2]=He,He>>>0>999999999)for(T=Le;M=T+-4|0,n[T>>2]=0,M>>>0>>0&&(B=B+-4|0,n[B>>2]=0),He=(n[M>>2]|0)+1|0,n[M>>2]=He,He>>>0>999999999;)T=M;else M=Le;if(T=(We-B>>2)*9|0,q=n[B>>2]|0,q>>>0>=10){L=10;do L=L*10|0,T=T+1|0;while(q>>>0>=L>>>0)}}else M=Le;M=M+4|0,M=A>>>0>M>>>0?M:A,He=B}else M=A,He=B;for(ct=M;;){if(ct>>>0<=He>>>0){tt=0;break}if(B=ct+-4|0,!(n[B>>2]|0))ct=B;else{tt=1;break}}A=0-T|0;do if(Qe)if(B=((Ze^1)&1)+k|0,(B|0)>(T|0)&(T|0)>-5?(L=m+-1|0,k=B+-1-T|0):(L=m+-2|0,k=B+-1|0),B=d&8,B)Le=B;else{if(tt&&(Lt=n[ct+-4>>2]|0,(Lt|0)!=0))if((Lt>>>0)%10|0)M=0;else{M=0,B=10;do B=B*10|0,M=M+1|0;while(!((Lt>>>0)%(B>>>0)|0|0))}else M=9;if(B=((ct-We>>2)*9|0)+-9|0,(L|32|0)==102){Le=B-M|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}else{Le=B+T-M|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}}else L=m,Le=d&8;while(!1);if(Qe=k|Le,q=(Qe|0)!=0&1,ae=(L|32|0)==102,ae)Ze=0,B=(T|0)>0?T:0;else{if(B=(T|0)<0?A:T,B=Zy(B,((B|0)<0)<<31>>31,Tr)|0,M=Tr,(M-B|0)<2)do B=B+-1|0,s[B>>0]=48;while((M-B|0)<2);s[B+-1>>0]=(T>>31&2)+43,B=B+-2|0,s[B>>0]=L,Ze=B,B=M-B|0}if(B=fr+1+k+q+B|0,Ls(o,32,u,B,d),vs(o,Gr,fr),Ls(o,48,u,B,d^65536),ae){L=He>>>0>$t>>>0?$t:He,Le=cr+9|0,q=Le,ae=cr+8|0,M=L;do{if(T=Zy(n[M>>2]|0,0,Le)|0,(M|0)==(L|0))(T|0)==(Le|0)&&(s[ae>>0]=48,T=ae);else if(T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}vs(o,T,q-T|0),M=M+4|0}while(M>>>0<=$t>>>0);if(Qe|0&&vs(o,5710,1),M>>>0>>0&(k|0)>0)for(;;){if(T=Zy(n[M>>2]|0,0,Le)|0,T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}if(vs(o,T,(k|0)<9?k:9),M=M+4|0,T=k+-9|0,M>>>0>>0&(k|0)>9)k=T;else{k=T;break}}Ls(o,48,k+9|0,9,0)}else{if(Qe=tt?ct:He+4|0,(k|0)>-1){tt=cr+9|0,Le=(Le|0)==0,A=tt,q=0-Hr|0,ae=cr+8|0,L=He;do{T=Zy(n[L>>2]|0,0,tt)|0,(T|0)==(tt|0)&&(s[ae>>0]=48,T=ae);do if((L|0)==(He|0)){if(M=T+1|0,vs(o,T,1),Le&(k|0)<1){T=M;break}vs(o,5710,1),T=M}else{if(T>>>0<=cr>>>0)break;eE(cr|0,48,T+q|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}while(!1);Hr=A-T|0,vs(o,T,(k|0)>(Hr|0)?Hr:k),k=k-Hr|0,L=L+4|0}while(L>>>0>>0&(k|0)>-1)}Ls(o,48,k+18|0,18,0),vs(o,Ze,Tr-Ze|0)}Ls(o,32,u,B,d^8192)}else cr=(m&32|0)!=0,B=fr+3|0,Ls(o,32,u,B,d&-65537),vs(o,Gr,fr),vs(o,l!=l|!1?cr?5686:5690:cr?5678:5682,3),Ls(o,32,u,B,d^8192);while(!1);return I=Hn,((B|0)<(u|0)?u:B)|0}function vZ(o){o=+o;var l=0;return E[S>>3]=o,l=n[S>>2]|0,ye=n[S+4>>2]|0,l|0}function d6e(o,l){return o=+o,l=l|0,+ +SZ(o,l)}function SZ(o,l){o=+o,l=l|0;var u=0,A=0,d=0;switch(E[S>>3]=o,u=n[S>>2]|0,A=n[S+4>>2]|0,d=qP(u|0,A|0,52)|0,d&2047){case 0:{o!=0?(o=+SZ(o*18446744073709552e3,l),u=(n[l>>2]|0)+-64|0):u=0,n[l>>2]=u;break}case 2047:break;default:n[l>>2]=(d&2047)+-1022,n[S>>2]=u,n[S+4>>2]=A&-2146435073|1071644672,o=+E[S>>3]}return+o}function m6e(o,l,u){o=o|0,l=l|0,u=u|0;do if(o){if(l>>>0<128){s[o>>0]=l,o=1;break}if(!(n[n[(y6e()|0)+188>>2]>>2]|0))if((l&-128|0)==57216){s[o>>0]=l,o=1;break}else{n[(Xy()|0)>>2]=84,o=-1;break}if(l>>>0<2048){s[o>>0]=l>>>6|192,s[o+1>>0]=l&63|128,o=2;break}if(l>>>0<55296|(l&-8192|0)==57344){s[o>>0]=l>>>12|224,s[o+1>>0]=l>>>6&63|128,s[o+2>>0]=l&63|128,o=3;break}if((l+-65536|0)>>>0<1048576){s[o>>0]=l>>>18|240,s[o+1>>0]=l>>>12&63|128,s[o+2>>0]=l>>>6&63|128,s[o+3>>0]=l&63|128,o=4;break}else{n[(Xy()|0)>>2]=84,o=-1;break}}else o=1;while(!1);return o|0}function y6e(){return lU()|0}function E6e(){return lU()|0}function I6e(o,l){o=o|0,l=l|0;var u=0,A=0;for(A=0;;){if((c[5712+A>>0]|0)==(o|0)){o=2;break}if(u=A+1|0,(u|0)==87){u=5800,A=87,o=5;break}else A=u}if((o|0)==2&&(A?(u=5800,o=5):u=5800),(o|0)==5)for(;;){do o=u,u=u+1|0;while(s[o>>0]|0);if(A=A+-1|0,A)o=5;else break}return C6e(u,n[l+20>>2]|0)|0}function C6e(o,l){return o=o|0,l=l|0,w6e(o,l)|0}function w6e(o,l){return o=o|0,l=l|0,l?l=B6e(n[l>>2]|0,n[l+4>>2]|0,o)|0:l=0,(l|0?l:o)|0}function B6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;ae=(n[o>>2]|0)+1794895138|0,m=Ad(n[o+8>>2]|0,ae)|0,A=Ad(n[o+12>>2]|0,ae)|0,d=Ad(n[o+16>>2]|0,ae)|0;e:do if(m>>>0>>2>>>0&&(q=l-(m<<2)|0,A>>>0>>0&d>>>0>>0)&&!((d|A)&3|0)){for(q=A>>>2,L=d>>>2,M=0;;){if(k=m>>>1,T=M+k|0,B=T<<1,d=B+q|0,A=Ad(n[o+(d<<2)>>2]|0,ae)|0,d=Ad(n[o+(d+1<<2)>>2]|0,ae)|0,!(d>>>0>>0&A>>>0<(l-d|0)>>>0)){A=0;break e}if(s[o+(d+A)>>0]|0){A=0;break e}if(A=EZ(u,o+d|0)|0,!A)break;if(A=(A|0)<0,(m|0)==1){A=0;break e}else M=A?M:T,m=A?k:m-k|0}A=B+L|0,d=Ad(n[o+(A<<2)>>2]|0,ae)|0,A=Ad(n[o+(A+1<<2)>>2]|0,ae)|0,A>>>0>>0&d>>>0<(l-A|0)>>>0?A=s[o+(A+d)>>0]|0?0:o+A|0:A=0}else A=0;while(!1);return A|0}function Ad(o,l){o=o|0,l=l|0;var u=0;return u=RZ(o|0)|0,(l|0?u:o)|0}function v6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=u+16|0,d=n[A>>2]|0,d?m=5:S6e(u)|0?A=0:(d=n[A>>2]|0,m=5);e:do if((m|0)==5){if(k=u+20|0,B=n[k>>2]|0,A=B,(d-B|0)>>>0>>0){A=YP[n[u+36>>2]&7](u,o,l)|0;break}t:do if((s[u+75>>0]|0)>-1){for(B=l;;){if(!B){m=0,d=o;break t}if(d=B+-1|0,(s[o+d>>0]|0)==10)break;B=d}if(A=YP[n[u+36>>2]&7](u,o,B)|0,A>>>0>>0)break e;m=B,d=o+B|0,l=l-B|0,A=n[k>>2]|0}else m=0,d=o;while(!1);Qr(A|0,d|0,l|0)|0,n[k>>2]=(n[k>>2]|0)+l,A=m+l|0}while(!1);return A|0}function S6e(o){o=o|0;var l=0,u=0;return l=o+74|0,u=s[l>>0]|0,s[l>>0]=u+255|u,l=n[o>>2]|0,l&8?(n[o>>2]=l|32,o=-1):(n[o+8>>2]=0,n[o+4>>2]=0,u=n[o+44>>2]|0,n[o+28>>2]=u,n[o+20>>2]=u,n[o+16>>2]=u+(n[o+48>>2]|0),o=0),o|0}function $n(o,l){o=y(o),l=y(l);var u=0,A=0;u=DZ(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=DZ(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?l:o;break}else{o=o>2]=o,n[S>>2]|0|0}function pd(o,l){o=y(o),l=y(l);var u=0,A=0;u=bZ(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=bZ(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?o:l;break}else{o=o>2]=o,n[S>>2]|0|0}function uU(o,l){o=y(o),l=y(l);var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;m=(h[S>>2]=o,n[S>>2]|0),k=(h[S>>2]=l,n[S>>2]|0),u=m>>>23&255,B=k>>>23&255,T=m&-2147483648,d=k<<1;e:do if(d|0&&!((u|0)==255|((D6e(l)|0)&2147483647)>>>0>2139095040)){if(A=m<<1,A>>>0<=d>>>0)return l=y(o*y(0)),y((A|0)==(d|0)?l:o);if(u)A=m&8388607|8388608;else{if(u=m<<9,(u|0)>-1){A=u,u=0;do u=u+-1|0,A=A<<1;while((A|0)>-1)}else u=0;A=m<<1-u}if(B)k=k&8388607|8388608;else{if(m=k<<9,(m|0)>-1){d=0;do d=d+-1|0,m=m<<1;while((m|0)>-1)}else d=0;B=d,k=k<<1-d}d=A-k|0,m=(d|0)>-1;t:do if((u|0)>(B|0)){for(;;){if(m)if(d)A=d;else break;if(A=A<<1,u=u+-1|0,d=A-k|0,m=(d|0)>-1,(u|0)<=(B|0))break t}l=y(o*y(0));break e}while(!1);if(m)if(d)A=d;else{l=y(o*y(0));break}if(A>>>0<8388608)do A=A<<1,u=u+-1|0;while(A>>>0<8388608);(u|0)>0?u=A+-8388608|u<<23:u=A>>>(1-u|0),l=(n[S>>2]=u|T,y(h[S>>2]))}else M=3;while(!1);return(M|0)==3&&(l=y(o*l),l=y(l/l)),y(l)}function D6e(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function b6e(o,l){return o=o|0,l=l|0,IZ(n[582]|0,o,l)|0}function an(o){o=o|0,Nt()}function $y(o){o=o|0}function P6e(o,l){return o=o|0,l=l|0,0}function x6e(o){return o=o|0,(PZ(o+4|0)|0)==-1?(ip[n[(n[o>>2]|0)+8>>2]&127](o),o=1):o=0,o|0}function PZ(o){o=o|0;var l=0;return l=n[o>>2]|0,n[o>>2]=l+-1,l+-1|0}function Gh(o){o=o|0,x6e(o)|0&&k6e(o)}function k6e(o){o=o|0;var l=0;l=o+8|0,n[l>>2]|0&&(PZ(l)|0)!=-1||ip[n[(n[o>>2]|0)+16>>2]&127](o)}function Kt(o){o=o|0;var l=0;for(l=o|0?o:1;o=_P(l)|0,!(o|0);){if(o=T6e()|0,!o){o=0;break}GZ[o&0]()}return o|0}function xZ(o){return o=o|0,Kt(o)|0}function It(o){o=o|0,HP(o)}function Q6e(o){o=o|0,(s[o+11>>0]|0)<0&&It(n[o>>2]|0)}function T6e(){var o=0;return o=n[2923]|0,n[2923]=o+0,o|0}function R6e(){}function GP(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,A=l-A-(u>>>0>o>>>0|0)>>>0,ye=A,o-u>>>0|0|0}function fU(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,u=o+u>>>0,ye=l+A+(u>>>0>>0|0)>>>0,u|0|0}function eE(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(m=o+u|0,l=l&255,(u|0)>=67){for(;o&3;)s[o>>0]=l,o=o+1|0;for(A=m&-4|0,d=A-64|0,B=l|l<<8|l<<16|l<<24;(o|0)<=(d|0);)n[o>>2]=B,n[o+4>>2]=B,n[o+8>>2]=B,n[o+12>>2]=B,n[o+16>>2]=B,n[o+20>>2]=B,n[o+24>>2]=B,n[o+28>>2]=B,n[o+32>>2]=B,n[o+36>>2]=B,n[o+40>>2]=B,n[o+44>>2]=B,n[o+48>>2]=B,n[o+52>>2]=B,n[o+56>>2]=B,n[o+60>>2]=B,o=o+64|0;for(;(o|0)<(A|0);)n[o>>2]=B,o=o+4|0}for(;(o|0)<(m|0);)s[o>>0]=l,o=o+1|0;return m-u|0}function kZ(o,l,u){return o=o|0,l=l|0,u=u|0,(u|0)<32?(ye=l<>>32-u,o<>>u,o>>>u|(l&(1<>>u-32|0)}function Qr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;if((u|0)>=8192)return OA(o|0,l|0,u|0)|0;if(m=o|0,d=o+u|0,(o&3)==(l&3)){for(;o&3;){if(!u)return m|0;s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0,u=u-1|0}for(u=d&-4|0,A=u-64|0;(o|0)<=(A|0);)n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2],n[o+16>>2]=n[l+16>>2],n[o+20>>2]=n[l+20>>2],n[o+24>>2]=n[l+24>>2],n[o+28>>2]=n[l+28>>2],n[o+32>>2]=n[l+32>>2],n[o+36>>2]=n[l+36>>2],n[o+40>>2]=n[l+40>>2],n[o+44>>2]=n[l+44>>2],n[o+48>>2]=n[l+48>>2],n[o+52>>2]=n[l+52>>2],n[o+56>>2]=n[l+56>>2],n[o+60>>2]=n[l+60>>2],o=o+64|0,l=l+64|0;for(;(o|0)<(u|0);)n[o>>2]=n[l>>2],o=o+4|0,l=l+4|0}else for(u=d-4|0;(o|0)<(u|0);)s[o>>0]=s[l>>0]|0,s[o+1>>0]=s[l+1>>0]|0,s[o+2>>0]=s[l+2>>0]|0,s[o+3>>0]=s[l+3>>0]|0,o=o+4|0,l=l+4|0;for(;(o|0)<(d|0);)s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0;return m|0}function QZ(o){o=o|0;var l=0;return l=s[N+(o&255)>>0]|0,(l|0)<8?l|0:(l=s[N+(o>>8&255)>>0]|0,(l|0)<8?l+8|0:(l=s[N+(o>>16&255)>>0]|0,(l|0)<8?l+16|0:(s[N+(o>>>24)>>0]|0)+24|0))}function TZ(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0;if(L=o,T=l,M=T,B=u,ae=A,k=ae,!M)return m=(d|0)!=0,k?m?(n[d>>2]=o|0,n[d+4>>2]=l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0):(m&&(n[d>>2]=(L>>>0)%(B>>>0),n[d+4>>2]=0),ae=0,d=(L>>>0)/(B>>>0)>>>0,ye=ae,d|0);m=(k|0)==0;do if(B){if(!m){if(m=(b(k|0)|0)-(b(M|0)|0)|0,m>>>0<=31){q=m+1|0,k=31-m|0,l=m-31>>31,B=q,o=L>>>(q>>>0)&l|M<>>(q>>>0)&l,m=0,k=L<>2]=o|0,n[d+4>>2]=T|l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0)}if(m=B-1|0,m&B|0){k=(b(B|0)|0)+33-(b(M|0)|0)|0,Le=64-k|0,q=32-k|0,T=q>>31,Ye=k-32|0,l=Ye>>31,B=k,o=q-1>>31&M>>>(Ye>>>0)|(M<>>(k>>>0))&l,l=l&M>>>(k>>>0),m=L<>>(Ye>>>0))&T|L<>31;break}return d|0&&(n[d>>2]=m&L,n[d+4>>2]=0),(B|0)==1?(Ye=T|l&0,Le=o|0|0,ye=Ye,Le|0):(Le=QZ(B|0)|0,Ye=M>>>(Le>>>0)|0,Le=M<<32-Le|L>>>(Le>>>0)|0,ye=Ye,Le|0)}else{if(m)return d|0&&(n[d>>2]=(M>>>0)%(B>>>0),n[d+4>>2]=0),Ye=0,Le=(M>>>0)/(B>>>0)>>>0,ye=Ye,Le|0;if(!L)return d|0&&(n[d>>2]=0,n[d+4>>2]=(M>>>0)%(k>>>0)),Ye=0,Le=(M>>>0)/(k>>>0)>>>0,ye=Ye,Le|0;if(m=k-1|0,!(m&k))return d|0&&(n[d>>2]=o|0,n[d+4>>2]=m&M|l&0),Ye=0,Le=M>>>((QZ(k|0)|0)>>>0),ye=Ye,Le|0;if(m=(b(k|0)|0)-(b(M|0)|0)|0,m>>>0<=30){l=m+1|0,k=31-m|0,B=l,o=M<>>(l>>>0),l=M>>>(l>>>0),m=0,k=L<>2]=o|0,n[d+4>>2]=T|l&0,Ye=0,Le=0,ye=Ye,Le|0):(Ye=0,Le=0,ye=Ye,Le|0)}while(!1);if(!B)M=k,T=0,k=0;else{q=u|0|0,L=ae|A&0,M=fU(q|0,L|0,-1,-1)|0,u=ye,T=k,k=0;do A=T,T=m>>>31|T<<1,m=k|m<<1,A=o<<1|A>>>31|0,ae=o>>>31|l<<1|0,GP(M|0,u|0,A|0,ae|0)|0,Le=ye,Ye=Le>>31|((Le|0)<0?-1:0)<<1,k=Ye&1,o=GP(A|0,ae|0,Ye&q|0,(((Le|0)<0?-1:0)>>31|((Le|0)<0?-1:0)<<1)&L|0)|0,l=ye,B=B-1|0;while(B|0);M=T,T=0}return B=0,d|0&&(n[d>>2]=o,n[d+4>>2]=l),Ye=(m|0)>>>31|(M|B)<<1|(B<<1|m>>>31)&0|T,Le=(m<<1|0)&-2|k,ye=Ye,Le|0}function AU(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,TZ(o,l,u,A,0)|0}function qh(o){o=o|0;var l=0,u=0;return u=o+15&-16|0,l=n[C>>2]|0,o=l+u|0,(u|0)>0&(o|0)<(l|0)|(o|0)<0?(oe()|0,fu(12),-1):(n[C>>2]=o,(o|0)>($()|0)&&!(X()|0)?(n[C>>2]=l,fu(12),-1):l|0)}function Q2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if((l|0)<(o|0)&(o|0)<(l+u|0)){for(A=o,l=l+u|0,o=o+u|0;(u|0)>0;)o=o-1|0,l=l-1|0,u=u-1|0,s[o>>0]=s[l>>0]|0;o=A}else Qr(o,l,u)|0;return o|0}function pU(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;return m=I,I=I+16|0,d=m|0,TZ(o,l,u,A,d)|0,I=m,ye=n[d+4>>2]|0,n[d>>2]|0|0}function RZ(o){return o=o|0,(o&255)<<24|(o>>8&255)<<16|(o>>16&255)<<8|o>>>24|0}function F6e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,FZ[o&1](l|0,u|0,A|0,d|0,m|0)}function N6e(o,l,u){o=o|0,l=l|0,u=y(u),NZ[o&1](l|0,y(u))}function O6e(o,l,u){o=o|0,l=l|0,u=+u,OZ[o&31](l|0,+u)}function L6e(o,l,u,A){return o=o|0,l=l|0,u=y(u),A=y(A),y(LZ[o&0](l|0,y(u),y(A)))}function M6e(o,l){o=o|0,l=l|0,ip[o&127](l|0)}function U6e(o,l,u){o=o|0,l=l|0,u=u|0,sp[o&31](l|0,u|0)}function _6e(o,l){return o=o|0,l=l|0,gd[o&31](l|0)|0}function H6e(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,MZ[o&1](l|0,+u,+A,d|0)}function j6e(o,l,u,A){o=o|0,l=l|0,u=+u,A=+A,wGe[o&1](l|0,+u,+A)}function G6e(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,YP[o&7](l|0,u|0,A|0)|0}function q6e(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,+BGe[o&1](l|0,u|0,A|0)}function W6e(o,l){return o=o|0,l=l|0,+UZ[o&15](l|0)}function Y6e(o,l,u){return o=o|0,l=l|0,u=+u,vGe[o&1](l|0,+u)|0}function V6e(o,l,u){return o=o|0,l=l|0,u=u|0,gU[o&15](l|0,u|0)|0}function J6e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=+A,d=+d,m=m|0,SGe[o&1](l|0,u|0,+A,+d,m|0)}function K6e(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,DGe[o&1](l|0,u|0,A|0,d|0,m|0,B|0)}function z6e(o,l,u){return o=o|0,l=l|0,u=u|0,+_Z[o&7](l|0,u|0)}function X6e(o){return o=o|0,VP[o&7]()|0}function Z6e(o,l,u,A,d,m){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,HZ[o&1](l|0,u|0,A|0,d|0,m|0)|0}function $6e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=+d,bGe[o&1](l|0,u|0,A|0,+d)}function eGe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,jZ[o&1](l|0,u|0,y(A),d|0,y(m),B|0)}function tGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F2[o&15](l|0,u|0,A|0)}function rGe(o){o=o|0,GZ[o&0]()}function nGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,qZ[o&15](l|0,u|0,+A)}function iGe(o,l,u){return o=o|0,l=+l,u=+u,PGe[o&1](+l,+u)|0}function sGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,dU[o&15](l|0,u|0,A|0,d|0)}function oGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(0)}function aGe(o,l){o=o|0,l=y(l),F(1)}function Xa(o,l){o=o|0,l=+l,F(2)}function lGe(o,l,u){return o=o|0,l=y(l),u=y(u),F(3),$e}function wr(o){o=o|0,F(4)}function T2(o,l){o=o|0,l=l|0,F(5)}function Ol(o){return o=o|0,F(6),0}function cGe(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,F(7)}function uGe(o,l,u){o=o|0,l=+l,u=+u,F(8)}function fGe(o,l,u){return o=o|0,l=l|0,u=u|0,F(9),0}function AGe(o,l,u){return o=o|0,l=l|0,u=u|0,F(10),0}function hd(o){return o=o|0,F(11),0}function pGe(o,l){return o=o|0,l=+l,F(12),0}function R2(o,l){return o=o|0,l=l|0,F(13),0}function hGe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,F(14)}function gGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,F(15)}function hU(o,l){return o=o|0,l=l|0,F(16),0}function dGe(){return F(17),0}function mGe(o,l,u,A,d){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(18),0}function yGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,F(19)}function EGe(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0,F(20)}function WP(o,l,u){o=o|0,l=l|0,u=u|0,F(21)}function IGe(){F(22)}function tE(o,l,u){o=o|0,l=l|0,u=+u,F(23)}function CGe(o,l){return o=+o,l=+l,F(24),0}function rE(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F(25)}var FZ=[oGe,m3e],NZ=[aGe,Ty],OZ=[Xa,Zg,Fh,h2,g2,d2,m2,bf,_y,y2,Pf,$g,ed,E2,I2,wu,td,C2,Hy,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa],LZ=[lGe],ip=[wr,$y,Xke,Zke,$ke,PFe,xFe,kFe,Y_e,V_e,J_e,i3e,s3e,o3e,Dje,bje,Pje,Bl,Xg,u2,sr,hc,xP,kP,Hke,aQe,EQe,LQe,$Qe,dTe,RTe,JTe,cRe,SRe,HRe,nFe,EFe,VFe,cNe,SNe,HNe,nOe,EOe,MOe,$Oe,pLe,xLe,dP,oMe,wMe,HMe,sUe,IUe,HUe,XUe,e_e,m_e,I_e,L_e,z_e,$_e,d4e,F4e,Iz,g8e,Y8e,aHe,wHe,qHe,sje,dje,Eje,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr],sp=[T2,Ly,JL,f2,A2,xr,so,Xi,Ns,ws,Uy,Rh,B2,CP,id,XL,ZL,wP,BP,tM,xf,ne,jOe,rLe,cUe,y8e,j4e,iZ,T2,T2,T2,T2],gd=[Ol,n6e,Ny,nd,Gy,ga,mP,Nh,w2,zL,EP,qy,vP,rM,Vy,TLe,vUe,E4e,w8e,Rl,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol],MZ=[cGe,aM],wGe=[uGe,__e],YP=[fGe,yZ,i6e,a6e,ITe,XFe,uMe,DHe],BGe=[AGe,WRe],UZ=[hd,Oh,IP,$A,lM,v,D,Q,H,V,hd,hd,hd,hd,hd,hd],vGe=[pGe,JUe],gU=[R2,P6e,SP,Wke,HQe,OTe,XTe,BFe,pNe,mLe,Ry,fHe,R2,R2,R2,R2],SGe=[hGe,BQe],DGe=[gGe,JHe],_Z=[hU,$L,Se,_e,pt,aFe,hU,hU],VP=[dGe,Wt,Fy,gP,i_e,v_e,n4e,Bje],HZ=[mGe,Sy],bGe=[yGe,WNe],jZ=[EGe,nM],F2=[WP,ko,yP,eM,vu,nTe,ARe,aOe,BOe,VL,_3e,z8e,cje,WP,WP,WP],GZ=[IGe],qZ=[tE,KL,My,ZA,p2,Bu,jy,rd,xNe,DMe,qUe,tE,tE,tE,tE,tE],PGe=[CGe,q_e],dU=[rE,xRe,_Le,WMe,RUe,u_e,k_e,u4e,U4e,P8e,Fje,rE,rE,rE,rE,rE];return{_llvm_bswap_i32:RZ,dynCall_idd:iGe,dynCall_i:X6e,_i64Subtract:GP,___udivdi3:AU,dynCall_vif:N6e,setThrew:ca,dynCall_viii:tGe,_bitshift64Lshr:qP,_bitshift64Shl:kZ,dynCall_vi:M6e,dynCall_viiddi:J6e,dynCall_diii:q6e,dynCall_iii:V6e,_memset:eE,_sbrk:qh,_memcpy:Qr,__GLOBAL__sub_I_Yoga_cpp:a2,dynCall_vii:U6e,___uremdi3:pU,dynCall_vid:O6e,stackAlloc:Ua,_nbind_init:Wje,getTempRet0:MA,dynCall_di:W6e,dynCall_iid:Y6e,setTempRet0:LA,_i64Add:fU,dynCall_fiff:L6e,dynCall_iiii:G6e,_emscripten_get_global_libc:r6e,dynCall_viid:nGe,dynCall_viiid:$6e,dynCall_viififi:eGe,dynCall_ii:_6e,__GLOBAL__sub_I_Binding_cc:a8e,dynCall_viiii:sGe,dynCall_iiiiii:Z6e,stackSave:hf,dynCall_viiiii:F6e,__GLOBAL__sub_I_nbind_cc:Sr,dynCall_vidd:j6e,_free:HP,runPostSets:R6e,dynCall_viiiiii:K6e,establishStackSpace:wn,_memmove:Q2,stackRestore:lc,_malloc:_P,__GLOBAL__sub_I_common_cc:b4e,dynCall_viddi:H6e,dynCall_dii:z6e,dynCall_v:rGe}}(Module.asmGlobalArg,Module.asmLibraryArg,buffer),_llvm_bswap_i32=Module._llvm_bswap_i32=asm._llvm_bswap_i32,getTempRet0=Module.getTempRet0=asm.getTempRet0,___udivdi3=Module.___udivdi3=asm.___udivdi3,setThrew=Module.setThrew=asm.setThrew,_bitshift64Lshr=Module._bitshift64Lshr=asm._bitshift64Lshr,_bitshift64Shl=Module._bitshift64Shl=asm._bitshift64Shl,_memset=Module._memset=asm._memset,_sbrk=Module._sbrk=asm._sbrk,_memcpy=Module._memcpy=asm._memcpy,stackAlloc=Module.stackAlloc=asm.stackAlloc,___uremdi3=Module.___uremdi3=asm.___uremdi3,_nbind_init=Module._nbind_init=asm._nbind_init,_i64Subtract=Module._i64Subtract=asm._i64Subtract,setTempRet0=Module.setTempRet0=asm.setTempRet0,_i64Add=Module._i64Add=asm._i64Add,_emscripten_get_global_libc=Module._emscripten_get_global_libc=asm._emscripten_get_global_libc,__GLOBAL__sub_I_Yoga_cpp=Module.__GLOBAL__sub_I_Yoga_cpp=asm.__GLOBAL__sub_I_Yoga_cpp,__GLOBAL__sub_I_Binding_cc=Module.__GLOBAL__sub_I_Binding_cc=asm.__GLOBAL__sub_I_Binding_cc,stackSave=Module.stackSave=asm.stackSave,__GLOBAL__sub_I_nbind_cc=Module.__GLOBAL__sub_I_nbind_cc=asm.__GLOBAL__sub_I_nbind_cc,_free=Module._free=asm._free,runPostSets=Module.runPostSets=asm.runPostSets,establishStackSpace=Module.establishStackSpace=asm.establishStackSpace,_memmove=Module._memmove=asm._memmove,stackRestore=Module.stackRestore=asm.stackRestore,_malloc=Module._malloc=asm._malloc,__GLOBAL__sub_I_common_cc=Module.__GLOBAL__sub_I_common_cc=asm.__GLOBAL__sub_I_common_cc,dynCall_viiiii=Module.dynCall_viiiii=asm.dynCall_viiiii,dynCall_vif=Module.dynCall_vif=asm.dynCall_vif,dynCall_vid=Module.dynCall_vid=asm.dynCall_vid,dynCall_fiff=Module.dynCall_fiff=asm.dynCall_fiff,dynCall_vi=Module.dynCall_vi=asm.dynCall_vi,dynCall_vii=Module.dynCall_vii=asm.dynCall_vii,dynCall_ii=Module.dynCall_ii=asm.dynCall_ii,dynCall_viddi=Module.dynCall_viddi=asm.dynCall_viddi,dynCall_vidd=Module.dynCall_vidd=asm.dynCall_vidd,dynCall_iiii=Module.dynCall_iiii=asm.dynCall_iiii,dynCall_diii=Module.dynCall_diii=asm.dynCall_diii,dynCall_di=Module.dynCall_di=asm.dynCall_di,dynCall_iid=Module.dynCall_iid=asm.dynCall_iid,dynCall_iii=Module.dynCall_iii=asm.dynCall_iii,dynCall_viiddi=Module.dynCall_viiddi=asm.dynCall_viiddi,dynCall_viiiiii=Module.dynCall_viiiiii=asm.dynCall_viiiiii,dynCall_dii=Module.dynCall_dii=asm.dynCall_dii,dynCall_i=Module.dynCall_i=asm.dynCall_i,dynCall_iiiiii=Module.dynCall_iiiiii=asm.dynCall_iiiiii,dynCall_viiid=Module.dynCall_viiid=asm.dynCall_viiid,dynCall_viififi=Module.dynCall_viififi=asm.dynCall_viififi,dynCall_viii=Module.dynCall_viii=asm.dynCall_viii,dynCall_v=Module.dynCall_v=asm.dynCall_v,dynCall_viid=Module.dynCall_viid=asm.dynCall_viid,dynCall_idd=Module.dynCall_idd=asm.dynCall_idd,dynCall_viiii=Module.dynCall_viiii=asm.dynCall_viiii;Runtime.stackAlloc=Module.stackAlloc,Runtime.stackSave=Module.stackSave,Runtime.stackRestore=Module.stackRestore,Runtime.establishStackSpace=Module.establishStackSpace,Runtime.setTempRet0=Module.setTempRet0,Runtime.getTempRet0=Module.getTempRet0,Module.asm=asm;function ExitStatus(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}ExitStatus.prototype=new Error,ExitStatus.prototype.constructor=ExitStatus;var initialStackTop,preloadStartTime=null,calledMain=!1;dependenciesFulfilled=function t(){Module.calledRun||run(),Module.calledRun||(dependenciesFulfilled=t)},Module.callMain=Module.callMain=function t(e){e=e||[],ensureInitRuntime();var r=e.length+1;function s(){for(var p=0;p<3;p++)a.push(0)}var a=[allocate(intArrayFromString(Module.thisProgram),"i8",ALLOC_NORMAL)];s();for(var n=0;n0||(preRun(),runDependencies>0)||Module.calledRun)return;function e(){Module.calledRun||(Module.calledRun=!0,!ABORT&&(ensureInitRuntime(),preMain(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),Module._main&&shouldRunNow&&Module.callMain(t),postRun()))}Module.setStatus?(Module.setStatus("Running..."),setTimeout(function(){setTimeout(function(){Module.setStatus("")},1),e()},1)):e()}Module.run=Module.run=run;function exit(t,e){e&&Module.noExitRuntime||(Module.noExitRuntime||(ABORT=!0,EXITSTATUS=t,STACKTOP=initialStackTop,exitRuntime(),Module.onExit&&Module.onExit(t)),ENVIRONMENT_IS_NODE&&process.exit(t),Module.quit(t,new ExitStatus(t)))}Module.exit=Module.exit=exit;var abortDecorators=[];function abort(t){Module.onAbort&&Module.onAbort(t),t!==void 0?(Module.print(t),Module.printErr(t),t=JSON.stringify(t)):t="",ABORT=!0,EXITSTATUS=1;var e=` +If this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.`,r="abort("+t+") at "+stackTrace()+e;throw abortDecorators&&abortDecorators.forEach(function(s){r=s(r,t)}),r}if(Module.abort=Module.abort=abort,Module.preInit)for(typeof Module.preInit=="function"&&(Module.preInit=[Module.preInit]);Module.preInit.length>0;)Module.preInit.pop()();var shouldRunNow=!0;Module.noInitialRun&&(shouldRunNow=!1),run()})});var Fm=_((PKt,Rwe)=>{"use strict";var Ppt=Qwe(),xpt=Twe(),K9=!1,z9=null;xpt({},function(t,e){if(!K9){if(K9=!0,t)throw t;z9=e}});if(!K9)throw new Error("Failed to load the yoga module - it needed to be loaded synchronously, but didn't");Rwe.exports=Ppt(z9.bind,z9.lib)});var Z9=_((xKt,X9)=>{"use strict";var Fwe=t=>Number.isNaN(t)?!1:t>=4352&&(t<=4447||t===9001||t===9002||11904<=t&&t<=12871&&t!==12351||12880<=t&&t<=19903||19968<=t&&t<=42182||43360<=t&&t<=43388||44032<=t&&t<=55203||63744<=t&&t<=64255||65040<=t&&t<=65049||65072<=t&&t<=65131||65281<=t&&t<=65376||65504<=t&&t<=65510||110592<=t&&t<=110593||127488<=t&&t<=127569||131072<=t&&t<=262141);X9.exports=Fwe;X9.exports.default=Fwe});var Owe=_((kKt,Nwe)=>{"use strict";Nwe.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}});var GS=_((QKt,$9)=>{"use strict";var kpt=dk(),Qpt=Z9(),Tpt=Owe(),Lwe=t=>{if(typeof t!="string"||t.length===0||(t=kpt(t),t.length===0))return 0;t=t.replace(Tpt()," ");let e=0;for(let r=0;r=127&&s<=159||s>=768&&s<=879||(s>65535&&r++,e+=Qpt(s)?2:1)}return e};$9.exports=Lwe;$9.exports.default=Lwe});var tW=_((TKt,eW)=>{"use strict";var Rpt=GS(),Mwe=t=>{let e=0;for(let r of t.split(` +`))e=Math.max(e,Rpt(r));return e};eW.exports=Mwe;eW.exports.default=Mwe});var Uwe=_(qS=>{"use strict";var Fpt=qS&&qS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(qS,"__esModule",{value:!0});var Npt=Fpt(tW()),rW={};qS.default=t=>{if(t.length===0)return{width:0,height:0};if(rW[t])return rW[t];let e=Npt.default(t),r=t.split(` +`).length;return rW[t]={width:e,height:r},{width:e,height:r}}});var _we=_(WS=>{"use strict";var Opt=WS&&WS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(WS,"__esModule",{value:!0});var bn=Opt(Fm()),Lpt=(t,e)=>{"position"in e&&t.setPositionType(e.position==="absolute"?bn.default.POSITION_TYPE_ABSOLUTE:bn.default.POSITION_TYPE_RELATIVE)},Mpt=(t,e)=>{"marginLeft"in e&&t.setMargin(bn.default.EDGE_START,e.marginLeft||0),"marginRight"in e&&t.setMargin(bn.default.EDGE_END,e.marginRight||0),"marginTop"in e&&t.setMargin(bn.default.EDGE_TOP,e.marginTop||0),"marginBottom"in e&&t.setMargin(bn.default.EDGE_BOTTOM,e.marginBottom||0)},Upt=(t,e)=>{"paddingLeft"in e&&t.setPadding(bn.default.EDGE_LEFT,e.paddingLeft||0),"paddingRight"in e&&t.setPadding(bn.default.EDGE_RIGHT,e.paddingRight||0),"paddingTop"in e&&t.setPadding(bn.default.EDGE_TOP,e.paddingTop||0),"paddingBottom"in e&&t.setPadding(bn.default.EDGE_BOTTOM,e.paddingBottom||0)},_pt=(t,e)=>{var r;"flexGrow"in e&&t.setFlexGrow((r=e.flexGrow)!==null&&r!==void 0?r:0),"flexShrink"in e&&t.setFlexShrink(typeof e.flexShrink=="number"?e.flexShrink:1),"flexDirection"in e&&(e.flexDirection==="row"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW),e.flexDirection==="row-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW_REVERSE),e.flexDirection==="column"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN),e.flexDirection==="column-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN_REVERSE)),"flexBasis"in e&&(typeof e.flexBasis=="number"?t.setFlexBasis(e.flexBasis):typeof e.flexBasis=="string"?t.setFlexBasisPercent(Number.parseInt(e.flexBasis,10)):t.setFlexBasis(NaN)),"alignItems"in e&&((e.alignItems==="stretch"||!e.alignItems)&&t.setAlignItems(bn.default.ALIGN_STRETCH),e.alignItems==="flex-start"&&t.setAlignItems(bn.default.ALIGN_FLEX_START),e.alignItems==="center"&&t.setAlignItems(bn.default.ALIGN_CENTER),e.alignItems==="flex-end"&&t.setAlignItems(bn.default.ALIGN_FLEX_END)),"alignSelf"in e&&((e.alignSelf==="auto"||!e.alignSelf)&&t.setAlignSelf(bn.default.ALIGN_AUTO),e.alignSelf==="flex-start"&&t.setAlignSelf(bn.default.ALIGN_FLEX_START),e.alignSelf==="center"&&t.setAlignSelf(bn.default.ALIGN_CENTER),e.alignSelf==="flex-end"&&t.setAlignSelf(bn.default.ALIGN_FLEX_END)),"justifyContent"in e&&((e.justifyContent==="flex-start"||!e.justifyContent)&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_START),e.justifyContent==="center"&&t.setJustifyContent(bn.default.JUSTIFY_CENTER),e.justifyContent==="flex-end"&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_END),e.justifyContent==="space-between"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_BETWEEN),e.justifyContent==="space-around"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_AROUND))},Hpt=(t,e)=>{var r,s;"width"in e&&(typeof e.width=="number"?t.setWidth(e.width):typeof e.width=="string"?t.setWidthPercent(Number.parseInt(e.width,10)):t.setWidthAuto()),"height"in e&&(typeof e.height=="number"?t.setHeight(e.height):typeof e.height=="string"?t.setHeightPercent(Number.parseInt(e.height,10)):t.setHeightAuto()),"minWidth"in e&&(typeof e.minWidth=="string"?t.setMinWidthPercent(Number.parseInt(e.minWidth,10)):t.setMinWidth((r=e.minWidth)!==null&&r!==void 0?r:0)),"minHeight"in e&&(typeof e.minHeight=="string"?t.setMinHeightPercent(Number.parseInt(e.minHeight,10)):t.setMinHeight((s=e.minHeight)!==null&&s!==void 0?s:0))},jpt=(t,e)=>{"display"in e&&t.setDisplay(e.display==="flex"?bn.default.DISPLAY_FLEX:bn.default.DISPLAY_NONE)},Gpt=(t,e)=>{if("borderStyle"in e){let r=typeof e.borderStyle=="string"?1:0;t.setBorder(bn.default.EDGE_TOP,r),t.setBorder(bn.default.EDGE_BOTTOM,r),t.setBorder(bn.default.EDGE_LEFT,r),t.setBorder(bn.default.EDGE_RIGHT,r)}};WS.default=(t,e={})=>{Lpt(t,e),Mpt(t,e),Upt(t,e),_pt(t,e),Hpt(t,e),jpt(t,e),Gpt(t,e)}});var Gwe=_((NKt,jwe)=>{"use strict";var YS=GS(),qpt=dk(),Wpt=sk(),iW=new Set(["\x1B","\x9B"]),Ypt=39,Hwe=t=>`${iW.values().next().value}[${t}m`,Vpt=t=>t.split(" ").map(e=>YS(e)),nW=(t,e,r)=>{let s=[...e],a=!1,n=YS(qpt(t[t.length-1]));for(let[c,f]of s.entries()){let p=YS(f);if(n+p<=r?t[t.length-1]+=f:(t.push(f),n=0),iW.has(f))a=!0;else if(a&&f==="m"){a=!1;continue}a||(n+=p,n===r&&c0&&t.length>1&&(t[t.length-2]+=t.pop())},Jpt=t=>{let e=t.split(" "),r=e.length;for(;r>0&&!(YS(e[r-1])>0);)r--;return r===e.length?t:e.slice(0,r).join(" ")+e.slice(r).join("")},Kpt=(t,e,r={})=>{if(r.trim!==!1&&t.trim()==="")return"";let s="",a="",n,c=Vpt(t),f=[""];for(let[p,h]of t.split(" ").entries()){r.trim!==!1&&(f[f.length-1]=f[f.length-1].trimLeft());let E=YS(f[f.length-1]);if(p!==0&&(E>=e&&(r.wordWrap===!1||r.trim===!1)&&(f.push(""),E=0),(E>0||r.trim===!1)&&(f[f.length-1]+=" ",E++)),r.hard&&c[p]>e){let C=e-E,S=1+Math.floor((c[p]-C-1)/e);Math.floor((c[p]-1)/e)e&&E>0&&c[p]>0){if(r.wordWrap===!1&&Ee&&r.wordWrap===!1){nW(f,h,e);continue}f[f.length-1]+=h}r.trim!==!1&&(f=f.map(Jpt)),s=f.join(` +`);for(let[p,h]of[...s].entries()){if(a+=h,iW.has(h)){let C=parseFloat(/\d[^m]*/.exec(s.slice(p,p+4)));n=C===Ypt?null:C}let E=Wpt.codes.get(Number(n));n&&E&&(s[p+1]===` +`?a+=Hwe(E):h===` +`&&(a+=Hwe(n)))}return a};jwe.exports=(t,e,r)=>String(t).normalize().replace(/\r\n/g,` +`).split(` +`).map(s=>Kpt(s,e,r)).join(` +`)});var Ywe=_((OKt,Wwe)=>{"use strict";var qwe="[\uD800-\uDBFF][\uDC00-\uDFFF]",zpt=t=>t&&t.exact?new RegExp(`^${qwe}$`):new RegExp(qwe,"g");Wwe.exports=zpt});var sW=_((LKt,zwe)=>{"use strict";var Xpt=Z9(),Zpt=Ywe(),Vwe=sk(),Kwe=["\x1B","\x9B"],NF=t=>`${Kwe[0]}[${t}m`,Jwe=(t,e,r)=>{let s=[];t=[...t];for(let a of t){let n=a;a.match(";")&&(a=a.split(";")[0][0]+"0");let c=Vwe.codes.get(parseInt(a,10));if(c){let f=t.indexOf(c.toString());f>=0?t.splice(f,1):s.push(NF(e?c:n))}else if(e){s.push(NF(0));break}else s.push(NF(n))}if(e&&(s=s.filter((a,n)=>s.indexOf(a)===n),r!==void 0)){let a=NF(Vwe.codes.get(parseInt(r,10)));s=s.reduce((n,c)=>c===a?[c,...n]:[...n,c],[])}return s.join("")};zwe.exports=(t,e,r)=>{let s=[...t.normalize()],a=[];r=typeof r=="number"?r:s.length;let n=!1,c,f=0,p="";for(let[h,E]of s.entries()){let C=!1;if(Kwe.includes(E)){let S=/\d[^m]*/.exec(t.slice(h,h+18));c=S&&S.length>0?S[0]:void 0,fe&&f<=r)p+=E;else if(f===e&&!n&&c!==void 0)p=Jwe(a);else if(f>=r){p+=Jwe(a,!0,c);break}}return p}});var Zwe=_((MKt,Xwe)=>{"use strict";var $0=sW(),$pt=GS();function OF(t,e,r){if(t.charAt(e)===" ")return e;for(let s=1;s<=3;s++)if(r){if(t.charAt(e+s)===" ")return e+s}else if(t.charAt(e-s)===" ")return e-s;return e}Xwe.exports=(t,e,r)=>{r={position:"end",preferTruncationOnSpace:!1,...r};let{position:s,space:a,preferTruncationOnSpace:n}=r,c="\u2026",f=1;if(typeof t!="string")throw new TypeError(`Expected \`input\` to be a string, got ${typeof t}`);if(typeof e!="number")throw new TypeError(`Expected \`columns\` to be a number, got ${typeof e}`);if(e<1)return"";if(e===1)return c;let p=$pt(t);if(p<=e)return t;if(s==="start"){if(n){let h=OF(t,p-e+1,!0);return c+$0(t,h,p).trim()}return a===!0&&(c+=" ",f=2),c+$0(t,p-e+f,p)}if(s==="middle"){a===!0&&(c=" "+c+" ",f=3);let h=Math.floor(e/2);if(n){let E=OF(t,h),C=OF(t,p-(e-h)+1,!0);return $0(t,0,E)+c+$0(t,C,p).trim()}return $0(t,0,h)+c+$0(t,p-(e-h)+f,p)}if(s==="end"){if(n){let h=OF(t,e-1);return $0(t,0,h)+c}return a===!0&&(c=" "+c,f=2),$0(t,0,e-f)+c}throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${s}`)}});var aW=_(VS=>{"use strict";var $we=VS&&VS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(VS,"__esModule",{value:!0});var eht=$we(Gwe()),tht=$we(Zwe()),oW={};VS.default=(t,e,r)=>{let s=t+String(e)+String(r);if(oW[s])return oW[s];let a=t;if(r==="wrap"&&(a=eht.default(t,e,{trim:!1,hard:!0})),r.startsWith("truncate")){let n="end";r==="truncate-middle"&&(n="middle"),r==="truncate-start"&&(n="start"),a=tht.default(t,e,{position:n})}return oW[s]=a,a}});var cW=_(lW=>{"use strict";Object.defineProperty(lW,"__esModule",{value:!0});var e1e=t=>{let e="";if(t.childNodes.length>0)for(let r of t.childNodes){let s="";r.nodeName==="#text"?s=r.nodeValue:((r.nodeName==="ink-text"||r.nodeName==="ink-virtual-text")&&(s=e1e(r)),s.length>0&&typeof r.internal_transform=="function"&&(s=r.internal_transform(s))),e+=s}return e};lW.default=e1e});var uW=_(bi=>{"use strict";var JS=bi&&bi.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bi,"__esModule",{value:!0});bi.setTextNodeValue=bi.createTextNode=bi.setStyle=bi.setAttribute=bi.removeChildNode=bi.insertBeforeNode=bi.appendChildNode=bi.createNode=bi.TEXT_NAME=void 0;var rht=JS(Fm()),t1e=JS(Uwe()),nht=JS(_we()),iht=JS(aW()),sht=JS(cW());bi.TEXT_NAME="#text";bi.createNode=t=>{var e;let r={nodeName:t,style:{},attributes:{},childNodes:[],parentNode:null,yogaNode:t==="ink-virtual-text"?void 0:rht.default.Node.create()};return t==="ink-text"&&((e=r.yogaNode)===null||e===void 0||e.setMeasureFunc(oht.bind(null,r))),r};bi.appendChildNode=(t,e)=>{var r;e.parentNode&&bi.removeChildNode(e.parentNode,e),e.parentNode=t,t.childNodes.push(e),e.yogaNode&&((r=t.yogaNode)===null||r===void 0||r.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.insertBeforeNode=(t,e,r)=>{var s,a;e.parentNode&&bi.removeChildNode(e.parentNode,e),e.parentNode=t;let n=t.childNodes.indexOf(r);if(n>=0){t.childNodes.splice(n,0,e),e.yogaNode&&((s=t.yogaNode)===null||s===void 0||s.insertChild(e.yogaNode,n));return}t.childNodes.push(e),e.yogaNode&&((a=t.yogaNode)===null||a===void 0||a.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.removeChildNode=(t,e)=>{var r,s;e.yogaNode&&((s=(r=e.parentNode)===null||r===void 0?void 0:r.yogaNode)===null||s===void 0||s.removeChild(e.yogaNode)),e.parentNode=null;let a=t.childNodes.indexOf(e);a>=0&&t.childNodes.splice(a,1),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.setAttribute=(t,e,r)=>{t.attributes[e]=r};bi.setStyle=(t,e)=>{t.style=e,t.yogaNode&&nht.default(t.yogaNode,e)};bi.createTextNode=t=>{let e={nodeName:"#text",nodeValue:t,yogaNode:void 0,parentNode:null,style:{}};return bi.setTextNodeValue(e,t),e};var oht=function(t,e){var r,s;let a=t.nodeName==="#text"?t.nodeValue:sht.default(t),n=t1e.default(a);if(n.width<=e||n.width>=1&&e>0&&e<1)return n;let c=(s=(r=t.style)===null||r===void 0?void 0:r.textWrap)!==null&&s!==void 0?s:"wrap",f=iht.default(a,e,c);return t1e.default(f)},r1e=t=>{var e;if(!(!t||!t.parentNode))return(e=t.yogaNode)!==null&&e!==void 0?e:r1e(t.parentNode)},LF=t=>{let e=r1e(t);e?.markDirty()};bi.setTextNodeValue=(t,e)=>{typeof e!="string"&&(e=String(e)),t.nodeValue=e,LF(t)}});var a1e=_(KS=>{"use strict";var o1e=KS&&KS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(KS,"__esModule",{value:!0});var n1e=Y9(),aht=o1e(Swe()),i1e=o1e(Fm()),ea=uW(),s1e=t=>{t?.unsetMeasureFunc(),t?.freeRecursive()};KS.default=aht.default({schedulePassiveEffects:n1e.unstable_scheduleCallback,cancelPassiveEffects:n1e.unstable_cancelCallback,now:Date.now,getRootHostContext:()=>({isInsideText:!1}),prepareForCommit:()=>null,preparePortalMount:()=>null,clearContainer:()=>!1,shouldDeprioritizeSubtree:()=>!1,resetAfterCommit:t=>{if(t.isStaticDirty){t.isStaticDirty=!1,typeof t.onImmediateRender=="function"&&t.onImmediateRender();return}typeof t.onRender=="function"&&t.onRender()},getChildHostContext:(t,e)=>{let r=t.isInsideText,s=e==="ink-text"||e==="ink-virtual-text";return r===s?t:{isInsideText:s}},shouldSetTextContent:()=>!1,createInstance:(t,e,r,s)=>{if(s.isInsideText&&t==="ink-box")throw new Error(" can\u2019t be nested inside component");let a=t==="ink-text"&&s.isInsideText?"ink-virtual-text":t,n=ea.createNode(a);for(let[c,f]of Object.entries(e))c!=="children"&&(c==="style"?ea.setStyle(n,f):c==="internal_transform"?n.internal_transform=f:c==="internal_static"?n.internal_static=!0:ea.setAttribute(n,c,f));return n},createTextInstance:(t,e,r)=>{if(!r.isInsideText)throw new Error(`Text string "${t}" must be rendered inside component`);return ea.createTextNode(t)},resetTextContent:()=>{},hideTextInstance:t=>{ea.setTextNodeValue(t,"")},unhideTextInstance:(t,e)=>{ea.setTextNodeValue(t,e)},getPublicInstance:t=>t,hideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(i1e.default.DISPLAY_NONE)},unhideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(i1e.default.DISPLAY_FLEX)},appendInitialChild:ea.appendChildNode,appendChild:ea.appendChildNode,insertBefore:ea.insertBeforeNode,finalizeInitialChildren:(t,e,r,s)=>(t.internal_static&&(s.isStaticDirty=!0,s.staticNode=t),!1),supportsMutation:!0,appendChildToContainer:ea.appendChildNode,insertInContainerBefore:ea.insertBeforeNode,removeChildFromContainer:(t,e)=>{ea.removeChildNode(t,e),s1e(e.yogaNode)},prepareUpdate:(t,e,r,s,a)=>{t.internal_static&&(a.isStaticDirty=!0);let n={},c=Object.keys(s);for(let f of c)if(s[f]!==r[f]){if(f==="style"&&typeof s.style=="object"&&typeof r.style=="object"){let h=s.style,E=r.style,C=Object.keys(h);for(let S of C){if(S==="borderStyle"||S==="borderColor"){if(typeof n.style!="object"){let P={};n.style=P}n.style.borderStyle=h.borderStyle,n.style.borderColor=h.borderColor}if(h[S]!==E[S]){if(typeof n.style!="object"){let P={};n.style=P}n.style[S]=h[S]}}continue}n[f]=s[f]}return n},commitUpdate:(t,e)=>{for(let[r,s]of Object.entries(e))r!=="children"&&(r==="style"?ea.setStyle(t,s):r==="internal_transform"?t.internal_transform=s:r==="internal_static"?t.internal_static=!0:ea.setAttribute(t,r,s))},commitTextUpdate:(t,e,r)=>{ea.setTextNodeValue(t,r)},removeChild:(t,e)=>{ea.removeChildNode(t,e),s1e(e.yogaNode)}})});var c1e=_((GKt,l1e)=>{"use strict";l1e.exports=(t,e=1,r)=>{if(r={indent:" ",includeEmptyLines:!1,...r},typeof t!="string")throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof t}\``);if(typeof e!="number")throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof e}\``);if(typeof r.indent!="string")throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof r.indent}\``);if(e===0)return t;let s=r.includeEmptyLines?/^/gm:/^(?!\s*$)/gm;return t.replace(s,r.indent.repeat(e))}});var u1e=_(zS=>{"use strict";var lht=zS&&zS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(zS,"__esModule",{value:!0});var MF=lht(Fm());zS.default=t=>t.getComputedWidth()-t.getComputedPadding(MF.default.EDGE_LEFT)-t.getComputedPadding(MF.default.EDGE_RIGHT)-t.getComputedBorder(MF.default.EDGE_LEFT)-t.getComputedBorder(MF.default.EDGE_RIGHT)});var f1e=_((WKt,cht)=>{cht.exports={single:{topLeft:"\u250C",topRight:"\u2510",bottomRight:"\u2518",bottomLeft:"\u2514",vertical:"\u2502",horizontal:"\u2500"},double:{topLeft:"\u2554",topRight:"\u2557",bottomRight:"\u255D",bottomLeft:"\u255A",vertical:"\u2551",horizontal:"\u2550"},round:{topLeft:"\u256D",topRight:"\u256E",bottomRight:"\u256F",bottomLeft:"\u2570",vertical:"\u2502",horizontal:"\u2500"},bold:{topLeft:"\u250F",topRight:"\u2513",bottomRight:"\u251B",bottomLeft:"\u2517",vertical:"\u2503",horizontal:"\u2501"},singleDouble:{topLeft:"\u2553",topRight:"\u2556",bottomRight:"\u255C",bottomLeft:"\u2559",vertical:"\u2551",horizontal:"\u2500"},doubleSingle:{topLeft:"\u2552",topRight:"\u2555",bottomRight:"\u255B",bottomLeft:"\u2558",vertical:"\u2502",horizontal:"\u2550"},classic:{topLeft:"+",topRight:"+",bottomRight:"+",bottomLeft:"+",vertical:"|",horizontal:"-"}}});var p1e=_((YKt,fW)=>{"use strict";var A1e=f1e();fW.exports=A1e;fW.exports.default=A1e});var AW=_(ZS=>{"use strict";var uht=ZS&&ZS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ZS,"__esModule",{value:!0});var XS=uht(TE()),fht=/^(rgb|hsl|hsv|hwb)\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/,Aht=/^(ansi|ansi256)\(\s?(\d+)\s?\)$/,UF=(t,e)=>e==="foreground"?t:"bg"+t[0].toUpperCase()+t.slice(1);ZS.default=(t,e,r)=>{if(!e)return t;if(e in XS.default){let a=UF(e,r);return XS.default[a](t)}if(e.startsWith("#")){let a=UF("hex",r);return XS.default[a](e)(t)}if(e.startsWith("ansi")){let a=Aht.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]);return XS.default[n](c)(t)}if(e.startsWith("rgb")||e.startsWith("hsl")||e.startsWith("hsv")||e.startsWith("hwb")){let a=fht.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]),f=Number(a[3]),p=Number(a[4]);return XS.default[n](c,f,p)(t)}return t}});var g1e=_($S=>{"use strict";var h1e=$S&&$S.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty($S,"__esModule",{value:!0});var pht=h1e(p1e()),pW=h1e(AW());$S.default=(t,e,r,s)=>{if(typeof r.style.borderStyle=="string"){let a=r.yogaNode.getComputedWidth(),n=r.yogaNode.getComputedHeight(),c=r.style.borderColor,f=pht.default[r.style.borderStyle],p=pW.default(f.topLeft+f.horizontal.repeat(a-2)+f.topRight,c,"foreground"),h=(pW.default(f.vertical,c,"foreground")+` +`).repeat(n-2),E=pW.default(f.bottomLeft+f.horizontal.repeat(a-2)+f.bottomRight,c,"foreground");s.write(t,e,p,{transformers:[]}),s.write(t,e+1,h,{transformers:[]}),s.write(t+a-1,e+1,h,{transformers:[]}),s.write(t,e+n-1,E,{transformers:[]})}}});var m1e=_(eD=>{"use strict";var Nm=eD&&eD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(eD,"__esModule",{value:!0});var hht=Nm(Fm()),ght=Nm(tW()),dht=Nm(c1e()),mht=Nm(aW()),yht=Nm(u1e()),Eht=Nm(cW()),Iht=Nm(g1e()),Cht=(t,e)=>{var r;let s=(r=t.childNodes[0])===null||r===void 0?void 0:r.yogaNode;if(s){let a=s.getComputedLeft(),n=s.getComputedTop();e=` +`.repeat(n)+dht.default(e,a)}return e},d1e=(t,e,r)=>{var s;let{offsetX:a=0,offsetY:n=0,transformers:c=[],skipStaticElements:f}=r;if(f&&t.internal_static)return;let{yogaNode:p}=t;if(p){if(p.getDisplay()===hht.default.DISPLAY_NONE)return;let h=a+p.getComputedLeft(),E=n+p.getComputedTop(),C=c;if(typeof t.internal_transform=="function"&&(C=[t.internal_transform,...c]),t.nodeName==="ink-text"){let S=Eht.default(t);if(S.length>0){let P=ght.default(S),I=yht.default(p);if(P>I){let R=(s=t.style.textWrap)!==null&&s!==void 0?s:"wrap";S=mht.default(S,I,R)}S=Cht(t,S),e.write(h,E,S,{transformers:C})}return}if(t.nodeName==="ink-box"&&Iht.default(h,E,t,e),t.nodeName==="ink-root"||t.nodeName==="ink-box")for(let S of t.childNodes)d1e(S,e,{offsetX:h,offsetY:E,transformers:C,skipStaticElements:f})}};eD.default=d1e});var I1e=_(tD=>{"use strict";var E1e=tD&&tD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(tD,"__esModule",{value:!0});var y1e=E1e(sW()),wht=E1e(GS()),hW=class{constructor(e){this.writes=[];let{width:r,height:s}=e;this.width=r,this.height=s}write(e,r,s,a){let{transformers:n}=a;s&&this.writes.push({x:e,y:r,text:s,transformers:n})}get(){let e=[];for(let s=0;ss.trimRight()).join(` +`),height:e.length}}};tD.default=hW});var B1e=_(rD=>{"use strict";var gW=rD&&rD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(rD,"__esModule",{value:!0});var Bht=gW(Fm()),C1e=gW(m1e()),w1e=gW(I1e());rD.default=(t,e)=>{var r;if(t.yogaNode.setWidth(e),t.yogaNode){t.yogaNode.calculateLayout(void 0,void 0,Bht.default.DIRECTION_LTR);let s=new w1e.default({width:t.yogaNode.getComputedWidth(),height:t.yogaNode.getComputedHeight()});C1e.default(t,s,{skipStaticElements:!0});let a;!((r=t.staticNode)===null||r===void 0)&&r.yogaNode&&(a=new w1e.default({width:t.staticNode.yogaNode.getComputedWidth(),height:t.staticNode.yogaNode.getComputedHeight()}),C1e.default(t.staticNode,a,{skipStaticElements:!1}));let{output:n,height:c}=s.get();return{output:n,outputHeight:c,staticOutput:a?`${a.get().output} +`:""}}return{output:"",outputHeight:0,staticOutput:""}}});var b1e=_((ZKt,D1e)=>{"use strict";var v1e=Ie("stream"),S1e=["assert","count","countReset","debug","dir","dirxml","error","group","groupCollapsed","groupEnd","info","log","table","time","timeEnd","timeLog","trace","warn"],dW={},vht=t=>{let e=new v1e.PassThrough,r=new v1e.PassThrough;e.write=a=>t("stdout",a),r.write=a=>t("stderr",a);let s=new console.Console(e,r);for(let a of S1e)dW[a]=console[a],console[a]=s[a];return()=>{for(let a of S1e)console[a]=dW[a];dW={}}};D1e.exports=vht});var yW=_(mW=>{"use strict";Object.defineProperty(mW,"__esModule",{value:!0});mW.default=new WeakMap});var IW=_(EW=>{"use strict";Object.defineProperty(EW,"__esModule",{value:!0});var Sht=hn(),P1e=Sht.createContext({exit:()=>{}});P1e.displayName="InternalAppContext";EW.default=P1e});var wW=_(CW=>{"use strict";Object.defineProperty(CW,"__esModule",{value:!0});var Dht=hn(),x1e=Dht.createContext({stdin:void 0,setRawMode:()=>{},isRawModeSupported:!1,internal_exitOnCtrlC:!0});x1e.displayName="InternalStdinContext";CW.default=x1e});var vW=_(BW=>{"use strict";Object.defineProperty(BW,"__esModule",{value:!0});var bht=hn(),k1e=bht.createContext({stdout:void 0,write:()=>{}});k1e.displayName="InternalStdoutContext";BW.default=k1e});var DW=_(SW=>{"use strict";Object.defineProperty(SW,"__esModule",{value:!0});var Pht=hn(),Q1e=Pht.createContext({stderr:void 0,write:()=>{}});Q1e.displayName="InternalStderrContext";SW.default=Q1e});var _F=_(bW=>{"use strict";Object.defineProperty(bW,"__esModule",{value:!0});var xht=hn(),T1e=xht.createContext({activeId:void 0,add:()=>{},remove:()=>{},activate:()=>{},deactivate:()=>{},enableFocus:()=>{},disableFocus:()=>{},focusNext:()=>{},focusPrevious:()=>{},focus:()=>{}});T1e.displayName="InternalFocusContext";bW.default=T1e});var F1e=_((szt,R1e)=>{"use strict";var kht=/[|\\{}()[\]^$+*?.-]/g;R1e.exports=t=>{if(typeof t!="string")throw new TypeError("Expected a string");return t.replace(kht,"\\$&")}});var M1e=_((ozt,L1e)=>{"use strict";var Qht=F1e(),Tht=typeof process=="object"&&process&&typeof process.cwd=="function"?process.cwd():".",O1e=[].concat(Ie("module").builtinModules,"bootstrap_node","node").map(t=>new RegExp(`(?:\\((?:node:)?${t}(?:\\.js)?:\\d+:\\d+\\)$|^\\s*at (?:node:)?${t}(?:\\.js)?:\\d+:\\d+$)`));O1e.push(/\((?:node:)?internal\/[^:]+:\d+:\d+\)$/,/\s*at (?:node:)?internal\/[^:]+:\d+:\d+$/,/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/);var PW=class t{constructor(e){e={ignoredPackages:[],...e},"internals"in e||(e.internals=t.nodeInternals()),"cwd"in e||(e.cwd=Tht),this._cwd=e.cwd.replace(/\\/g,"/"),this._internals=[].concat(e.internals,Rht(e.ignoredPackages)),this._wrapCallSite=e.wrapCallSite||!1}static nodeInternals(){return[...O1e]}clean(e,r=0){r=" ".repeat(r),Array.isArray(e)||(e=e.split(` +`)),!/^\s*at /.test(e[0])&&/^\s*at /.test(e[1])&&(e=e.slice(1));let s=!1,a=null,n=[];return e.forEach(c=>{if(c=c.replace(/\\/g,"/"),this._internals.some(p=>p.test(c)))return;let f=/^\s*at /.test(c);s?c=c.trimEnd().replace(/^(\s+)at /,"$1"):(c=c.trim(),f&&(c=c.slice(3))),c=c.replace(`${this._cwd}/`,""),c&&(f?(a&&(n.push(a),a=null),n.push(c)):(s=!0,a=c))}),n.map(c=>`${r}${c} +`).join("")}captureString(e,r=this.captureString){typeof e=="function"&&(r=e,e=1/0);let{stackTraceLimit:s}=Error;e&&(Error.stackTraceLimit=e);let a={};Error.captureStackTrace(a,r);let{stack:n}=a;return Error.stackTraceLimit=s,this.clean(n)}capture(e,r=this.capture){typeof e=="function"&&(r=e,e=1/0);let{prepareStackTrace:s,stackTraceLimit:a}=Error;Error.prepareStackTrace=(f,p)=>this._wrapCallSite?p.map(this._wrapCallSite):p,e&&(Error.stackTraceLimit=e);let n={};Error.captureStackTrace(n,r);let{stack:c}=n;return Object.assign(Error,{prepareStackTrace:s,stackTraceLimit:a}),c}at(e=this.at){let[r]=this.capture(1,e);if(!r)return{};let s={line:r.getLineNumber(),column:r.getColumnNumber()};N1e(s,r.getFileName(),this._cwd),r.isConstructor()&&(s.constructor=!0),r.isEval()&&(s.evalOrigin=r.getEvalOrigin()),r.isNative()&&(s.native=!0);let a;try{a=r.getTypeName()}catch{}a&&a!=="Object"&&a!=="[object Object]"&&(s.type=a);let n=r.getFunctionName();n&&(s.function=n);let c=r.getMethodName();return c&&n!==c&&(s.method=c),s}parseLine(e){let r=e&&e.match(Fht);if(!r)return null;let s=r[1]==="new",a=r[2],n=r[3],c=r[4],f=Number(r[5]),p=Number(r[6]),h=r[7],E=r[8],C=r[9],S=r[10]==="native",P=r[11]===")",I,R={};if(E&&(R.line=Number(E)),C&&(R.column=Number(C)),P&&h){let N=0;for(let U=h.length-1;U>0;U--)if(h.charAt(U)===")")N++;else if(h.charAt(U)==="("&&h.charAt(U-1)===" "&&(N--,N===-1&&h.charAt(U-1)===" ")){let W=h.slice(0,U-1);h=h.slice(U+1),a+=` (${W}`;break}}if(a){let N=a.match(Nht);N&&(a=N[1],I=N[2])}return N1e(R,h,this._cwd),s&&(R.constructor=!0),n&&(R.evalOrigin=n,R.evalLine=f,R.evalColumn=p,R.evalFile=c&&c.replace(/\\/g,"/")),S&&(R.native=!0),a&&(R.function=a),I&&a!==I&&(R.method=I),R}};function N1e(t,e,r){e&&(e=e.replace(/\\/g,"/"),e.startsWith(`${r}/`)&&(e=e.slice(r.length+1)),t.file=e)}function Rht(t){if(t.length===0)return[];let e=t.map(r=>Qht(r));return new RegExp(`[/\\\\]node_modules[/\\\\](?:${e.join("|")})[/\\\\][^:]+:\\d+:\\d+`)}var Fht=new RegExp("^(?:\\s*at )?(?:(new) )?(?:(.*?) \\()?(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?(?:(.+?):(\\d+):(\\d+)|(native))(\\)?)$"),Nht=/^(.*?) \[as (.*?)\]$/;L1e.exports=PW});var _1e=_((azt,U1e)=>{"use strict";U1e.exports=(t,e)=>t.replace(/^\t+/gm,r=>" ".repeat(r.length*(e||2)))});var j1e=_((lzt,H1e)=>{"use strict";var Oht=_1e(),Lht=(t,e)=>{let r=[],s=t-e,a=t+e;for(let n=s;n<=a;n++)r.push(n);return r};H1e.exports=(t,e,r)=>{if(typeof t!="string")throw new TypeError("Source code is missing.");if(!e||e<1)throw new TypeError("Line number must start from `1`.");if(t=Oht(t).split(/\r?\n/),!(e>t.length))return r={around:3,...r},Lht(e,r.around).filter(s=>t[s-1]!==void 0).map(s=>({line:s,value:t[s-1]}))}});var HF=_(rf=>{"use strict";var Mht=rf&&rf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Uht=rf&&rf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),_ht=rf&&rf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Mht(e,t,r);return Uht(e,t),e},Hht=rf&&rf.__rest||function(t,e){var r={};for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&e.indexOf(s)<0&&(r[s]=t[s]);if(t!=null&&typeof Object.getOwnPropertySymbols=="function")for(var a=0,s=Object.getOwnPropertySymbols(t);a{var{children:r}=t,s=Hht(t,["children"]);let a=Object.assign(Object.assign({},s),{marginLeft:s.marginLeft||s.marginX||s.margin||0,marginRight:s.marginRight||s.marginX||s.margin||0,marginTop:s.marginTop||s.marginY||s.margin||0,marginBottom:s.marginBottom||s.marginY||s.margin||0,paddingLeft:s.paddingLeft||s.paddingX||s.padding||0,paddingRight:s.paddingRight||s.paddingX||s.padding||0,paddingTop:s.paddingTop||s.paddingY||s.padding||0,paddingBottom:s.paddingBottom||s.paddingY||s.padding||0});return G1e.default.createElement("ink-box",{ref:e,style:a},r)});xW.displayName="Box";xW.defaultProps={flexDirection:"row",flexGrow:0,flexShrink:1};rf.default=xW});var TW=_(nD=>{"use strict";var kW=nD&&nD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nD,"__esModule",{value:!0});var jht=kW(hn()),yw=kW(TE()),q1e=kW(AW()),QW=({color:t,backgroundColor:e,dimColor:r,bold:s,italic:a,underline:n,strikethrough:c,inverse:f,wrap:p,children:h})=>{if(h==null)return null;let E=C=>(r&&(C=yw.default.dim(C)),t&&(C=q1e.default(C,t,"foreground")),e&&(C=q1e.default(C,e,"background")),s&&(C=yw.default.bold(C)),a&&(C=yw.default.italic(C)),n&&(C=yw.default.underline(C)),c&&(C=yw.default.strikethrough(C)),f&&(C=yw.default.inverse(C)),C);return jht.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row",textWrap:p},internal_transform:E},h)};QW.displayName="Text";QW.defaultProps={dimColor:!1,bold:!1,italic:!1,underline:!1,strikethrough:!1,wrap:"wrap"};nD.default=QW});var J1e=_(nf=>{"use strict";var Ght=nf&&nf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),qht=nf&&nf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Wht=nf&&nf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Ght(e,t,r);return qht(e,t),e},iD=nf&&nf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nf,"__esModule",{value:!0});var W1e=Wht(Ie("fs")),Qs=iD(hn()),Y1e=iD(M1e()),Yht=iD(j1e()),$p=iD(HF()),AA=iD(TW()),V1e=new Y1e.default({cwd:process.cwd(),internals:Y1e.default.nodeInternals()}),Vht=({error:t})=>{let e=t.stack?t.stack.split(` +`).slice(1):void 0,r=e?V1e.parseLine(e[0]):void 0,s,a=0;if(r?.file&&r?.line&&W1e.existsSync(r.file)){let n=W1e.readFileSync(r.file,"utf8");if(s=Yht.default(n,r.line),s)for(let{line:c}of s)a=Math.max(a,String(c).length)}return Qs.default.createElement($p.default,{flexDirection:"column",padding:1},Qs.default.createElement($p.default,null,Qs.default.createElement(AA.default,{backgroundColor:"red",color:"white"}," ","ERROR"," "),Qs.default.createElement(AA.default,null," ",t.message)),r&&Qs.default.createElement($p.default,{marginTop:1},Qs.default.createElement(AA.default,{dimColor:!0},r.file,":",r.line,":",r.column)),r&&s&&Qs.default.createElement($p.default,{marginTop:1,flexDirection:"column"},s.map(({line:n,value:c})=>Qs.default.createElement($p.default,{key:n},Qs.default.createElement($p.default,{width:a+1},Qs.default.createElement(AA.default,{dimColor:n!==r.line,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0},String(n).padStart(a," "),":")),Qs.default.createElement(AA.default,{key:n,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0}," "+c)))),t.stack&&Qs.default.createElement($p.default,{marginTop:1,flexDirection:"column"},t.stack.split(` +`).slice(1).map(n=>{let c=V1e.parseLine(n);return c?Qs.default.createElement($p.default,{key:n},Qs.default.createElement(AA.default,{dimColor:!0},"- "),Qs.default.createElement(AA.default,{dimColor:!0,bold:!0},c.function),Qs.default.createElement(AA.default,{dimColor:!0,color:"gray"}," ","(",c.file,":",c.line,":",c.column,")")):Qs.default.createElement($p.default,{key:n},Qs.default.createElement(AA.default,{dimColor:!0},"- "),Qs.default.createElement(AA.default,{dimColor:!0,bold:!0},n))})))};nf.default=Vht});var z1e=_(sf=>{"use strict";var Jht=sf&&sf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Kht=sf&&sf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),zht=sf&&sf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Jht(e,t,r);return Kht(e,t),e},Lm=sf&&sf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sf,"__esModule",{value:!0});var Om=zht(hn()),K1e=Lm(F9()),Xht=Lm(IW()),Zht=Lm(wW()),$ht=Lm(vW()),e0t=Lm(DW()),t0t=Lm(_F()),r0t=Lm(J1e()),n0t=" ",i0t="\x1B[Z",s0t="\x1B",jF=class extends Om.PureComponent{constructor(){super(...arguments),this.state={isFocusEnabled:!0,activeFocusId:void 0,focusables:[],error:void 0},this.rawModeEnabledCount=0,this.handleSetRawMode=e=>{let{stdin:r}=this.props;if(!this.isRawModeSupported())throw r===process.stdin?new Error(`Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default. +Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`):new Error(`Raw mode is not supported on the stdin provided to Ink. +Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`);if(r.setEncoding("utf8"),e){this.rawModeEnabledCount===0&&(r.addListener("data",this.handleInput),r.resume(),r.setRawMode(!0)),this.rawModeEnabledCount++;return}--this.rawModeEnabledCount===0&&(r.setRawMode(!1),r.removeListener("data",this.handleInput),r.pause())},this.handleInput=e=>{e===""&&this.props.exitOnCtrlC&&this.handleExit(),e===s0t&&this.state.activeFocusId&&this.setState({activeFocusId:void 0}),this.state.isFocusEnabled&&this.state.focusables.length>0&&(e===n0t&&this.focusNext(),e===i0t&&this.focusPrevious())},this.handleExit=e=>{this.isRawModeSupported()&&this.handleSetRawMode(!1),this.props.onExit(e)},this.enableFocus=()=>{this.setState({isFocusEnabled:!0})},this.disableFocus=()=>{this.setState({isFocusEnabled:!1})},this.focus=e=>{this.setState(r=>r.focusables.some(a=>a?.id===e)?{activeFocusId:e}:r)},this.focusNext=()=>{this.setState(e=>{var r;let s=(r=e.focusables[0])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findNextFocusable(e)||s}})},this.focusPrevious=()=>{this.setState(e=>{var r;let s=(r=e.focusables[e.focusables.length-1])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findPreviousFocusable(e)||s}})},this.addFocusable=(e,{autoFocus:r})=>{this.setState(s=>{let a=s.activeFocusId;return!a&&r&&(a=e),{activeFocusId:a,focusables:[...s.focusables,{id:e,isActive:!0}]}})},this.removeFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.filter(s=>s.id!==e)}))},this.activateFocusable=e=>{this.setState(r=>({focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!0})}))},this.deactivateFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!1})}))},this.findNextFocusable=e=>{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s+1;a{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s-1;a>=0;a--)if(!((r=e.focusables[a])===null||r===void 0)&&r.isActive)return e.focusables[a].id}}static getDerivedStateFromError(e){return{error:e}}isRawModeSupported(){return this.props.stdin.isTTY}render(){return Om.default.createElement(Xht.default.Provider,{value:{exit:this.handleExit}},Om.default.createElement(Zht.default.Provider,{value:{stdin:this.props.stdin,setRawMode:this.handleSetRawMode,isRawModeSupported:this.isRawModeSupported(),internal_exitOnCtrlC:this.props.exitOnCtrlC}},Om.default.createElement($ht.default.Provider,{value:{stdout:this.props.stdout,write:this.props.writeToStdout}},Om.default.createElement(e0t.default.Provider,{value:{stderr:this.props.stderr,write:this.props.writeToStderr}},Om.default.createElement(t0t.default.Provider,{value:{activeId:this.state.activeFocusId,add:this.addFocusable,remove:this.removeFocusable,activate:this.activateFocusable,deactivate:this.deactivateFocusable,enableFocus:this.enableFocus,disableFocus:this.disableFocus,focusNext:this.focusNext,focusPrevious:this.focusPrevious,focus:this.focus}},this.state.error?Om.default.createElement(r0t.default,{error:this.state.error}):this.props.children)))))}componentDidMount(){K1e.default.hide(this.props.stdout)}componentWillUnmount(){K1e.default.show(this.props.stdout),this.isRawModeSupported()&&this.handleSetRawMode(!1)}componentDidCatch(e){this.handleExit(e)}};sf.default=jF;jF.displayName="InternalApp"});var $1e=_(of=>{"use strict";var o0t=of&&of.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),a0t=of&&of.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),l0t=of&&of.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&o0t(e,t,r);return a0t(e,t),e},af=of&&of.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(of,"__esModule",{value:!0});var c0t=af(hn()),X1e=WCe(),u0t=af(awe()),f0t=af(x9()),A0t=af(pwe()),p0t=af(gwe()),RW=af(a1e()),h0t=af(B1e()),g0t=af(R9()),d0t=af(b1e()),m0t=l0t(uW()),y0t=af(yW()),E0t=af(z1e()),Ew=process.env.CI==="false"?!1:A0t.default,Z1e=()=>{},FW=class{constructor(e){this.resolveExitPromise=()=>{},this.rejectExitPromise=()=>{},this.unsubscribeExit=()=>{},this.onRender=()=>{if(this.isUnmounted)return;let{output:r,outputHeight:s,staticOutput:a}=h0t.default(this.rootNode,this.options.stdout.columns||80),n=a&&a!==` +`;if(this.options.debug){n&&(this.fullStaticOutput+=a),this.options.stdout.write(this.fullStaticOutput+r);return}if(Ew){n&&this.options.stdout.write(a),this.lastOutput=r;return}if(n&&(this.fullStaticOutput+=a),s>=this.options.stdout.rows){this.options.stdout.write(f0t.default.clearTerminal+this.fullStaticOutput+r),this.lastOutput=r;return}n&&(this.log.clear(),this.options.stdout.write(a),this.log(r)),!n&&r!==this.lastOutput&&this.throttledLog(r),this.lastOutput=r},p0t.default(this),this.options=e,this.rootNode=m0t.createNode("ink-root"),this.rootNode.onRender=e.debug?this.onRender:X1e(this.onRender,32,{leading:!0,trailing:!0}),this.rootNode.onImmediateRender=this.onRender,this.log=u0t.default.create(e.stdout),this.throttledLog=e.debug?this.log:X1e(this.log,void 0,{leading:!0,trailing:!0}),this.isUnmounted=!1,this.lastOutput="",this.fullStaticOutput="",this.container=RW.default.createContainer(this.rootNode,0,!1,null),this.unsubscribeExit=g0t.default(this.unmount,{alwaysLast:!1}),e.patchConsole&&this.patchConsole(),Ew||(e.stdout.on("resize",this.onRender),this.unsubscribeResize=()=>{e.stdout.off("resize",this.onRender)})}render(e){let r=c0t.default.createElement(E0t.default,{stdin:this.options.stdin,stdout:this.options.stdout,stderr:this.options.stderr,writeToStdout:this.writeToStdout,writeToStderr:this.writeToStderr,exitOnCtrlC:this.options.exitOnCtrlC,onExit:this.unmount},e);RW.default.updateContainer(r,this.container,null,Z1e)}writeToStdout(e){if(!this.isUnmounted){if(this.options.debug){this.options.stdout.write(e+this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stdout.write(e);return}this.log.clear(),this.options.stdout.write(e),this.log(this.lastOutput)}}writeToStderr(e){if(!this.isUnmounted){if(this.options.debug){this.options.stderr.write(e),this.options.stdout.write(this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stderr.write(e);return}this.log.clear(),this.options.stderr.write(e),this.log(this.lastOutput)}}unmount(e){this.isUnmounted||(this.onRender(),this.unsubscribeExit(),typeof this.restoreConsole=="function"&&this.restoreConsole(),typeof this.unsubscribeResize=="function"&&this.unsubscribeResize(),Ew?this.options.stdout.write(this.lastOutput+` +`):this.options.debug||this.log.done(),this.isUnmounted=!0,RW.default.updateContainer(null,this.container,null,Z1e),y0t.default.delete(this.options.stdout),e instanceof Error?this.rejectExitPromise(e):this.resolveExitPromise())}waitUntilExit(){return this.exitPromise||(this.exitPromise=new Promise((e,r)=>{this.resolveExitPromise=e,this.rejectExitPromise=r})),this.exitPromise}clear(){!Ew&&!this.options.debug&&this.log.clear()}patchConsole(){this.options.debug||(this.restoreConsole=d0t.default((e,r)=>{e==="stdout"&&this.writeToStdout(r),e==="stderr"&&(r.startsWith("The above error occurred")||this.writeToStderr(r))}))}};of.default=FW});var t2e=_(sD=>{"use strict";var e2e=sD&&sD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sD,"__esModule",{value:!0});var I0t=e2e($1e()),GF=e2e(yW()),C0t=Ie("stream"),w0t=(t,e)=>{let r=Object.assign({stdout:process.stdout,stdin:process.stdin,stderr:process.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0},B0t(e)),s=v0t(r.stdout,()=>new I0t.default(r));return s.render(t),{rerender:s.render,unmount:()=>s.unmount(),waitUntilExit:s.waitUntilExit,cleanup:()=>GF.default.delete(r.stdout),clear:s.clear}};sD.default=w0t;var B0t=(t={})=>t instanceof C0t.Stream?{stdout:t,stdin:process.stdin}:t,v0t=(t,e)=>{let r;return GF.default.has(t)?r=GF.default.get(t):(r=e(),GF.default.set(t,r)),r}});var n2e=_(eh=>{"use strict";var S0t=eh&&eh.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),D0t=eh&&eh.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),b0t=eh&&eh.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&S0t(e,t,r);return D0t(e,t),e};Object.defineProperty(eh,"__esModule",{value:!0});var oD=b0t(hn()),r2e=t=>{let{items:e,children:r,style:s}=t,[a,n]=oD.useState(0),c=oD.useMemo(()=>e.slice(a),[e,a]);oD.useLayoutEffect(()=>{n(e.length)},[e.length]);let f=c.map((h,E)=>r(h,a+E)),p=oD.useMemo(()=>Object.assign({position:"absolute",flexDirection:"column"},s),[s]);return oD.default.createElement("ink-box",{internal_static:!0,style:p},f)};r2e.displayName="Static";eh.default=r2e});var s2e=_(aD=>{"use strict";var P0t=aD&&aD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(aD,"__esModule",{value:!0});var x0t=P0t(hn()),i2e=({children:t,transform:e})=>t==null?null:x0t.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row"},internal_transform:e},t);i2e.displayName="Transform";aD.default=i2e});var a2e=_(lD=>{"use strict";var k0t=lD&&lD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(lD,"__esModule",{value:!0});var Q0t=k0t(hn()),o2e=({count:t=1})=>Q0t.default.createElement("ink-text",null,` +`.repeat(t));o2e.displayName="Newline";lD.default=o2e});var u2e=_(cD=>{"use strict";var l2e=cD&&cD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(cD,"__esModule",{value:!0});var T0t=l2e(hn()),R0t=l2e(HF()),c2e=()=>T0t.default.createElement(R0t.default,{flexGrow:1});c2e.displayName="Spacer";cD.default=c2e});var qF=_(uD=>{"use strict";var F0t=uD&&uD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(uD,"__esModule",{value:!0});var N0t=hn(),O0t=F0t(wW()),L0t=()=>N0t.useContext(O0t.default);uD.default=L0t});var A2e=_(fD=>{"use strict";var M0t=fD&&fD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(fD,"__esModule",{value:!0});var f2e=hn(),U0t=M0t(qF()),_0t=(t,e={})=>{let{stdin:r,setRawMode:s,internal_exitOnCtrlC:a}=U0t.default();f2e.useEffect(()=>{if(e.isActive!==!1)return s(!0),()=>{s(!1)}},[e.isActive,s]),f2e.useEffect(()=>{if(e.isActive===!1)return;let n=c=>{let f=String(c),p={upArrow:f==="\x1B[A",downArrow:f==="\x1B[B",leftArrow:f==="\x1B[D",rightArrow:f==="\x1B[C",pageDown:f==="\x1B[6~",pageUp:f==="\x1B[5~",return:f==="\r",escape:f==="\x1B",ctrl:!1,shift:!1,tab:f===" "||f==="\x1B[Z",backspace:f==="\b",delete:f==="\x7F"||f==="\x1B[3~",meta:!1};f<=""&&!p.return&&(f=String.fromCharCode(f.charCodeAt(0)+97-1),p.ctrl=!0),f.startsWith("\x1B")&&(f=f.slice(1),p.meta=!0);let h=f>="A"&&f<="Z",E=f>="\u0410"&&f<="\u042F";f.length===1&&(h||E)&&(p.shift=!0),p.tab&&f==="[Z"&&(p.shift=!0),(p.tab||p.backspace||p.delete)&&(f=""),(!(f==="c"&&p.ctrl)||!a)&&t(f,p)};return r?.on("data",n),()=>{r?.off("data",n)}},[e.isActive,r,a,t])};fD.default=_0t});var p2e=_(AD=>{"use strict";var H0t=AD&&AD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(AD,"__esModule",{value:!0});var j0t=hn(),G0t=H0t(IW()),q0t=()=>j0t.useContext(G0t.default);AD.default=q0t});var h2e=_(pD=>{"use strict";var W0t=pD&&pD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pD,"__esModule",{value:!0});var Y0t=hn(),V0t=W0t(vW()),J0t=()=>Y0t.useContext(V0t.default);pD.default=J0t});var g2e=_(hD=>{"use strict";var K0t=hD&&hD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(hD,"__esModule",{value:!0});var z0t=hn(),X0t=K0t(DW()),Z0t=()=>z0t.useContext(X0t.default);hD.default=Z0t});var m2e=_(dD=>{"use strict";var d2e=dD&&dD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(dD,"__esModule",{value:!0});var gD=hn(),$0t=d2e(_F()),egt=d2e(qF()),tgt=({isActive:t=!0,autoFocus:e=!1,id:r}={})=>{let{isRawModeSupported:s,setRawMode:a}=egt.default(),{activeId:n,add:c,remove:f,activate:p,deactivate:h,focus:E}=gD.useContext($0t.default),C=gD.useMemo(()=>r??Math.random().toString().slice(2,7),[r]);return gD.useEffect(()=>(c(C,{autoFocus:e}),()=>{f(C)}),[C,e]),gD.useEffect(()=>{t?p(C):h(C)},[t,C]),gD.useEffect(()=>{if(!(!s||!t))return a(!0),()=>{a(!1)}},[t]),{isFocused:!!C&&n===C,focus:E}};dD.default=tgt});var y2e=_(mD=>{"use strict";var rgt=mD&&mD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(mD,"__esModule",{value:!0});var ngt=hn(),igt=rgt(_F()),sgt=()=>{let t=ngt.useContext(igt.default);return{enableFocus:t.enableFocus,disableFocus:t.disableFocus,focusNext:t.focusNext,focusPrevious:t.focusPrevious,focus:t.focus}};mD.default=sgt});var E2e=_(NW=>{"use strict";Object.defineProperty(NW,"__esModule",{value:!0});NW.default=t=>{var e,r,s,a;return{width:(r=(e=t.yogaNode)===null||e===void 0?void 0:e.getComputedWidth())!==null&&r!==void 0?r:0,height:(a=(s=t.yogaNode)===null||s===void 0?void 0:s.getComputedHeight())!==null&&a!==void 0?a:0}}});var Wc=_(mo=>{"use strict";Object.defineProperty(mo,"__esModule",{value:!0});var ogt=t2e();Object.defineProperty(mo,"render",{enumerable:!0,get:function(){return ogt.default}});var agt=HF();Object.defineProperty(mo,"Box",{enumerable:!0,get:function(){return agt.default}});var lgt=TW();Object.defineProperty(mo,"Text",{enumerable:!0,get:function(){return lgt.default}});var cgt=n2e();Object.defineProperty(mo,"Static",{enumerable:!0,get:function(){return cgt.default}});var ugt=s2e();Object.defineProperty(mo,"Transform",{enumerable:!0,get:function(){return ugt.default}});var fgt=a2e();Object.defineProperty(mo,"Newline",{enumerable:!0,get:function(){return fgt.default}});var Agt=u2e();Object.defineProperty(mo,"Spacer",{enumerable:!0,get:function(){return Agt.default}});var pgt=A2e();Object.defineProperty(mo,"useInput",{enumerable:!0,get:function(){return pgt.default}});var hgt=p2e();Object.defineProperty(mo,"useApp",{enumerable:!0,get:function(){return hgt.default}});var ggt=qF();Object.defineProperty(mo,"useStdin",{enumerable:!0,get:function(){return ggt.default}});var dgt=h2e();Object.defineProperty(mo,"useStdout",{enumerable:!0,get:function(){return dgt.default}});var mgt=g2e();Object.defineProperty(mo,"useStderr",{enumerable:!0,get:function(){return mgt.default}});var ygt=m2e();Object.defineProperty(mo,"useFocus",{enumerable:!0,get:function(){return ygt.default}});var Egt=y2e();Object.defineProperty(mo,"useFocusManager",{enumerable:!0,get:function(){return Egt.default}});var Igt=E2e();Object.defineProperty(mo,"measureElement",{enumerable:!0,get:function(){return Igt.default}})});var LW={};Vt(LW,{Gem:()=>OW});var I2e,Mm,OW,WF=Xe(()=>{I2e=ut(Wc()),Mm=ut(hn()),OW=(0,Mm.memo)(({active:t})=>{let e=(0,Mm.useMemo)(()=>t?"\u25C9":"\u25EF",[t]),r=(0,Mm.useMemo)(()=>t?"green":"yellow",[t]);return Mm.default.createElement(I2e.Text,{color:r},e)})});var w2e={};Vt(w2e,{useKeypress:()=>Um});function Um({active:t},e,r){let{stdin:s}=(0,C2e.useStdin)(),a=(0,YF.useCallback)((n,c)=>e(n,c),r);(0,YF.useEffect)(()=>{if(!(!t||!s))return s.on("keypress",a),()=>{s.off("keypress",a)}},[t,a,s])}var C2e,YF,yD=Xe(()=>{C2e=ut(Wc()),YF=ut(hn())});var v2e={};Vt(v2e,{FocusRequest:()=>B2e,useFocusRequest:()=>MW});var B2e,MW,UW=Xe(()=>{yD();B2e=(r=>(r.BEFORE="before",r.AFTER="after",r))(B2e||{}),MW=function({active:t},e,r){Um({active:t},(s,a)=>{a.name==="tab"&&(a.shift?e("before"):e("after"))},r)}});var S2e={};Vt(S2e,{useListInput:()=>ED});var ED,VF=Xe(()=>{yD();ED=function(t,e,{active:r,minus:s,plus:a,set:n,loop:c=!0}){Um({active:r},(f,p)=>{let h=e.indexOf(t);switch(p.name){case s:{let E=h-1;if(c){n(e[(e.length+E)%e.length]);return}if(E<0)return;n(e[E])}break;case a:{let E=h+1;if(c){n(e[E%e.length]);return}if(E>=e.length)return;n(e[E])}break}},[e,t,a,n,c])}});var JF={};Vt(JF,{ScrollableItems:()=>Cgt});var eg,dl,Cgt,KF=Xe(()=>{eg=ut(Wc()),dl=ut(hn());UW();VF();Cgt=({active:t=!0,children:e=[],radius:r=10,size:s=1,loop:a=!0,onFocusRequest:n,willReachEnd:c})=>{let f=N=>{if(N.key===null)throw new Error("Expected all children to have a key");return N.key},p=dl.default.Children.map(e,N=>f(N)),h=p[0],[E,C]=(0,dl.useState)(h),S=p.indexOf(E);(0,dl.useEffect)(()=>{p.includes(E)||C(h)},[e]),(0,dl.useEffect)(()=>{c&&S>=p.length-2&&c()},[S]),MW({active:t&&!!n},N=>{n?.(N)},[n]),ED(E,p,{active:t,minus:"up",plus:"down",set:C,loop:a});let P=S-r,I=S+r;I>p.length&&(P-=I-p.length,I=p.length),P<0&&(I+=-P,P=0),I>=p.length&&(I=p.length-1);let R=[];for(let N=P;N<=I;++N){let U=p[N],W=t&&U===E;R.push(dl.default.createElement(eg.Box,{key:U,height:s},dl.default.createElement(eg.Box,{marginLeft:1,marginRight:1},dl.default.createElement(eg.Text,null,W?dl.default.createElement(eg.Text,{color:"cyan",bold:!0},">"):" ")),dl.default.createElement(eg.Box,null,dl.default.cloneElement(e[N],{active:W}))))}return dl.default.createElement(eg.Box,{flexDirection:"column",width:"100%"},R)}});var D2e,th,b2e,_W,P2e,HW=Xe(()=>{D2e=ut(Wc()),th=ut(hn()),b2e=Ie("readline"),_W=th.default.createContext(null),P2e=({children:t})=>{let{stdin:e,setRawMode:r}=(0,D2e.useStdin)();(0,th.useEffect)(()=>{r&&r(!0),e&&(0,b2e.emitKeypressEvents)(e)},[e,r]);let[s,a]=(0,th.useState)(new Map),n=(0,th.useMemo)(()=>({getAll:()=>s,get:c=>s.get(c),set:(c,f)=>a(new Map([...s,[c,f]]))}),[s,a]);return th.default.createElement(_W.Provider,{value:n,children:t})}});var jW={};Vt(jW,{useMinistore:()=>wgt});function wgt(t,e){let r=(0,zF.useContext)(_W);if(r===null)throw new Error("Expected this hook to run with a ministore context attached");if(typeof t>"u")return r.getAll();let s=(0,zF.useCallback)(n=>{r.set(t,n)},[t,r.set]),a=r.get(t);return typeof a>"u"&&(a=e),[a,s]}var zF,GW=Xe(()=>{zF=ut(hn());HW()});var ZF={};Vt(ZF,{renderForm:()=>Bgt});async function Bgt(t,e,{stdin:r,stdout:s,stderr:a}){let n,c=p=>{let{exit:h}=(0,XF.useApp)();Um({active:!0},(E,C)=>{C.name==="return"&&(n=p,h())},[h,p])},{waitUntilExit:f}=(0,XF.render)(qW.default.createElement(P2e,null,qW.default.createElement(t,{...e,useSubmit:c})),{stdin:r,stdout:s,stderr:a});return await f(),n}var XF,qW,$F=Xe(()=>{XF=ut(Wc()),qW=ut(hn());HW();yD()});var T2e=_(ID=>{"use strict";Object.defineProperty(ID,"__esModule",{value:!0});ID.UncontrolledTextInput=void 0;var k2e=hn(),WW=hn(),x2e=Wc(),_m=TE(),Q2e=({value:t,placeholder:e="",focus:r=!0,mask:s,highlightPastedText:a=!1,showCursor:n=!0,onChange:c,onSubmit:f})=>{let[{cursorOffset:p,cursorWidth:h},E]=WW.useState({cursorOffset:(t||"").length,cursorWidth:0});WW.useEffect(()=>{E(R=>{if(!r||!n)return R;let N=t||"";return R.cursorOffset>N.length-1?{cursorOffset:N.length,cursorWidth:0}:R})},[t,r,n]);let C=a?h:0,S=s?s.repeat(t.length):t,P=S,I=e?_m.grey(e):void 0;if(n&&r){I=e.length>0?_m.inverse(e[0])+_m.grey(e.slice(1)):_m.inverse(" "),P=S.length>0?"":_m.inverse(" ");let R=0;for(let N of S)R>=p-C&&R<=p?P+=_m.inverse(N):P+=N,R++;S.length>0&&p===S.length&&(P+=_m.inverse(" "))}return x2e.useInput((R,N)=>{if(N.upArrow||N.downArrow||N.ctrl&&R==="c"||N.tab||N.shift&&N.tab)return;if(N.return){f&&f(t);return}let U=p,W=t,ee=0;N.leftArrow?n&&U--:N.rightArrow?n&&U++:N.backspace||N.delete?p>0&&(W=t.slice(0,p-1)+t.slice(p,t.length),U--):(W=t.slice(0,p)+R+t.slice(p,t.length),U+=R.length,R.length>1&&(ee=R.length)),p<0&&(U=0),p>t.length&&(U=t.length),E({cursorOffset:U,cursorWidth:ee}),W!==t&&c(W)},{isActive:r}),k2e.createElement(x2e.Text,null,e?S.length>0?P:I:P)};ID.default=Q2e;ID.UncontrolledTextInput=({initialValue:t="",...e})=>{let[r,s]=WW.useState(t);return k2e.createElement(Q2e,Object.assign({},e,{value:r,onChange:s}))}});var N2e={};Vt(N2e,{Pad:()=>YW});var R2e,F2e,YW,VW=Xe(()=>{R2e=ut(Wc()),F2e=ut(hn()),YW=({length:t,active:e})=>{if(t===0)return null;let r=t>1?` ${"-".repeat(t-1)}`:" ";return F2e.default.createElement(R2e.Text,{dimColor:!e},r)}});var O2e={};Vt(O2e,{ItemOptions:()=>vgt});var wD,tg,vgt,L2e=Xe(()=>{wD=ut(Wc()),tg=ut(hn());VF();WF();VW();vgt=function({active:t,skewer:e,options:r,value:s,onChange:a,sizes:n=[]}){let c=r.filter(({label:p})=>!!p).map(({value:p})=>p),f=r.findIndex(p=>p.value===s&&p.label!="");return ED(s,c,{active:t,minus:"left",plus:"right",set:a}),tg.default.createElement(tg.default.Fragment,null,r.map(({label:p},h)=>{let E=h===f,C=n[h]-1||0,S=p.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),P=Math.max(0,C-S.length-2);return p?tg.default.createElement(wD.Box,{key:p,width:C,marginLeft:1},tg.default.createElement(wD.Text,{wrap:"truncate"},tg.default.createElement(OW,{active:E})," ",p),e?tg.default.createElement(YW,{active:t,length:P}):null):tg.default.createElement(wD.Box,{key:`spacer-${h}`,width:C,marginLeft:1})}))}});var Z2e=_((AZt,X2e)=>{var iY;X2e.exports=()=>(typeof iY>"u"&&(iY=Ie("zlib").brotliDecompressSync(Buffer.from("WzmldgG9bVwKtw2AiKrr15/TXjBi3O6p4GPsCmiaKasTbJt2D+y21UTTKAOXMxqqqpq6VIbMhM6nhTJgV91V/5cFDwSquCGpJ1XeWdjhTo079eGQs7AbMhPpEM0oNVKxWVSokGh1zUG1OJFHO+BouYdnwZE6MKWCZkTDEH/XOa63elQXHNewNtw3eZjOST/PFzqE15siLy8+9Pwl5wiSrGtqcy24yMtDbvbtnc6+SKLjQeHW8wsYF3HDH+mfwvahWfT13uhuqPbHARtcHABFCLFV+2AucYtH5HCfSPg0sVm+8ec1x5FggZBS6voI6fWF2RVXSgNifmuv/0ZNERTIsq4D3OVL3+xRCpTLpvbP52V0hXXqrhAMQ4qu15dSVmBI5AI7MVyT5/xv7+y/vukaU4ZnwuRwn4hn5d1As6UWYtXwXrV3/8ukdDu1OBMnIX+eJdAD2VdSuCm1+OVPq69f7e4FqJG5kPElJgbGGzLYDUyQHqruJlZh6vo3RK1FGymt7PHYsbWoLS3rsaf082OnA+jOWmb/+bwqd7VSdI672R2EYdi150KDcyLkJqaMO2bdiMKjEC1EbLN/SYfNIigs/ljpTlHV2m+ZhJtS9HWsba2FjgiYLuJDsvCvAPH9dDquCbfjlVUCu/qbvnTPDhYcX8D/1vx3uf3vkuYSnHgwBe0gu5bhtftLM/75W/n1+yZ31ybrQIXW8RNlQ40yb4Isl5Xga37U0P3x4EFs4//p8b3ZWRgCtEskmt7S9DtgVJoS3eT78Y2t46GDVLZrxzefAcfEeiAV3QxzMHfDxN0vDWw3R0OYgB9g+F5tVpaAbp+/dwM4x3V8PMeWYikddFcklIfn893+h4P7+eGT6To7SbNDS5tClDqDoifApehFX1SCp3q3jof9Bc1iPFbPdJIBRvehUcINBn1AgKjRbdCPfjT/6fNjd/dO1OSZz1PV1U0xgBbYKPM3Ya4FQR1vmASNHxabfzx4MEV/vz2+mUyyARbMhoD0hrbOhoiotl0ihGimlcWndUjlAMTuQZl2LhVBiM53foq69kP7z1/U0MZPaKKVd9KCFYh/SfIq0TVhqkIQLN203o/eVWAGCUYBfeGfZfEVqL154YYXt4BjyO/fN812PYeUt9kalyRymKXWuSBSkqGr7n1F/N/9e4gGmmQD4GhBM4YamXEy7lW912B3A9QBMGYBWc7IGBOtMSZINgpWSbpZpJls1/9/39LPFMS3mUJNpKUklaeTMSYItXL0uWe/Pexy89Ho5hIBkmOsufucc19VV1Xjo5v4f9GNcSaWS10YKVeaNMCR85ks2vv9z37IEml/UCSn4ZypWz8TslLY7a7uqY0XskNhlJnZJdwn/9/3pvm1Pfe9RCJBVLFAVn0JlL7h9+JXG7/tBEjpy7dx1XbXPWax+rz3nHtH7977spXvZaKV+V4ihHyZCCETZDQyE5ggDPMlACqBYtWApfo9KEr6wdJXR7Ak/Q5+6wogK0IsliJEsqpNSW2lsW41EdXGuN2PXs2flY19Gz/WrHo/u9nsZ72Y3SyXvVrOYjn/v00/23sf3Ddr2SPvfm3IDkFJRfMGbA6xiy5F9TXvzhxbM6M9sfzJsICkkeS17O+/DAGgGqlOmTJlSmw6wKpNl5Nqef7/j286sw/cWU8H5a4BGvNP3y9Qln1BBwPNtATCMINEApJEPPi+bnw7E3zFt7Z23y7ixMITTQIMNLD0vFB41eZwKoP+4Up1d+blBjlu0giFPH9zY8Ai2FNrWOGq1qzZLLOmj9BIhDE0G8L3q0osCmHIdU94WKarShKoBM7Qkopuv6w/D3o+/yfa0FtSb/Tea2UgA4kYRZQIYqyqas1aq2oUDUHveH8O4Wb931Ckh4TnXYEWQkyIAyEEleItjSCBtL7avv8dwW3spuaDXOkP0i3FLOhBc8V5FQww6HZB8AIT3E77h3Hv/P47dChYJEORiEEyBMzQIUPBgh063FCwQ4QbDvx8TV3P8/7dcwDpf8GCgAUBURVVAQZRZVUBVhXgSgEDrPcMDAzW1y3++f68m3xhr7hiBGLECASCZBFkg6hAVCAqEBUV3aTiZIOfrvrnvXMfzbIPGNBgQIMGBg0MrMjAwIospYCVGBg4kkF9qcCJdECBzjI8udm3JLZPkf/vLokF+hRgGgLDUAZQWleQVlUsEDdxr/Qpr0Qz2KZVZSfakAoBSsVsIv75RyhLwskA2/z/f+4au5ue/TxlT8IA0YmiTsHCyM0KjFkzcOlO/N+AxIs0Qfk9UFbArxp5km5W0QGc0HcqMcqgr+LK1I9eBf0Mmq/see0jsOJUuMguhCcf+1qd+5ZW75op4YvuCvoZ9Jj94rYKMTJNQjj2TXYPHb6XqZCwVlMG3jzbrKbWNutghn285871s3z5OEWrdwShnykkrOX+OfRknEF8d4xTQF80BLuriePXfUin0EnlXaA1eNGKMosEvfhya6R7WCQf+EQ5EQ3MNg3OGqqvGBFgVow+kkJiUrJXErBuR/NebYx4hta6H71fm7wuNmRaqkFcDjw7WbcM+rWA0S74PWtIhrSuAjWS1gOShuV27spkClYmhyA8lTySmUw0X8s/qFJvh2M3unws/vt6xtZaOQP/oEXE/J84i/3KJFoQ3AbCIqDUPFRdnsdAEiVziRyt11wyQAXJ7R9XP+F0sBe/77vvAAz6v+xaH/5Hv+SNxO9bZp1ePsXufZsFjvMnxXIm68xedh66UB45jl0V/50kJJYLIrxsukPBKJP0UccI+Hi8ky4Sy6VEvpMEMzwVqcFOkvZUfJqws2SBI0oPdpIaFFmS8TZqzSnX/uQrppF6wVxIpPgGll58oeL8Bo6V9cLCy03UTpzbkNHXmy3QV4tc1T9ZCiLZmXZXnXDCeHz/HJrOAgoiHAh8er+prR5YLci9jZwN4Pll5pfJey1GLJZz29QwjO7X9XK32nGg5VlqGWZ4Tkb4nsHSWahv9wJWIkbO3jDD7N7GQuA4bLbAN4l4gzIQAIwAox3MXdKQoZbKJ7eDlIOaRa4nq6B6jaHvHGuTXxwLcg8BRtSrh64NUXLnKnYCqY98cj2RhpgDsY9cgek8crLgOlmQC1v2dvU7QFWf9fxVA4wAqi4uI2GIWRv55FSyAc0iGrGhCV4IxU6MDacoDn0z9mmBVtGcQCbjS324rqV3Vpr6JG1t7dIsicL2utKK6dWBdj/1ROr/BU/WfvNYQteeTtAMubmB9AWes7kx5Wzd+H7hDJGM/uEAg3+lVghwpxzaOCguWf14LKU0mN139UeteCV4LCwHg8YX5Z93VZdoS0R9FjFDo4Rceown4BlgktP4Gg4a01s4/UgXTW/pYmLP9pbA/yjVr/PgtCA0/cuX5F36sJST0SekTuAmIgsjTLxKPgialVgEOycyamJ6s8YnoRWdtV69MDjNedux5M3CNiP3yWRvs9gpo012zrbfpTh/UwrvDqwqjGG76nblzJ1z9CWFl8EU1e/1TM0SY4TPyj4BgYRg31sNKQJUQxRSLUQiBF5Fe8WoopLTehXXr+L6VV6/9Po5KZJkH6IphhRRqiWu7owgURdk4tDsCEWX5p55EQZjiPwOwhhx+vGO4FZsRxz/z3AE62wgzq9HUIv1/szhoq/5a+MvFnp7MF9u1VduvVVpulrK/Pc5qWhygvCM8Ic7yO3QEE8Jc6Ne/uF+GHfOlsD3Wrm8rq67uzV3JJvoS14x27UrCT0i72U8DR+9V8P5j3FVjcPIepDhTufUi+SJImciDRVUj1GU6rU8X3VA4TlCLTEsRz9Ym/3wDGpvontKrI72sDSE509JQ4ysoIBmq6VVp8pCQiIvKUWz0X1UkubO1WZMLiM9+/ldJRiHGxlFdNrt1uNpR58fluHK5CictBrz6QYzm1DJf97krboSsqsDRisIu1pvN5ViEtvYMhzUnw4PH1R4Qb1qbGR7uKZO9fV8ZPAGI3NSyCqmfF+N9ZIqktqqvUTgRbUI4JybOCKYanylLN1wGtzUeH1Bb+TMeCpczNVJgQb1lpnLE+BFPWB5OSHTnQry2bvh8gj15FDwg4T8WG/JR8TTbFryA4Sfq6GZOdreyAzmiW2qq+FHFP9lfbBG9TOuA3ijk8Wwxttdp7sgbAZF8mZ5iXa6bYl4mtEyKSNBw2oy7vWj1Vyk5V71b1v+JDWVXZNuY7VWjDb9fErQqikGyzh2U8WvRHrJEknolZ09n/ovBSZeA63IVPTWvh6AihgPYe5prt1emr5/VNIPqazqNds34ONK7vSK7Rd6sL5Y/LysFW5yUBT+cGQbT/ZcJn32REu6jbplO/eretn7Hek3l4jxuWQCZyIoYyYkUBpPwSJNbl7IfwvWJHisYH/QKK3OJ1PihvsMW/2zduAk4x6m5+WB2F81uUYtYb5FJr0Oyppsdsh6p7d8sTJeUZoTHf+VvdESxeZhMZZi2syxaUNKDckoRr3XlfAG78FSOjCaj+aR9DFCRWtZShIeriaBbhz80F1CmF7X8u5/WqGfTRbTJclVYXmpwdeOQDcW8cthOaSXBwjm1aRWfYklfy6JyYaEO41XWYWyDmVEOO6TWIpgsn7w+/YEn45AG1r242i0gPpZb82uxPv/2XYzR4CSezjvTj8efeNVMFFI82OfLxlBijcP0GVFgiKX2Az5ljJ0QUCJfzmokD4FRqPfUhBBMWe6svtfql2NpqTOlWdfGAxL8zFnqVb2M012IDs1qJSqxRyZUlkjpkazpFB//K9e0PinG/X9v+aBvJT9o3Pv/Or26er0brYKyhT2E/rJ0eFvi/r+rv5SolLjE+n7OZXOaynWoti4wJbqsXXrn6kMqsCu0TdfxXftlNBnkgoxpX4Ai96ThXmaVAliWBFQuk4VOWYZa0YLIRaZz30GMzTyUEWJSqmmdpbjay0bkMaEb4Mtn5BvjKHCNsFc1XsyFl2Z1aqQLmY2QXdH1a3ogmjT4uF0LgIw7dCdio1eonRogRUEaTEWB5mxGKWhVmntjOqEyBTxNDL9GBtV0WlISmRtXwSIM4yKEA9eJIo5LGpFYW1upPDpqMoJi/doIsY3yWIpfj7xTE+D5mshpnElIDRMlmJvBrqLw/oezdXReWyY5zoYT1YufC1fZEW9pi4oaeaQWr9yUdlkXkJlYSqd6FyNSdbJ4K4x6OeX7eCKlfrOaCOp/ElKPNorOPeF9TD9eTHztnMrGeUGiRuCgA/rOmQIlWGWt+6jFI+4yNmxq86Lc3hNyCFljGYYgI0agjCZ49uMhUcGNOgCayjSc7AM9g8tEwPb8rCSFG6UvBO71WJTnXSUYZhqmtn918A0qfhO1yZCwxq7pHgvKYVg5kB9D2pz47jHUKXa3rI8j7gr1Tqknb4d9QFOyjOfMaOs5vCWpe87VF6fZMaLgYmUC90bSyhk74+k6CfvF9lciO4PqUe/wV4MrLlVUrdLwDhE+4g8WPpZFIoyu5B4Z02Yt9MNASGzjaqtVkd4R0Z1slBr6hV2/WqSg5X6vVQJA8WtA0msjtnNJVZtFZZkB2NEEiQjpFVtpmxUP6j1JOcr9d90KxYDbONpJK9whCyjfTIUwbrWqdkslp2JGvlmmW8zd+adLsywpErFm3PJog7WtFt4Ptk1lIoLZzsikuZ6son7tbgdyVvvWOrLnuCSmbWvMc+forl/ABu88b8f1ja0GJN+NEfk44GiriweW8hmtxxxavWRu07+NtvozokuZTU8Y6agyUyWeuTlGy12I5TMxd02onzFUiSbfrQGN/PfOEBpaopGXTBH8or9y3iuKvQFJ2DsLsBCSdBj9dkllfqzeepgJAOOjAYLd6ZVhCPupRimrZ54LUyZNnk3czOh2NRFV+2kcGfjeKccL5OaKp/+diQvL/RHrg+QoRDC95wbJXsTQ4jqhixBJ98KXzzUUrOgEHPxBGtLIWGx6J8qpO5rNgGFHgL48pooA0J0P0t+RsEYhQpKpc9dhaVQVtAh2whXL6FxldoZxAlmBmOp6xsABasN84zlhhdrx56loBTp5PP/48pulvxmYFcl88AgCkO/HOu4AZOsPlMG1/z21oibCrILJFusUUMbt3A3ejzakQ6gFLM82ZE+RTIFTnkSVjS8hws0ykUp6qRHUJM+8zGUzEhdOuShPlMplKxd28o2Izs+iq+blcloK1/q4GcX1WALGHCFZTeFGu9F3Xn9hsubvkTbz2hwcutuGPMzh0Is/+DK1YAnOcWxM0CngtIuKUNkaTiC7ScqLOOU/fXsbmykufFG5ZDkCdDp9yQ3RnIzJGSTSZfihq0HBidy8hdWxe36095pwgPx2Vj/9N6U+IWT9tqODy7KAXWmK/fslktPJhSXtNe3AqXvQ8Z+N38yuGoBPQuzRDR9iMa1cizoLTJ6XMuqr5xOm0ONYpYA7yxcw0mPXRpekmZp8bPlVFkH89jtjlsXmEM2MTz7urFF6RbevmqUHArVlUxu4YsPwBpL+/tGqyZOET/FvD1iUCeD0Zf6mLAh5fN1WWyQtmOI9gxhoAP26VNNwU3xe1H9NszbSnefpJemacA5psg5ACs6AEVBdXU5AWbg58VMv1vgCebXbCnxdLxWDjcf2UH4pcass5pKqKALi0iL830ZllHFePsL15XEujuJwk8aZxY7szFY3ONwI28H+S9PIH+wIvWROFKOmNB+ZCSD9tmUzCXmFSt4YRvEOdjWQvDdNkuUb832W+r/Z/swaHYudI6WbYXqjWPQhyl673Lie+/jZCtUtwk0OLJ7Pi0jTH57E1voliImhSjyG8vRC2hMfFpeXKJ3m1Y5uWbduvgM0ZPQdMBCrMzQaX7EEdm60AU+taUWGukV+hExLgUCBfnF64fBhI5y3lKNHIk4xZt2zOV5Sh75aHgKlZaTrTrDf/MDEfKpq7ueGzHCiG7tVFfUVWasaCFDWNc6L2JcOLmaamawVKST7ILiJ4M5UCxOtlr1PS7o5MOnE12lS6E+zyliVkHAwXSzoLpjR18dto+Fj9dhdeb1XXyp9zjWZrHfJN69NCajWaDTqUW3KRP1te2PgGjrtOusMxeh4l61ZZWrLsbirsl/Muq+rL+fazFc4sFxpDI/f5LaDOAL4CEZTLEFkD0PmMLT1UYqZtxOyBp5dknE4pJE3QEqFubBFHLzfXTs0iVyJkxjOLRJtU/y9ECtBLj4Ci/QNnrn6GVjWSep5rBvUHzInC5hPN6fHugOhbU2bv/7ElawutTG+03PFTU/acQUY9aL0sf/PxR9TGPtufInh8t1Yu4HM/7lLT+qXIPXjwvomTro+jORQ4mU5iHNsCxgzBlXl4t+3GLxh7nW3LCGbt66lrugm9twA3ooI3aiY0qan1nl2tXGJXlw5SbQq0cemg/SWuXIRM41cz3zWZ+/iJfppWI6jFlagPF/EjIRbMQfHwCVacXu2zjGDs/NOxoiF3AL1h6uupVGGsNyP0HCpj/qpqC8HK5ag/moKN9g4KoA+A8XEyFMokQmysfSEJdURKvWjc5YccPQd0mTUJtHKJHqjMxs293a3IEDuNs/a21eVRsHlznTC7z84L/xXr4cfjrpzsYv6TI1Ppcs3/offPI3Iw7ooyfXmVfehwbRVTjLhov3nnN2azUkzeW+s77dt+UOPb1Y48fpNlc/3O/COx25vYfahSBbb+4NlO/BmlSP6vO46QxfLrhIhtPv8IJhHw6z7JDd/IhgcBcJ2XHY4j46PILdJtjw28/DF64RuMqfIjNtvunMGMujhA9G6BDBaT7ojNyVkLkbGEMag6jCXsM7dL6X1NMLfVMATNZobijsTo0S/jFPZypmmy9uy1tPism4N2rNih8tUNK34z0zCx57iAguMmFwvpTzYxNdB2UZyyZh1/y57PAeSLV1MMg2qQzcL3unqMxUGW6f/2uvX8TyC78KZ+ZHWabSR+hDsKyoOqXSe+iP9r9fBKAsYnmVhhTuNNFCZvJQr3VK7hUpGuMQkphh7apDJN+Nh225pggazJwxcDbZkQ2Jr/HpPiXzQLIX2hV/OKpjEhzPYcIoRZMr2/vGx0mPGUes62mv6VRxHr2aP/ZQv0uiO2laHzDvfl80ceQO18GiXV0PeHIOvwu1ssWdeHrSLWad2fHNeFu8/OGKxrkdX5v7LH/fHgPXwQbpHX3qeyL9Z+SI21/DjWpumVyMCcqkqpOyHKYgi/kq6lqGYHe6o7WAUHKnQ1b4DBRaT0aN6vRtSjUbNCYuCiTV+X4mLsQZUvHQ/Gur9QTZ+bKZTtLtKTMeNSuMpB4jELTcWimnR9jNJwN4h/jAvZ/gXvTUwFRpJaHyo1xPTEaozShJXF6ocIV8j3/+YB81zOUdtbmT72Zf58nEN27WEyI6VgfnTMYNJFfHi2s8+Saq0rxFfxjMlLWsfveuzD8HZK2dEGNIFYODEvoJYtcl3BxANyQSNG0qgsm1GYM7w2MFGrXhH9xB+GJ1zPloO+Ct32tW/Za1WuhVPA63WhtLzmw10I6QNhUAjZntgPKfFmmdURTZ7UoAeyWBw0Y6XP9SWfSshHDM+xmDfWgAZg2cg51uyLSF6GDkDb8kuN2L0TE+m9pYfs48IM4yKbsC0CAv886A9X+m9lu382jsz5tpPSfhRp60F1+Vq65UZM4qkUatyWO+lpv/mjL5SZtFkDJPdDcEjcHDMd+vrobLqVaEk/XUMFoz6UVtGgkHqNJmMkf+Kd2h1CMzVQ8F7EfBxrQJYjUeB1FgenkyX8VpA5XZJUfKytonVKWVMTsJjY0AduI3gGS5cA6ya2NZsnqqHo111YTRLNxmo+CIj7S3r9OcXrTWOFZJr6B7TO5qLmIIzq0mlJ4DJHM6AGaV0eSvnZYxDCGoqENW6m/Ervu2ktAzV+A6mLWVE8EUjylqZZbSYinNuW0IuIq5L1wBYqr4Bgh27AoWvd3wrbaIdanf0tE73tx3mhDpul1aS6Km7+8OfUyNAo5lwyZEu6z33BGd6tYOfPvVugKsC7lyByPf4iF2xEdM+yJia2shELvLM4FIpxHBndiyH/s65AHPu2/GNTbCdelVRCvucl5yufVMvpx76fvpyL4qS4x5GDd/Dkf7OphM6oBsbtH4GSD0k/9meDeaG8qkA2tYCBBf8CnzXNIhqn/K8zcrL250wlnsvyDCU6/zH4yn40eb/3zrMv+lfvcZb4ax0+PpMEUGt5y6XPt9TdAF2on3UEJkxgTvS/vIUi+JIyJ1040pk+SU70ddIq5/LMGZR9yY2+B6QWPS3+OyEDlZ6dWyIg0szYXO8pnZ96klJ6fxz0tqWEbkbY7qH77V10mTb1wGbiynEk2mH031lut5qDe9/4+LO1bJPbnJcOgTm0NLM9guqOvbxkcY3/UBHFevgJ16Xm3Q6BrOkrvUUXIwCxHKZ7ud1MgekWx7SDjrVN2FzRNuODJ5WblfaohNY7NzbQ3kxHsy+1vvJ/GDYyl+1eER4Jg1rHOQYtHnxf+CwL0frCtbnI4e7Ymypv/YJ+yTIROauCSfraOexFuBmi2c5I4EfjDc6TjmxdOpdgkshazMUqO0wNMY669XXb+BnENz1tK+cTr+5yyEcL5HTePILzNz0Y7qnalqdcZPFSZCi24Bk3bE8J8hoNl+rsg3RlS667EIyWq/GrjoS6cFkpM3qAXoCpvcsgzZHpFmJVA8PTFCe6YkjHKGp5VRX56O7csL/R39wuQ0+HC9e8Pei8nlo7Zt7/wDjt3jvd6HXBlb4V0+vcEH5I9BtWS6q5R4Zm+/6hba0yPqZrKtvoqP7EeRfCVQU6s2GB03A8MbAx6hNNKujsCSTDOaaHtZJeaSnB0mRt8usUn/+K8XtInlMmJ5nW6cBN2vjDAKdYFFzNA42z15P3pAP/dTAWr7gftpZ1ZzPLW3BRZ9f/8E6loAwH90gvqqD1VsAZJaFCDdfnVnPW90xqA6j7e/bmNwL51a1jlVr6LvC8/zScudCJx9xYs6/NoeQwl0jyLsMjh+SHxuKJfOhTALDoVpJoU74SriZp2WkRvVVhRbUEdpDcs441j6UayT0A1Vbx0Ql45CLZqHx5+Ojq12CmgzO0F4iyzwzTFpksjHsTHS2E2Jim6fDR/+B1P1A//UkRSO7t2L5lQHxfsPmmnXvg15Drfa8GoPFofoudbjbS8qc2MrjYEK97cgmUP1vKsnqWdLBnitCA222lgEOBzQ4gIFlWhFVBKX/1gx83qhck/h04kD5wv7avkM/ssRFz8D/dbcjxfqtYpawZON9m+PQNDBLin2dqy+dt5wk9JVUxhHul2hQVjAfCDyj2s0pcPk1NYRc9gSRb22HEFrq8YbCqvAowzBk068qkCbrGjl9h0Rl4IBF6+Qqbxzt729FoVjHG2n2WB913QwxZVDbUHDL4udmkVd3pv4ulQPbzg86/OYCj/fbEoDF7+FfKG7j+/Ki/2SrpOmMuH6CaActgJB/ykTh0TZ6+pf2W1OGVpBxKTnLpgIX15CKBy04Le6loLeFiEgrEMpE7e1Xmolc7q3sTbmg269GuayrDc8joe32gBjFT3H615eDIJlpCmUjYbmKfo0jgmdWm1r82VPZgmG2ReXv/KQcAuGNHUm88C3Pv5BMuEvFuux7xeo/e+cpNW1I4pB1u1D+SwgVUY8qdyqHGAg4nFkehkJ/3NyYNfrtpbTUP3UKcn+cJbNR8UUnF9t3PCfJzdR6KXceXCi7Sj9vqr3gKvSuFsd7ij78iCxu00Sb6NF7FEUBcK1otcepQNV7/yHVtVjkhTRICJVtGf+SRwJdZq1lSOilCOeSG97GP1dRESQsu7ADfkZ98SjFiDmn7o9JGatahUAxbhlwPnV/1B9QW08zPmVs4DClelXyvPI5poWYn3VLlcv1ZlcBOs13T8o1Cf+tm880oF78JiWeLrW3gb2K47PNHy0Z9t6xw6/YlA7SXsl363hPZ5kiCnZbBYvrcjr62vxcFFsMGy7Yf4yF9/o6gld47D4eohGPrrwNf1W1yXLCWIZdaKY2choRtUOlh0M50fVUz2Gpwq5Zlyl7b1yA/tI3+RVFS8j9JpSK7zjx68/HuRC7Xha3JCSArbuKIOGh2HPvlAoslZBNXYaD2ljFd/yru54d+fvHWauX4+AYmj395EZk+WoUaNwZtq+eif2Vjy5Hb1LcE993DIom3DOzTE0Lf/RHtracp8M/RGMuxYho79QOyPv4Ibt6rg63TeDu+5IH1/X8lgoxjfbuwu4eLcVJYbyUih30sil3LaAKplTHYF4H/m3IHyvGHd95X5xfBF6AQkOMx8hlsJU3uWaeoHmti43WX3PsPLtAju0KnxQ0fudxNPH8fDWlmVwymTDHH+A7m7RV1IspWemOdvtiLnxf2LpBvTLYLzWL+OV8/H0SierBsPjwkw5Ao0+c4AHscZa6xNOmUp12fHO8OUpY3oOnjSHk0KdF8MnefMSe/BUUs98lHGIHk8TQQHL48jHH11FehWC/JZjb/OvMOLdz66yN4rL1EVYqx7JlUXLRIxPHeNkciY5SkdHIOFIGUZrh7GdG6TAtqw1Wpe8+85sAcURVWW1gz2NSwnFAmBVG4Ig5P7yHiFJm2VUy1kIIvs4pJKJBR+3cBaP1mBrSC6AsCYuCFg25N41dmXxAYiQ90FOQiaAZ0YIiogcffHaL0v6rzfjv9XA9CxGEZOJUmZFHvu3mZ0jZYY4x6FkuDOvarQPArw3mz4WwLyursIRNnQFdquoc43o2K8ByPRWQso8iQ9pYQsIB8VHeYjmPtUKF2XLfBsRU4wDVxKCS4V1fvfvWsc2ocpLDN89ceNGQrtg9avAHzHQnhd9vhBybyXGDujUZU5sg0f6qgwitlKMvRhJe3s7W68oaKrkIKBvbtfjoKHZkhgDvfVGHTNhqpA/59fHirGuFRnzRD6gemZGx4kx5xxJmfEMll1mTWlFBsrpxgwUChrKjX5ZcsROeYhSnpP414WVYaxZRm4tnlGtj42hx8XQY2WIbrpT7B91AzlUMjNGJ4lhxFnWiFcNXk2CDOYajsiRkVmI8KwMEfx1fjieTtXuT32rwbCIgKT8cipvnECePeJdg1eXIIO51jOyZGo2YgXt1yQKbh9+2eCafJp7bB4AfBYDuhMbpWnoyUZ3h24e4sg5L6RuoYn/5eWtu9Uqn6vZUKwqT8SXqwHYs2SjNs0H1UWTq3dpezgDMiMEbYUP6V92w6qyJhCNY8T+sl5XyIiZvAAR8jadA6DpQ/n0yyH+wGOIAkyOArJEs21uVaLI1hstjngPIFoH4Ddd4I2PfyxTizjyZIaTknD1dSJVIOhC8Wa+Ja9/Aoauz9D1GeKBukkTPMK2AbeNTCEyK/DPa1YJCNsG3K/YJzI1MO180vmc81nnoWO5vHJncyRQbJGIsDcZYDs0iwPAWyBIL6LIJ7z/0ixnEu2zg82n02XWdk55kkR0q2l44IAv8THRwV7TXDoyliuWDujgCdzuD8GJ/GITjDnP90XUdsbS8T0jGhALt1FM/3nS8ktJD6Ihn6Eo4GBQccJdrC3oX+z9n/FSB1sfeSGobRhCNc9sQaZXgh24UTA2WSZOBsRSYzaigJ8LmrFLZObxBOKzJBCdK4HdOFtb3QW2YQC4uQHQjANrZ/hjsK3V5NpthaocmbkkjSObSE6wLtEwIWatQ1zkIW9MDhGswXOBRzgaRZ7nJNT+I5EsxSNlFaXnQPubaAGpvv2B4sl5Dfz9L4JnTF7qZ+F/88czg1n7nItwIWDMYvE4c8h1qxm8/oHI0Q2Qs5i3hmcioc1TdjfvYHwTNX1LmPoGnbIhN97kvhC4F8p6/aoleOiJ2r2s4mW8N4dfHni+Q4rWRwUbkC0s+JjloGdQ2UWk4rsxQLcJHYHe/cnk/KO2QQz4zaj92Sl4nsa6o+2vL2VUvO8BUXQE9puNSv68faMOIqIWk0VZbLUSnON5lph2G5YjVVvgc+t3fqELeyQXOy2+wmvHhE4g/anZ04/XiilOlXZuqxSwR1BhcQvWKVvSnNbxhji2kEOmUM3gqppsmGdSMZuPrRCfkSqK1Y5G0OK5yoiH1m11T+3bjJmN3tzYVfHNiiU3rP+N+Scti9iOfbZX4fxcf9GI+KWLfW2yu2KSJDwJL+QB8WG7IubdHvmLRvGOsLoZKdsg+pgrLMjvQLFsKxyH83AeRrgans2uyloVJncUvzi5pJzw/yZH91tGY2ncFWZFzuJy8iIJoAlJfcCAGU/WPZtw8CCx9IgRdrylbbcKo1I2A/62um5HpSVGQkGeFrpOtK+Y+S32OxQs0SChEQDhssDWNJzoz9jsCZC0ayMhC+ALpH/hWvvoSlYYMOBSNpKqFHL6F0p967f1GQUDMTbdRc5WNi5Uyt36Py/OlQbDrINyiIFxPdcaGS/I+tZ6GmMqJFHVBe1a7/VlX5P5QF6OAx3VMdFWDxQBipIbbPa85jYotfibFmjbReaF0VFS08bQ3LcHlKnCDts3mV9FyA/VbSilPHSxEVFHKbE/NiK/YC1bay5fHk7qpOsv9UjDVsDR2XR2ImLqF6bGokvt3VrU23eIoVtl6E/H0G0xdH2G6s7A/sFs0CKR+V1X38EY5H3AL/wI1orEpZG283JkqWoaDidmv7IGvUjIYSJ9x2iy4Qd+ZYxgrZiMGuo7XeesVc3G43ztTwxyJS5hQV1Je7I1XZt9B2+CCBxsex2+/bIH9VIC3FST0Tl7WjGYncohTQQWiZxO5sbCuXyvk/HRXrCUwjvKYVmuu6jSH6bdEe5BxAEF9IzSHLRdsb4r/nIKz8M6DAb1mR7/OfA+T50+82YwYeRv8WifsnnR/jUpw+DPHAXWyVG938Pf6w0i/qnidWwvSeSjPFAZ9pSYFTQoVkHkwaCtxmEOzVxVbe7DKn4l3jpDV2To+gz1t8P41XcmVlkZup5ttYksVbXV33Kz/kraSX4LAGhF0N9RRJ4shbXT+Tbna57POa+eWw5P+VuYah9PBtZZPZh9QAtJAhfvjgNbfrWDZiUJHsJLj/+hzBR/QhBNG9FgeG5x9Hcv6WRAsY5Qnn03+0/FctcHNXenGNNLrr4raDHhtBkEbipiKl0uPwzg26bxCaXHu2ub36kgx3Ws9eXw7rFFwUBOl7ZjuPEF/NDAU0AIja/HPYnTXQpHAkoqSOgBd2RYOslEpvtHVvuZ19Wp/Tj3Phf+cQNIte6GOg27H65W9SnjSG2wZetcP3ld+eol7uej+CQuGx29lnMQWTEBKtXEjo544NEBgKoKDNssfVq/gCI4iU+T/8QtnDlbVU0ZtnK6FUZyoGLQTHt7KBNmAJQRFDS80zZRQAR+Rv3w9xclEIGaC2O/0Xq0mEO8rTpypE8zrMclR/ug03hESV11QlT6CbOE6CN59AxyrnkFW9aL/OBV1qq1nT2VUPKUwzYxolaApmGnZW2dlC+mpGuEFbrpOuhWs7QDSdHjZm8ZS3N9EQO5wWzr/XCIprpWZeAhurpZWdy+MsYNg3I4HnIvxXn6kqGfEIttqCmlGmZf+p36iK1WrF38VB8k50e3Zm+3VXQMlZWdxOCOOV9Rn+5yc9HHtT3PzRuhMn774wxSZDuGG5PwxubzFVmsxo3BTfnPL9M3dEhtkmL2uuhdqC24G8CLKHjmrHIe5tLLl9JrjGPZG2ag6783WPnopYYeGE1XVgDwBRHWoAVDN/9+YrPaJ4u7rufZWW7Y8Gdia582buq+uf1hUeJAkuwCFwwpjRq2cMHtHM7zusAbx/dIQW+rAjaSgd/26kDXmzOO84Z67iZFF9zO4XyQ9sreKLcCn51DaNz810wT3gQJtz2cF0ZnkJtx/Gc/p3Tz+r8nYPqx/Muz9tulbO93I0OzZEbDFX54jhD0U9/EApKI+Dc1d/lSWnRjwW/KKqRuxRExKX5lcr9oVSIx1K71y3qs4NdoVT3leYUmSbLB4Se1ADCWr7u2PEq/LDCGnj8ByGQpo4QON9lmjRamflEYj0eyNlCFD6afu+l5iV3Ncw1ZB0p9qaYMz1HiyM1X+2vzmNx8PgTi+JU44rNANz3oV+lj4kvkzN/P4ttbPJUIDkqpxsEFvogcAHFkxOadgNoBrFaLfppGSRlWLSBJmg3xTcck+I2jQNtiGMoExKt3ZNSIDT/59+P7ja/1+NkC7aMA/OaR05htGsgRUJtk4H+kCbqDYw7I+6fXEmOyd6ej+5pa7PEn2XNDhY70a/Wvd+n+Vhc4GcU4E9+k9/zptA8tRXbG4wiZY4HPgXtkkMYaJRwo3I928ZdM8zEw+xcJOJteLMhwR7yRcROvBv1SMXgzsD0MJ0ZWjHCstXR5OZmdB/U1rhs2NVOPRRtJK2r/E4fWr6S2JxTFiCGye7kEq6uWTZYjEm6bowGh9fQN/xgho7P4wHMSBxTI4HoSSdvogOIxVrsPln1gJ/b1VUnSC6kzyjJU3r7/lPh2U3yS55Sng3Hkbr4mSOnUpwV432NjqF+YIWS/kAAQ5TT1fOEL4KBXR31HQ3tLlmymrxwHUhBS+pKxFxrQ+mN1Qu5rOIFfz+Lme7bxz9pzqnrK3jdUPifBQf1eDHfpeLhBcnY+d/GCemyLcnxFL4w5XFNkzcqj4PgD9CGRFUuMVLisSxkW3DpWfsNzr6JxDUyJxUbZ9BRkbiwJdprNq2swNQlfz7ISKwiFySF9ktPqbU1K1L8aGEkwWveC5K8KAbllcIgYUzE8/qf8etOU/cUHOikAuaZ3L39RCgoefUbEOm+OY7P8OlWE/fFIpjOhOcyttbYOAnaIWDU046Gk9lmbprP4/O3kwLbWGiUYmfsPX/WrD0S2gqzr00TImNxGtU+ncaMMXYmh22PoVhh6GUP9Y7f8MU1o8djgwRaDEDHWf8SwP0Yye2nP2oEAJWPOU8/8QJP2A/1CmSDWOzHNf5aTcAHiLQvVP53NXyOKxBavGoOPrFr8S38UwuflmRAj59QnaNtVCVKbQWDCqMnQpPj94/mblhJ5f/MCSjkgwb5/PX8zo8n4mhrgLgro8uen9Yzc/WSO57BXgnpZqXHPxTaJnbWp+J5RzNt3b0rWTh8grR98pCP5jW/ZB9DJ3j3Keir33Pn1JqvK7KxyCXXTP/alcBHd2ls/vzqfH8rvZ3R4VMvZ2l4wwexWl6a/xTN2Go9JxbuU4M+mxHL/pul0SbYnhFNJnTiIJ6D5QgK6Km1dh2ZWn7pm9qwtb5BGktaQ0e8YIIfPU3iJwI460OzF7MKfmrZ2S/jW8xvexbSnro+EFtzc+yRHQPpfyFTQM8UijJ5FIz6n+06USjH4Odhet3gwaEket+xkI01JiOXhjTh6gYncgWh84fmgqCFWJ/hdPBxDq9jD5LdPwAQPWrUr3ffIuvrlYqrSHboyBswh9jvyNc/0M/bN+o56YEe+UVs/7/gtbv7qJDxbaOCNLUf+8wnl6zkMxLn3xGuEERsYNSTBytxYv6oXJxgfmr6XVjlhM8H7FqN9ABMkQ8zNCdyHFu8Va6qT0pAZsvAgahoadcozdkXvK76dTuRXKnzyMOzxwpBIiyRgTLLvMZ4ynfc4b9+BJn1J5te8j8m7F0j3CkJxQEFe+lBsyoL4/WkJSfZjzSoamjrDAGHvHXqDgltOrj5g3eZ/vXb/gjUag6O193vr969gNvmwL2ZqokCUUzb4yvctcNboOW/A5mT60seDXBtWEHCxchQ0Z0eBY9koqC0VBRFDtH3SvN8UUqL+ogeJlvvjyLIKAHIrIMYceD3h+/XX4MBfnJEegSTbP07fX9RFSja9k0A/z7SC2rES3DzXdXW6Zbo9ugpZHoE/QDdU9t+Onf4P7pytBQU/rvZ9ZZr1TpRXg52+c7YWFPx+jPO6S7SLqyWvplytdrXGVfHKt+hCv8dBPySrLrAHa9E+EPO9oyFvSO4GdYmWKcRJvupV8/wP/pjbcIE9+Is2AjDMCeGK21ValvDXduSAZ14Jf6iXHfbf5PaBTEKzP5+EP8xLNpluIvmWV2OY71Oc0rRXCe+Fxm0D8bUARcMZCdZT7FCXlTvqh0CjC8HKMhDImyHg1ZBpxn5/CFZo+8MxtwkAHGtO+O+/p4qGKVQj4dqd9RcX8A8zKDm0uJrOsI1Wyr0ycPQ/hM3PyUhntxtvny/ipbb8rgd/7IpvhfgcS3+atsACVVRBZUicOXOAFmKxPE+Fn7uwY1UOa4bIBDbpBTcyAPqLX3iA3I6O06+kaJUQCOSZcFZIYmrwJf2JrPbFolXySWDxB9tdElPwpXkhbMSk4/EGbg7nWyskFmYmv9h5jclts/3dD3tqfUq0eWB+zGkAAu9bGy2SWyVxydO0q6t7FU0XEpjuQ+eQjIUTPWi04Hecfq+6ZKHzLENbot0tv8/PhfC9Rfc2D9PgfraALtxoMA6HHn0gWA5hvyiFy46jM3LZk97bzOV4wE3ZLDXNET+/CJuNUVkpMVSMWyY/dfvItGXssbYRyW6PRyfKIl8wDVUukJG9OeMQ0tpYdoNPiezI4k5T1S6AfEqoCXGX2Mw+WRJz19iGi2lZ509gqODtNaElKPMtzLCrZIybi3eojIIOw3gtpIsdliXzzZzXXspTR/LFK+mhk7oDXghllfNt1nszD6nYMvw9ZSKf4aun08cz7+SqpRn23EuFIRvvq87fa3KVztbaDjObi7FEMheIUb8cNxiDofaWj8osjUFqesSw06K2wBOOOuc0dpKZu6QWl6HaZ0U9PWDDF5ThDXaTkrCJjbOfarMG1d9D8hZpXWQP6Mva3pkWJoFvTUS8N7ctagRaluCmAPYj7HlbIiGkfJtLHfDHDGBrdm/hIjCX4ONLvX+Sg66UcGnu76jdyXaR7TjkeWdwgLu7ZYIa9e64sqrcYxG9Elskg4PnqVsTwqUVBrNKCSmY+muDZMIy8NR3et7InDUqtwao6uJettTJGwOP6kHYnWpRXakt1ufTQIfFw7U8tI1kJn7NeG/vz/LSRFtwK8HGp5IH2aGSiHdWjElnL44rNQ4cyxbfCtqLdzQfM1xZV+XUFY2JYL9xzz17mbEYs95rINyUttm1FIZZp+rRhMWrlJ+Sxb8+rAaJLHYLvydyg9gKs9Hu1awlKjhyIkyhlyoFiIs2zLe1nBLgRLCA0XD3T3UWXtD1N++iCEpheDwgpCuKl+Pug2117wA9yXTzd2ZJeeLZF/26aFEZv0svCmCKWarMlVR+pBAr8NVw4sh53JENGOJ+PUqDleEYCfThyNxvVcgk9kcNty6kbzMNF+h0pawwF3Ou198d6WzIqpqTTQuVJmKnTSB3fW3l+Furo/le/fcBLJLhG19focW7V7vcRz7+OYcd8h/nL9Xb2b3WrhwuUNFEt/3ZmzI5nP1/fw4nRN7k8m4C71tR0/rVJr0+IenVCVpWsHZWinCWmHnL00LJ/7ARAD1uMs9w7f9iZDqI+zqC+kTiUI6J6aE9I+aDo2emVy4LhkyCoGZRfvWHklj3ttL2lZQ+ZM2q+EXsJ+ZHUEoajCdFo/vKPDuy40BqdUkRU8b8QG1iKvLFD/TInlHPtHrc9Q9kwJRjfWDPuql4shpuLhVM26zVMMCSx8lJ5bajkmRk2GOQ5Dayk7T7oFLJRNsu7KkEzMEMpIWIKjSTC6KeaSI0YxEQ+K3EQMaQ5AwL55Gg7SxbcTiPiCTu88iVAgPl+ljQsFFVkMQeIXrbfTVe6AhE24SatZTUhttlW7SUwV8g5k7IYE4W5VVgUmbbL/HzYfmutdtj5AG75yBzgpyNdCTXLjJqkg52YnjhePLkUzFUKJKat1Et1jJv0Ql7vOypGboMgBQpU2vEWVP7zZaVIkHXVkqbz4lHeYiKxoylVwvvB58JjPUES67oiI5VvN709pzRWuIrL5cca/9FePN3Fzij9/XUfAYg0k4AF0I/5VlbH6pEkCx3Ylz6LUs8P2Pbu/OF+AOuP2eIjYNNu1b2zWBLvhDNX2XzUHeHA3jF0U8FYpoCk5+S9OqfCQJyYeJYbfzvk1XZc8X8Kvf5NIsPzmdyhuxUWduFzisXiaOrRfGh9fafWpJskylmOmE8kTAn/5qP95Kc0djX8XudgVYh6Uw3mlLfHIWNnK6H6O57nfWnK6NPEuUBuawv9vsmzvJ63WPcxCGKdKQKq23tz4FV5Zw0LvSk/f3nc0TdjniRRxXeSsVDp8g240xqVkjf8Vl3H+XFMRxiTlmqEasItvPIOep4NE7ZDqHXWP98tQP9FXVW+3BkA7Yn4nqY2nItkufOT1xWgw8bdzxg/Ekt7rMl3Z+2h70te/t5V39oX6zOj6YX1L4+efGoifd3X1l16TRbm1b/EkA/aVr4minKKqRbCLj8EJN92iD9cqNu9SVCIp5PsCCdTlAob+mQXqkmQ8fdsu4ZMeTDC/DfobewJvwpeHOUfJ8fx/NnIoL/Yr4HrC/uJvKBVwlx/6uENPv8HXoOw6GEFvt55hQCvCgBMoVLeE2E/PrYO9ZeUqMAMbrML45/xKCArkDIoLJXRxrUg0dujaqgs/Yq3VbH9ZDYYOB8RBDqHpTNAdDg+RzAncq1Zn6tJx0zQKZ4dKZARPPObk92qVR4koSPhLRWmtED27+EpMNLG+ZtAbOs8rOOStDcPx10/RqiMzkQrS+JCGvz/ROR9De4sI2flUpM9KXazpLWaTudkwPtcPWoY5LUc3r5Z6fSD1yIjqiaaXzdoZwMeNQfm/mrKi/qRNz0HtXbcoN/TKt1GRhmvpkdB5q5GGg0Z2c8fteiRFWyFTEU4mitcJAYpVNqH3kwVVsfkRZPnNN2EJhT3bopfodXbw8VpxPaxlJP8Ch20lhkXsvhQ+W5n3/FvJZ796eo+NicnVuzT0dO4FC937zlW3/AYrfUTd58OxwdzREdb8wdFH1Xa5GoHT8Ni1e27+whq9pjgPuJTowpg3Jdu9HP+G6H+d01ToKdFJ/DGbBBi/CVFHA4A/bWxYGbQHt6A19NiU5oBlkUrcnaWMOMKbfvY/oLNkRlI/ywf2aBBdLjAC1uGwDSPMD2PRp7EHch0k/u+Xjyg912+ci7QO3g0wjWJoFNWjKft6Yx99Z9N0nTeF1nAalOKT11meQgqcHhI+FsJPpEaVdrOXUREgufgyCh8zXu9zkY5PYqB4gQ00sUIfw/dFLMJ11TJBQ0ok+G/CM9wvPQoJJUGAJQbifP5hLh5iqhwfozrqGzYg1Jz+5PZkc7pp9CIU15zyOP5h+BdrKpyfEehXLtYQPh2raD245vaGOW/FNbdNBwjYeWYY4/WhlWfN3snBeIB8tZZjPM+GR1nPnpJnaD2Tzz9XgxBh8IKmPfbLFEgxnttvCMBDLPxHUaInDNYNloWxGqOWZ7+fsj1HPLIa2tykpphjs4VGAO8RpaPoGViY2UQXEhh6/7cyQCLg60wPYuGCY2RF8eJvh0GRbeytZECzlF+uABX/DQm+zrgvJ2ub9k6yzeWhhivfu2IPr2C6b5+snPvxzdFv1n808pgJ3++rUg8TmIoqv+MeExyvOGUxEo9NkDODc27StuWUc7SkMAWJ+ImKTh6R7ak38Wd8cuPLomQYwFUrvsq3euqZrOJY0m1oF32Gq8pL5bHQDbdOuVrF/BJEQHQ2/CdevyNZRyMl7eUt7muyuRVxx9FeKyn3VTlX+cWtdaUGoztK6mEjWp16qKKhGiZx2RY5DWXQYVn+YGhYnrZzXpi5e5sZLwyzt7lyt7+QiYCH1OZp717kGVVCbDetDJPKxXBn46d3vuzenVMeDyMtfad2XBcXRPl3FC2Fdg4dXIYw1M8HuISJvqnfuID2vdYTyuyOZf4N7QLkycWHeJ/Q0Q4k3nQxlG9YuyvBtmAnDt7Y8T9aiPNThmwXeIQ52QthT/At+/cvSvHHysrc3uNQc7btH2M8tGrK3vjMOyGCGtWYWai3u6wb5NsNWaK6Iqmh91Ck6OTshnGqEe+thB/TKb09ibhDhzo1/42rHkWhE5tYnqkzUCYzjYFYaAReD+GtMR0qC7Ctw3zZJFYHqh6wMjMeRUw7zhQHOoowBriPxRzOlk0zSEza+7ronvs/RUKCEeoVYQwGNqGF9CyFSRKCROqvDRBLWBTaYNkBN9neK2j4yTgtr4WjAocLGxthMKIusoIZ15SPiUhtRG13oVBwB4VgBqA26mMIi4epNxNHyOzqMluwwsYoQ+q/jTgkc9ygFp+c8opLoeMSINLRfw9kVBTrPyBrsptj0O7M+aKZbvoO0PALvznpZWvcAQfVP31MLpVxrwrkjaThKLMQDedPX0+6eqPeMIjTq+xbmdZaXK6nuFG3BHuLzkN9a8YdnADFZEgEMqdaqdZI4heLvVM9itbu8HlpQVdzI/i2ReRWpK/IwAPRi93kQpcsE9IhMM6/dnf9fkwAp4JfloD6yY1REiyxuwN6HCbkz/UbTYpjuTa1xE7/vIQ8ATt2niK5SZr55vuYtO02oMRM0+mGyU66cyOMZ6s3MxsHMiuIx9w+036fbUL2knzVapvkTnklljy1HagPgCx6olwmRNhfpKQ8xMnNbDW4O78v07hc0cZZ4w5ATIzHIwrDZUjQu/cBPN5WsUoFRI+QSRumnygwTSbcvNFjfnXyodRJetQUOeJ1bqD9OG52o4HVE4OCjeWiNzv8xfgi0vVuHean7TsFcg0q3WG36GFXtndWRMTtYlwf2fZ/nK+8STlRZEtr++PDDFZemk7EQCaG1WejhKniOQ1m0/f9meCqq54hy50v4mo5B/WrNv4t9sCbT5taxrtiywuSogUdD6ZVoAMSKubZBp5QKx2Knd/z8McAhoeFofKmX4k4uVNODW6ghKzc7Rz2FOCZ+M58LmyHZz4hkLX/AuIeZ+fsaUfc4nhVg596KznqWCKgoOmNtCjiJ8sIUPOaynz/NfPm9T4XqiJ2n7dafiuWkkFbAQqaUEQNXUAF6mBjnbZSG0Gk08+zkqzD2Hwhe4nLJZ96qH2YQBgAiFD62w31hhkhSCAaLLEIyul3f+5sXJcsdV5JqTKUDDb1yk4Fo+yHw3dIdSTerK674DrnLqvzRQ4gOgc6YUCtQqG1FyzovAjovpF+NREhevQNUAYF9Ty6JXFk1CHCmWVzy0e+gR54FhpKMmd/xA76CR8TaqYdfMarQ6DmiFysr6bMqPNHFFuzbOjkuIX+/QHRDqtAPW3/+TzULbPy4kMPdbLsFeZvJ00G7fQHauyuNbySrNJKLTRQPERiwtskzTi0JZo9hYPXHfnIAT5fQrRi84YxaH4EQWhS09glP6D5cIPYj/CXcm4nx+P8FJg+IvIkH4E0pJ3acsfmZUDwZvMZZNTRa3WNwGEmgcaa+ou5MrgK8IfD+AuTpamqOuuEnOc+vcFcp49RbxdHWQDEK5jpT6MhK5Txkzjsnu51TXmzhMYGQlSbjD3EiWPWaoPUws6ryhKqnH4AQO86ZXb8IaTcnE4OMemIZNt+bZmg9J3xVsjhtvS4zkGigzryD6yLFCbrSSUhTn1gNzyu2N5/wBN1FNFYwJ2C50A/VCsJlH50H8qhe9rBhTJihdkXAHSurWdqPnpYoKddfHkqoUDBnGQ73cnZ2LEPs2Rs6pmSYyvOCxKTAMPA/1UWC+gi0IPgV+uxryHxjZjobN+eaJkj+t3+B0DV+TUaBuEabhijx2r0J74k8t9qkwKceO1CxaYNkdYE8n29O5Y75/JilUvM5vKERjpvV2i8Ui2jQCotyY7wojS835NsAC/jRg/iWeYTIXTdKW/A/mOHFH+VQfDI/bt9qEYxyTMX+FJwqepdvIGhJ3alXMsYfW45hqcGKoPrCLj3qJPGK4e5XBDHV1OYQQzLkLdEbdiEsS6Mji42Bi0nTt54TebDOpIRnfG5NK6wylGvuQ/BDfaX52FN8hJqIt92TLkfEpBFVk5oO12j4hBYniLj4WCXBaF5vb32I6h+Qum0IO5+/m8ui7t4trXZFK8C/olsWwFM+dbXrE61qFwb/L0ubWTTmTullqzB4lnsoH8Xj4xj+oWAH0aXSY1AYZYWRyqmT71AMz6tCeKkVoxSarDJNkO9YquSwgSk8RnttWBaQnfUs4cvPyBrKLMQWzGZpYhx5jXBE5yLbfVflodBo2tN4ifETE4ubNi78/tl8CFCUJ8M0M8eFVsd0/yE2ifG57woANWNnw1D2QYgSn0j2U6u8wctFr9dZuque63veo0wUBQ483nOWakhlhFcRl0BWxpT/3VSS7o6EOhOE8e6pkAEzvXahyWAGfYCTtSFP7O7xRaOZNthbsC6AP9+N4s5sfVEuw8kH9282wWJCQtDyBmEeH+U1pQYrkO2ngIQOOPo35Of6B+Id62J7qTcx4fFzbBB8i6GTZUtmFch/1ApyWNyG/sIkUOX82pcJBTfEolP6tJVx8efcsMb/lKZ+UH3KpTwgp97EzcZZj8IoO1s1gieLuJCx3qheA04CaWHjWeRXdTmW+/X4yrxh5ktIgcbVgkZhegB4dGE6A+5HnGVTVSsMv+2fOMMJSpJNTQpbjTtODrsLLniQf5X5joFhOVxrnMLw2mGBu8t7XQ8ltNt+w/493//HOB1Nxoi9JfixjNntF9KAyo9ekW9gJpp9onkZ8qnTQPNpAVbvoJi8EmarphhZk+kurRDff6XSZdLZWs3g2QCjw1L24X298k3w7IeBGErnoEhZ75vSG1SwSSmkq96nfjGg9o2KB7vPO0AqeJiO/xh2PRAMXA6uexbBUFn4tjDYkgMJGyUc1nxnfoNCA7w1Jz2zxxe7P4zxdV01pMriVMXim7j/TNSpFj1EZi2MYZ2fD5T3uKbfd+VgYDDhPHvWNVPFQCjGbnTtc3pDV/3JKytH9pe9qK+/V0QffRexiLBN2zH6z5knSJF2LhEAd/CR/SvYgMusdC72eNTvWqDCealdOnhCtT31IlSbMfKhGzTZf5+5vnt4Qo4L3CDg/eG3Bs+QHHAHHJxUpnrTiTgH4jM5QekP6UYpUgQDp1yAbSOEyK/rrEE6uCEtM1hvdYgL+vk1hOwpPrw19LxehWSGCTpFZlfTTOVfmaDE3e1eVY0+ci9c/KU2fp/CRP6MVlQg0DeGxivFZVA73jfpz9KnZGPPpDkrG2xuMdnX2GnevGF9E6MmW5LD+CIwIif7z+FY9Z5EimLAsSdicIIe2v0iozrqticl8AFqKPRNhr/S2STDHwdgNoq1z+2iW/9SPCMgbQDTnUK+JvbuzZwKfZ7UP2Ce7yR3LeU0/q9Mb+u032rY9Ui+iiHqAq+Zbi24lmQ2T71nQMZMIGKchJ50tnc6Wy6mq3FV+4JdPl6TV9klf25ZfvkfHVGSKcu8ZJTTuzgOdSt1LzCv5crYv8Lh3+QkU+i7J64Y9SP1bRAHQAU6BFD6KGHeTDjucMK6qrOqGbc3Ei+uAW8I6AnAhk3poC+HZjy9Xx3sZ8uLplaDR+/C0EpSS0A7aOqGGR+Xp4AYKrGejPqFTKJxRcHOvoX496pdp8BESQN4RXXs93Fbpltdn9nzq6Ra9TPUP9F1bFhArRBlOu0BuDHUaMV1qpUmVUGiefyqnUUarKpNjBltJsuwkZDBqJTFF2v5J4v8iZlQ7lQIVUzBLk4pJkeRDs9tBqOV0pL8ewponrHJipoWlMiLswRQhBLEcRGvsmljKN/xSYOX7aM93/uFz7WbXxVnwWDBYA0RtxGrNkks7sX8nHi3LZTHRS83l2YsTHOPqwT18jQ+9j2xUFPAYvz5skH1fPIYTOBTaeZRsjiFiJg9osFYTVIu2xTE41fyJopdV0Ggl0veWmG75puBeDTnW4lgFW5vihoVmzB3pW2zO9jMZhRXzgW1aPx8YZidG0whH9LKIc2hSGZiWu7eNGnXqkZYgMZeTKdS/xsBvwcCzo5e4ca0s05Cg62hxOnU/Aml1SvWsDzSv6iGnU/86H/irPrDYFML/Akb6+D/WCOrPz1Ou1weBZfW9nmVzZvjHXKHFn0NPI3ZZfndIQqOskTNRDHFko6YulyJv5ZlC0zBMumZhX8EfWmjwPJbsmSVpemxOnBUa05k2x0GBJQLRfqleo/wJOXwBmwcsXCWVUHaT5quM1AH8QEf9vSKDOhfQBEIuVuNmOJU1esD8laFB2Gq1WZlebVpTm4l6tSHXqwF4y9f3vlhULqs9P+zLBsgF6cbsQjEjpitxyhlce6WF4vQaC4hQa54pay0P325w2Td0s0B2IL4zmKBnPdym39wxhrIxMXbWbEj2yInK5//QRgyXD6MfzyxyQWgtEMapgEXe3vpXCpTM7s54gaP3n1gAbY/vWw9DUS0HX1wOdyNQyubdC0x9dCgOUaqXxapPonFslPi1jQ12m6cyup4A99PE31O2KslZTVZkapNKWfABEJsEvjAMxMQaqC5ONlOEt5Qay6wx2kj0AGxK65Hqn1CjvfwxDVY3avK9bxeebpeedhr/kN9Mvk7xfFJDxi905F5vD2YZGLpwksGqtmcdLWLbZBZhqtD3HYjwu50Qj4juXHqylQ9os77Q6XZDZCLqD/o7Z8+vxaEjUvxymUy5I6ryjFiSFbcr0D70O3PaR8//Fr+6EIBV9vOSBABoErXu0Ux2t2vu7kC7HK8blJObY9fGCVaQ5/uHB74sYkA/edLbIpXdfckDUMfiwteau1+gkc/QnvUfGRd3jteHD7i4dwjWFSqyxtt5EtjfZprawl0vc9VnYIN/X6mvOhadhVideWhihdDnnyELH7qpBjjgKF78bkxWcIj50Ao9x5R3VWHLIpSfAuAFG2mYdUAdwN4R+HQAB0fgj6Pw9/+Dp4S7XL0gFsCmFpt2KTw4P3biatOyjbBZUGTDZoTJlS8nhRUiHCK21qIFC2rAvaGowAabe7J53HtQJHm7yUQLiccdX1YgeEVQhuMZ0RhuPqsANcxASdw6eNHoVkRYzeAihSEDSEJlYR1CXLZwtUIrYKoMcCAVBgoQCuwF24kwJXH14nsQVAiWq1EBtICEQC1hKAMPhCkHnwHqJ5gSNjDcWGigiIV4rgAEuLFQhZhFbGYfhS0xgN3A4ic+50hur4Qv1ZXrF3atALHHUTbVfiNm31gOxOOAKQm9g4+I2RkLJx6neM9K71leELOIRSF4UpwRal0eEbMf9CA8Eb9DaZv4DpbdQFb+yqEQsylqJ54OuGSljwgMywT1gciK3wgxwiMbmFmGfSPIAy5ZqYwyg2WGqibkBX8gVLl8IWZLVEbIGz6T0JohImaP2M+IfIs/EKrB/4hZiX1C9IZfEKo3HNgSHOMWewr6BrXHBxjoBXxCzN6w7wV9z8WVXmNwMF3QU/AaRyxnxGs9YjklXrsRy4Th1QkRCj//TPw+pU8vI/rjy48c91+5/IXtxy3b6+Nc3dvzn69P+NnLKfkGh5YvXm5xoBxymXBQ+pXmPb+v+Z9XL/hdaZrmO/yGbr36hV+ZM68esa/pJs0bnI785tUWp0RJrhJOSgmKJ4wr/qSuwLiwud7mrIvrblbepXmRQopdrqGgLCUcInqcZg0kkg8zikKO0+M24JjmsECRhdpkoIiC65ilVEeDfYBCPX6YdnSxKA9QdExogGPXNzAC28nQuUBGjQHE3ZprllDUPeoTBSJXN+Y94bqXWzPZ9P3BoY7mYoNR6T3sfSvIKRkhZ5S0m5DTvV7B0WNbHAVaufUGcRz3HLAJj7AP0MGsCQwIhrpH80QeNQaEB6iqm2LThDSADHoF9xY2SAwILMtvUzujKIIdFI1o6pMSAjYJFAErpZAKOziKdQbbkyUf0/kBCvW0AoNYyrdFcyQXYZNAVIYTEZoArM46rGALKGpHc+mCMblI0sc7j8c+hV0gg1qAKOQKDkVMyPfJkNOU0BR+WawaWdJZQoQRyu5yrrpdzg18cSSkldueVG5ETXsQrSuDX6PJmyIkjm07cuUpzZDbp2c7ZKb/SZoltIOEMhaw95uiAxo3KLIjx6t2vQ8SEAwkeLIreFIwwiZB44YQF0eDW8cC9UDt3AAll/C2ThTOXBQk7tmLkvxYFEN9AydFi8JWeOxh4JjeCeqvEKH2R5rZNR125WyEm2lWCruvw21j6GXkniuKFQsIKzdiLiUzq0ChDT+384vLc5DB8xQTbHZqAZHXe80StpOLcZWk7U3xeCz9HqbE8tygplWJnZy7rrLXqbPbXhs/MbQycH87Z3LxQRMWKFM71K3YiaLUy8GY7ZbUx7YcHOs5Tfz7uP/Dgc6dP/DX9D59nGJvSs7Lp+GnGEZO9ika7uJYcFGKoaVMIL7eid5N2OaQuW8D8G+3NJBMYrYShUOYypIQ9k+AcsL3raB5gToeuOehaprYEiPDV/t9JdjII7CHKo/KBji+9hwCeIFd2dEAMY/Ur+C2YTkbUbB03GCCQorJZDLrt+EkSZg3LN2uSa+ebt4oxZuEuc693lvg8qBXjsDrpDDkiwJCDm+/x6UrJb3dOxK4nZDx1zKRhbYCKKJkL8e7xOxyPOnTIY3n4IIg33dV3mp3GD2+W1QmSmAh/ZtEbNbBHXgroWiHLKZMLTgZhQQASegZUDRB/h4ngcjBUAqe3k6GUubJ/v1VhA1yRjEirqCQWdiFVA711gt5e6WvTYcEJrPEzJwMIsvLIb1jKd89A10tRDcxeVFCjvbdeWbAepwN53Skqo6fJA2QEeDHjQLrRAmCg0oTt618tKj/HYvHYULixOBHpwaOfwpBwu4GZgN5iD0UcCSwQo8O3IcRUJTfFovAACjiT4qrSx9LYIFCJJDmr8BMWB2oShVqqxN0CD/DwOV1XwB8A9RlcpNGULYe+6TW8vYC/teoWaREFWINRSO7SmaRApClA4MNrTDvU/LaAQ0U6kguQgfm9CZdpxboAaoRyvCCllJvhD8BM2X0JsdMGZ0U57TwSvhz4Eqf2FPhHZP/M+EN3eI9PP1qxJpOFtVRI47T2NCvCLVGGtd7SqmBwxEJYRK/ckzy1QXpmIDjLIdbslyIwsHK/HtQ6jtPjtOkixOK7j/DkqBHB21IgJ8zmF9Y/dh1J/dhcnEUqGaBXTUDwItayDVNfJv+F0gwxw5YmwBUuUOBFgpcABELwB4qgmFpgIkvzTmfM53PfkDENtP6BcBECSw3ITud7LqvGtuBIllMfGd1+O+dOESqqhwmlbRlUesL1c8qx0mbKLVb8CbdkqvEPNKdmWz29VUZu7NFMANEHeaKdgTxnuhl7vWtN2of0xT6/OpkNJZwvVETCRI6seH327WyTzm4iP78O71oLTp0IFEwVdhQJuYkMHB8Zo4E3lYyIwIFnY6D0GYaFCTp/E200JnXfSFL3J40jNsARTkaxNEVGDsyOU5QQDedzdMOBHRtBjR1JvzYlFXP9fZt2hGu0x7bA0g98JTECdjHZiLDfIc7c7wyze1xG6JWMQFxLcARr7EIhKBBqIWJ4gkyFJh6BAnnKyGzccdb4vS9jAHfnr0oXOpmN2gqI37nkSgnphd0oK0E2iUn+RIkRFP0Dsa5a9G0JZhAYCLlz8ZTspNBQeBGZMNgZuy5ElvHSTllGg/B55JIOE+Xa4rZyRQKkXp70thuue54vknsrqUdr1OxxWhJUzM6Pec/3eLXG/HW050Ni+DW+7yLvXZXw/RusXAkZezADOAHqJnTicJRQlwwtEyUpr8ip2baZsyZ2yMQ952HBZXc+8IImnCGU5x3nXTDwTTRkE67fQbTYBhTaFzPsFhdiVV3SBnx1GhjmkJdRPqtWetgEwV3cVpoIULBQBNw4+8NAWsTX2ejMGDvDx+rWEBO1TnRkHOi9j7p4acrSyYwwsUPIybZ6cnzt0cSsJw60JHdiGC7j0eTuG0scVS8nz8D7PXnzwA8ANNiGly4AjoNXRfLJFnrkCyrxRFx8H4SEy4544m8Qjzw5jDhWN6X+W1Ss58ECxRFLPjgztA8pYSjmPyb1RMlPTFkDSaQP8jVo5OtuSjzV1LRi1ngcx3KjS+oL0e24us+xc6u+6kzbisB/8/S4c83w8vVlC5l4ULtP1yosS2sDjlJVw0xrAu0bk4jGjtrx7iMoKDtniMjnQmV5ypJ9VjMPBr0JJKWEGP/KzkvQCSomEt3Qx34o6LBekYfmSAKg5+MDBGtri/7AG+htorsbNLLKkuuMjbBBJ3aTsSx2GFhYgNYDmWnF8AF4wsqH660k0d0JPZ4t7ClwD41Fb6GsnpaQW+C8l5Nh5CRggiUkqgX4/p2ZlIds3FshsNG1XvY+Whir1nS/7uGDJ9GrurKycTUIvQPNaRSh0h0tMBxqSeGqrMsiLrDTcFNBYWUwhnAaN7Be3oMVXmF89ZidW/iLnkuymJfzdvQ1ncxdOJkevxrfe8djgN6VTeGIz7Z510FfNDmRz5VTm42p9277756T2w0BIKf1+6ku8sGLs1ok+5IOHbDJDc6at0Dkw0/ZiXQdUJKcmfjifw92ypOc5IQ1kwagzhl7TheeGcjlwSM5SXEmomSGlY6gd0nwRBQk4HBuTX0OKR/NzTLlMXyD+3s8aL7BeHdNNKu2K9kD5s9y8G0o+nOXAeD2jT8ng3Hj226VprTsYsINBJjl5eGXib/hT/7Jr+vShKzr81TH29wLUWzSnfrXxNIF4sqjHDKhwkGlbFgCm8CsvO0OG90llxcTSaTiSRLR9UbCsLH6M9br4UqZAJWiQGw1/syJVxEtkPi1kxgb5XRXEuGxbk7n+c7f76axjF0YKjTGrGHOk7KiDseObySBKhpHXAhGHHzgLdSc+QRIAzHDs/KEax2ElvFib5zhhrVqwsJSRXHHKc/voDyWjHUmbt/YWSyigUvVHuolEgEERQzltEN1joVyo94PDVBrerOFYZExlDksIReFLOkX8+EFqIPiUZXxQIOpytGiTSqol5L2UX48zJ1QRdfEln63O6ljxOqs3UIiVVFrjqxTM0B5P7G12bC15lzEzTjvAi1r02Ycw0M6bYxqh8ZGrtQS2dc078eIxjCgvUi3k8FOJHXsSznOctX12aCdIuCXd2dU9xey2YHWsmNq3IFoozzmMnkGxfpetGCHZx8rbAPaW+LC/BBnmsDRjCo1N04Eoo1EyjgKLjNfZ0u+i41cWo4vaMJHX8ho3rs2rX954TvU+VzBa53Ei0ycU4VfkDqqXxqQrHpej4pthWppXbCAJJr4VLbiktReuBsZSyX831HrcyYdcRBJlwpsB2ywKjWChblzHdygqCieHOFnTkcLXQV4ChXNKk1NKqqLqx7CSpAaGprFtmFAtEPL4CmvtFsTk3QPfyxX8qHgjeLFu0gUJQS8iPpVn8c0p3BXXZ9ymkZUXCGk5U47ZDHecNybIFHODOBh3S8MHXMhp0SFUr+HsUCToXzNAcs6nG1FRKodQZyDV2rxlbuytLFUbnRgDAuvXYmA3TTKgiZu0s4xEBW0UYAKkqMMKlecngYZxWqFeyWoIJp713EOASCxVQQsKMiixcOqoa04SBRIPa64HP92WLtKHQAThO0e9r9VrTYUQRmlCdEhSFm5WQpuN4vioStgtN10XhXYBfAqJWY8yJ+970KRQsDQdQhIYx1BIMX0+UORmIHBu+owkNI7e3CblwiqoJXnF/JZHd3E0n45/P+nAbBh3JnCuM3pKsQr6MOtvhPSJrE+zuDOG7M0S1OpiQ80VGAlc/fryel9ttc5nnfgNVZQOP2ZDgEhFaEAVaub0SJU62Dacnrsv8yN4YkGBDycTmWD+sIRoEnJYGabvWYt6ZHxnrdr/DWdX1npn6cY4GCqhmNGa9AhiJz2ixIbi06qUkyA7idcNF7aBWxS6PxrZCzAxSoMGn9H61AX958p+TfY0+JmfBt98UWLGtlMUKELB7hzeWKuv47w2IXwVadXkxryn9aKsxdRzeHucRjdNvNWszGrIcDe3vTV8DN3UkY0urBYXHZnXcqgbHd00hEodoh05YZtgvSoNrM81HYoCsUz2Ond1PbHBWEWKG1eckpBhFUSdgFOoB+kKoZ0FAnQI5AxeV4kn+0cqLPFuE0hiZa7Ni1yJgyQ3XI62LI1IYJCe/FrMkTNSJAL0QCQ5Aj0LK61UQECNZ1CgookcKL4SiX2QeoWTyA6iZHxxGousboxECWV6oDhchZLyHS0S/E6gfwonFiClyHL8BmvLIGRveYLfp0RoBMo7MUKRyHi79Z/ayTnwn2mYobAciLO4tyya4LgZNSYV6Rz4m0DTVcBQ4omQALlLzN0/MpDGm9R51giFNm0vVf7H911o0qOdJiKZJpiGB8T54DiiU0xqMST+7yNIDaEJgPMLIpMDar7c7POEUCo/fbmpijgUgfkQk4B+MLcjlhLjbIkyWiKirBh1h5tNc0z0aYP+ioBio0EO8BY/7/5xsgxtDzQoCXOMQqtKo8gL9CYAaUu10KnaFy1XGMaxULB7UUDc22gahaoyi1NaYcssFGZJOxywLhgwc5iryO50ScF3iEZUwVvKwWuDgkohqtJfJQ1WFtZFMkbZQySUZli23aAmKLFLb/ebpb55ChllO2quzMu8PYdc6QuwgmHy4JJu/91y67ICzxoBlnj2TEy7ASbYAGg0sIGrhl13uhgPlk40xToWvgxusH0FiNNfh9BNOwgx0YT9FYoASSjwq0V6Y17eVycJs0jJcY5/nbSZfvqeBo5dPM3XF6wEz5PeIEuzjeu0pZeB8bAcvRCcySkymeFlKtJxeSzM3X5suV5HUmDJDuxThzRCGWsEwInUwGOVdtdgnwj7i51xoDQv2lkrjTkmKWi8JBkoZIJFeGyePbFXEu5h0oV2rcufRIJpugcjwZ5krB22Blg6EV9JuL8NPUXUqt4Tbb91x2wneJRNLQQ0k+0U0UxtrSbo7Tq6lBD8bkzhAMy3A9K95W3OX6WmizqEjq2DsuC7m833n0SmPAW54oYM/CHrFjGAEjvgEkEDnSyGlixiOMLxR4FdtoqB2Hm3MzARwvI7S+wsVMiBIKXOAcH8SHIGzMr09fH5PVeKdDt65p3Fvq0OWhB7htmlsiWeyXr5UePgYJ91+47MU2Kf3CuXgFjw7wLAtsMbFG0jRMwgSWLjf3AeG0L99TzC69fzaR5r3kAgJqpo6oFfFr7a5Esb7qBmAZF8BULIN6N4YUcCu3ULxFcCzNa959goVX2YjkwyNHxZ+uIWAlWaeOss1o2MNNTk9WQothRLDgGA/L8xRB/R1RDIV+LulIclzKFp2GrZY39HBST57Jh3expJHTk4XJ/le/W3aKqZP9Cg20cU68JBzyvBLSH6j6572j3T3n7sqvMTxFVMVm7bc8cw5GZhao6+BIlTXFuv2208Ez3kEafAJVrM7EJBjwB78HLYti3mzUPPTso5MvKZ+grznArzki5J1HR94wah569NH/D1V46CnljaP5oV9+Ru221D7m00x9rNyvEVaYqN1mX1yVtxLiJokJWFsn88V3M1WVzE2dWXT8Nf+P35XQDRHJy62i4knzzT2TQ0ILVYp64XxofJ4nIXHUeNfHIhpilrKuxRdHVbdJF7W2xXYhOi3C6KiE85DAPUrRr6e55g5QkNzz7Vudztx2HckF9huXoK9C5auEf9fg98LvrgpJzP7OTRmwN75s0gYDySd/flvcEqzJ8EHIjZpKwaaXAHu3eE1RCfd7PPL5pnPs4GRVBVyareXuXco5v8UTkgq3JpUO39FkMmvabbg3fLmXhjtl8SZuLWfSBJdjb+sitGedPPSqz24aUqvrSVxdDN8dC89xv/BkVjsbguKltNSjAy4ivC3f+MXtt+leWV4TIrS5X+gH3ZzUiQlmpM6W26V4rH1cJin4ZVRr8IoeJ4URE7W+Nm0mSoJPXbDppTJaiYypl8EHUp8FHbQz5B/SdGXwUeLggXHXRl/veaTY2Gz+HxETTntV/7X6sun4m674HyYzcXQ09CesEz4M61Sq0iZ9Sbe/Czl7hkXt2Ol+rXNpWN2D+DRTwtA2Sggn7sPu19RLeJ/79wzuq1gg7QmkJOg0GkYXRll6wXwPFNPgJU95d0jv7fr/tfqxfwWDc9pgMfWkMlp74GeF7KtyaCZ+mBHvXgRqTdjdIC1LGItOarmAVSaB5Ji5vbtc5Evnm8gIl8dlDAmqdmgCNg/Vj0ScKceb9b0inPnnF7w+UGdgTf/pkK8J7JAOi7rLZNy9N5MiEMc6wCEpz/4SF54Q2Oth2/224kFpPfn4XDFZi826GyF2Ky3n23GnHr+Kq/hwBd6rxd+iPXdy/CXi38ZOOXP8XKvaVe37UuUWaXuvJnm7q5A42OU6XDP8fQN1LXtYOp+DKMS+oOkJwK1jI1fOsYCS/KlKkfz4FPmv3DB06CCKU2XJpaERvJ4vNpiDD+Ekt2NkYI+9N8WJXH2bvvI+qYDuJuk8TuRJpLOSxr3pes1HQ6cNxBZovVdCMeZiSX1E2wIq/+WkkPdCFkI4A1/vrAU67fceg90CcHhAvIcgpnxeojy9DCMbEltAEU/vex1VFwejW8XH/XKPlS/vP/x1kHgrsCZ9AIU5zEtufb9vpVPN7/N//scHBtJg/S+zUAtMlkWJoFynLHVeuUlyE2EVT4iI8iAJZjzd6df+ls2pCa2b/H6Yere0kAg4dv8Ok0oXRkDJrofQsa70eqtgMsTT7N4+x/erKOb31haJkWk0StSmZJfrq5sQeVUel+Hw4HQseQ+Jqrz0I9CLLwwuNPXW7502n+6P+etiwyJIpdnYn8JJqNYtXf+FkpoKYMjx5Z1pWzhF/ncYy8iE8iTSGXp5XJYOHeJpK1nRi7z0J8Fa10KeJjYdJvIIx+zb1a8/56F8/yS4LkY1Fq0tJTAKjxS+/87D09LoUTjoZESuz+/ddB3lxCKPFB3HQ9Lux0Aw6Hn82n8P4bAqx3DhDn0wqnKjB9W2mOTNaPgXESwGvs4Vzu8UZ/QABbsAXWOLSdBpsVEX75CM+Ja+IPUHgHYMVkvvWwJr7i91itwiX5voXJCjaaOwiaNwSaf69kbWB7JYGGSnO6PNk0dhzP2Orhxa0VMTUhkLAKzJxQMaN+iYI7QPl6TbsKbR9Xls61+AssvZCqKz/NbZHnFM/Qn1vMLfrklEvn6hQLLefGJpIa6e1zdcSMmVLvT3JpLa5jQ/gUMeanZXiBan7apJTqbDEfw+7p9Hms9of6/Y83FYfhakH8BrCVxsHyzuCzkc7f03wtsxwJYHrW9gYB5kSOxhGJJGyBdidYigcWPHwEZN6rQ8lwExN0BI4BQn2gNI569M2OLqwNvpSK9g74ByEhOQsMV0bjp2v2CFDgQRW4KBrImA6cCK+BIoAayhLbCsb9TYAYGA7vPu7ZCwoblPVEv3Lvb18O3Qf8Hp0fEtfa0dDbam4YlHSJYsFhAM7cy8VbzMZ/BMjsv7BiMjbLgdoIcGPpIAfM5ZR/OaP57sB1PtT8+HwJl3MIYaQlyXNn3ER4gAPGDwbiwAag/bHzMHsRpF8cVVfvnDcAeQ2/TfAbgM8IhBfuq6Jt6Zm2JLcPOfK0R+nFA2BmSJXuMjtPsrc7ybT35l5d2thZVyMm0rJE4sGIh2xAE6BFXsTj7atqbHFVLIKCZqyTU20FrFjiQd1kzhUfFlxF+oV1gdRm2TFQpbPkfjnUeUy/5+B2WeArIxDNEa+DO2weHXA+Npyt0NIO3A4ZATmKu1FLXCxJ8qp/Jc7/c4Czuc5dLrEQWiuF7TGlixNXUIBrrNhcBCqeQnhPnEZqTdTl+KBRCri+7vrKJiPifUVpILSnNL4rDFC3xIfqNqGAm6b9OG2BSSruvQKIWSQfBbon9uEMk/Uuub42UIp20zuN/Vc947UQ7d+gQZrHMNb/w5+T1QrM66TWUC7VfzM4QH8zWQi3vNjIgzPIBPVzM65pNcF4u5P7lo7FkRyyeE59M6ebxihfisyzdg3ratmOAUDCyCgIk3EyQNLuEBLWxrSxzTi/Pp/FEBAt+saXPxTBuwv8qLTHy/UM5jBN9Spz9Hl8KF1rmFoPtGpYR66RCa8oor5GyEfPDKNpGX97IYmvmp66KfVx2dkh9LXgp5i1JqWXIXXjYlzy91ORTdlzR0N1OkD0x6d+PXsBC3/2ckZHJYlIvTgXyNDw+ykONHxIklvfXxPWacsDR8ZdXn7bSsecxqd/AaRAtu0EY4BGKiq2WiUWWI6HKHX3vZ3cjUMRU0B1OM14Lxfiw/xOvKC7Lk0gwO9bz84OL7wNolFO7WRL3Ci+nay2+raj/YkzGyt9bXz+U52ufN4fqI/owv8hyQkul8vqXipjHveffJERWj0K2xrgEYWeGMeHmCHmD1VA3+taZEMaGFgUW5s6GVHSIG9FYcL3zOSzOHyHJnaE/YxerGoskeCqm+JQ3fOW0HUOb1WFbdytadRk/vd5tYBR1sGidQEwU/p22U+2sj9yBZ/dLAbgLOypcSfToFz/j8gciq7vBWR/h+M9rb55dxnC5gQKwFx+UGtmXDczvhIivDNPfw2mBBMuvLk9LVCFc7AlonPM205lXu0U74OosGAzIUez2qXt6zcX7nSbnXKUOvp5koEF/QwzyArTexDi8c8QZwZL2B4uUc7Lr7dWz4dk1crx2sFK3L+NgmLTPz2cCYVitHWAyMsZwj2eIG9rze/29oBHrA+Zr2QXx+e+tCC60QgqWIsQ2OY127sixApyF470nKhhryQA72Q3OTjcJ85hB1YCSJgfbaCiJktiSRYIVyFTTYVt5CDSXMuSe0/Kr3FZU8ia7R/VZl2A5iQW+xfd2Rkk34LNXjL+hSTgXwtpbdDcwPqB06IA+g3dM4jNJhrDJ3RdeFJT1s8W1xJWBNNKj37KjVLZ4ymSHORUkkKKfIB1ZI/ODVtzS1JCqzP0VO858KNOsOM25JEFtQO3ggjx0WtWZ0agFFvgClRLhL2oPKWIhh6cCISyJUcCxsMc0kzYauCtPJ3Ad4s9UOUV7pOJcRSr5Jj0XY/xCv6B50l3lKEYnXvbndOKd6iBeTUeEpSqfUyZ2vgg74td930A4Ki+atZzmaOmNi7iLKvKG7AYdgQkLk6/3GMdlQL7s8kNzKLqDM+e/uY4cCbYlK5QRZpqicW5kuNq7Gmo1h0+xjDpMEvPNcw+EuXmMHbRbldQzE6noronQls25dZGhE+4Ogy7euFcnyVtoS9lZRRX1gbJLOvKrdSmHoZTrEHr5vbYjhZdCXCwLM65eZZMkmW0FOyMKDtWCBn9PSjlkOwC9r1lpCZHkxvFPhFvnN27uVGGTTiqMiI23X3Mhg5rXZucZmms9WwLUdXcsgE2abUBeU6XgqJWGJz2vVa3LSIs9w/BcgVJXns0UuDbZULJOYoUGHSKozC1WXZZSqUHsWsCbs4EwoNmkWb2sngV5qdY26O65a9fMK5iXbtecGdbXq9Wq8p4wVHyJ4u1kTH5gVRmSgjlf6dUThMvoh5HeofD3T020UhxvP6MZecDWfBKvNTtsb3iu+zchd9zTedIXi6pfNaEgCx1i26yMGKtv9sNG7iURRMl5VoTmrhezfw1nmd7RulLhM4hPbUK6fjqetwMoJS5sYF74C9HV6we5AhRbPVhSDOpLFuYqktBFfxwlnJCdKMhIm9lnJIV02UTOs6cgNsjxRiqaC8ZWgrOF7XR31u+V/hBFXLw69/CFmjoV2P6c6VKwa2qiFpOJa6jBZlmsMUSYxu0SksLRqCcx8rueqMsov+Ln1UWAwz4uOYMsyF8a3B6TH2ut5khNzTV7N69yB3wJTQgBImvFFD3IQmuNuNff/JrDqxmAi/vxibcAP1y1yZDUPLkC2WguodygomzPUz2X5p32JULJU4RS1RXR58TmD8iTmGKiCfI54OPjaCG8P7ExoKq0acdGmx7vjNnAKbKhGc0Jpeu0MVkqSU4kMu0CKA9VpeSVx1rCpFFm2Tg32OmtWEQopJ2cENcoJgeMidWNTMUw+WR1n9/h0h5xv2E9SQKgH/P5KvLuoGhBKzwRhQnSXcYqWYljkMtDUyD7dDPu8aD4KJ+427dvyf7XvPbFDouChh/bQIZ8zR0bTXekWbrb5/KZ3WApj7Vl0LvWhfeyv1yOOS5OPYpgxd/jorMrz95GEa4e1w1vF+6e1eVJ8S/Z+Ww5TXh6ntREHgW6pJpf55MWV8La3rPQwvywkriweFZYerpVAcw5Ikkp0YgYVI3Bh7ekiDX+kkfQ4gagN6GIEsibcaeJbqRUg8OSUoIfH9tY132pBh3ZiLI+TJ2CHHxRUQTj+uYKe1xtVBKySpBMkcTtfgLrLCZMAcdvXJNCS8mBJqfWOzr5BzuEnA8LuKlPRYEfOabnC4/XFD4yXwUIA5V77pPd+byhrWDvPkOuKuYZg97Ey3CnKLvHvpgvHAq06z0WkpOvyzIxOmSiBqGsTyfdYQ3vIaaNNsOrGQ31jaadDRAGGdEy0tOH7hk4UaUVyHHuKvrXOsm4KxsPguguAQ8mHaW/82xE1eZywLeuo8/pjR+CeFyr4EwvySi5X9qv798NUNkjwZbjtPAWmdQBflXMMMaduJRjIL8dwp3XqtP7kFMXRLY/4dHAbTy431Rle/Wmbd3/voTVH+a+unlv5RmwfQP4H031GMWBDGUwvQC1jUJsQWzmgJgfbCdQJAucW+WhsONw0Vvar3CbNGeTF26zT+n0zynOJQOqzcSq0GSuBgcMIsQ20SZxBELVcPXUOA/xmYq2xGTLTHlyKLiqkomlQYG5ZiR5tX42FN+tjfsuXCXq8/pYVYhjBgXPjDguorrydSOANBOZwBl/cDSMixd7XnjyNT/SJEXDhmh+yAAxeEvR2y9wDuMJL6mHao/AJ23sgLRwubbLXat9XeK0b8+Fk2ruU7Gh7Z2rkKa93y0c4iWJYB51t1FzFQfOKM7WxSx96Cj6Q85ECCLrVi0S04Jjfw8OWYsDlSwL2CEiYtdWeCauWASFZ3MYc+qQjELnXMXXbFB265fBkmCKzmFYzokDi8holX2+VDhBvFzax3GHBMtwVAkNr1VvYyGF1ETa3c85RmbLN3AQf3f87Z2IRszpJLOXntIJcDV3PLbeCqyuzaIcd+2nO05CLYKRDn1GFdbpp12ApIdimpdRI/TwLki7x6T0X1Ssm193VKMF8NpRukvr0+dQJCpGn9/Th0o8usVN85MO1ArQJiNagw95+RHNibr6G1KFceAI2Fl6kVgCuXIc/VkqVR7kIn1lCFzMr/UTrHw9zEgh39vWwSwkkDrq3LmWy6Tz+hweinJT+qC2pz6MVjss/i4nt1udTfS1VmQzc9o3rfRolQKRQ3DThbnbY+eNMHiseUGZ0NfI4KQ2uEPfj+QCWcGSpNj3KW0vkufajeiCOhkm2xJQxvEO/zFHmjCXWxFVxmoclSAkJ5asTyhnzT6kRPMo9poNfGJZ5sqTEyF68CfECdUpMHFTKIqU6E8rMUg8OY4qLsMTLzRwmmEVPGIdbnGKDq4XacE3H9pYOUEkejMlAgrRWj2p2QZEepPakXAlRH0wO9VQhOAZ/O5C7GAopvF1OSWT73kVzMhBsdFikZ4yHwqK0VG7Vmk++EE+6+FoT3nJ5xxISyI4yajOuVjAi8+O+5G+xkxqXHyCw/lpTFHri0C1bCFgllnlLWueIZPFgH9IR3nSFDiFrlWu/uRbeKdb7gUGMY/sieT1mFFKcFEYIq8FkixMCfDdywrVOm3eXGKCQY01FFXvUyatGIYDLY84NbZ3yVTVhwR5NhgkOiwFr3ma+VkVhrOMhx6CSUDFd2MjQizRnxQsgSBaS5RyTj7TTAG/Nma3kn5HLUSs1JMti+kLSjF0Rp16qSpyyKr4gqFU2wRPCYNfsLtkUg8sjsPStgI+YLOM6RHAPvFuzmYBtLF8Wxxp69XSkWmD++6OzeiD8TyNvYVYZtDK3HII+y9EbxvrojkPrkDjLnvhid4rG1psrG1hz7I5BdKW6LEPVhG9zUa5+JSVcQzZCbiXQBUE4hjwKhDCesMeY87zJSc3XOsnlMg9CL7BhZNV8cH+1JIsFY5GX16jNEA6ajGRMFIEuBilG3TPh9MrdNgQPHWp7NwMpIHnft2bllUg9dmCI++DTDtIJFgZFKKXf7Fk2/H4PI4R/DwRbTBKxCSegkIXcOvFhwVYhWzmqXtifb5gGEfcCMjmo4s2VYNxUtmpUROTfR16sP3s5e6OkHua2i+l7UK/9DvdaLutEH3rHZcxK620FLmJNtO/9HC9pGe5zC/95Mf6+/oBEHNN9MhdzxXcggfDTdEdYnklZOm8SYfbJfinPEy8BcvEaMS6e0XKcbDe0fHC7dhtaRu3ZwNofC63AoEiV7mL3cY8b/R/sQrBGQ7OY5P4/qCN9x1hofDNlBCbMBZuLWMMJ1EHIANsWz/xx3Nfle9C5iokBI1NT5yV2oN/Yvx87HGnGOi8XyHxIih8FRl3yjjZKwszcG+eOdAhBqnSanzpgcQPJXy+hB67Msq3JbPeMiZc7NsqbJQAJVosQ5sktlgTvq2oJ/orkqK7V7UyqSgvDtv7grPak1C6YYHmTYMy9O2SEVYotcWgozxXCxSPBm8pgPjg5xUOFz3hBYo0aw0l19k4bjWQhGIoxxrB1ZIeGI7HshJpO9q5XkWRdfaGke7gxJHIlWK+rBauJ9ohiS76YH8IXj5W7AmEI4xz9jWwYrn7PSUVAk08fXC4zGEOgKaKpz2FPqpMTAO2VsSMucHQsj3P2YmA5qquxMVUvjADxWe2YlmaWHXsOQS54cKCQ681sv5ualXOyeHOYsAoq3bUO4VObHU/yxOk6ZUYlNB/7GaCmB7O0QlDkdWzyEVW7YSPWD/m4CJ2OKsZBT+fvO49x3Bh8nSk/tZZnMxKHt9tQ5ImKcHTWHhIMiz4XeuGSS4GJVxW7pd9RYjlhlUSD/EHsT+KitqUVwtPGe++qz0X1CTCKBJfYp6NHYxh+OQ/9BAS3PMhT3tdjklnFTjT/MFiUpD2Cb4sUxPFvi/FPIa3qRONJCWvT4jCqvLa1J3lNUXWySrUtz3fAWtr+mpskqC3BMgjEzrdzOhTAxRe6vst86HA7xquv/ueWoiSrS+rYZ6ON6eT5cEFab8CwjwYBc6DcWf9Deo/sRWGRmUAS8/1ocaJlIbucA3v/u/3a+m3tj3lJMi9/38xDrX0vW7sJYREE/x7/IzIjMan87UqKkZ8W6/o938s3CubEOQ6euzFPbfo9SxVVqBbwI8pny9Dd/WjEqQ8oVchIJlsacfKKFMZLJBRW9y8A00+/B84A5HnCPrCg1Aq+4NEiHRcERSs702cInW0LD701mzpO1jtDwLgmskHSdH43JgwiaIsGLTgTOD0BceaPTIUYD+9hvqlCrKkFD17I+ftC2RwXPt90EUZ41nSqARe9dLDFxMgFwAR1tQxcVLSEpXmatBTYGZrpVdRZQBD1McAF4V6pdCPJ59fG+HuE37vS3F0sMrgKGNt5BciXowigWtWm/Gvr+w4E+CaqxP1BKLn91PCjhpwh3/EiVPFg+L4oPELC64nCKSrdzSSs0vVjN/IYHezHQgVUbPq/h1zJctMAcl/Of2Lqk0AgqrFfqtFy7f11QHHftMiYtYDeYyQuro9BQ+O17IC7oRaS/NyRm1ZmJQNGCT07vU0cl8TLLJSM+8rzJZSyu29/X81JOW/7P1x7dOGvKTDyrBPC4G+LtflADHiaccd6sswPV2MHh4G6wyHmBbLI7+x9lTm3UoqjPfABK9dPE3B2G77CaZMxPpVN8VBOLfBrkiF7Fqs+vP6mAxsZxLnlu6ne4MObADticse2UUMDhfUoFIcb34Hd82tmpZ0E+lkbh0x49agRpU5KQT+4UZDzSgGlKcvSjku+uoN3hd5Q2d4ZTZok2m7PDX4cA9Yr3GCraJ+kvB5+H5tSEwlpfODqXCi0ek0WJbo0PI1gFP0176ximQIzCUTnJv85Bh8AHg3BL0Rh/39XmG5htEB4ERrsotDC6qnUgZqsj4Z1a3FRnaPy7VAZUO1HJkXxBX0lbLNsVnwybKncHd4OJYniVnlF8hSfijfT8JHQ4hJwF8mumMQCK7IGHPwXtruBv6pNggHZLQYsv8JCBCIpPIQbE+I24RNwcEDqFGBVR08k8wsUVI0GN2o27FrXFt5yxgr7sBT6idBUTUOmAkwsWL7PEAHWJegyDgf/QhgJJEUAk2cS0cCd3AACEcmQrEiSQiskWcPYSAwzB6/DxvyJ2BW/yyr7XzLNQmvCmimMkEGlUSJMBaBsU+KdXk7/q/cVsKnk6lW9+OrPibsiySaLAYGMUYsVZQ5yMMqAr4FXUH0jo0UgZBUaDAqFQSCr+DYLvBjp8ptCz2uM15MbRLKsQJFlDGBbTQi3hdKum+uFtbNrNABgvjRpNtwodKKAYCXMjRmGQlZgMGWcMXITsm/bL883jI9ZAkNPHwhBySqQEABSTudrV+DmorJA/u23kvbg1xC4m5yPNyMDicwX4gbpmCAd4rURRzWdjp9epBnpntcgcTcdmIuVO84/jkFKhyYAFaPa78miLENcmEjSRRF5tFf9TmRe/eOu7BtYc5XTtPA3mJYBDEjcrPzPT9Hw+0Ao7XyXNeOvD5sj9eduWdKL7PFsKR3c1q1yHUvQs2D+cCn5ewJcKMfnH9wE/ck0upRmZsUnrSOOlQSaCKRabcE1mX02MyXNrZ82HnfIIsexioAyXA15a+gBtgoEJG0+TWq17bem2EcmKVU+swuDs0FRx8xSHaJY45wFb2QdThL3WzkZc/fYgFhSmiz+k7bBSbFg3YvL+wpZVlV8A4hMVXUxQxeDQwgKwNeycpCAoSJkJdUQp+sgM9CCHaooFR5alznVt6ADJC7RnrIvTqD1WICPMwWf/PhxjCQakgAOEjYgD6B833yE1PTFJkjCYtxMJS5ARopZODjCULIqyUICV6P1Vbqv4rKnbLMvofRJVK9R6bOsoD8fQHrC91jw//yUY+zLOs88ZRnGRCRexTVGdM+vvn9ofd+jDM19we+6rfmWjWUumPICX6tKxyKQbv5AAcqWwycf2WLCapWqAJgAdutfCUJWKwpGP4/uKXKlKUeB+oKno0bxn1pJAErZXhMHP7Qo5AclpbKas9UaEKwJf+fAiD+O3nqR+d5g1OxHYkzDV+pZRW+7KJ1RrWEyQzA8foPQMTc9cG6dGKG+L0aD11Cshnz5JJgTBQ/rUBj4/F0C+UoFDVUfyD4fIbmQGNTgVakaeZJbp0R/vjurm31JUCay9Nib1isQ8qo75KNDKLojxvhT95LuYbWe4EkpivyxRL93wRrDCBdGgwJnylvZolYrr4nt/cMJv+ECa/Po0uw1Zs/YYUWG2VSfDOsavP2vI8ejEFgMmn34XveOCYqH+F1hogcsmzCGvIzw2wytfBo+ihbENe58xFkW6qW7Na4joTFn2chjj49g5KrzyJhxIHh6NEXurnG8F5tlsUnxiA1MbNJrnNNaimzxCcP8Ot6aI0OGweC/BsNaatNBZ76Z9ZB+DIXuub9tU5ZMrTXghl0in/4AcCmzXF+BI0wj3F/ECt8AVEFmXEAIyIn0vWmaxUHCFRQbot0dGqR2K5HEhKkT0cSMwXJ6Dl1HOxeeNiBuQ3RYa5gEpL7mIRll1J7RSlg7FL58Dqv+QczOmequj3pZi4wJhAPqcH0knu6p8cdoVzXU7oThzOkh/4O5w5SgFRFECoZ9yt8KzPfHIFHDr1LOBroU6BW++PT3j0ST57y7skaaIvAhHShpB23U6E0YZES4K1YUOJ4Tm5NuH5+t+Wl8696BnWsKE5pM02RNthkNU0ymQdtVshx2xwrxl1fjDcZHTsu6CGyjwkzZLNOSRXmf0mLuxQcnzaZtqbtLSj62Bz/a3J59M9yJBvw1loVTiTgFrbMPlmQ+yweeE/QC69g6LC6Mf4f49+oi3Vee1C+ruLjDHC33B4+N4Gf0b9RvA0OSAL8JRRTsuIvZBsK0h6k221QLsnA/aAQiZFDb2HxSR4kPvOvr6lBfp6iX8lSfr+jrD1e/M8gz6qKBvNjY50hMcfJy4yDbjCvUtRvyvZBd1iT77wMQ+IQpaiSKwyKj3fURDUW38vDeddxawqVpKGn/iftMA75aguj3kI+k5f8GCwrsbPTFaOi1Xgr6j1fdSkqx2jdqaQpcDuzAisf8M/1udqqwSBwFojOy+CtRHvmZX0iPDTPDu+ApTlp4D3uYMWB7vtuGwoGgPyURynXS1ZrpZUi3tSmFY/dbRZjtBQKqW8cxQ5drK86BElmNd4W52NZrygb4yMr4YTUMOshpbx0d4/OJxH3I1tBsGN3XmCKUxqbk15k6dTXGKprtdJXC+gN7X4MU0P1L5exDeruW3XQK2wLHaW4B4GSx7nvZKaubuWnLrTszfLqiT8dr74xigSJTXNh6OC2O7BOdo8nTugmJJH/qRrXvjmnrj1w/AGVhfhwRv5mXGM28bqf3fBJFKOVqlVzoLLM0yKwxm1V96V0tGVXfXi5AlEU9dugQDdGY5dT661xuGJtjfafZeoOPSKhZPecbp/2XnD1RtUIkINZNG4M0aTWobupMsr9ABLJsh0X361iELlAyfQWfoCVAXMvJ4j3RaWuIgqtZG/yUceCNYHK/hztNn02M4Iil4WYMCQh015wbLEopGJBCzLmTf6LDE66f3wDHyNtEnUKjidFYYnHDKeRKYzjn111A0G/5lFxtFUJFgrVlM27vMUKfhBr3slwJVsUCRYoA2z7iaD/TPFkyEBUckija36g7ZqAjm9BgUVZ9dSSTWxbzYpEB90apvoQ61RFholKxU1d/5+KwLTYmEtQ0vSkenywx5yqjJcGsMqjYhzOpCiTAnXs8k2svq6pcJUFOtEfwXTF5rHUUhTzPRdoEFWibSZKUjLxdg0wR1SOSCZ3TZ6qKcCO7mIzOyyJcZssoDcGaFChskizlmoxD44256zBDe17xC3cpLhYySbI8iEGMy6pjSQpCZ8JTw5ktp+NmaciZCCGaagqlVJcRCOfiAiflE5K8V6YkucD/XBwlIRISix5jQwSIR0GMX0rWIPX1103nr9flEfGcHagUS1yCkHrfHNYqjot+3cai6svIWRxqkBXAxMKCrRcgcX1cDr18/hf0jXPBM5HgUw+aKLP5Utp5yFI2HlSaPD1t1oUfFtGR4zELfFideoMdaoGPcF4nDmOLOeIopobah85LGVQTGDHhVesZ5TBPnt7YePv1appdmJs/R/WHxTcBa8NNDWozV1b1P+mxuVOsBRrFyg8mPt9fDeLIcm/BdCf8e+ny0Yhbx2GsRpJ1Ov0V0tX34qvH0Jl/3snN4cNSKI10MrH/c8RWIUuqtLOlZgn4VyeZPuWhFaMzcQjRy0SUY9l8W6pKi0TAnYI6YBZE2kjdcW0hdMdL29eNqkJxDoM+a9O+e9/jr/AUgG0oT/wLewvdROrwScUa8vrg24KNoeA7loiAql3jmeVXgIpNdM18jqwURkSz1pHFj1upDfkdBvPqJGVgCgR8oaPHxZb2NsrwBBxh1tb5ivvK5g9fzfqIojKrZMUZzTh4HZmycFYlrH6CUPLQTQ3HtHvSppDPg2iVfGA5H7kHTQ4sScjvX/KGtg7s1WkzYWT0ZGZlnk/gIBOLa1Jspu1AJ8Ql+8thAZftMml5O2aFUnlvU7rBjhuGLGjBnWVOulAK7oc6N18C9wa2XthBowxDMwBGezQquOvUArK2tYGiRGugsdyrOiNMOw5fnx/eitLIpAtXO2wbyp6ttXT6JF5FPFxPMvXALhAjxPEtje7fJ+/J43J4Ktp9dX+aYH0E1sJwrk8Ae2jYOQzcgWQ6Oa96oljeLiYN58z92OzcXjw61K6fmqkLZHgxDWsplv7+NhZyetAZykfrSLgM8stclQ3zzc0BGhTbyAJxUg7LrYZhlBiNR8z31GsiNiFTJG4x6yQptw0j60JvcttROhVl2PRe3WChY59+uk6CdURzQszjZOhXeYXSEMoFjOn3O4L7EFPPHRiFYXHszk2sGySw8CvcM7FTpSYLnfOzicQDdjGMgFVwaSDDwMUIMvUaE2IktHUCBLpf1LigvWLW4fggq4IX/md+dFkdrAyqvKABzjlcRac8dan5YNEUFxHaDRLjwPFNaokN9KPcZlshLsZfYt+8Ff5KxA6K1SUhGaKA9ARWSmHLqjsiVolSP6CJNmPNUoHY/MMEcZRaiLM80ID/h2W2TIVRGhYP4i+mb2dTd2kIisvGj0YGd454NcBDw0p56hDZDOLCSGwCaF5wMQK2hkuhJ2GLEVCMHgi/P0BTHH2gS+5WAd7vPSSXI0eh/Chb7h1sEKoQl5Lu0EzoOlNqVDO/SfMW5sRMfgNaKs+5+3eGwsJxQcd8SgnxQa36+pih+CCCJ2Ro964ZWd8RLIZgS11wBq38FlCTy9yPgSWx2H0AFbpIw51AS8K7HlV+xs6fJn0Lab+q68a/mGHG5dc3KMEXeKsdJf0bgZXpdIObsaoFVzycTbRr4+QiKAc7GHMhpelZ5bA1l1m/ym5+vCkb+oCnOHLp0Nso0d4GooYYM5XfHbVBWiSN1ojiwL84/ia/e8vlqcrP8Hka8ha1xVw9EZZIztqEGKhKiF0iTyrfbVHeo3Rby2w75M1OGrJx/WmIjtsjGsYeTpeyWRpaoXFCGHBib4BWwPpBVSLWWOjKYqeqOmXJK1ZtZIQKErT9ZooTxfe0xrmeJS2sLghBkDtiz/Ce6XQ1ztVgs2wPD3Z0NdEOtWXRl8YLiqwvhHp4h1bwnfOXVBG5V/+2FzK/l+N/+00wOpICOzgZxWlYTrBdzW5Qi5aeR6BFg6LVz9dfFe7XKwsOaU33xZyyB2ewfShkAYvCaThAOT6K8xq768gCDo7sep+y4G4ej8qQ6XVKdA/UMx+pnSV5jmSspLqchLh2epHmKXxoN7yFBbnd/YakypIi0nWZtpmQZa7oJ+yQO2gCZuvYgDVEIIsgoGs8yXFlMLM8QVYyWFPXuXrf04Tf3z6kWC9Q7m9zHp7IJOZ2J1IbhjMgPTWHfs4UDKR1Xtj0fbJ+ga/pj3lH3fYcjindMEzHXLxuADsobiJkTctS/MxDggfLvFBLE3rWbV8P25TKsN+/C3n3SRtBfQ+xV+H3wIeJjWKFTYkMddLWIHJMFPxGnEWAzj7s/j8GvjWDNkNua0HUwLPjaGVY9sH/x/9Zrf0tXO3RtGASDKkIbockGMu/ZYRb8zeO2VjTobxckro5L6xl0KQ0wj3oUXEoh9vuSy1WDxARACR9VrqgC3WcssXU5uIuNlWUk8E32dUQY4YakLyuFxrXFO52mNivnBAeZCcKF7gXeKNHtkoCQ2x5q9u0uex0Q3WbZsxu1zan6TYQGYDOtbf+aZPBuyf59MWnpDXfEzIXzkcIuFuUd1XX6yeGpL6bsiF9dRrykE8AVzGcA0mecSgzftN8EG916l1F8+28MAJRgjk7xMMklkggGJZ6ipb4jvICHzJ0UNSallxHcXXIwuYnkrNRGrpl1Cu71ZWjVcnVDk3kdORhr9Xv0tZ8wg7GJP0rfw8USYNWhvEvNt5BpDIbUiMrp14YP1TCSEcKUCv+jsl+JEWKIw9OFGcfn6IhxsotgDfkQlPAvCEKqKIUJgWSmDtAQYRdN6wFr1xnoLV82JyUubVnopOBRhRCscLmJKFyYOqSx7279FDZIHaSIlBcpmBJnsdgiLBfCdEjik4W14fuZemQRUp1cG+M+4f6Gw+00WHH9WtYQWXvaXmEhAIVqh1WV/Gx6xZSifASEF9E+wj6XP3jpSyD9fA3xrO3QYzZUoq9/wQT57qEZl92S3lkG96h7+bvJ0562lPf/Y4w0Fa4G8elQ4fS6NyE9kiwquJEhIGY6Qy3/8LobLg3km/T/f+wxtngjRY/2vVK97rUUT/rqXoba6G32vQfTX1RzRi+pFlUp9sSSR3Vivd+p2/4dxjAsp+bNCgUrfNI0G632PaiWYcXlCOnrQoF1qPhnWCxwVWBTnERoVaWfECejmyVidiZiv1ncXEy3U+e0roG5icnhxl1VIR/+ey9VSSPEjh9n4S7TcEj5kaDl+Myhix5O/sjeDTkWYvtSUyq2vQiuDFAjd8GRkMeG8uVeAqTShHvEqZS0hcnja01vWqtcLm3HYnEiDM/tADjYi6KGtb2GlV8YahXZ1Bnh2Yr4WFJlIe4YL0MTWJCI4n5DAjga9ZmWOB+U2p7VMmaxcNsGeOkpQcx14WNRAybXr7xPQHbLJEEVNyprQFeegvLmIK/iX2AWVrfh8oyybhgmYrw8L41Dh4K99vJer7OPN6kjoWDpsmylC1hvVmsn5fcEtrHTdno4PFrBh0OBnwD4IIdOFHiLpwLswZoEvrAa0o2QvH1GiIqbjBSVgUrOUscJa2X6sQaVggdXa/R1OPkDm2DimEWnmtfSktbjFfS83RTZbrsVYAvbrlGKDyhmgS/fWpSXRLuDTsPl2UqZm29phHljZBxd5KG+83U3rCMglJiXEzHSr4N03vyMAlSuigJ8z7LhftNeoBjLdE2sk+QQuFoJHi1wBS797GjM96ap2SCUeNqKoperiFCIxlmDMug6mHTEGhm4FJDOt5eOCbF6UK88d5hGC7hgNNxiw4XiQfX4j7Ek1u0SRKhSskcAR6S0iUQMxqHVzDJgm7+3iOatiZGDUejjDwpZ94kruasyT9rxBC/2wZQgLzLypbt5dRRtdMXqTdbuYDc63FUl6zFFQtH49WI5XrbP5+xGe+8u2aTLhA+BY4XvlUsOh2b5QJMR+HNklbx61Bw3wm4TR00iHdG+FDFJNyYerOVK2qF/ifDl9tnkMcjJuXO5bTG8ROtQbTHr9fb55iVd4DyTuL4NlAeJNQDlHbYmRTc6cv1FsAsCUKPgaLCW/jS3I2J9G0NOdD8CayWoGD9ApFYEPP0TpaXjBt8w3uKmCA8VtEoOu+xu9Le3xAGD9izJpolRU1ms8F69ujeJEDRxP/2HcY3RNGhcJze+C4v5xTJbiEhpHJnEHpmZDhUSjDJU8L12eJsXTFtXvqmKYNOHqDwIR0xTzozEZGd2PkltKEMiTsSvUWOHKHKcmt7+RlncwQpVCERruMqWMpHKwIuNnQWPqHS46blqJ7j1bQxjzFXFDHRrQeyVUURf/U4SaAP3cu/Uow+AKgcRQq/OKFWNXHyUlHu3vKNH/x1XntGppHVmkYqeiA9b0TGuSb3AsXLOR3i7ZC2MgwdY9s8i9u3X1OiHhT0CjGEgvFA2GIsxFTfgjj6rq79BXubb4ZZI1IPyrgO9GWOloSotMO0VZkxR9vREMMCZhVu6Hw1C7aLkoqponpH7/NDuap8jCXNbuC6uRiVgjXYZmYGzTXoYpHEyHmq1xlb1oknOXpfe0NbGLm/pcAgaEVz5bVLOTbITocqc6l+dFibcVMPZHQIhybuWzE2XdztZ4Q+13+gAIusG/iL6JQRIVcJFTrx1iF1LUmbEJNWIZd9lJvGwBJhoJKSMyhkdhazNYj+SraMSgE82X87aimck/2KXigru4YSFJCx5dbQNsTRjHqzyUGkxwkJQmr8oxYGHzZZGX3vj4ssNnawJjEEkZtwaQS3TT30UUnc7/peKcxFg81qHiuFwB3wNtf3KVfzY8pVh6RrKY2oVzAHR1Xf5EXucPdX9Sj1N/jkFuR3fQwNsoMI7B6OTXNQUwlXQWyit78imeBiUV8OSvzWoekfooTc+wapT85BVN1xJKvUTSVhX9PnzrY3qEtdXlLMvh8VUGTncSFmRMGCnUgr30yxZ/5zPwcTr4YdWmV7ch3bmk4YSq8pRhrl87TyjrFlOqjbhVRGB6Ggg9nR5y0lldren7h12pxe3Pr3afUPiX3xaErpetXJhY6Qz/mBHcqwGrdvH4onlQ88HMgPkCTQI3CT3CWWrS+wnjWAwFw1ITUHtbYCcLifN0FoCQbsLX47dEPTu/of/jrL4aLhhCKcU3slVIDouF+PRcl0HKTQ61W32Ntyavk/9Sw77DcidNeCzgrqmrylYp2HLln/ItbBmxrDiXyi1/hq5/LR1sWCks26HrV1nf/4CBVLrZz21Q7VIpfMcuyJIKy27/LpUg5Np4TU1QfgV5PXi4b18dvV966/CJC50HCfF72GJHrJHf3GJ3jHFytvJtZ81eB7oYKSX1UkTzjEjrRb2FQfoKTzDKE4Mb2psLTBBiWf6nuihI/Dut7PCCklYmLjgjTaFPsA8sKzJ1pkr5sbytjiVHXoG8+XcQLKZ4NjS25T1ht/zNNYG4aXzaYzbDnW4yvWSVG4QkswG07npQmtnmbwOyNNGlyV1+s7Y9wTkVstSYzbjNp5pdU4nCrhwHjCtJ2HG4OOGA3kuiC76Q1FgTmIl8dsv93MojBbcnvfnbTZDjbbwbZ2als7tcVWW2y1nZ3Zzs5sb+e2t3PZvQH+YIiO3e1/o70jWIGC6KBhet7akobT+ORmgSQlOJnrkiH8/j2e07lcYd6FYJPom8DT+EHHwPvjhWFYBd8vdBa64l7De1FNcKHOXExwkZ/T+Ti6iT6fNjHOmYlP2W/cmabnk+33x7+oT7eh/TYXZdDm2V6Gfgp4kNEYZ3Ps3p48bMTYzg4E4MLIrDYiicjBC7+25lXnN4zAQ0jnGyB8MzGFmBhmJHOTonT/TBkVEaMrR/NIPWOxd8SZYe9vIYT7Ua4rbKAw8wgrJ4aAFAhD2aBF01GETHlTLhVUFt6fjrz7vhYkQPHIB1Te6/wiHZ3t8MJNa9mnLkzJc9y8ft9qfvdYy2kzW0bU8hsrjQaZgOYdY9EZ7ixnVHEB/meme79lr7wXoktXONye51eKbdKLSSLMnK6JdPr5wgazNu9k2/I5Eb/5FvbAxCB/ilHpkn8bs8z/+qqhY8IQr5HkMmBEGAEjifkvYtAQUlPbt6aX/JS+msiiMdtMGOlEO6RX0EKiXdhXY6SGQOKXo1VpsRTwFa54ntuM0P7KsifLg192sAhDIKkEY+NvWRO1SjVrndMfz4dvMwqAlTJ8WjJ+Z2wPXyqcUlw+9tWUBkmOviPY8MRuCOV7CNSXpklT/aMqCuiHoUwoz5AEe1Q/e3Tgwo+7O3VA26Igo4xjpdlf7Cpu8Bbti8nx29v5kCaKKJIjiMfKE75TvdsDGdyacpTLGopR19hKwMaAwFpuyFPXrGxD4IdCcDNVHfA5D1xhoYfwAaz/2p7Vz2fVzloHI+WCVyiwWJqz0+kW4A780PB8GkR6oK1+g0vMStl55Xv2jgd2nny4eaMoz4UCirB1fAq+hB81pqVSSsfyLrG57o4dj7ozEvFcf9hQFDgLeDigkfTvMkaGRFuDwU2QejtqerX0LTO0teMFJxg+HM3Y9vP+8RPOtbpJfRX0Lt19xFgm2BSsngUGqK07g8YQoOjd+ChKhwmDqRZW6k0dcEE2wM3S6e630eLSiSrmUTFUimrYYV69UGsJan6Ekcoyj1NkuQvsvEqg016n3eHa9TXydI6dwf4yUp3YTHTDX90EPzqIudClKcaAT6cJfp9TxS4vlKZkuJBuYJrkIRUZ4XRLYZwM+/OkclJLCELHF+ubwSKjVlRVurfeKHEuriWdPqpO599G3ZUZi8ZMETxUv7+aBormKE8WwKSoOeoCRhph/lt3ABjya7/38lzHsuAMreD6+ZTilgaWh9c/iDaVDUGh/7gzODp35ngjpModrE1GsINTCgB45AnBDHSx/Foj8CtbL7tSdvddEeNxpop3H5hBWermeDwB4DyETz6oZeTnTzTfV3bkkt+4ucMECOyXt5mqDU0DrqhnttxDAlvj+yq/3sZXLJMKhMeBt6h5xhNBaQ7vd6a67E2jZVJJsESsEl2px5STiQOb+fGVU3jeTEK3MBbpuuYvy2/VTdpfd8HovLbpz3eLdaP91dxefg3h3lc+VglktH0n5NJjS5g5sD2Cm1s/Xvz+9tu0v09p8RN+qa93ljrW+lF/xe9ZU5X5E0l3IRQhsq4xrCNjjJOgvzYKAdrm4XRIX/xXDB5qsb423DhPl7ObHlM8GZ+W6abqTMEWCxCrcqQLOCyn+2xx69j929s4B9+LaWs4OJ0HP9rh5828zAuW7TNWt+dAXh913moDBt0g2KvJZmqF2kBgNrWI5f0nsqxG/DG1KCMRX5SM+sB7Me1ei2nn4NbFZf4pUEIpelV4MrrDO/gxFcmk8LLpoZrfLhqmrZp+3wZP+jWYx7vkj9amh1SGtFPeoZctjtLh0xY+gAz6DsI1KMF0VVA6SiiSUZRYOQ+jLJ7/xL3gqWEMWdnqNqkcjjNL79JDGKCTo5UxgUyM1gTcaMIFLTzGGMOTxuceM2VP3BdHEHBPe/QmYaQJf5e/3T7raRwu8+AgnlXWDbi8cqv2vDvoefTW96sEAWim13dbmU7GRTOesyn3+kfhrLeYRghQoFBnZxGe9RWJEVPW8YJ/9P+IfbzqxWtGL2+KsTrv+EHyE7ykWIasLuele9rglXQGem0o5g3bCPedTkuDPAO/zYZmZRAPDbR5maet4E/b8MOoj/Jbep+hkv7cW/gFHkAGT1CNj8zTYLXcRIpxLPgTWYP1dAGyPL9uAwHi9mZRE1H4jgdHOtv4+cGma7JEuFHZ4c8xGk29Yvz7sYJg1u3QQmkYQ63VRxsHdPvJRePeAu00BL/Zc20KQY/9oEuOBC91gebfpG/pWIv8sLbHLiT5X/KcNP4490EQefSaLGEKqrme33mAD+BAe5S2JNDJ8YZusace2TA6Ke5o5zmMRXhiPgNGiuhNwp7O+ERPnc79X0V2bg2ngY7TUZpcXv4m3fW+GecykL6VZNQjMaWw7xFGHKqOB5E0QoDYZ2ep1SOUG6LSElN7yzh6t/l4o4Xm7/JkdH6wNnopL6pQeyzTIeusfelMOo858J/NEZ2z5uwQx0AIQtKgaSGz6+Kq15zqsAPXbHJObKEI6/RDW6uG4i544pwgnY7jTzYrB6NbaXAcxpCxp8cU2QyaI+j1Y1D0siveXIh9PYBeb6Bc/iV80jX4Dc1hu2kZIXrMtFAkyfahmBs+eJZE9RYFAjUJmIST3hSByhqfd+SJODgYYwyYhDHGgI0wBIEBkzDGmCYgXca2vk3uUMDTKsDTKsAzIsBTkC0nGR3QNzkYI4cfwiWOdxFzIbxZsi0MuhBlfjsM45sEHoo9AwprkAa/9t+7bRgH7BiA9WdEvJhiMULvxQbRRYLhDD+c3nNcMZ13noWl6QRPF8FNu999s8w/APPIJWTUxBkeSpx808oY7bbhOSmT9PphpOkv6LEbPCWSxzWt0ZqAZzpjaIcJRidHUsRECcUIOikezAGRXTjddcvE56GW4Klj9QyXVx7r+PD1oO/NOe3NO3JsKkuxpikZWp8BJzOdpilYwRJ03VBPYonFONMD1cSjeP0pFePH962MRsc08AkmmfMunXCi8wbJDTTRzUrR6HwvM2deoDh735Qj8hxJ1NBcQ2F1uj1twB0zOw+ayWeaMk8kMGucqCLt4nJPEGnA3yQMuWjS/06ka9KWRsIo8zOEw+QV27gsIwZKQw+LNrRkrI0JclmFCGX3wGfSCF6ay2NBw9AhIZoVLknTRJ8jI1ZR3J63sQpoVS8jh0L2T/OyRwwfYc+obWLRdnbMllCN/tQ2cLwDpjG9R1F7TPgVFemF2mWPKLI9qWmp0mQg2hNExuPBoFbJrNZwltla3TFGnIoX+GeYiw7LFZ+uUh4kPwqWHZcXKp87LX/Z+9bFcsO4ObRFbdvpbKeuJpDMlnTh7DxhmodzF2Ya7EQrz319ZIEtFSIBaS8Rl51yq9s7QEseeiPqb71ot0FIEQQTmDT7XDymGJVCuCN38Kq99Vkz33WKBsQUIKOcQCHxaO1ThQMNWOzRVSq6xaa60elPPkuKq84oL+w/EkbQMBuhi0tTJ64oJzAVFss9inIri4u0oi1U0QNwtMT0tKvUTFWHvPjBUJ7Svgj4gIAIta/ZvzOW2wvH58MUM5yDAaNXuZwYPo+n+rpr+ivsK4WrVkOIagfIjXueQAQpqZNA5tY+CyjXY6HRYS8pXJT1FY5vZb8lWFowjgXnjc49YAeoA8FdvF2KS29TzBSA9VfPXlCH5YpPy5QHyY+Czx2XFyrfOi1/2bvtYrlh3FDsRIEKktMVwi9gU5xFRrLLfQl/I7AQtigPtxSNrZ7ydqdqSU+BVls5oTE0aXRdcg3bmeFoCS7HEjx/AXVD2eBxKyZ8ZpXBFONQrFBLTWAg61HqU8BMGZPxrZ8GEGlGFdaLk62E/IgAt1nVXDVBSAQ4Cfy5Se1pd0ONoHGx0GvKcV0r7c4matdH7UddoTb8ZOy0iE7vfyerT6XVb9LnF7yCrGFD/mG6U7n90/6ZaGfYtLUqF51fzxbfBNS/CGfeElkLMLHZp3N8VF6uN9Yq88BPcXc6VjRm7gF0BnQ1RonwaLukBAXWlzM1QK3XOu15Xzhbp7LIc21m7I1aqy4oS8DV6sLcY0UMlEPHI9Yik8lzUDVDBK6awlg24080mM55YU6X0gG8mx7XaIIVO36yOXL4FLdOowqv2Na976Jg4X/RxaDXL8u1FBrb7Bla96xfipR1tkuMWiBJMlysILu7cSsL/CJT+FOrlj8g2xgJXcsFiDmv6vf2jcbGIJ5fXR8yW7NLXVWklHSXKdGFnpBFD03Zk2b4FZMxmuOoUFNqUxl7mEBgCBrg2QwLg1hMRtJOJi9reoEW5O9gUK3VaJFU4SXNaxRI5spcRepDohx8OoS8ydEUdMuwEonpQXdfYbmw1unK8kLyDrhQAZBhSi8ngUPn9dADl4aur3A6uZ/hNc8VcoeArvqIjWAVS6HrxrDAGNXdJr3mkrWCo8b64OZqdkxwbsEETrGpV+VJZh09pToo6z1TByUg3fcztJY1r4AcFDksVm83Ca1jYdsBa/utWeLAxpZrHLyE9FXVowswPWTqjpYxdzxTAL6GmQ52AqgAB/T0AVa0I4ZPCmZp6PCFRgz8vvEKr0EX4lPKpnWM6oBf1Ilx8I0KTbwajJoaWMuIzqzSOeOLoZIFKuw6PR8KRPKXXCwcYKYENQjvAtMdAEW/zOOfjagP5IfEtEYSElIAeHoxY/PRqLwSVJXDyKmYdiaaaenjrNSTtacTNfW7OSO1ea6Z6NgXW+L074iluR/BRDGYcy+QSTE0gc1+k01QK+e79bjekCVWQR82KtgPRcKW0OzOKRIHIVGfUMU5BYG3LN24Ddn4hl/eLAYfR/qIGObBxsOyphMCr3JzZSSj7DIjMVX0NPrjzmdLaznJE6LdTp228trnlZATHPxAGBlZWJh0Z2Db2y/WbBpUVjE6YxvHRaK2ndY8vmkuzScNljWqKM6VV5XoEOLVSAlt2VJkSy+qG++cqdIsJET4tIcN2Gy7kDUapQcaVd31p0E8KGnhfSouyG7yxHqFEBjRM29Pwxpe1LpAJnVLzD9M5aJ9UioaPmMEIQY4TjYwZWrV5Txy8TaQRHbNjU5HnEKjzbcBEfTWaPKtkeixaHEaUpebUg2qKzNmk8xWyaIMFbi5/DDFLhi0urtFy261aJ8iU5QlawmuTnHSAOhIS9h+g41UE3YmkHi/f/72E4VrihGCTVuMB+HOUIlGf7qaAy0IQP0TmYy22BsxgqHNXT6S/NFUg0lIngHLsB0+fTNo/tUZXb3JdVo/ViNSMCnkfFleEOaQsvzn2tF7OXnSca0o+ICCFidG84+TSnE+ztkG0FGAl6K8afdi9HF7qu54GyUJrdzhRPv1TBPcXNMvTPl1gZJbs8vV09DxwXCtKk1RbHVcpHu8734stof/vqeA1N7Ms3ySfdxXdZ1jRH6ug8jIWaf+4GGxgPa3gV/H+7ZjwfPfqP8x3VH2ZOR2PpJ3f9nitVnSg9LpXvtsG7cm9erF6ud4U1efpwm7SIlsoyWWSAIQkVgQEkmx4CCfTut/voiQCEn3nBIyPD/lxY26yrmSSuak148wUI1q/y0Ue0T0NOXS+arp+bV0LBUyuBe909eCslv9UGPgRzubsffn6ozY3rf+YP+/Pzv36fs5Q3H4kcR/V/UzdLPzH6Zlke26zqLpwmS2iVdQ8vwrgwpSa+hKZL3nsSicNbwNcp+Azdj/JX1zbwqpXY1R9vKkfYwu7GazXT2T2Rp80WkQd7V0ZWLHm+g96ZksLhX0uUIPJFx/Gd0I1/DLTwwlwEG5lsjBzhYMyG4KtgKDCQrM0683FfqzohhAheQLzat0IlECfP0iYDRHo+fpbI1yH2xwaEA9hntBoajXv5ASGqyL3tBQfvpbTUPqME7ii+snOc4KfwKk4Lf9RGGoMB25mjh92kmgVewmw+g43MzDjfGk31C4cdazOEYqZrPqvJTDVRqPfHZZN/0QUCAC2Sh0E6XgAVhB1OfA2TgovVgVOW968nC4xE0ZnG9cCZWw/cCVH1EKLpU3hKSYEnymjWvMA1BYc5zT2cz63cSm5j6l+jeU8DR1pEMAG9M3dl9m4A/DTNzgo9dQBrFGDdFIHu7TNnisjmrBtSTfYTYcT7Db3k4eq6Oi4sozcbGlXl5Pb4C7N56xSAf2sJluYJ8jvsv5OVs5K+zYAhR71elVGlHlhCNjmXHyGdb+RSTzuNyjQTaQgEF5UiiIeRJhrAR3KZc5Jq/v4eE1n/gssHPk0RCz8rXCOOXXvqKUZpoFig70e28QkTwjuW/KJJyEL2YKcpqQquUOa7gmcO9jRqd6+pgF7qsinJntnfAoNADqx2ZQr/u6yTu3JUxvYVVsdSkgvJqyguGdjxuX2vnlHwCOYsLbKf9wMIGttZ+7CXUt8Rp3NHp3TIgNEgkrTWREGXJqYTC11+qR9CQ65diHZ3o6lTG5AEU6O7S/T5i3zEajxwlpkQAI2wN6A3oKja0SO5FHvhxlGYIkJNfWl1PEAI4hLqvVL3QF8RPcBySYfHxVKRLAz5z5jqefdX7FxvD4JczmxEms3S/px3U1Rf88ZaKOdbwh/vwKk4XhOqoJx1uK/FvT1HFZRsFPaq3hr9mzJEtLAjSfOksPHJAeWHCXtgFYXb5/OpSoHun++OTMGKKtwe/DRAReJ1wM12tIObFI6PxPIqsIFT0g6R9q+mZwXJYZ0FwUr9YTOE8+RIUr+I4sDhoUA1tIiAxIQoiEyCAgRMlY/6LPhYIqRMXWrDcNCjgH1I+bLr6pdx1Cl11PtjRo+vJLiIL/MsvOF5l5yeiS5fnBCXiC4uRBwu3e/HTLijOmxfX94wZgzOgLCVRAFLpIwkvw+hYgSTqt5bE4kVm6bx1oSMoNJ/Z5X2l94Oa92TLP5uFmAYV0MqLSy7k2LDKkxQ4C7yasKAkKuuSXjRXyZEZ3ycfM+URmhxcBtkglfYskvtZ1Wy/CAf0pfd5j584U7N0kkDuTpQGBjpVBGzTVB/HwKjEvxd+IJxUL2LMFxjTAbRA/bvE4QzcJHgbmTQ/L1HqrChYe+2IAq8VFOrQYoJ8BPsxZc+z/8frBWsEjFqfd+Qxdkbw1ZgqzWWof/79LihggFfR1WwXiuaHVHUQS92p7YZSI3dajJIUYbyXH8DrMjKlTg+wLirJ9IgIr2/zp1hqZc4jseE2ITuKWj1Eeo/+XxkplNfoWMtkN86fL/5po60bGd8Prk0y0xz/RTQmixbxwrOG4Ms8DkpO3YIQqE0CHvkIjs/VzgyO1x2eXme00CvrBzv5MLxpsObRgyzhRxLO2x4s5qMa3nxb77h487l0vPALUUpAm0KcxgO06UhsADKG1nG2fkW1dlK3R7dCV2KDDBCS3CGtoRjJmTlL+9zuKIuqk1E7kUSpUFiHhE+FYZxptfs+jRSXbUFdqZmhOkBiyap3mn4HvG0rCXY8d5tYEeD6Q2cUS4k5TYEowCr4+Exosy6Gpqs0T0c6bBvSVFtg08gQ3Gy/GqTWz+Zfx6G8gkw+nwzwUmnpxIRmtdgVaxQGeK9lKpP1yc78Mp3mEg6SyxI8qX/ZxSAAEcp2CFfX65PHqFSk+IWNTjlKUWDcOomv1xZjtIhZ1NHWne3fkmz66+UgU6wLveOS5y+ca1LmWIY3vV0b0F+CfJqgxDNWm1C3laRsw4oU9jeXLXuQcbKxU/AdI4v7Mndt/Sixnd64TEkq/ocL6EfqtgjtuIFj7Zizl1vsNShQjmmCJkcHzcwRvMR5rnlw91QXBaGF1cIAoTyG4SFLiKlxVWG5+FWPYolT0+WQTpsqPUR50us8EMV7O6QkwwhaVpZLJFOmrTvYDgCnhGJ6i567Rsd85x/dCCdduEs521+q4dqTWumSI8105wuzKQ2Sg0/USD6aggMGKgw+KQTZFMaQxSZsDvPxfWz3iJ6siI3D6GbggnGfqYvzviCcBlnENC9UYHMnSlcgRmM2j2AFdDMg7tifGpivHdeBQTiJHaUKO630Ff6VYJscPFIIGo9UiaAaQJxTLp9IR6BvW+N5AFaBoK1E4F+2IBImmyfhUUx32EAJlF1vgFosHQn0G/72g+u3KuF6bI8lrfWnnhARP6AG9U8eNTICJHZx3/Ly4VYWgVUgDhDdNsjBzszHgbLxyTZBQgSF1o69MMwZB8+12lwJJZy95tYVJnoZCIfMjusGfNmBF6D4OVsCWVVICmMEmz+CR6xRgDIuWRnvs0+oJzi8M5xp96QFCAiuIRizjEuzZIi5UsUDpQiZl5dXKf5mw1cSQNCEUA4Vw8elFPpGLZAVUTkDlnOnIPTxEQus4MWdD4y73H4WIYPxSqOluOBJCTgxAbPbvHvYCRyd9nR51eBVZhPzcTR4NiPb24IkFLEM6B0oHwUI40uUXAA3yucGtR5nMfTOonMuVPo6UR4dfhDKu0IEUm4r0IhBLg6VTjwfz8QT15fG86vE/GL1ze7cOGovSqKYYpBQ1GuYgEiyxEHbAcVJAJUooVq+fgR0mvieudzrwxZFH0yOBWsNgH5OJX3JPe/fOC2rKYxaTx20lOwmjeUmIzs2SHWSNcpttg+E0Vy+JbmjR5UvZFrwtBA8iWV/t/ngzR66WKXeLaqvZXKEjtJemvTfdCkyTQ5cg9CGlEXrLCvyeILZPY+B2MP85dtaB12TE3KG4VKxqnOuFrlGHQaBQh1aYe3fHuBDMdO8u89jeHbeL+DTcMXlMiydvkp0DvvdMhKu4KyCqEiPFoFYFeC4juq/6XY6gDXQSkQKL2VhnFWRvp8e3pYecYrSTEHR92wfoJbkt8w+wlVTlKMrPgsAe7Cjwwv1EBrcfI399FAo9E4AF7uVoT/qBtX1+mB5vasMIHiLj6e8rMdAnG6pstxTtYTTNegEblR8ATpBUuBU75ycCh0UrGttDCC1aG7m5n5bRWqQbEVcwsWJ6LfRYyP7G9XvShBcOLBDicCIJPGKLxUIkwpdM5P32BQ1GzhKQDsLlFN8otGlVgG+B4xzJ6z1NntMyrGOukIX34eJNYZOpaM7SubGyOykO15PYaVOkvU8b2ARc2ZdVP2pHFjivuSEgxDhSPXtpgjk0v6QGr4uDigJ0Q2j3dHky7IRpnV3N9qC8D0w+t3jH6J2CmIW1KEsnZ7hS45SAT6E0kFraSn8Q4Xoh8f9xG8pP6JgJgmcOV1XBtTGxRQ7d+uQPgca9/VX8W8vKWL4m8gbmvYyRHHneW2CcrkS19No6WMmvcWX8bUUnwqJwOlN/gJdfEsxcraDuRqpRN/mcPUHxIooRhnIf7KCS1Z67xeU1T3pApRQXSb4l06mZxp9l064O1GwmIoFThhBwvQzaaKoZGDCUuppgc/9G5bGQ+aMi2RbtmSmKyVGC3YkRwwsEK50/U0pAT5tcUi6Yl12qChFRDaiOzYM4B5G5PZDrNQZVuNDGrUgg5UUfo7PddVWZtNTNJ2yw7dTXtgLCzxRXLbVfhAFLYp6lHFT2dDtVdYU7g/f+64dDsNCn2QdCNsPoRR+1iGoRgPb4akIpF1hY4xtAmbYv3gzGxguCrEJNRuOg8K0hPExcQxBdH22ewDinkYWy8kw7xcsCMbfLHnPOKtczp5n6vwzKnJVoKLSvw5EijEKfsr8r3FgVjCvDoHiDzAXBIyOKdR/gP/OSyrwU0wONOrACHIrDr7UOHwrvpDG+hhIcjV0mA7zVvsksFdS9c2bZ2EOCkZCIoRRT2GkaCDUXLUCDg8yvX0OAKSaSJQTAMbFTl0a6rjgJKs5jZuQlw33cVmWCAQtHuSmpwA3n1E49w59WQYS3yJPjS+a18ay8rxFQqrqJQj9KzYJJ422QRLzK2qkpWpwIb+iUhxEtRQ1lI4O0TacIGI6ZeOXGh0UII7ZiJYLgLQSb1/XR9PNZl/+ImUApXoiZT8XbRIpT5dCw4weiEgDwAmVQkVrxVZ6m/33XaeeVK+7nAnsLkpMke1JnGgrYhqVwVAJr85faK9yC5AAxucxBBho3w2CFuRZwH6cKTPWfpgMZ1fpIim+VwyTHKUrSwJ0sLJisaqfjAS9KEOd3icZ7KWtmQCM97jtFkQfBweZ2uy/IFVYnFhIYX9jIg4kERCcilfexARttQ0HKo1oLAgKuYsXxsU0jaE1eb/v2ylKs0Ekw9xa/F4rDVIbHPjhOpPeUHuWa5rhoC5TGx/HsU0zoK/cJvkwmwrD6dlT4bO6lrfOQWCgPRFJx40IBbY9+NwAF0BFkflgxpijPI5f+05HR67Wc9tP1+Z6AFf0J8NXlA5oZRBggvy63jCC6IyjeG0P598KRqK9OiYULtVHKz1f1prYxDlB8AiRFfsbGOFCIymUDYA1Ea7bJwm78YewsyBrg+Sjhb0bS4AqpZGiu34jcxcKXxeeQpdyM1QYQxpac+NGY/UmAcnmEdirVSwjHuMXjsQ3fiTacpuZ7YvHLc9lO9P9tnv5GGh2/qcU7CYBemjvY4B4o4TbEWhAufDCBHCiwgb55ewHopOyNe1NqM5qa7hTFqx/XtA8TzPYrdKU+AgxguvzzMsjIiCnOdjUI60QvjCnlUnzo40v/GtE0DwYwffQJDNB5TvBOlkT+IX7t/2btxa/qtqdqGkJ4b01NVzPz20znDiXJZORQj6ROFYrU1SFCIf79N77ZIU/tfDeb8iBSarByVKyWPZEIwWVo85S2L5HnpD5/CDhSpX232aSrdj44/xAcW5MqPpDzNTwOgkXFxkLsYp4DYk0eOozvcngu/ncntAG/3HYbCWhXW79Bdyn9BUCSFyzYUIXHDb7S1j9/JKqPKrTDHEqvYsNPDexjq6Y2Y/i7n4xBJXDNQYbp8NEhPrWOe/zTXJbTsDjB/eWx1o/EP2SfN1AcNLAp/q3xvDzvoxVNiBeDZTNgAoY8KBrwCw2F9AHnjtTNGuhg8TK1vfk2Ax3UC5Zja6fFX3cIHmuz89Hpcp8oWpeCWqfmM8XREgr5gX8OPQCowtQE/oz/GBN5KvJjbhuMz6UgkBRq1xKk8Y7yXGz+/JPnJglkVgALLuzO6d+afp8TJijQbVoj6kyb3ZBcTbptd7Cn6SiRzHeoHK5jbOr/pJmkdT2nFBe9yHQjp5TW+TE7t+GmBkuUsLMoRqfA2DD6Zp3TQQMy8qtxPJktEHT8CgxQBfjhnvbII9/4Rf/Z8Ds+3FXTb996/6v+RCh3e5ovUqrFoGzFV5M2v3GzF1cCAhyxBXRf/8K4f1EBUy8YKb/GuHBUDdNBwjIwZVzzSXWD9IDXmSMZidB/MAmn8Q4ZmtF5MFVqgfJhDl6guHZEuOZSxYWwYTCwWLf8Wi+V50i9jyrmgwylrxg8C4/zwDW8hn8NCSQfFRHi3c/Uo7Sxx/HJo6l42Q/vlxO6udv5MWK/cbdgyISxd//Vwa0u7etn4KOGRZfdUl9xfvx1OWP4ykHmzHJzaLIAtPoptzj3llkaGQ2uN3IIotfVLxZbiIzOO+bz+Q92vrlLrMiyz7kjIa4dcxmNZ7GDfeOpYxfkofxTXHzaLgOCcAUk2oQYX9IpBs8uWdDe6lyyUHva/EWFc9EOUz17OM7Rk9FwU/pwMOyhpXdqZ0cfbrGcpx1X407R9xNfQfbw2+z5gVgwitsfxgrwXlMt6XQEQT/hN09CJ1jA9E48YIU2lMRrnKI/tI+uSCnfOw8ithkRCbp8gDVfa1IytqSyggQ1Y4RgA+HcItHAkhco8mz2Y7cFysWtHbFeXdDG63A+SkUhJ2ZxzM5SpVReJHbBFzWi4ut9KwrniOXgZCSISYPMijPnB0VWJvLw/hSjG+dcx6FDw75zbhnje5ohOqyBml7GRYm26+MhF0jhHDHodagnTTKhgRTrL1o19pRlhboFTKvxCOf2ujlkWyO+u1ntpSMSivc4F3GtAiE3EGRPXZzh05wFveYaXXbAl6dnXX8EDEqAIBitBNhCNerxBpZHWW9/yunzrAfHhRzi2tCF08GqEGD/9lDJvHwKOBi30i5lN3yKIa3PP+ygw3i/01p/UlPMnA0hieVYovstEOLQP/1ekFMfSRyX5Q7/jWNIPQuIOyJGuHeBe9ZHA+3RXTlq/LEdzbdfztg9jvRSFnL0/kswZ07mycm3CRnq9+XOReo2w39XrE11WJN/eCmMt+bfUQKTfXAIzPuSQS3V90PiN+i/BxcSDeBLvG5x+IWcFVS8xPs+c3rZKZmawn6lJ9oa9n7eRYTaacMjXAFfxg8ppyLJwu1SyfpAKNk850AG8IWyEgjyhQWSJxXcVA0TD/DG4Jx15nvLG/59XHjV+DpVb6npbSaI4O5YJrlGF4x25Rhsr4wj8Z9Bl8AWuP7zHo4nwo3/ET9c2+R12fYyOqhEb/26EokaeSBECCavhW2dEcEyGXrPDrX5Pg0bq3vdCwq6aLxTxMNKPOJ3a/rjiA2bCQ4Rj03lc/YEsNI0lqZSGJti3JbAlzcPkj7JSA1NE+nhWpg124q2W2z8N9K/7xJVqVAol9rq20LjPMJyRXRGuZYlRG8WaffWaY2scPAVqUc/UJbPDTP1xPlD3j9S3fzTN3sgQOFlgQ1WNO7Q8CZGyz47a2SrSBLksei5EU63gdjGwd7DitpIiSMRbBCfz5kPF9MhPnbmfsS67LAwd9ZuinJ3ExZVjMyCabjLI/oAWJ2tQ3Du8zLBvh4XljY/PMl41hH6BnzHAZ1IakR/8vs50wpRqqQ8gQZjKp94o2jxO3+JCiFcsMqoDwrNBhsQVGu53regsM0LvzChF3BG3ZldnimNVOmhUJlGGoKE/NjjEpf4Ubz3OVQ8/8UGXrON4GfoUgaT4kC7AhrjZgOc3XMznOvf4dZulsu0I6OstULcLOsIlEK2bJsMNkx90BvNICkEZ7QTBHWlV7F/mqhpwz21SRJVKnSbtbn1pdOI4rYWU0d1565P0cEuE3LPDggr2Fuf65muiBiNS+QCXHVZnB5rpLbrG7x4NasJxTcvAwJqBhjivYSy6AnjcwFo5HL8PTDFKWX1g2Vt7HRY6ea0JkKqH1oqi2NvUKU5TR6GkfkXn6RizDP5V9F//Jb/5RURZhbVe4Bmta9K3XMthKEYs0IaTszLUBNnGEKKUGUUcBEfmDcyLA78g2ZKQl5ZcVnwgJjdjJx7nayr5QY1z1o6p1f3qZwWBn6XqJugUK05Vn4o1ljECDGMIGoW+baqECI1J10WrRMH1a3ebdyxOvPKKkM0WTqpYoCx+qb6tK2zq34kPU6/SLbwl/2U9NU78jqLSLOxvfqq0x8qFSaYmKOdQ3jakFZv9cW9UPalWQ4k7MmFgMH5DHkrrSaxV/5ahx0Xq7a3FewDER2INWwYS2FSGNYW3uYPKSkf0EHTqA7j06MdFL58DNcJ/tCPWbjKbIFkz+tsb2ByEoA6o35qbzfMK+QRiJb9Vq3W8WWsMonuTuA4ZFC79xGj1LHp55ajsdnec6Urq05oH0fmdr8j2Imt0mMmqMfrQoO8Q2G/6jsj4PS88Hc3CISYO8OypfW/dlzxLI4uda+q5JSBRr2dUlba0wCkOwJ90k6KLw0EtmR3YfYsucOKpAJXuzC9shlLFhE2Hwc0dXHk/auRcC/bk8J0C1vIJ/ddQPw5bwLHxn1x4QKMSzlw48T/I6fxGH37ARqsw5RdGeh1dmBN42ceXiridPW4Z4Y/zxJH+d2xRQ7D7VmN4U5DseZ920C8tP2seeuSwz27MtjrgNH/M4ovclY2SMROqKffW9WxjNne1gr5a6CKZcqUjm2bI7vMgL5/Jg1TKrJ5bzSsQV+T71fBW3G9hMD1xbmG08SdtXN8F3z4m+61BuKdLwY3ogiVypVBXhDkdS1kEZJAueXf/JEm/oTtxEJe7UCF7S+/+Mod8rIFDvRGNwvKzbSLLKbfIr8eDomnARk6TQ+oaG0vSNDKhhYcrE4JCCBQYA+qpMrC32A0aJhz/n4md//pWuf6d3IdRQHoOSQNldPFIViXZGTapPslh1vV0BiAHzabQ+mkclLcWs8ur/8DGCwzsMZVtP1CRCmjC/ayjOkVP2GI7bG3CMmubO81Rwy0Heq9mfA3g1Jh7HpLRQrKvqihsecxX1Hno36VQ3lRtOjDrZDbtcQu6cy+gbK08W8L5Jdwl4/QSFMqll1RYUJ/XNNosT2VwOscNCWwnF7SPl0i9QcAs2ThrQQ9pi+WUXmT5g3nR0T4VURZ6gUAOtFBiZUNK6iqoo/3LUcmktsEmY3akI/Eaog7KUTbk9RE1RX2LA77aksW3Y8zYmSRXRnw9RoRxzQ7fBGb1C4wtPIF86+CrEnyRbGWl/JcGsUxSsvJUWLyCe4EDmuKUIs7XJ5IXSnRy3EGR2p2ZbBXK98NtOvjldcva5Mby85mBhNco3pyB8gtRujIjA24DvzsMCrjr7dxFRARGcHsl9dIn1MRBn4xc0Cj2uunOwl1sovkwJ/feo2HLXyK7Pwf1vg30xFtl3fzlTyfiiS+EOY3+/yoZso2t26uydbmoY0cmsac4P9M03xPZuCQmzBHer5F89fUhPmkZUP6Wsz0qu38S7vnijE/hvllDoS3D0UTm7umyoXvs3mWAgTMEQ2oz9FMTfx3jM38+n+erYMjJzcDinnjtn6/ySrPcZRV5mlTK1/ZAOuYasDF5wWlWcYlYe6xBghmekYqQMeGDFvceLAcESVYJOWSJ+kF10hyQjcCmEkarJdRyCrQ+vxtOi6DK6ZHdwJMtKMwDda6dcBnZYQnKXOg0lGMtcJiM5BoQhONXqb/PhuM17e4uHGdNKx2IhU8fPkouniQjUQ5Vr56rCA2jO3PLZN41kbDf79TXCUVHbT4Bl+s52hbinqZYaDsGXlL/vxEtLCv5nPDHkZCkdirH9u7qM+G6soUCPrulwuBDEYSYq7wDOjyC8YU5ZHp87ECm8xSw/dbGX6KgTkke2WDJYSWHdF8Lv9p0CB7aMYvP+EhhNCG8NP1jPgfD3glVYzpcdOW/mXGc2aAnYHhXZqO6ykXArgQanb0R+0a0suiOLxNXRiPmh+vRnhF5KOKDxdKa23Bf2BTP8fcxwOLAGIjkyoWzL2+XhwKksSr6CwK17iCO0rZAvfSGsI6h7mJq11k5eNchQtAqdPKUBhlzMPcC/ojz7Ru3hjnZ+KJ5NCNoX660DI0eKgX/AHQLyiZEtCTQ6kZzLOT5fF9GHYlS9TD4QtlEoi1j05KRq6X9rA89r/xE0+BomBBppg4KGnQYtLU1ad1XlPKx05m2mIYB14jqL7BwVhvbFDnrBVZ8YQ4Zj+fBjupzkFaIrSo1pYNGlW2WVUuEe4mxJkAkr1HUdjyF3RbitsOo2yBW24wUUTU6sCtHyKTgPe2J08x2ASocxBdBE3QfdMrwn/qUcAjI8i1pvRs12t/FtK1tyv7qHGuS02evFKSQiyUbIoZLJYpIyhOvzYDABEXAmcMEH5b6R8KuDWtUBenJFKTwOj91wAZTBTglgBp/k1DTvjfCu/1MJWQWRX6ZWiG6GneFllQC7VXxg8A7L36uv8e/3tRud+eqqtJ3F4P6S5MYW+cKfTUWz9P3KWb4xGBER2PCNWPPxBYHeKJWAhoE2YsK9SmCCess/NuA5B0WT/cX//7dKG9fZbJ/1nV9mlMsi8U/jbWDDJpzARQOlaQOBdp9S+U8KR9ZRw6+OJrgzVBkIX9hH0mRjc8wjtThG0iYhp/zBRklHcpUffj7S0VU2q2aRDvxu0t90wxtmmRtusUSU9p9jJH2veeN62eSNjvJqoJkTvQ6cTQvXj3Snpv7czG37b+0r3z4NTeWwdvjF6bmDj1xxnjpIQX1dhatyyqVqoYfhcB/y64VWpS6V7f9rAmeHITajGZX07w953pRFIT18b6yhChT+CQgCfQJtAkpU4Kk1IkxaJoFLR6FqSqtLM+98f/Apw3UTrU34q8CEcz9Kz1OV+NrsiB0/5LR/NDtUhiXdgSqR00GRCiuTIiG479HykwP9dpTEZE8XEc+P1XNTr2Xz7459cxCE7M52meOvgvH/gh3dL/2TJ6M1Xik71U8KwrZ9aKR4T/y0f7o3BNxdhUIU+itYKSNpnkDtH8rU1T0OuGI7dmUA82UEncoX9kWA+NEmNtagOXUhRr1x7qMhWu+8sHxvWqaiqNYIm/Xb+L/5owAyR0ythyBEQYS8081My2Jj81AU0IWW/PE2yQM6fU18XiEz27Heoe49e6MWuKoI43iQkwLLw3o1r+JQHEj4p6CKvIzDc04fUHhKnveLcaHN+SdtCslZ3A/aQVLZ2+tAt+Oh6GvCc3+R5b3q4LOtd4zcOoNYEmZcO3epEtDomQWOuDwzzFVuPQsNuGyL4AjLeNke9KOotR2F8U5Q70bUoT6uOLvzYB7425xnxXSw5Gy7613U5CeMFug06iuExcMVgCYu1WX9MSltSvXZ8LbPF3mCHwRdI9d0vEEUpEUei0v10kLFN++aW6gVU0+HEXf/Z524EpbBGJFodmOBwerQmqUGscLQuNRy8sXP9mJK7PXrLuN6wt+XJaRVne/YilyoekhuS5mooRovwYCOAFbYKA+4BfcZSHvK97lefXwxipueQRVIDcYlMIL3c/iaQUlsB4QwVFkr3MkJwbuSuGQ12UEO0TOhRW1GBhqAYAPA22fFSWOXoBjMcdN8fcIgMe1EI8qh/U2aFlZZjz4U+DM6M3jj9uQact+DQEH+eqaBDRs/ClJTxNHYS/ppFN5+8KOA7XzWNW2k0n9o9MXXcGJW/9KXBLpNoS6i7LtGsSjdcDyKPMiw9dUvO0RbjtMJJU4X5WeO18ZOfthrhIQ+sbaW1+P/Mye+XOlO4SaZWUdktb7Z7iKv+J15UBjeyGPzdmoZAMJ99Sg1rOJqr8Ppv0kxLM9eIZUDX77FKx2Ns5pauJyUz5SVyNmNqrcK2aV8Zi9J0p3qehMlAzFngXs0fMZ1mJUsC00O+wDX1uUb1DyiPmswkSANfbSlMzg6fsJFW45JwO2MZBP9RlFr67HbXwK3ovHN8/ienRhHQWUZ+OnxIY8q5TmbvC02dViOKHGSFnqaGO+lwmMdc4rKEE/k5uJucM9F2JZAOFcUSXMJwHqTpRKGpGGFauACCSS5vj9mFxvIcVrNXf+6HLr6yEt5XspozPj8ylG5q+XbfUkhH6x+SG2uK2Oagn2qOcA/f44rfGLZbXOhfSH6wrQ8PBrMGb3bAvUBcZBJ6U+ojp322Nnv0JlVTFm9YRUStmuhnFIiwOu1FV0LQfgCluei0k7YaJ2DQuslWtjhAXW+VaKmpUfbEU69dLKUaZHPXIvc3mMwXWob6e2wADE0YceiIQnjgvoeZrr1Z6LJZGmAmosksKcVFqxMvrtrpULwqGqjgFRK7kNpEbxQYZylnVqBuKGDtkBX7hNISfm9+Ud8dq37Q1SoeXMUoQ/wDkvYxA9tJ5+NGg4ljtnkJqZmGmT0ZfIKol0kX7/ptzkB+5WAmTDBfWg11NFI9XGyJbqz64+7YEb/C3xGZOXJFagStnyUS2zUqWpCe5zPLLlK5D2WZphlGg3gIQ+dMbtQO8zumXi7Mjd77YdXDD3y2zPKwE4CAbHY8izhQNIF0zXzNQYA0H3oN1DrHwDGKoCMIrdBynt8hRIOVpv+4MITng3X4P0ynRgE963zlo8RogeV9phEeHLi11R0aGOsqV4ZwCztbnotGxRoJ/FmwxtQ1X5N6SgpCFs0jgUHN0g7VgJ4c1F6nrZbAwIpSkKiJKy+X/2EKlWBV6lSg8PY1zzTJhkqeg8ZKgaa4b9meXMt9O0iiXfebFsu5z09Qsb6dTm96u7dnjnW2kGehkejrBEPwDIl8JBpKqwVGlVOVDb611ePvMwXpRL07d71fBIWtL9udwo0gK2/ciozXPO0djuvdkjGmeMuQlvN8PDbU7GDuuWOV9pA7cL9wYZ+Qa24dTuLvqyL/nwM1wKrF3sX3g978+hBGbMG1dv9mNbzFPEZS3KGN6nP78BcIejB5So1WZj7Zg2V3T0JV6/9HDbQwvXKe+LryG4k52Mz9S4Ipllnxe6LVFZO1Vh9vkMYIHUjV1m9/2YFizLRjpV4D8LZj9sZ8d8sDeCKeIaLCeMxXtm7EpzDjblyYPsDW9rbugpes3mYQF95H3M7sgYkwwwzUqy+QqxZyvKS+/9h2PqNy72eynwM9ia/1NcY6V4Rq0WNMu1dUJjLvWlB5uqoqvqyMTtBXqznRfnloYW+uKUF8BqWhqTS+JDEvGXjSUHgLrg0GtqiwqIjP03b1TOatif0Z6P9Fz7XD/8J6O33io0WoCFXmI1/QGDXuFuJwokL5yPy7UVL5ogEin+PZbpkIVfXOMxn1ZHlpI8d+AMEjCbrff08tekO1fH1kWuF9SlJObwhyqZvPtiG+3Huo0WtoNwkql+tmNobemDVxgs5AYyzcMRX/w2FCniAMPvTK9JKPExd5+qa3aIxrcosnxIT35ZVDhKbJMaSHgE/E6LbWrnUaL8D0DpcLNyMpDYNXcd+ZGD7ARCbu0wkODEypjnJiNhqI04NjEhIdlcEEMeC7nyT3//m76Snm9ELtFo5YTO29DDBYZ0bS60QcWt5upPjsmNPkLdJQAzoLdPPC7ishXPc7hPd9mW7LgExMIJq67ipQ/NmYsFDIMrSjDZWOazvEK+Tz54Fh9AHwk1tqdn/gi7tnpQOP6Fy8PibnvOyupNx97Hb8rtVlRqzRpd7nPpPr+ek5DedoAsK3s/n4ZK2V5tWNHy2op2QI50Pflnkr7EWv0AAxlqZ61RQ0/Days+VGpjLqTz+SQfI8ItlHa21hIDUqmVTaUWeRo2QwLGfPP52yf49qmQbVht5Pw+Oz2lUL/3cZYcg3MNZ3hmIk9x71G9Q9EoutNMa0JEsPUGnuA3+qobB4tqYpprHwrDPnJRDNST+usdjsWtQIDLplFr2Qiur7U0NS4SblG6vgULR0fR8COmvNK+xiaIjKafv64ED0+XMTOo8cXXSFXyoDFHT3dEhAUrvXTJpd600N6KzPD6rT+RZ+PujyA9st2HvLwaisdNUbukr7rSUMsuyXIJyENqzZ5l/So8DRqUkFiKNfM22Afmq63Ub7jVHorPFpiNk/jTBiNGv+IZVpnrIZHlnlcG1/ztCU8vsiTjCP7KFs4R+AaTz5LyE/5z5AHXt4fO8/+IvSWfxyN3GPW3xG1Q5eKkjJRGcSPNV8tWSnjzuvM9XgUItppbEyXuhLTIbZUt006JZ9gl8ucxDGH+vkBcRJq8+diuQX1nRazSou3u94s01roTtvQ7yQMRoXHLk5rW2wrQnQbJYv4LSqdNridz4LFQRpNKjjMbppIao8bRZFoqHGOUCJXneBUNaOGGG+2dindNBcsZfWzuy033vQ5JtTmPxZMw+u8Ce+MzPXm+rVVaKHZnS9AK4KktVUzcXtfyjztsGZ5hinM8rI2K44HG+vcFe4iAanIOAiUzKOzHweK5N1jriM2jJk6aaiVzfaBAdrEBTtUWaDt3m63sd6LtuWE0eqSSgLFVlcq9oGqMz7CrosnNxpSZ3A9OVOs1v2eg7AR7XS4bV7/9HLp50pIXyrOYtdbyTVaV4PXULDzVTY3lws6AQpuomSI17wP9ogXDYifDc5sngteUlgSa4iFx2p/P2IX8LbO0WratGM3SDYW6HlToyrI0auip25fUveP6HuDXIU2UwytUgkeP2/xqHtxjIuNqvWa6bMKS6eBNEH2aH041xstStA0xuy/6ATxW8nhK1JQVSDoeK1Qu0kQqUMwdX6NLNu3bEbzuNkSXDjTdgnLOV67olU3kNJcdLZk4vGB9n3vHyYP3QmGNkHDTY4vp9TdZkeJe7935ex3yZbOJ1FJj+LgJtzz0GGui+0hIzHRIL3C5n1JfxKlQQcqaSpdD4XYRkNHSV6sDGu3vP9BgZWreUq2k+Ca5cZ40aPR4t6mZ5hof+ZQ4tKVIz1G+COZOdNNGc9iUJ+apv4GYSnVYq6MrBTEurXRGAoXPtFa6baNaAzXDnFhkIqPETrrBXasahltd3y80ZPbk1JoHXzUyInd7R2h42hrmtC2jqYj3ZbEuGdEp/A76Eq8p7P/bikd/IIzmq03D/+d+XivdH4pbBntNgsAVXP13kVQpe20QwqL5QJvAm5tBylX1PlBL6NEo1lbeQQp5VsVCk9JzkM5plGGPK5ZuW4xfAkI0XPjoI2lkqCKIc0Lv7AFWF0Fw4VGZtVu305hJeASkRXh61PaNzSQBDfGvypJl6fp/2+csqWfJYCbp9+w8Qb35SjhqFD+VkWaEQflMk694fHXzr0OHm2kjd1ot08AsZZR2k6aUrCfenFjaEbTjmTeOG4sOarjpdDYLAbqE8Iupt6vfIcPfApWGTCVlsp3LW4gXSuVMyXifomhoROFqejVFH5IplaEjj7EPYhKB9zFhaW0Cr3NRH9rFkwF/V5ADEWszRl63JohdikUmLByeGHRMhcnQUardoCIaALQnPSFkX0ZixBGbOjxzqZWmQ6hz7gCtsARS+zA7Bx6IJLj8oJQBPyOKyMIJu8lDkt4IN1Jshp09KrJrrEGx9/bkUdbJlkwZgmMhruh7Oc0hN+O2qiOIiCqFI9Ff+0I4zzjMInjnPJ8sl8bEg3o/cRwAXSDwp/5VMkMr88cXyA4puTS5VT6Ik9XALcLYjvgQYfmUKUBR3tF7QI2MDaBPXwn6N7b4ho1SmFUGvNoTp8d+6hTK1WojuxZQiSofGgDHCxtLowdWg7AgpWxSRyJUMiQ4swvejBciKdH1XYSG1L6kWlsOdEWe7N7knZYjKNMboqUA7QVppGwc+tecXb3zBqwSgaQYJoDmbk1T3ypqNSLhj5euX9xKtfSo6NZMXoL5BIus44IhKP/Q+Vb//APMsgd2lCAMZj1wxxuRhMkNhC29ndYZDw9Y7p0Mw7z7YdjoWF3yy4dWdjddFdSq7ye2UIyZJkSmSJE+yU1b5DJxWVSJmZTY/BYSPHB1I5Nhz93yxh1cZYFYT77g+fJ6M+eFC4X+DuYNpdGf3eybgroj+hUyjiMI5dCKve2LWq93MrVx1IpBuXvCH0dpB3IwRy4xpQEWL6Em7UGmeepW5irMfKbbrNfx1SjMkq9Dq4dZQBrJ/8oThds+PVPsPaPBDd3VYO2DQvAJx23qm/OdSrVg1GOvl+gayE0eWT7HhYBA/fbwmkFLf7g8bdK3mF8soaN0Vutro82NB4Zg+Hk3XFyEfD2dbTwkz7/Mw00idre1waWmi5hDi5NWJOJjN+KBLA+MPHJHujnDe4qJRGbdvFKlIDGyK9C23dMPXbxjtIdCb5kdxOmowWxv0+ZZl2Kr2EiuzoFFZQGSdxxymnAJ+W0ltLNu6OtYkCztXKw90fLqv2mmwhlQTLzA6fOTUnTyByVQaZGUbgi6inz030dgba7iFOt8fZgrv84UnpY+JQxeyCB+2/47Bp0FNXa8PKdRKtu8yeiflYxTMxOndwLCWxTsV9r1bYNayLQbLKrsXO6ojxHkZ3KcSf2Cc6RCayz/dIOqxXyT6L0JtoyQGQPNRXwlYOQFEf6F2BAun4pD9d2XPTjTmSaST0CeYx+38A5GbqvxEFzz/y2xp9ArmiKByRNECVLuXXjTdr4QVpTX80c/hL8BWw9/Ut7qpEUHeDHPLUe2R5q76H0+M1o/GHD4GqMdhLy7+RpnnqgQsEu2/1+tTyebCF/VoyGz/iEouyDMI5O/5oQ/96ck8dZw9Ghtg+Xc9S7Jct4RbxSZj9PzTd05CnXin0XgkLBb6Waqnt8SS1iwmqRRTrLc77rL9EzV4k64mjbfTdDtKPPNX77hgx/S50xYe7wImix6Aqe49kGZGzIcn5ZTQ8Utroc/UZ+1bWoZNGTZc7b99vYtLvbXUtTcmNJyNjnsT2XWfe2tgmtkF5yZR/HJEXkDLF2PbzOaykCpwYqt9PYlDKqam22VMkSUFdIVvFkGfFHs+HPK6G2N3Ack183sYJAddCzOnZ4lvHVDZgOYfyvvRjuXMqh8AoRi8z5dE5RpyBhJMKhDlhQdeAOFQHlUKctsiNQNEzaiptUSj3QiOvxykzPmKfMO6arbbKHj+2WNREqV1to7x4WIIgUpTeZ5j+LxufiybIR/k7otQMyYhM3pWBbC0iG6rwAtkJXIL1LTDHm0eUSW1G2mk3Ax/rZlFZ8v9aTes/qHIZYrKRCNPLByuiqIAmMZi24rBiR0NiokDB3YUeQTCxzCuqZpJLyKKFYRpgsW9v1VDAG45PSmmAdB8D7qSncS7Xc2XBQnSQMfnRVlNiRZa93T6LgNpoQmcnr4BTNS8yPCD6SJFJCHf16Z8foSnmt33zvdXl7z4DttMlusLnwbHD4tEYLaiNb04GvX8SoUKkmdCh1i8/iOPc6DPLGNg43+HSeSe1uSPN/jvl63aj+1wQtBFtr/5FprsWXX6h4+zcoeMz7evvz/it8278H2JIN9mzARs/BV4/bx37/NY85cFKCDyUws/X743XroNTZZynytAT3+rmUgxth1PvynIvz0TZSoAyHCjtU0Bn2UWSwiJqIOpULf9ycRb4PguIXL7gx71NIpoTbFSM5ICrZl5l4v71ckIifmMtRvWmvWQitAieQUc9IRhSC0iQJeJpKRLDFrU2SmJahWRToqcjMiZb1PqmrV+U9jHzKwET2XCwuwQsH+ndAAI5oG69qEbwq/B9tEfPhDmgDkb7VDQXzj10bxlhbUbD4CksJfTHO2JFc+nFldL6qYHiIkddVhcRFmUp8rd2zh0z92qS8mAWiXnG8IT9aSocU/XYTxkjK050hZV1UVTIrLuc1O7PhRJTfbq24/caBqjzfTn7Hp/ygFGKMrnXdhNQnFoKESEQ40JyiqlM1rWrLgLiqmhKQJyxRHPGAT+XRXt7Vphj9/405F4DQ2lQz+mIFUWh78JgVPUsovdxZ25cYAMWJMR9cR+QEzokcVo6mXhquXRDe9id7wfM2HP69w/HHA9U0ePI5z6ItIKSz1VIapUqx00CviDhOJCzFZc8nzl0IxuQwdKyBKZIlAlRhXgPqIf7b+o7txsyI8+9aw9itVCJT0qjwpb2wFZULpeX5ZESjqUwfoWw3E5JJ1FESwpoNLLiZKjHYijqtSnbIp+oLWXOikSylkjbi4Xr0KsrsEGBrq1/o56Xk83rR/BGvsTamKBhNXG0LIzgLQ8hqeahrWAQe5IliKueVHAyQbLR4bu9LmfXQoE37A8JMc0Bowh2o2nO5pHJQBQfiJvF5WgXFkSEpp9WnTcjWP+i4ZU4hx5BIt6Q3Fuwdu3P2LHwaeZr+6an2skvu8WkfME6izpNUiztjjm1ge0GWeIcSdNnlxzuzlTyNk7tBOg7h5T1oC5gF9KrfwV737VlfVkrrbxyvh/sOjbPD3kY/R6HISGBL0xBWGW8C6TsAenT4NtBdRHkbRmPmJrYN0st9rWx/2Je10vwYrj6I192m1Wg5+pYUFmWE49jxt0a1uU5U48bjK1m7sAnYdTKiN3COognYUpOGxc1FQHnxC1YJdxbiarg2iIhMN2WpwunVJE+VcMkkxCmFiK0a7CckwYg31OF2vCBUOoyNHn7VsUBGWL2Iwxn/0ZBv/HcOWW4NrOm5PnH48i/Vre0MbOGobfV2a/s4j0bfP7QbXDdgOo75jIbs9pO2OgdlIWvVkWV0nJ8Moq1sbAmXFLY0KtuI0Z4dUo72qkqkG1IvzotAVwqtRTBj/c0Y7Ri12M/xV+CHRmX0MbomIZBhOEcD0pTxSqw94kdFERV1iCBR/oMh91DI6PJqoxQeBvvsUWwcbMzqotBs2laM6pXaNfXVLIRR2FQMWpqrPQga9vy4YP/H5db1D8moTaTNnuK/BkfPo+H91i7CqdpP9VMXFeH4e2efSu6s+kexe3sUhGyikKf4yIZtOTyb+7+7+4mqpE05/9l8jTWJYDUcBITOMHiUajKJxZ/ZVU7oniBQqLDwW63xepCYoVWNzeUXJGhrd8P2GEPm33a3JJkH+cZCxT24+S+EOZG+32ot7XxKLfu2PLYvBgwMqkgCHNSUZZU2OlSUBgWWJ4U7MpfYq19q0TKBr7Yg5eyqjIou1t4lG+0wi9dMi+ukZp8KpQpDQBBgmhRMDdc/Ke0kt5iQU/twV5X3S4VDgPBJMrl4+j4lqUkMPiWFmDrHyUCR9HofyqGvm/2ANmr2HWt8YK08cdb7KAocjhZfUtxJgUvSYH6G9Kgbnw6SZSdHL7TlDb6Ug0tUeZjsF+Mshvv3fM9v8plv06BTl33tJTw7opkcO2RKA1bQTQPdICbAI3KzQHzHJuaCFZYnnEA+afdqpp3eR/jSqXWaaDghmadxXtkmwCOJodXIKarXXKAzEVehxXolR38xRDIMUlgZy8ntsaYTCp+UFJwl1buAWsXbI0hrQEWnaGaI4OGbknlaQVJcO/ozvdfiCr6p+rxceG0FUTD8pnmUoHHyyzYrffTwAFiyIAyz9AMK9nhpDj2vEuauTrhEIlf24SZrA2ZSF7nG56+4coWmqHzE9kVKSunCyQk3O3Euhz44x2V/FJefytWXfkwnvvNcGzlTjnNipM/oEnhAE3vtf0Se2YpUaKB9ElqjMKSuytSU4auaFcMB5FemlThpMCXHPWwzDTaUkHSYEEXDV4nIMVArGu8JqtAdzoW1yoTR79JJQMceCr9O4CRlQqdxvw+wHJqIeMpWW1FnV8XM6vHOm9WKkM70h9NLHu52pqoeJ9hQSkx0SsOx9smJNioC6PeUavINcAFk6ge6y2UfcgOrM3IekEeESDO6BZ51sDuL9cd3w/ld7pbSESfvMI7npG1PaxP6+r8l99yWvMS/PPHarDyqXtSI0VNTO4vDF+lCfV+s5SO9U5MulUo1pou4+aQzyfmAmUGdM63ih9I0broRoLDYmSn1qpxCUhv7KRSlOHcv3VyetQP3aifZZJx5uWb6tDe37loxNDuszagZNBgFzeqm+DL2Sogd2kZuamtxggUXYv3+0fxWSWPSTsbLR9Xvtnobg+FMAYfF2LPS0NybP7sAewvWhunhu+NKw84hSx40DUaojGmiXVKZJ6usTcEiHGadkQKBEElHHoM7faIBfpuorXrrKIdJY97OdaLLFXTfaYoM4SJ8toAHhty29ACpBux5iw29FEEwWtyr9EJuRJb0HNCHKSVkVNB0KIZCCJhzXXLxiW3apcpMkKs1s/5/BXtAwf4v0hhYEvKIzUlLPLjMvh1hB7xwIN15QU2vH0a/qRP5T64J6bUbnV2lUbPPxRRGhCLRVKGyccVatICbbTy/XSURC5aSEFcxgxwXFKPqX885mY0QMF4Bc/c/VowdufrEkyg3lKIPjlvVZ/0RKuxRDARvnfhf7DoRXeurI/vK9FV7Om9HENzSzyi1LjnzfsWbHivnhdQUv4ZAu63EFK7ToH1mI3Arruc089qbEgQmlcao3IiOkgbR9kNyjy3H9mNoiI538+hWS8jyCx1gUdUcE04AdVR3oxFuJxLvN5O1t7XBwiJH/JEsVaCdcG6+38ikEXG4wRy2SG1D/5IqH0yIh85LKlTw/di2XLTr2WZkdSQ6RKEdMQuyOt5elpgaX+F0OQ4g+3GWXI930iAqU7Q6fmEGjeyQVOrkjfO4YRhaJr7fnAmAS+5B/UKkvG2ehoBt8UGxW4Bmsr8oliSgFtZW4kIdWtVjU1NEdJzWVDNQbXIzyEWE/bXgKami7iVhokitn9fTE2J/TdqqdZKW54eCZDd8vsjXQqoYuxz5BDD+u5s39nOl3urVd0zk8ySHnpTCYVm1eFI5TaIwdIGQ8l1NuIBVDaBVGxV6lcRx8Ybl6a9LhqQBtJpVFj9ULLSIrhqpvcH6u7dUvA2V43UNxPYMjJtM2kdADLMptKYPhogZxjRX/acLsenuIYVexIr3ZfFZu2lh+3CZSernvZAWqWqHPo4HidTA/CodqW+uXx8T6E+NcOYggNVFS2vrC7k78PvZd4R/7/LzVcoRRtMwSc8N/jyuzBov/OGtSz8rMQaoV5bdezaFFO9ePSyEE+99k0kt+eKMwkQfj+SJwhQ79zhTJrj1WzK77e/H9JPns4fzLlUdIAZvGK0DTc2O75oDH4d83b++61KqFR1Ej+NHAZb09VzjVW45wB1xpfq8QUVRuxz3vABiGKxWCyFI3cz3XyeFTWEIZAERyDh9+1wiCfcx6QbqPX6P6KlywHKZBaURumLX6wliMzAY7ORHQX263hTscFqsXERfFIORWuWSmRv3s3FM4GWgLttC3z9X0vXlkIgxS9LrlmCIsXzIb942loh0B/xOKatqXB2rkU8fcoM3eLIER52hMNdjBV9sqgtsEwzT0Opd4qVITRqpIhg/Oe8eWzyKG6sF+Q+B4RoKRiP2yB6xFhjp8gGbcUDT4SGH/aOhHS0yyBpNKr4P9iotn8WTy/vcaX4Lx8NC5redhDkdOeEfX11S7O6OPxZ4Z1Bu39wNhHbZiyABLpQmCQ7X+cnDRU/q4U9T9fQtfJjTlaIh/ey38fhjf8mIAUwoGHghKwLuxAA0yRb2v3HKtpTP0BSiaK07yGMe9PmGFZrtVZX+yHDMzGYONZ127sjuZw62GVneratpP2zPt3V2jFNVJuHPXmbTyu2T7yliGBm/xiOJH2o3oPlgrRhtIiPQSnm5/eN914M22qpP5SjKHnIfHFu/66Ma7FeQ00Dqj4oyQ4e5wQH7ewRy9UhKUSVyEyJ51SWz9p9cBm182XYNDki21b4AXcmKFF8Z3TgI5HRbLwj77lnPvDjX7iVHc0oJLaduE2+m21OkkzbFAQ740GKM9xwMHinUo0YVUUTBMhJvZ71jFOfzVcFaHrLEO1TC/iFedf03wgaV3Bzvg3jOjp5YRZ5TfzDiK9L/yVXJxR88Hsuxu3qArhRvQZHJPZoKhFZzuqDMQEL3uFMUwgFWhSiXP3K5BkbhmQ1qHMz+vFqyHXAcqx+AKUGEYP/INN+eWSuPtzeKaJ9gM0/K2URnhib7gKH46f33isQH96gaAiGHEAHcwrJBCUW+x572ir36StmcKQ+mMNs82Cnm3a3K9sS3uK6G9juyT9wY2lNslPacD1A9IDfxVrt/JsAHNC+FkRrdJUh3MSdmcGhccnZLFrzxrFw6CKBNvdzV93ODqmNE/2Z+cJZfzGsNuxZ7E4aUljiDK9dTRLG4GD1q1uYjcEjFxwtaYdeH3KOKJx4E8jdAKrVGwFWtmGoRgqlynujQgpIpadmFjWTHXnUsKgZ/Nce1Je86eBuimV4M0jKTUJacRifwgfjnLsvw9H9RV26TtJzoSw4aRkwZ8Ix4PmBtEt075oofdZM7MUKrrBDOMqQOZWXzYxwbn+QiFBacJw8xN3T4fbJkeOksay8WwQWRPQTMHuuD/Hy67M6IWb43hEJVHWLD9CIfA0JMedsWtUN4YUugnXjzjP9Cl2tO+wJyJi8mQ3T2j5iWKUa8m4yh+IQSkH0BM4LS3VSMh3yMWEhtyhhlquBbQ3plsyPwaPU7vaQczlLKbBlZtpcUkIahUhyuQR28lNfMg6YaVU3p7EOe/FwQ2IAkJd0Ahl7D4rfG5V/f+cGaGflKV4fM814bBCt8fTMRK4mKsIJDXp9qVMo0gjugnto2l9sn1SbLqqjAYGyGEuyNsMIEgnv0IdUBi0FGDAy4plSyScbLb81lFAdziimOFwtk4Rky5OAtUlGV+i5GNZe36tteFWtBGKD4talXIOGmT5pqd5LUCQ9JEGeroXEZSrVT+4c/AiveDe6r/C/XXTvvW7OKTcoaP0mIl/8dl0Micm9JaUc/nWFVw5xb+B9EwOB2LDv5d6EbBxNyyeSfw+FbO/3pp2J3jsERO4HAB339UayuvNg/YP5XXAjC/RUEr5M3Qnam7WcBU23DkkBg+O9KO3X1bZAfwE58OKeP6Uy0l6XXfVPvGCojHsvlvc+gkKSnd0U24tIdU15Dm9gRsF7h0SmLQnCLGRAwDrwkc+wjy6jnQjUvuQgWc/aAPo7prDP/RPd6qZAp/MtjT7CGjXnUNWdvzRG88Q4ADLIcUTToL4pCKjSuctuFcYpJBY1+OanQvKdoVZLW45cYyjhsskWJCnQFCxPINWJHQcky95AjJ5jxKz39wgMal5gR1CcFkmlAmXnG1Omp5VtQ9db0mp+mSfljZpumAJkX7tRXmrrkL5sb5D0SDbxBRsNGotZyvwRs0gAmMou806thikkiHicL/UhX/PVRJ6m+bAVStooM8ge/a5iTj1NV4Ybq8UrBmvmZs87TYm40r3qP7d7z9xn9XN8KrpZaPPsUDsCUVU4p7v93Rs5pBXYw/h313WlnMzeCKfGZ4UH/Q//yL1+oNn8+hvaYQhwpaCzirZE09YhC9j1Kv1naFJ6zsRi/mcVv3k3iKUVYOsIWGeElmdZfYX2TSWwUgBm7QJ9s8yqlipfPTK8nV4I6m89jlntJ1fpESk1vvxX6NAdMZwK7RYfzYVnqd/XTO9C3yIAFoz3OLEpxEcWiKTey6UvPyvH+OOjhResUZJGphnFkWyEePTegrgSvSwQzVJMxveFKtauSBzQ/aH51ftCHE7htJGQK1FPaGmXrFhI87tRokrqy+RnlquSt/kidRy0VueZmDsnyddvr0lc+irFdjzFuF9BnDG9+JcBVUq12cy4SN5BHzabVAMaRBXA3+Wp49yCOUuQxsdINKnQQKtlv405u81yu95/xhk/BzGQf0DPDEduPUFYlaSm3yywUHHSz8Ju8D3RN8DiQcFjbuxlwZSAGjBQ5QrNKnDyS7Zo+GLHsCgeJh5TiZme8jeOHefOHUNwive7u+NuWZmpUSKfrioqD1SYN51IhSh0mzrB+t9k2EW4yk0x5hN9MdDO5wYKW8DT358bUI/HKztstiXPd4wF85dTEzsLups0BKyzUN6FzDX1LtfcvV1id2SXLmfnaJp4vYPNU2GS+KPL7xGNpEpoKTPfFnuLT7QxSOfFNRm6YfSamnPHv3MqEgu+Zh7SnpHt2kmNTPLJybJc3dr81mh2rQvm3MUhQvLutHmfOlcz6sCDNe2ubrJGszZF22w7EVB78TfbtXzHFxD9PtziO1XdO+8Dsv9blepYUL00cnr2nHj/tC5saTSwe8fenpXpr4oI3nKUXVBRDz4RPIl5WQ7DCXEy87X0lWfkDyWT+FLVOjUV2Zrl7iMWby7GkFqdwQRDtL05yKyu49xJG3ArEwuJOvcjh1NkAUVMXn2tA87ZlaclTWtWKRpySqcjo9sCGpd/dOx/ocpqbFBzLrrZMnKx09mHNP1mZKf0nDXY5+vrPF1ckBaEDBX3CjQtif99kx6TrsQ4JpUe1czGVuvI8LPR98uIDlcH+Z/9YMN2Xsu4Yh9smeN/MN0VGN8JNsQbqDy+KMo5W6gUIS094bN8wgeg23lwXMNFVkeV9kzLe8GZ+ZMP0pUX22EfgHvif/Q6Z1aw7E5vW3JdMzkYg/n8dyDcAWTOB+/n6eyMxWl2ekNp1DEP2EQVcV1bTbW77rl+NpyEb9anahOvksdDrlzlVadzuVAhqtYekElJvfGeKizVykWXGmeWXikuHCcvTxw2rP1+EFISkZrPHexy8jExozFg8Yr1BlTB7xVaZ4eedmX9psmgcbaxEbRtn8ZosWGfYjcS2Kunxb4uCKmr8Xfm1omibWus18I5RH19FZp4TlMPrOAtQdlJs9Y8klZjAPxjNz7t05U94jv/Nfldg/911ii1vzmo+Ccob2CLju3TZmZICX5lK1nfXW0YT+3SCyJVDZjMBNFXlig6vXpm97mJ4UpFU1CXr+ax4x2s/4RTPWlThxzJP2WTo8RQZGy2AFFu2GVWK1qC2O3ujxsNspmK1zfMHFDBCmZBd/yQzKugIiyq6IrhwUU090winvG2xAEVPbDSXLLDtJMo1BZzSGNcgXoVROqMITeDDxzanLqYTNtLy6GkNv8V7s69ioUmTW59eVYzo12NveBdosfyZ22+rEPSvLjLvejpp8zJ/seC2utrntmtuaj++X3M7xtIApseA2j0ynfc9UdFHvz6yxH/fj3kSdj3fd6LFoJRyyADh2CN/9jdMZRmXdDbgc+bCTZ9x/6gyTDq2T6HLWJssmgUOcrpZEIWh09C45t1f0HIqB873RIO4EIpioIRB8R7ob8CB9Jy6okXoOfHDGB4b58fHnZH4dmoaBdsl2APvH1N3PYGoWQ4MDaunUCo6VzqsTqf0SGP3Kgx7/34t2KTAtX59e0ltOl0hbrUwQ4IRJp8edp1KLZE1oTdjcqTBHM6voCe62MfZj8sKqMRiW/EzQh6NB88mbN6gKW86i0hYyIRRl6hR7qQH+dzrqb4kBenQv0m8h8gplJKUL2xegJfeV7GHnKB2CtT91MwLQ++WIq++QqUGwTyC2SXnXSvLGYRAT1JJnq1AvAW5VoFYC6esfk2xmlRafL4CbCEEFHM3NOicaCKPwJOMdIHx5KIzt3kqSuZ7Qd362737lsf41V1O7x866G1UZx7gMLiEp+k3LwYRLKqMPQzZfvEei3FB7y5Qa134qOXDI2P/uKE3jPJ8wYz3h8heygu9JvqjU4Qn3mDK9frA3wtRkP4IsXgdqYgkoGpIymGe2nzr2EiJ1TXKNSumkjOyh1+ycNnD1OWIz/CIpOlWqF0zWqpLg1t72oi3zGDB2DW2kuk+Nx5L0POZE4XrhA+zgeR32uV06oS3gwUvmj6RzB3lBXLjJxWuyfzSJKkFrF6JBMoGPYhITcB96wHwpnqZtKTRsirR0jqAI9JyWN/BPZzmELO8KMZDFkqjgF+3Fkk6i045RBuoj0E81NLiqAMjwNd1Lb7Hg4cyIfOYK1P1Tpr7ofLXw0axwwpT7q7zmRgtEG09RJOOX8iNvyB7taSHialggZ8OqkFu7yYXYRTU2sOZyjPEDZEMTrLfqaG7LB1KnG42Jj4WGY5dhpZWS2GQfmym3HP5NYtfKoYygCVV9Vj4Kw843YppHcP10LNooeXV3o5Pw9aRZfdNmPKW0eHqqhKkQNOBOa+q4tFlRiLpgVKz7KQJWcgOzlXfytj4WCDcB8WbGKQL58iA5cRjTyIQIAmXyNm4eG3XCjWx6sZxEcJF6VrMt/nviV2cIWaGoYac1D3R8+Hpl/0Qe3wjIF3lXRmMcpkurj5r6z7YXMV8bHtMy9zTXYwcp5usatQ9YycZFfnHsW2jU/tMNZ1/ytGKwuiOd4mFCvxpkj0ws2BLwms6pX8jpbiBEiup2LDn29ULluPobc5trU/2YHjAZiPGG8x1/ko1rqRSYze0WWuWeikjP6dc90e2+2tMR15nMjQ9eA87FE0NE3uyfej6fNRPD+owv4ziRn5yHY3Q4FAtl3Z58hUfVF/T2J+oBulMvx7Nm73IoboNZWY5cDJLsaaSQwhGTOCxxJyhDivLxnJ9uaCCiaw+6PojVx5F3AOiEVmxk/NxSgIy4eyO92DMnf1Y33MF4F7MDdgRlm2gS/kldsP9jRA58lBd/Yto22VXHKeI+d+U10dPvervumVOlH34rwQ51dad2adbFD6GIEab6744qQ1DUgrcrUe35NU9Ihr82nsIsrC1bRtgHuBWTt2ssq60szCWdYzchJGS5Kpmqsd80JBSur5Ka+VjJ0ZQWQl97DjlvJLd0zWKx2/irr+kEitK6GauI83W6zG/4EGoWwACjkjYo6mPxbt8V+/OgJOULwzvgCY+KuagYvigr3xBINk53evrx2aFKL+mzx1T9HrxwKH9nZQYpOh55b3t6khdqxc3nxiiDOte32R+q7NYC+yQHPdvJYn31wc68ceYc86gFyERZdV3g3oiIqfiRK3MAQaDi/jz++4nOJwlu6jMyUaOfiWS6JBps7QXK1EkecZB9RLl7IO/LGUkaXxj7tft7igaHif3aEbgS8L/QOvLJ/9h1hkY7YqlwSfxsHjIRFkKNZVOPirXRHfcv2jwMADFY01XkzciXCQQ965Bz2EDuayKHaZDD4tt8tD2FMxPz4HRAJ/i8Ofocs8nzuBsNAuzI8UBnz4ZsKpnyDsj0+entV/5Zh/3SvI7/HL2qD8P6EL5xMEp8XKQR/ViMPn57VIw30anzEtfVsQFGzpY+wMyr45zvMiMWdBFt2DiNznjEk86BXOio0upswPyHlUQL2DeatmwMFpcbPQLhtB7UtrOJhWk5Lmcm7RyL7WqScymLiZVWPovomPCJh5Nf78/lQaXwvqYfo08qvmrAImmnC8bNnBY/rxqhkCSRX2KRlQWZdpnnh9h3L3+HOH13NzMW2C0rDUUKP7E9RFQEqqyz5+wXWZaDAb+i8AdZcm2/SoR25p9SohQrcGBvlkytC7G/2HT5lTOnhKKcN0nreB64PGlIB7+71+l8jsrHda51TfP21WHdu6QpWXfuUd9ek5wMlrUMdd9QrhyYl/1KhAXSs+PFXUIzBWteCoRhjjd9uvzMN3W2SJzGdY6PBrkgD8E1hENYdG+mrJlWhYuOQA/NPu+rJm4IeDDwDbKO3QZK0f/DnN9qakHdDpKSZ0KXADT0Y8VcQgyj7xgrAIejo2mp3nCFz8QM3oy85fFVsy8u+aDT/gXjznm52omDQ3jNk+ZP+vpDdQrRv/7q48q4aByM5UrvxAzOKzdo6if90wRPV9wgrnHw7uIR/cKF6f+uH1CvCw8Gx3SZu+rUf7n81c2486H54JtmmrM/ChhRQTf/L5OCQ+mVCwn5TnvvkLYmDl9jkdXmIgU+d/1azyAD3pWV2wea3XGmAcRt0AhserZKzrdr1CE60WpaLPBVIVHhTxdC7mr1LFjJ3rA/nd2RWcDdBYHOqOGcYwUm3FZ3x9/tsKi3m847i6mnyuVT8TDOw/6pSEQM+FJW/mMeDnyZeg+KmJe1Cwi/8Xai9HVQPcc8UHn4jYsOGDSp5w2yDw4D3OIZ9sQZLAuw0l1dP4DT0KNeKWgsxyn/VnQzBlW/ysgDcPme0+7+3a/PqjLzd51NqflMNVyemsoji5fY6yPjCC3VzXdV+XG/YpRXtzT9wvPES53zu52xKaqnw0vG7P+tYw4Q+HIG0BJWwReOKnvLkrYW+c0HOkDXAzlvF9hrYc3o5JMeBwrxhIQ0mrOMl8GkyYalm1iRMz84TXyVQxCFWVidv1Ub1YZlMd8SG1SxhMF088IWypc5rfoxZCZW2LRlX+g0qYh8sm1miq8pNSEj7QiG8Jhghwz0un1d5RxaXmlczF5DfM6owmCR5UkIvRsl3/aQZ8mtqG9ayKve0yvulz5wWxpaDWwoS3uz75TiuW+48dpCquVfTx5ZuqeTKXG6wqsJtpyTBZt226Dgz10saQiiPacpJUnck7YI7nFl4nCKSBymhRGeeoyyovRsKjWQNk1Jya233Eut6YaejQu5pZYiDDZPj1GJ7GgUkwdhiiM6g4ksJ4T9nEPYiXDWyTsoxr8Od1fR8eZiPCKiGVFNwZmXEwJwcEuVSvMMwZSmBcy8yD0B5s0qXK8pGbC/n5qpOWGl5OzGfwpJe0M72Jr1bDA7WBRJCz/B6hw/90L4WdaisMKxEEyujHcYAQjRbI0fEd6qpNl0/UESiSUck+NzTqzfRnnvYe77oMTuNrfOgmM3OzQyF45ptLHWYcf+C1ZBii6MRzfhbYT2V3VfwLMxZ/P0mxpBA8NcZPEzaK0+ZUnT7ifCyC5OeWWtR9NFOYozV9lOlhxtYT5Iw2Tvx8n9WB6rcWlIXqv+XGVkawMi8tsDhVPRSua6GBRmLQQ0Bn2m0jJtVbrw8YT77GTii/jdMM5Lqf/HMSxYfu8G705F14zfhrDJMri22XCio0vZ5s9B1/r88f3IKN8C3MBoYWbSViNs0lLKCcOTpkQ5edZR7le1+wMYYAuPcMDmdhhjDEQWhzGGGMMIZFxGGOM6RLmup2kW57luWa5b5NMzTJM8mVSEgToc8Y5YAQgJa6zs8RxSAyZso4XxyH28aoXZxRur15oDSOIWbYkS6+ER9zNDBNPZlKDQEtLPXS70kn+rHzfeV0vKL5znBzOJnhUWKXfklm0kDn47NAQ1qiXw1aYe0h1ilix01WRvDR+AWOR+lJDOaa6Gk9ukZNfY0STWix5joLcEqtYcx0INU+yKjp1udPqDXR6cRIDZRAxM6BXXwudeYS+TbeJ7TLJt235AuDhxQ3bHSkCz0u06mfSgfL8gCk337aLdWOf5Ub0QHm2vBRLNE2SpSj7LHXeODQM3T5W9mzmO7OsYcAcpOda0thYN+16terB4kIJ+nHccifiRbw2sX5NishIM+FJlyDjHr8s4srbsLABmqRLQtP4gHae9ngdpArJYuafJx77u+zLc0vsUP9zse5XyOW9eKTkaYouFmKI8vN3BbEp1dfCIWcDMYnOWjxMHWFgDZuQgdA/bXChw82PUqVbp9aLz8ZJf1EdpM9jckeLs2vB/vaDwUdIhzEMQ8plNu3g5xZz7hmYpSfv3GKN0wu+4aeN0y3OEifN8P7ua3iWRKNrPpN5DKRFQZ56jA0YaRLM6P2mXOBQ+mCHOaulWka4O3OKuA5fyJowv2alnVG7hDg4D0iYD14xu3+hsEi1h+5e12YPmcEyOAgG1yEAHIoYupFSrPAfKAVFm1WETYVeD7/NVbCov3YE20LvR0mOwc2yJy5WuEIpr/LgU7TjyOD+aQ4upmq/GAkE+emF5ZNxamy0qh3HBnnG4TR7Y0JwdiLMX1+y2eaBkDnK22BmAiHTOHLmDuPCu5ILunz4gXd/KHtTFY0iBHbs4g/onK7VNPrcT7YlJmFYUNPoEHlpNmcIgUWYGf6CvoSc5C+3dm5wZh4wrkVpjJ2hbbAYqijT9OLCZ5GnjPFM4EIH6drcGE854DCxk/RC4kQs3Lv+yNj7WQjmAiONmYYx6jkW1V6Nr6QcNxMKFke+roPIbf89Y3+GvcS/K7QHDSdBZPv9A3V4h8ISpLOVjwlJad7iMJZNH8KIDIwM4+S8SjlEL9q6Mz/9HptqYB2Rz99Bj+rHoEVyZZs9ZOCGK4iE1EW5NI4o1wUja1whrqEQTwggnWwdF0UZlOrS39tsjFsmqAtbphhJVb1RSyf8n66eYP9eQIM31WaKf62NeiQuxauFVh9Nxy9Pa8PVoTqYGbOJFLeNbhxD6daUfHT52AfhJ7/LKO1WSLpxakawWD5SU7qxLwglh6hVQBYLrOvrpbnyXhSHUAKx3QbVqnGV+PMUCvLgTnLULWOZavQ02z8DxI99uyRg6yyX+Ux2MTFWWm+Wi2bar72DUzlns3hEmXnTWMzrmXLMztUqZp7Bp/gm3TKJqQQQ9OLecJnEmVlh0Xiec42ZJDJND3Sgknp1++TGkeChBeNL4VvfIK6iPXlS4D86AgqA/lE4jD1iWExuYaEJThtPOHOqxIENochkKA4j15ZeNWsUB+0nMq9iwgv8UdUxSMkevO4d4gZeeXM+SUlLn1UfdeV1vCzCC9ON7zRPTVfjv3HxncgXvH2PVZmpY4ZrG808MUu9KxbL5u37DRjMj/sd8AsEsKpJfoh3AfqinjKDJEU+AT5IUcR9Dt8qn3+0gqsrcbXc4U/6w0JiSjkocJdqlcscovx2338oFBH5ehirz0pZVSyb/ONl64emJYnUAFUY4jVFnWxUCuqUYF7MRaF2ZMtMH/ZCKZ4LwcZlsyewkraEnnaUxywO0/Vi2vDVp6UudQg2btFH39FjZFDVrzODE+pQdAU6kr295hv926GXWmRovDVxVbjv4k58cuRiFv++8dj1MDTweK7Rq1matytYP+Pbd3r+sYUiH1GYQtcxcV5LU9VJ59qLKcW4wBZQSEq11NiuvOvHL1ynw02Ml8U00rnTRYQOoz9PdxHfwx5+YeIrvFtFOnIa70BcA2js7Wf+TWzSbqe0jDXyY75/dWITxBJr/Kg8ll9TjbE7pRvA5Fkdl/KQ81NCPewZim85L5R/4qN6hr4I0dFCBBKF65qWGF/7MX7N2wM/evURfDTAx3sK0L/iZG5SsWfanUmRizNbV9pPIcRN6UB9a3xQXCGS43VwpbCOWzn/4WHhR6SLB1x3dy8bYUjb0i2XG4uNr2lSuuy5o5e0L37SaBMVNFV84WzWUJ7BtbYmozzyH2P8ILE+4Yen2FANXdrQ5HuSTsNqH9GdXGCxBz2Uw05WTlr4G6D+7GVpeoJ4+gKtPFs7V7rkLPizd8dmot03nILcARzpHMOTHPWp+Kgy8NzYzm3dkK8mpAAiwPYv+ZCeOIoJqobrMc73sHiccEzL6ew0CrX5QCWxUG4Ya4myMbCbg8G40mT9vyEiReU7gi8NORSEpGb/etOTXdRCGK/gxEmROG6p/1Iv24eJ2IRdGnrv4vHKMWm4Rshl4JE6I9L2IWIPyqeM7cdGdlTqqVLNpVnBL9owPE5RU7THpugv37q+f3owf7PnRox9MGCv1x/vu9yc0fGg+jo+hTXmpTD5JVIIgHnO/MQM+9Aqs1w93mqM13mjoXr4mLMALHRMo7V6ypXh06SdIyc77FiZsblmfltu5uQ4czJ/18wO1jrZ8umnDiy0UvkRYgbky0m5TFrBLir/Ua+EBTVup3hw2iG8FeDYq7M6+zHWc+ZI1vfMB47TKbfP4pgHaznmi1wR1aeTCg69ZUVLDj7ZYpcnCY+i4L/dxdtIN+oojARSKTrnRScjNl9gcboZU/6W4WGQgh5LxJycioXC801FnRqLdK+D1GqmV1kXjHo7y1oaaNNnD6SUru+uipfKciFg5Xtg1w7qO8kUzicjaO/eggECoeJ6+pE/0RZiNeVOjtweZFfLit0be9QUkRNbjGTaNIDiWSITZCDd3DATjJkzic1x4w5R/bX1b6WAaYIfba89IuM0txNb6tO25YV23zl0sSlPVcPWZl3iawuJe6kA0AftNZ1sTioPGJxQoNhd7NoOscMszGw0OuoSbCrl8Dhnv7t+m2U5S3p+Ip5VWVz3BqjSQtGtSQNYCLIFHlIypWS1YEGqvh446FXVSaL92VrdEKzFjLvJQRHeVR3D85RdabnGSjRzc3QtU2ZYludJKk6bjTHeTWQUFiC8qe9pwZUSOqGATTTR2E7bjYguXlZVNkvddHCkJhTWyDkmAaUWuZfHP4bj4fs2dPS4SWmuEVy4pX0TNi8/7WKCjgFmDzITW2Oe6EVA9n+sP5KswKNpQhcUHhxuh7H0WrO2afiCuCSmh/7hYoA++bIcGBl1jUpnX63pz2jJjaPRB5w7EY/HlwlKSKSIiYrTs8WE9EfoycMHhf/vOl8jQAKu+W0iJwG3tGljpglLPTYVfcX73x9yUu0PJ5an/V2Kz1Cvd8kLfyWBUYUg4qXmQcA7FawwrD3x8ddXzte3zTSn35g0ChD4wilYn6Rwo+w/8yrB7yX54tQsBT4iefqeyvUmh3GmM6MbHNSSVrH4sMsdUByGqKCG7dZGxp0ZKACpo+eLJQlpMPZRS/uGkd4z4rvAjMaa5utUYGU1btgmnSCne0HEaYdIfbRvSsAIXVqKoa9+/ql8bMp/PB0ALDs8TR9YI37L3yXsDVNIE0OpUvmBItwQhlRIVbDUBY8NO7oKjw20wPd+FX5v3c/6gZmX02Q22CqeF4JGvgbRxxWeogU2Hp8zzRc8WjxOv9fV0oVrsgvfzvOy4GGKL/572UWWd6HpKVwUUPsVINqCaTkutJC8tiFzPFK/OzS359d+0puRhDIVTa2nQaHKuD8s6fo+HxVGO/GCunbAyDN9MnRXoH7qKmAfjQMdFx4bEejbHqGY/cvnI1y6XLJfg6aiNmr2AfWtUWlcEmsToIQrvMJAypR6jDvC86jTdUAFUpkWCX54mNg7LlulI5/PnPNrXjlKO9LqfiSrGUyLmxb98F69qQw/hVEyEhSv3irykBusplRFm5hdrsBexh0U7JsogV+kzN1uhK8xLH7pnROfX3oFrAV/LMjnY/Lm+DQnKViAt6ikk06jlFFSoEMB6LsFw/GXPf3+dZOB1ySdYv5NmuqAK4KCb+1sWn8OBizDJj/kulzybTdqVc970DjuemKO4fm29FJkF/eKQ48R8m+IF86+32iP5e7+8ZmrnUnhj/xuZZA+b1Opm9N2LmBsYv5MpAa8ScQacFWeqe1nn75h+cYZVycH5uf12TEbHCnuQRWZkoVMF7VZujixrJV/PRQ4inFxFLMvHR0zwCiPIo+RlLzkz8JoMgX1a/23JMXbH3zGJcDiclR2sUsVGxJ7+6bNptPxKOo0Zclr8dtmL0t1uvix/ARIb7Nn09OuL0HX/ggSHmHOrPxlC0NcAza/OdNm8zGHkvPQ5Lu4wWW0MJSf3qmhfCJ30frSe1QfFjp31aF3L6WVHgK4d54SCt2krA+Pj/uuev310rVfm7CtL1Nr8J1A2XQ8IJX5KHZsoyYSmMuyiayQx7R0J9jJESOzkx5uhAuuXVddeTrJCtXIEyQjFOgeX0oqruDDAcpVyQs9F0V0YHZuW/b/uZZhYDaQI8sgdfp0eLf5tmsvrU1noWXDrKip3OWnFflW+z/zZYxwGNI/iugqgfNi0Adf+jXqCubtjhNWERMVrkeAY7hsirVg5SrnwoHKdKBJ7TEg1loTidfxyrl63wI7FHYfq46Phl3Rs/CR3n2kbXqwMD2MroadkETFJ/CapfaCJz7ZYFT3gqxaqP2VwjHA7VA3Deh4zd4/l8VKzEK3v0eqSD4MxTnKj+iYmyYpsK+xjdNlq7cjTXGbxrwO23/9Zwx5mJdRUjKrgSptQ/EyGTGNceqmhqG9BdKyqZ8omK1Dr7WPcAuxsW90bQWrEGpoy8vJTzq5GUvOlN2OnbcKTx/zTnpCkP4dKH2lvjsVDd3+SEjEXrPkRe3GDPNdaEDzdfqgwrZ/Upc5HIIldPlo4h2JW+h8KmZjLaCsvIp0rG1oWee8JmHnLMSHuOCpmfjp5bU7kB1M4Hy9NDgI4zKdmTsD+/ZRUle29cos3R4H4g+C6oFcqm1t0YVUzY9ztoVmBNFboSqlEvXsJI0Iu7N/pK8AgteWAgdxl3siRr7ZzVGXX/8QJR6SpwKbYEZINaaIaXD7cnnpwdqp+aVy2IBVvt97uM8CPHFa1X9EKUXg64YsnsIIHtSWkYCEcxEstztiuYrLke/AP9eIGtlf5e5dpTwGE6cxiET6ojDknylPhzzTilvt3L2WvvGKIUP2jSN4csmt3MpJwFS5LDAI5XVxRGoHpIv5ieS16f87Eg9TlpivF8hK301xE3HDwT3JaynTpxnbb700wrOhIwuf9R8gJpjRx7SoKLgeIpIXyUsJBimG5TKCEcRAjk+eBzKUg4aNblcK4ZFFfneTdxMMQ3PEWfEEPdA76Cb4LSIPciEeItGE2OTi/xhh28MMSnHhp3qiqrherKBFEGwPkDEO+yQC/boFM4+otPHaNJZuWAR0GrELYXqJl+58AeXJbtAPF6PH41I3+AsjadbD7AQLn6jgxnqBqzBZsFfPykWXp8liS3+F2kPUfUvvsEMv3f54CaTLPlI99b1SSj68cuxRXExuFNtYPj2jMb1fRXsEZWqGropDk+Vx1IcU0jjw6RNAewEz0G2zDaOvf10gsbvDOQuz1YAUzzds6GdlBSbDqziIPQycq+Cp3WQSWNwCD05pKL/S9bpH8K0yTdgQKjN2YVP30cEPiIteoTtwCCsUNDoXhxP4fPn3xDKgQYAj4juP+9m0akl5Lb3g57ebW4cM+Ozx/AczjYxTBUe1KZJCOlP+LqOkMwbXAKVEVLhAsITFsjBUgQAWKM808gfa65xjJVb7qf+fsWN2hTwfIve6A4lzCkvRbU0sQLXz43aE8nyS9EaZJZNReBWxD6B6fuV3gDKWj9GgFEwncXxSdu/Rn2EvYlz9vLg4K4sFUxROljMV+ueIXrFZIGkJ8ke+Zvv1jeO+5AANKWa89DrEykHP3MNOxGmdo0eF6UUw3p9NX7WcvwwaJe+CLBIw2RbbL5/L8XbYFCnKtD2egkMU9juAn6ZgNsDAqsJg/OJGwmHSyCkojJ46Vtq4FVo7CCMxJd5rgXCZbSU4EbLlHFGDbQY8mYppU1a2AEN2bdOCLg2/igCvBy61YbrFnkT5+IurCbL/he09hvlcI3hSZwlb80VQyNGk82d6ecziNi6+Jad+vy50480cypw4e/01sVAujPw/7YvnvIwT/zpKH2kabYZrtvZni931dWx2Xz7+vPjQkG4xU/DyjhmtZ2etC4RLXbVgS1CjWoqQYWXLvQx+ufYoFtjJec5ZZNJI9wuhDgQzJA0hKwtVkaso+MiJDKfqcCeJ9bvliyEb925TwtHAFROQpgehXn71Z6WvVZQgd5u3vNRvn+eAkHbJSTcssaV56uAg2KexqNS0TUUrLKQRsFBZG8VL6akUiXxkikQ+sokd0lWCy2hRuyK6X4Pn8KAUzk59hgK7ULy5OX72zKBrtbdBuLz9PK7RrjuTea/5rWVfpSmnfV35cGQ+23tvStkJcrxlRqvSJA+fUU3Z86hCd9NmAbbxz/gNX9z5E29S8T6nv8O3w/P1hZSIhIF38nMUWvJ/sec1Oce4P3U0dIb2PnkZFO3M/CsUrdUDIiWNAxTrIElItUsKbkYWrG3E+iSL7ZYa/bd1lS8prdpDi3Y3A7dSBTjvjjdbXL2uSCZx/49pAEzQtYrTvgWhI7oOCRzU8Zf7cSRMuQ8RxYPpqu749JiHO9ZmgQK2r9OvUVuei7b6pNWVT5Xz4YGyfeZw+06GdRVgOFMIKmJeYtzfJ6IErzgSfkvv+UIyEzywgKV6JEEma+dmb8ADa7FCxHc3m7db7ALM3SbHphu+qIwVZN0OBTfTBCSaMbMg8hOP/eZZyhLaxU6gInA9LAwET8wcncxhzjaAn70HdinuBaOsF6PsYQ8g+LWc8Xk18tDSI2cFsIXuGM5+nPxc+BdTbFsewfTxIN1UZHegrzlUVdXHm5wfVYI8TGS+EEu3C2wxXOJkCBlqbb3hsSL1+pIsMEiuQUyjS/qXfDSWagODjNegH+fd72JDPgpcl/nbjec1hUiHGJGbYsVwxz+U0891Jp5TNn22wTCqWyglyCPdpuUd6dijz6nyB+Mz/fmp+fOPrIKski2adA+VsGIw/75dY0gkgcpd57vpH1BZ6gJVWf15GxhcFCfsI6rfm++rUFxqLdpEo7YydnJt4KPrkPV2h9dP/enbNKLgDId4xX951Pw6JBGTK3GDQJUEubxGBlQHrtK4pyb0x72yz/tuwBwUEfunsXx8sjazjaI4zMz7HQYCjKqCIquinTVkiBth7ZbCQChFZyhp2PROH/NFR5FJ0WdtT4YaHQRlHjMe6wKftLnLKq/rua7p85Hc8wDVm0xK6D1IRfrjKqj5goiKjU5SfDop83BRo4AExBAlIam/myJWs+78tn8Y5x8lYU6KCVG2TO2rBFcibSuDFeg+3UsG1w51hykZcWoyDBcUPkUd+hR5HgLtr3mWELHJqGkyBFb5jNEP+DJuFFK3ixjxzqGt0CSM4hll7stTe4d6sgK4UofE8hpOCI51g/s/j375UL9CfSFMBsM489dde1XVn+VkHy/AryvpgvEXA4PPyM4AU07AqSpgucqA0rDzN1+G1bVIKgqUw08b8B9QzD7ME/yBZhr5OUBPx+jEhPMSrxdCrlps2gDRGzOepmbhIw2VPt7leVVEND0Xo2L/BzWOUL3+6AOO6f5V+OvlLY4jx9KhE470tw5czz6VrIv7btU4MG11RC4TLISyHh3C+vE3sOvrKA8+rTNlLUVuaq5OdRIBkRVi3R1dJyP/GoVAPW42X150E6w9roiATVZf56nCtpoqKWP96MKorCW3w4eiNK14wTPBulHshmB77qFRlsd9oOjWO4iaYXHZ4scMsi6a94yhkX1nQgYX+p4hmT7XfZm+cTVY7QSu2CCh5lMET2f3lt2L7FlUKaGmxXZ5OrwArM558BN4fmnJCY7YRuwO4H7d4tDBhvoXcRNxNyBE3sQPOJcON+Lvn6ge3HYFyH65IEciNEQT69I6dSEoOaSqFlt4/DIlp9K+RBccU3+C5pinF9CXwvfXgkaFlaCwgz72H/VC7FNBnCc1GPXGn2D3bufP30Ua0f/Wfho3cpw9evp/5wGlBeuL9yB6kI/qMlwnhRkZKjJ0RE56FKE+OWkkxUvjqGj6qljEfoxRMIZbozW8mHlHQWjM3+FvS/+PLKAuYEtPFd1iJ5+FDZ2DSspNpZ6elBAXgyUKie1/yBwKinx2WGkgaLOUN8DfFYsECMkXDSkEN5EsEXzHAUOV7vwsKZrlf4Kvw/9lg37UMDw4iiv/VXqsQ//jA/ln3j5d+clX4d9rXqtT/ktJ+cN/nb+i9v4tGDX4v8z1UUw7TPPGaf9LSfhZaVD7zMrL5X/7G3poVaoGFcuDT9qoOMZP6ZiHXywI0NiuP/cu/2+eEATQJP//8yjIJDFbkuf7KEoH/fZgRX5NQS13GCezdFMntH5lB0dpstEV4/Qfr0KIf05/bTOsgfJvreS8c41PYbvJLHfQeCBI1rT5MQ90hVajVDrN3h0Jt/54k3czN+0me32gu9S431RT62ye5jqQQr+l570TnDLUGnLYZestAau2RURWLdB4kAB95mY2K3oCBp1hlnXeQeNtwJ5aOYQc52nIj0/uUbaFUCuy96cfaoMRJNf4n1xlXKgndDHCVAXT4eyzp95JFRZR4n2iYtW0KlpkLmqK8hn2TeI1QBco/fSOaIvq1/FYyBOFDPw0uib5Ni/SpOXjDXFOmh/Zgf+YsmQDIsesmaeuSajxSPXr13/QLZAfoUWv0+FzSzvC395d/n6s272pyfaoJkxVlKroXMXE1xL+mZIj36yWEwV//OW9qNbrV5c7du/zPnITv8xwgJ4bb1ikqOSa3btAG13np/WbB3sNeR/kOtcFW+UeVRZIm4f29pTixH/ml6Rkr0Y6nQ731g780TjSLvE4239UCbLRTpr8uqiTnIr/KVePOt88ajZH/C+pdaOj8zpELbtb03a9pV3pTylYhOZI8FlFzg/nSzoY1GC7cmTj/fEXaPbW46uU0iAtbqn0PNSkZcXpJtNXXs3uB+y/GpS0Af5M1/Hj6T55PcXpG29h8UfjrXr/hgRB78t8LJq7zMtauLL4SAr5/9cKRdS0/c28rHk6R4JjyXzfE5h930ahovJVvIqfEUIT7K4JxyFxaolX2bBvrKHLjgGGUmxD1rD3gpZpTlqRcq1zCBcg6GRPwSruFRXOLjg8524bNfUOtUqrwHH/2njWu1CD1XQy+1rL0AidtKFVREOBPPSkE4ynGyV4SaT4F1+/qjZVh1IDqntKl8NGcxypxks06txVUq1GuATtGk3Pd64ZaHQ2LqJVyG7zVU0QcQ8ZMh7Ar1MjrXC7kMxGzYFxZ4rf1eFQb/ZNiho2GGDUmbonV0g0T/yOen8cAUR9X29VVCMRNk0+d3HZmJgdRVbnvVnfIGCL/6hQCKZ/cQyO/BhQY8J4Ohph+JY7y+fWDAtsmw51hxaVk1BXCJr492EfKBz5/uGJDm5opqbuNT2URAilmfVLKYjSKjeOxfceBuRmMYu3ogQ66p2bmCcd5tp2NhDkxe2Y0M+yhakCXipXLVaU3u3XIlLrgmccs1GkkaJzeM3djpGKtpRU4qVpiuWe+uB2kb9LPORt+BXjmoEbK3/qs5KTqX1dlSzxoW1Wxsct8X8rigr+KMHiyDAOlHmHuzYx1I5yXILXnOi2A+5K468MvK5pvpfkZGKTK+KlQ59J5JqyLnhxYKXDt4JdTjSLgeaP0njMA5vdQPOlw5ucWA8ryqcSfHEkGKe87p5rASO08NX3WMqbMp51AaB++WOJM/9/B2O8zilaLdr+w9K33brfP25/XD6cPG4fty+XeXI31FdxcXZys/t3vLs0PO4fb18Oi5Mf+4fv8fnTcN3ya40nF+3hS6zOhw/j7e5hFcN+cfcQq5eT6/ZvMotBLp7ExpBOIl2EL+JgzVcxt24WTbVJPzXhMvytmWx5o9nbLs41M7v0hzNG6bQbe2WpPdi3MmifXGU50y6uo7zXnpuUc91gWspGt/Euy4UuvY9ypTu4UW50czetpFV1yHJvFT5EebSa3CrfrPZuWzlYzdxl+V/PMcqLfnSvHOm37luZ63c+Znmjbz5F+YN8fF6ahe2Ts+x3tryJ/sn2xf9av7c99zb7g034Hv2J7cH3bGZ2W+fZv7Hb+aOVzy6bl4x7FxufX+KDj9Xb6H/b7Z1Hf+5i71vGP+bxh3hr3bxE98P6wG612TWsq4GUXNnuCKfS75hq4dTUwNQVnpK6su0Mwm3LfgT84/bKIoFvVv+X2/2DLn1meoRD6ZVtCVx4+2C7Hg6sgG1HeGTVMFXDeerf2dBDYF2xk4CBd4KdCMx2/0vm9SGQ9UDuxsMi7JG8bQ8TWTvyfxw+75Un75vDfw2z1ccRXcPGquBYpe/Msb8L0FV/V/h7ee2lIk/e9QSEEPKha+KTzRLVJcEi99Xl0iF9JUjuQXcr+GhMgKkfI68ylj7nNw9D5aEiyrjpzNkbv9M4m4mudRFgPwTDcaG8cXYQ9KKkOu7Jhva9artyckKoSz+TrntAI9g9Sx96sdhv4CzJWZvZpHOo7rEDq19Nk9WOGSFAokdKEIPmwR3mnc78OPayYMzJgn9wIj4sZnFeTnlg/leGGVcHk8CEpgvmtpjY9ADO1e5zzly96JMB8u/AkD8x5rBAodjmy7yIfNTdiOdBvIRgTNxtRDn+2LSFdACmdadVKO8P1ym8DspecwSb94bTmE5hQ+BDnF9SrUE/kpLKP8r1pTbZXrh9fOhM98xxyhdhZzln8qeB1mYTzPjukugtXAA+m2zEdaA2aA8jNF1l0QYCHMbut0Yd2y7hL4nkoxXUffjk1PJIVYGNJXuiRXHPELXN2T8xpEj0G7ajLiRkp5dcHSF/akdpxlrgwVhCcnYMrAkVrg27l5Pq9JlCX7RMhuFE2vivTctIgz8A4LY9BtQK8oAoGY5aAOW5aJddS1qD8TixI1SAMp+kzdzPrnF1wD/Ne1fqpOqE9LZBZuNkBX+UHeOPoUfbjpHiS4gqnNDZBoFTX8bhSEJSmF9V83AJSUvMP0aFpQyfAbf9GxJHRyL5ymFV1t45dG+BqSK2czNtHATS+7O48+rM9Z1RFAsj8+y/8cXA9oSfTu0t+VGGq+0uaa0UvTxnFSNCZcPjELtv+XMYeXzLHWSJppXgKMrCVRTkUmPeG0Y7UkyuA8/nQd5bnk7ObIWVb5Jjp7EMER39kHJsZAHJ2Xlmp/Mq2zsAL2aHsDGrzsX5hb1MEbC+6hknScJySAIV6cg6JH5Z6DyqV9tdCp9D+NajTtqmooqEojK+tbxk9wD0/uzzzGXny9aQnu2mh4U3J5ZgBZknEb4OS3reLMDKexG/p1HqdbP93o1P96+BJmo0L3UJ1hDljYd3n4u83IRTOmpiwwiwyF3UmIs+VjU7uUPIdrNYQTB+etmG9Eifz+GQM7KKSJ5L1WOiJvuYNXMrnc3PHW1lm6mw/8QisHBmUcUPH0Zshg0cXZe4LO3SSJG02Avh1cuCVsW8z3g8AbCoOblGnjuATktyLj2g6u5Ho/Zq6KX+3QxowmO66rwtSfYuoQqVrDTjlPbJGhD8+kNKx0dnXBmcp8jOMrwDTBvMEFH8kSeqLEVbLNLjyzJoHtd6doO3ImFKiRoVJ97xg3L1yhPNe1zLTulCxT1CokRHIf6x6AYNCILaiqi6aBz8SVUmagA22P+7c0R79l0zVOsPiKw/OUc0T44Ynjz+vTFGRw9M6aHX53U6yFZLCoeTkLYcufPW+grgSK/tm5+XaEqMFvRmOohhGIZx+3NOkxf5j13zs+mOqvv0W9DpujucPUsYiFTAcj1CcBQi8x+zNyYM0S4ven23/dzfndQ9p1IH2KEtVJpSs1vCJMtZkmhFnWYjgfT20ZfRCIyooF+n+HxM9cRsGimuXZiGgpSQ2EOHyLI0dN/71/wxsO4c4ZTDeBJnzTOdmdMB2dXM8BNsv+SDDKEM89C7XjEQFRYxStJEcSKh9568kmRuQYB2LAWmrNEsqMvSLPb8jmkKN+TI2UNgvqVJkOQC/p3IDLacCc2keX44VzMsXz4+eWE/TJlM2xG4QxiQ8OfEojoTl4QTxOPew7TxjF58m2dtQHj3hel5LsPuiEgSNx4zQy6fYS6D+xxELdidBloX40MtZKV6fjQ/kkC6TW8oO2vBBlj4vYYhI/WysEUGU9TC92vaEvMlHuYwaXb2fEO3zxA2xOm5UfSRwVEa0XXDTCvXzQsCryySQ6nZ4wVqSnT0jHpqOsjcvovzcNbA6QbhmKziI7oPBV76WZVcsqGkGOeOqLP3Vkn6rji+M4Rx2XtNHKXpG1/JvWrvx5T5N2pCSX2V8z5WYMatpHAvWxT5fZ067DSc4o0E+YRq1NO3xJv7UbxZsw3SnUek2nRPJOnRMWHuoH4gi7z1iJtuO0Lr3dH79RQwn5yE8ZZ5dJ6GkByS1bAc0LEW+D2SvLM8vpehonOr8MRa+ARcqsSMDBfe3mc0cJZ07LmELgDE90ib12Sjz+70U8PDLIcKzYuPipJ+yY51N770nkMtGujJ20Il/kr3adYhl6gEAplfV2wIGD573YS8M74oW8eeBvjP9oMm2Zxml/M3oaNDZfFx87mDGzFZLXiyI6L5cWWZiB5VX8YsImHPWm9PD4skQ2RAZ5LEeAp7lDKjFuiicw/xUi58z/yAQOTt3OmRuLTuUJr/OYhcliWKjZKJwHBhJ87jHY38cFFotB/qD0iMqB5JejwPuLyWo5O9zmIi2MiVE/o5JMjh7TQqkYGSaFKqOg5fgEkCA+2+02VOa+bvOFsPLYjAmLGqzfOyyCx6cf/ZM/5NnnsQuOBp0PiuvT3bgtS8sQaBDtvi82hiBabgApWskN9FfcAcgTQhRGSngiIf1KSTodu+x/HZzMu3HRGCnXQSgqxRboaZoCj8zvywlv/sYOCcXacniPR3O3lHrnGGdMSg5RzPc9WcmxZj+Gq2+9N5eumhAxftcKW9i4Ts75uOrXOJ3kbTJxbMAeeED64Td2lskORCirD08scWLMb3vEsuz8188Ko4N/8L78OMXZ7J+iaQfmsIOjZ40vYEI7IK10X3a74imlu/EpBtW6ZpSRh1s9wk+LmhF7rQ9mOikI9x0hxUmxC9XLgTZtP5SAjOiD9hGIZhxI7BzvpldxfakjRaAMXMrInokwUQnruYg0u3h02UM0FFU5hHZycXY+iYwGpLX17FeMDR/ioSZyT416x2noV0WtO7CF4AfhRJCYsWdX4xu5049FzR3eGZF5w95Dftv7yXzmpiChhQxPJSNw6+ImjvQAPekGGYOQhsKe9EQyYoF2fy7XSiqvAxRI2mTE2k5/v4CZLk20auw46MQYTIVXFXPVAlqBpLJMSN/v4ssPdPB1TSgOwWErUBcj3D2HOUB1Qyg9xULf32UJU9qcsZ75fZz294hEfngyTbxFgCZCAPpIJ6aE4kkDWCoOaDe6muQsiGPbkkHU2JyKuFzDMtu/7gBPjJrS1Dqo2yEnGLL1LDIm4YJokr/nuGTbZP/0Tea2l17fD1cLOgX+hh5RrEIZB6MiiuOZO47xVGqpQYH6yDobWqxmrpStsFu00P3J9+zKU78TZ7JOZF1ZzPwhcStVoj4Br0mokJj7dWHty4a1d8MQuQg7LNcp81qf1wyqC01c3IECfL99+c+aYe3T/7TCLGFU0SnzTUcdGh58I9OglvP7jDnNo+g46d3MRh4puNsFXsHkNb4W/rbRmkmsfX+Kz95/ZSvO9+iZLgVWqezDvYtcztuVdwu2gTrpDWbuY7yPcuqoPqs5S7zkOU+gWHRull8UY1qtBe9Oon7QYN/NNBWCnjxP73M7laKxKPhwI/zhAvgDWcCJeHKEiWYOOGzFg8VMt0Q1VQDUPA0ZfHewb9zJbIoYbYi9zKGaLyFzKNLtPvHLU/QmLHq5qmkEDU9v6yEL7Jzwn1qs4WWXHJC7sGb41pUcwJPxJzbxepNKe3ayvLovCvdNKgPawc5dj4Bu8a86fQ53I/s6b9LGh5XmBaQDgEO6Nuxmr0PMFWsxt5cgM60+GHJ+U5E57N+LATOwuIxZe0d0KH9n7RlAXjbtoay+oW2wCudMBP6HsWMMHY1Q50byeJU7Gcm+NjXXLFi0spt8znVPe1FpKDJsVSmGBtXdwunJiazI7f+othoP6oeCeFPw4CHk41NqSHctUu1QUvfJK7UnvNflD0egtUcZTW8r5aszHjnOfmtTXYAZyWP98xoWowLv5SJpc/N00aZcWOKGfrycFvnvk+zEvlo8pheuP8FN0FJkFo4QFvpciiX534M8CT2EUSBQKPV+/I26AeOtecUf7H2WEK9C99lfJwVvQ1DRVUV1jQklh8jdPrBxCWzNwWKRr0AvFQqam/gLG27e5FaeyWxEnpBT7NxkMPRscNaA157zXApKiDGe1L57nGehK3/SiukSv6QUB1Ehc6n7ugxbgoY4WvP/TDsr5PZXK/AK+wWbX7VlrDulZhoiFixnmlzfjJCRXZLJpZHIZhGL49R/1ukDtgI71sr0WCiUbHEZnAlYiqGXzgOrLlNdwVAShhwM5+k8DLZzsGnRUH3H//ifvvXOWUom6owSSJNeMCqv190gMtv5NovFiQl0NXa4qOiq3ETKqpxcz8X/+j+hl2NT4a8WGy5eQuTcNTw+WjcENBU9Hq+/IBzD8x9FaKS8ESde2HcDUJppg5//QmxzxG8g8/HYcIYO/L9IcS21JDM1Zcc5p+ILr6cKmxDgl0yhMlMwLfplOI2Affvn7AY1sN9qrYMOothwVP8OEyIBj7saAeCTiZYtggj8tThQ1KjZjCl2tWIrZ11+LrO62UJZDXIIJZ6lzJBpaZlrApFIRoirwOKyOPcorbndNJt2I8ksItYXL8bQli5a2PkL9CNcTMFJwjyUDZGIyPhnLjYm/qSGryqrFbNEOBbdWouOca1Smr5Zt0wc2VbmmpZ5JsV6D2S/cBU9ab2mdi5BBqZoAffefM7XrdsCSKwXEtxjjuVLtvo/q1G8sUlNZNRl75gOqt10GBCKPGeiX0HTiSEfs4ieFl6BA6gQF6rkCKpSyjdNT5WLDmAxK296OQxu017otkESZAyn0C+/Y4WVEZpV7dPIVQjxu8jWfzKqz3B5t5NhAeOtTUWF/WVVzSBIewSmLspeN9YflEvny+f5cjUzlc4Ju3DklyS6Yx2AJsqnTfJ9axAoHrc2DH/vt0leJ9iHBqcVFjxSVerPhwA8D0+A3PbXwJG6wn9/8e89fvcMA7pmV9REihhQwlAiWI1In4FBbj1fWWTqFo63P2p63OI06vz+NL2Uc6FZI+rLQdXS8dUa/70CaqIcSjqaGuU5s30vcrI/MXtH6caqE60eyNO/QPrBaUeQUG6txviCM9sUkExASE3wLCRpmGQ4cXdvSpLbdwO0y312vBSuoEGoJUaf+OVkli+NZgrD0FRkXcHM1Cgw5CeWXLwj35aEoxCTBUz9RKUAfXdfKDKEhZ+G/cTVBz2c1UvVzmKFAuwqFZ0BN9At1UPawRNFt4svzVACnCW4dRdJ9MbugTElNPbHJ2Aqxm76KgJGeb2k2cjR4m/Q5lns/Cq/cKEEPqgeAlqvuWn0aLAnTOFF/nk+GbKh3/hoCQ5CeG460dfCAXKdsBX/Q0XGK/aZwq+wGaC3ZHwHAQOSiAhpwrG5DR1Aofjvo/yGcSmvj6Yj7JOMvN+92APKLxfC6httRzY9c4QGNa6BrnBAXx4e0MVb1wyWDot75sbLGy+aw/QnJXwwuM4wpC4DIN/b3lyDWxFdi5kr2+qfFjkGJ5RUnx2jlOtutuE9Res/PQWxtvoN+qkemsOER6y9crOHdjVFo6sRReHzmYDkMH5qO7sy5wJOaqNzmqVYFhGIZxqGtqqcqxTU+I8NX/v0IpsP/Yb0c5f2/20M9TVNwWrLxs9bSNSuow2iXo9tDN2C4UNgsQ1hxTBTk0MF2q09EVf9yjcY7gIpQHCl13dQ2IIv8ta7AvPXw8+Hx8/rqHB/ldWHQuO1eqvXDuy6vTbVFtJoogn3hP4jDatN2H6uVbPgIHnGi1M60GZny1YggWs7MsT8xEbk0csen1e8FKZeT/uAoRA6AnE6rAuGSdiDO7yHsZXIjKjU3OP4pVqk7eQAV/JTa4B4b8L4inpHbzlJ/wmlOODcSWjaz6zbKR3HnATjrTITdBZ7/icASslD96gO0YndbQXtGH59abZB6kpVCK8+rCzOWn400teLLg+5PzOxzrqm4FTfUDdiqFZXjTxozUKvmxNYTIJvAqin9ZHbj6rFwCBLqbI6VQwlyYsJkSoV0zSdn0p2efG2Tu3VFCXgpnXQ6/F1FXAJRrlHATSUPeRRdbvNtR/hMbgK4E6S7gaSCPiTGRH+fn4N8ek8cKgfD305enjVlGskrjZap5Yv5kFI30sELCTDlwvRMNcCah09iHYMMLsV51XJfPqbS2QXSSbTdP0u5+fD1PpQouKAJKeUYULguKl+6h8eAyyX+92RpU2s2p0Il5/u5TB/9sMo/bh4VjvcWdxHRFetJVtuygIvHk5nw0V2ZcbwhZrl7v9W1nuIXW4evJp88Rr2EmFV9NuaLzL5a6WKQ/XyIrF6+I+dC2zusVNU6hTkriyakfOZDIeG2pe2sBfhTj5y8MTuSE15HURp3D4sHlghqI5jhE2wko4KnEq0FDO9zunaP8g1OBp9v4FfSdMVoK316wjgBrQbG/H2sDqpdJQnoX7uEeReHrXDvMXJMZNw1+abRSgaoSE7GRBiw34Em8c884e/r7USSlg2NiiEQhs6B1cAFlHDYbkkxgnDt8qe0MJrqpF+PY6EbYbJYLgYPETa5x0fzBsN8pkTr2OmeQra+nPPpKc334LAW5Ye0tYx8ye0xJ25R+KM4F0jZ79tUHhaDYj32OkfBIzAOgeyuhQw6CsJkbVZZLWSOQapwJaTV5Mb1FGpnV5k9yY3mCnYxwRNV8T/XjGxiikzUit1ydg6ZN/CqnOJ2y42paiWLAo4ddCMwZMPtE/kDIEu4Ov1XnSh6slCO6deMc/H45KZVUXlE0RIBPYytQKx39SeA9GSc1r6zIimkMKdpSnDeUI12m8GW9EInIZl62K2yPI+LE7HutTBPX/yNBiQ50oJgElb6v9Roqwh+y6TovJNfseOsmHiBn/b5CDKKKwi9kmaWON80MOeXZ3Xodu80/mheH5GkDm1DT+FZqEgJ8ziUgFcE9plvLPVbvskz6GZSWVdoy9yURl06MDLM1Xrtgzom2LomnEjDSDB2xKMHak3HbF/7/OqIJMVJdkzZN8Er1H2nVhOZA1ZOGJpSJ6h9S04RYUH0i7ZogqGpKbQpNR5VVuhiEckd1llIZhPhANVXpchD8oHpNqR+E5oUqUsoXoZxS/Ugp9kJ8p7pNafsiuKV6TqnbC80V1bZK671Qjql+SZKDECuqfUqbreCZ6nNKq63QPFJ1KQ1boXym+iulZivEK6r7lHZbwZbqMaX2TGjeU61TupgL5RfV25TKXIgvVDcpXc4Fv1D9P6V+LjR/UBmlbIVSqJaQogpxS3UIadsK9lRPIXVVaM6pNiGtq1Ceqc5D8iTECdVVSJud4DPVl5BWO6H5RrUKadgJ5ZLq75CanRD/Un0MabcTdFRzSO0kNEuqIaSLjVAeqN6EVDZCfKJ6F9LlRvAX1deQ+o3QHFE1IeWfQnlH9TOkmAnxk+oupO2fgnuqU0jdTGhuqHYhrWdCeUv1R0hldAgLcYvsEJivcijfgo36Yb0iVG6RtTsoqxyKA7NHh+mKUD5wKwW2qxxiCaZ+mDVSLJENOhjaVJZgvw7RSPxzuwUWbcr/zGYdNo1U/rnVOshtih2zU4exkfKOWyawbhNXsG4/LBsprshWgWmbyhUs6EAlMUW2DczqlKdg/X5YVVKZIqODqFN8M7t2mFRS/uaWD2zqxCNYtR/mlRSPyMYdjHUqj2DpDqWSOCN7BpZ1ymewZYdtJZUzsnIHpIViC7bvMCShvEX2D6zSQuyZtfphkYRiz20emKSFyp5ZsUNOQrxwOwfmaaH8wmzaD+skVF649TooaaE4gr07TJNQPiJLAtu0EHdIcDTSC0oryVFLr6M0C0dHeqrSysDRBb1VKs2Bo/f0mlRamXNU6LWj0jxx9IFeCaWVDUeX9PpQmoaqNqndCE1PlYt0MQrlnuqsSShhZBJ0SiqhZdLRsSjhyETVKYMSLpisUsdBCe+ZNKlT5kooTNpRx5MSPjApoVM2Srhk0oeOjRKumUTTKQcl9Ey6ptOuykuzfuNQ1LEWU1dyuyo59aVN98nED1iDf/8F5P70l31Yd+1Xxaa3/Kr4810f/xvL+faPf7QWu779cf8U+zoP1/V7bLoSn5bnm3fL2+3nP77kYvk88SAYDH45l6vz3S+O2/3Vr4rr1fNBt8L/6wn8o3XTn8/+SH8EWM00K2ddi07u0q/b/m9XMVP3f6X/usGoIz3+nXSVAkp3KaB0ORhInaYBgW0DpesU+BvDFPb3uBpeBxurxAMUqw2xFcxhhOvltkk0Jqvb0tC60AxnxsMuIB3liLR8sGXcfvzAt5/GmrLY548UL30KZN3GmZO2Cfyqw2Gsdsc3PovuyUtXHzn3dYdg7PaM/ohJRBFpfJFYM6OW17R9zhbrj5IPdA0dCj3tyPypEhmkgIEIhzq9mumrDW0fR6wvaadobY47jWu3VXHu1jPsDk4UXirEkWi7veaB/Yk/M/8Gf6X1sPDiFszTXdf9IBxPSu6XXYvRMos+6+7Pf3BSg//H4a/2x8T8lRdw1YzTxy1fb9dxpguDjdE6v3qDNHJwGw5rSiXB8ap9y/CoIJ1E46Up6aQwysBxtcL4H9xSDu924nhzbdEyKamninDBqybslKpd3CgHffMoDjbNW+3GtJR7/ai2uLcZnTVhVJuddsAFvcftjQAetyPAxOLaGWnIBHi3Ygiu6G2VdzZ7EjOJdzEx9v6k/B/nH0S1iVKsqhp9oS0Ztai1MUYpWdPYStUeXCmjjvfAK/GoeCOePu9bXbpRJisOys4q3Cp7q+ZOaXrulZlRYWVCDGX59aHEcGyuyvMCc0NuEBOt4ynygOjgOauGwfy/tmHOWNoW8xa5QlzSLtNb5EdEnziFUvs6EqtE3WM4IFQ8DMgrxFdax4p8hyihi/t7ZVyfNmIdqDMML+xLm2HeIQfEVaJW5IyoI057Rd8hlg11wvDIVI5GzAvkDnGT2jrdIO8QXcPzHNkghgXziOEPxtJGzB+QbfWuOx21y/SIPCbRDzhVpfZdI1YD6h2GbwiJhw45JvEltI5r5Pskyh7PGyXW0Yj1HvUYwxFX5XnE/B15kcR1oJ4iH5KoLzjNFH0JYrlF/ayVuC6/R8yvkJdJTE07Tv8hPyTRbfF8QJYkhgPmFYY3jKXtMH9B9klcNp37e+SnJPo5TpMy9KtGrOaovzD8j3CGh/fI6yS+NgdHyGkkStXFXa8M/boR64r6jKGlsbQ95ltkjsTVgFqQNYja4nSn6EsSyx3qJYZdui6/D5j/RW5H4mbQjtM58jaIbofnF2QEMTxhPsGwSGNpgfkTsgtjJ0tdpv+Q90H0G5xOldr3Qaw2qA8YvidhwsMSuQ/iy17rOCBvgigzPF8psd4sxHqG+hbDq3RVnifMP5HrIK73qO+Qj0HUP3E6VvQWYjmiflQ2uTTX5WXE/Bu5acS01Y7TGfLQiG7E8yPSQgwbzD2Gf9NY2oT5GrlqxOVW575HfmxEf8DpszL0dSFWB9QfGH4m4RUPZ8irRnzdah2XyHeNKJNRpYzrsRHrCfU1ht9pX9oC8xNyaMTVHHWNnBtRjzj9UvRdEMsF6lcMT2kqr0bM/yB3jbiZa+t0h7xrRLfA8x/IphHDOeb/MPyTkqXBHMiWKdajtk6fkEdED6ei6LskVlATwz4I8AA5Ir5UreMK+R5REs/nyrh+vxDrRJ1jmIV9ea6Y98gLxHVFHZAPiFpxelb0BbEM1I2yybPmurwcMM+Ql4hppx2nd8gPiC7w/A1ZEMOIuWI4DmNpiXlC9ojLnc79R+QnRN9wulSGftmIVUM9YPgVhAUPI/Ia8XWndeyRUyXKoIu7e2VcDwuxHlBfMDyHfWkHzHfIrMTVBrVD1iRq4vSg6EsjlnvUKwwPYSqzEfMxcluJm422TtfI2yS6PZ6PkJHE8IL5FMPbMJa2wfwZ2WURaJfpJ/I+iX6L0zul9n0jVlvURww/gnDAwwq5T+LLTOt4grxJoszxfKPEetuI9Rz1Dwyvw1V5PmD+hVwncT1DfY98TKKe4fR2IqBOzpc2WfMs2ijU3Bu1KaVrS0e0mhnbjzFOMKYkWk1cteSdCOwAbZIKAcKagtC2Pyi3CsmJNhNQ3gkovEZ+pqHXgrp9qjMI6gShjYK5GXPeq+VPcM0/m7llrotMMqHtBdd4wTWf1fIrtL1g337t4wX73At/7H4GhSATqI0wfZBSkeJAShqFOAhVIp8FlERkmLBAQiZMyZlhZFRXHeagfoX5V2oUhgwQIhwP0ijpNvwEDRGt98QWnjVJN7iGODFmrsgC8ggralorCmQqxGJKHCVqnfk32kitDuNE/Q51LvANB5E6XhU2bDQhOGO00/b5hnWcV5H2dV/N/VbRV0/mcvwa7WjX3dZ9HcfT0hSezrpGxPYMaw7nwOyk1RaSLv8mVGYDsgOia14vn9w75iGCFc73IunXhwRH7SHFLaV5xuhwEyK2vOBgaQDJ8b/nVvWatueAk3yRBI44c/Dx2w8QjxSna+e+BtdPHeVSqeXnHPQ+sk4vx/o/r4PVNlmbLH2PS5kiyYdZAVtWdv9mJKdH4ixG8UWf3zGZ3mZ4hxfxtwmoU/1sv9K37vO4eswxMOmz0kl3T0B2NbGcparsPn5yVh6jf0x+vpy2QzrI/XitXJr83+J8xfq8LNftU+Kvxxg7FOvL2+Zr/7wtyxEJL5ukGqR+tPl5egzTZpXq8DYvr1+nNwyvs8/ardLisJnvf+wjhaY/mopzdfzay+XN6vUxvySS4SmAp/N5kkT0q/P61saa3rOTIHJwp4BsIDF62sLx4nfoMTzhndivBww28kVSG/J9d8JLcMvQEqeGgvPJbe3vE6VazaoxUe1hmA1AiZfN+htCRVoYyO13D080bRq/RMX7ZdfPnQzPevf+eHMLLATnatjCS+9GBca6GTZsKZ2lVn4GmDeyBxo6UVReh0WUvykULwTVHHwQDf9469Eh5UzesBrGJfWYW6ATlLRnYYk+qi0PzhJvZLaQ+J0EX8dkdhwBaqU3MyXHZ/twbHAo/Jnl+9u/iSbcNKlq6DbNZgj/ida/sTqd7N67qBc8RP5ov5Hf/BDIInDhUWktidE5MNrIj3lizhGqocfdv8cUyuAugSKEDt+2iQZoFWXNIasTum/GhxOQSHeJH+A/mueShOjN1T3KfMxGVXHMkQBOrWp1Xq2mbbtNh7ukGfHJwEXtp1j4FUmLByAIJmj+Vuao4YVf8ai2sPzpL2gE36+KtC1KGVVA/0d/8mwpFolCHwwEVBH0i8+TaKPZ1V9Tx8u+Uu3i0mHwnCzx8ZgeY9dbVOs8W9QIl8nQ5KLotM75dKmdNgUaVmo6OyqkotxlUEpxvsSApeQLS0ReXE5Zzp0KXKEKR/tEq0e4aMNn2WLNk3fj16lNxPql2McSODBAAn78FE48mqPo3Wyk9Wt+nLSo3q1GGD443/cMh0ybP82+PAtF4gIrZJhcL0pokYwQhfutFxHpl1x9N++FPoxgnmjevfRqr8YHgXp4sUorH/rGYEeqpdg5byVjnN0XalbFFI7UWlFx8YUwXlojUUVcN3V5jnzoR+WD8NoimxlID44QdFG8p7x5ykyeVdXAOxRBC/ylVm1OxeMEcF68ap/RKErst62ctAQ+jg7Tupw8JhxBqWlX9Y5toQONCshKgQ2HBhrVHyufUwZWVXDjnFVIkG/EVTLv6t6NlFRbQqHbBxSblAH0rnFSKarAGW2oEEfcs64XyleEH0LQUhldVdVT6UYB44kJK76Yprt1lnP0hiOrMWdVT9EWgVPMKomTT6VtkX4VD5L3qorwwtyCQnpNzzvR7SH4jtJbCqbzWOhpwNWp5IDUZHsLR42rMt9nofOlkUIlJQfL4i9/iodvd5bW3AXPijNbC//JQ72mOP+6Xh+rkjTzgXxJCuloOcp8KD/Ivg++rkPfSuI3u25/eSOdJ7v8HXcbT/5qL21eKViDKf+PRF5RjPKj7epwGeEemntmRsUdk7qbP8qa0dui+ammwAVtoI0AZpRFQT71Jowlc5lAY3usTMUEy3HFtEKkzpPjHFrSSaT12we7+T0juirjNWRWahY30xit2e5+qsariv3ShauFQgYtc0ne6txNtZrRvJ1sjpXFt11YpOR2ws3gv9r25FSfe0ZzatFKOR7kpglmxdbtsMZcuyBfDOsNGev4DTejEU5briHHeBiMBEEeWrPcWjQPnY4V7h4f1LoPjfpTP2DT0AT8BZeJZmo1PdiCoxkOf353TYuNv8dl+6lp/PqhMx66izoar7veeIh/dt4USD3EQbl8/oxpkwFGJjPc0MbEmXRszA3S1VFcFnCl82R5a2mzbO2qqIU1v//0za9F2uGGFOzMssZYsjRyfCq0jvK4MGCl3GNaCcCiAg3FGH5bgZRBrQxT2VJ2f2THGA+kZq8SJU8pycUbBmIpLzSkvuWDICh0pqvlzNuWWnu7+Rjpb9meJgaGic+yWd1BjWbibaBSTIgrUb6TQ5kUyJQZSGmmufldxUZNSra/vVhnbiwUqsmVSblN6qRFXUiEtZbYltBMt1EKGMiO/bxzP/9uw1aYijY6wolrdUzoJRxdopPTmLHsVsFS1MgHTBOsY8FqulFrC5Ko9QvbkGELc0g4eSYX1VeOFC0SuJ4VevFW+e9abrvtk//qAvx15ig28AM4HGoNsg3I2uGqhN8Y7BNUMIH0YAC3x8DZ5WRSBkZJFUsCYBPgDvSBwApNg5DhBNyTNHLrSDM3fi7sQLip3keZDxev6lho1hnlYo0G0kGU7e8qJs6KjItvjwa1PE7QbDKxMhRvqUfE9dFY0g4q7KZsxlfhfLTLOdWJU2j3vfmS8tqPB9nRWYMPnuKuCD4lkmdlPoTFLYZkKb9KNm7jaoSTlOQUvmgInD4wU7NGG1V0R92OiZsJwZRzCtn2jM+57J0pKX/MVCYpaWcswTIvMSqaFPTi3z2xPqMsd1473TkBPHWNg9FIOWWBZ7/xLd4Wm25f+qz9X9Brxk49gC2HfZsRi5aaCcs3JK46bn5f4uuw4elOAxgszbe8KK1EJHoFKRllae/a4R4iOh0JVtV4RLvGi3jRnyyPDnc+w2LW5nbEcA/ZyRkR3dRUJcApWSoIZPHvWLF/pQnSM5/77yBp2eh1On/sr6iBmbuyCTZjFHWeToP50P2xtzR32ZPjgHSdfTvlTWqSUinm2YGcYqHMGTjbgZCfHphZiVSaU78aKxBR84/vggHB5S31nc7OpaXyTEKIWlaH2Yj4ULRlFCxXZyxauZtlORgD2tGDHDUPatD8Z9oaimYqVbvD/nBSjAby09w3rspVdt78yjv7srxxfaZOq1V14khF4qXU9PTU8aWDFXFjyZxYgMku+2LGTGRvRhvnf+HtigztwygwiPijc/sTfzVlk07p8cDTEPwmvjk8ryYb70uPe/5StBq4IxmYS5sqoJtGDo5rPo41fAyT76XZ1TH3K4OEseUWhPjJI+7Q+jYsabPT0jcMecTJLonmHY3QMX+YhaTfzXACoPjrt4zTV99szWEH2Hl+WkmgwccrYAvv8s3A4BdRD8UAOATAZtgVJd04mR1uPBTNhMcfirL7l01iBoNfs1hM/qcDZQyuQB0WLDf+X4CePqtPuFVDc0o/sS3ZRN/8XnEUvbTvIfyjxVk2/WoESrVcaO4bMduzj8fhYHFs8RqQP+0XEPmlHXAUMJjfV9SVXRX7/1HtH2WOMwLr0N1vn33Xb+C+1iI4RbNyPVABx7lY0LLhtgRGlQMYY2B58E+VMC3M4FO09osQ37pXrlmGjN+HNi6WJ2vsR/qc+o3GDNL2xKm9r2vqelA2K2A/ncnwtcyJcXxm+J2qvuCg+EGV3FrlLx0DaKIA70luIubDFOsht01d2QnUXVbpLDBsY8nz308109GZnuZZeqlwa4u19vQmjeSzyH0Ixn7YxVZ2FPOofjg5KpRzgM9eMcdY8eioMNdkvVO2FPjyBZd3l2fC39NROX0dUhfnL8RKOZq+kB/WJzbWyMiKLZqThkvusatnY1RHvv7uqY9niDXguF72VSH2mf7cRNiFdYDLxu6K6ZvyEaAKqQaaiS1abzWiVXc1OYc5Y3S2xoOnGHNHsiNHf70JP8yq6j9q2/zubpEU5Zy0tMJv017Z4G7HYQdonkTAb2Puiy2HUgsXi1To8qSo8gH3OyKWVFQT1yI2HuKC24lEVTPHGgp4SJgEduePZAZYMoN25A3iWz3gY/GcdOCBntN/QbBKK9xlwsof/Ijvfu8wTVd5+UGr//uhNLamp2w1h4uSRZbN92qu84LAeh4eqhzHcJbEUwIE4paqOiieCLmae+gOirDGUXRK1QHzonL1zqOndTo5L2cTnG1dH1E/0VkJH9Bm0PQoYnRRpLULbftx7pLS+JbdrHPsru3O36fZ0QtUrqMynOZOLhNY1fjSNb9wMo6tnXTVaeDPpRzP/GcSj7P0/Rkzudq+xW4kvwlg0t7+eeTz//KOG6BWQI8u8nuLi/5R8fQhR7z7d55X6XSDJqZ505vwZvM6wHYIDKkrFoP+g4fzMLgRmSEfZhgr6bqCe+AdaH8Qk+PE9yebQbyqmQJ5xnWvpq++m6jSBJtN5rMGITi5FMlP49K+I+L0IW8I5mdpTSsR9mejB14LVUBFrijSS27ZQU8lu3cGLcFOvvnFoIyCSJ0ySwhLOVMZgBXtbrH0O4L/SsDjvAGUtq0g4k3uAbiD2+JuqzjJKYjHVIMydp8VsDr2YhFkEffyA8mz37qUaNeDSuIgoBkUZzMzcqtr2B9Ei4Swz3KdzqZBiWY5T2srSZhHT2tXTO/alSzwalV7aNXX4kqV2tfizlTY6NSvVJfNhoij+l+98bqsNDL4iu0CJeOMGV65cwfZSP+r6H+uGMvc/CsPsF2ZLv6Ke7Te1z4qqThG2DqxU3/lw3nDVu8yHX2cvnHWY0+Vej/ZWvcR52Kizj3SKo+23cvb716VwuALDlcBEJ+4v7eLXzvVw24V3DKywi442lMzXEmj70i/YVeSFeHowEg8a4+4zvp1DgXQWaIx9CpubAJVl+G42+lzDajCkXcqbzaPTgCSnqPvuzEozys/GCfLr5c1Xa5XfmzdYE0KFNWpvuKTidg7uhfaNAaPfq4HThYYv+JLxiXulTr1Vx1GC+bYpVo0L47mkb/nmVV41ZGJtnO/6l9/j9JtX/GPb8LRKL30dwbL+Vc/jwXSaXn5f7wNEcYJ6uIvj7w6Errp7dwvviTdqAWFqZ36TUL4SdppTRLSJ18h3WetdQBo8sYM/kb46QZu3wbAiazaLTn91FXAb92dh+2Is4JF4P71pzcaHM83MT2uJeopc47jswRYM8d8BDvqWgUlqXVO12C+LlA+kSYrjj+rAZUlPfNG9xg90j405F7C9KqpDCCVW5Rx5tUIEZzko/g1ZiLFz7m86JCyORgaOw7DOqWpVwNppXAy31jaMCzwvCOvcwTPdWXQc0/+8AOvzeRiMJe9I+odU67nv5fgsP4y/YwRwDiV81tbzFkl6/Cq7fv1/dyMMJtsUmc0DTBLLZol6SWAXgZicPaETm7K2XWfdwA3DC61AG/qkBGQU3Pur1bif3fvMrLZp44l7BsmB0Qhjdc774TKbP5Ezv0PjJgTnqzae2mtSOHdaw8A9xk4zS02XdhdToAzqxh4ABTWWdEZUta3H/4dVHcHNUd1WRypSp967JX7d22CATomMi2wMkxH+1BkqBhqEGnUBIVdhBF4huiR0RCrDiaFmkrfIAUQyaPH5nuljkzRmqbeNAawEkCOLT1rDZYbVYbpaOYgJgwJnSE6Ch00PqeET6YE0o0MEArKQHgDIhv46bWFgaKS3m1joZ0HniBywl82Z7xhgPS4C1Q+/Yiikh59SnXSQ9SGnFTU++0uOYSVFOSE2UMao6icpmZyLX+pBZ29unvFB6LcVcQYS7AaQ68obIHGAqTH6NDzaaRBeHQ3uU1/n/zwopI+VH8ctc97cFJRb8oiCZdjEwGq6b93A7N9bZBFmB41HhlN+rCmFGvWiNwmqGK6qqn0WG0w0TmNHpl1jsHGA1IzX11A++21ZeqDScrfFJt3ZJdnXP5I4syHJSyE8/zYf+D9go+T+XSRvfz23L+pOs+0zHd7G91yUT37lls9vsNjnPfm79qQZ0e3SPjTUMXtZGLFHu8T1NqZVs5/MQxvxHB6qFFn+9u7YZ9o4gVx1afbC5DTScixSvOoZM3l6VOpdu+WwTl5r04sY5NcHH5HdZ/6FSZlDaW8s3P97y7NjuZJrJQu9R6oNENVLa57orNb3VwCAlqly7CEb2/V415k+l7QQcnV7m1R5HXe/LFeIt+xQiTuYpe8xFo7ubzwL6sl6mxs09dtc/o53xP3bBPTPJvNRtTsC5thHSX+BIii7EoWl8KmX8jQhShifExKk1ELTHYon6PodgdOF3m4rc668FR1YelaOZC1wFasLo5Ey82vuruPh5ap5y2TAFEpO/S9LoR/D+tlGueJ0NpjG8Bf/wlOET9FETmSrjOLyQkx8+qHgZiiSekTk3E05TUcUwZ9ynetAnwDcl/B5wn7uO+7OCW+RVdp6/VLizAnKTJR8Nggi0NpbaUCQVC84c1RqWDIyHAEQNjIaNk8iOQT+46NFFSnY0gaJH/ePIzM5Qy0rzlmNffBB2ky0m5rBoLWh3m63Atpav9mie5bxyJtyGJEtC78ErNmc1YTA2ld/pkfzclQZFS5L2Xeq27urxlpb272xCcHzUoWprGLjso7680foG72R03V4EvuyQMavYxIEQNCm3gvoV8fQ55d9vLwPTZl7T/zsz/e/3pzBUwyHja//8qwh1axxO98hGgrzCmzb+Vx3GuTCVbFXihIPuGglL60uecBeT+vLUH4oMQk1XwYOawx8ijEYG+NOmxLzspZt6piMvoYX0xdhwBHL+GYWSuXabFa+qbPQJg0nzUUM8+H7raV6cjFDIxLvubW1PCUXAVmduPkJh0bHywazG7J4HWlosE+DxSNaY0SDokOkLOx+KLR7IbG8ci10c2iwewO8MgaODfMVxHzWHJiZqK5XGz4pJjJ1OYZxeR1GlJgdL5A5GbNiRjPSqpZSbMVzGN0m3Eu4q6CIaklnxQ0ZslrqWxW1TCvn0vZCviUZUuSclFLQjdkJlKxauKkT6z5zKxy+jJMCVRtMKDqfq4ZCEkIaVQdMpi8Gei3ygF0OL5GN0ScBjM2bYrKsGqHmjT1tclY85K00TdbGJUyQnctUn3s7QlQbHJRmeiuwm7p2cbMoN5DDS07EhAwIQkLhNomoe6zCzoeYrlEFzCpewL95TibuLHNnpKgvFwSs4UzbiReeCgn1FT8h/8UFHSJVmrE7+agk0WZ/t2+Y5cV+nq5J0xl3KBPi8HbCCJFvF1mJFaz0r6qx5If+EGoVUQCgxYRVrusj5KJaLET2InUPOiUYOcMVwfdR07UEiooL8ZwQz5idaun+hYXsuN9lhz9LkLlKlVOXabCvcCcCZig5sNIHhBNI3AS+fo6dySWof9d+kWMgWL4ClD2MNw8Rqr7udn7QSJX8D6dY2zuNYnaO2841LufTn7u7I1NZRks3w1g6GtpUkbF7iSFIynUuMqoCg1p+PWyo9JQbD/0ijQ+snc7pBCtV9jIEBZjliFO/o+6F+UGBkGCOI+fkczEsyr1yapZ1gMKQctk8WFGQes9YL7pp8xstWykZj4E2almUxRsiGaxYMbckhl6IDvWKTTqxH0Fl9yD+L1qbrRa9bo493Y2jzgL0L66K2ibniYSWIU9MEuBEb7FQueDubYzDKZUAREkXK34WughAtLHA6BUAtvIMKleujVbod4RedC8dOJDi2CbOonii+V6r2t9Gzmree6QCpKoK221WOckGnjbMzDqBktU9Sco/ok3LuBj29Wdqoq1KLsUgx8JC0Ef9jpiSg3pdbHARJo9f9PXYwBkoTx428Qw13s6Fh9ziLygiBmuWIk9mJB7/Z5nuKVuACPuQx7d7iXdTwKd7VA3VCMDjyhDPipTyec4LFQLs/6KRxQNqlkMA/YIY5LkOlrbsl4UfO06Z/T58jJSYi7eA0BI19GHtwy2Sno27H/s+ml9Lom/Xzn+ZA9jAz1fm/FvcEUjKfZ2pZiJAvnxeRx19xl9VD14iQlSIVnGEjAKltugj74vyn+1W1hytW4K8k3SWF1JIPVOSYlmQ3nMZXhGkWcFs+dGurypOR1wXKOPt+bjtD7RbPcSNmIeqds/YJD+VjXzibiI2OLmHSRj19sVEzvrJdOHcGV7gM55bKb6Vxj71F7wRnOdkiwvDTTxeP1dCYO0z1mcSOzvH0o/Fm2TzYzGzoJF5AfC80TkAPnwupTciU5J0YaljozEHj1faiatq+oAVs/JfwxMMZT6IO1ovMPSmOodMxfdpzYYvBqi3qb0cIonCv4wVDZn1TEPSMpC266yKMmsztQUpqy7/MAeGQfJEU6j0Jno9FGu0O0eMqA+ulIdEmIBk4Y3GGLpQs5IaiVN75+h2Yieo55UxQURhdlyuOs3mRS6joEY4w2fOg5icNTLC3vRjZ4RkZNIUpKzpLqMpttZKeaDmo6mPMFF2ajhQbHm5PqLbHRRSSkpTBEJbGBqSB8RYOO2lWc/YRVkCh2bkTnXPxnv/PleO3FEdZQ0Q4sGalptLXIud4nBpiogc9FD2jGFcdmcKSlT/25/ivXifBWtrpdoE4ptXbx7kkJsjayF1C81jIvxFtfVNDqffUMr7VlfExpSrPs4zABBd55Y0A2Qd9SNz9Cqt2ojCpxjk0eveopnKGo56UyotSZbF72GR1LI3tcmap0GtRQELdP9WHOuX+/PfGC+kfUWMNimWguQ2SIThIa0wdcha1ob9SxR/dO0piwXpCdSwxzZyBrqi0KghEhnoHyPqdr9MKL7krhfmY4FNZeHbMY/NqnomArrE6Y8wmftOUDwpMjjtDdH1BvmU9VSx72Gx89A8fyEJ6g/8aEJEdq5Cq03ievUkW5egcBtKRqZ9C2QvjrwzBJrBzUv7by+aNiIrt1tmt+Ie5cVcnYbW/2ZsFZwBoZtsELym2yLpjGObPpGYEibg9OE9dvLrZaPGSGbElKNYSfyXIpMrAe47Ybx6p3rwd0teasiLgja644/rF54X8XWtIVEtcpGDVHFGx/Fe9uPxLzKvCXl71b2LsiR36jMnLkS7vcFHKv+rTLu9UsUlJ3MO1p35G1eQ+z0/ETPG/g9sVGRecWgxcIqapR+OQuuFZ2TyxSKUX/YPZGOV+dV98jheLnP9MJKQfObepdE6paVMC+fCMdNBLuJalhvggOci9IE7MxTr4fXfoNqUNHCYFsH5Mg2Fos1SyFNw0y5MJb4beTtJjiKADYUSZREDhnC3e3k0vifJNSsY2H0qLyRj4lWIWOoydNZofRMZ9geQMa4C6KHok9yBCKDfmlWIoXxkbhUs43c11DyscifmSgEla5lW/o0r+RY9Gy9QVwXK9XsD6zpAZyRcqiBb5b32oo9129yP9+Whp2YT6KbzXK3FIwR67EReOEI//4QGN3QrtkAJVaGX3v8KBSwx1U2xzAbqulRVgPhIcKLA/fNbvtSIqY5O2JNl5MnzeDIiUsVyGtmKYAbiU0Wxwwsfth3HRMLwBmL/gCtuwSju8CPXpjiPBQDLqJjUDNFwG1l8tYgZA/zhxQRv7T8WDg+ViebOz44jm+w9/Gj/xIP/ziBPc8zH+dS93PqMgvw5GsB/KnD7BqbgCMzbIZU4hxL34Y8EHZ4zp4GwM6bcAvznAX8KB5B5+4y4GiueyUMy/34osP/MbRFcSciuBLMNe2gvq61OrFkuSGEJLGNZ1PI1kOSTzL6/CRRjvQ7iAcYiV6UYIYOz4eTkwSOqYp4IJRP+aGRzDj/STVxkBPvzEeZ99AyBqd5ijc4ipH0IXN+pMkXhS3M+bpIFmM6/Q76UdXPEYt1N8Ull+rlNQOtauKbhMUDKWsVusKtELhISkD14MOWeSAydTSIYaIvGI1+3gh0tnMuxsUU34zGfua6QujEt4adMm1DGCuHNAP5yiZSygTLIE1s60Ark5Ea50KvneaSTyZ2GH5akS8j1Xa5ZY1NwEOmUtvhWbJRSXAw3dlrPGCg6QgKJlwRCMhP1DjwWxS7aZSe6pPCWp57xiUx8WcFxOxiOWBzwOz6KdPmwqLquFdmtenFXNJTrIpz20gJyycIxTjaTfTjMABmu0kXVYyuXSebV1Ja3WMdUEU1ilQSRlTyMcKCj/fr28wR6SkzNohAXU0v8BimkBgi6R6HqaM+A4/RHE56A0nGPmvAQa2pUxX9eVOOD50umjsxwARxcUKiHoonhfaEhomuRJazMYGSa7eWoSl94smVbAP1FJymyqBXQQ1gDww+3dJbulkoc6Ww034YhrjWC+Kt0vve1jem6DFf0jwb5PL5x15gxVd2NHmKfsIMHDvEyIayx5hXE4Bl7QH3hYPjwDwVx7uuLe31ERVwqtyxYSIiRtGXto1jBW4PVhRkhRsLsHzdUZhD4lAcHW7vxa3UXNXcx6URsAVSdBb7nCvxey5/rvR6fRU0II9E+7kGo4STqvSBx6dnkk8Ko+XJjLHKiJX5eMDvKYarBE9HdvF9LbpuTSnFMLVJZ8BuFOPNhSjq+Ct9xQBMloKIq1YMwdVSoo7D50WuNo3bgl8UUef2+TEWBZVArP3vqh3h3li436UoihDKsanO12Rk3rxOR7F6Mcb587lY8skLjrnVwL3RyhWXHXRjZ/kyQsRTrE6TE4CMnUdM0zQd9yvlklv88bJlPujlGomv96olyWZXaDvI1X6tEDPGUQ11bcS3lrI7gwgcklEhiGhtZVPf+/ETOi1CHA20MHVRHskhgfSHboH1S6ptQ8yIoiHkuV7ogn8o52UfIAS3NAz/bhU4pihCNG0ZDFNm1gsmmRGS6w7nf16ia+4EUAoKQ94wCGv5uZyFXkS6/0tdmLwOxnGxq14XmDkp5EK4MhzMj6SvnAanzkFFMUgmmtzGp/k9gfNlmfCZOqejvk3ke3EPS2YSu0bQE6pmTTM77TxrcKaiCOgSp1/6k/fM5LRuOV6pPCvZI1emvV6xyLg3E/6o5KhrITaemZAyAExSG0KCC9UUPFa9ueacD0fkSBlzQMqWRcvXzY/vKwySfzamcP4ZfSPhl4aruw790uydVhwhhGs0vKsBIGuGjtbJAZYx8sn5WrMRlZL4+yOxDl/CUi8Hc4WhytWkCVySC1FrM9JktOE9Myh0GdQuLxKVc0R3WaAyoAuFiHYT+k+f/pfYjykQarBH9tEPAT47Bx+/H7OjPvTR3zlC3urWPgGVAUYpMHZpOjMKuFw65ZT6kzasWawanVKmGGFUHJm7L81o0P4ofNDEsZWvadUGOb3n+pk3fsVqO1CtDutx3mCUo+1mdSIkEdVdZ6gA+M/n6o/6pc78YfZPafrLkXejntPr/f41MLyJ5l15ZIfAJFJTZKyQDZ7KgfsqvxAe5LdkxvO+/9ytoKP2QD13rAzRlw5ALkEeFSxRmeKi3OQpIJgT5u7dLD5EyuAmxKLCzFOSo8NR7wbUK3nSvIjgkmq9Yl3nFsc7864QZs+GWf0OIolU5kUVF3cciUhsbKuvuKfJEAyt5l/jXhwnV7071MJjBrmYIOu7vYMlQ6CdNE+SQsWrk0ZlJ9qjrsh5bs2RUid/xQBGJgOXLaHkNMYlM7S3BdR2yfVOnWRjRyEYE/yMXrssV6j78WyYNODabW/zKdUF0vVtcJOmUe7LlUyv1l8H4BUToSls8o3H7jRRE0jVMXXSFOw2q3mMNV+9yN7awsfV2Y0HQMlIYn1rV1cLb8CMSTScg2DjVqQhnbOQCWkCuZhgjA9zOdSqIcwrPqD/Lpow9rmpsZ5354ZM8zRh41Vh8pBBUBvEwdTxCEY5qvdoLKoL658eRjGo4bCz5Xr9kitN1L2z5p+rbYI3tkj+rknwKYLvCMFH90E49BZSvDP7iuvsg1fYB0fPkGRcA1Rvo8HEcglmMYJSbE+yt5QoYHdVPPsn23yKPvj7KKnnFe2XlkrSpnzrTQZJeLTqGbxmk/cCiMRzA+69OLC/5PVSNcdKvOO5wZFB7B2GXt4S+eiQ/bG5SSYSwZDDwQvyDg1N8God34D7dJLda0rQ/z0oX72nnJMw5sJPuzcellze6sY0pirOOo8je9hWLGEBjxMnImgnYgYren9B4aUMugrGVwm7yUxXmMrCPJBg2sVAPJmLgpzUcJinUTnakhFjgI15JNBLMQwSpdB3FXcNRgB3ZVUKQY0D93GA/K2snFIhmBnHS5ug3NwHqYT2/cS6+Zef2lyY7lcW3gT35aBJ0Muf4wjm7hhWu4OwnAsL3DSdRAXP9rM6jh4z5gFHYA7XgAey+7jbx+PcveLyeVxcMHvlOfLukJYt/82Jl1c+qtHAe9D6uLoBXym88GAbMUgjIr0/VNgCqN7Xq29oNJrWxOA6vqDxKPxIXannAlvQmkIUT1vWi5ggHLI6JUmbQpHXiNPIrlMQlFSKAoxZVdUe5UpJpWOblYSqckp3nK0I6whqOuST6Z+USEybo70TRnU4rZx3iXmmokEbsHcVrPVq2JvLRiwS1SPW/ILyDDN+vI58ykNEnjWC1ZwEiZ/H6/Rqi6zN2U8Rg1cRF+V19BsxYVBruSGs6eKBdwyqhxGFspdHFVykU5yFZJBeok6wtKHgyPADk8Dm6Plz2O6Gg62NaCjEAsbjAuMjFPlNea7JvDmUtujmZ7wHaZTC8umaqdisY9wR3Sd2k1gcq7krjFCalkic6SlRl1QnovfY643pNl21412TD00ILQR66XT4rJRnmDFYRiDZaM5EXUt7WWpZmhkNUhyCKueMN/oX9/LwEI61NebA8Jd3+uAxhGemR64EIdtUFhPPvOzcP+uJIZ1waxRjPUT5YXwIiXZxGvNE4kGPT0SkmsNxsZ5orDrEFEREmg053pYNgmbaoZtvlsZiFBtZUhC80GdNAqX3TYuZlauHxx1y9tEhVzOaZ829+aXKMIVXZ3oFp5tGi2aKV55dUXpQzuuw98l5RTOoIbC1SoPVjdyr2Q+CypwryXlfjLAU2iw36KRHl71zoTtxrM3mG8x4ysLSkdulvcPVipyDS3mdaYPutCmDZuTkk3hL7+bs/KWpGXvlKQj5GHqljXUrO1bkIL9zcDsh7StS2JEeSqomfOc875Gr3vW2u03t4zAXKZNQh1zIOhR1rqOICNOkvVPdr7i1m1Cqg0VZ6FGisHz3ziweO3oxn/GxRapfRkmrifSzZ+YgKJX0jshvF5B9Z7NL9Xz4qoC4rLNRmCg2k8WxsylI5B6AVuJaf0PI1P06zjFgQnAmY6DggOMTF8HFDObZE2Q0YRAGWurIckFWq1oWmIeErSzG1UOmdArxWJS3wUdmntlFUpdEzF/cQI/tbwfroLQC8iN7MkSc4Wn+bBi4/6RqcQrlbzUKBPmBLcD9D90UA/E5BNtDACDSnFzQENubcS52oxSYGsmHcFrf4p1r0JqEQ8MMfSUMaL5+kfFHkTz6MTK9Hr82fVptp4h6hvR2FOamC60xfNM113WsfGApH+ay79kX9hE6SVD+Pqf4/9hMBvFnfSb5Jvzwn/WL9d0ATFe+PnuJ1EDiMkvbhLOBLWI3EN0VA8PVs11AB1K/H6ugNtD4yqz54RkoxycA0s7tboHFce3UDx+Q2GoFbJTrJ2xlQzfb4TSlwxYW72iAQCvMsnWaXDCTX1+Sn7RhqrN28vZJ1C7RcKwiN32hQ1LWHGX4Iab8AS4h9YkcK67ZlDL/2UyMkZh47ok7jYjcOmclmagq/qXLAWvO7SnCPg+AwY1P8EVjCequTwDj+2DLODzN98WQleEeY7VqPGcQ7FGh8IWRyG25jHf7WTUvtOrlPWDEsRgZR6b7lKmyeUzCkC/1iRk6jQjlIs00x4Ku8V99SFXY7xEvktw1e8WobTuDms0Q6WqoxONk0eBPNsieX8KzajL+tlcWKprllFIcb4NYTH2dwX0hNB+yHpPkgvfqcbdYzl92W6Z+qknMw6nvcoH008XyKuK54XyIUxY/widGLmEea0OY8PAYPXA0m9f+vo0c/w2eBo0IUIU3w/zqpM/WCyWOuTWeD3TNl6Tpsn7rn9x9R4YzIj3Ii2RB1WOWCaknup/j8eG4Lx50z6cJDDRNy/XSwzw2oM/ZhKj48sWEM3zJMRV6n7tBtsljrn58RVUX03oYqVpi67Mk68DoJYSNTNSY01zzUSfDlVLbR8I0FfZ/DaSkUpwPodSPojuU9koYOd7HZCuV4Lz3CSsczbSeAYAvqXT7YUamIj/waJXxxnp5S4vQNDfxoEXlSurJyf1SAhZpEFbZpe6+Zy9Tv3qYKsOyLw6V8+AHYas2aNE6H/Oi9JE7jpYTiY2OnNjcGmTmpzta/ZYyX/040jeG/Xah5mIwDTPNaECN4UQvpKg+/nNBCmlinA+aFgMJRUMIEpYogewSCIj0UorrJrh/M/v7HLuYGm3SuQxZbx1de0VeNsL8x11vhlr5pYv5pfYnsXEYuIxzzZpT2fWQRnwhdv7ilhFpDmxKOr3oO3hGewBurXN868HEADMJ6VUaL8siBxqBxn5mwGFrVmo+yc43k47M110Pf5B9RlLgvjz2W3X51L/5cJ90etGpYtijoZcS+rH4/U5XDXxoGl22ni2bHfczAXmhYZtNXosRkVKiuognNAlDaLTs1ZQgG3UjZ7un6bN/VDla7Gjbo12PVjja8GjuUTHfxafyDOL4bObm9NR8prNePUQ3cq/YFNdPYJ6PO0xFeALooyMAEyhAdOaXfI1B1xwbOWjA2MUmAMm71q1/Hmu7E0sFUDs4H0YGZW5Lo0bYfx3Z8qvfqtGtlnkR8vLQGzZIzkzx/Xk9p8dgeBqW0X824Awip/vkq7aoA+YTERQgFmV0lNzCEbeAaP4Zff0zff5npjXXvj/qRcmqO/M9eEUFIQ4/VH/nr1DZkwgKsLMktBjMIY2Uu9O53T2k8291bvMIhLU3PYp+2opESnava3bKoBiaGH7UgJtpQscM7rRFOZt7V7ivzKl+qQYZ72cgm7PCef83RtLwV5s5rMhkL+WQ51yFAp6TnZZEk/tzzNlK75q0sqvpCAByjTIhpW8yCZz0ySa6oZ51q/Np2WPubmyifh2dEzNoDUvxV66ql/U5ZreJVJy2P1PIv23RlJMqZ7SeOtNdDFsjcfAbyNN2fgVWvqjWN+6dN97Jz5KYRsnPw2XqkLEsaYHUaDPMAHSse1cyZIGjPLcP4a0v3ug/wyaPbAiYZK41kzlU8cGHvI2q5defXgLrWTqwxE0cbayXcKQ3N2/zH+iDHBn/6CSkZ9qVa4nKz0cpZ8o+WNHoFggG2L/IPWf11qjH8boZUWzZxzXDCv0IXDzWBJPMc6pTGyOXIIjBpHUbJRTgVavzsucr53/Hli+TRj8QZVYoKH/D1MbAAs5Ft95Ard9XGfmYvopjxgIORtyjpKa/J7zUPIXP66L7qykkICMJDdgFb5HkqsXVSolRnwFLly6smaK5uEqNlA/rfPyZCl7q/mGhWgunCiQg8hURj0+U6SEdcEpWYKa80LoG6SE45DvC7qyBxccxZczgSqyEKL6qLwpfiiQX3aW5PWUx1uZiPHs4djJNvh6HRUCQfSIVNd+wzO9Vm08Qy3mCMWZtFB/+2JQtI3ABgzb3ZlNVNCwgXfha/7R9fhhIblk0n1S3nR/Y/L7fXcCAdwfXBj+OBN/3s8zTG9w1/m/O61wUH93cJ/3HuxEJP50rw27rhfPip2CuoX3o194NOyX1Z8/yVRWkI5Ktqy+9ekQ4/zTcAWmt2eIFcIOk2Lf2CnAQG6sCuzZr4u8wkfu1+o1VUaP+xNtskG2a9g75WK0YCmaa1yrG6UmtaIyYJC1MJ6EUxBEiXDQmfJhz7dFnOSy0kD7oSLAW7OJKBGxo3AGxcG0sQ9CWbkzuJDNM5280WdrMkk6/h4mND2EL9F/68jG6MdewMlfn977d7vKfD3nDkWbrsK39yCFP7j0ExRiwSgUT74lPp3lDG5GfHDAaXla3PWndk0ljZxG9WfKXP3sD3tGGpcVR94sCZoIgcg7iNbTe+Sn8OaM/50NErHil7BSjljTpMpWqHuaHergLvPK8PF5WIrrzdDemWb4zw53Q+9/dLH7GXHNLq+ufF6tVlA4l0Z/hECfT0QHt9LKN3UhIdoBax4a+S5z/HVnAJEzDnd2NVzxlIvi0lsKNvSmrtKaBd8cXrlJXHo0xwVcD2J/F8jLp2c4f9zxL2O7qTOr7FOX9qoLda+WsvssFxs77mQCke5YKLjiGDoba/MVQ2jtFpUg61ahUS124X+IDSpByN+JiY6/EWxLsvTakP8M3wWRAKhel4ivwt+cug0YaEvRtfgZJvn8ifV+ZvPW0kXfBb5LyLtZGZIL6ZHD2q9rmnQcXWchjjfpvBE5qSZhoSG3KFj+pSbFBnf2QfhUu9/UQtVrixvntljhhVEP7vQ1qh3/x/Q6Qd/TSUErAwVil1x5cd7l0w4cxjPj0qVJXzK9oyXv1ws88hWt/aElbTq0d17c2WeeWNUB81/tijGphzUPrR+LxSOAD/nV04Q1jd4TU8zMtVRU3qluXgEKwL/BlFnnAlJL4jQwcBbtVDyRz239X7vMn9ouSYsfUofd85rorZ/8v6BCGK01clgAXappzmoab4bn8sFgpSoCJ+LIaTwcbJPgHstIRNxRX8bjT65V8O6FzJ2EFVJ5G+60PNN7okfuXTixj80RyuXSOgtvpi88lQn/Py4YmCMmF46GWBYxlpU/YAc9QRMsPhA3iiU6yyKezRK5DPZTNKpKb1LdA4Qm1kKCwwcT23m9p+Dqo7LxPZc+w90xZujX1EbB7ayrveCx3fEu59WGhRezlcsJokZvemlXd9IkkwR9L8nrWAZYEOnjNcjpGbWfYfHCvnWbrIp1Won4eYDg+qDlqhxlldif5dwfynLGtRSmvj98s2WcLiwKWIu6knXALsiqM9i37OtOt2TKK0FhrB+1fXs7YRTpsjq6N9yItDNHriQDkY/MxGs43g4QYfSTl17yFr+wd2ulhMVugtrhmAkrCzhUjccX6vnarm+2syBvJuJsh1QT6iLzcuskcIg5gRvIW8kkcJfWZkLq2KC7rtirzwVEiL6mKLm+74EHh3Bd2qQhFw9zZzklpHrSiYQOrWrh7wzXn83G6eojuTHadcp/U0MzKyb2Qw1f6S+X/xWLrfarj24LNu3DyhxrGzw6ZmRrXo7ZjWag7THHs+y2NH8+/jfHxQLa1E+hxRhzA6RQJmkNtvMrt1eOiP81RlfpZ357Nj4D5uniDRn4P9cxKu2rF2W9zU6/WyH8Tzk63XPKMWdVVe9dE/B7EzpLQPyIRS39vpzmpAecxdfwCqJVI5w5zong3qP3zPGFDTsjxSC5xUqPykzZEpUWbZYI/OFYolAkTVJ3mYRtyEwegbxG4Pp+KPZxv+IO4tj8FJ3eUEeOHCw0UeFLg8ZTe/U3veCay+q+L2tc5S/ffKAmXryF1Rx+SNyM0YDSQczSiSQpohBIHdJ0kOEnkzkilngoYgsyYW+K4ozWyRyvV+jwAz9vTIlhEL1Yx+b3z23JZ+Skd9qXpcn8p8dp0NdC1l3nsZS+GK3RwyvFd7tIC78ELDFQXIQGMBzyvUJnrY8cBFFLmMAVmP/qhy5cRBZxm6oBo0v/AERH2MoEYWPPmNbLbQljnoASPYm3JMMfn5k0wTzNUJmu53CNyqri2wHTY/AcEqU2xqi33w39gGp/e/XpdS929aZGhXWGVNPrcrPQWteeGQbKf/anlemCM6dB4sM9qEHJ4aFIQLlUq9XXzhyq0L8cerBhMf6LrwUXJ5ke0OVBjK8S1S6TUYsLBZRajTqwmglaUhr/UONvYLkwr4vTDSqHbIBv4byXbn+aroTmowrGgU1ZEEaGbvng9JWLtscSCCifDh4u03deteULDXGmGE63C9dcna6sZGffx8vqEzCy5VDDU2H+ZHlJm7uwNiob9jx0y2HD0BMTOOHnXl5W0c5inj+nPwI25dz+eKPIj73iYt3wnf+eywa/5GSBeb4ieCtNEaASeQ1Saf3LzYQxybtR7OC+eZ+yShj/hd1UxUwlfJ9LVGEl2NxuDRYDjLKeL9JeACI9FSCbDXD8RyXldRID6mkFyY0mPs92rjySf9qjelMnjZV6Jsq3m9BaD4bwCIOPyXep2k6NVtaS2RQC2y00nDTGiy8N54dYvC9wljxYQ+lincZn5IxVkovLoGONjVnZOlrVtfCXI7m3mfWDjl6Ys1OPjwfOZxc84kGoZr4/n7AxmqGMNRnHW9QKmgfkndPL9dDs+Hnh9nAUBxAGPSASLYbxbR9s5TA07UPzeZNe9P262Vu9DtgXCMVSou/3GprYScfptH8mAq4uemEQ5QzhL23AFrk20BlLHzWtTtLdiiF+pGvk6fknfsqbVUULbXrVA+791yQGpkPANqg8mEKw1tVQdlCqUgfghq2XdjsamKIOs6PIhuIRxpn4HTJjgay/TUBSXxpReRfKpyKNzeh+NkFuLUwFIaAp+zE1OS1qaR+vgVrVy80URE1AX6jNP3T7LpNy4gl8dc0cag2Kk3SW/LotHdGtyHuW0O/oxzA4RfEu6ne09Ar4x/t4893FwBqBz+3wKtyZiJZeHTeZZzl0EswoPEQhaFSJ/5ita/uln68H5JxHJjz6Q8tzTbGnNIF+5nBmVMlJb6NnIX72lFJquVGrsKFF3f8kYUjZKnY4IFZZVW0LRdxOYBWzBs8GiEstb16xkkdDcv/G5byJbcym3zbkRvqedrwFnzWieHIa2aGBrl6LzAWCckR4sW4yHkcwCWQDgLR68HRL4OKNMEEiZ9XLbv3dyIXndV0KmxuSUU5u4nw/Y0f/sINd4/F5ydIy2AC6pPRRPamUmK2YKpkApFtUTY6aeosPMSjB50lpN3khE0hzTSvEJl5lkXenNQ+vJUJIYU2DSNrM23VIu/mt1LyhfZMDNgHKEPzWNQ9LIQkeb3OwUBCw48S3/zRvxwa8E37G60LiedFKSIvy0VYr+pd0Yvlhk9ia8G169+HfC5Avo4MIlKqeZTTqyLqnsrcg5golzs0SdMLB/Hz9h4z1yyj3YmtJV9ihPdn4pXY3WvrCFu+RhzXqv+sv9S12DDne8+46ZciDI0kCJTgSBdpQIPkPNRkLCt0JD7TCXcRx5uHtM7bzP7eHDz0b4UDAYRJPozGZcGdJj1l5TGz4kP7diU9pE/TXONzaR0rPTva+hYwznwQaB3jdPj4WivkNIWy9kxEetVnraNDMWHTLUHSApXrdLQED7re5l1G7EtNl7P99GBpznP85SPBgzeKZYf0BOsWj+5RcSfl3hj4sRgikKVdaefK1fIXR7Tl0Y3UHTaI0mUGfAy7H350wEKK83jljYmwAH/WqRuD4aua+LYrymycPScYPTVmTia/udOvhxx76rHwMev3I2XbRHbgACjB8x3dITNZbP/yyIvBHO2y23NjoivuuQblHsKMQGFW0Yde6gwaXzBmMT6G62bQqTCDmj5277CAu+oo+3jplAAx46DDYOZuaJN96nMZzU3w8H0FY/6rObHMh9FS47TL2HvPhvzkLXRWHxdu2Zwszyt/6S87CjPgqyu3+ECAw4hXzIDFIV6//+P1jRssHCNCtXA4rO46ghpl7sJqrUDGI9NWwJ4y/j0hWjG9VZyhvGojjpOL2JynNAJfQ+KvfdVWdgW0cYTdCBvM4b6SAo2652R9Z+wtc3ghEwhtc09hFGrphLlTdEVWQOWzh53YB1wZi/u1DxkBEvbqZrjePRMIwDmUKo18V58C4hdVIzlelU6qfZJkyhCGhMG/H5Q5fCy4bQB/KQZczi8US6WsPoPRjqoCnganSwUK2pEa697wMMJGXN0SsQ38zX/kpnZFyIm17gJwCsThkbSd3XGURwB25VO/vmrMZVeHelriDuQUyhs6EJNqbz1wf7vpZ5nsI7gRC3feA3rHB20JpiJRFLZ4vVM2XnnYexztxV29sX4dAuPPs0/yTghPHavFWx6KPPxzpJpTLG+iDdZBVV3qVU6oMqQ+lbF6T3EKZMLH2rDZvnh/lhNLwsAXrogjKQBmmP44CCB5hmuHISuG/a/Ycm1RZQOrKoB9xbMNNeu2+H+IGh8/zpdG4fELrvDBpYke9yhMpWcKM88oB1WAz7ulFOZW2Q1aZdo1E15W2T1vCYpX8tHcicuQPCCxu8xJVTvYHt6hXlVkbHamdzYnG6kf2JnLXuSYGLB87s0tWenvp2IlKI3HRKIzVX/mqxP+2Y+7ibog+jtKgaG1HdgdRTqzaHyrfNSTKuLGv5/LG+yuPuzSnRsIbK6c+drk7d/qeqYt7+OA/FdL1wHn8NfY5pTz1WE57VI3iXvnZ9hqUEt8V/sXqPqdV5kdHhjfM3lCy79QoYdyfFyGoj5sK/25zJTjcxopHLXjPlF45zC5gxUlKnenNrdfOzucWuXwGmVWfvUuGfDuIWx+bZtcpOkApRL9X1VWXHkY72aCSey7PfSSUWN24NFkZdztJlc6iuy3ORTpopMEmMRjDygPlIIRutR8ZQ4Z9IJhAW7WTHzf1c8aDHH6du1+AzGIBtLmDIK1QD4VNKzNGGlGacE7OLw7/vhkqOSw4hj8DN5x37TMlGIjT4vb47CoJ24btfqS3Ai9dhZE3ju6tqw/0yAGjghS1IK58cN6m/7qXV8MHtdYFfMAAhCvA+v4dXoi2mqlih8OouSlF5dCYEsRohm1zTHs8w4RSI30+GuCOgI+94Ggb+8ijtw7/myeNvqAKsVcnQAA8VaZ6aJjImdNR7IV6SV9mhTaWDd1AAwBEcu3lWG6+lWjp2JgrvsjkCXSZYr3s5jj+/LYsOy+LD6KtFWkAG+9PJ5C6cm8ihWXwMcOXsOUAI4ETDeXFzfuAz+O6qa/qoRGKfCELuON3jUDQZarXTm9lpdTzIDenTIVP7LLvJaIhZvmoIE4XsvUN+PjBkqMNRuJwYS3KsK2hxNWngaQBBPpB4ukZNjixkCpu1jI2VJREoPJxo9BxOaARalDlsTlwIgZDgVtx1E22pqKvM/syolQTLNbl9hvwhEn/+ii6yFi6LsZI9y7NjG5Gu8cdOERnKHGEJE6DamNDtifXew10kMVdmOS2OFfl3YLMxObeeIOqdZTLJf8UtK6Z6/Pf4pUvS8yf52Mcmp3TJTLbpOYkb3YH16W+AuDfwqbaPT8H0gXJ4khmA8QlP04CZdWANgDDAO5Zo+gCi4N/+B/d2/m8c/aUWdwDK8C+temY4+/dD/xyl82PwbdrzNNDg0bbkTDefT8u7zwe5utP206ZDOFxfhO0uAoQB+tqLcdudmr22NjusfnIXVQtl+spiYjpUMlWhDWbOM37prPfMf589/5JpXbjK3ao0ATJHpIFxwIcbB/AyX2U+TVQKds0q87vZBDqpJVHNWcqmHxeY+Oc8Sbia5Et+KCyKX85oDM96nuQYZX5a6zduKtYAdCqNkQk7irHfhu3OlvIVX2CxlHNY9NAmdK8MUh/NhSLsI03e3Xn5H8CcxphqAqfTtBmQIkadXzoFEwBj/nXgt79oWVHLrCBOTOu/3Ll2/xTUoToDXEQtkSoqIip64r4zA67pVa9lJnwaKgZfkADCcx9usfGXX04nNixk7o3fETgMO49OmOkuIGgZwP+2+uokHImPYHCX/KqsmjgO+p84G0qzqHrBoD7+p0w5meVqPo8eo3jCWz2sQjjPovoRuV+c9kdKF6fl/7PiczhFh0XXHqFm7hx24kjm0XgH+C6mg8gDTT7LuTUWrPYRCPZpSFPgdWtVp4W1JWj5tHG03tS6pGzUMcMXV1M3aYd5c340ALQi6GHPw3YNDTXeWuC4DfVDdiakj7XjjJIGse8KCVFnUFKeoC8GcXq3bsAB0LcqNNwUjvAdF+7IxzR/K+Tvz/n/E0IR3EDdAaNs+Go8p0v6cYXs5SV6YjNPxgFuzxuTc/PMIk5HodN8yiHONpFuIc6OlIArHoZnsUwu14j50wmnnG5bwziDdi+Ku+rrobcObBzgA8QyXnNQwJ0tLIG2NwktQuuNdsBJ3BhwVAwwcbIjN27hD5mMWK0v24Z+s59bkjiy4vQ65RtBlugmZq5ycPjeztZ5wV7FjmDjV0HsElfkK1hnd4+arcuUbAGHYyy6uNLmgZNnM9IeHXIzayqmcsPCRsQRbCFcByMxYyKCsZxo99jXdojgWE1JYQsCxIQf+2d7Nxjknvp3gjSElIBruW4QvEUBiqLK+OPKSDS93jKZkj+FShNBBDi80QoYBv7DslN6jqCjWf0qx8Wo9MZp51ec1BM+WHy6mjZNQWErvsreNlWWq1pJVZljlEJIzjOCcCooxZpoaKB7cjEnXOt62hCgX7C6gBQYWDmjqpTZ+7LjuasZivEVlHLWYemXNjRJPp6SlM4P+/4zKbqkdp1i00ilIVQq7IiZUDE/F0rOizg0UxiC1xujOKqs+sOTlQx3LJyvwF6xUYaZuqZ3SODXQnt0mQNquxXCqO2RuFDiLQL0TOmtWsfJHMAwrb8rc8hnXZGmT/zdAHb0myJnBTaHeYOUQS80SjcJQOsBqppofveLBsBue5DkmFqtFpOSrptofCDLIW3CrXsxMCjwVVazGe77NWab2HoV/SYyEUaBU8lGrGcQuQDPvn4rGd04cXreTATCTO2AAUhTXbR1Gtu4vTH5i0mj5OZAUjGVhNcdFG/BvQpWjN+n6iLym5++EFnLT15XB1ws2BgPZxgHND7cqbJbZCrzXRa1BcduwyyrbjNDCCrNoAvIDj4E4WAmp8KQ1kX79JCKhUMmSmBHhO4Q7C1FKF+2XiNNxvCXc5c8hDbucvAHyyAZxc0y3PSlgE8q8WDw8sVOO53phtNkB+iEeoHZq43g7c2t74M+wnB3BmCspx2C4RFW4mHYVCnrEwrdAq28bgBw3FTxUwv1VOV/VFSw7/JUpdWKacIFqffrvwSoD71A8sNZmglQnWqpJTOgkw29vbGeTg49fgk+/b51poRs6lybLO/CISkwzxb2NLOOk//F+qcT2aRp4zXRAnrn4eNxDLl0T8FxXmNBaZlq4FmbKWC460rESrvbXoT7nkmtShIgy9Iy2M6pTO/jgg6wEhoT3Aw/kdAwUjoeET/cSCRADQt/MDCVXDsOnLXaXc4RjfHvAKrhHmoiR4J9HqleuhKtxGolkvxfcxI6gbEweWwhOatLTNian8faXBtrc22srdMA8eqcmQE1Xuh64/TLb6xp7TtuvmJI4irk7RD1zjBPK50qi2WWJSeR2FFdEteQsPJCvTG96jI1jjXqg+C/OdOdUXKzmCqtPufT3PmSRSuWyp3OpbqbO7y7EYkL1ur/AH1Ss8A/qZkfn9SdgI+/tz8Wo71Bw/Cot8u5Hki6Hp1s7GJjVMwZrJ6IcYo1krBs1xomMIt27cBiJYPtVuE4Xto9qIMIb3rIqn6xQPwlkDXvnwL3iEnw5k15rp8/7mqYOBauYjDNFj1gxvJoAZ+JaQNY1jjduGP+enxg+LspebiU8Q5+4i/OaEbf42n5811mYXnw8Rd+cYEEYosBATfLUcQNfu/r+3qYcGEsrwX82v0FdHlbRj/NB6pD86ZNYn5Sd06rcAsNBuTHxkK37i9RfGrs8xjAc5JimxpYyMTW1kOJmd0MmI48AdiriI0kVwmYjjyX5WeyHbE7XGkzSzHVwLsz6FkvHe2svaNQvYFhgmo3MA7LNLPY7D2dlja0lXLQ2puP+Wd5IjoA9wZwco4lmu6AvNnuMAnqRuTdj1MUHk9dwpciSmQ4+VqYHs8g7KLdjbUyDidbKyrRkPhbRR28x8kGzz3DynIJpzdpzyajeKHPusz3NgUgXIezL2MW4GWOvezH71h9jMt6sFXOpQloNMUJJjqo95+ZCrK44/YehMxRx4axIaUCxuuoYGVIo4wjSajQxY6A2ekBkSWZZjK6yKtjcH6+uA9vBq19jGykvrnSPWOUnWznoc+k+Sf2FDY6aEGlCeN1GrOD9Zw5+mznXdwIP5rXDlxEqS2l6VbOZgsvc1zi/dgAtrIHI8V3u9macIJVXpJGZcwupoblZETceHFar+Bpmw1vqlrK2zVabYorVBsgnJlhIKFy39A8mbWkOiblR7shYcRCZqX5rZYJ+LxvA/wQ4gFZH+cJoO9XaW8B3YoM1MG8nWjwK3zFvgMZRouTyiQvoAPLTwGgHNpNQpqmeq/xU3j7ircwjLA00jTixDibyTzyBr31duBUNpfNQbvXUT9SNQxI2tFEuvH3uU1PNTzingd1ZxiWZvW5zxlDy54wPutVuIZT5AoDYR3EobV8GUO10RcckSkm0i3dQ5IWs+08HRspZdXZuvHKOujiPIJg5uwDGW99LCexzUZrBJ6FGryBHqfJAxeId78ft7EwzC0LMjPVItfHknh/A9O6s2ShuCR02nhQOxrGDJJdn3LtLckBYySeC1nhaG6jtGAkB8gn+AJomFyZSpkecwEtXa5BH7NHNNYO8gjK21fidi3g+c1LTsoLgrwHnA/JYUw3FlevjGQ9Q18u7hssj6GA8XJ18/Jv6JKIDbyB5yN+i+DluFRk/v0VCOYLi1dRv6h+hdoph6htLHDWTPdqOy9afCqmOvVbcpZinbk+EcNU+DzFsLPzKS8qfLnvVhl+J0nUmFP409f4DkVrakb6WW0hl2IKdqUW3DI1rV7CxskQvcS9As27YGmFqujeYZ6SD0D5yAENkhYK0sRDmq8MLh+fvezy1S7vLoWPCx9cIsGCwXBg35yX2Or2pKSLXty4V1bF8xvKXYx3+ZpAaa7nBl7MmJ+Pa3PtcU776dYe9XpCvX/hWbsLmVdysn6qJ3ySkpy8fGQfpne+XdDwtP89cfSW/3GtYhveq4/plSyY3oQVvGfyis/nmKM2GoOUREHSFYEk+vDGqkwV4b2Z+61i5rott/PP3Zy/nPB2LRSBmBx/vLxWFfHTADXxJnBy/I3FUst+WrWfmOBmHe023s2pBNJtOIoDqYDBJqocea+ddWC/j4IaUxwAEWZ0Bq0C6W/NUxMuXAAAHnUrJyIrOiERfg5d8NyHVsOmLQhveOuiax5czOm4WaatdqrOY2i5DtDpENtTHq5Lr5fHmleoYRqeuBKF0sLuUpcWDPcD2ttfR+BF/t2aESvHHNRHOwi0fmjuN47toUUc5SD4CYlqOiJVix1hpSqthZFRhVmU1EJ3rEjcWqnq94sdk9NpzPjXs6+9h3xsaGTkGf2Ud3dM0psxcOTVNdrOh5+oigMk6avyALk9pZoF4pz1kzAU87azp19OW814OzGzMf9KI5u0PL3HiJsh8uybaJXpeMZUr54KQ55x3HgIo2/wUwY1A2fnrxxtfxQvs93xBsLJGRgY5Hq2EJVko85iv0X59ucDQY7CdZzYeoqM9u87X5pgGWa42BpvKOcJgGSd3AlrJDR0bGH4KjH2ScNIl3gIMGRjo0V8/2DNHMSKvGg5TlZHJUU7g7evSWbq4vRNUpPblZuTJhRJQ+iViEwB7Qr+xiNPUVErOVH3aRzhrNoWxUEi+mEkK6aDW0uHY2KGVvLs+P+n7+Wk3pZreRXlL3Dmfbys46wPknCk6i5UQsy+1x8Xy5piltk8OdmxERA0aArmH2k+z+Fhe9SwMLZFcTh0N6WTPP96ltFcjgqnR1Yql8KgHuG+Nv6xMhw4narBuoBCk+GoKePoahrb+WjtY/QjSKxOB3Necgh5YBdWumTgX9MrKbWrh7h+DNtvlAsRA75RrNmMDOTGCepCU7Eclwwbz/V5rsFMxdZR/Ki4KNOE+jGp6OqatEa6ScOMqhtjBYHMK8P1Bid85blD56vSaWqYbhh/hKrdUpWlZGciUytKJ4C9a0f1SpKppqRvpzZox/2KElXlZ/jKU66uRVWeju9vB1+Zmp9LdnV57apRyVI1LUk/dXapr7SOur6urcsjSVd29bh9pp9KRWv60ozboQ5GRGWnVNCltOfW1MozpubDePqnfduH88kHPtxX2zXKR0kyhwceFdpL3JwcXQp7+MycX8KjV+DM2X3K4nv9pl4cNoHKFj8R16ItKt/ELe4lHz+xSVDrxbP3S5+K3oRCyQovZiK3ye1LXMwIr0oex3pUGePaWVsGsCzeups2ttSAB6vJGdorKIyTfHVQL9z42Wqey2uh1jrceqVzT8GGs5QDV24tINCMhOy9UN07ws2MqzrDkrW3sHOlcPITmiCn1odGHAUc+13aN90jdNSPqoGfewVcVJ2ZBpCz41QZMbiuPwa/EFivUdbEt33Bo0E1ogES+UDPR5JWuXdex8KlCQTWFurGVtrQLJawiyH3RhYWVal2Y70+cGQwyTejI07f1VZoT65mr4rlbCdHFM/HfUhXqP9gqOifTIMPfAsxGSb0AeMNrWaOmvDJGAKSC1KTElVoD4OFohwHbICQCAlMv1qZelHydDEnX3qi0gpvQpfdGqA+JXfxN/3ClZtXd1kvNvUGiG56HDwzx9XCNKFzi+dJsIoxtv98iC7bbUuL+ch+qV8JBObgYlG/bqd/lxECY5zIzUR0jvd9ucx54bf1sYSwG85kgY/OcW6umWVnEONpDNOQrICjiCZYr7gumxCSb3manUmEnoTaWLq9QXJFSVWTuJ9OHLmaddXfkE5ZehL6uwS2JSAlAgBPrv+vPO39nqESHk9k/yHLSoTaS5W2VJITiIyp+mDCs4+YqVoCat2sXSaGQZVhBw0PstspfkjkpfURsWD1kETZyp36tzJuHWoDhEQMCygxQPkzzbyG5lMxmTuHcxvphWzyAnEQKBxTLF2kZUipYRl30AO5L5jOE/MDKu7JgtEAQ+Av0QKtdA5jIAcrkoRSWAmEll2Tg1VYFuHkHospzIIW0W6ePZ0kOUXo6UWrbV9NGjiz/erb4exa0cuZPZmqXZBzIQ3zP7C8Z+vp0TWussG/6KEP5MPdgFpUxbEWsb/DkRcCJFUTJGoL9jEOI7umy5c7X4uZJ74IkNViY88BflvQE7KxVun/1ZOa07SvKqdqEc7pt3NL0lcBVI3io8+e9IbIZaDFqeLJzjighpP8N5MASyUav0WgEPdYk3EwU3JmTYeWs3KhWTCy1yJDoCH8orxyQZLdVv6pfyYhb7KvYtm7wA2YhEZFuCG9sj8p9137/jfUHVrrCCPDg93WIKca4zwkXNBlBMM1ki6bZWKuJjULsaLfqcYNJuW4YDk4fhyeSID+U6EeBLn93lQQmTrt+CQpxSvk5j2JhFpMsQMBxs1YuFvwsMCVZQqQDpAgIjtwweXpSDKaQoz8fYyjD/J8oacl25QdZtBk3xcBEhULdk6ZYkVuOqebuapbctY7Sr8YxN86r5q4fvCX4m0d1/f0zTjTMhqwsTIHlCKBoSo51yLNla+tDjmOnzI5FqfBkNllJYfc0hrlAMOVlgfMU1geKvqZ/j1D08g7KV+BDvYCUgHolPy3CWm+NNcIkGVop+D68WBW+fpufQKup+gimS6nCVOLs1UaqWrYmUvS1XHUUcp0MfWxSVnukGx97GdsntIwcP/GnAgzTivsWY7SP6OPDdfLpQjplyruRdjlsnpUeabWYI3KfMLDbDlVieUjPmkFtMr7rB4mV7YQflXxbQUoPdQYtnP330uVYISjBgOXpEurFZFbIq0jcC+RSs+mz7GvI0FgbXMfEQGnRxg2OBUjWi1G3G19fNul88KKjG4anYPUnl3d7mXbwb/u2VI/s3n5/efAE8g9Xef/XzVuK7UcfEqgZMoCZDWI3HBY306S1VBkgwREoE4mfgL7k3jRgKLXkPL/Khtp6NJhD2cdiBeLTL4MeDmFwD1KtHIIVZKLKG3spoiJlw7MVclkkroOkVy8CMWKhOlyRaoBJWkKQSe/3Qtb2U2URCaMfNHSp5ROPLlM1dCZbAgP8A6Ze784YmjpvGkSsIHowo54CD7rNgClgSTisMH81gTsTDJdbcieQXyGt2ktQTr8CcXkNl0cJH6oRCgYOnKAdFjRaBnGyKYllSMIH6ygUSIYeA9w5sNZVugQkagxMN+0CTZj7C9RlrTmYmD1wlU2GV+dsrK1J/+pCtyPQXIAPorgcP5dN+GK6IV/vIRlooWLUnk2UwCq4j4BTwhNl0T8Fv8eIdYbqYBqVW5yOWm8SNk9+3cF4BRYmDgr8SwJ0c8ST1VWtqq8dRWnNy9QoLOCssnR1PZS/G/9eRZ2GArBrqBqxEf5Iw6FPisgVHG3ji7YArb8qwWLeL4FHKTe7ViyUhF/TBftcl2cIttWRHqr3DAVMW0rEDOxvihh4G+TWBaj21dD6CKLXX+KeLYQRetjYYn7i0JXecIjuvEOLruBo1fpos8wNy+H1+pXxitl0FTZdn4S7uSjTz0kgGcTqX909+LhPd0eWa4qaaUirZn5dlbqwl+WcXuPt9Qbk2sbF7g389BzsmlDRCcj775eoAQWjfnA7Guh6latRmaOnok6tCGLy4ZsdeWNENjhTpI8TPE0ObZGhxeiqfEozXJQIMTd0eml3YKaCdjqDTrfXgDt+BfwzlqULSvKllrlGi/rKbtgg/34LlVLyZlVcrKhx+4mqtDQZbgYN8xDBow30WzwZv5DJ+PlzQ42rZjhNIRrG7lu0+S7vTnkAMeqDtYD7Fgl7eyA1KHl+LyJWPQsDx4IcOR2tYeg4m8ZNIpx2qHDOuIS6KmvVBwWvZJdDBBMqFQZ73VRSX/jw7CTVqG0DB4/jofXr7FseK7bCigOMIlCQ/xSAoaHPuZHKauqoyqedlp06fY0zQGHFOHdr4FtEVZAyjwwInE3KIbL2LGPi4Z6CLEr/61rSVZWKgtHmRcG4c5SwfgEOfwhZTXCRGyAk7F+0SSuBz4zCy6632Au5vHgEKUfMrL83C22bUl3iT5XuXzDzZXu7X18D8q+W7IHlrKDuIwcTpvwVcwh6HYslWBVFILh48/JjV1f9igkrn800tnslGIjSFFedZ4uH4gzJwla7/BIKj5fpgBwEk0aPfVUJrbLOrc16B5TE29upv3JblszntzNha18CCXII2ySyfTTSxhTHYnUPKLHBhxQ6npsLEZHPjSPJNZ52Y1CfnfOJFc6arXE5tA4t5Ljph9sTKUfEvx72oLKz8l2M4z9pn3HDPgwfPSfzi28sH//ag5Yzcl6ZMohwzkMj4DNnDD78eTYnduk73Anm/PJgR94dYP2bdeVI89GXGaePEqJxikYWxtTbIZkUTDVFklyFkWiIOaztM1LN5s0wxe2oUhhX2DHSzYMOHKdX+oXqYzfs+nCHdQm1va7x+LGb7YXylnCRiL0xmsN8xBxzGy1thGJZ6MlNy08vPyzRdYfjhMjpbmkPT7WCAMJf0q8vvP45uHQXw5OxObRMBsdxb8WAPxm6/nZ4/fdc8lTS4FSA+vvgs2OH9q1E9h3jhc/L0s5HuB10UrIOU+uuXZ4c1vPBAlPN71bYo17dR493Y+oP4k1I4LqsiAHRrXwkp6uHH4i3HreYG7PO3gFIF/LmK0O6H18/ZNPP6C7b81HnHPBPdW6N1bxNq+J6xVPL/tghAh9o8dDnftjFMNHP/6r5tVAnR5H94AJ+Ow+5zmbi/HUhPyv9nblfI4D6lVv69aqmhE1RskbVz5OBYJ2HQ8ahpCREA0egRTPvm9dz3XI97uXvEnJnFIM3FR0+syfu8lIbu7y27Vp4zKaZKaIj++ha6Gk4/D9OHZVn2AiTW+q7TMveaR6hTsEUbda5IBPB8gSjwzHKUU3AZOU1IY10BsCLl2uPczDCJj0yL4YPdIoZKsTrFLpE9zRZHj906Nh4swfHPNH0ZEIvXFcjb+3wjSZ0N8p+1H78zUyTemmBvl2toi3Xn/COdQQYr+fhkQPJ3OWaWLtdODO7hOP1xMKF6Xlbo8c2oICTdb9IyQ/Ax1aDkVZBjYdjMXGJvYym+MJxsMeTiLZNHdqY9/GUWF1i3g7yyQSSLzAHMZe5kst8V5cc+D93mmjyVprTizr4Y4Tix1KGarWeZBdz/JnCoMmgUEYuJzFhFAwymOSUicrQUZYHF1CvAH4g9Y8/Id9522RQTOrbVUv+wHiWg3BLuqsmQxIdSJd9XyCBATaOG7ZLAiXArKiA7UEibJbYznbtRs3xs08597A2u4V9vL7n4KUodH9jEx9EnsPtyJ2O/HhRYglYutWk7p1qQC+b2j8ldVwlPm3I98+mFMWNR44NJh8ZKiL9ouMJmwl4fOOSOI0hdBQkdbCVzIdiuI0IHmernhjcLINMzmOnqEjPdHOxa0izz7jwSUWq4fdhG1I8YAPjIkhYhmS6AE0NxwZOtTeNZIbdwVc/T8hSkQGvZ7wbkN8jFyFbNGAfDWJvLolCtPFqX3zvXYZNjm58PSCPd7iCYxpd3TdrfTEi9Y5dz0HOuHa0hH5VI5DZgJgZS8USpLJo9XJN9/ipdS4jUJEWsH8OeuUDbjHRN4Dnb0gdA7kD4QwpxvjK5/JxM0gpI9Xr3oyHldrOzCmT4I1jToLe/8SzyGz49cwfa2ZDq8pIrh4bAaJcVNLub3JmzY1JJsnKC9SuaeQsqGTVy87UeJcjHK/bsJBJ1lCY5DIKNz6Y06Unjiun6LTdtWSd8ZnsDXG0WnlSl+587FTmVrnckfE5vPFzlgg1KseScklJUZ9BXgel/+ez04AguQ35nt8bjJ2iuXJEFbO+kbsA4UqbmdX5o3YAuh45Ez+gDH3flMnf0VjNAXQDyPsNtV11JWXAtUjOeyx0THx1QVCGRj8vpQHuHp3L6UbNmMXpVqWihWuhmOhkhTZzpoY1HR2tz2e+rYffDUdSdvlSf4y/lwZYQxHn9OKf5QN/20nlMAahwav5tcN63LyC2uN+e4RuY+4WEWTNZUvYdHtZpGOZX+Uvgvd0Opsh0U8KIdEhPlJpb8qoe86inSBO5zSOqLu1Sz60KJQuTW914DDqHGoqI7uBbFUywLGNymOqqFaGY8DrN83Yfe8p1mzioFCHTY+acG0q0pbT7KmBX2yybw+os0n024l4jc7TUh1t3Pveu0ewIRvoU7qyafaTjIj56iHj6eD2LbB6N/YdmuMbAqiMvvxWvD2SKc+vJP8HSQNbN8I6Oe+Wtj0mPZIDl1OPb71bl62Oeag0jvRodbOCtTmBfplAIv0bMbOQ2n1oWRXuaIE6cY7CndZi8kkNO3M4RWfm1BtC29B0PWhMDUpJON4atjU2eWhKsOUWEU6go+9l+uSQ5D0sLI/UrZhItLkWQrfamaMxhKSZWKz5TUL+EyeEFT4Mi7c6jCIM51FGb+SYBO+/GVewPeo5AnAxXuP7adGgrwP2RUp4RiklOhdCDkktzTGUhJ00+lVCPGcMjK2hpmj82L01+qB/DobaGTvpTCWzSj7maWTCND+hIElwLTNz9pBrp1S7xGIv85Tg8s0E8kzotC/WTKnAlrpwCOyUpf7hFZvVaXtLMmFQHVPPka0PiZF1VRGZupcOuTmgiS3RTGHipAecgWCZZSmT7hKgPWL0KOX10sWbJGE1Uu0b+TRww4Kuryq7qa6EIxS5f6gxo55WCzFjGDtFVCDG8awq3XHl9Ohzcdr5cCYO8dXu/BinVGKCau3Hedc+QGqubOnqM+Fy5PU+luqL1KvlWA66XOeBYZ6xzuenD1hrWgyVaoA0/W+BhQCxkiTYOglIyH2BpqwQytPO5t7ed0ndyfTrlRDFvvn3DcWgQtRho97x5jyq7uwOiqhDMSGUmDD857bzN2M06wc5saCMjNPZue1nkQ3x3ZCeggsV/HQ/aGL3ufB+9Dqff9XdGW6a4ck/5yr8g36Wvtl3pJP4GyvjFvuYwHNnXI/9OH3wXdypdIKjlT85MZtf2ETgKkmJfCBXRQfKULUDlfOSg5Szd3lyrDUFJ2H4DBTDVPNIcdssFjYNPc6WxLmOGRlHiUtDN1h1wbSexDiwdJOna3kkfs/XNP1aLwDoJeDgAWMh41e8KkuMmhurfdXZ1Nz9clWKOSGSgfBtFwlK4JoMR2NHCw4Nd7qlP8oJuGulwdZH1Zd9vQoef8WxwQsi9vcZrVIW3rHw9pf30BOt0B00yBgG178fyEFJl4bRvTktUUUyVsx91AusdJh8VBpTnbyv8qEXT3EM667at2158odyJTeODWjF2LKu8fuwIHZ7PMwlr3P7IwoO3/g4/Pp1h0wlHps+glyh9Johfusskaxt++xSRvV1oicVlwsHexYahYZmCCJ1NQqxwuy7iB9lhGCyGdSETUn7Pyxnq/wk1B3Tv9ZMvAtFbsca+UKxA05SpAW5xNrjUlhl1PNhllAvPUkFbPKvDiDFffBpTQGJkAd/cM3cmuHddJFGWDU7ag5ruNtyT19/2OR85eVZlwAtOdxL9fWtc+6dttaaqmF9qac6AxfId1Wl8iykfQQzcdeDasjuYZRRAi/X1C8YnwV5LDLtYLS8uUb/oKPgfJTicbzXcRS6DqZfrT4FFHvLK1lAdnKmsF5SzddGrwIEpq4w+lnog8PLZ57hVdZ/iBMxvVd/q4nO/2bGPW1KSNnB9B8l7lJH+oA/Fd8Fj4GeCoPKwceW6U8+t6badWluHONPo+cE9/qkIKPd+//I2CyHy9fGBc/fguXEGI4vXQW0grdXZdCHSx2hf7tkd0SiZUmWUJNbdhKkUJKJz7HVzk1E4nQDSvEY3Cdv8YNWEyxtKD3PA/uarQPpzy7E838nie3PLKzSx6WcfT2xjpdWHtrt4hXqRG11FYPJcpJl+NZWkWTTv6IMmsNPBEYq2dUnocsuuto4vOnFyErTSEn4S293oMg0n7vBVCbyIqp+kyF5YtQDYR0Ae9liUrjbXht3P7KIkaolwB6/jtd/xw2nn0QWYi5BL9j0c79nB1IMdWvUUIkRbZt2Oeci7xQO1gRW2fPQi0hrL5qnKzfnWh/yiomtpVGg72lrCrn5QOSa+QOuF7TGm5tG8jMSZrmSc513fx/UIdo0lL7s7DEbSrGGtP0MmZl3y7pIA1QxIAxZvu90/cH9j9dc2o7Acd/1kNsRD37MToOtr0vzTAnXS6uZangi8gVwpjydkPQAi5msU/WJ6lX06QFn2FHx2qglcxw4ey5JU6IKXK7Og2R/YZzYoVJJ7nzjAxM1YFOdc//hPJ2UMnw/5tOPn3E15TWl8orW/lO7dhJM28/EcHNXGObP02Hr4xI/TZrbZOSKPJ0iZ6OToa7BFIa6e1PpkGVJRV87KRZBZmD5CKR8xdYGcH3/hfIMpomE1lcOaAAvG93gcERSKrXGHvQGSpMl1gp7dwP8F4gMJL+q005TlylvM26vCC61XE413/UIuOcIP+vmp/F7W0XP+OFDRPF8z/338OVtTot/e+S4V+qMcKq7HrxEe7qeZptsWy8hT6UYucun9VGZkakSw6ZrRmeh+cBw76p+CEa+VQ89dbxEOP4gQzMaJ8WT6qsUnd3q1fREeDg30es7AerZ/oYcdgzhlpZ0DTjUlkhqD/oqemczQ81UXxD8dCGY2hAVeH+0NP48XYMO8doZrH9glVMOwXLZ7R5WB5BKikPgcLw8MxHcw7zZwhE3fE58JCt+3kZKJJnEdaxFLuvAtTk7g+YJJ4oWvf321RQcmVtNyb+d86XJqZqNoU8v1qxeOwBoE7Z0HydZw/rxUQGs+WAXsbmlGbmMJ57dHz7daeqKiHDQYYUdTzF9fEYC9gygXSUgc2bfxIUOBImGtFWB59ALM7YCodD5QtSpOOaqZ1WROmYTLe8OhbwqQ8bYK6cymjgMtPi3t7bINgWlvgixpugzp1DgEaDXUGkDwDXA3tFC6/kxnSRict4pGz9GXtEiAlmqxgkbzQ/otLbsRpMHxHUjkcAteWgrz/zLBFECzGNyq8o29kIf5akCAcjzOuR3QBuJ5WFUCTnzHA5wCloYz2DdztLpSXQ5RABcorvH1uEXCMngUS8IjbCajYpa7E3mvQRXxLL6IWfcp3+4IYWD/D4KXhKFsRPm2qtfbbgNZepRIa8gvOuo6VK8f15u2T+1yuDx2yB3/NUstwwblVqSF3ZaOjpyN/3Li6bICisZiRikcejAm+kVHx+OjPwgWfVSfUmJgeqYnPSK8J7E0Dlg2oGP04oMVlYu1a2XFYx9e2j3GZ0Df69D3bzG63qvbluuESo9s8+RGCiF1gi4+6W6YVV+7NwMVMWHBTJzCKCnjeXmKRQFJh78R5vxF4uEc1s3S5rW0U19YMLSDeNzxyqkucxyWem9PQSQQUkKNemRkpqjXZei3b/7SkfKD9CFjq2zG2287IdWH/ZiJHn8SaSvyXRLSQFCe/G7D8yByh7RFPUuhR/a+Lzi8BQq6g1fjRdGPk/dVpalFZ0Rwr/jyxJpB6lWG8rufTa/C54AN/z4/rC/ULSR6+jN0A6Mx79ppGPnrqt2/SKbv5fucQ5OItj9tbGLOH1ke06C94aUb5odsWBssEFfz8SmTyaertBDzDV2w6rK2xVGSHmd8JXXeMERSuAsdq5Dp2f68PGnj/9AVoOUCCTuuqTEJO9p5ZYjtEDuMNXuMTCkvPOKQlI4bMXPimvz61Qr93h5OxhXzIPv8bL7tLXwRNMR5XcvMY/53yW1xVVsRlIiVN5dVkxTh9Greh831woHm6sEA86OQukjImIFzERGIfhrfCyngpGqJ8NHqmVjs/EJcZzJjit1WdTgyWlB3NzPsA5NFUnrQTbeSCZJDE2Jbh+55o1nPM5tuIZvjJxYrHUce4FZ/0VgZBXFstn4t6Aqlyv/IAYiKXjT+xhgIFLLO2rqNqWMVvsOHmd+CLHb502yA2GDenyHLa57JdZtU9TiSeJFKoP2t+8zR5PvcYOvupUbDb3DU+6pj+0j6Oc5rkI2qSCn2OhYlmfTZqXSMXXwKMThR9Ewim2Q2ND5uTpy6NnaeJCby2P4Spt/DV32R66BxY9eX8n++dRC3ofxF7KQQRZvPKasPdBLFmvQ9xymUMvQYu3wGBgk8H9tbztpuzIwYa2RUCrFd2SOfL24hynlxbbtaVLn7n/2moZ+QprQ2uSOZTpUWSRVidMWXKRqRbQqPaKXCtM8UfbSpy8iWdjNgZIl8I6OSi1DBGC5A1ue57CIKxTFwWkrzkT9ycpXJzoaYszdJwHFxqAA7ajKyBaD0MMWbtb/DunEl5mM6/KOazL5GFapO+YxyPlc398OMvK+kQ0eipBTvnOitoca+ZdyS2Uu7ZKuSSHzgJDEdQyVLo88njsqnN5yB70FBGXSTOgjbRxaa+qcdTGK/6hs2ulSQPD6nAcEJp73pAQsXyOxh5WFGLc05ypJOtIF3gJ7A9mHzLPSz7lth44BzJ2GhCnYo7E8HrAtrHFP5J6vA6R6LHoHF8nNo8/AHU4yVKm+xMsAm2a2JNJol/kjwHIXQ8wFBe+NQd3Ra0rv9UVKRdOvecRIjftYaP/PIUnqPAbx0b+cLO+P+izO0rNoSZhU/ufas0j5ED3aCN7Ec+viIM7c71/StqokiycfrpAKgJH18IZWKGbruhC3QBxmmDaWw4q08FPK590VTtu8ifAXXkTgZ+bgq5JOGMm0tBkdqNB7dXEdyUfkB17fvX6Nbygi1WXqVPwTlT7H+zgCWPd7i0g0JXRS+fc/vtFtcMFVJ3J19xaRPbKiezL2XzJb4DmK9buKQOx3n7aKE7bANFg/BFFExD4ZJIiOrFZOnc0SLpSNyw9GzBi0NTl1200IsfxICneLkKPdalAQsh//orJJjCQjHmckssfM1y9fFczcDzcgdKhcvCGP7puPcbS9mhuVeP+gUQ8eDxc/0TJeJ2l+iLAevqIna19/ZdlaO7cW2BLXuPdaRMb9fEB4dPSE6aKHQHci3hku7gNjKoum4gzUD69aQCbH+LlLu06yMsQeCEmWqMMc0C/wzC5oosftbN9asFHM8aH4wnaOQA+zpzzj8XAUqffSSBFtJOHfLK8+eWapHHPLZRfuX8/gdFjsVAsdZwiIA7wM7zeS91PArafypYIDK7KS9Un3h+uX48tWz/efcNNOPuX/dR8Ac+u1gXf9HHveKXRS0TiSI+ZStKXKE2ephUhupQ9tQkSg6BaJClG1TGnkS1ggCYWfBOVlefwEtdeHsBKDhxxO4mJzxmqycY46nlQoBWCCwGopIjEMd4QRcLYdDH409fyxOQw0AuLb1z/dJiVR9b+nN2vEFcDeUKhfNhy2rLmqrqLc/YwWViPZ6wMp8jf0QCIxe9D0YtxHP0sOTITUzlY4aScxC/qdyYZvcWJccviV+nODq65Nwicmq5qJeTTzG5N8Rt5XNEvrw9JZgnNVksrhjvfW11YfH2iKq3BtUeJHxahXvB7fujQQka61yeJa8EoXQfAddBcUu0MP5pKoLfBorwcpXYobh1m9qPC1EC6PBR7XIl/r75S04PRG6aj7fs8Ab2tmk18bHw8H0aMdVqptnrKubYxZQ8psw6tvtbfxQJrrPi2sCVr1LbCNNt9OIbQy0yB8CC+0yGU8ALatNLTuRx9jIcbDVUW62/sIcf6U8o8TsNyMpEOruXEdn2AoRv395Ns6erfktVFVvsUslcINz0lc1L8AxUCVihBcfgk4Z52zx5mYqR04f+IuMieNjyEtS9ffvsagE/rwVH2WxxClaEUvE6vMwP8nN1qYd+UH59DLkLsLKtS1wt44J3UzvDDNe4zv723364Kpn4FIMCxn4mw7lybn/cx/iGxvQS1W3qHyOspIaXhRjjQ5UQk+vDqoSg0yIUbpgxdn48jXwLXQF5V9JNlL5b28X4Eosg56eOfOWXabUxGBbuWUxnuM0gYscP53o8yJPnwzAgufzLISOFyTcrNkLK3qbU2mXy+gittRJGK4yyHfH90aN6HYLUdL2YopqE87VLeSUHil6x471pnhmH2iJy9ei8ztHJ0dmOZOOIh+T4pePnl64AXRiO/N0yCNNHZNYVLd8iXyVfBjcqW6ihDQZZOJOPf+XEwy9lYUJZTZ/FGc+IpfkXeYlM4zrjrpjSwqjMdXwiT5GTK2up+AFUH7CC0WESZcNZLXcjFRuI/XQTHh0z4Nq/e38fbv7BV4kVhOWUunfwFU7fPLsvWpgVy3/pfVawHdNTRJH9tazLoblV/NDtmypiNuiQaEok4V4pZI4XLeMIofh3Uv4ZkVlSBVhlAOHEd0H0jiBazhAbMyg7xscTo1/ZBY14ZnvF1Tjepad282nsQfcwDvIGv8nvmOdOvtf99H4GtPhNA5SKQrRnsgxYNZb9eeqx8thAf9Diw/QBUXbhHu4bB6eSYyFGFD3JxBvOBSWXNnUqsKuwLXoIcAbuE7s25MQHrQoSWuw/AklHFvcXEQodRBqjvYnQPjM0cWW9dbguGovmVf0ncdmPUJti5L4GQfTJH+U1x7vII7T3VWtu9pwdAwPB8Lj+v+cDh5vTjOvtbqJ17n+JhTpuj3DIKjI3xWrRXnom6zv/9neYg4H7CEvf0/Qu0OsPHo3OcaVbncU/7ru6MOHKxdyjIlJzjWV398Xqzry9np1Yj4SqnuOz5WypqTsrv7FSWPCWZjlp0Oqb4+1wcft0yIHShagyc0G4PsKWZJTzOUQCbXrybrV1DP78JdXSBu73hBT8wlTtND+iHyNaBsN/sKNUtkCUcnuvLZu4Nv2l7/J/WRZel3F8H","base64")).toString()),iY)});var EBe=_((LZt,yBe)=>{var pY=Symbol("arg flag"),Yc=class t extends Error{constructor(e,r){super(e),this.name="ArgError",this.code=r,Object.setPrototypeOf(this,t.prototype)}};function UD(t,{argv:e=process.argv.slice(2),permissive:r=!1,stopAtPositional:s=!1}={}){if(!t)throw new Yc("argument specification object is required","ARG_CONFIG_NO_SPEC");let a={_:[]},n={},c={};for(let f of Object.keys(t)){if(!f)throw new Yc("argument key cannot be an empty string","ARG_CONFIG_EMPTY_KEY");if(f[0]!=="-")throw new Yc(`argument key must start with '-' but found: '${f}'`,"ARG_CONFIG_NONOPT_KEY");if(f.length===1)throw new Yc(`argument key must have a name; singular '-' keys are not allowed: ${f}`,"ARG_CONFIG_NONAME_KEY");if(typeof t[f]=="string"){n[f]=t[f];continue}let p=t[f],h=!1;if(Array.isArray(p)&&p.length===1&&typeof p[0]=="function"){let[E]=p;p=(C,S,P=[])=>(P.push(E(C,S,P[P.length-1])),P),h=E===Boolean||E[pY]===!0}else if(typeof p=="function")h=p===Boolean||p[pY]===!0;else throw new Yc(`type missing or not a function or valid array type: ${f}`,"ARG_CONFIG_VAD_TYPE");if(f[1]!=="-"&&f.length>2)throw new Yc(`short argument keys (with a single hyphen) must have only one character: ${f}`,"ARG_CONFIG_SHORTOPT_TOOLONG");c[f]=[p,h]}for(let f=0,p=e.length;f0){a._=a._.concat(e.slice(f));break}if(h==="--"){a._=a._.concat(e.slice(f+1));break}if(h.length>1&&h[0]==="-"){let E=h[1]==="-"||h.length===2?[h]:h.slice(1).split("").map(C=>`-${C}`);for(let C=0;C1&&e[f+1][0]==="-"&&!(e[f+1].match(/^-?\d*(\.(?=\d))?\d*$/)&&(N===Number||typeof BigInt<"u"&&N===BigInt))){let W=P===R?"":` (alias for ${R})`;throw new Yc(`option requires argument: ${P}${W}`,"ARG_MISSING_REQUIRED_LONGARG")}a[R]=N(e[f+1],R,a[R]),++f}else a[R]=N(I,R,a[R])}}else a._.push(h)}return a}UD.flag=t=>(t[pY]=!0,t);UD.COUNT=UD.flag((t,e,r)=>(r||0)+1);UD.ArgError=Yc;yBe.exports=UD});var bBe=_((p$t,DBe)=>{var mY;DBe.exports=()=>(typeof mY>"u"&&(mY=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),mY)});var TBe=_((BY,vY)=>{(function(t){BY&&typeof BY=="object"&&typeof vY<"u"?vY.exports=t():typeof define=="function"&&define.amd?define([],t):typeof window<"u"?window.isWindows=t():typeof global<"u"?global.isWindows=t():typeof self<"u"?self.isWindows=t():this.isWindows=t()})(function(){"use strict";return function(){return process&&(process.platform==="win32"||/^(msys|cygwin)$/.test(process.env.OSTYPE))}})});var OBe=_((fer,NBe)=>{"use strict";SY.ifExists=Sdt;var Dw=Ie("util"),Vc=Ie("path"),RBe=TBe(),wdt=/^#!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+)(.*)$/,Bdt={createPwshFile:!0,createCmdFile:RBe(),fs:Ie("fs")},vdt=new Map([[".js","node"],[".cjs","node"],[".mjs","node"],[".cmd","cmd"],[".bat","cmd"],[".ps1","pwsh"],[".sh","sh"]]);function FBe(t){let e={...Bdt,...t},r=e.fs;return e.fs_={chmod:r.chmod?Dw.promisify(r.chmod):async()=>{},mkdir:Dw.promisify(r.mkdir),readFile:Dw.promisify(r.readFile),stat:Dw.promisify(r.stat),unlink:Dw.promisify(r.unlink),writeFile:Dw.promisify(r.writeFile)},e}async function SY(t,e,r){let s=FBe(r);await s.fs_.stat(t),await bdt(t,e,s)}function Sdt(t,e,r){return SY(t,e,r).catch(()=>{})}function Ddt(t,e){return e.fs_.unlink(t).catch(()=>{})}async function bdt(t,e,r){let s=await Tdt(t,r);return await Pdt(e,r),xdt(t,e,s,r)}function Pdt(t,e){return e.fs_.mkdir(Vc.dirname(t),{recursive:!0})}function xdt(t,e,r,s){let a=FBe(s),n=[{generator:Ndt,extension:""}];return a.createCmdFile&&n.push({generator:Fdt,extension:".cmd"}),a.createPwshFile&&n.push({generator:Odt,extension:".ps1"}),Promise.all(n.map(c=>Rdt(t,e+c.extension,r,c.generator,a)))}function kdt(t,e){return Ddt(t,e)}function Qdt(t,e){return Ldt(t,e)}async function Tdt(t,e){let a=(await e.fs_.readFile(t,"utf8")).trim().split(/\r*\n/)[0].match(wdt);if(!a){let n=Vc.extname(t).toLowerCase();return{program:vdt.get(n)||null,additionalArgs:""}}return{program:a[1],additionalArgs:a[2]}}async function Rdt(t,e,r,s,a){let n=a.preserveSymlinks?"--preserve-symlinks":"",c=[r.additionalArgs,n].filter(f=>f).join(" ");return a=Object.assign({},a,{prog:r.program,args:c}),await kdt(e,a),await a.fs_.writeFile(e,s(t,e,a),"utf8"),Qdt(e,a)}function Fdt(t,e,r){let a=Vc.relative(Vc.dirname(e),t).split("/").join("\\"),n=Vc.isAbsolute(a)?`"${a}"`:`"%~dp0\\${a}"`,c,f=r.prog,p=r.args||"",h=DY(r.nodePath).win32;f?(c=`"%~dp0\\${f}.exe"`,a=n):(f=n,p="",a="");let E=r.progArgs?`${r.progArgs.join(" ")} `:"",C=h?`@SET NODE_PATH=${h}\r +`:"";return c?C+=`@IF EXIST ${c} (\r + ${c} ${p} ${a} ${E}%*\r +) ELSE (\r + @SETLOCAL\r + @SET PATHEXT=%PATHEXT:;.JS;=;%\r + ${f} ${p} ${a} ${E}%*\r +)\r +`:C+=`@${f} ${p} ${a} ${E}%*\r +`,C}function Ndt(t,e,r){let s=Vc.relative(Vc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n;s=s.split("\\").join("/");let c=Vc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,f=r.args||"",p=DY(r.nodePath).posix;a?(n=`"$basedir/${r.prog}"`,s=c):(a=c,f="",s="");let h=r.progArgs?`${r.progArgs.join(" ")} `:"",E=`#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')") + +case \`uname\` in + *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;; +esac + +`,C=r.nodePath?`export NODE_PATH="${p}" +`:"";return n?E+=`${C}if [ -x ${n} ]; then + exec ${n} ${f} ${s} ${h}"$@" +else + exec ${a} ${f} ${s} ${h}"$@" +fi +`:E+=`${C}${a} ${f} ${s} ${h}"$@" +exit $? +`,E}function Odt(t,e,r){let s=Vc.relative(Vc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n=a&&`"${a}$exe"`,c;s=s.split("\\").join("/");let f=Vc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,p=r.args||"",h=DY(r.nodePath),E=h.win32,C=h.posix;n?(c=`"$basedir/${r.prog}$exe"`,s=f):(n=f,p="",s="");let S=r.progArgs?`${r.progArgs.join(" ")} `:"",P=`#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +${r.nodePath?`$env_node_path=$env:NODE_PATH +$env:NODE_PATH="${E}" +`:""}if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +}`;return r.nodePath&&(P+=` else { + $env:NODE_PATH="${C}" +}`),c?P+=` +$ret=0 +if (Test-Path ${c}) { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & ${c} ${p} ${s} ${S}$args + } else { + & ${c} ${p} ${s} ${S}$args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & ${n} ${p} ${s} ${S}$args + } else { + & ${n} ${p} ${s} ${S}$args + } + $ret=$LASTEXITCODE +} +${r.nodePath?`$env:NODE_PATH=$env_node_path +`:""}exit $ret +`:P+=` +# Support pipeline input +if ($MyInvocation.ExpectingInput) { + $input | & ${n} ${p} ${s} ${S}$args +} else { + & ${n} ${p} ${s} ${S}$args +} +${r.nodePath?`$env:NODE_PATH=$env_node_path +`:""}exit $LASTEXITCODE +`,P}function Ldt(t,e){return e.fs_.chmod(t,493)}function DY(t){if(!t)return{win32:"",posix:""};let e=typeof t=="string"?t.split(Vc.delimiter):Array.from(t),r={};for(let s=0;s`/mnt/${f.toLowerCase()}`):e[s];r.win32=r.win32?`${r.win32};${a}`:a,r.posix=r.posix?`${r.posix}:${n}`:n,r[s]={win32:a,posix:n}}return r}NBe.exports=SY});var _Y=_((_tr,tve)=>{tve.exports=Ie("stream")});var sve=_((Htr,ive)=>{"use strict";function rve(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function mmt(t){for(var e=1;e0?this.tail.next=s:this.head=s,this.tail=s,++this.length}},{key:"unshift",value:function(r){var s={data:r,next:this.head};this.length===0&&(this.tail=s),this.head=s,++this.length}},{key:"shift",value:function(){if(this.length!==0){var r=this.head.data;return this.length===1?this.head=this.tail=null:this.head=this.head.next,--this.length,r}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(r){if(this.length===0)return"";for(var s=this.head,a=""+s.data;s=s.next;)a+=r+s.data;return a}},{key:"concat",value:function(r){if(this.length===0)return pN.alloc(0);for(var s=pN.allocUnsafe(r>>>0),a=this.head,n=0;a;)vmt(a.data,s,n),n+=a.data.length,a=a.next;return s}},{key:"consume",value:function(r,s){var a;return rc.length?c.length:r;if(f===c.length?n+=c:n+=c.slice(0,r),r-=f,r===0){f===c.length?(++a,s.next?this.head=s.next:this.head=this.tail=null):(this.head=s,s.data=c.slice(f));break}++a}return this.length-=a,n}},{key:"_getBuffer",value:function(r){var s=pN.allocUnsafe(r),a=this.head,n=1;for(a.data.copy(s),r-=a.data.length;a=a.next;){var c=a.data,f=r>c.length?c.length:r;if(c.copy(s,s.length-r,0,f),r-=f,r===0){f===c.length?(++n,a.next?this.head=a.next:this.head=this.tail=null):(this.head=a,a.data=c.slice(f));break}++n}return this.length-=n,s}},{key:Bmt,value:function(r,s){return HY(this,mmt({},s,{depth:0,customInspect:!1}))}}]),t}()});var GY=_((jtr,ave)=>{"use strict";function Smt(t,e){var r=this,s=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return s||a?(e?e(t):t&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,process.nextTick(jY,this,t)):process.nextTick(jY,this,t)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(t||null,function(n){!e&&n?r._writableState?r._writableState.errorEmitted?process.nextTick(hN,r):(r._writableState.errorEmitted=!0,process.nextTick(ove,r,n)):process.nextTick(ove,r,n):e?(process.nextTick(hN,r),e(n)):process.nextTick(hN,r)}),this)}function ove(t,e){jY(t,e),hN(t)}function hN(t){t._writableState&&!t._writableState.emitClose||t._readableState&&!t._readableState.emitClose||t.emit("close")}function Dmt(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function jY(t,e){t.emit("error",e)}function bmt(t,e){var r=t._readableState,s=t._writableState;r&&r.autoDestroy||s&&s.autoDestroy?t.destroy(e):t.emit("error",e)}ave.exports={destroy:Smt,undestroy:Dmt,errorOrDestroy:bmt}});var lg=_((Gtr,uve)=>{"use strict";var cve={};function Kc(t,e,r){r||(r=Error);function s(n,c,f){return typeof e=="string"?e:e(n,c,f)}class a extends r{constructor(c,f,p){super(s(c,f,p))}}a.prototype.name=r.name,a.prototype.code=t,cve[t]=a}function lve(t,e){if(Array.isArray(t)){let r=t.length;return t=t.map(s=>String(s)),r>2?`one of ${e} ${t.slice(0,r-1).join(", ")}, or `+t[r-1]:r===2?`one of ${e} ${t[0]} or ${t[1]}`:`of ${e} ${t[0]}`}else return`of ${e} ${String(t)}`}function Pmt(t,e,r){return t.substr(!r||r<0?0:+r,e.length)===e}function xmt(t,e,r){return(r===void 0||r>t.length)&&(r=t.length),t.substring(r-e.length,r)===e}function kmt(t,e,r){return typeof r!="number"&&(r=0),r+e.length>t.length?!1:t.indexOf(e,r)!==-1}Kc("ERR_INVALID_OPT_VALUE",function(t,e){return'The value "'+e+'" is invalid for option "'+t+'"'},TypeError);Kc("ERR_INVALID_ARG_TYPE",function(t,e,r){let s;typeof e=="string"&&Pmt(e,"not ")?(s="must not be",e=e.replace(/^not /,"")):s="must be";let a;if(xmt(t," argument"))a=`The ${t} ${s} ${lve(e,"type")}`;else{let n=kmt(t,".")?"property":"argument";a=`The "${t}" ${n} ${s} ${lve(e,"type")}`}return a+=`. Received type ${typeof r}`,a},TypeError);Kc("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF");Kc("ERR_METHOD_NOT_IMPLEMENTED",function(t){return"The "+t+" method is not implemented"});Kc("ERR_STREAM_PREMATURE_CLOSE","Premature close");Kc("ERR_STREAM_DESTROYED",function(t){return"Cannot call "+t+" after a stream was destroyed"});Kc("ERR_MULTIPLE_CALLBACK","Callback called multiple times");Kc("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable");Kc("ERR_STREAM_WRITE_AFTER_END","write after end");Kc("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError);Kc("ERR_UNKNOWN_ENCODING",function(t){return"Unknown encoding: "+t},TypeError);Kc("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event");uve.exports.codes=cve});var qY=_((qtr,fve)=>{"use strict";var Qmt=lg().codes.ERR_INVALID_OPT_VALUE;function Tmt(t,e,r){return t.highWaterMark!=null?t.highWaterMark:e?t[r]:null}function Rmt(t,e,r,s){var a=Tmt(e,s,r);if(a!=null){if(!(isFinite(a)&&Math.floor(a)===a)||a<0){var n=s?r:"highWaterMark";throw new Qmt(n,a)}return Math.floor(a)}return t.objectMode?16:16*1024}fve.exports={getHighWaterMark:Rmt}});var Ave=_((Wtr,WY)=>{typeof Object.create=="function"?WY.exports=function(e,r){r&&(e.super_=r,e.prototype=Object.create(r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:WY.exports=function(e,r){if(r){e.super_=r;var s=function(){};s.prototype=r.prototype,e.prototype=new s,e.prototype.constructor=e}}});var cg=_((Ytr,VY)=>{try{if(YY=Ie("util"),typeof YY.inherits!="function")throw"";VY.exports=YY.inherits}catch{VY.exports=Ave()}var YY});var hve=_((Vtr,pve)=>{pve.exports=Ie("util").deprecate});var zY=_((Jtr,Ive)=>{"use strict";Ive.exports=Vi;function dve(t){var e=this;this.next=null,this.entry=null,this.finish=function(){oyt(e,t)}}var Tw;Vi.WritableState=ZD;var Fmt={deprecate:hve()},mve=_Y(),dN=Ie("buffer").Buffer,Nmt=global.Uint8Array||function(){};function Omt(t){return dN.from(t)}function Lmt(t){return dN.isBuffer(t)||t instanceof Nmt}var KY=GY(),Mmt=qY(),Umt=Mmt.getHighWaterMark,ug=lg().codes,_mt=ug.ERR_INVALID_ARG_TYPE,Hmt=ug.ERR_METHOD_NOT_IMPLEMENTED,jmt=ug.ERR_MULTIPLE_CALLBACK,Gmt=ug.ERR_STREAM_CANNOT_PIPE,qmt=ug.ERR_STREAM_DESTROYED,Wmt=ug.ERR_STREAM_NULL_VALUES,Ymt=ug.ERR_STREAM_WRITE_AFTER_END,Vmt=ug.ERR_UNKNOWN_ENCODING,Rw=KY.errorOrDestroy;cg()(Vi,mve);function Jmt(){}function ZD(t,e,r){Tw=Tw||Ym(),t=t||{},typeof r!="boolean"&&(r=e instanceof Tw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode),this.highWaterMark=Umt(this,t,"writableHighWaterMark",r),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var s=t.decodeStrings===!1;this.decodeStrings=!s,this.defaultEncoding=t.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(a){tyt(e,a)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new dve(this)}ZD.prototype.getBuffer=function(){for(var e=this.bufferedRequest,r=[];e;)r.push(e),e=e.next;return r};(function(){try{Object.defineProperty(ZD.prototype,"buffer",{get:Fmt.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch{}})();var gN;typeof Symbol=="function"&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]=="function"?(gN=Function.prototype[Symbol.hasInstance],Object.defineProperty(Vi,Symbol.hasInstance,{value:function(e){return gN.call(this,e)?!0:this!==Vi?!1:e&&e._writableState instanceof ZD}})):gN=function(e){return e instanceof this};function Vi(t){Tw=Tw||Ym();var e=this instanceof Tw;if(!e&&!gN.call(Vi,this))return new Vi(t);this._writableState=new ZD(t,this,e),this.writable=!0,t&&(typeof t.write=="function"&&(this._write=t.write),typeof t.writev=="function"&&(this._writev=t.writev),typeof t.destroy=="function"&&(this._destroy=t.destroy),typeof t.final=="function"&&(this._final=t.final)),mve.call(this)}Vi.prototype.pipe=function(){Rw(this,new Gmt)};function Kmt(t,e){var r=new Ymt;Rw(t,r),process.nextTick(e,r)}function zmt(t,e,r,s){var a;return r===null?a=new Wmt:typeof r!="string"&&!e.objectMode&&(a=new _mt("chunk",["string","Buffer"],r)),a?(Rw(t,a),process.nextTick(s,a),!1):!0}Vi.prototype.write=function(t,e,r){var s=this._writableState,a=!1,n=!s.objectMode&&Lmt(t);return n&&!dN.isBuffer(t)&&(t=Omt(t)),typeof e=="function"&&(r=e,e=null),n?e="buffer":e||(e=s.defaultEncoding),typeof r!="function"&&(r=Jmt),s.ending?Kmt(this,r):(n||zmt(this,s,t,r))&&(s.pendingcb++,a=Zmt(this,s,n,t,e,r)),a};Vi.prototype.cork=function(){this._writableState.corked++};Vi.prototype.uncork=function(){var t=this._writableState;t.corked&&(t.corked--,!t.writing&&!t.corked&&!t.bufferProcessing&&t.bufferedRequest&&yve(this,t))};Vi.prototype.setDefaultEncoding=function(e){if(typeof e=="string"&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new Vmt(e);return this._writableState.defaultEncoding=e,this};Object.defineProperty(Vi.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}});function Xmt(t,e,r){return!t.objectMode&&t.decodeStrings!==!1&&typeof e=="string"&&(e=dN.from(e,r)),e}Object.defineProperty(Vi.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}});function Zmt(t,e,r,s,a,n){if(!r){var c=Xmt(e,s,a);s!==c&&(r=!0,a="buffer",s=c)}var f=e.objectMode?1:s.length;e.length+=f;var p=e.length{"use strict";var ayt=Object.keys||function(t){var e=[];for(var r in t)e.push(r);return e};wve.exports=dA;var Cve=$Y(),ZY=zY();cg()(dA,Cve);for(XY=ayt(ZY.prototype),mN=0;mN{var EN=Ie("buffer"),ah=EN.Buffer;function Bve(t,e){for(var r in t)e[r]=t[r]}ah.from&&ah.alloc&&ah.allocUnsafe&&ah.allocUnsafeSlow?vve.exports=EN:(Bve(EN,eV),eV.Buffer=Fw);function Fw(t,e,r){return ah(t,e,r)}Bve(ah,Fw);Fw.from=function(t,e,r){if(typeof t=="number")throw new TypeError("Argument must not be a number");return ah(t,e,r)};Fw.alloc=function(t,e,r){if(typeof t!="number")throw new TypeError("Argument must be a number");var s=ah(t);return e!==void 0?typeof r=="string"?s.fill(e,r):s.fill(e):s.fill(0),s};Fw.allocUnsafe=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return ah(t)};Fw.allocUnsafeSlow=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return EN.SlowBuffer(t)}});var nV=_(bve=>{"use strict";var rV=Sve().Buffer,Dve=rV.isEncoding||function(t){switch(t=""+t,t&&t.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function uyt(t){if(!t)return"utf8";for(var e;;)switch(t){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return t;default:if(e)return;t=(""+t).toLowerCase(),e=!0}}function fyt(t){var e=uyt(t);if(typeof e!="string"&&(rV.isEncoding===Dve||!Dve(t)))throw new Error("Unknown encoding: "+t);return e||t}bve.StringDecoder=$D;function $D(t){this.encoding=fyt(t);var e;switch(this.encoding){case"utf16le":this.text=myt,this.end=yyt,e=4;break;case"utf8":this.fillLast=hyt,e=4;break;case"base64":this.text=Eyt,this.end=Iyt,e=3;break;default:this.write=Cyt,this.end=wyt;return}this.lastNeed=0,this.lastTotal=0,this.lastChar=rV.allocUnsafe(e)}$D.prototype.write=function(t){if(t.length===0)return"";var e,r;if(this.lastNeed){if(e=this.fillLast(t),e===void 0)return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r>5===6?2:t>>4===14?3:t>>3===30?4:t>>6===2?-1:-2}function Ayt(t,e,r){var s=e.length-1;if(s=0?(a>0&&(t.lastNeed=a-1),a):--s=0?(a>0&&(t.lastNeed=a-2),a):--s=0?(a>0&&(a===2?a=0:t.lastNeed=a-3),a):0))}function pyt(t,e,r){if((e[0]&192)!==128)return t.lastNeed=0,"\uFFFD";if(t.lastNeed>1&&e.length>1){if((e[1]&192)!==128)return t.lastNeed=1,"\uFFFD";if(t.lastNeed>2&&e.length>2&&(e[2]&192)!==128)return t.lastNeed=2,"\uFFFD"}}function hyt(t){var e=this.lastTotal-this.lastNeed,r=pyt(this,t,e);if(r!==void 0)return r;if(this.lastNeed<=t.length)return t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,e,0,t.length),this.lastNeed-=t.length}function gyt(t,e){var r=Ayt(this,t,e);if(!this.lastNeed)return t.toString("utf8",e);this.lastTotal=r;var s=t.length-(r-this.lastNeed);return t.copy(this.lastChar,0,s),t.toString("utf8",e,s)}function dyt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+"\uFFFD":e}function myt(t,e){if((t.length-e)%2===0){var r=t.toString("utf16le",e);if(r){var s=r.charCodeAt(r.length-1);if(s>=55296&&s<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString("utf16le",e,t.length-1)}function yyt(t){var e=t&&t.length?this.write(t):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return e+this.lastChar.toString("utf16le",0,r)}return e}function Eyt(t,e){var r=(t.length-e)%3;return r===0?t.toString("base64",e):(this.lastNeed=3-r,this.lastTotal=3,r===1?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString("base64",e,t.length-r))}function Iyt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+this.lastChar.toString("base64",0,3-this.lastNeed):e}function Cyt(t){return t.toString(this.encoding)}function wyt(t){return t&&t.length?this.write(t):""}});var IN=_((Xtr,kve)=>{"use strict";var Pve=lg().codes.ERR_STREAM_PREMATURE_CLOSE;function Byt(t){var e=!1;return function(){if(!e){e=!0;for(var r=arguments.length,s=new Array(r),a=0;a{"use strict";var CN;function fg(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var Dyt=IN(),Ag=Symbol("lastResolve"),Vm=Symbol("lastReject"),eb=Symbol("error"),wN=Symbol("ended"),Jm=Symbol("lastPromise"),iV=Symbol("handlePromise"),Km=Symbol("stream");function pg(t,e){return{value:t,done:e}}function byt(t){var e=t[Ag];if(e!==null){var r=t[Km].read();r!==null&&(t[Jm]=null,t[Ag]=null,t[Vm]=null,e(pg(r,!1)))}}function Pyt(t){process.nextTick(byt,t)}function xyt(t,e){return function(r,s){t.then(function(){if(e[wN]){r(pg(void 0,!0));return}e[iV](r,s)},s)}}var kyt=Object.getPrototypeOf(function(){}),Qyt=Object.setPrototypeOf((CN={get stream(){return this[Km]},next:function(){var e=this,r=this[eb];if(r!==null)return Promise.reject(r);if(this[wN])return Promise.resolve(pg(void 0,!0));if(this[Km].destroyed)return new Promise(function(c,f){process.nextTick(function(){e[eb]?f(e[eb]):c(pg(void 0,!0))})});var s=this[Jm],a;if(s)a=new Promise(xyt(s,this));else{var n=this[Km].read();if(n!==null)return Promise.resolve(pg(n,!1));a=new Promise(this[iV])}return this[Jm]=a,a}},fg(CN,Symbol.asyncIterator,function(){return this}),fg(CN,"return",function(){var e=this;return new Promise(function(r,s){e[Km].destroy(null,function(a){if(a){s(a);return}r(pg(void 0,!0))})})}),CN),kyt),Tyt=function(e){var r,s=Object.create(Qyt,(r={},fg(r,Km,{value:e,writable:!0}),fg(r,Ag,{value:null,writable:!0}),fg(r,Vm,{value:null,writable:!0}),fg(r,eb,{value:null,writable:!0}),fg(r,wN,{value:e._readableState.endEmitted,writable:!0}),fg(r,iV,{value:function(n,c){var f=s[Km].read();f?(s[Jm]=null,s[Ag]=null,s[Vm]=null,n(pg(f,!1))):(s[Ag]=n,s[Vm]=c)},writable:!0}),r));return s[Jm]=null,Dyt(e,function(a){if(a&&a.code!=="ERR_STREAM_PREMATURE_CLOSE"){var n=s[Vm];n!==null&&(s[Jm]=null,s[Ag]=null,s[Vm]=null,n(a)),s[eb]=a;return}var c=s[Ag];c!==null&&(s[Jm]=null,s[Ag]=null,s[Vm]=null,c(pg(void 0,!0))),s[wN]=!0}),e.on("readable",Pyt.bind(null,s)),s};Qve.exports=Tyt});var Ove=_(($tr,Nve)=>{"use strict";function Rve(t,e,r,s,a,n,c){try{var f=t[n](c),p=f.value}catch(h){r(h);return}f.done?e(p):Promise.resolve(p).then(s,a)}function Ryt(t){return function(){var e=this,r=arguments;return new Promise(function(s,a){var n=t.apply(e,r);function c(p){Rve(n,s,a,c,f,"next",p)}function f(p){Rve(n,s,a,c,f,"throw",p)}c(void 0)})}}function Fve(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function Fyt(t){for(var e=1;e{"use strict";Yve.exports=Pn;var Nw;Pn.ReadableState=_ve;var trr=Ie("events").EventEmitter,Uve=function(e,r){return e.listeners(r).length},rb=_Y(),BN=Ie("buffer").Buffer,Myt=global.Uint8Array||function(){};function Uyt(t){return BN.from(t)}function _yt(t){return BN.isBuffer(t)||t instanceof Myt}var sV=Ie("util"),cn;sV&&sV.debuglog?cn=sV.debuglog("stream"):cn=function(){};var Hyt=sve(),AV=GY(),jyt=qY(),Gyt=jyt.getHighWaterMark,vN=lg().codes,qyt=vN.ERR_INVALID_ARG_TYPE,Wyt=vN.ERR_STREAM_PUSH_AFTER_EOF,Yyt=vN.ERR_METHOD_NOT_IMPLEMENTED,Vyt=vN.ERR_STREAM_UNSHIFT_AFTER_END_EVENT,Ow,oV,aV;cg()(Pn,rb);var tb=AV.errorOrDestroy,lV=["error","close","destroy","pause","resume"];function Jyt(t,e,r){if(typeof t.prependListener=="function")return t.prependListener(e,r);!t._events||!t._events[e]?t.on(e,r):Array.isArray(t._events[e])?t._events[e].unshift(r):t._events[e]=[r,t._events[e]]}function _ve(t,e,r){Nw=Nw||Ym(),t=t||{},typeof r!="boolean"&&(r=e instanceof Nw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode),this.highWaterMark=Gyt(this,t,"readableHighWaterMark",r),this.buffer=new Hyt,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(Ow||(Ow=nV().StringDecoder),this.decoder=new Ow(t.encoding),this.encoding=t.encoding)}function Pn(t){if(Nw=Nw||Ym(),!(this instanceof Pn))return new Pn(t);var e=this instanceof Nw;this._readableState=new _ve(t,this,e),this.readable=!0,t&&(typeof t.read=="function"&&(this._read=t.read),typeof t.destroy=="function"&&(this._destroy=t.destroy)),rb.call(this)}Object.defineProperty(Pn.prototype,"destroyed",{enumerable:!1,get:function(){return this._readableState===void 0?!1:this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}});Pn.prototype.destroy=AV.destroy;Pn.prototype._undestroy=AV.undestroy;Pn.prototype._destroy=function(t,e){e(t)};Pn.prototype.push=function(t,e){var r=this._readableState,s;return r.objectMode?s=!0:typeof t=="string"&&(e=e||r.defaultEncoding,e!==r.encoding&&(t=BN.from(t,e),e=""),s=!0),Hve(this,t,e,!1,s)};Pn.prototype.unshift=function(t){return Hve(this,t,null,!0,!1)};function Hve(t,e,r,s,a){cn("readableAddChunk",e);var n=t._readableState;if(e===null)n.reading=!1,Xyt(t,n);else{var c;if(a||(c=Kyt(n,e)),c)tb(t,c);else if(n.objectMode||e&&e.length>0)if(typeof e!="string"&&!n.objectMode&&Object.getPrototypeOf(e)!==BN.prototype&&(e=Uyt(e)),s)n.endEmitted?tb(t,new Vyt):cV(t,n,e,!0);else if(n.ended)tb(t,new Wyt);else{if(n.destroyed)return!1;n.reading=!1,n.decoder&&!r?(e=n.decoder.write(e),n.objectMode||e.length!==0?cV(t,n,e,!1):fV(t,n)):cV(t,n,e,!1)}else s||(n.reading=!1,fV(t,n))}return!n.ended&&(n.length=Lve?t=Lve:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}function Mve(t,e){return t<=0||e.length===0&&e.ended?0:e.objectMode?1:t!==t?e.flowing&&e.length?e.buffer.head.data.length:e.length:(t>e.highWaterMark&&(e.highWaterMark=zyt(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}Pn.prototype.read=function(t){cn("read",t),t=parseInt(t,10);var e=this._readableState,r=t;if(t!==0&&(e.emittedReadable=!1),t===0&&e.needReadable&&((e.highWaterMark!==0?e.length>=e.highWaterMark:e.length>0)||e.ended))return cn("read: emitReadable",e.length,e.ended),e.length===0&&e.ended?uV(this):SN(this),null;if(t=Mve(t,e),t===0&&e.ended)return e.length===0&&uV(this),null;var s=e.needReadable;cn("need readable",s),(e.length===0||e.length-t0?a=qve(t,e):a=null,a===null?(e.needReadable=e.length<=e.highWaterMark,t=0):(e.length-=t,e.awaitDrain=0),e.length===0&&(e.ended||(e.needReadable=!0),r!==t&&e.ended&&uV(this)),a!==null&&this.emit("data",a),a};function Xyt(t,e){if(cn("onEofChunk"),!e.ended){if(e.decoder){var r=e.decoder.end();r&&r.length&&(e.buffer.push(r),e.length+=e.objectMode?1:r.length)}e.ended=!0,e.sync?SN(t):(e.needReadable=!1,e.emittedReadable||(e.emittedReadable=!0,jve(t)))}}function SN(t){var e=t._readableState;cn("emitReadable",e.needReadable,e.emittedReadable),e.needReadable=!1,e.emittedReadable||(cn("emitReadable",e.flowing),e.emittedReadable=!0,process.nextTick(jve,t))}function jve(t){var e=t._readableState;cn("emitReadable_",e.destroyed,e.length,e.ended),!e.destroyed&&(e.length||e.ended)&&(t.emit("readable"),e.emittedReadable=!1),e.needReadable=!e.flowing&&!e.ended&&e.length<=e.highWaterMark,pV(t)}function fV(t,e){e.readingMore||(e.readingMore=!0,process.nextTick(Zyt,t,e))}function Zyt(t,e){for(;!e.reading&&!e.ended&&(e.length1&&Wve(s.pipes,t)!==-1)&&!h&&(cn("false write response, pause",s.awaitDrain),s.awaitDrain++),r.pause())}function S(N){cn("onerror",N),R(),t.removeListener("error",S),Uve(t,"error")===0&&tb(t,N)}Jyt(t,"error",S);function P(){t.removeListener("finish",I),R()}t.once("close",P);function I(){cn("onfinish"),t.removeListener("close",P),R()}t.once("finish",I);function R(){cn("unpipe"),r.unpipe(t)}return t.emit("pipe",r),s.flowing||(cn("pipe resume"),r.resume()),t};function $yt(t){return function(){var r=t._readableState;cn("pipeOnDrain",r.awaitDrain),r.awaitDrain&&r.awaitDrain--,r.awaitDrain===0&&Uve(t,"data")&&(r.flowing=!0,pV(t))}}Pn.prototype.unpipe=function(t){var e=this._readableState,r={hasUnpiped:!1};if(e.pipesCount===0)return this;if(e.pipesCount===1)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit("unpipe",this,r),this);if(!t){var s=e.pipes,a=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var n=0;n0,s.flowing!==!1&&this.resume()):t==="readable"&&!s.endEmitted&&!s.readableListening&&(s.readableListening=s.needReadable=!0,s.flowing=!1,s.emittedReadable=!1,cn("on readable",s.length,s.reading),s.length?SN(this):s.reading||process.nextTick(eEt,this)),r};Pn.prototype.addListener=Pn.prototype.on;Pn.prototype.removeListener=function(t,e){var r=rb.prototype.removeListener.call(this,t,e);return t==="readable"&&process.nextTick(Gve,this),r};Pn.prototype.removeAllListeners=function(t){var e=rb.prototype.removeAllListeners.apply(this,arguments);return(t==="readable"||t===void 0)&&process.nextTick(Gve,this),e};function Gve(t){var e=t._readableState;e.readableListening=t.listenerCount("readable")>0,e.resumeScheduled&&!e.paused?e.flowing=!0:t.listenerCount("data")>0&&t.resume()}function eEt(t){cn("readable nexttick read 0"),t.read(0)}Pn.prototype.resume=function(){var t=this._readableState;return t.flowing||(cn("resume"),t.flowing=!t.readableListening,tEt(this,t)),t.paused=!1,this};function tEt(t,e){e.resumeScheduled||(e.resumeScheduled=!0,process.nextTick(rEt,t,e))}function rEt(t,e){cn("resume",e.reading),e.reading||t.read(0),e.resumeScheduled=!1,t.emit("resume"),pV(t),e.flowing&&!e.reading&&t.read(0)}Pn.prototype.pause=function(){return cn("call pause flowing=%j",this._readableState.flowing),this._readableState.flowing!==!1&&(cn("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this};function pV(t){var e=t._readableState;for(cn("flow",e.flowing);e.flowing&&t.read()!==null;);}Pn.prototype.wrap=function(t){var e=this,r=this._readableState,s=!1;t.on("end",function(){if(cn("wrapped end"),r.decoder&&!r.ended){var c=r.decoder.end();c&&c.length&&e.push(c)}e.push(null)}),t.on("data",function(c){if(cn("wrapped data"),r.decoder&&(c=r.decoder.write(c)),!(r.objectMode&&c==null)&&!(!r.objectMode&&(!c||!c.length))){var f=e.push(c);f||(s=!0,t.pause())}});for(var a in t)this[a]===void 0&&typeof t[a]=="function"&&(this[a]=function(f){return function(){return t[f].apply(t,arguments)}}(a));for(var n=0;n=e.length?(e.decoder?r=e.buffer.join(""):e.buffer.length===1?r=e.buffer.first():r=e.buffer.concat(e.length),e.buffer.clear()):r=e.buffer.consume(t,e.decoder),r}function uV(t){var e=t._readableState;cn("endReadable",e.endEmitted),e.endEmitted||(e.ended=!0,process.nextTick(nEt,e,t))}function nEt(t,e){if(cn("endReadableNT",t.endEmitted,t.length),!t.endEmitted&&t.length===0&&(t.endEmitted=!0,e.readable=!1,e.emit("end"),t.autoDestroy)){var r=e._writableState;(!r||r.autoDestroy&&r.finished)&&e.destroy()}}typeof Symbol=="function"&&(Pn.from=function(t,e){return aV===void 0&&(aV=Ove()),aV(Pn,t,e)});function Wve(t,e){for(var r=0,s=t.length;r{"use strict";Jve.exports=lh;var DN=lg().codes,iEt=DN.ERR_METHOD_NOT_IMPLEMENTED,sEt=DN.ERR_MULTIPLE_CALLBACK,oEt=DN.ERR_TRANSFORM_ALREADY_TRANSFORMING,aEt=DN.ERR_TRANSFORM_WITH_LENGTH_0,bN=Ym();cg()(lh,bN);function lEt(t,e){var r=this._transformState;r.transforming=!1;var s=r.writecb;if(s===null)return this.emit("error",new sEt);r.writechunk=null,r.writecb=null,e!=null&&this.push(e),s(t);var a=this._readableState;a.reading=!1,(a.needReadable||a.length{"use strict";zve.exports=nb;var Kve=hV();cg()(nb,Kve);function nb(t){if(!(this instanceof nb))return new nb(t);Kve.call(this,t)}nb.prototype._transform=function(t,e,r){r(null,t)}});var rSe=_((srr,tSe)=>{"use strict";var gV;function uEt(t){var e=!1;return function(){e||(e=!0,t.apply(void 0,arguments))}}var eSe=lg().codes,fEt=eSe.ERR_MISSING_ARGS,AEt=eSe.ERR_STREAM_DESTROYED;function Zve(t){if(t)throw t}function pEt(t){return t.setHeader&&typeof t.abort=="function"}function hEt(t,e,r,s){s=uEt(s);var a=!1;t.on("close",function(){a=!0}),gV===void 0&&(gV=IN()),gV(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,pEt(t))return t.abort();if(typeof t.destroy=="function")return t.destroy();s(c||new AEt("pipe"))}}}function $ve(t){t()}function gEt(t,e){return t.pipe(e)}function dEt(t){return!t.length||typeof t[t.length-1]!="function"?Zve:t.pop()}function mEt(){for(var t=arguments.length,e=new Array(t),r=0;r0;return hEt(c,p,h,function(E){a||(a=E),E&&n.forEach($ve),!p&&(n.forEach($ve),s(a))})});return e.reduce(gEt)}tSe.exports=mEt});var Lw=_((zc,sb)=>{var ib=Ie("stream");process.env.READABLE_STREAM==="disable"&&ib?(sb.exports=ib.Readable,Object.assign(sb.exports,ib),sb.exports.Stream=ib):(zc=sb.exports=$Y(),zc.Stream=ib||zc,zc.Readable=zc,zc.Writable=zY(),zc.Duplex=Ym(),zc.Transform=hV(),zc.PassThrough=Xve(),zc.finished=IN(),zc.pipeline=rSe())});var sSe=_((orr,iSe)=>{"use strict";var{Buffer:cf}=Ie("buffer"),nSe=Symbol.for("BufferList");function Ci(t){if(!(this instanceof Ci))return new Ci(t);Ci._init.call(this,t)}Ci._init=function(e){Object.defineProperty(this,nSe,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)};Ci.prototype._new=function(e){return new Ci(e)};Ci.prototype._offset=function(e){if(e===0)return[0,0];let r=0;for(let s=0;sthis.length||e<0)return;let r=this._offset(e);return this._bufs[r[0]][r[1]]};Ci.prototype.slice=function(e,r){return typeof e=="number"&&e<0&&(e+=this.length),typeof r=="number"&&r<0&&(r+=this.length),this.copy(null,0,e,r)};Ci.prototype.copy=function(e,r,s,a){if((typeof s!="number"||s<0)&&(s=0),(typeof a!="number"||a>this.length)&&(a=this.length),s>=this.length||a<=0)return e||cf.alloc(0);let n=!!e,c=this._offset(s),f=a-s,p=f,h=n&&r||0,E=c[1];if(s===0&&a===this.length){if(!n)return this._bufs.length===1?this._bufs[0]:cf.concat(this._bufs,this.length);for(let C=0;CS)this._bufs[C].copy(e,h,E),h+=S;else{this._bufs[C].copy(e,h,E,E+p),h+=S;break}p-=S,E&&(E=0)}return e.length>h?e.slice(0,h):e};Ci.prototype.shallowSlice=function(e,r){if(e=e||0,r=typeof r!="number"?this.length:r,e<0&&(e+=this.length),r<0&&(r+=this.length),e===r)return this._new();let s=this._offset(e),a=this._offset(r),n=this._bufs.slice(s[0],a[0]+1);return a[1]===0?n.pop():n[n.length-1]=n[n.length-1].slice(0,a[1]),s[1]!==0&&(n[0]=n[0].slice(s[1])),this._new(n)};Ci.prototype.toString=function(e,r,s){return this.slice(r,s).toString(e)};Ci.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;)if(e>=this._bufs[0].length)e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift();else{this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}return this};Ci.prototype.duplicate=function(){let e=this._new();for(let r=0;rthis.length?this.length:e;let s=this._offset(e),a=s[0],n=s[1];for(;a=t.length){let p=c.indexOf(t,n);if(p!==-1)return this._reverseOffset([a,p]);n=c.length-t.length+1}else{let p=this._reverseOffset([a,n]);if(this._match(p,t))return p;n++}n=0}return-1};Ci.prototype._match=function(t,e){if(this.length-t{"use strict";var dV=Lw().Duplex,yEt=cg(),ob=sSe();function ra(t){if(!(this instanceof ra))return new ra(t);if(typeof t=="function"){this._callback=t;let e=function(s){this._callback&&(this._callback(s),this._callback=null)}.bind(this);this.on("pipe",function(s){s.on("error",e)}),this.on("unpipe",function(s){s.removeListener("error",e)}),t=null}ob._init.call(this,t),dV.call(this)}yEt(ra,dV);Object.assign(ra.prototype,ob.prototype);ra.prototype._new=function(e){return new ra(e)};ra.prototype._write=function(e,r,s){this._appendBuffer(e),typeof s=="function"&&s()};ra.prototype._read=function(e){if(!this.length)return this.push(null);e=Math.min(e,this.length),this.push(this.slice(0,e)),this.consume(e)};ra.prototype.end=function(e){dV.prototype.end.call(this,e),this._callback&&(this._callback(null,this.slice()),this._callback=null)};ra.prototype._destroy=function(e,r){this._bufs.length=0,this.length=0,r(e)};ra.prototype._isBufferList=function(e){return e instanceof ra||e instanceof ob||ra.isBufferList(e)};ra.isBufferList=ob.isBufferList;PN.exports=ra;PN.exports.BufferListStream=ra;PN.exports.BufferList=ob});var EV=_(Uw=>{var EEt=Buffer.alloc,IEt="0000000000000000000",CEt="7777777777777777777",aSe=48,lSe=Buffer.from("ustar\0","binary"),wEt=Buffer.from("00","binary"),BEt=Buffer.from("ustar ","binary"),vEt=Buffer.from(" \0","binary"),SEt=parseInt("7777",8),ab=257,yV=263,DEt=function(t,e,r){return typeof t!="number"?r:(t=~~t,t>=e?e:t>=0||(t+=e,t>=0)?t:0)},bEt=function(t){switch(t){case 0:return"file";case 1:return"link";case 2:return"symlink";case 3:return"character-device";case 4:return"block-device";case 5:return"directory";case 6:return"fifo";case 7:return"contiguous-file";case 72:return"pax-header";case 55:return"pax-global-header";case 27:return"gnu-long-link-path";case 28:case 30:return"gnu-long-path"}return null},PEt=function(t){switch(t){case"file":return 0;case"link":return 1;case"symlink":return 2;case"character-device":return 3;case"block-device":return 4;case"directory":return 5;case"fifo":return 6;case"contiguous-file":return 7;case"pax-header":return 72}return 0},cSe=function(t,e,r,s){for(;re?CEt.slice(0,e)+" ":IEt.slice(0,e-t.length)+t+" "};function xEt(t){var e;if(t[0]===128)e=!0;else if(t[0]===255)e=!1;else return null;for(var r=[],s=t.length-1;s>0;s--){var a=t[s];e?r.push(a):r.push(255-a)}var n=0,c=r.length;for(s=0;s=Math.pow(10,r)&&r++,e+r+t};Uw.decodeLongPath=function(t,e){return Mw(t,0,t.length,e)};Uw.encodePax=function(t){var e="";t.name&&(e+=mV(" path="+t.name+` +`)),t.linkname&&(e+=mV(" linkpath="+t.linkname+` +`));var r=t.pax;if(r)for(var s in r)e+=mV(" "+s+"="+r[s]+` +`);return Buffer.from(e)};Uw.decodePax=function(t){for(var e={};t.length;){for(var r=0;r100;){var a=r.indexOf("/");if(a===-1)return null;s+=s?"/"+r.slice(0,a):r.slice(0,a),r=r.slice(a+1)}return Buffer.byteLength(r)>100||Buffer.byteLength(s)>155||t.linkname&&Buffer.byteLength(t.linkname)>100?null:(e.write(r),e.write(hg(t.mode&SEt,6),100),e.write(hg(t.uid,6),108),e.write(hg(t.gid,6),116),e.write(hg(t.size,11),124),e.write(hg(t.mtime.getTime()/1e3|0,11),136),e[156]=aSe+PEt(t.type),t.linkname&&e.write(t.linkname,157),lSe.copy(e,ab),wEt.copy(e,yV),t.uname&&e.write(t.uname,265),t.gname&&e.write(t.gname,297),e.write(hg(t.devmajor||0,6),329),e.write(hg(t.devminor||0,6),337),s&&e.write(s,345),e.write(hg(uSe(e),6),148),e)};Uw.decode=function(t,e,r){var s=t[156]===0?0:t[156]-aSe,a=Mw(t,0,100,e),n=gg(t,100,8),c=gg(t,108,8),f=gg(t,116,8),p=gg(t,124,12),h=gg(t,136,12),E=bEt(s),C=t[157]===0?null:Mw(t,157,100,e),S=Mw(t,265,32),P=Mw(t,297,32),I=gg(t,329,8),R=gg(t,337,8),N=uSe(t);if(N===8*32)return null;if(N!==gg(t,148,8))throw new Error("Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?");if(lSe.compare(t,ab,ab+6)===0)t[345]&&(a=Mw(t,345,155,e)+"/"+a);else if(!(BEt.compare(t,ab,ab+6)===0&&vEt.compare(t,yV,yV+2)===0)){if(!r)throw new Error("Invalid tar header: unknown format.")}return s===0&&a&&a[a.length-1]==="/"&&(s=5),{name:a,mode:n,uid:c,gid:f,size:p,mtime:new Date(1e3*h),type:E,linkname:C,uname:S,gname:P,devmajor:I,devminor:R}}});var mSe=_((crr,dSe)=>{var ASe=Ie("util"),kEt=oSe(),lb=EV(),pSe=Lw().Writable,hSe=Lw().PassThrough,gSe=function(){},fSe=function(t){return t&=511,t&&512-t},QEt=function(t,e){var r=new xN(t,e);return r.end(),r},TEt=function(t,e){return e.path&&(t.name=e.path),e.linkpath&&(t.linkname=e.linkpath),e.size&&(t.size=parseInt(e.size,10)),t.pax=e,t},xN=function(t,e){this._parent=t,this.offset=e,hSe.call(this,{autoDestroy:!1})};ASe.inherits(xN,hSe);xN.prototype.destroy=function(t){this._parent.destroy(t)};var ch=function(t){if(!(this instanceof ch))return new ch(t);pSe.call(this,t),t=t||{},this._offset=0,this._buffer=kEt(),this._missing=0,this._partial=!1,this._onparse=gSe,this._header=null,this._stream=null,this._overflow=null,this._cb=null,this._locked=!1,this._destroyed=!1,this._pax=null,this._paxGlobal=null,this._gnuLongPath=null,this._gnuLongLinkPath=null;var e=this,r=e._buffer,s=function(){e._continue()},a=function(S){if(e._locked=!1,S)return e.destroy(S);e._stream||s()},n=function(){e._stream=null;var S=fSe(e._header.size);S?e._parse(S,c):e._parse(512,C),e._locked||s()},c=function(){e._buffer.consume(fSe(e._header.size)),e._parse(512,C),s()},f=function(){var S=e._header.size;e._paxGlobal=lb.decodePax(r.slice(0,S)),r.consume(S),n()},p=function(){var S=e._header.size;e._pax=lb.decodePax(r.slice(0,S)),e._paxGlobal&&(e._pax=Object.assign({},e._paxGlobal,e._pax)),r.consume(S),n()},h=function(){var S=e._header.size;this._gnuLongPath=lb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},E=function(){var S=e._header.size;this._gnuLongLinkPath=lb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},C=function(){var S=e._offset,P;try{P=e._header=lb.decode(r.slice(0,512),t.filenameEncoding,t.allowUnknownFormat)}catch(I){e.emit("error",I)}if(r.consume(512),!P){e._parse(512,C),s();return}if(P.type==="gnu-long-path"){e._parse(P.size,h),s();return}if(P.type==="gnu-long-link-path"){e._parse(P.size,E),s();return}if(P.type==="pax-global-header"){e._parse(P.size,f),s();return}if(P.type==="pax-header"){e._parse(P.size,p),s();return}if(e._gnuLongPath&&(P.name=e._gnuLongPath,e._gnuLongPath=null),e._gnuLongLinkPath&&(P.linkname=e._gnuLongLinkPath,e._gnuLongLinkPath=null),e._pax&&(e._header=P=TEt(P,e._pax),e._pax=null),e._locked=!0,!P.size||P.type==="directory"){e._parse(512,C),e.emit("entry",P,QEt(e,S),a);return}e._stream=new xN(e,S),e.emit("entry",P,e._stream,a),e._parse(P.size,n),s()};this._onheader=C,this._parse(512,C)};ASe.inherits(ch,pSe);ch.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.emit("close"))};ch.prototype._parse=function(t,e){this._destroyed||(this._offset+=t,this._missing=t,e===this._onheader&&(this._partial=!1),this._onparse=e)};ch.prototype._continue=function(){if(!this._destroyed){var t=this._cb;this._cb=gSe,this._overflow?this._write(this._overflow,void 0,t):t()}};ch.prototype._write=function(t,e,r){if(!this._destroyed){var s=this._stream,a=this._buffer,n=this._missing;if(t.length&&(this._partial=!0),t.lengthn&&(c=t.slice(n),t=t.slice(0,n)),s?s.end(t):a.append(t),this._overflow=c,this._onparse()}};ch.prototype._final=function(t){if(this._partial)return this.destroy(new Error("Unexpected end of data"));t()};dSe.exports=ch});var ESe=_((urr,ySe)=>{ySe.exports=Ie("fs").constants||Ie("constants")});var vSe=_((frr,BSe)=>{var _w=ESe(),ISe=cH(),QN=cg(),REt=Buffer.alloc,CSe=Lw().Readable,Hw=Lw().Writable,FEt=Ie("string_decoder").StringDecoder,kN=EV(),NEt=parseInt("755",8),OEt=parseInt("644",8),wSe=REt(1024),CV=function(){},IV=function(t,e){e&=511,e&&t.push(wSe.slice(0,512-e))};function LEt(t){switch(t&_w.S_IFMT){case _w.S_IFBLK:return"block-device";case _w.S_IFCHR:return"character-device";case _w.S_IFDIR:return"directory";case _w.S_IFIFO:return"fifo";case _w.S_IFLNK:return"symlink"}return"file"}var TN=function(t){Hw.call(this),this.written=0,this._to=t,this._destroyed=!1};QN(TN,Hw);TN.prototype._write=function(t,e,r){if(this.written+=t.length,this._to.push(t))return r();this._to._drain=r};TN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var RN=function(){Hw.call(this),this.linkname="",this._decoder=new FEt("utf-8"),this._destroyed=!1};QN(RN,Hw);RN.prototype._write=function(t,e,r){this.linkname+=this._decoder.write(t),r()};RN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var ub=function(){Hw.call(this),this._destroyed=!1};QN(ub,Hw);ub.prototype._write=function(t,e,r){r(new Error("No body allowed for this entry"))};ub.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var mA=function(t){if(!(this instanceof mA))return new mA(t);CSe.call(this,t),this._drain=CV,this._finalized=!1,this._finalizing=!1,this._destroyed=!1,this._stream=null};QN(mA,CSe);mA.prototype.entry=function(t,e,r){if(this._stream)throw new Error("already piping an entry");if(!(this._finalized||this._destroyed)){typeof e=="function"&&(r=e,e=null),r||(r=CV);var s=this;if((!t.size||t.type==="symlink")&&(t.size=0),t.type||(t.type=LEt(t.mode)),t.mode||(t.mode=t.type==="directory"?NEt:OEt),t.uid||(t.uid=0),t.gid||(t.gid=0),t.mtime||(t.mtime=new Date),typeof e=="string"&&(e=Buffer.from(e)),Buffer.isBuffer(e)){t.size=e.length,this._encode(t);var a=this.push(e);return IV(s,t.size),a?process.nextTick(r):this._drain=r,new ub}if(t.type==="symlink"&&!t.linkname){var n=new RN;return ISe(n,function(f){if(f)return s.destroy(),r(f);t.linkname=n.linkname,s._encode(t),r()}),n}if(this._encode(t),t.type!=="file"&&t.type!=="contiguous-file")return process.nextTick(r),new ub;var c=new TN(this);return this._stream=c,ISe(c,function(f){if(s._stream=null,f)return s.destroy(),r(f);if(c.written!==t.size)return s.destroy(),r(new Error("size mismatch"));IV(s,t.size),s._finalizing&&s.finalize(),r()}),c}};mA.prototype.finalize=function(){if(this._stream){this._finalizing=!0;return}this._finalized||(this._finalized=!0,this.push(wSe),this.push(null))};mA.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.destroy&&this._stream.destroy())};mA.prototype._encode=function(t){if(!t.pax){var e=kN.encode(t);if(e){this.push(e);return}}this._encodePax(t)};mA.prototype._encodePax=function(t){var e=kN.encodePax({name:t.name,linkname:t.linkname,pax:t.pax}),r={name:"PaxHeader",mode:t.mode,uid:t.uid,gid:t.gid,size:e.length,mtime:t.mtime,type:"pax-header",linkname:t.linkname&&"PaxHeader",uname:t.uname,gname:t.gname,devmajor:t.devmajor,devminor:t.devminor};this.push(kN.encode(r)),this.push(e),IV(this,e.length),r.size=t.size,r.type=t.type,this.push(kN.encode(r))};mA.prototype._read=function(t){var e=this._drain;this._drain=CV,e()};BSe.exports=mA});var SSe=_(wV=>{wV.extract=mSe();wV.pack=vSe()});var MSe=_(Ta=>{"use strict";var zEt=Ta&&Ta.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ta,"__esModule",{value:!0});Ta.Minipass=Ta.isWritable=Ta.isReadable=Ta.isStream=void 0;var RSe=typeof process=="object"&&process?process:{stdout:null,stderr:null},FV=Ie("node:events"),LSe=zEt(Ie("node:stream")),XEt=Ie("node:string_decoder"),ZEt=t=>!!t&&typeof t=="object"&&(t instanceof jN||t instanceof LSe.default||(0,Ta.isReadable)(t)||(0,Ta.isWritable)(t));Ta.isStream=ZEt;var $Et=t=>!!t&&typeof t=="object"&&t instanceof FV.EventEmitter&&typeof t.pipe=="function"&&t.pipe!==LSe.default.Writable.prototype.pipe;Ta.isReadable=$Et;var eIt=t=>!!t&&typeof t=="object"&&t instanceof FV.EventEmitter&&typeof t.write=="function"&&typeof t.end=="function";Ta.isWritable=eIt;var uh=Symbol("EOF"),fh=Symbol("maybeEmitEnd"),dg=Symbol("emittedEnd"),ON=Symbol("emittingEnd"),fb=Symbol("emittedError"),LN=Symbol("closed"),FSe=Symbol("read"),MN=Symbol("flush"),NSe=Symbol("flushChunk"),uf=Symbol("encoding"),Gw=Symbol("decoder"),Ks=Symbol("flowing"),Ab=Symbol("paused"),qw=Symbol("resume"),zs=Symbol("buffer"),Qa=Symbol("pipes"),Xs=Symbol("bufferLength"),PV=Symbol("bufferPush"),UN=Symbol("bufferShift"),na=Symbol("objectMode"),ts=Symbol("destroyed"),xV=Symbol("error"),kV=Symbol("emitData"),OSe=Symbol("emitEnd"),QV=Symbol("emitEnd2"),EA=Symbol("async"),TV=Symbol("abort"),_N=Symbol("aborted"),pb=Symbol("signal"),zm=Symbol("dataListeners"),rc=Symbol("discarded"),hb=t=>Promise.resolve().then(t),tIt=t=>t(),rIt=t=>t==="end"||t==="finish"||t==="prefinish",nIt=t=>t instanceof ArrayBuffer||!!t&&typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,iIt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),HN=class{src;dest;opts;ondrain;constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[qw](),this.dest.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(e){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},RV=class extends HN{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}},sIt=t=>!!t.objectMode,oIt=t=>!t.objectMode&&!!t.encoding&&t.encoding!=="buffer",jN=class extends FV.EventEmitter{[Ks]=!1;[Ab]=!1;[Qa]=[];[zs]=[];[na];[uf];[EA];[Gw];[uh]=!1;[dg]=!1;[ON]=!1;[LN]=!1;[fb]=null;[Xs]=0;[ts]=!1;[pb];[_N]=!1;[zm]=0;[rc]=!1;writable=!0;readable=!0;constructor(...e){let r=e[0]||{};if(super(),r.objectMode&&typeof r.encoding=="string")throw new TypeError("Encoding and objectMode may not be used together");sIt(r)?(this[na]=!0,this[uf]=null):oIt(r)?(this[uf]=r.encoding,this[na]=!1):(this[na]=!1,this[uf]=null),this[EA]=!!r.async,this[Gw]=this[uf]?new XEt.StringDecoder(this[uf]):null,r&&r.debugExposeBuffer===!0&&Object.defineProperty(this,"buffer",{get:()=>this[zs]}),r&&r.debugExposePipes===!0&&Object.defineProperty(this,"pipes",{get:()=>this[Qa]});let{signal:s}=r;s&&(this[pb]=s,s.aborted?this[TV]():s.addEventListener("abort",()=>this[TV]()))}get bufferLength(){return this[Xs]}get encoding(){return this[uf]}set encoding(e){throw new Error("Encoding must be set at instantiation time")}setEncoding(e){throw new Error("Encoding must be set at instantiation time")}get objectMode(){return this[na]}set objectMode(e){throw new Error("objectMode must be set at instantiation time")}get async(){return this[EA]}set async(e){this[EA]=this[EA]||!!e}[TV](){this[_N]=!0,this.emit("abort",this[pb]?.reason),this.destroy(this[pb]?.reason)}get aborted(){return this[_N]}set aborted(e){}write(e,r,s){if(this[_N])return!1;if(this[uh])throw new Error("write after end");if(this[ts])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[EA]?hb:tIt;if(!this[na]&&!Buffer.isBuffer(e)){if(iIt(e))e=Buffer.from(e.buffer,e.byteOffset,e.byteLength);else if(nIt(e))e=Buffer.from(e);else if(typeof e!="string")throw new Error("Non-contiguous data written to non-objectMode stream")}return this[na]?(this[Ks]&&this[Xs]!==0&&this[MN](!0),this[Ks]?this.emit("data",e):this[PV](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks]):e.length?(typeof e=="string"&&!(r===this[uf]&&!this[Gw]?.lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[uf]&&(e=this[Gw].write(e)),this[Ks]&&this[Xs]!==0&&this[MN](!0),this[Ks]?this.emit("data",e):this[PV](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks]):(this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks])}read(e){if(this[ts])return null;if(this[rc]=!1,this[Xs]===0||e===0||e&&e>this[Xs])return this[fh](),null;this[na]&&(e=null),this[zs].length>1&&!this[na]&&(this[zs]=[this[uf]?this[zs].join(""):Buffer.concat(this[zs],this[Xs])]);let r=this[FSe](e||null,this[zs][0]);return this[fh](),r}[FSe](e,r){if(this[na])this[UN]();else{let s=r;e===s.length||e===null?this[UN]():typeof s=="string"?(this[zs][0]=s.slice(e),r=s.slice(0,e),this[Xs]-=e):(this[zs][0]=s.subarray(e),r=s.subarray(0,e),this[Xs]-=e)}return this.emit("data",r),!this[zs].length&&!this[uh]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=void 0),typeof r=="function"&&(s=r,r="utf8"),e!==void 0&&this.write(e,r),s&&this.once("end",s),this[uh]=!0,this.writable=!1,(this[Ks]||!this[Ab])&&this[fh](),this}[qw](){this[ts]||(!this[zm]&&!this[Qa].length&&(this[rc]=!0),this[Ab]=!1,this[Ks]=!0,this.emit("resume"),this[zs].length?this[MN]():this[uh]?this[fh]():this.emit("drain"))}resume(){return this[qw]()}pause(){this[Ks]=!1,this[Ab]=!0,this[rc]=!1}get destroyed(){return this[ts]}get flowing(){return this[Ks]}get paused(){return this[Ab]}[PV](e){this[na]?this[Xs]+=1:this[Xs]+=e.length,this[zs].push(e)}[UN](){return this[na]?this[Xs]-=1:this[Xs]-=this[zs][0].length,this[zs].shift()}[MN](e=!1){do;while(this[NSe](this[UN]())&&this[zs].length);!e&&!this[zs].length&&!this[uh]&&this.emit("drain")}[NSe](e){return this.emit("data",e),this[Ks]}pipe(e,r){if(this[ts])return e;this[rc]=!1;let s=this[dg];return r=r||{},e===RSe.stdout||e===RSe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this[Qa].push(r.proxyErrors?new RV(this,e,r):new HN(this,e,r)),this[EA]?hb(()=>this[qw]()):this[qw]()),e}unpipe(e){let r=this[Qa].find(s=>s.dest===e);r&&(this[Qa].length===1?(this[Ks]&&this[zm]===0&&(this[Ks]=!1),this[Qa]=[]):this[Qa].splice(this[Qa].indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);if(e==="data")this[rc]=!1,this[zm]++,!this[Qa].length&&!this[Ks]&&this[qw]();else if(e==="readable"&&this[Xs]!==0)super.emit("readable");else if(rIt(e)&&this[dg])super.emit(e),this.removeAllListeners(e);else if(e==="error"&&this[fb]){let a=r;this[EA]?hb(()=>a.call(this,this[fb])):a.call(this,this[fb])}return s}removeListener(e,r){return this.off(e,r)}off(e,r){let s=super.off(e,r);return e==="data"&&(this[zm]=this.listeners("data").length,this[zm]===0&&!this[rc]&&!this[Qa].length&&(this[Ks]=!1)),s}removeAllListeners(e){let r=super.removeAllListeners(e);return(e==="data"||e===void 0)&&(this[zm]=0,!this[rc]&&!this[Qa].length&&(this[Ks]=!1)),r}get emittedEnd(){return this[dg]}[fh](){!this[ON]&&!this[dg]&&!this[ts]&&this[zs].length===0&&this[uh]&&(this[ON]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[LN]&&this.emit("close"),this[ON]=!1)}emit(e,...r){let s=r[0];if(e!=="error"&&e!=="close"&&e!==ts&&this[ts])return!1;if(e==="data")return!this[na]&&!s?!1:this[EA]?(hb(()=>this[kV](s)),!0):this[kV](s);if(e==="end")return this[OSe]();if(e==="close"){if(this[LN]=!0,!this[dg]&&!this[ts])return!1;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[fb]=s,super.emit(xV,s);let n=!this[pb]||this.listeners("error").length?super.emit("error",s):!1;return this[fh](),n}else if(e==="resume"){let n=super.emit("resume");return this[fh](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,...r);return this[fh](),a}[kV](e){for(let s of this[Qa])s.dest.write(e)===!1&&this.pause();let r=this[rc]?!1:super.emit("data",e);return this[fh](),r}[OSe](){return this[dg]?!1:(this[dg]=!0,this.readable=!1,this[EA]?(hb(()=>this[QV]()),!0):this[QV]())}[QV](){if(this[Gw]){let r=this[Gw].end();if(r){for(let s of this[Qa])s.dest.write(r);this[rc]||super.emit("data",r)}}for(let r of this[Qa])r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}async collect(){let e=Object.assign([],{dataLength:0});this[na]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[na]||(e.dataLength+=s.length)}),await r,e}async concat(){if(this[na])throw new Error("cannot concat in objectMode");let e=await this.collect();return this[uf]?e.join(""):Buffer.concat(e,e.dataLength)}async promise(){return new Promise((e,r)=>{this.on(ts,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Symbol.asyncIterator](){this[rc]=!1;let e=!1,r=async()=>(this.pause(),e=!0,{value:void 0,done:!0});return{next:()=>{if(e)return r();let a=this.read();if(a!==null)return Promise.resolve({done:!1,value:a});if(this[uh])return r();let n,c,f=C=>{this.off("data",p),this.off("end",h),this.off(ts,E),r(),c(C)},p=C=>{this.off("error",f),this.off("end",h),this.off(ts,E),this.pause(),n({value:C,done:!!this[uh]})},h=()=>{this.off("error",f),this.off("data",p),this.off(ts,E),r(),n({done:!0,value:void 0})},E=()=>f(new Error("stream destroyed"));return new Promise((C,S)=>{c=S,n=C,this.once(ts,E),this.once("error",f),this.once("end",h),this.once("data",p)})},throw:r,return:r,[Symbol.asyncIterator](){return this}}}[Symbol.iterator](){this[rc]=!1;let e=!1,r=()=>(this.pause(),this.off(xV,r),this.off(ts,r),this.off("end",r),e=!0,{done:!0,value:void 0}),s=()=>{if(e)return r();let a=this.read();return a===null?r():{done:!1,value:a}};return this.once("end",r),this.once(xV,r),this.once(ts,r),{next:s,throw:r,return:r,[Symbol.iterator](){return this}}}destroy(e){if(this[ts])return e?this.emit("error",e):this.emit(ts),this;this[ts]=!0,this[rc]=!0,this[zs].length=0,this[Xs]=0;let r=this;return typeof r.close=="function"&&!this[LN]&&r.close(),e?this.emit("error",e):this.emit(ts),this}static get isStream(){return Ta.isStream}};Ta.Minipass=jN});var HSe=_((Trr,IA)=>{"use strict";var db=Ie("crypto"),{Minipass:aIt}=MSe(),OV=["sha512","sha384","sha256"],MV=["sha512"],lIt=/^[a-z0-9+/]+(?:=?=?)$/i,cIt=/^([a-z0-9]+)-([^?]+)([?\S*]*)$/,uIt=/^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)?$/,fIt=/^[\x21-\x7E]+$/,mb=t=>t?.length?`?${t.join("?")}`:"",LV=class extends aIt{#t;#r;#i;constructor(e){super(),this.size=0,this.opts=e,this.#e(),e?.algorithms?this.algorithms=[...e.algorithms]:this.algorithms=[...MV],this.algorithm!==null&&!this.algorithms.includes(this.algorithm)&&this.algorithms.push(this.algorithm),this.hashes=this.algorithms.map(db.createHash)}#e(){this.sri=this.opts?.integrity?nc(this.opts?.integrity,this.opts):null,this.expectedSize=this.opts?.size,this.sri?this.sri.isHash?(this.goodSri=!0,this.algorithm=this.sri.algorithm):(this.goodSri=!this.sri.isEmpty(),this.algorithm=this.sri.pickAlgorithm(this.opts)):this.algorithm=null,this.digests=this.goodSri?this.sri[this.algorithm]:null,this.optString=mb(this.opts?.options)}on(e,r){return e==="size"&&this.#r?r(this.#r):e==="integrity"&&this.#t?r(this.#t):e==="verified"&&this.#i?r(this.#i):super.on(e,r)}emit(e,r){return e==="end"&&this.#n(),super.emit(e,r)}write(e){return this.size+=e.length,this.hashes.forEach(r=>r.update(e)),super.write(e)}#n(){this.goodSri||this.#e();let e=nc(this.hashes.map((s,a)=>`${this.algorithms[a]}-${s.digest("base64")}${this.optString}`).join(" "),this.opts),r=this.goodSri&&e.match(this.sri,this.opts);if(typeof this.expectedSize=="number"&&this.size!==this.expectedSize){let s=new Error(`stream size mismatch when checking ${this.sri}. + Wanted: ${this.expectedSize} + Found: ${this.size}`);s.code="EBADSIZE",s.found=this.size,s.expected=this.expectedSize,s.sri=this.sri,this.emit("error",s)}else if(this.sri&&!r){let s=new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${e}. (${this.size} bytes)`);s.code="EINTEGRITY",s.found=e,s.expected=this.digests,s.algorithm=this.algorithm,s.sri=this.sri,this.emit("error",s)}else this.#r=this.size,this.emit("size",this.size),this.#t=e,this.emit("integrity",e),r&&(this.#i=r,this.emit("verified",r))}},Ah=class{get isHash(){return!0}constructor(e,r){let s=r?.strict;this.source=e.trim(),this.digest="",this.algorithm="",this.options=[];let a=this.source.match(s?uIt:cIt);if(!a||s&&!OV.includes(a[1]))return;this.algorithm=a[1],this.digest=a[2];let n=a[3];n&&(this.options=n.slice(1).split("?"))}hexDigest(){return this.digest&&Buffer.from(this.digest,"base64").toString("hex")}toJSON(){return this.toString()}match(e,r){let s=nc(e,r);if(!s)return!1;if(s.isIntegrity){let a=s.pickAlgorithm(r,[this.algorithm]);if(!a)return!1;let n=s[a].find(c=>c.digest===this.digest);return n||!1}return s.digest===this.digest?s:!1}toString(e){return e?.strict&&!(OV.includes(this.algorithm)&&this.digest.match(lIt)&&this.options.every(r=>r.match(fIt)))?"":`${this.algorithm}-${this.digest}${mb(this.options)}`}};function USe(t,e,r,s){let a=t!=="",n=!1,c="",f=s.length-1;for(let h=0;hs[a].find(c=>n.digest===c.digest)))throw new Error("hashes do not match, cannot update integrity")}else this[a]=s[a]}match(e,r){let s=nc(e,r);if(!s)return!1;let a=s.pickAlgorithm(r,Object.keys(this));return!!a&&this[a]&&s[a]&&this[a].find(n=>s[a].find(c=>n.digest===c.digest))||!1}pickAlgorithm(e,r){let s=e?.pickAlgorithm||EIt,a=Object.keys(this).filter(n=>r?.length?r.includes(n):!0);return a.length?a.reduce((n,c)=>s(n,c)||n):null}};IA.exports.parse=nc;function nc(t,e){if(!t)return null;if(typeof t=="string")return NV(t,e);if(t.algorithm&&t.digest){let r=new Xm;return r[t.algorithm]=[t],NV(gb(r,e),e)}else return NV(gb(t,e),e)}function NV(t,e){if(e?.single)return new Ah(t,e);let r=t.trim().split(/\s+/).reduce((s,a)=>{let n=new Ah(a,e);if(n.algorithm&&n.digest){let c=n.algorithm;s[c]||(s[c]=[]),s[c].push(n)}return s},new Xm);return r.isEmpty()?null:r}IA.exports.stringify=gb;function gb(t,e){return t.algorithm&&t.digest?Ah.prototype.toString.call(t,e):typeof t=="string"?gb(nc(t,e),e):Xm.prototype.toString.call(t,e)}IA.exports.fromHex=AIt;function AIt(t,e,r){let s=mb(r?.options);return nc(`${e}-${Buffer.from(t,"hex").toString("base64")}${s}`,r)}IA.exports.fromData=pIt;function pIt(t,e){let r=e?.algorithms||[...MV],s=mb(e?.options);return r.reduce((a,n)=>{let c=db.createHash(n).update(t).digest("base64"),f=new Ah(`${n}-${c}${s}`,e);if(f.algorithm&&f.digest){let p=f.algorithm;a[p]||(a[p]=[]),a[p].push(f)}return a},new Xm)}IA.exports.fromStream=hIt;function hIt(t,e){let r=UV(e);return new Promise((s,a)=>{t.pipe(r),t.on("error",a),r.on("error",a);let n;r.on("integrity",c=>{n=c}),r.on("end",()=>s(n)),r.resume()})}IA.exports.checkData=gIt;function gIt(t,e,r){if(e=nc(e,r),!e||!Object.keys(e).length){if(r?.error)throw Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"});return!1}let s=e.pickAlgorithm(r),a=db.createHash(s).update(t).digest("base64"),n=nc({algorithm:s,digest:a}),c=n.match(e,r);if(r=r||{},c||!r.error)return c;if(typeof r.size=="number"&&t.length!==r.size){let f=new Error(`data size mismatch when checking ${e}. + Wanted: ${r.size} + Found: ${t.length}`);throw f.code="EBADSIZE",f.found=t.length,f.expected=r.size,f.sri=e,f}else{let f=new Error(`Integrity checksum failed when using ${s}: Wanted ${e}, but got ${n}. (${t.length} bytes)`);throw f.code="EINTEGRITY",f.found=n,f.expected=e,f.algorithm=s,f.sri=e,f}}IA.exports.checkStream=dIt;function dIt(t,e,r){if(r=r||Object.create(null),r.integrity=e,e=nc(e,r),!e||!Object.keys(e).length)return Promise.reject(Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"}));let s=UV(r);return new Promise((a,n)=>{t.pipe(s),t.on("error",n),s.on("error",n);let c;s.on("verified",f=>{c=f}),s.on("end",()=>a(c)),s.resume()})}IA.exports.integrityStream=UV;function UV(t=Object.create(null)){return new LV(t)}IA.exports.create=mIt;function mIt(t){let e=t?.algorithms||[...MV],r=mb(t?.options),s=e.map(db.createHash);return{update:function(a,n){return s.forEach(c=>c.update(a,n)),this},digest:function(){return e.reduce((n,c)=>{let f=s.shift().digest("base64"),p=new Ah(`${c}-${f}${r}`,t);if(p.algorithm&&p.digest){let h=p.algorithm;n[h]||(n[h]=[]),n[h].push(p)}return n},new Xm)}}}var yIt=db.getHashes(),_Se=["md5","whirlpool","sha1","sha224","sha256","sha384","sha512","sha3","sha3-256","sha3-384","sha3-512","sha3_256","sha3_384","sha3_512"].filter(t=>yIt.includes(t));function EIt(t,e){return _Se.indexOf(t.toLowerCase())>=_Se.indexOf(e.toLowerCase())?t:e}});var _V=_(mg=>{"use strict";Object.defineProperty(mg,"__esModule",{value:!0});mg.Signature=mg.Envelope=void 0;mg.Envelope={fromJSON(t){return{payload:GN(t.payload)?Buffer.from(jSe(t.payload)):Buffer.alloc(0),payloadType:GN(t.payloadType)?globalThis.String(t.payloadType):"",signatures:globalThis.Array.isArray(t?.signatures)?t.signatures.map(e=>mg.Signature.fromJSON(e)):[]}},toJSON(t){let e={};return t.payload.length!==0&&(e.payload=GSe(t.payload)),t.payloadType!==""&&(e.payloadType=t.payloadType),t.signatures?.length&&(e.signatures=t.signatures.map(r=>mg.Signature.toJSON(r))),e}};mg.Signature={fromJSON(t){return{sig:GN(t.sig)?Buffer.from(jSe(t.sig)):Buffer.alloc(0),keyid:GN(t.keyid)?globalThis.String(t.keyid):""}},toJSON(t){let e={};return t.sig.length!==0&&(e.sig=GSe(t.sig)),t.keyid!==""&&(e.keyid=t.keyid),e}};function jSe(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function GSe(t){return globalThis.Buffer.from(t).toString("base64")}function GN(t){return t!=null}});var WSe=_(qN=>{"use strict";Object.defineProperty(qN,"__esModule",{value:!0});qN.Timestamp=void 0;qN.Timestamp={fromJSON(t){return{seconds:qSe(t.seconds)?globalThis.String(t.seconds):"0",nanos:qSe(t.nanos)?globalThis.Number(t.nanos):0}},toJSON(t){let e={};return t.seconds!=="0"&&(e.seconds=t.seconds),t.nanos!==0&&(e.nanos=Math.round(t.nanos)),e}};function qSe(t){return t!=null}});var Ww=_(Ur=>{"use strict";Object.defineProperty(Ur,"__esModule",{value:!0});Ur.TimeRange=Ur.X509CertificateChain=Ur.SubjectAlternativeName=Ur.X509Certificate=Ur.DistinguishedName=Ur.ObjectIdentifierValuePair=Ur.ObjectIdentifier=Ur.PublicKeyIdentifier=Ur.PublicKey=Ur.RFC3161SignedTimestamp=Ur.LogId=Ur.MessageSignature=Ur.HashOutput=Ur.SubjectAlternativeNameType=Ur.PublicKeyDetails=Ur.HashAlgorithm=void 0;Ur.hashAlgorithmFromJSON=VSe;Ur.hashAlgorithmToJSON=JSe;Ur.publicKeyDetailsFromJSON=KSe;Ur.publicKeyDetailsToJSON=zSe;Ur.subjectAlternativeNameTypeFromJSON=XSe;Ur.subjectAlternativeNameTypeToJSON=ZSe;var IIt=WSe(),yl;(function(t){t[t.HASH_ALGORITHM_UNSPECIFIED=0]="HASH_ALGORITHM_UNSPECIFIED",t[t.SHA2_256=1]="SHA2_256",t[t.SHA2_384=2]="SHA2_384",t[t.SHA2_512=3]="SHA2_512",t[t.SHA3_256=4]="SHA3_256",t[t.SHA3_384=5]="SHA3_384"})(yl||(Ur.HashAlgorithm=yl={}));function VSe(t){switch(t){case 0:case"HASH_ALGORITHM_UNSPECIFIED":return yl.HASH_ALGORITHM_UNSPECIFIED;case 1:case"SHA2_256":return yl.SHA2_256;case 2:case"SHA2_384":return yl.SHA2_384;case 3:case"SHA2_512":return yl.SHA2_512;case 4:case"SHA3_256":return yl.SHA3_256;case 5:case"SHA3_384":return yl.SHA3_384;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}function JSe(t){switch(t){case yl.HASH_ALGORITHM_UNSPECIFIED:return"HASH_ALGORITHM_UNSPECIFIED";case yl.SHA2_256:return"SHA2_256";case yl.SHA2_384:return"SHA2_384";case yl.SHA2_512:return"SHA2_512";case yl.SHA3_256:return"SHA3_256";case yl.SHA3_384:return"SHA3_384";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}var sn;(function(t){t[t.PUBLIC_KEY_DETAILS_UNSPECIFIED=0]="PUBLIC_KEY_DETAILS_UNSPECIFIED",t[t.PKCS1_RSA_PKCS1V5=1]="PKCS1_RSA_PKCS1V5",t[t.PKCS1_RSA_PSS=2]="PKCS1_RSA_PSS",t[t.PKIX_RSA_PKCS1V5=3]="PKIX_RSA_PKCS1V5",t[t.PKIX_RSA_PSS=4]="PKIX_RSA_PSS",t[t.PKIX_RSA_PKCS1V15_2048_SHA256=9]="PKIX_RSA_PKCS1V15_2048_SHA256",t[t.PKIX_RSA_PKCS1V15_3072_SHA256=10]="PKIX_RSA_PKCS1V15_3072_SHA256",t[t.PKIX_RSA_PKCS1V15_4096_SHA256=11]="PKIX_RSA_PKCS1V15_4096_SHA256",t[t.PKIX_RSA_PSS_2048_SHA256=16]="PKIX_RSA_PSS_2048_SHA256",t[t.PKIX_RSA_PSS_3072_SHA256=17]="PKIX_RSA_PSS_3072_SHA256",t[t.PKIX_RSA_PSS_4096_SHA256=18]="PKIX_RSA_PSS_4096_SHA256",t[t.PKIX_ECDSA_P256_HMAC_SHA_256=6]="PKIX_ECDSA_P256_HMAC_SHA_256",t[t.PKIX_ECDSA_P256_SHA_256=5]="PKIX_ECDSA_P256_SHA_256",t[t.PKIX_ECDSA_P384_SHA_384=12]="PKIX_ECDSA_P384_SHA_384",t[t.PKIX_ECDSA_P521_SHA_512=13]="PKIX_ECDSA_P521_SHA_512",t[t.PKIX_ED25519=7]="PKIX_ED25519",t[t.PKIX_ED25519_PH=8]="PKIX_ED25519_PH",t[t.LMS_SHA256=14]="LMS_SHA256",t[t.LMOTS_SHA256=15]="LMOTS_SHA256"})(sn||(Ur.PublicKeyDetails=sn={}));function KSe(t){switch(t){case 0:case"PUBLIC_KEY_DETAILS_UNSPECIFIED":return sn.PUBLIC_KEY_DETAILS_UNSPECIFIED;case 1:case"PKCS1_RSA_PKCS1V5":return sn.PKCS1_RSA_PKCS1V5;case 2:case"PKCS1_RSA_PSS":return sn.PKCS1_RSA_PSS;case 3:case"PKIX_RSA_PKCS1V5":return sn.PKIX_RSA_PKCS1V5;case 4:case"PKIX_RSA_PSS":return sn.PKIX_RSA_PSS;case 9:case"PKIX_RSA_PKCS1V15_2048_SHA256":return sn.PKIX_RSA_PKCS1V15_2048_SHA256;case 10:case"PKIX_RSA_PKCS1V15_3072_SHA256":return sn.PKIX_RSA_PKCS1V15_3072_SHA256;case 11:case"PKIX_RSA_PKCS1V15_4096_SHA256":return sn.PKIX_RSA_PKCS1V15_4096_SHA256;case 16:case"PKIX_RSA_PSS_2048_SHA256":return sn.PKIX_RSA_PSS_2048_SHA256;case 17:case"PKIX_RSA_PSS_3072_SHA256":return sn.PKIX_RSA_PSS_3072_SHA256;case 18:case"PKIX_RSA_PSS_4096_SHA256":return sn.PKIX_RSA_PSS_4096_SHA256;case 6:case"PKIX_ECDSA_P256_HMAC_SHA_256":return sn.PKIX_ECDSA_P256_HMAC_SHA_256;case 5:case"PKIX_ECDSA_P256_SHA_256":return sn.PKIX_ECDSA_P256_SHA_256;case 12:case"PKIX_ECDSA_P384_SHA_384":return sn.PKIX_ECDSA_P384_SHA_384;case 13:case"PKIX_ECDSA_P521_SHA_512":return sn.PKIX_ECDSA_P521_SHA_512;case 7:case"PKIX_ED25519":return sn.PKIX_ED25519;case 8:case"PKIX_ED25519_PH":return sn.PKIX_ED25519_PH;case 14:case"LMS_SHA256":return sn.LMS_SHA256;case 15:case"LMOTS_SHA256":return sn.LMOTS_SHA256;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}function zSe(t){switch(t){case sn.PUBLIC_KEY_DETAILS_UNSPECIFIED:return"PUBLIC_KEY_DETAILS_UNSPECIFIED";case sn.PKCS1_RSA_PKCS1V5:return"PKCS1_RSA_PKCS1V5";case sn.PKCS1_RSA_PSS:return"PKCS1_RSA_PSS";case sn.PKIX_RSA_PKCS1V5:return"PKIX_RSA_PKCS1V5";case sn.PKIX_RSA_PSS:return"PKIX_RSA_PSS";case sn.PKIX_RSA_PKCS1V15_2048_SHA256:return"PKIX_RSA_PKCS1V15_2048_SHA256";case sn.PKIX_RSA_PKCS1V15_3072_SHA256:return"PKIX_RSA_PKCS1V15_3072_SHA256";case sn.PKIX_RSA_PKCS1V15_4096_SHA256:return"PKIX_RSA_PKCS1V15_4096_SHA256";case sn.PKIX_RSA_PSS_2048_SHA256:return"PKIX_RSA_PSS_2048_SHA256";case sn.PKIX_RSA_PSS_3072_SHA256:return"PKIX_RSA_PSS_3072_SHA256";case sn.PKIX_RSA_PSS_4096_SHA256:return"PKIX_RSA_PSS_4096_SHA256";case sn.PKIX_ECDSA_P256_HMAC_SHA_256:return"PKIX_ECDSA_P256_HMAC_SHA_256";case sn.PKIX_ECDSA_P256_SHA_256:return"PKIX_ECDSA_P256_SHA_256";case sn.PKIX_ECDSA_P384_SHA_384:return"PKIX_ECDSA_P384_SHA_384";case sn.PKIX_ECDSA_P521_SHA_512:return"PKIX_ECDSA_P521_SHA_512";case sn.PKIX_ED25519:return"PKIX_ED25519";case sn.PKIX_ED25519_PH:return"PKIX_ED25519_PH";case sn.LMS_SHA256:return"LMS_SHA256";case sn.LMOTS_SHA256:return"LMOTS_SHA256";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}var CA;(function(t){t[t.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED=0]="SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED",t[t.EMAIL=1]="EMAIL",t[t.URI=2]="URI",t[t.OTHER_NAME=3]="OTHER_NAME"})(CA||(Ur.SubjectAlternativeNameType=CA={}));function XSe(t){switch(t){case 0:case"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED":return CA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED;case 1:case"EMAIL":return CA.EMAIL;case 2:case"URI":return CA.URI;case 3:case"OTHER_NAME":return CA.OTHER_NAME;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}function ZSe(t){switch(t){case CA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED:return"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED";case CA.EMAIL:return"EMAIL";case CA.URI:return"URI";case CA.OTHER_NAME:return"OTHER_NAME";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}Ur.HashOutput={fromJSON(t){return{algorithm:ds(t.algorithm)?VSe(t.algorithm):0,digest:ds(t.digest)?Buffer.from(Zm(t.digest)):Buffer.alloc(0)}},toJSON(t){let e={};return t.algorithm!==0&&(e.algorithm=JSe(t.algorithm)),t.digest.length!==0&&(e.digest=$m(t.digest)),e}};Ur.MessageSignature={fromJSON(t){return{messageDigest:ds(t.messageDigest)?Ur.HashOutput.fromJSON(t.messageDigest):void 0,signature:ds(t.signature)?Buffer.from(Zm(t.signature)):Buffer.alloc(0)}},toJSON(t){let e={};return t.messageDigest!==void 0&&(e.messageDigest=Ur.HashOutput.toJSON(t.messageDigest)),t.signature.length!==0&&(e.signature=$m(t.signature)),e}};Ur.LogId={fromJSON(t){return{keyId:ds(t.keyId)?Buffer.from(Zm(t.keyId)):Buffer.alloc(0)}},toJSON(t){let e={};return t.keyId.length!==0&&(e.keyId=$m(t.keyId)),e}};Ur.RFC3161SignedTimestamp={fromJSON(t){return{signedTimestamp:ds(t.signedTimestamp)?Buffer.from(Zm(t.signedTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedTimestamp.length!==0&&(e.signedTimestamp=$m(t.signedTimestamp)),e}};Ur.PublicKey={fromJSON(t){return{rawBytes:ds(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):void 0,keyDetails:ds(t.keyDetails)?KSe(t.keyDetails):0,validFor:ds(t.validFor)?Ur.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.rawBytes!==void 0&&(e.rawBytes=$m(t.rawBytes)),t.keyDetails!==0&&(e.keyDetails=zSe(t.keyDetails)),t.validFor!==void 0&&(e.validFor=Ur.TimeRange.toJSON(t.validFor)),e}};Ur.PublicKeyIdentifier={fromJSON(t){return{hint:ds(t.hint)?globalThis.String(t.hint):""}},toJSON(t){let e={};return t.hint!==""&&(e.hint=t.hint),e}};Ur.ObjectIdentifier={fromJSON(t){return{id:globalThis.Array.isArray(t?.id)?t.id.map(e=>globalThis.Number(e)):[]}},toJSON(t){let e={};return t.id?.length&&(e.id=t.id.map(r=>Math.round(r))),e}};Ur.ObjectIdentifierValuePair={fromJSON(t){return{oid:ds(t.oid)?Ur.ObjectIdentifier.fromJSON(t.oid):void 0,value:ds(t.value)?Buffer.from(Zm(t.value)):Buffer.alloc(0)}},toJSON(t){let e={};return t.oid!==void 0&&(e.oid=Ur.ObjectIdentifier.toJSON(t.oid)),t.value.length!==0&&(e.value=$m(t.value)),e}};Ur.DistinguishedName={fromJSON(t){return{organization:ds(t.organization)?globalThis.String(t.organization):"",commonName:ds(t.commonName)?globalThis.String(t.commonName):""}},toJSON(t){let e={};return t.organization!==""&&(e.organization=t.organization),t.commonName!==""&&(e.commonName=t.commonName),e}};Ur.X509Certificate={fromJSON(t){return{rawBytes:ds(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):Buffer.alloc(0)}},toJSON(t){let e={};return t.rawBytes.length!==0&&(e.rawBytes=$m(t.rawBytes)),e}};Ur.SubjectAlternativeName={fromJSON(t){return{type:ds(t.type)?XSe(t.type):0,identity:ds(t.regexp)?{$case:"regexp",regexp:globalThis.String(t.regexp)}:ds(t.value)?{$case:"value",value:globalThis.String(t.value)}:void 0}},toJSON(t){let e={};return t.type!==0&&(e.type=ZSe(t.type)),t.identity?.$case==="regexp"?e.regexp=t.identity.regexp:t.identity?.$case==="value"&&(e.value=t.identity.value),e}};Ur.X509CertificateChain={fromJSON(t){return{certificates:globalThis.Array.isArray(t?.certificates)?t.certificates.map(e=>Ur.X509Certificate.fromJSON(e)):[]}},toJSON(t){let e={};return t.certificates?.length&&(e.certificates=t.certificates.map(r=>Ur.X509Certificate.toJSON(r))),e}};Ur.TimeRange={fromJSON(t){return{start:ds(t.start)?YSe(t.start):void 0,end:ds(t.end)?YSe(t.end):void 0}},toJSON(t){let e={};return t.start!==void 0&&(e.start=t.start.toISOString()),t.end!==void 0&&(e.end=t.end.toISOString()),e}};function Zm(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function $m(t){return globalThis.Buffer.from(t).toString("base64")}function CIt(t){let e=(globalThis.Number(t.seconds)||0)*1e3;return e+=(t.nanos||0)/1e6,new globalThis.Date(e)}function YSe(t){return t instanceof globalThis.Date?t:typeof t=="string"?new globalThis.Date(t):CIt(IIt.Timestamp.fromJSON(t))}function ds(t){return t!=null}});var HV=_(ms=>{"use strict";Object.defineProperty(ms,"__esModule",{value:!0});ms.TransparencyLogEntry=ms.InclusionPromise=ms.InclusionProof=ms.Checkpoint=ms.KindVersion=void 0;var $Se=Ww();ms.KindVersion={fromJSON(t){return{kind:Ra(t.kind)?globalThis.String(t.kind):"",version:Ra(t.version)?globalThis.String(t.version):""}},toJSON(t){let e={};return t.kind!==""&&(e.kind=t.kind),t.version!==""&&(e.version=t.version),e}};ms.Checkpoint={fromJSON(t){return{envelope:Ra(t.envelope)?globalThis.String(t.envelope):""}},toJSON(t){let e={};return t.envelope!==""&&(e.envelope=t.envelope),e}};ms.InclusionProof={fromJSON(t){return{logIndex:Ra(t.logIndex)?globalThis.String(t.logIndex):"0",rootHash:Ra(t.rootHash)?Buffer.from(WN(t.rootHash)):Buffer.alloc(0),treeSize:Ra(t.treeSize)?globalThis.String(t.treeSize):"0",hashes:globalThis.Array.isArray(t?.hashes)?t.hashes.map(e=>Buffer.from(WN(e))):[],checkpoint:Ra(t.checkpoint)?ms.Checkpoint.fromJSON(t.checkpoint):void 0}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.rootHash.length!==0&&(e.rootHash=YN(t.rootHash)),t.treeSize!=="0"&&(e.treeSize=t.treeSize),t.hashes?.length&&(e.hashes=t.hashes.map(r=>YN(r))),t.checkpoint!==void 0&&(e.checkpoint=ms.Checkpoint.toJSON(t.checkpoint)),e}};ms.InclusionPromise={fromJSON(t){return{signedEntryTimestamp:Ra(t.signedEntryTimestamp)?Buffer.from(WN(t.signedEntryTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedEntryTimestamp.length!==0&&(e.signedEntryTimestamp=YN(t.signedEntryTimestamp)),e}};ms.TransparencyLogEntry={fromJSON(t){return{logIndex:Ra(t.logIndex)?globalThis.String(t.logIndex):"0",logId:Ra(t.logId)?$Se.LogId.fromJSON(t.logId):void 0,kindVersion:Ra(t.kindVersion)?ms.KindVersion.fromJSON(t.kindVersion):void 0,integratedTime:Ra(t.integratedTime)?globalThis.String(t.integratedTime):"0",inclusionPromise:Ra(t.inclusionPromise)?ms.InclusionPromise.fromJSON(t.inclusionPromise):void 0,inclusionProof:Ra(t.inclusionProof)?ms.InclusionProof.fromJSON(t.inclusionProof):void 0,canonicalizedBody:Ra(t.canonicalizedBody)?Buffer.from(WN(t.canonicalizedBody)):Buffer.alloc(0)}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.logId!==void 0&&(e.logId=$Se.LogId.toJSON(t.logId)),t.kindVersion!==void 0&&(e.kindVersion=ms.KindVersion.toJSON(t.kindVersion)),t.integratedTime!=="0"&&(e.integratedTime=t.integratedTime),t.inclusionPromise!==void 0&&(e.inclusionPromise=ms.InclusionPromise.toJSON(t.inclusionPromise)),t.inclusionProof!==void 0&&(e.inclusionProof=ms.InclusionProof.toJSON(t.inclusionProof)),t.canonicalizedBody.length!==0&&(e.canonicalizedBody=YN(t.canonicalizedBody)),e}};function WN(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function YN(t){return globalThis.Buffer.from(t).toString("base64")}function Ra(t){return t!=null}});var jV=_(Xc=>{"use strict";Object.defineProperty(Xc,"__esModule",{value:!0});Xc.Bundle=Xc.VerificationMaterial=Xc.TimestampVerificationData=void 0;var eDe=_V(),wA=Ww(),tDe=HV();Xc.TimestampVerificationData={fromJSON(t){return{rfc3161Timestamps:globalThis.Array.isArray(t?.rfc3161Timestamps)?t.rfc3161Timestamps.map(e=>wA.RFC3161SignedTimestamp.fromJSON(e)):[]}},toJSON(t){let e={};return t.rfc3161Timestamps?.length&&(e.rfc3161Timestamps=t.rfc3161Timestamps.map(r=>wA.RFC3161SignedTimestamp.toJSON(r))),e}};Xc.VerificationMaterial={fromJSON(t){return{content:yg(t.publicKey)?{$case:"publicKey",publicKey:wA.PublicKeyIdentifier.fromJSON(t.publicKey)}:yg(t.x509CertificateChain)?{$case:"x509CertificateChain",x509CertificateChain:wA.X509CertificateChain.fromJSON(t.x509CertificateChain)}:yg(t.certificate)?{$case:"certificate",certificate:wA.X509Certificate.fromJSON(t.certificate)}:void 0,tlogEntries:globalThis.Array.isArray(t?.tlogEntries)?t.tlogEntries.map(e=>tDe.TransparencyLogEntry.fromJSON(e)):[],timestampVerificationData:yg(t.timestampVerificationData)?Xc.TimestampVerificationData.fromJSON(t.timestampVerificationData):void 0}},toJSON(t){let e={};return t.content?.$case==="publicKey"?e.publicKey=wA.PublicKeyIdentifier.toJSON(t.content.publicKey):t.content?.$case==="x509CertificateChain"?e.x509CertificateChain=wA.X509CertificateChain.toJSON(t.content.x509CertificateChain):t.content?.$case==="certificate"&&(e.certificate=wA.X509Certificate.toJSON(t.content.certificate)),t.tlogEntries?.length&&(e.tlogEntries=t.tlogEntries.map(r=>tDe.TransparencyLogEntry.toJSON(r))),t.timestampVerificationData!==void 0&&(e.timestampVerificationData=Xc.TimestampVerificationData.toJSON(t.timestampVerificationData)),e}};Xc.Bundle={fromJSON(t){return{mediaType:yg(t.mediaType)?globalThis.String(t.mediaType):"",verificationMaterial:yg(t.verificationMaterial)?Xc.VerificationMaterial.fromJSON(t.verificationMaterial):void 0,content:yg(t.messageSignature)?{$case:"messageSignature",messageSignature:wA.MessageSignature.fromJSON(t.messageSignature)}:yg(t.dsseEnvelope)?{$case:"dsseEnvelope",dsseEnvelope:eDe.Envelope.fromJSON(t.dsseEnvelope)}:void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.verificationMaterial!==void 0&&(e.verificationMaterial=Xc.VerificationMaterial.toJSON(t.verificationMaterial)),t.content?.$case==="messageSignature"?e.messageSignature=wA.MessageSignature.toJSON(t.content.messageSignature):t.content?.$case==="dsseEnvelope"&&(e.dsseEnvelope=eDe.Envelope.toJSON(t.content.dsseEnvelope)),e}};function yg(t){return t!=null}});var GV=_(Ri=>{"use strict";Object.defineProperty(Ri,"__esModule",{value:!0});Ri.ClientTrustConfig=Ri.SigningConfig=Ri.TrustedRoot=Ri.CertificateAuthority=Ri.TransparencyLogInstance=void 0;var El=Ww();Ri.TransparencyLogInstance={fromJSON(t){return{baseUrl:ia(t.baseUrl)?globalThis.String(t.baseUrl):"",hashAlgorithm:ia(t.hashAlgorithm)?(0,El.hashAlgorithmFromJSON)(t.hashAlgorithm):0,publicKey:ia(t.publicKey)?El.PublicKey.fromJSON(t.publicKey):void 0,logId:ia(t.logId)?El.LogId.fromJSON(t.logId):void 0,checkpointKeyId:ia(t.checkpointKeyId)?El.LogId.fromJSON(t.checkpointKeyId):void 0}},toJSON(t){let e={};return t.baseUrl!==""&&(e.baseUrl=t.baseUrl),t.hashAlgorithm!==0&&(e.hashAlgorithm=(0,El.hashAlgorithmToJSON)(t.hashAlgorithm)),t.publicKey!==void 0&&(e.publicKey=El.PublicKey.toJSON(t.publicKey)),t.logId!==void 0&&(e.logId=El.LogId.toJSON(t.logId)),t.checkpointKeyId!==void 0&&(e.checkpointKeyId=El.LogId.toJSON(t.checkpointKeyId)),e}};Ri.CertificateAuthority={fromJSON(t){return{subject:ia(t.subject)?El.DistinguishedName.fromJSON(t.subject):void 0,uri:ia(t.uri)?globalThis.String(t.uri):"",certChain:ia(t.certChain)?El.X509CertificateChain.fromJSON(t.certChain):void 0,validFor:ia(t.validFor)?El.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.subject!==void 0&&(e.subject=El.DistinguishedName.toJSON(t.subject)),t.uri!==""&&(e.uri=t.uri),t.certChain!==void 0&&(e.certChain=El.X509CertificateChain.toJSON(t.certChain)),t.validFor!==void 0&&(e.validFor=El.TimeRange.toJSON(t.validFor)),e}};Ri.TrustedRoot={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",tlogs:globalThis.Array.isArray(t?.tlogs)?t.tlogs.map(e=>Ri.TransparencyLogInstance.fromJSON(e)):[],certificateAuthorities:globalThis.Array.isArray(t?.certificateAuthorities)?t.certificateAuthorities.map(e=>Ri.CertificateAuthority.fromJSON(e)):[],ctlogs:globalThis.Array.isArray(t?.ctlogs)?t.ctlogs.map(e=>Ri.TransparencyLogInstance.fromJSON(e)):[],timestampAuthorities:globalThis.Array.isArray(t?.timestampAuthorities)?t.timestampAuthorities.map(e=>Ri.CertificateAuthority.fromJSON(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.tlogs?.length&&(e.tlogs=t.tlogs.map(r=>Ri.TransparencyLogInstance.toJSON(r))),t.certificateAuthorities?.length&&(e.certificateAuthorities=t.certificateAuthorities.map(r=>Ri.CertificateAuthority.toJSON(r))),t.ctlogs?.length&&(e.ctlogs=t.ctlogs.map(r=>Ri.TransparencyLogInstance.toJSON(r))),t.timestampAuthorities?.length&&(e.timestampAuthorities=t.timestampAuthorities.map(r=>Ri.CertificateAuthority.toJSON(r))),e}};Ri.SigningConfig={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",caUrl:ia(t.caUrl)?globalThis.String(t.caUrl):"",oidcUrl:ia(t.oidcUrl)?globalThis.String(t.oidcUrl):"",tlogUrls:globalThis.Array.isArray(t?.tlogUrls)?t.tlogUrls.map(e=>globalThis.String(e)):[],tsaUrls:globalThis.Array.isArray(t?.tsaUrls)?t.tsaUrls.map(e=>globalThis.String(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.caUrl!==""&&(e.caUrl=t.caUrl),t.oidcUrl!==""&&(e.oidcUrl=t.oidcUrl),t.tlogUrls?.length&&(e.tlogUrls=t.tlogUrls),t.tsaUrls?.length&&(e.tsaUrls=t.tsaUrls),e}};Ri.ClientTrustConfig={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",trustedRoot:ia(t.trustedRoot)?Ri.TrustedRoot.fromJSON(t.trustedRoot):void 0,signingConfig:ia(t.signingConfig)?Ri.SigningConfig.fromJSON(t.signingConfig):void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.trustedRoot!==void 0&&(e.trustedRoot=Ri.TrustedRoot.toJSON(t.trustedRoot)),t.signingConfig!==void 0&&(e.signingConfig=Ri.SigningConfig.toJSON(t.signingConfig)),e}};function ia(t){return t!=null}});var iDe=_(Vr=>{"use strict";Object.defineProperty(Vr,"__esModule",{value:!0});Vr.Input=Vr.Artifact=Vr.ArtifactVerificationOptions_ObserverTimestampOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions=Vr.ArtifactVerificationOptions_CtlogOptions=Vr.ArtifactVerificationOptions_TlogOptions=Vr.ArtifactVerificationOptions=Vr.PublicKeyIdentities=Vr.CertificateIdentities=Vr.CertificateIdentity=void 0;var rDe=jV(),Eg=Ww(),nDe=GV();Vr.CertificateIdentity={fromJSON(t){return{issuer:gi(t.issuer)?globalThis.String(t.issuer):"",san:gi(t.san)?Eg.SubjectAlternativeName.fromJSON(t.san):void 0,oids:globalThis.Array.isArray(t?.oids)?t.oids.map(e=>Eg.ObjectIdentifierValuePair.fromJSON(e)):[]}},toJSON(t){let e={};return t.issuer!==""&&(e.issuer=t.issuer),t.san!==void 0&&(e.san=Eg.SubjectAlternativeName.toJSON(t.san)),t.oids?.length&&(e.oids=t.oids.map(r=>Eg.ObjectIdentifierValuePair.toJSON(r))),e}};Vr.CertificateIdentities={fromJSON(t){return{identities:globalThis.Array.isArray(t?.identities)?t.identities.map(e=>Vr.CertificateIdentity.fromJSON(e)):[]}},toJSON(t){let e={};return t.identities?.length&&(e.identities=t.identities.map(r=>Vr.CertificateIdentity.toJSON(r))),e}};Vr.PublicKeyIdentities={fromJSON(t){return{publicKeys:globalThis.Array.isArray(t?.publicKeys)?t.publicKeys.map(e=>Eg.PublicKey.fromJSON(e)):[]}},toJSON(t){let e={};return t.publicKeys?.length&&(e.publicKeys=t.publicKeys.map(r=>Eg.PublicKey.toJSON(r))),e}};Vr.ArtifactVerificationOptions={fromJSON(t){return{signers:gi(t.certificateIdentities)?{$case:"certificateIdentities",certificateIdentities:Vr.CertificateIdentities.fromJSON(t.certificateIdentities)}:gi(t.publicKeys)?{$case:"publicKeys",publicKeys:Vr.PublicKeyIdentities.fromJSON(t.publicKeys)}:void 0,tlogOptions:gi(t.tlogOptions)?Vr.ArtifactVerificationOptions_TlogOptions.fromJSON(t.tlogOptions):void 0,ctlogOptions:gi(t.ctlogOptions)?Vr.ArtifactVerificationOptions_CtlogOptions.fromJSON(t.ctlogOptions):void 0,tsaOptions:gi(t.tsaOptions)?Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.fromJSON(t.tsaOptions):void 0,integratedTsOptions:gi(t.integratedTsOptions)?Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.fromJSON(t.integratedTsOptions):void 0,observerOptions:gi(t.observerOptions)?Vr.ArtifactVerificationOptions_ObserverTimestampOptions.fromJSON(t.observerOptions):void 0}},toJSON(t){let e={};return t.signers?.$case==="certificateIdentities"?e.certificateIdentities=Vr.CertificateIdentities.toJSON(t.signers.certificateIdentities):t.signers?.$case==="publicKeys"&&(e.publicKeys=Vr.PublicKeyIdentities.toJSON(t.signers.publicKeys)),t.tlogOptions!==void 0&&(e.tlogOptions=Vr.ArtifactVerificationOptions_TlogOptions.toJSON(t.tlogOptions)),t.ctlogOptions!==void 0&&(e.ctlogOptions=Vr.ArtifactVerificationOptions_CtlogOptions.toJSON(t.ctlogOptions)),t.tsaOptions!==void 0&&(e.tsaOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.toJSON(t.tsaOptions)),t.integratedTsOptions!==void 0&&(e.integratedTsOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.toJSON(t.integratedTsOptions)),t.observerOptions!==void 0&&(e.observerOptions=Vr.ArtifactVerificationOptions_ObserverTimestampOptions.toJSON(t.observerOptions)),e}};Vr.ArtifactVerificationOptions_TlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,performOnlineVerification:gi(t.performOnlineVerification)?globalThis.Boolean(t.performOnlineVerification):!1,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.performOnlineVerification!==!1&&(e.performOnlineVerification=t.performOnlineVerification),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_CtlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TimestampAuthorityOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_ObserverTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.Artifact={fromJSON(t){return{data:gi(t.artifactUri)?{$case:"artifactUri",artifactUri:globalThis.String(t.artifactUri)}:gi(t.artifact)?{$case:"artifact",artifact:Buffer.from(wIt(t.artifact))}:gi(t.artifactDigest)?{$case:"artifactDigest",artifactDigest:Eg.HashOutput.fromJSON(t.artifactDigest)}:void 0}},toJSON(t){let e={};return t.data?.$case==="artifactUri"?e.artifactUri=t.data.artifactUri:t.data?.$case==="artifact"?e.artifact=BIt(t.data.artifact):t.data?.$case==="artifactDigest"&&(e.artifactDigest=Eg.HashOutput.toJSON(t.data.artifactDigest)),e}};Vr.Input={fromJSON(t){return{artifactTrustRoot:gi(t.artifactTrustRoot)?nDe.TrustedRoot.fromJSON(t.artifactTrustRoot):void 0,artifactVerificationOptions:gi(t.artifactVerificationOptions)?Vr.ArtifactVerificationOptions.fromJSON(t.artifactVerificationOptions):void 0,bundle:gi(t.bundle)?rDe.Bundle.fromJSON(t.bundle):void 0,artifact:gi(t.artifact)?Vr.Artifact.fromJSON(t.artifact):void 0}},toJSON(t){let e={};return t.artifactTrustRoot!==void 0&&(e.artifactTrustRoot=nDe.TrustedRoot.toJSON(t.artifactTrustRoot)),t.artifactVerificationOptions!==void 0&&(e.artifactVerificationOptions=Vr.ArtifactVerificationOptions.toJSON(t.artifactVerificationOptions)),t.bundle!==void 0&&(e.bundle=rDe.Bundle.toJSON(t.bundle)),t.artifact!==void 0&&(e.artifact=Vr.Artifact.toJSON(t.artifact)),e}};function wIt(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function BIt(t){return globalThis.Buffer.from(t).toString("base64")}function gi(t){return t!=null}});var yb=_(Zc=>{"use strict";var vIt=Zc&&Zc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Yw=Zc&&Zc.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&vIt(e,t,r)};Object.defineProperty(Zc,"__esModule",{value:!0});Yw(_V(),Zc);Yw(jV(),Zc);Yw(Ww(),Zc);Yw(HV(),Zc);Yw(GV(),Zc);Yw(iDe(),Zc)});var VN=_(Il=>{"use strict";Object.defineProperty(Il,"__esModule",{value:!0});Il.BUNDLE_V03_MEDIA_TYPE=Il.BUNDLE_V03_LEGACY_MEDIA_TYPE=Il.BUNDLE_V02_MEDIA_TYPE=Il.BUNDLE_V01_MEDIA_TYPE=void 0;Il.isBundleWithCertificateChain=SIt;Il.isBundleWithPublicKey=DIt;Il.isBundleWithMessageSignature=bIt;Il.isBundleWithDsseEnvelope=PIt;Il.BUNDLE_V01_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.1";Il.BUNDLE_V02_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.2";Il.BUNDLE_V03_LEGACY_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.3";Il.BUNDLE_V03_MEDIA_TYPE="application/vnd.dev.sigstore.bundle.v0.3+json";function SIt(t){return t.verificationMaterial.content.$case==="x509CertificateChain"}function DIt(t){return t.verificationMaterial.content.$case==="publicKey"}function bIt(t){return t.content.$case==="messageSignature"}function PIt(t){return t.content.$case==="dsseEnvelope"}});var oDe=_(KN=>{"use strict";Object.defineProperty(KN,"__esModule",{value:!0});KN.toMessageSignatureBundle=kIt;KN.toDSSEBundle=QIt;var xIt=yb(),JN=VN();function kIt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:"messageSignature",messageSignature:{messageDigest:{algorithm:xIt.HashAlgorithm.SHA2_256,digest:t.digest},signature:t.signature}},verificationMaterial:sDe(t)}}function QIt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:"dsseEnvelope",dsseEnvelope:TIt(t)},verificationMaterial:sDe(t)}}function TIt(t){return{payloadType:t.artifactType,payload:t.artifact,signatures:[RIt(t)]}}function RIt(t){return{keyid:t.keyHint||"",sig:t.signature}}function sDe(t){return{content:FIt(t),tlogEntries:[],timestampVerificationData:{rfc3161Timestamps:[]}}}function FIt(t){return t.certificate?t.certificateChain?{$case:"x509CertificateChain",x509CertificateChain:{certificates:[{rawBytes:t.certificate}]}}:{$case:"certificate",certificate:{rawBytes:t.certificate}}:{$case:"publicKey",publicKey:{hint:t.keyHint||""}}}});var WV=_(zN=>{"use strict";Object.defineProperty(zN,"__esModule",{value:!0});zN.ValidationError=void 0;var qV=class extends Error{constructor(e,r){super(e),this.fields=r}};zN.ValidationError=qV});var YV=_(ey=>{"use strict";Object.defineProperty(ey,"__esModule",{value:!0});ey.assertBundle=NIt;ey.assertBundleV01=aDe;ey.isBundleV01=OIt;ey.assertBundleV02=LIt;ey.assertBundleLatest=MIt;var XN=WV();function NIt(t){let e=ZN(t);if(e.length>0)throw new XN.ValidationError("invalid bundle",e)}function aDe(t){let e=[];if(e.push(...ZN(t)),e.push(...UIt(t)),e.length>0)throw new XN.ValidationError("invalid v0.1 bundle",e)}function OIt(t){try{return aDe(t),!0}catch{return!1}}function LIt(t){let e=[];if(e.push(...ZN(t)),e.push(...lDe(t)),e.length>0)throw new XN.ValidationError("invalid v0.2 bundle",e)}function MIt(t){let e=[];if(e.push(...ZN(t)),e.push(...lDe(t)),e.push(..._It(t)),e.length>0)throw new XN.ValidationError("invalid bundle",e)}function ZN(t){let e=[];if((t.mediaType===void 0||!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\+json;version=\d\.\d/)&&!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\.v\d\.\d\+json/))&&e.push("mediaType"),t.content===void 0)e.push("content");else switch(t.content.$case){case"messageSignature":t.content.messageSignature.messageDigest===void 0?e.push("content.messageSignature.messageDigest"):t.content.messageSignature.messageDigest.digest.length===0&&e.push("content.messageSignature.messageDigest.digest"),t.content.messageSignature.signature.length===0&&e.push("content.messageSignature.signature");break;case"dsseEnvelope":t.content.dsseEnvelope.payload.length===0&&e.push("content.dsseEnvelope.payload"),t.content.dsseEnvelope.signatures.length!==1?e.push("content.dsseEnvelope.signatures"):t.content.dsseEnvelope.signatures[0].sig.length===0&&e.push("content.dsseEnvelope.signatures[0].sig");break}if(t.verificationMaterial===void 0)e.push("verificationMaterial");else{if(t.verificationMaterial.content===void 0)e.push("verificationMaterial.content");else switch(t.verificationMaterial.content.$case){case"x509CertificateChain":t.verificationMaterial.content.x509CertificateChain.certificates.length===0&&e.push("verificationMaterial.content.x509CertificateChain.certificates"),t.verificationMaterial.content.x509CertificateChain.certificates.forEach((r,s)=>{r.rawBytes.length===0&&e.push(`verificationMaterial.content.x509CertificateChain.certificates[${s}].rawBytes`)});break;case"certificate":t.verificationMaterial.content.certificate.rawBytes.length===0&&e.push("verificationMaterial.content.certificate.rawBytes");break}t.verificationMaterial.tlogEntries===void 0?e.push("verificationMaterial.tlogEntries"):t.verificationMaterial.tlogEntries.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.logId===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].logId`),r.kindVersion===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].kindVersion`)})}return e}function UIt(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionPromise===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionPromise`)}),e}function lDe(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionProof===void 0?e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof`):r.inclusionProof.checkpoint===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof.checkpoint`)}),e}function _It(t){let e=[];return t.verificationMaterial?.content?.$case==="x509CertificateChain"&&e.push("verificationMaterial.content.$case"),e}});var uDe=_(BA=>{"use strict";Object.defineProperty(BA,"__esModule",{value:!0});BA.envelopeToJSON=BA.envelopeFromJSON=BA.bundleToJSON=BA.bundleFromJSON=void 0;var $N=yb(),cDe=VN(),VV=YV(),HIt=t=>{let e=$N.Bundle.fromJSON(t);switch(e.mediaType){case cDe.BUNDLE_V01_MEDIA_TYPE:(0,VV.assertBundleV01)(e);break;case cDe.BUNDLE_V02_MEDIA_TYPE:(0,VV.assertBundleV02)(e);break;default:(0,VV.assertBundleLatest)(e);break}return e};BA.bundleFromJSON=HIt;var jIt=t=>$N.Bundle.toJSON(t);BA.bundleToJSON=jIt;var GIt=t=>$N.Envelope.fromJSON(t);BA.envelopeFromJSON=GIt;var qIt=t=>$N.Envelope.toJSON(t);BA.envelopeToJSON=qIt});var Ib=_(Xr=>{"use strict";Object.defineProperty(Xr,"__esModule",{value:!0});Xr.isBundleV01=Xr.assertBundleV02=Xr.assertBundleV01=Xr.assertBundleLatest=Xr.assertBundle=Xr.envelopeToJSON=Xr.envelopeFromJSON=Xr.bundleToJSON=Xr.bundleFromJSON=Xr.ValidationError=Xr.isBundleWithPublicKey=Xr.isBundleWithMessageSignature=Xr.isBundleWithDsseEnvelope=Xr.isBundleWithCertificateChain=Xr.BUNDLE_V03_MEDIA_TYPE=Xr.BUNDLE_V03_LEGACY_MEDIA_TYPE=Xr.BUNDLE_V02_MEDIA_TYPE=Xr.BUNDLE_V01_MEDIA_TYPE=Xr.toMessageSignatureBundle=Xr.toDSSEBundle=void 0;var fDe=oDe();Object.defineProperty(Xr,"toDSSEBundle",{enumerable:!0,get:function(){return fDe.toDSSEBundle}});Object.defineProperty(Xr,"toMessageSignatureBundle",{enumerable:!0,get:function(){return fDe.toMessageSignatureBundle}});var Ig=VN();Object.defineProperty(Xr,"BUNDLE_V01_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V01_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V02_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V02_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V03_LEGACY_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V03_LEGACY_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V03_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V03_MEDIA_TYPE}});Object.defineProperty(Xr,"isBundleWithCertificateChain",{enumerable:!0,get:function(){return Ig.isBundleWithCertificateChain}});Object.defineProperty(Xr,"isBundleWithDsseEnvelope",{enumerable:!0,get:function(){return Ig.isBundleWithDsseEnvelope}});Object.defineProperty(Xr,"isBundleWithMessageSignature",{enumerable:!0,get:function(){return Ig.isBundleWithMessageSignature}});Object.defineProperty(Xr,"isBundleWithPublicKey",{enumerable:!0,get:function(){return Ig.isBundleWithPublicKey}});var WIt=WV();Object.defineProperty(Xr,"ValidationError",{enumerable:!0,get:function(){return WIt.ValidationError}});var eO=uDe();Object.defineProperty(Xr,"bundleFromJSON",{enumerable:!0,get:function(){return eO.bundleFromJSON}});Object.defineProperty(Xr,"bundleToJSON",{enumerable:!0,get:function(){return eO.bundleToJSON}});Object.defineProperty(Xr,"envelopeFromJSON",{enumerable:!0,get:function(){return eO.envelopeFromJSON}});Object.defineProperty(Xr,"envelopeToJSON",{enumerable:!0,get:function(){return eO.envelopeToJSON}});var Eb=YV();Object.defineProperty(Xr,"assertBundle",{enumerable:!0,get:function(){return Eb.assertBundle}});Object.defineProperty(Xr,"assertBundleLatest",{enumerable:!0,get:function(){return Eb.assertBundleLatest}});Object.defineProperty(Xr,"assertBundleV01",{enumerable:!0,get:function(){return Eb.assertBundleV01}});Object.defineProperty(Xr,"assertBundleV02",{enumerable:!0,get:function(){return Eb.assertBundleV02}});Object.defineProperty(Xr,"isBundleV01",{enumerable:!0,get:function(){return Eb.isBundleV01}})});var Cb=_(rO=>{"use strict";Object.defineProperty(rO,"__esModule",{value:!0});rO.ByteStream=void 0;var JV=class extends Error{},tO=class t{constructor(e){this.start=0,e?(this.buf=e,this.view=Buffer.from(e)):(this.buf=new ArrayBuffer(0),this.view=Buffer.from(this.buf))}get buffer(){return this.view.subarray(0,this.start)}get length(){return this.view.byteLength}get position(){return this.start}seek(e){this.start=e}slice(e,r){let s=e+r;if(s>this.length)throw new JV("request past end of buffer");return this.view.subarray(e,s)}appendChar(e){this.ensureCapacity(1),this.view[this.start]=e,this.start+=1}appendUint16(e){this.ensureCapacity(2);let r=new Uint16Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[1],this.view[this.start+1]=s[0],this.start+=2}appendUint24(e){this.ensureCapacity(3);let r=new Uint32Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[2],this.view[this.start+1]=s[1],this.view[this.start+2]=s[0],this.start+=3}appendView(e){this.ensureCapacity(e.length),this.view.set(e,this.start),this.start+=e.length}getBlock(e){if(e<=0)return Buffer.alloc(0);if(this.start+e>this.view.length)throw new Error("request past end of buffer");let r=this.view.subarray(this.start,this.start+e);return this.start+=e,r}getUint8(){return this.getBlock(1)[0]}getUint16(){let e=this.getBlock(2);return e[0]<<8|e[1]}ensureCapacity(e){if(this.start+e>this.view.byteLength){let r=t.BLOCK_SIZE+(e>t.BLOCK_SIZE?e:0);this.realloc(this.view.byteLength+r)}}realloc(e){let r=new ArrayBuffer(e),s=Buffer.from(r);s.set(this.view),this.buf=r,this.view=s}};rO.ByteStream=tO;tO.BLOCK_SIZE=1024});var nO=_(Vw=>{"use strict";Object.defineProperty(Vw,"__esModule",{value:!0});Vw.ASN1TypeError=Vw.ASN1ParseError=void 0;var KV=class extends Error{};Vw.ASN1ParseError=KV;var zV=class extends Error{};Vw.ASN1TypeError=zV});var pDe=_(iO=>{"use strict";Object.defineProperty(iO,"__esModule",{value:!0});iO.decodeLength=YIt;iO.encodeLength=VIt;var ADe=nO();function YIt(t){let e=t.getUint8();if(!(e&128))return e;let r=e&127;if(r>6)throw new ADe.ASN1ParseError("length exceeds 6 byte limit");let s=0;for(let a=0;a0n;)r.unshift(Number(e&255n)),e=e>>8n;return Buffer.from([128|r.length,...r])}});var gDe=_(Cg=>{"use strict";Object.defineProperty(Cg,"__esModule",{value:!0});Cg.parseInteger=zIt;Cg.parseStringASCII=hDe;Cg.parseTime=XIt;Cg.parseOID=ZIt;Cg.parseBoolean=$It;Cg.parseBitString=eCt;var JIt=/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/,KIt=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/;function zIt(t){let e=0,r=t.length,s=t[e],a=s>127,n=a?255:0;for(;s==n&&++e=50?1900:2e3,s[1]=a.toString()}return new Date(`${s[1]}-${s[2]}-${s[3]}T${s[4]}:${s[5]}:${s[6]}Z`)}function ZIt(t){let e=0,r=t.length,s=t[e++],a=Math.floor(s/40),n=s%40,c=`${a}.${n}`,f=0;for(;e=f;--p)a.push(c>>p&1)}return a}});var mDe=_(sO=>{"use strict";Object.defineProperty(sO,"__esModule",{value:!0});sO.ASN1Tag=void 0;var dDe=nO(),ty={BOOLEAN:1,INTEGER:2,BIT_STRING:3,OCTET_STRING:4,OBJECT_IDENTIFIER:6,SEQUENCE:16,SET:17,PRINTABLE_STRING:19,UTC_TIME:23,GENERALIZED_TIME:24},XV={UNIVERSAL:0,APPLICATION:1,CONTEXT_SPECIFIC:2,PRIVATE:3},ZV=class{constructor(e){if(this.number=e&31,this.constructed=(e&32)===32,this.class=e>>6,this.number===31)throw new dDe.ASN1ParseError("long form tags not supported");if(this.class===XV.UNIVERSAL&&this.number===0)throw new dDe.ASN1ParseError("unsupported tag 0x00")}isUniversal(){return this.class===XV.UNIVERSAL}isContextSpecific(e){let r=this.class===XV.CONTEXT_SPECIFIC;return e!==void 0?r&&this.number===e:r}isBoolean(){return this.isUniversal()&&this.number===ty.BOOLEAN}isInteger(){return this.isUniversal()&&this.number===ty.INTEGER}isBitString(){return this.isUniversal()&&this.number===ty.BIT_STRING}isOctetString(){return this.isUniversal()&&this.number===ty.OCTET_STRING}isOID(){return this.isUniversal()&&this.number===ty.OBJECT_IDENTIFIER}isUTCTime(){return this.isUniversal()&&this.number===ty.UTC_TIME}isGeneralizedTime(){return this.isUniversal()&&this.number===ty.GENERALIZED_TIME}toDER(){return this.number|(this.constructed?32:0)|this.class<<6}};sO.ASN1Tag=ZV});var CDe=_(aO=>{"use strict";Object.defineProperty(aO,"__esModule",{value:!0});aO.ASN1Obj=void 0;var $V=Cb(),ry=nO(),EDe=pDe(),Jw=gDe(),tCt=mDe(),oO=class{constructor(e,r,s){this.tag=e,this.value=r,this.subs=s}static parseBuffer(e){return IDe(new $V.ByteStream(e))}toDER(){let e=new $V.ByteStream;if(this.subs.length>0)for(let a of this.subs)e.appendView(a.toDER());else e.appendView(this.value);let r=e.buffer,s=new $V.ByteStream;return s.appendChar(this.tag.toDER()),s.appendView((0,EDe.encodeLength)(r.length)),s.appendView(r),s.buffer}toBoolean(){if(!this.tag.isBoolean())throw new ry.ASN1TypeError("not a boolean");return(0,Jw.parseBoolean)(this.value)}toInteger(){if(!this.tag.isInteger())throw new ry.ASN1TypeError("not an integer");return(0,Jw.parseInteger)(this.value)}toOID(){if(!this.tag.isOID())throw new ry.ASN1TypeError("not an OID");return(0,Jw.parseOID)(this.value)}toDate(){switch(!0){case this.tag.isUTCTime():return(0,Jw.parseTime)(this.value,!0);case this.tag.isGeneralizedTime():return(0,Jw.parseTime)(this.value,!1);default:throw new ry.ASN1TypeError("not a date")}}toBitString(){if(!this.tag.isBitString())throw new ry.ASN1TypeError("not a bit string");return(0,Jw.parseBitString)(this.value)}};aO.ASN1Obj=oO;function IDe(t){let e=new tCt.ASN1Tag(t.getUint8()),r=(0,EDe.decodeLength)(t),s=t.slice(t.position,r),a=t.position,n=[];if(e.constructed)n=yDe(t,r);else if(e.isOctetString())try{n=yDe(t,r)}catch{}return n.length===0&&t.seek(a+r),new oO(e,s,n)}function yDe(t,e){let r=t.position+e;if(r>t.length)throw new ry.ASN1ParseError("invalid length");let s=[];for(;t.position{"use strict";Object.defineProperty(lO,"__esModule",{value:!0});lO.ASN1Obj=void 0;var rCt=CDe();Object.defineProperty(lO,"ASN1Obj",{enumerable:!0,get:function(){return rCt.ASN1Obj}})});var Kw=_(wg=>{"use strict";var nCt=wg&&wg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(wg,"__esModule",{value:!0});wg.createPublicKey=iCt;wg.digest=sCt;wg.verify=oCt;wg.bufferEqual=aCt;var wb=nCt(Ie("crypto"));function iCt(t,e="spki"){return typeof t=="string"?wb.default.createPublicKey(t):wb.default.createPublicKey({key:t,format:"der",type:e})}function sCt(t,...e){let r=wb.default.createHash(t);for(let s of e)r.update(s);return r.digest()}function oCt(t,e,r,s){try{return wb.default.verify(s,t,e,r)}catch{return!1}}function aCt(t,e){try{return wb.default.timingSafeEqual(t,e)}catch{return!1}}});var wDe=_(e7=>{"use strict";Object.defineProperty(e7,"__esModule",{value:!0});e7.preAuthEncoding=cCt;var lCt="DSSEv1";function cCt(t,e){let r=[lCt,t.length,t,e.length,""].join(" ");return Buffer.concat([Buffer.from(r,"ascii"),e])}});var SDe=_(uO=>{"use strict";Object.defineProperty(uO,"__esModule",{value:!0});uO.base64Encode=uCt;uO.base64Decode=fCt;var BDe="base64",vDe="utf-8";function uCt(t){return Buffer.from(t,vDe).toString(BDe)}function fCt(t){return Buffer.from(t,BDe).toString(vDe)}});var DDe=_(r7=>{"use strict";Object.defineProperty(r7,"__esModule",{value:!0});r7.canonicalize=t7;function t7(t){let e="";if(t===null||typeof t!="object"||t.toJSON!=null)e+=JSON.stringify(t);else if(Array.isArray(t)){e+="[";let r=!0;t.forEach(s=>{r||(e+=","),r=!1,e+=t7(s)}),e+="]"}else{e+="{";let r=!0;Object.keys(t).sort().forEach(s=>{r||(e+=","),r=!1,e+=JSON.stringify(s),e+=":",e+=t7(t[s])}),e+="}"}return e}});var n7=_(fO=>{"use strict";Object.defineProperty(fO,"__esModule",{value:!0});fO.toDER=hCt;fO.fromDER=gCt;var ACt=/-----BEGIN (.*)-----/,pCt=/-----END (.*)-----/;function hCt(t){let e="";return t.split(` +`).forEach(r=>{r.match(ACt)||r.match(pCt)||(e+=r)}),Buffer.from(e,"base64")}function gCt(t,e="CERTIFICATE"){let s=t.toString("base64").match(/.{1,64}/g)||"";return[`-----BEGIN ${e}-----`,...s,`-----END ${e}-----`].join(` +`).concat(` +`)}});var AO=_(zw=>{"use strict";Object.defineProperty(zw,"__esModule",{value:!0});zw.SHA2_HASH_ALGOS=zw.ECDSA_SIGNATURE_ALGOS=void 0;zw.ECDSA_SIGNATURE_ALGOS={"1.2.840.10045.4.3.1":"sha224","1.2.840.10045.4.3.2":"sha256","1.2.840.10045.4.3.3":"sha384","1.2.840.10045.4.3.4":"sha512"};zw.SHA2_HASH_ALGOS={"2.16.840.1.101.3.4.2.1":"sha256","2.16.840.1.101.3.4.2.2":"sha384","2.16.840.1.101.3.4.2.3":"sha512"}});var s7=_(pO=>{"use strict";Object.defineProperty(pO,"__esModule",{value:!0});pO.RFC3161TimestampVerificationError=void 0;var i7=class extends Error{};pO.RFC3161TimestampVerificationError=i7});var PDe=_(vA=>{"use strict";var dCt=vA&&vA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),mCt=vA&&vA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),yCt=vA&&vA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&dCt(e,t,r);return mCt(e,t),e};Object.defineProperty(vA,"__esModule",{value:!0});vA.TSTInfo=void 0;var bDe=yCt(Kw()),ECt=AO(),ICt=s7(),o7=class{constructor(e){this.root=e}get version(){return this.root.subs[0].toInteger()}get genTime(){return this.root.subs[4].toDate()}get messageImprintHashAlgorithm(){let e=this.messageImprintObj.subs[0].subs[0].toOID();return ECt.SHA2_HASH_ALGOS[e]}get messageImprintHashedMessage(){return this.messageImprintObj.subs[1].value}get raw(){return this.root.toDER()}verify(e){let r=bDe.digest(this.messageImprintHashAlgorithm,e);if(!bDe.bufferEqual(r,this.messageImprintHashedMessage))throw new ICt.RFC3161TimestampVerificationError("message imprint does not match artifact")}get messageImprintObj(){return this.root.subs[2]}};vA.TSTInfo=o7});var kDe=_(SA=>{"use strict";var CCt=SA&&SA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),wCt=SA&&SA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),BCt=SA&&SA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&CCt(e,t,r);return wCt(e,t),e};Object.defineProperty(SA,"__esModule",{value:!0});SA.RFC3161Timestamp=void 0;var vCt=cO(),a7=BCt(Kw()),xDe=AO(),Bb=s7(),SCt=PDe(),DCt="1.2.840.113549.1.7.2",bCt="1.2.840.113549.1.9.16.1.4",PCt="1.2.840.113549.1.9.4",l7=class t{constructor(e){this.root=e}static parse(e){let r=vCt.ASN1Obj.parseBuffer(e);return new t(r)}get status(){return this.pkiStatusInfoObj.subs[0].toInteger()}get contentType(){return this.contentTypeObj.toOID()}get eContentType(){return this.eContentTypeObj.toOID()}get signingTime(){return this.tstInfo.genTime}get signerIssuer(){return this.signerSidObj.subs[0].value}get signerSerialNumber(){return this.signerSidObj.subs[1].value}get signerDigestAlgorithm(){let e=this.signerDigestAlgorithmObj.subs[0].toOID();return xDe.SHA2_HASH_ALGOS[e]}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return xDe.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value}get tstInfo(){return new SCt.TSTInfo(this.eContentObj.subs[0].subs[0])}verify(e,r){if(!this.timeStampTokenObj)throw new Bb.RFC3161TimestampVerificationError("timeStampToken is missing");if(this.contentType!==DCt)throw new Bb.RFC3161TimestampVerificationError(`incorrect content type: ${this.contentType}`);if(this.eContentType!==bCt)throw new Bb.RFC3161TimestampVerificationError(`incorrect encapsulated content type: ${this.eContentType}`);this.tstInfo.verify(e),this.verifyMessageDigest(),this.verifySignature(r)}verifyMessageDigest(){let e=a7.digest(this.signerDigestAlgorithm,this.tstInfo.raw),r=this.messageDigestAttributeObj.subs[1].subs[0].value;if(!a7.bufferEqual(e,r))throw new Bb.RFC3161TimestampVerificationError("signed data does not match tstInfo")}verifySignature(e){let r=this.signedAttrsObj.toDER();if(r[0]=49,!a7.verify(r,e,this.signatureValue,this.signatureAlgorithm))throw new Bb.RFC3161TimestampVerificationError("signature verification failed")}get pkiStatusInfoObj(){return this.root.subs[0]}get timeStampTokenObj(){return this.root.subs[1]}get contentTypeObj(){return this.timeStampTokenObj.subs[0]}get signedDataObj(){return this.timeStampTokenObj.subs.find(r=>r.tag.isContextSpecific(0)).subs[0]}get encapContentInfoObj(){return this.signedDataObj.subs[2]}get signerInfosObj(){let e=this.signedDataObj;return e.subs[e.subs.length-1]}get signerInfoObj(){return this.signerInfosObj.subs[0]}get eContentTypeObj(){return this.encapContentInfoObj.subs[0]}get eContentObj(){return this.encapContentInfoObj.subs[1]}get signedAttrsObj(){return this.signerInfoObj.subs.find(r=>r.tag.isContextSpecific(0))}get messageDigestAttributeObj(){return this.signedAttrsObj.subs.find(r=>r.subs[0].tag.isOID()&&r.subs[0].toOID()===PCt)}get signerSidObj(){return this.signerInfoObj.subs[1]}get signerDigestAlgorithmObj(){return this.signerInfoObj.subs[2]}get signatureAlgorithmObj(){return this.signerInfoObj.subs[4]}get signatureValueObj(){return this.signerInfoObj.subs[5]}};SA.RFC3161Timestamp=l7});var QDe=_(hO=>{"use strict";Object.defineProperty(hO,"__esModule",{value:!0});hO.RFC3161Timestamp=void 0;var xCt=kDe();Object.defineProperty(hO,"RFC3161Timestamp",{enumerable:!0,get:function(){return xCt.RFC3161Timestamp}})});var RDe=_(DA=>{"use strict";var kCt=DA&&DA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),QCt=DA&&DA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),TCt=DA&&DA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&kCt(e,t,r);return QCt(e,t),e};Object.defineProperty(DA,"__esModule",{value:!0});DA.SignedCertificateTimestamp=void 0;var RCt=TCt(Kw()),TDe=Cb(),c7=class t{constructor(e){this.version=e.version,this.logID=e.logID,this.timestamp=e.timestamp,this.extensions=e.extensions,this.hashAlgorithm=e.hashAlgorithm,this.signatureAlgorithm=e.signatureAlgorithm,this.signature=e.signature}get datetime(){return new Date(Number(this.timestamp.readBigInt64BE()))}get algorithm(){switch(this.hashAlgorithm){case 0:return"none";case 1:return"md5";case 2:return"sha1";case 3:return"sha224";case 4:return"sha256";case 5:return"sha384";case 6:return"sha512";default:return"unknown"}}verify(e,r){let s=new TDe.ByteStream;return s.appendChar(this.version),s.appendChar(0),s.appendView(this.timestamp),s.appendUint16(1),s.appendView(e),s.appendUint16(this.extensions.byteLength),this.extensions.byteLength>0&&s.appendView(this.extensions),RCt.verify(s.buffer,r,this.signature,this.algorithm)}static parse(e){let r=new TDe.ByteStream(e),s=r.getUint8(),a=r.getBlock(32),n=r.getBlock(8),c=r.getUint16(),f=r.getBlock(c),p=r.getUint8(),h=r.getUint8(),E=r.getUint16(),C=r.getBlock(E);if(r.position!==e.length)throw new Error("SCT buffer length mismatch");return new t({version:s,logID:a,timestamp:n,extensions:f,hashAlgorithm:p,signatureAlgorithm:h,signature:C})}};DA.SignedCertificateTimestamp=c7});var d7=_(sa=>{"use strict";Object.defineProperty(sa,"__esModule",{value:!0});sa.X509SCTExtension=sa.X509SubjectKeyIDExtension=sa.X509AuthorityKeyIDExtension=sa.X509SubjectAlternativeNameExtension=sa.X509KeyUsageExtension=sa.X509BasicConstraintsExtension=sa.X509Extension=void 0;var FCt=Cb(),NCt=RDe(),ph=class{constructor(e){this.root=e}get oid(){return this.root.subs[0].toOID()}get critical(){return this.root.subs.length===3?this.root.subs[1].toBoolean():!1}get value(){return this.extnValueObj.value}get valueObj(){return this.extnValueObj}get extnValueObj(){return this.root.subs[this.root.subs.length-1]}};sa.X509Extension=ph;var u7=class extends ph{get isCA(){return this.sequence.subs[0]?.toBoolean()??!1}get pathLenConstraint(){return this.sequence.subs.length>1?this.sequence.subs[1].toInteger():void 0}get sequence(){return this.extnValueObj.subs[0]}};sa.X509BasicConstraintsExtension=u7;var f7=class extends ph{get digitalSignature(){return this.bitString[0]===1}get keyCertSign(){return this.bitString[5]===1}get crlSign(){return this.bitString[6]===1}get bitString(){return this.extnValueObj.subs[0].toBitString()}};sa.X509KeyUsageExtension=f7;var A7=class extends ph{get rfc822Name(){return this.findGeneralName(1)?.value.toString("ascii")}get uri(){return this.findGeneralName(6)?.value.toString("ascii")}otherName(e){let r=this.findGeneralName(0);return r===void 0||r.subs[0].toOID()!==e?void 0:r.subs[1].subs[0].value.toString("ascii")}findGeneralName(e){return this.generalNames.find(r=>r.tag.isContextSpecific(e))}get generalNames(){return this.extnValueObj.subs[0].subs}};sa.X509SubjectAlternativeNameExtension=A7;var p7=class extends ph{get keyIdentifier(){return this.findSequenceMember(0)?.value}findSequenceMember(e){return this.sequence.subs.find(r=>r.tag.isContextSpecific(e))}get sequence(){return this.extnValueObj.subs[0]}};sa.X509AuthorityKeyIDExtension=p7;var h7=class extends ph{get keyIdentifier(){return this.extnValueObj.subs[0].value}};sa.X509SubjectKeyIDExtension=h7;var g7=class extends ph{constructor(e){super(e)}get signedCertificateTimestamps(){let e=this.extnValueObj.subs[0].value,r=new FCt.ByteStream(e),s=r.getUint16()+2,a=[];for(;r.position{"use strict";var OCt=ic&&ic.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),LCt=ic&&ic.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),NDe=ic&&ic.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&OCt(e,t,r);return LCt(e,t),e};Object.defineProperty(ic,"__esModule",{value:!0});ic.X509Certificate=ic.EXTENSION_OID_SCT=void 0;var MCt=cO(),FDe=NDe(Kw()),UCt=AO(),_Ct=NDe(n7()),ny=d7(),HCt="2.5.29.14",jCt="2.5.29.15",GCt="2.5.29.17",qCt="2.5.29.19",WCt="2.5.29.35";ic.EXTENSION_OID_SCT="1.3.6.1.4.1.11129.2.4.2";var m7=class t{constructor(e){this.root=e}static parse(e){let r=typeof e=="string"?_Ct.toDER(e):e,s=MCt.ASN1Obj.parseBuffer(r);return new t(s)}get tbsCertificate(){return this.tbsCertificateObj}get version(){return`v${(this.versionObj.subs[0].toInteger()+BigInt(1)).toString()}`}get serialNumber(){return this.serialNumberObj.value}get notBefore(){return this.validityObj.subs[0].toDate()}get notAfter(){return this.validityObj.subs[1].toDate()}get issuer(){return this.issuerObj.value}get subject(){return this.subjectObj.value}get publicKey(){return this.subjectPublicKeyInfoObj.toDER()}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return UCt.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value.subarray(1)}get subjectAltName(){let e=this.extSubjectAltName;return e?.uri||e?.rfc822Name}get extensions(){return this.extensionsObj?.subs[0]?.subs||[]}get extKeyUsage(){let e=this.findExtension(jCt);return e?new ny.X509KeyUsageExtension(e):void 0}get extBasicConstraints(){let e=this.findExtension(qCt);return e?new ny.X509BasicConstraintsExtension(e):void 0}get extSubjectAltName(){let e=this.findExtension(GCt);return e?new ny.X509SubjectAlternativeNameExtension(e):void 0}get extAuthorityKeyID(){let e=this.findExtension(WCt);return e?new ny.X509AuthorityKeyIDExtension(e):void 0}get extSubjectKeyID(){let e=this.findExtension(HCt);return e?new ny.X509SubjectKeyIDExtension(e):void 0}get extSCT(){let e=this.findExtension(ic.EXTENSION_OID_SCT);return e?new ny.X509SCTExtension(e):void 0}get isCA(){let e=this.extBasicConstraints?.isCA||!1;return this.extKeyUsage?e&&this.extKeyUsage.keyCertSign:e}extension(e){let r=this.findExtension(e);return r?new ny.X509Extension(r):void 0}verify(e){let r=e?.publicKey||this.publicKey,s=FDe.createPublicKey(r);return FDe.verify(this.tbsCertificate.toDER(),s,this.signatureValue,this.signatureAlgorithm)}validForDate(e){return this.notBefore<=e&&e<=this.notAfter}equals(e){return this.root.toDER().equals(e.root.toDER())}clone(){let e=this.root.toDER(),r=Buffer.alloc(e.length);return e.copy(r),t.parse(r)}findExtension(e){return this.extensions.find(r=>r.subs[0].toOID()===e)}get tbsCertificateObj(){return this.root.subs[0]}get signatureAlgorithmObj(){return this.root.subs[1]}get signatureValueObj(){return this.root.subs[2]}get versionObj(){return this.tbsCertificateObj.subs[0]}get serialNumberObj(){return this.tbsCertificateObj.subs[1]}get issuerObj(){return this.tbsCertificateObj.subs[3]}get validityObj(){return this.tbsCertificateObj.subs[4]}get subjectObj(){return this.tbsCertificateObj.subs[5]}get subjectPublicKeyInfoObj(){return this.tbsCertificateObj.subs[6]}get extensionsObj(){return this.tbsCertificateObj.subs.find(e=>e.tag.isContextSpecific(3))}};ic.X509Certificate=m7});var MDe=_(Bg=>{"use strict";Object.defineProperty(Bg,"__esModule",{value:!0});Bg.X509SCTExtension=Bg.X509Certificate=Bg.EXTENSION_OID_SCT=void 0;var LDe=ODe();Object.defineProperty(Bg,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return LDe.EXTENSION_OID_SCT}});Object.defineProperty(Bg,"X509Certificate",{enumerable:!0,get:function(){return LDe.X509Certificate}});var YCt=d7();Object.defineProperty(Bg,"X509SCTExtension",{enumerable:!0,get:function(){return YCt.X509SCTExtension}})});var Cl=_(Jn=>{"use strict";var VCt=Jn&&Jn.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),JCt=Jn&&Jn.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),vb=Jn&&Jn.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&VCt(e,t,r);return JCt(e,t),e};Object.defineProperty(Jn,"__esModule",{value:!0});Jn.X509SCTExtension=Jn.X509Certificate=Jn.EXTENSION_OID_SCT=Jn.ByteStream=Jn.RFC3161Timestamp=Jn.pem=Jn.json=Jn.encoding=Jn.dsse=Jn.crypto=Jn.ASN1Obj=void 0;var KCt=cO();Object.defineProperty(Jn,"ASN1Obj",{enumerable:!0,get:function(){return KCt.ASN1Obj}});Jn.crypto=vb(Kw());Jn.dsse=vb(wDe());Jn.encoding=vb(SDe());Jn.json=vb(DDe());Jn.pem=vb(n7());var zCt=QDe();Object.defineProperty(Jn,"RFC3161Timestamp",{enumerable:!0,get:function(){return zCt.RFC3161Timestamp}});var XCt=Cb();Object.defineProperty(Jn,"ByteStream",{enumerable:!0,get:function(){return XCt.ByteStream}});var y7=MDe();Object.defineProperty(Jn,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return y7.EXTENSION_OID_SCT}});Object.defineProperty(Jn,"X509Certificate",{enumerable:!0,get:function(){return y7.X509Certificate}});Object.defineProperty(Jn,"X509SCTExtension",{enumerable:!0,get:function(){return y7.X509SCTExtension}})});var UDe=_(E7=>{"use strict";Object.defineProperty(E7,"__esModule",{value:!0});E7.extractJWTSubject=$Ct;var ZCt=Cl();function $Ct(t){let e=t.split(".",3),r=JSON.parse(ZCt.encoding.base64Decode(e[1]));switch(r.iss){case"https://accounts.google.com":case"https://oauth2.sigstore.dev/auth":return r.email;default:return r.sub}}});var _De=_((dnr,ewt)=>{ewt.exports={name:"@sigstore/sign",version:"3.1.0",description:"Sigstore signing library",main:"dist/index.js",types:"dist/index.d.ts",scripts:{clean:"shx rm -rf dist *.tsbuildinfo",build:"tsc --build",test:"jest"},files:["dist"],author:"bdehamer@github.com",license:"Apache-2.0",repository:{type:"git",url:"git+https://github.com/sigstore/sigstore-js.git"},bugs:{url:"https://github.com/sigstore/sigstore-js/issues"},homepage:"https://github.com/sigstore/sigstore-js/tree/main/packages/sign#readme",publishConfig:{provenance:!0},devDependencies:{"@sigstore/jest":"^0.0.0","@sigstore/mock":"^0.10.0","@sigstore/rekor-types":"^3.0.0","@types/make-fetch-happen":"^10.0.4","@types/promise-retry":"^1.1.6"},dependencies:{"@sigstore/bundle":"^3.1.0","@sigstore/core":"^2.0.0","@sigstore/protobuf-specs":"^0.4.0","make-fetch-happen":"^14.0.2","proc-log":"^5.0.0","promise-retry":"^2.0.1"},engines:{node:"^18.17.0 || >=20.5.0"}}});var jDe=_(Xw=>{"use strict";var twt=Xw&&Xw.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Xw,"__esModule",{value:!0});Xw.getUserAgent=void 0;var HDe=twt(Ie("os")),rwt=()=>{let t=_De().version,e=process.version,r=HDe.default.platform(),s=HDe.default.arch();return`sigstore-js/${t} (Node ${e}) (${r}/${s})`};Xw.getUserAgent=rwt});var vg=_(Ji=>{"use strict";var nwt=Ji&&Ji.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),iwt=Ji&&Ji.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),GDe=Ji&&Ji.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(gO,"__esModule",{value:!0});gO.BaseBundleBuilder=void 0;var I7=class{constructor(e){this.signer=e.signer,this.witnesses=e.witnesses}async create(e){let r=await this.prepare(e).then(f=>this.signer.sign(f)),s=await this.package(e,r),a=await Promise.all(this.witnesses.map(f=>f.testify(s.content,swt(r.key)))),n=[],c=[];return a.forEach(({tlogEntries:f,rfc3161Timestamps:p})=>{n.push(...f??[]),c.push(...p??[])}),s.verificationMaterial.tlogEntries=n,s.verificationMaterial.timestampVerificationData={rfc3161Timestamps:c},s}async prepare(e){return e.data}};gO.BaseBundleBuilder=I7;function swt(t){switch(t.$case){case"publicKey":return t.publicKey;case"x509Certificate":return t.certificate}}});var B7=_(bA=>{"use strict";var owt=bA&&bA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),awt=bA&&bA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),lwt=bA&&bA.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(dO,"__esModule",{value:!0});dO.DSSEBundleBuilder=void 0;var fwt=vg(),Awt=C7(),pwt=B7(),v7=class extends Awt.BaseBundleBuilder{constructor(e){super(e),this.certificateChain=e.certificateChain??!1}async prepare(e){let r=WDe(e);return fwt.dsse.preAuthEncoding(r.type,r.data)}async package(e,r){return(0,pwt.toDSSEBundle)(WDe(e),r,this.certificateChain)}};dO.DSSEBundleBuilder=v7;function WDe(t){return{...t,type:t.type??""}}});var VDe=_(mO=>{"use strict";Object.defineProperty(mO,"__esModule",{value:!0});mO.MessageSignatureBundleBuilder=void 0;var hwt=C7(),gwt=B7(),S7=class extends hwt.BaseBundleBuilder{constructor(e){super(e)}async package(e,r){return(0,gwt.toMessageSignatureBundle)(e,r)}};mO.MessageSignatureBundleBuilder=S7});var JDe=_(Zw=>{"use strict";Object.defineProperty(Zw,"__esModule",{value:!0});Zw.MessageSignatureBundleBuilder=Zw.DSSEBundleBuilder=void 0;var dwt=YDe();Object.defineProperty(Zw,"DSSEBundleBuilder",{enumerable:!0,get:function(){return dwt.DSSEBundleBuilder}});var mwt=VDe();Object.defineProperty(Zw,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return mwt.MessageSignatureBundleBuilder}})});var EO=_(yO=>{"use strict";Object.defineProperty(yO,"__esModule",{value:!0});yO.HTTPError=void 0;var D7=class extends Error{constructor({status:e,message:r,location:s}){super(`(${e}) ${r}`),this.statusCode=e,this.location=s}};yO.HTTPError=D7});var $w=_(Db=>{"use strict";Object.defineProperty(Db,"__esModule",{value:!0});Db.InternalError=void 0;Db.internalError=Ewt;var ywt=EO(),IO=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.name=this.constructor.name,this.cause=s,this.code=e}};Db.InternalError=IO;function Ewt(t,e,r){throw t instanceof ywt.HTTPError&&(r+=` - ${t.message}`),new IO({code:e,message:r,cause:t})}});var CO=_((Dnr,KDe)=>{KDe.exports=fetch});var zDe=_(e1=>{"use strict";var Iwt=e1&&e1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e1,"__esModule",{value:!0});e1.CIContextProvider=void 0;var Cwt=Iwt(CO()),wwt=[Bwt,vwt],b7=class{constructor(e="sigstore"){this.audience=e}async getToken(){return Promise.any(wwt.map(e=>e(this.audience))).catch(()=>Promise.reject("CI: no tokens available"))}};e1.CIContextProvider=b7;async function Bwt(t){if(!process.env.ACTIONS_ID_TOKEN_REQUEST_URL||!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)return Promise.reject("no token available");let e=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);return e.searchParams.append("audience",t),(await(0,Cwt.default)(e.href,{retry:2,headers:{Accept:"application/json",Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).json().then(s=>s.value)}async function vwt(){return process.env.SIGSTORE_ID_TOKEN?process.env.SIGSTORE_ID_TOKEN:Promise.reject("no token available")}});var XDe=_(wO=>{"use strict";Object.defineProperty(wO,"__esModule",{value:!0});wO.CIContextProvider=void 0;var Swt=zDe();Object.defineProperty(wO,"CIContextProvider",{enumerable:!0,get:function(){return Swt.CIContextProvider}})});var $De=_((xnr,ZDe)=>{var Dwt=Symbol("proc-log.meta");ZDe.exports={META:Dwt,output:{LEVELS:["standard","error","buffer","flush"],KEYS:{standard:"standard",error:"error",buffer:"buffer",flush:"flush"},standard:function(...t){return process.emit("output","standard",...t)},error:function(...t){return process.emit("output","error",...t)},buffer:function(...t){return process.emit("output","buffer",...t)},flush:function(...t){return process.emit("output","flush",...t)}},log:{LEVELS:["notice","error","warn","info","verbose","http","silly","timing","pause","resume"],KEYS:{notice:"notice",error:"error",warn:"warn",info:"info",verbose:"verbose",http:"http",silly:"silly",timing:"timing",pause:"pause",resume:"resume"},error:function(...t){return process.emit("log","error",...t)},notice:function(...t){return process.emit("log","notice",...t)},warn:function(...t){return process.emit("log","warn",...t)},info:function(...t){return process.emit("log","info",...t)},verbose:function(...t){return process.emit("log","verbose",...t)},http:function(...t){return process.emit("log","http",...t)},silly:function(...t){return process.emit("log","silly",...t)},timing:function(...t){return process.emit("log","timing",...t)},pause:function(){return process.emit("log","pause")},resume:function(){return process.emit("log","resume")}},time:{LEVELS:["start","end"],KEYS:{start:"start",end:"end"},start:function(t,e){process.emit("time","start",t);function r(){return process.emit("time","end",t)}if(typeof e=="function"){let s=e();return s&&s.finally?s.finally(r):(r(),s)}return r},end:function(t){return process.emit("time","end",t)}},input:{LEVELS:["start","end","read"],KEYS:{start:"start",end:"end",read:"read"},start:function(t){process.emit("input","start");function e(){return process.emit("input","end")}if(typeof t=="function"){let r=t();return r&&r.finally?r.finally(e):(e(),r)}return e},end:function(){return process.emit("input","end")},read:function(...t){let e,r,s=new Promise((a,n)=>{e=a,r=n});return process.emit("input","read",e,r,...t),s}}}});var rbe=_((knr,tbe)=>{"use strict";function ebe(t,e){for(let r in e)Object.defineProperty(t,r,{value:e[r],enumerable:!0,configurable:!0});return t}function bwt(t,e,r){if(!t||typeof t=="string")throw new TypeError("Please pass an Error to err-code");r||(r={}),typeof e=="object"&&(r=e,e=void 0),e!=null&&(r.code=e);try{return ebe(t,r)}catch{r.message=t.message,r.stack=t.stack;let a=function(){};return a.prototype=Object.create(Object.getPrototypeOf(t)),ebe(new a,r)}}tbe.exports=bwt});var ibe=_((Qnr,nbe)=>{function $c(t,e){typeof e=="boolean"&&(e={forever:e}),this._originalTimeouts=JSON.parse(JSON.stringify(t)),this._timeouts=t,this._options=e||{},this._maxRetryTime=e&&e.maxRetryTime||1/0,this._fn=null,this._errors=[],this._attempts=1,this._operationTimeout=null,this._operationTimeoutCb=null,this._timeout=null,this._operationStart=null,this._options.forever&&(this._cachedTimeouts=this._timeouts.slice(0))}nbe.exports=$c;$c.prototype.reset=function(){this._attempts=1,this._timeouts=this._originalTimeouts};$c.prototype.stop=function(){this._timeout&&clearTimeout(this._timeout),this._timeouts=[],this._cachedTimeouts=null};$c.prototype.retry=function(t){if(this._timeout&&clearTimeout(this._timeout),!t)return!1;var e=new Date().getTime();if(t&&e-this._operationStart>=this._maxRetryTime)return this._errors.unshift(new Error("RetryOperation timeout occurred")),!1;this._errors.push(t);var r=this._timeouts.shift();if(r===void 0)if(this._cachedTimeouts)this._errors.splice(this._errors.length-1,this._errors.length),this._timeouts=this._cachedTimeouts.slice(0),r=this._timeouts.shift();else return!1;var s=this,a=setTimeout(function(){s._attempts++,s._operationTimeoutCb&&(s._timeout=setTimeout(function(){s._operationTimeoutCb(s._attempts)},s._operationTimeout),s._options.unref&&s._timeout.unref()),s._fn(s._attempts)},r);return this._options.unref&&a.unref(),!0};$c.prototype.attempt=function(t,e){this._fn=t,e&&(e.timeout&&(this._operationTimeout=e.timeout),e.cb&&(this._operationTimeoutCb=e.cb));var r=this;this._operationTimeoutCb&&(this._timeout=setTimeout(function(){r._operationTimeoutCb()},r._operationTimeout)),this._operationStart=new Date().getTime(),this._fn(this._attempts)};$c.prototype.try=function(t){console.log("Using RetryOperation.try() is deprecated"),this.attempt(t)};$c.prototype.start=function(t){console.log("Using RetryOperation.start() is deprecated"),this.attempt(t)};$c.prototype.start=$c.prototype.try;$c.prototype.errors=function(){return this._errors};$c.prototype.attempts=function(){return this._attempts};$c.prototype.mainError=function(){if(this._errors.length===0)return null;for(var t={},e=null,r=0,s=0;s=r&&(e=a,r=c)}return e}});var sbe=_(iy=>{var Pwt=ibe();iy.operation=function(t){var e=iy.timeouts(t);return new Pwt(e,{forever:t&&t.forever,unref:t&&t.unref,maxRetryTime:t&&t.maxRetryTime})};iy.timeouts=function(t){if(t instanceof Array)return[].concat(t);var e={retries:10,factor:2,minTimeout:1*1e3,maxTimeout:1/0,randomize:!1};for(var r in t)e[r]=t[r];if(e.minTimeout>e.maxTimeout)throw new Error("minTimeout is greater than maxTimeout");for(var s=[],a=0;a{obe.exports=sbe()});var ube=_((Fnr,cbe)=>{"use strict";var xwt=rbe(),kwt=abe(),Qwt=Object.prototype.hasOwnProperty;function lbe(t){return t&&t.code==="EPROMISERETRY"&&Qwt.call(t,"retried")}function Twt(t,e){var r,s;return typeof t=="object"&&typeof e=="function"&&(r=e,e=t,t=r),s=kwt.operation(e),new Promise(function(a,n){s.attempt(function(c){Promise.resolve().then(function(){return t(function(f){throw lbe(f)&&(f=f.retried),xwt(new Error("Retrying"),"EPROMISERETRY",{retried:f})},c)}).then(a,function(f){lbe(f)&&(f=f.retried,s.retry(f||new Error))||n(f)})})})}cbe.exports=Twt});var BO=_(bb=>{"use strict";var Abe=bb&&bb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bb,"__esModule",{value:!0});bb.fetchWithRetry=qwt;var Rwt=Ie("http2"),Fwt=Abe(CO()),fbe=$De(),Nwt=Abe(ube()),Owt=vg(),Lwt=EO(),{HTTP2_HEADER_LOCATION:Mwt,HTTP2_HEADER_CONTENT_TYPE:Uwt,HTTP2_HEADER_USER_AGENT:_wt,HTTP_STATUS_INTERNAL_SERVER_ERROR:Hwt,HTTP_STATUS_TOO_MANY_REQUESTS:jwt,HTTP_STATUS_REQUEST_TIMEOUT:Gwt}=Rwt.constants;async function qwt(t,e){return(0,Nwt.default)(async(r,s)=>{let a=e.method||"POST",n={[_wt]:Owt.ua.getUserAgent(),...e.headers},c=await(0,Fwt.default)(t,{method:a,headers:n,body:e.body,timeout:e.timeout,retry:!1}).catch(f=>(fbe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${f}`),r(f)));if(c.ok)return c;{let f=await Wwt(c);if(fbe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${c.status}`),Ywt(c.status))return r(f);throw f}},Vwt(e.retry))}var Wwt=async t=>{let e=t.statusText,r=t.headers.get(Mwt)||void 0;if(t.headers.get(Uwt)?.includes("application/json"))try{e=(await t.json()).message||e}catch{}return new Lwt.HTTPError({status:t.status,message:e,location:r})},Ywt=t=>[Gwt,jwt].includes(t)||t>=Hwt,Vwt=t=>typeof t=="boolean"?{retries:t?1:0}:typeof t=="number"?{retries:t}:{retries:0,...t}});var pbe=_(vO=>{"use strict";Object.defineProperty(vO,"__esModule",{value:!0});vO.Fulcio=void 0;var Jwt=BO(),P7=class{constructor(e){this.options=e}async createSigningCertificate(e){let{baseURL:r,retry:s,timeout:a}=this.options,n=`${r}/api/v2/signingCert`;return(await(0,Jwt.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:a,retry:s})).json()}};vO.Fulcio=P7});var hbe=_(SO=>{"use strict";Object.defineProperty(SO,"__esModule",{value:!0});SO.CAClient=void 0;var Kwt=$w(),zwt=pbe(),x7=class{constructor(e){this.fulcio=new zwt.Fulcio({baseURL:e.fulcioBaseURL,retry:e.retry,timeout:e.timeout})}async createSigningCertificate(e,r,s){let a=Xwt(e,r,s);try{let n=await this.fulcio.createSigningCertificate(a);return(n.signedCertificateEmbeddedSct?n.signedCertificateEmbeddedSct:n.signedCertificateDetachedSct).chain.certificates}catch(n){(0,Kwt.internalError)(n,"CA_CREATE_SIGNING_CERTIFICATE_ERROR","error creating signing certificate")}}};SO.CAClient=x7;function Xwt(t,e,r){return{credentials:{oidcIdentityToken:t},publicKeyRequest:{publicKey:{algorithm:"ECDSA",content:e},proofOfPossession:r.toString("base64")}}}});var dbe=_(t1=>{"use strict";var Zwt=t1&&t1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(t1,"__esModule",{value:!0});t1.EphemeralSigner=void 0;var gbe=Zwt(Ie("crypto")),$wt="ec",e1t="P-256",k7=class{constructor(){this.keypair=gbe.default.generateKeyPairSync($wt,{namedCurve:e1t})}async sign(e){let r=gbe.default.sign(null,e,this.keypair.privateKey),s=this.keypair.publicKey.export({format:"pem",type:"spki"}).toString("ascii");return{signature:r,key:{$case:"publicKey",publicKey:s}}}};t1.EphemeralSigner=k7});var mbe=_(sy=>{"use strict";Object.defineProperty(sy,"__esModule",{value:!0});sy.FulcioSigner=sy.DEFAULT_FULCIO_URL=void 0;var Q7=$w(),t1t=vg(),r1t=hbe(),n1t=dbe();sy.DEFAULT_FULCIO_URL="https://fulcio.sigstore.dev";var T7=class{constructor(e){this.ca=new r1t.CAClient({...e,fulcioBaseURL:e.fulcioBaseURL||sy.DEFAULT_FULCIO_URL}),this.identityProvider=e.identityProvider,this.keyHolder=e.keyHolder||new n1t.EphemeralSigner}async sign(e){let r=await this.getIdentityToken(),s;try{s=t1t.oidc.extractJWTSubject(r)}catch(f){throw new Q7.InternalError({code:"IDENTITY_TOKEN_PARSE_ERROR",message:`invalid identity token: ${r}`,cause:f})}let a=await this.keyHolder.sign(Buffer.from(s));if(a.key.$case!=="publicKey")throw new Q7.InternalError({code:"CA_CREATE_SIGNING_CERTIFICATE_ERROR",message:"unexpected format for signing key"});let n=await this.ca.createSigningCertificate(r,a.key.publicKey,a.signature);return{signature:(await this.keyHolder.sign(e)).signature,key:{$case:"x509Certificate",certificate:n[0]}}}async getIdentityToken(){try{return await this.identityProvider.getToken()}catch(e){throw new Q7.InternalError({code:"IDENTITY_TOKEN_READ_ERROR",message:"error retrieving identity token",cause:e})}}};sy.FulcioSigner=T7});var Ebe=_(r1=>{"use strict";Object.defineProperty(r1,"__esModule",{value:!0});r1.FulcioSigner=r1.DEFAULT_FULCIO_URL=void 0;var ybe=mbe();Object.defineProperty(r1,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return ybe.DEFAULT_FULCIO_URL}});Object.defineProperty(r1,"FulcioSigner",{enumerable:!0,get:function(){return ybe.FulcioSigner}})});var wbe=_(DO=>{"use strict";Object.defineProperty(DO,"__esModule",{value:!0});DO.Rekor=void 0;var Ibe=BO(),R7=class{constructor(e){this.options=e}async createEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries`,f=await(await(0,Ibe.fetchWithRetry)(n,{headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).json();return Cbe(f)}async getEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries/${e}`,f=await(await(0,Ibe.fetchWithRetry)(n,{method:"GET",headers:{Accept:"application/json"},timeout:s,retry:a})).json();return Cbe(f)}};DO.Rekor=R7;function Cbe(t){let e=Object.entries(t);if(e.length!=1)throw new Error("Received multiple entries in Rekor response");let[r,s]=e[0];return{...s,uuid:r}}});var vbe=_(bO=>{"use strict";Object.defineProperty(bO,"__esModule",{value:!0});bO.TLogClient=void 0;var Bbe=$w(),i1t=EO(),s1t=wbe(),F7=class{constructor(e){this.fetchOnConflict=e.fetchOnConflict??!1,this.rekor=new s1t.Rekor({baseURL:e.rekorBaseURL,retry:e.retry,timeout:e.timeout})}async createEntry(e){let r;try{r=await this.rekor.createEntry(e)}catch(s){if(o1t(s)&&this.fetchOnConflict){let a=s.location.split("/").pop()||"";try{r=await this.rekor.getEntry(a)}catch(n){(0,Bbe.internalError)(n,"TLOG_FETCH_ENTRY_ERROR","error fetching tlog entry")}}else(0,Bbe.internalError)(s,"TLOG_CREATE_ENTRY_ERROR","error creating tlog entry")}return r}};bO.TLogClient=F7;function o1t(t){return t instanceof i1t.HTTPError&&t.statusCode===409&&t.location!==void 0}});var Sbe=_(N7=>{"use strict";Object.defineProperty(N7,"__esModule",{value:!0});N7.toProposedEntry=l1t;var a1t=Ib(),Sg=vg(),Pb="sha256";function l1t(t,e,r="dsse"){switch(t.$case){case"dsseEnvelope":return r==="intoto"?f1t(t.dsseEnvelope,e):u1t(t.dsseEnvelope,e);case"messageSignature":return c1t(t.messageSignature,e)}}function c1t(t,e){let r=t.messageDigest.digest.toString("hex"),s=t.signature.toString("base64"),a=Sg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"hashedrekord",spec:{data:{hash:{algorithm:Pb,value:r}},signature:{content:s,publicKey:{content:a}}}}}function u1t(t,e){let r=JSON.stringify((0,a1t.envelopeToJSON)(t)),s=Sg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"dsse",spec:{proposedContent:{envelope:r,verifiers:[s]}}}}function f1t(t,e){let r=Sg.crypto.digest(Pb,t.payload).toString("hex"),s=A1t(t,e),a=Sg.encoding.base64Encode(t.payload.toString("base64")),n=Sg.encoding.base64Encode(t.signatures[0].sig.toString("base64")),c=t.signatures[0].keyid,f=Sg.encoding.base64Encode(e),p={payloadType:t.payloadType,payload:a,signatures:[{sig:n,publicKey:f}]};return c.length>0&&(p.signatures[0].keyid=c),{apiVersion:"0.0.2",kind:"intoto",spec:{content:{envelope:p,hash:{algorithm:Pb,value:s},payloadHash:{algorithm:Pb,value:r}}}}}function A1t(t,e){let r={payloadType:t.payloadType,payload:t.payload.toString("base64"),signatures:[{sig:t.signatures[0].sig.toString("base64"),publicKey:e}]};return t.signatures[0].keyid.length>0&&(r.signatures[0].keyid=t.signatures[0].keyid),Sg.crypto.digest(Pb,Sg.json.canonicalize(r)).toString("hex")}});var Dbe=_(oy=>{"use strict";Object.defineProperty(oy,"__esModule",{value:!0});oy.RekorWitness=oy.DEFAULT_REKOR_URL=void 0;var p1t=vg(),h1t=vbe(),g1t=Sbe();oy.DEFAULT_REKOR_URL="https://rekor.sigstore.dev";var O7=class{constructor(e){this.entryType=e.entryType,this.tlog=new h1t.TLogClient({...e,rekorBaseURL:e.rekorBaseURL||oy.DEFAULT_REKOR_URL})}async testify(e,r){let s=(0,g1t.toProposedEntry)(e,r,this.entryType),a=await this.tlog.createEntry(s);return d1t(a)}};oy.RekorWitness=O7;function d1t(t){let e=Buffer.from(t.logID,"hex"),r=p1t.encoding.base64Decode(t.body),s=JSON.parse(r),a=t?.verification?.signedEntryTimestamp?m1t(t.verification.signedEntryTimestamp):void 0,n=t?.verification?.inclusionProof?y1t(t.verification.inclusionProof):void 0;return{tlogEntries:[{logIndex:t.logIndex.toString(),logId:{keyId:e},integratedTime:t.integratedTime.toString(),kindVersion:{kind:s.kind,version:s.apiVersion},inclusionPromise:a,inclusionProof:n,canonicalizedBody:Buffer.from(t.body,"base64")}]}}function m1t(t){return{signedEntryTimestamp:Buffer.from(t,"base64")}}function y1t(t){return{logIndex:t.logIndex.toString(),treeSize:t.treeSize.toString(),rootHash:Buffer.from(t.rootHash,"hex"),hashes:t.hashes.map(e=>Buffer.from(e,"hex")),checkpoint:{envelope:t.checkpoint}}}});var bbe=_(PO=>{"use strict";Object.defineProperty(PO,"__esModule",{value:!0});PO.TimestampAuthority=void 0;var E1t=BO(),L7=class{constructor(e){this.options=e}async createTimestamp(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/timestamp`;return(await(0,E1t.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).buffer()}};PO.TimestampAuthority=L7});var xbe=_(xO=>{"use strict";Object.defineProperty(xO,"__esModule",{value:!0});xO.TSAClient=void 0;var I1t=$w(),C1t=bbe(),w1t=vg(),Pbe="sha256",M7=class{constructor(e){this.tsa=new C1t.TimestampAuthority({baseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async createTimestamp(e){let r={artifactHash:w1t.crypto.digest(Pbe,e).toString("base64"),hashAlgorithm:Pbe};try{return await this.tsa.createTimestamp(r)}catch(s){(0,I1t.internalError)(s,"TSA_CREATE_TIMESTAMP_ERROR","error creating timestamp")}}};xO.TSAClient=M7});var kbe=_(kO=>{"use strict";Object.defineProperty(kO,"__esModule",{value:!0});kO.TSAWitness=void 0;var B1t=xbe(),U7=class{constructor(e){this.tsa=new B1t.TSAClient({tsaBaseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async testify(e){let r=v1t(e);return{rfc3161Timestamps:[{signedTimestamp:await this.tsa.createTimestamp(r)}]}}};kO.TSAWitness=U7;function v1t(t){switch(t.$case){case"dsseEnvelope":return t.dsseEnvelope.signatures[0].sig;case"messageSignature":return t.messageSignature.signature}}});var Tbe=_(Dg=>{"use strict";Object.defineProperty(Dg,"__esModule",{value:!0});Dg.TSAWitness=Dg.RekorWitness=Dg.DEFAULT_REKOR_URL=void 0;var Qbe=Dbe();Object.defineProperty(Dg,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return Qbe.DEFAULT_REKOR_URL}});Object.defineProperty(Dg,"RekorWitness",{enumerable:!0,get:function(){return Qbe.RekorWitness}});var S1t=kbe();Object.defineProperty(Dg,"TSAWitness",{enumerable:!0,get:function(){return S1t.TSAWitness}})});var H7=_(ys=>{"use strict";Object.defineProperty(ys,"__esModule",{value:!0});ys.TSAWitness=ys.RekorWitness=ys.DEFAULT_REKOR_URL=ys.FulcioSigner=ys.DEFAULT_FULCIO_URL=ys.CIContextProvider=ys.InternalError=ys.MessageSignatureBundleBuilder=ys.DSSEBundleBuilder=void 0;var Rbe=JDe();Object.defineProperty(ys,"DSSEBundleBuilder",{enumerable:!0,get:function(){return Rbe.DSSEBundleBuilder}});Object.defineProperty(ys,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return Rbe.MessageSignatureBundleBuilder}});var D1t=$w();Object.defineProperty(ys,"InternalError",{enumerable:!0,get:function(){return D1t.InternalError}});var b1t=XDe();Object.defineProperty(ys,"CIContextProvider",{enumerable:!0,get:function(){return b1t.CIContextProvider}});var Fbe=Ebe();Object.defineProperty(ys,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return Fbe.DEFAULT_FULCIO_URL}});Object.defineProperty(ys,"FulcioSigner",{enumerable:!0,get:function(){return Fbe.FulcioSigner}});var _7=Tbe();Object.defineProperty(ys,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return _7.DEFAULT_REKOR_URL}});Object.defineProperty(ys,"RekorWitness",{enumerable:!0,get:function(){return _7.RekorWitness}});Object.defineProperty(ys,"TSAWitness",{enumerable:!0,get:function(){return _7.TSAWitness}})});var Obe=_(xb=>{"use strict";var Nbe=xb&&xb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xb,"__esModule",{value:!0});xb.appDataPath=x1t;var P1t=Nbe(Ie("os")),n1=Nbe(Ie("path"));function x1t(t){let e=P1t.default.homedir();switch(process.platform){case"darwin":{let r=n1.default.join(e,"Library","Application Support");return n1.default.join(r,t)}case"win32":{let r=process.env.LOCALAPPDATA||n1.default.join(e,"AppData","Local");return n1.default.join(r,t,"Data")}default:{let r=process.env.XDG_DATA_HOME||n1.default.join(e,".local","share");return n1.default.join(r,t)}}}});var PA=_(wl=>{"use strict";Object.defineProperty(wl,"__esModule",{value:!0});wl.UnsupportedAlgorithmError=wl.CryptoError=wl.LengthOrHashMismatchError=wl.UnsignedMetadataError=wl.RepositoryError=wl.ValueError=void 0;var j7=class extends Error{};wl.ValueError=j7;var kb=class extends Error{};wl.RepositoryError=kb;var G7=class extends kb{};wl.UnsignedMetadataError=G7;var q7=class extends kb{};wl.LengthOrHashMismatchError=q7;var QO=class extends Error{};wl.CryptoError=QO;var W7=class extends QO{};wl.UnsupportedAlgorithmError=W7});var Mbe=_(bg=>{"use strict";Object.defineProperty(bg,"__esModule",{value:!0});bg.isDefined=k1t;bg.isObject=Lbe;bg.isStringArray=Q1t;bg.isObjectArray=T1t;bg.isStringRecord=R1t;bg.isObjectRecord=F1t;function k1t(t){return t!==void 0}function Lbe(t){return typeof t=="object"&&t!==null}function Q1t(t){return Array.isArray(t)&&t.every(e=>typeof e=="string")}function T1t(t){return Array.isArray(t)&&t.every(Lbe)}function R1t(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="string")}function F1t(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="object"&&e!==null)}});var V7=_(($nr,Hbe)=>{var Ube=",",N1t=":",O1t="[",L1t="]",M1t="{",U1t="}";function Y7(t){let e=[];if(typeof t=="string")e.push(_be(t));else if(typeof t=="boolean")e.push(JSON.stringify(t));else if(Number.isInteger(t))e.push(JSON.stringify(t));else if(t===null)e.push(JSON.stringify(t));else if(Array.isArray(t)){e.push(O1t);let r=!0;t.forEach(s=>{r||e.push(Ube),r=!1,e.push(Y7(s))}),e.push(L1t)}else if(typeof t=="object"){e.push(M1t);let r=!0;Object.keys(t).sort().forEach(s=>{r||e.push(Ube),r=!1,e.push(_be(s)),e.push(N1t),e.push(Y7(t[s]))}),e.push(U1t)}else throw new TypeError("cannot encode "+t.toString());return e.join("")}function _be(t){return'"'+t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'}Hbe.exports={canonicalize:Y7}});var jbe=_(i1=>{"use strict";var _1t=i1&&i1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(i1,"__esModule",{value:!0});i1.verifySignature=void 0;var H1t=V7(),j1t=_1t(Ie("crypto")),G1t=(t,e,r)=>{let s=Buffer.from((0,H1t.canonicalize)(t));return j1t.default.verify(void 0,s,e,Buffer.from(r,"hex"))};i1.verifySignature=G1t});var ff=_(eu=>{"use strict";var q1t=eu&&eu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),W1t=eu&&eu.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Gbe=eu&&eu.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&q1t(e,t,r);return W1t(e,t),e};Object.defineProperty(eu,"__esModule",{value:!0});eu.crypto=eu.guard=void 0;eu.guard=Gbe(Mbe());eu.crypto=Gbe(jbe())});var ay=_(hh=>{"use strict";var Y1t=hh&&hh.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(hh,"__esModule",{value:!0});hh.Signed=hh.MetadataKind=void 0;hh.isMetadataKind=J1t;var V1t=Y1t(Ie("util")),Qb=PA(),J7=ff(),qbe=["1","0","31"],K7;(function(t){t.Root="root",t.Timestamp="timestamp",t.Snapshot="snapshot",t.Targets="targets"})(K7||(hh.MetadataKind=K7={}));function J1t(t){return typeof t=="string"&&Object.values(K7).includes(t)}var z7=class t{constructor(e){this.specVersion=e.specVersion||qbe.join(".");let r=this.specVersion.split(".");if(!(r.length===2||r.length===3)||!r.every(s=>K1t(s)))throw new Qb.ValueError("Failed to parse specVersion");if(r[0]!=qbe[0])throw new Qb.ValueError("Unsupported specVersion");this.expires=e.expires,this.version=e.version,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.specVersion===e.specVersion&&this.expires===e.expires&&this.version===e.version&&V1t.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}isExpired(e){return e||(e=new Date),e>=new Date(this.expires)}static commonFieldsFromJSON(e){let{spec_version:r,expires:s,version:a,...n}=e;if(J7.guard.isDefined(r)){if(typeof r!="string")throw new TypeError("spec_version must be a string")}else throw new Qb.ValueError("spec_version is not defined");if(J7.guard.isDefined(s)){if(typeof s!="string")throw new TypeError("expires must be a string")}else throw new Qb.ValueError("expires is not defined");if(J7.guard.isDefined(a)){if(typeof a!="number")throw new TypeError("version must be a number")}else throw new Qb.ValueError("version is not defined");return{specVersion:r,expires:s,version:a,unrecognizedFields:n}}};hh.Signed=z7;function K1t(t){return!isNaN(Number(t))}});var Tb=_(xg=>{"use strict";var Wbe=xg&&xg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xg,"__esModule",{value:!0});xg.TargetFile=xg.MetaFile=void 0;var Ybe=Wbe(Ie("crypto")),RO=Wbe(Ie("util")),Pg=PA(),TO=ff(),X7=class t{constructor(e){if(e.version<=0)throw new Pg.ValueError("Metafile version must be at least 1");e.length!==void 0&&Vbe(e.length),this.version=e.version,this.length=e.length,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.version===e.version&&this.length===e.length&&RO.default.isDeepStrictEqual(this.hashes,e.hashes)&&RO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}verify(e){if(this.length!==void 0&&e.length!==this.length)throw new Pg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${e.length}`);this.hashes&&Object.entries(this.hashes).forEach(([r,s])=>{let a;try{a=Ybe.default.createHash(r)}catch{throw new Pg.LengthOrHashMismatchError(`Hash algorithm ${r} not supported`)}let n=a.update(e).digest("hex");if(n!==s)throw new Pg.LengthOrHashMismatchError(`Expected hash ${s} but got ${n}`)})}toJSON(){let e={version:this.version,...this.unrecognizedFields};return this.length!==void 0&&(e.length=this.length),this.hashes&&(e.hashes=this.hashes),e}static fromJSON(e){let{version:r,length:s,hashes:a,...n}=e;if(typeof r!="number")throw new TypeError("version must be a number");if(TO.guard.isDefined(s)&&typeof s!="number")throw new TypeError("length must be a number");if(TO.guard.isDefined(a)&&!TO.guard.isStringRecord(a))throw new TypeError("hashes must be string keys and values");return new t({version:r,length:s,hashes:a,unrecognizedFields:n})}};xg.MetaFile=X7;var Z7=class t{constructor(e){Vbe(e.length),this.length=e.length,this.path=e.path,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}get custom(){let e=this.unrecognizedFields.custom;return!e||Array.isArray(e)||typeof e!="object"?{}:e}equals(e){return e instanceof t?this.length===e.length&&this.path===e.path&&RO.default.isDeepStrictEqual(this.hashes,e.hashes)&&RO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}async verify(e){let r=0,s=Object.keys(this.hashes).reduce((a,n)=>{try{a[n]=Ybe.default.createHash(n)}catch{throw new Pg.LengthOrHashMismatchError(`Hash algorithm ${n} not supported`)}return a},{});for await(let a of e)r+=a.length,Object.values(s).forEach(n=>{n.update(a)});if(r!==this.length)throw new Pg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${r}`);Object.entries(s).forEach(([a,n])=>{let c=this.hashes[a],f=n.digest("hex");if(f!==c)throw new Pg.LengthOrHashMismatchError(`Expected hash ${c} but got ${f}`)})}toJSON(){return{length:this.length,hashes:this.hashes,...this.unrecognizedFields}}static fromJSON(e,r){let{length:s,hashes:a,...n}=r;if(typeof s!="number")throw new TypeError("length must be a number");if(!TO.guard.isStringRecord(a))throw new TypeError("hashes must have string keys and values");return new t({length:s,path:e,hashes:a,unrecognizedFields:n})}};xg.TargetFile=Z7;function Vbe(t){if(t<0)throw new Pg.ValueError("Length must be at least 0")}});var Jbe=_($7=>{"use strict";Object.defineProperty($7,"__esModule",{value:!0});$7.encodeOIDString=X1t;var z1t=6;function X1t(t){let e=t.split("."),r=parseInt(e[0],10)*40+parseInt(e[1],10),s=[];e.slice(2).forEach(n=>{let c=Z1t(parseInt(n,10));s.push(...c)});let a=Buffer.from([r,...s]);return Buffer.from([z1t,a.length,...a])}function Z1t(t){let e=[],r=0;for(;t>0;)e.unshift(t&127|r),t>>=7,r=128;return e}});var Zbe=_(Fb=>{"use strict";var $1t=Fb&&Fb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Fb,"__esModule",{value:!0});Fb.getPublicKey=n2t;var s1=$1t(Ie("crypto")),Rb=PA(),eJ=Jbe(),FO=48,Kbe=3,zbe=0,e2t="1.3.101.112",t2t="1.2.840.10045.2.1",r2t="1.2.840.10045.3.1.7",tJ="-----BEGIN PUBLIC KEY-----";function n2t(t){switch(t.keyType){case"rsa":return i2t(t);case"ed25519":return s2t(t);case"ecdsa":case"ecdsa-sha2-nistp256":case"ecdsa-sha2-nistp384":return o2t(t);default:throw new Rb.UnsupportedAlgorithmError(`Unsupported key type: ${t.keyType}`)}}function i2t(t){if(!t.keyVal.startsWith(tJ))throw new Rb.CryptoError("Invalid key format");let e=s1.default.createPublicKey(t.keyVal);switch(t.scheme){case"rsassa-pss-sha256":return{key:e,padding:s1.default.constants.RSA_PKCS1_PSS_PADDING};default:throw new Rb.UnsupportedAlgorithmError(`Unsupported RSA scheme: ${t.scheme}`)}}function s2t(t){let e;if(t.keyVal.startsWith(tJ))e=s1.default.createPublicKey(t.keyVal);else{if(!Xbe(t.keyVal))throw new Rb.CryptoError("Invalid key format");e=s1.default.createPublicKey({key:a2t.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}function o2t(t){let e;if(t.keyVal.startsWith(tJ))e=s1.default.createPublicKey(t.keyVal);else{if(!Xbe(t.keyVal))throw new Rb.CryptoError("Invalid key format");e=s1.default.createPublicKey({key:l2t.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}var a2t={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=(0,eJ.encodeOIDString)(e2t),s=Buffer.concat([Buffer.concat([Buffer.from([FO]),Buffer.from([r.length]),r]),Buffer.concat([Buffer.from([Kbe]),Buffer.from([e.length+1]),Buffer.from([zbe]),e])]);return Buffer.concat([Buffer.from([FO]),Buffer.from([s.length]),s])}},l2t={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=Buffer.concat([Buffer.from([Kbe]),Buffer.from([e.length+1]),Buffer.from([zbe]),e]),s=Buffer.concat([(0,eJ.encodeOIDString)(t2t),(0,eJ.encodeOIDString)(r2t)]),a=Buffer.concat([Buffer.from([FO]),Buffer.from([s.length]),s]);return Buffer.concat([Buffer.from([FO]),Buffer.from([a.length+r.length]),a,r])}},Xbe=t=>/^[0-9a-fA-F]+$/.test(t)});var NO=_(o1=>{"use strict";var c2t=o1&&o1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(o1,"__esModule",{value:!0});o1.Key=void 0;var $be=c2t(Ie("util")),Nb=PA(),ePe=ff(),u2t=Zbe(),rJ=class t{constructor(e){let{keyID:r,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c}=e;this.keyID=r,this.keyType=s,this.scheme=a,this.keyVal=n,this.unrecognizedFields=c||{}}verifySignature(e){let r=e.signatures[this.keyID];if(!r)throw new Nb.UnsignedMetadataError("no signature for key found in metadata");if(!this.keyVal.public)throw new Nb.UnsignedMetadataError("no public key found");let s=(0,u2t.getPublicKey)({keyType:this.keyType,scheme:this.scheme,keyVal:this.keyVal.public}),a=e.signed.toJSON();try{if(!ePe.crypto.verifySignature(a,s,r.sig))throw new Nb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}catch(n){throw n instanceof Nb.UnsignedMetadataError?n:new Nb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}}equals(e){return e instanceof t?this.keyID===e.keyID&&this.keyType===e.keyType&&this.scheme===e.scheme&&$be.default.isDeepStrictEqual(this.keyVal,e.keyVal)&&$be.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keytype:this.keyType,scheme:this.scheme,keyval:this.keyVal,...this.unrecognizedFields}}static fromJSON(e,r){let{keytype:s,scheme:a,keyval:n,...c}=r;if(typeof s!="string")throw new TypeError("keytype must be a string");if(typeof a!="string")throw new TypeError("scheme must be a string");if(!ePe.guard.isStringRecord(n))throw new TypeError("keyval must be a string record");return new t({keyID:e,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c})}};o1.Key=rJ});var sPe=_((air,iPe)=>{"use strict";iPe.exports=rPe;function rPe(t,e,r){t instanceof RegExp&&(t=tPe(t,r)),e instanceof RegExp&&(e=tPe(e,r));var s=nPe(t,e,r);return s&&{start:s[0],end:s[1],pre:r.slice(0,s[0]),body:r.slice(s[0]+t.length,s[1]),post:r.slice(s[1]+e.length)}}function tPe(t,e){var r=e.match(t);return r?r[0]:null}rPe.range=nPe;function nPe(t,e,r){var s,a,n,c,f,p=r.indexOf(t),h=r.indexOf(e,p+1),E=p;if(p>=0&&h>0){for(s=[],n=r.length;E>=0&&!f;)E==p?(s.push(E),p=r.indexOf(t,E+1)):s.length==1?f=[s.pop(),h]:(a=s.pop(),a=0?p:h;s.length&&(f=[n,c])}return f}});var pPe=_((lir,APe)=>{var oPe=sPe();APe.exports=p2t;var aPe="\0SLASH"+Math.random()+"\0",lPe="\0OPEN"+Math.random()+"\0",iJ="\0CLOSE"+Math.random()+"\0",cPe="\0COMMA"+Math.random()+"\0",uPe="\0PERIOD"+Math.random()+"\0";function nJ(t){return parseInt(t,10)==t?parseInt(t,10):t.charCodeAt(0)}function f2t(t){return t.split("\\\\").join(aPe).split("\\{").join(lPe).split("\\}").join(iJ).split("\\,").join(cPe).split("\\.").join(uPe)}function A2t(t){return t.split(aPe).join("\\").split(lPe).join("{").split(iJ).join("}").split(cPe).join(",").split(uPe).join(".")}function fPe(t){if(!t)return[""];var e=[],r=oPe("{","}",t);if(!r)return t.split(",");var s=r.pre,a=r.body,n=r.post,c=s.split(",");c[c.length-1]+="{"+a+"}";var f=fPe(n);return n.length&&(c[c.length-1]+=f.shift(),c.push.apply(c,f)),e.push.apply(e,c),e}function p2t(t){return t?(t.substr(0,2)==="{}"&&(t="\\{\\}"+t.substr(2)),Ob(f2t(t),!0).map(A2t)):[]}function h2t(t){return"{"+t+"}"}function g2t(t){return/^-?0\d/.test(t)}function d2t(t,e){return t<=e}function m2t(t,e){return t>=e}function Ob(t,e){var r=[],s=oPe("{","}",t);if(!s)return[t];var a=s.pre,n=s.post.length?Ob(s.post,!1):[""];if(/\$$/.test(s.pre))for(var c=0;c=0;if(!E&&!C)return s.post.match(/,.*\}/)?(t=s.pre+"{"+s.body+iJ+s.post,Ob(t)):[t];var S;if(E)S=s.body.split(/\.\./);else if(S=fPe(s.body),S.length===1&&(S=Ob(S[0],!1).map(h2t),S.length===1))return n.map(function(Ce){return s.pre+S[0]+Ce});var P;if(E){var I=nJ(S[0]),R=nJ(S[1]),N=Math.max(S[0].length,S[1].length),U=S.length==3?Math.abs(nJ(S[2])):1,W=d2t,ee=R0){var pe=new Array(me+1).join("0");ue<0?le="-"+pe+le.slice(1):le=pe+le}}P.push(le)}}else{P=[];for(var Be=0;Be{"use strict";Object.defineProperty(OO,"__esModule",{value:!0});OO.assertValidPattern=void 0;var y2t=1024*64,E2t=t=>{if(typeof t!="string")throw new TypeError("invalid pattern");if(t.length>y2t)throw new TypeError("pattern is too long")};OO.assertValidPattern=E2t});var dPe=_(LO=>{"use strict";Object.defineProperty(LO,"__esModule",{value:!0});LO.parseClass=void 0;var I2t={"[:alnum:]":["\\p{L}\\p{Nl}\\p{Nd}",!0],"[:alpha:]":["\\p{L}\\p{Nl}",!0],"[:ascii:]":["\\x00-\\x7f",!1],"[:blank:]":["\\p{Zs}\\t",!0],"[:cntrl:]":["\\p{Cc}",!0],"[:digit:]":["\\p{Nd}",!0],"[:graph:]":["\\p{Z}\\p{C}",!0,!0],"[:lower:]":["\\p{Ll}",!0],"[:print:]":["\\p{C}",!0],"[:punct:]":["\\p{P}",!0],"[:space:]":["\\p{Z}\\t\\r\\n\\v\\f",!0],"[:upper:]":["\\p{Lu}",!0],"[:word:]":["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}",!0],"[:xdigit:]":["A-Fa-f0-9",!1]},Lb=t=>t.replace(/[[\]\\-]/g,"\\$&"),C2t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),gPe=t=>t.join(""),w2t=(t,e)=>{let r=e;if(t.charAt(r)!=="[")throw new Error("not in a brace expression");let s=[],a=[],n=r+1,c=!1,f=!1,p=!1,h=!1,E=r,C="";e:for(;nC?s.push(Lb(C)+"-"+Lb(R)):R===C&&s.push(Lb(R)),C="",n++;continue}if(t.startsWith("-]",n+1)){s.push(Lb(R+"-")),n+=2;continue}if(t.startsWith("-",n+1)){C=R,n+=2;continue}s.push(Lb(R)),n++}if(E{"use strict";Object.defineProperty(MO,"__esModule",{value:!0});MO.unescape=void 0;var B2t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/\[([^\/\\])\]/g,"$1"):t.replace(/((?!\\).|^)\[([^\/\\])\]/g,"$1$2").replace(/\\([^\/])/g,"$1");MO.unescape=B2t});var aJ=_(jO=>{"use strict";Object.defineProperty(jO,"__esModule",{value:!0});jO.AST=void 0;var v2t=dPe(),_O=UO(),S2t=new Set(["!","?","+","*","@"]),mPe=t=>S2t.has(t),D2t="(?!(?:^|/)\\.\\.?(?:$|/))",HO="(?!\\.)",b2t=new Set(["[","."]),P2t=new Set(["..","."]),x2t=new Set("().*{}+?[]^$\\!"),k2t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),oJ="[^/]",yPe=oJ+"*?",EPe=oJ+"+?",sJ=class t{type;#t;#r;#i=!1;#e=[];#n;#o;#l;#a=!1;#s;#c;#f=!1;constructor(e,r,s={}){this.type=e,e&&(this.#r=!0),this.#n=r,this.#t=this.#n?this.#n.#t:this,this.#s=this.#t===this?s:this.#t.#s,this.#l=this.#t===this?[]:this.#t.#l,e==="!"&&!this.#t.#a&&this.#l.push(this),this.#o=this.#n?this.#n.#e.length:0}get hasMagic(){if(this.#r!==void 0)return this.#r;for(let e of this.#e)if(typeof e!="string"&&(e.type||e.hasMagic))return this.#r=!0;return this.#r}toString(){return this.#c!==void 0?this.#c:this.type?this.#c=this.type+"("+this.#e.map(e=>String(e)).join("|")+")":this.#c=this.#e.map(e=>String(e)).join("")}#p(){if(this!==this.#t)throw new Error("should only call on root");if(this.#a)return this;this.toString(),this.#a=!0;let e;for(;e=this.#l.pop();){if(e.type!=="!")continue;let r=e,s=r.#n;for(;s;){for(let a=r.#o+1;!s.type&&atypeof r=="string"?r:r.toJSON()):[this.type,...this.#e.map(r=>r.toJSON())];return this.isStart()&&!this.type&&e.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#a&&this.#n?.type==="!")&&e.push({}),e}isStart(){if(this.#t===this)return!0;if(!this.#n?.isStart())return!1;if(this.#o===0)return!0;let e=this.#n;for(let r=0;r{let[I,R,N,U]=typeof P=="string"?t.#h(P,this.#r,p):P.toRegExpSource(e);return this.#r=this.#r||N,this.#i=this.#i||U,I}).join(""),E="";if(this.isStart()&&typeof this.#e[0]=="string"&&!(this.#e.length===1&&P2t.has(this.#e[0]))){let I=b2t,R=r&&I.has(h.charAt(0))||h.startsWith("\\.")&&I.has(h.charAt(2))||h.startsWith("\\.\\.")&&I.has(h.charAt(4)),N=!r&&!e&&I.has(h.charAt(0));E=R?D2t:N?HO:""}let C="";return this.isEnd()&&this.#t.#a&&this.#n?.type==="!"&&(C="(?:$|\\/)"),[E+h+C,(0,_O.unescape)(h),this.#r=!!this.#r,this.#i]}let s=this.type==="*"||this.type==="+",a=this.type==="!"?"(?:(?!(?:":"(?:",n=this.#A(r);if(this.isStart()&&this.isEnd()&&!n&&this.type!=="!"){let p=this.toString();return this.#e=[p],this.type=null,this.#r=void 0,[p,(0,_O.unescape)(this.toString()),!1,!1]}let c=!s||e||r||!HO?"":this.#A(!0);c===n&&(c=""),c&&(n=`(?:${n})(?:${c})*?`);let f="";if(this.type==="!"&&this.#f)f=(this.isStart()&&!r?HO:"")+EPe;else{let p=this.type==="!"?"))"+(this.isStart()&&!r&&!e?HO:"")+yPe+")":this.type==="@"?")":this.type==="?"?")?":this.type==="+"&&c?")":this.type==="*"&&c?")?":`)${this.type}`;f=a+n+p}return[f,(0,_O.unescape)(n),this.#r=!!this.#r,this.#i]}#A(e){return this.#e.map(r=>{if(typeof r=="string")throw new Error("string type in extglob ast??");let[s,a,n,c]=r.toRegExpSource(e);return this.#i=this.#i||c,s}).filter(r=>!(this.isStart()&&this.isEnd())||!!r).join("|")}static#h(e,r,s=!1){let a=!1,n="",c=!1;for(let f=0;f{"use strict";Object.defineProperty(GO,"__esModule",{value:!0});GO.escape=void 0;var Q2t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/[?*()[\]]/g,"[$&]"):t.replace(/[?*()[\]\\]/g,"\\$&");GO.escape=Q2t});var DPe=_(pr=>{"use strict";var T2t=pr&&pr.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pr,"__esModule",{value:!0});pr.unescape=pr.escape=pr.AST=pr.Minimatch=pr.match=pr.makeRe=pr.braceExpand=pr.defaults=pr.filter=pr.GLOBSTAR=pr.sep=pr.minimatch=void 0;var R2t=T2t(pPe()),qO=hPe(),wPe=aJ(),F2t=lJ(),N2t=UO(),O2t=(t,e,r={})=>((0,qO.assertValidPattern)(e),!r.nocomment&&e.charAt(0)==="#"?!1:new ly(e,r).match(t));pr.minimatch=O2t;var L2t=/^\*+([^+@!?\*\[\(]*)$/,M2t=t=>e=>!e.startsWith(".")&&e.endsWith(t),U2t=t=>e=>e.endsWith(t),_2t=t=>(t=t.toLowerCase(),e=>!e.startsWith(".")&&e.toLowerCase().endsWith(t)),H2t=t=>(t=t.toLowerCase(),e=>e.toLowerCase().endsWith(t)),j2t=/^\*+\.\*+$/,G2t=t=>!t.startsWith(".")&&t.includes("."),q2t=t=>t!=="."&&t!==".."&&t.includes("."),W2t=/^\.\*+$/,Y2t=t=>t!=="."&&t!==".."&&t.startsWith("."),V2t=/^\*+$/,J2t=t=>t.length!==0&&!t.startsWith("."),K2t=t=>t.length!==0&&t!=="."&&t!=="..",z2t=/^\?+([^+@!?\*\[\(]*)?$/,X2t=([t,e=""])=>{let r=BPe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},Z2t=([t,e=""])=>{let r=vPe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},$2t=([t,e=""])=>{let r=vPe([t]);return e?s=>r(s)&&s.endsWith(e):r},eBt=([t,e=""])=>{let r=BPe([t]);return e?s=>r(s)&&s.endsWith(e):r},BPe=([t])=>{let e=t.length;return r=>r.length===e&&!r.startsWith(".")},vPe=([t])=>{let e=t.length;return r=>r.length===e&&r!=="."&&r!==".."},SPe=typeof process=="object"&&process?typeof process.env=="object"&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:"posix",IPe={win32:{sep:"\\"},posix:{sep:"/"}};pr.sep=SPe==="win32"?IPe.win32.sep:IPe.posix.sep;pr.minimatch.sep=pr.sep;pr.GLOBSTAR=Symbol("globstar **");pr.minimatch.GLOBSTAR=pr.GLOBSTAR;var tBt="[^/]",rBt=tBt+"*?",nBt="(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?",iBt="(?:(?!(?:\\/|^)\\.).)*?",sBt=(t,e={})=>r=>(0,pr.minimatch)(r,t,e);pr.filter=sBt;pr.minimatch.filter=pr.filter;var tu=(t,e={})=>Object.assign({},t,e),oBt=t=>{if(!t||typeof t!="object"||!Object.keys(t).length)return pr.minimatch;let e=pr.minimatch;return Object.assign((s,a,n={})=>e(s,a,tu(t,n)),{Minimatch:class extends e.Minimatch{constructor(a,n={}){super(a,tu(t,n))}static defaults(a){return e.defaults(tu(t,a)).Minimatch}},AST:class extends e.AST{constructor(a,n,c={}){super(a,n,tu(t,c))}static fromGlob(a,n={}){return e.AST.fromGlob(a,tu(t,n))}},unescape:(s,a={})=>e.unescape(s,tu(t,a)),escape:(s,a={})=>e.escape(s,tu(t,a)),filter:(s,a={})=>e.filter(s,tu(t,a)),defaults:s=>e.defaults(tu(t,s)),makeRe:(s,a={})=>e.makeRe(s,tu(t,a)),braceExpand:(s,a={})=>e.braceExpand(s,tu(t,a)),match:(s,a,n={})=>e.match(s,a,tu(t,n)),sep:e.sep,GLOBSTAR:pr.GLOBSTAR})};pr.defaults=oBt;pr.minimatch.defaults=pr.defaults;var aBt=(t,e={})=>((0,qO.assertValidPattern)(t),e.nobrace||!/\{(?:(?!\{).)*\}/.test(t)?[t]:(0,R2t.default)(t));pr.braceExpand=aBt;pr.minimatch.braceExpand=pr.braceExpand;var lBt=(t,e={})=>new ly(t,e).makeRe();pr.makeRe=lBt;pr.minimatch.makeRe=pr.makeRe;var cBt=(t,e,r={})=>{let s=new ly(e,r);return t=t.filter(a=>s.match(a)),s.options.nonull&&!t.length&&t.push(e),t};pr.match=cBt;pr.minimatch.match=pr.match;var CPe=/[?*]|[+@!]\(.*?\)|\[|\]/,uBt=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),ly=class{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(e,r={}){(0,qO.assertValidPattern)(e),r=r||{},this.options=r,this.pattern=e,this.platform=r.platform||SPe,this.isWindows=this.platform==="win32",this.windowsPathsNoEscape=!!r.windowsPathsNoEscape||r.allowWindowsEscape===!1,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.preserveMultipleSlashes=!!r.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!r.nonegate,this.comment=!1,this.empty=!1,this.partial=!!r.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=r.windowsNoMagicRoot!==void 0?r.windowsNoMagicRoot:!!(this.isWindows&&this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(let e of this.set)for(let r of e)if(typeof r!="string")return!0;return!1}debug(...e){}make(){let e=this.pattern,r=this.options;if(!r.nocomment&&e.charAt(0)==="#"){this.comment=!0;return}if(!e){this.empty=!0;return}this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],r.debug&&(this.debug=(...n)=>console.error(...n)),this.debug(this.pattern,this.globSet);let s=this.globSet.map(n=>this.slashSplit(n));this.globParts=this.preprocess(s),this.debug(this.pattern,this.globParts);let a=this.globParts.map((n,c,f)=>{if(this.isWindows&&this.windowsNoMagicRoot){let p=n[0]===""&&n[1]===""&&(n[2]==="?"||!CPe.test(n[2]))&&!CPe.test(n[3]),h=/^[a-z]:/i.test(n[0]);if(p)return[...n.slice(0,4),...n.slice(4).map(E=>this.parse(E))];if(h)return[n[0],...n.slice(1).map(E=>this.parse(E))]}return n.map(p=>this.parse(p))});if(this.debug(this.pattern,a),this.set=a.filter(n=>n.indexOf(!1)===-1),this.isWindows)for(let n=0;n=2?(e=this.firstPhasePreProcess(e),e=this.secondPhasePreProcess(e)):r>=1?e=this.levelOneOptimize(e):e=this.adjascentGlobstarOptimize(e),e}adjascentGlobstarOptimize(e){return e.map(r=>{let s=-1;for(;(s=r.indexOf("**",s+1))!==-1;){let a=s;for(;r[a+1]==="**";)a++;a!==s&&r.splice(s,a-s)}return r})}levelOneOptimize(e){return e.map(r=>(r=r.reduce((s,a)=>{let n=s[s.length-1];return a==="**"&&n==="**"?s:a===".."&&n&&n!==".."&&n!=="."&&n!=="**"?(s.pop(),s):(s.push(a),s)},[]),r.length===0?[""]:r))}levelTwoFileOptimize(e){Array.isArray(e)||(e=this.slashSplit(e));let r=!1;do{if(r=!1,!this.preserveMultipleSlashes){for(let a=1;aa&&s.splice(a+1,c-a);let f=s[a+1],p=s[a+2],h=s[a+3];if(f!==".."||!p||p==="."||p===".."||!h||h==="."||h==="..")continue;r=!0,s.splice(a,1);let E=s.slice(0);E[a]="**",e.push(E),a--}if(!this.preserveMultipleSlashes){for(let c=1;cr.length)}partsMatch(e,r,s=!1){let a=0,n=0,c=[],f="";for(;aee?r=r.slice(ie):ee>ie&&(e=e.slice(ee)))}}let{optimizationLevel:n=1}=this.options;n>=2&&(e=this.levelTwoFileOptimize(e)),this.debug("matchOne",this,{file:e,pattern:r}),this.debug("matchOne",e.length,r.length);for(var c=0,f=0,p=e.length,h=r.length;c>> no match, partial?`,e,S,r,P),S===p))}let R;if(typeof E=="string"?(R=C===E,this.debug("string match",E,C,R)):(R=E.test(C),this.debug("pattern match",E,C,R)),!R)return!1}if(c===p&&f===h)return!0;if(c===p)return s;if(f===h)return c===p-1&&e[c]==="";throw new Error("wtf?")}braceExpand(){return(0,pr.braceExpand)(this.pattern,this.options)}parse(e){(0,qO.assertValidPattern)(e);let r=this.options;if(e==="**")return pr.GLOBSTAR;if(e==="")return"";let s,a=null;(s=e.match(V2t))?a=r.dot?K2t:J2t:(s=e.match(L2t))?a=(r.nocase?r.dot?H2t:_2t:r.dot?U2t:M2t)(s[1]):(s=e.match(z2t))?a=(r.nocase?r.dot?Z2t:X2t:r.dot?$2t:eBt)(s):(s=e.match(j2t))?a=r.dot?q2t:G2t:(s=e.match(W2t))&&(a=Y2t);let n=wPe.AST.fromGlob(e,this.options).toMMPattern();return a&&typeof n=="object"&&Reflect.defineProperty(n,"test",{value:a}),n}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let r=this.options,s=r.noglobstar?rBt:r.dot?nBt:iBt,a=new Set(r.nocase?["i"]:[]),n=e.map(p=>{let h=p.map(E=>{if(E instanceof RegExp)for(let C of E.flags.split(""))a.add(C);return typeof E=="string"?uBt(E):E===pr.GLOBSTAR?pr.GLOBSTAR:E._src});return h.forEach((E,C)=>{let S=h[C+1],P=h[C-1];E!==pr.GLOBSTAR||P===pr.GLOBSTAR||(P===void 0?S!==void 0&&S!==pr.GLOBSTAR?h[C+1]="(?:\\/|"+s+"\\/)?"+S:h[C]=s:S===void 0?h[C-1]=P+"(?:\\/|"+s+")?":S!==pr.GLOBSTAR&&(h[C-1]=P+"(?:\\/|\\/"+s+"\\/)"+S,h[C+1]=pr.GLOBSTAR))}),h.filter(E=>E!==pr.GLOBSTAR).join("/")}).join("|"),[c,f]=e.length>1?["(?:",")"]:["",""];n="^"+c+n+f+"$",this.negate&&(n="^(?!"+n+").+$");try{this.regexp=new RegExp(n,[...a].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(e)?["",...e.split(/\/+/)]:e.split(/\/+/)}match(e,r=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return e==="";if(e==="/"&&r)return!0;let s=this.options;this.isWindows&&(e=e.split("\\").join("/"));let a=this.slashSplit(e);this.debug(this.pattern,"split",a);let n=this.set;this.debug(this.pattern,"set",n);let c=a[a.length-1];if(!c)for(let f=a.length-2;!c&&f>=0;f--)c=a[f];for(let f=0;f{"use strict";var bPe=ru&&ru.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ru,"__esModule",{value:!0});ru.SuccinctRoles=ru.DelegatedRole=ru.Role=ru.TOP_LEVEL_ROLE_NAMES=void 0;var PPe=bPe(Ie("crypto")),hBt=DPe(),WO=bPe(Ie("util")),YO=PA(),cy=ff();ru.TOP_LEVEL_ROLE_NAMES=["root","targets","snapshot","timestamp"];var Mb=class t{constructor(e){let{keyIDs:r,threshold:s,unrecognizedFields:a}=e;if(gBt(r))throw new YO.ValueError("duplicate key IDs found");if(s<1)throw new YO.ValueError("threshold must be at least 1");this.keyIDs=r,this.threshold=s,this.unrecognizedFields=a||{}}equals(e){return e instanceof t?this.threshold===e.threshold&&WO.default.isDeepStrictEqual(this.keyIDs,e.keyIDs)&&WO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keyids:this.keyIDs,threshold:this.threshold,...this.unrecognizedFields}}static fromJSON(e){let{keyids:r,threshold:s,...a}=e;if(!cy.guard.isStringArray(r))throw new TypeError("keyids must be an array");if(typeof s!="number")throw new TypeError("threshold must be a number");return new t({keyIDs:r,threshold:s,unrecognizedFields:a})}};ru.Role=Mb;function gBt(t){return new Set(t).size!==t.length}var cJ=class t extends Mb{constructor(e){super(e);let{name:r,terminating:s,paths:a,pathHashPrefixes:n}=e;if(this.name=r,this.terminating=s,e.paths&&e.pathHashPrefixes)throw new YO.ValueError("paths and pathHashPrefixes are mutually exclusive");this.paths=a,this.pathHashPrefixes=n}equals(e){return e instanceof t?super.equals(e)&&this.name===e.name&&this.terminating===e.terminating&&WO.default.isDeepStrictEqual(this.paths,e.paths)&&WO.default.isDeepStrictEqual(this.pathHashPrefixes,e.pathHashPrefixes):!1}isDelegatedPath(e){if(this.paths)return this.paths.some(r=>mBt(e,r));if(this.pathHashPrefixes){let s=PPe.default.createHash("sha256").update(e).digest("hex");return this.pathHashPrefixes.some(a=>s.startsWith(a))}return!1}toJSON(){let e={...super.toJSON(),name:this.name,terminating:this.terminating};return this.paths&&(e.paths=this.paths),this.pathHashPrefixes&&(e.path_hash_prefixes=this.pathHashPrefixes),e}static fromJSON(e){let{keyids:r,threshold:s,name:a,terminating:n,paths:c,path_hash_prefixes:f,...p}=e;if(!cy.guard.isStringArray(r))throw new TypeError("keyids must be an array of strings");if(typeof s!="number")throw new TypeError("threshold must be a number");if(typeof a!="string")throw new TypeError("name must be a string");if(typeof n!="boolean")throw new TypeError("terminating must be a boolean");if(cy.guard.isDefined(c)&&!cy.guard.isStringArray(c))throw new TypeError("paths must be an array of strings");if(cy.guard.isDefined(f)&&!cy.guard.isStringArray(f))throw new TypeError("path_hash_prefixes must be an array of strings");return new t({keyIDs:r,threshold:s,name:a,terminating:n,paths:c,pathHashPrefixes:f,unrecognizedFields:p})}};ru.DelegatedRole=cJ;var dBt=(t,e)=>t.map((r,s)=>[r,e[s]]);function mBt(t,e){let r=t.split("/"),s=e.split("/");return s.length!=r.length?!1:dBt(r,s).every(([a,n])=>(0,hBt.minimatch)(a,n))}var uJ=class t extends Mb{constructor(e){super(e);let{bitLength:r,namePrefix:s}=e;if(r<=0||r>32)throw new YO.ValueError("bitLength must be between 1 and 32");this.bitLength=r,this.namePrefix=s,this.numberOfBins=Math.pow(2,r),this.suffixLen=(this.numberOfBins-1).toString(16).length}equals(e){return e instanceof t?super.equals(e)&&this.bitLength===e.bitLength&&this.namePrefix===e.namePrefix:!1}getRoleForTarget(e){let a=PPe.default.createHash("sha256").update(e).digest().subarray(0,4),n=32-this.bitLength,f=(a.readUInt32BE()>>>n).toString(16).padStart(this.suffixLen,"0");return`${this.namePrefix}-${f}`}*getRoles(){for(let e=0;e{"use strict";var yBt=a1&&a1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(a1,"__esModule",{value:!0});a1.Root=void 0;var xPe=yBt(Ie("util")),AJ=ay(),kPe=PA(),EBt=NO(),VO=fJ(),JO=ff(),pJ=class t extends AJ.Signed{constructor(e){if(super(e),this.type=AJ.MetadataKind.Root,this.keys=e.keys||{},this.consistentSnapshot=e.consistentSnapshot??!0,!e.roles)this.roles=VO.TOP_LEVEL_ROLE_NAMES.reduce((r,s)=>({...r,[s]:new VO.Role({keyIDs:[],threshold:1})}),{});else{let r=new Set(Object.keys(e.roles));if(!VO.TOP_LEVEL_ROLE_NAMES.every(s=>r.has(s)))throw new kPe.ValueError("missing top-level role");this.roles=e.roles}}addKey(e,r){if(!this.roles[r])throw new kPe.ValueError(`role ${r} does not exist`);this.roles[r].keyIDs.includes(e.keyID)||this.roles[r].keyIDs.push(e.keyID),this.keys[e.keyID]=e}equals(e){return e instanceof t?super.equals(e)&&this.consistentSnapshot===e.consistentSnapshot&&xPe.default.isDeepStrictEqual(this.keys,e.keys)&&xPe.default.isDeepStrictEqual(this.roles,e.roles):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,keys:IBt(this.keys),roles:CBt(this.roles),consistent_snapshot:this.consistentSnapshot,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=AJ.Signed.commonFieldsFromJSON(e),{keys:a,roles:n,consistent_snapshot:c,...f}=r;if(typeof c!="boolean")throw new TypeError("consistent_snapshot must be a boolean");return new t({...s,keys:wBt(a),roles:BBt(n),consistentSnapshot:c,unrecognizedFields:f})}};a1.Root=pJ;function IBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function CBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function wBt(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError("keys must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:EBt.Key.fromJSON(s,a)}),{})}return e}function BBt(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError("roles must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:VO.Role.fromJSON(a)}),{})}return e}});var dJ=_(KO=>{"use strict";Object.defineProperty(KO,"__esModule",{value:!0});KO.Signature=void 0;var gJ=class t{constructor(e){let{keyID:r,sig:s}=e;this.keyID=r,this.sig=s}toJSON(){return{keyid:this.keyID,sig:this.sig}}static fromJSON(e){let{keyid:r,sig:s}=e;if(typeof r!="string")throw new TypeError("keyid must be a string");if(typeof s!="string")throw new TypeError("sig must be a string");return new t({keyID:r,sig:s})}};KO.Signature=gJ});var EJ=_(l1=>{"use strict";var vBt=l1&&l1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(l1,"__esModule",{value:!0});l1.Snapshot=void 0;var SBt=vBt(Ie("util")),mJ=ay(),TPe=Tb(),QPe=ff(),yJ=class t extends mJ.Signed{constructor(e){super(e),this.type=mJ.MetadataKind.Snapshot,this.meta=e.meta||{"targets.json":new TPe.MetaFile({version:1})}}equals(e){return e instanceof t?super.equals(e)&&SBt.default.isDeepStrictEqual(this.meta,e.meta):!1}toJSON(){return{_type:this.type,meta:DBt(this.meta),spec_version:this.specVersion,version:this.version,expires:this.expires,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=mJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,meta:bBt(a),unrecognizedFields:n})}};l1.Snapshot=yJ;function DBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function bBt(t){let e;if(QPe.guard.isDefined(t))if(QPe.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:TPe.MetaFile.fromJSON(a)}),{});else throw new TypeError("meta field is malformed");return e}});var RPe=_(c1=>{"use strict";var PBt=c1&&c1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(c1,"__esModule",{value:!0});c1.Delegations=void 0;var zO=PBt(Ie("util")),xBt=PA(),kBt=NO(),IJ=fJ(),XO=ff(),CJ=class t{constructor(e){if(this.keys=e.keys,this.unrecognizedFields=e.unrecognizedFields||{},e.roles&&Object.keys(e.roles).some(r=>IJ.TOP_LEVEL_ROLE_NAMES.includes(r)))throw new xBt.ValueError("Delegated role name conflicts with top-level role name");this.succinctRoles=e.succinctRoles,this.roles=e.roles}equals(e){return e instanceof t?zO.default.isDeepStrictEqual(this.keys,e.keys)&&zO.default.isDeepStrictEqual(this.roles,e.roles)&&zO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields)&&zO.default.isDeepStrictEqual(this.succinctRoles,e.succinctRoles):!1}*rolesForTarget(e){if(this.roles)for(let r of Object.values(this.roles))r.isDelegatedPath(e)&&(yield{role:r.name,terminating:r.terminating});else this.succinctRoles&&(yield{role:this.succinctRoles.getRoleForTarget(e),terminating:!0})}toJSON(){let e={keys:QBt(this.keys),...this.unrecognizedFields};return this.roles?e.roles=TBt(this.roles):this.succinctRoles&&(e.succinct_roles=this.succinctRoles.toJSON()),e}static fromJSON(e){let{keys:r,roles:s,succinct_roles:a,...n}=e,c;return XO.guard.isObject(a)&&(c=IJ.SuccinctRoles.fromJSON(a)),new t({keys:RBt(r),roles:FBt(s),unrecognizedFields:n,succinctRoles:c})}};c1.Delegations=CJ;function QBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function TBt(t){return Object.values(t).map(e=>e.toJSON())}function RBt(t){if(!XO.guard.isObjectRecord(t))throw new TypeError("keys is malformed");return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:kBt.Key.fromJSON(r,s)}),{})}function FBt(t){let e;if(XO.guard.isDefined(t)){if(!XO.guard.isObjectArray(t))throw new TypeError("roles is malformed");e=t.reduce((r,s)=>{let a=IJ.DelegatedRole.fromJSON(s);return{...r,[a.name]:a}},{})}return e}});var vJ=_(u1=>{"use strict";var NBt=u1&&u1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(u1,"__esModule",{value:!0});u1.Targets=void 0;var FPe=NBt(Ie("util")),wJ=ay(),OBt=RPe(),LBt=Tb(),ZO=ff(),BJ=class t extends wJ.Signed{constructor(e){super(e),this.type=wJ.MetadataKind.Targets,this.targets=e.targets||{},this.delegations=e.delegations}addTarget(e){this.targets[e.path]=e}equals(e){return e instanceof t?super.equals(e)&&FPe.default.isDeepStrictEqual(this.targets,e.targets)&&FPe.default.isDeepStrictEqual(this.delegations,e.delegations):!1}toJSON(){let e={_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,targets:MBt(this.targets),...this.unrecognizedFields};return this.delegations&&(e.delegations=this.delegations.toJSON()),e}static fromJSON(e){let{unrecognizedFields:r,...s}=wJ.Signed.commonFieldsFromJSON(e),{targets:a,delegations:n,...c}=r;return new t({...s,targets:UBt(a),delegations:_Bt(n),unrecognizedFields:c})}};u1.Targets=BJ;function MBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function UBt(t){let e;if(ZO.guard.isDefined(t))if(ZO.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:LBt.TargetFile.fromJSON(s,a)}),{});else throw new TypeError("targets must be an object");return e}function _Bt(t){let e;if(ZO.guard.isDefined(t))if(ZO.guard.isObject(t))e=OBt.Delegations.fromJSON(t);else throw new TypeError("delegations must be an object");return e}});var PJ=_($O=>{"use strict";Object.defineProperty($O,"__esModule",{value:!0});$O.Timestamp=void 0;var SJ=ay(),NPe=Tb(),DJ=ff(),bJ=class t extends SJ.Signed{constructor(e){super(e),this.type=SJ.MetadataKind.Timestamp,this.snapshotMeta=e.snapshotMeta||new NPe.MetaFile({version:1})}equals(e){return e instanceof t?super.equals(e)&&this.snapshotMeta.equals(e.snapshotMeta):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,meta:{"snapshot.json":this.snapshotMeta.toJSON()},...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=SJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,snapshotMeta:HBt(a),unrecognizedFields:n})}};$O.Timestamp=bJ;function HBt(t){let e;if(DJ.guard.isDefined(t)){let r=t["snapshot.json"];if(!DJ.guard.isDefined(r)||!DJ.guard.isObject(r))throw new TypeError("missing snapshot.json in meta");e=NPe.MetaFile.fromJSON(r)}return e}});var LPe=_(A1=>{"use strict";var jBt=A1&&A1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(A1,"__esModule",{value:!0});A1.Metadata=void 0;var GBt=V7(),OPe=jBt(Ie("util")),f1=ay(),Ub=PA(),qBt=hJ(),WBt=dJ(),YBt=EJ(),VBt=vJ(),JBt=PJ(),xJ=ff(),kJ=class t{constructor(e,r,s){this.signed=e,this.signatures=r||{},this.unrecognizedFields=s||{}}sign(e,r=!0){let s=Buffer.from((0,GBt.canonicalize)(this.signed.toJSON())),a=e(s);r||(this.signatures={}),this.signatures[a.keyID]=a}verifyDelegate(e,r){let s,a={};switch(this.signed.type){case f1.MetadataKind.Root:a=this.signed.keys,s=this.signed.roles[e];break;case f1.MetadataKind.Targets:if(!this.signed.delegations)throw new Ub.ValueError(`No delegations found for ${e}`);a=this.signed.delegations.keys,this.signed.delegations.roles?s=this.signed.delegations.roles[e]:this.signed.delegations.succinctRoles&&this.signed.delegations.succinctRoles.isDelegatedRole(e)&&(s=this.signed.delegations.succinctRoles);break;default:throw new TypeError("invalid metadata type")}if(!s)throw new Ub.ValueError(`no delegation found for ${e}`);let n=new Set;if(s.keyIDs.forEach(c=>{let f=a[c];if(f)try{f.verifySignature(r),n.add(f.keyID)}catch{}}),n.sizer.toJSON()),signed:this.signed.toJSON(),...this.unrecognizedFields}}static fromJSON(e,r){let{signed:s,signatures:a,...n}=r;if(!xJ.guard.isDefined(s)||!xJ.guard.isObject(s))throw new TypeError("signed is not defined");if(e!==s._type)throw new Ub.ValueError(`expected '${e}', got ${s._type}`);if(!xJ.guard.isObjectArray(a))throw new TypeError("signatures is not an array");let c;switch(e){case f1.MetadataKind.Root:c=qBt.Root.fromJSON(s);break;case f1.MetadataKind.Timestamp:c=JBt.Timestamp.fromJSON(s);break;case f1.MetadataKind.Snapshot:c=YBt.Snapshot.fromJSON(s);break;case f1.MetadataKind.Targets:c=VBt.Targets.fromJSON(s);break;default:throw new TypeError("invalid metadata type")}let f={};return a.forEach(p=>{let h=WBt.Signature.fromJSON(p);if(f[h.keyID])throw new Ub.ValueError(`multiple signatures found for keyid: ${h.keyID}`);f[h.keyID]=h}),new t(c,f,n)}};A1.Metadata=kJ});var eL=_(Fi=>{"use strict";Object.defineProperty(Fi,"__esModule",{value:!0});Fi.Timestamp=Fi.Targets=Fi.Snapshot=Fi.Signature=Fi.Root=Fi.Metadata=Fi.Key=Fi.TargetFile=Fi.MetaFile=Fi.ValueError=Fi.MetadataKind=void 0;var KBt=ay();Object.defineProperty(Fi,"MetadataKind",{enumerable:!0,get:function(){return KBt.MetadataKind}});var zBt=PA();Object.defineProperty(Fi,"ValueError",{enumerable:!0,get:function(){return zBt.ValueError}});var MPe=Tb();Object.defineProperty(Fi,"MetaFile",{enumerable:!0,get:function(){return MPe.MetaFile}});Object.defineProperty(Fi,"TargetFile",{enumerable:!0,get:function(){return MPe.TargetFile}});var XBt=NO();Object.defineProperty(Fi,"Key",{enumerable:!0,get:function(){return XBt.Key}});var ZBt=LPe();Object.defineProperty(Fi,"Metadata",{enumerable:!0,get:function(){return ZBt.Metadata}});var $Bt=hJ();Object.defineProperty(Fi,"Root",{enumerable:!0,get:function(){return $Bt.Root}});var evt=dJ();Object.defineProperty(Fi,"Signature",{enumerable:!0,get:function(){return evt.Signature}});var tvt=EJ();Object.defineProperty(Fi,"Snapshot",{enumerable:!0,get:function(){return tvt.Snapshot}});var rvt=vJ();Object.defineProperty(Fi,"Targets",{enumerable:!0,get:function(){return rvt.Targets}});var nvt=PJ();Object.defineProperty(Fi,"Timestamp",{enumerable:!0,get:function(){return nvt.Timestamp}})});var _Pe=_((Dir,UPe)=>{var p1=1e3,h1=p1*60,g1=h1*60,uy=g1*24,ivt=uy*7,svt=uy*365.25;UPe.exports=function(t,e){e=e||{};var r=typeof t;if(r==="string"&&t.length>0)return ovt(t);if(r==="number"&&isFinite(t))return e.long?lvt(t):avt(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))};function ovt(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),s=(e[2]||"ms").toLowerCase();switch(s){case"years":case"year":case"yrs":case"yr":case"y":return r*svt;case"weeks":case"week":case"w":return r*ivt;case"days":case"day":case"d":return r*uy;case"hours":case"hour":case"hrs":case"hr":case"h":return r*g1;case"minutes":case"minute":case"mins":case"min":case"m":return r*h1;case"seconds":case"second":case"secs":case"sec":case"s":return r*p1;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}function avt(t){var e=Math.abs(t);return e>=uy?Math.round(t/uy)+"d":e>=g1?Math.round(t/g1)+"h":e>=h1?Math.round(t/h1)+"m":e>=p1?Math.round(t/p1)+"s":t+"ms"}function lvt(t){var e=Math.abs(t);return e>=uy?tL(t,e,uy,"day"):e>=g1?tL(t,e,g1,"hour"):e>=h1?tL(t,e,h1,"minute"):e>=p1?tL(t,e,p1,"second"):t+" ms"}function tL(t,e,r,s){var a=e>=r*1.5;return Math.round(t/r)+" "+s+(a?"s":"")}});var QJ=_((bir,HPe)=>{function cvt(t){r.debug=r,r.default=r,r.coerce=p,r.disable=c,r.enable=a,r.enabled=f,r.humanize=_Pe(),r.destroy=h,Object.keys(t).forEach(E=>{r[E]=t[E]}),r.names=[],r.skips=[],r.formatters={};function e(E){let C=0;for(let S=0;S{if(le==="%%")return"%";ie++;let pe=r.formatters[me];if(typeof pe=="function"){let Be=N[ie];le=pe.call(U,Be),N.splice(ie,1),ie--}return le}),r.formatArgs.call(U,N),(U.log||r.log).apply(U,N)}return R.namespace=E,R.useColors=r.useColors(),R.color=r.selectColor(E),R.extend=s,R.destroy=r.destroy,Object.defineProperty(R,"enabled",{enumerable:!0,configurable:!1,get:()=>S!==null?S:(P!==r.namespaces&&(P=r.namespaces,I=r.enabled(E)),I),set:N=>{S=N}}),typeof r.init=="function"&&r.init(R),R}function s(E,C){let S=r(this.namespace+(typeof C>"u"?":":C)+E);return S.log=this.log,S}function a(E){r.save(E),r.namespaces=E,r.names=[],r.skips=[];let C=(typeof E=="string"?E:"").trim().replace(" ",",").split(",").filter(Boolean);for(let S of C)S[0]==="-"?r.skips.push(S.slice(1)):r.names.push(S)}function n(E,C){let S=0,P=0,I=-1,R=0;for(;S"-"+C)].join(",");return r.enable(""),E}function f(E){for(let C of r.skips)if(n(E,C))return!1;for(let C of r.names)if(n(E,C))return!0;return!1}function p(E){return E instanceof Error?E.stack||E.message:E}function h(){console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.")}return r.enable(r.load()),r}HPe.exports=cvt});var jPe=_((sc,rL)=>{sc.formatArgs=fvt;sc.save=Avt;sc.load=pvt;sc.useColors=uvt;sc.storage=hvt();sc.destroy=(()=>{let t=!1;return()=>{t||(t=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})();sc.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function uvt(){if(typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs))return!0;if(typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;let t;return typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&(t=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/))&&parseInt(t[1],10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function fvt(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+rL.exports.humanize(this.diff),!this.useColors)return;let e="color: "+this.color;t.splice(1,0,e,"color: inherit");let r=0,s=0;t[0].replace(/%[a-zA-Z%]/g,a=>{a!=="%%"&&(r++,a==="%c"&&(s=r))}),t.splice(s,0,e)}sc.log=console.debug||console.log||(()=>{});function Avt(t){try{t?sc.storage.setItem("debug",t):sc.storage.removeItem("debug")}catch{}}function pvt(){let t;try{t=sc.storage.getItem("debug")}catch{}return!t&&typeof process<"u"&&"env"in process&&(t=process.env.DEBUG),t}function hvt(){try{return localStorage}catch{}}rL.exports=QJ()(sc);var{formatters:gvt}=rL.exports;gvt.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}});var qPe=_((Zs,iL)=>{var dvt=Ie("tty"),nL=Ie("util");Zs.init=Bvt;Zs.log=Ivt;Zs.formatArgs=yvt;Zs.save=Cvt;Zs.load=wvt;Zs.useColors=mvt;Zs.destroy=nL.deprecate(()=>{},"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");Zs.colors=[6,2,3,4,5,1];try{let t=Ie("supports-color");t&&(t.stderr||t).level>=2&&(Zs.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch{}Zs.inspectOpts=Object.keys(process.env).filter(t=>/^debug_/i.test(t)).reduce((t,e)=>{let r=e.substring(6).toLowerCase().replace(/_([a-z])/g,(a,n)=>n.toUpperCase()),s=process.env[e];return/^(yes|on|true|enabled)$/i.test(s)?s=!0:/^(no|off|false|disabled)$/i.test(s)?s=!1:s==="null"?s=null:s=Number(s),t[r]=s,t},{});function mvt(){return"colors"in Zs.inspectOpts?!!Zs.inspectOpts.colors:dvt.isatty(process.stderr.fd)}function yvt(t){let{namespace:e,useColors:r}=this;if(r){let s=this.color,a="\x1B[3"+(s<8?s:"8;5;"+s),n=` ${a};1m${e} \x1B[0m`;t[0]=n+t[0].split(` +`).join(` +`+n),t.push(a+"m+"+iL.exports.humanize(this.diff)+"\x1B[0m")}else t[0]=Evt()+e+" "+t[0]}function Evt(){return Zs.inspectOpts.hideDate?"":new Date().toISOString()+" "}function Ivt(...t){return process.stderr.write(nL.formatWithOptions(Zs.inspectOpts,...t)+` +`)}function Cvt(t){t?process.env.DEBUG=t:delete process.env.DEBUG}function wvt(){return process.env.DEBUG}function Bvt(t){t.inspectOpts={};let e=Object.keys(Zs.inspectOpts);for(let r=0;re.trim()).join(" ")};GPe.O=function(t){return this.inspectOpts.colors=this.useColors,nL.inspect(t,this.inspectOpts)}});var RJ=_((Pir,TJ)=>{typeof process>"u"||process.type==="renderer"||process.browser===!0||process.__nwjs?TJ.exports=jPe():TJ.exports=qPe()});var oL=_(Ki=>{"use strict";Object.defineProperty(Ki,"__esModule",{value:!0});Ki.DownloadHTTPError=Ki.DownloadLengthMismatchError=Ki.DownloadError=Ki.ExpiredMetadataError=Ki.EqualVersionError=Ki.BadVersionError=Ki.RepositoryError=Ki.PersistError=Ki.RuntimeError=Ki.ValueError=void 0;var FJ=class extends Error{};Ki.ValueError=FJ;var NJ=class extends Error{};Ki.RuntimeError=NJ;var OJ=class extends Error{};Ki.PersistError=OJ;var _b=class extends Error{};Ki.RepositoryError=_b;var sL=class extends _b{};Ki.BadVersionError=sL;var LJ=class extends sL{};Ki.EqualVersionError=LJ;var MJ=class extends _b{};Ki.ExpiredMetadataError=MJ;var Hb=class extends Error{};Ki.DownloadError=Hb;var UJ=class extends Hb{};Ki.DownloadLengthMismatchError=UJ;var _J=class extends Hb{constructor(e,r){super(e),this.statusCode=r}};Ki.DownloadHTTPError=_J});var YPe=_(d1=>{"use strict";var jJ=d1&&d1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(d1,"__esModule",{value:!0});d1.withTempFile=void 0;var HJ=jJ(Ie("fs/promises")),vvt=jJ(Ie("os")),WPe=jJ(Ie("path")),Svt=async t=>Dvt(async e=>t(WPe.default.join(e,"tempfile")));d1.withTempFile=Svt;var Dvt=async t=>{let e=await HJ.default.realpath(vvt.default.tmpdir()),r=await HJ.default.mkdtemp(e+WPe.default.sep);try{return await t(r)}finally{await HJ.default.rm(r,{force:!0,recursive:!0,maxRetries:3})}}});var qJ=_(kg=>{"use strict";var lL=kg&&kg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(kg,"__esModule",{value:!0});kg.DefaultFetcher=kg.BaseFetcher=void 0;var bvt=lL(RJ()),VPe=lL(Ie("fs")),Pvt=lL(CO()),xvt=lL(Ie("util")),JPe=oL(),kvt=YPe(),Qvt=(0,bvt.default)("tuf:fetch"),aL=class{async downloadFile(e,r,s){return(0,kvt.withTempFile)(async a=>{let n=await this.fetch(e),c=0,f=VPe.default.createWriteStream(a);try{for await(let p of n){let h=Buffer.from(p);if(c+=h.length,c>r)throw new JPe.DownloadLengthMismatchError("Max length reached");await Tvt(f,h)}}finally{await xvt.default.promisify(f.close).bind(f)()}return s(a)})}async downloadBytes(e,r){return this.downloadFile(e,r,async s=>{let a=VPe.default.createReadStream(s),n=[];for await(let c of a)n.push(c);return Buffer.concat(n)})}};kg.BaseFetcher=aL;var GJ=class extends aL{constructor(e={}){super(),this.timeout=e.timeout,this.retry=e.retry}async fetch(e){Qvt("GET %s",e);let r=await(0,Pvt.default)(e,{timeout:this.timeout,retry:this.retry});if(!r.ok||!r?.body)throw new JPe.DownloadHTTPError("Failed to download",r.status);return r.body}};kg.DefaultFetcher=GJ;var Tvt=async(t,e)=>new Promise((r,s)=>{t.write(e,a=>{a&&s(a),r(!0)})})});var KPe=_(cL=>{"use strict";Object.defineProperty(cL,"__esModule",{value:!0});cL.defaultConfig=void 0;cL.defaultConfig={maxRootRotations:256,maxDelegations:32,rootMaxLength:512e3,timestampMaxLength:16384,snapshotMaxLength:2e6,targetsMaxLength:5e6,prefixTargetsWithHash:!0,fetchTimeout:1e5,fetchRetries:void 0,fetchRetry:2}});var zPe=_(uL=>{"use strict";Object.defineProperty(uL,"__esModule",{value:!0});uL.TrustedMetadataStore=void 0;var Es=eL(),Hi=oL(),WJ=class{constructor(e){this.trustedSet={},this.referenceTime=new Date,this.loadTrustedRoot(e)}get root(){if(!this.trustedSet.root)throw new ReferenceError("No trusted root metadata");return this.trustedSet.root}get timestamp(){return this.trustedSet.timestamp}get snapshot(){return this.trustedSet.snapshot}get targets(){return this.trustedSet.targets}getRole(e){return this.trustedSet[e]}updateRoot(e){let r=JSON.parse(e.toString("utf8")),s=Es.Metadata.fromJSON(Es.MetadataKind.Root,r);if(s.signed.type!=Es.MetadataKind.Root)throw new Hi.RepositoryError(`Expected 'root', got ${s.signed.type}`);if(this.root.verifyDelegate(Es.MetadataKind.Root,s),s.signed.version!=this.root.signed.version+1)throw new Hi.BadVersionError(`Expected version ${this.root.signed.version+1}, got ${s.signed.version}`);return s.verifyDelegate(Es.MetadataKind.Root,s),this.trustedSet.root=s,s}updateTimestamp(e){if(this.snapshot)throw new Hi.RuntimeError("Cannot update timestamp after snapshot");if(this.root.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError("Final root.json is expired");let r=JSON.parse(e.toString("utf8")),s=Es.Metadata.fromJSON(Es.MetadataKind.Timestamp,r);if(s.signed.type!=Es.MetadataKind.Timestamp)throw new Hi.RepositoryError(`Expected 'timestamp', got ${s.signed.type}`);if(this.root.verifyDelegate(Es.MetadataKind.Timestamp,s),this.timestamp){if(s.signed.version{let p=n.signed.meta[c];if(!p)throw new Hi.RepositoryError(`Missing file ${c} in new snapshot`);if(p.version{"use strict";Object.defineProperty(YJ,"__esModule",{value:!0});YJ.join=Fvt;var Rvt=Ie("url");function Fvt(t,e){return new Rvt.URL(Nvt(t)+Ovt(e)).toString()}function Nvt(t){return t.endsWith("/")?t:t+"/"}function Ovt(t){return t.startsWith("/")?t.slice(1):t}});var ZPe=_(nu=>{"use strict";var Lvt=nu&&nu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Mvt=nu&&nu.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),KJ=nu&&nu.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&Lvt(e,t,r);return Mvt(e,t),e},Uvt=nu&&nu.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nu,"__esModule",{value:!0});nu.Updater=void 0;var xA=eL(),_vt=Uvt(RJ()),m1=KJ(Ie("fs")),fL=KJ(Ie("path")),Hvt=KPe(),fy=oL(),jvt=qJ(),Gvt=zPe(),jb=KJ(XPe()),VJ=(0,_vt.default)("tuf:cache"),JJ=class{constructor(e){let{metadataDir:r,metadataBaseUrl:s,targetDir:a,targetBaseUrl:n,fetcher:c,config:f}=e;this.dir=r,this.metadataBaseUrl=s,this.targetDir=a,this.targetBaseUrl=n,this.forceCache=e.forceCache??!1;let p=this.loadLocalMetadata(xA.MetadataKind.Root);this.trustedSet=new Gvt.TrustedMetadataStore(p),this.config={...Hvt.defaultConfig,...f},this.fetcher=c||new jvt.DefaultFetcher({timeout:this.config.fetchTimeout,retry:this.config.fetchRetries??this.config.fetchRetry})}async refresh(){if(this.forceCache)try{await this.loadTimestamp({checkRemote:!1})}catch{await this.loadRoot(),await this.loadTimestamp()}else await this.loadRoot(),await this.loadTimestamp();await this.loadSnapshot(),await this.loadTargets(xA.MetadataKind.Targets,xA.MetadataKind.Root)}async getTargetInfo(e){return this.trustedSet.targets||await this.refresh(),this.preorderDepthFirstWalk(e)}async downloadTarget(e,r,s){let a=r||this.generateTargetPath(e);if(!s){if(!this.targetBaseUrl)throw new fy.ValueError("Target base URL not set");s=this.targetBaseUrl}let n=e.path;if(this.trustedSet.root.signed.consistentSnapshot&&this.config.prefixTargetsWithHash){let p=Object.values(e.hashes),{dir:h,base:E}=fL.parse(n),C=`${p[0]}.${E}`;n=h?`${h}/${C}`:C}let f=jb.join(s,n);return await this.fetcher.downloadFile(f,e.length,async p=>{await e.verify(m1.createReadStream(p)),VJ("WRITE %s",a),m1.copyFileSync(p,a)}),a}async findCachedTarget(e,r){r||(r=this.generateTargetPath(e));try{if(m1.existsSync(r))return await e.verify(m1.createReadStream(r)),r}catch{return}}loadLocalMetadata(e){let r=fL.join(this.dir,`${e}.json`);return VJ("READ %s",r),m1.readFileSync(r)}async loadRoot(){let r=this.trustedSet.root.signed.version+1,s=r+this.config.maxRootRotations;for(let a=r;a0;){let{roleName:a,parentRoleName:n}=r.pop();if(s.has(a))continue;let c=(await this.loadTargets(a,n))?.signed;if(!c)continue;let f=c.targets?.[e];if(f)return f;if(s.add(a),c.delegations){let p=[],h=c.delegations.rolesForTarget(e);for(let{role:E,terminating:C}of h)if(p.push({roleName:E,parentRoleName:a}),C){r.splice(0);break}p.reverse(),r.push(...p)}}}generateTargetPath(e){if(!this.targetDir)throw new fy.ValueError("Target directory not set");let r=encodeURIComponent(e.path);return fL.join(this.targetDir,r)}persistMetadata(e,r){let s=encodeURIComponent(e);try{let a=fL.join(this.dir,`${s}.json`);VJ("WRITE %s",a),m1.writeFileSync(a,r.toString("utf8"))}catch(a){throw new fy.PersistError(`Failed to persist metadata ${s} error: ${a}`)}}};nu.Updater=JJ});var $Pe=_(Qg=>{"use strict";Object.defineProperty(Qg,"__esModule",{value:!0});Qg.Updater=Qg.BaseFetcher=Qg.TargetFile=void 0;var qvt=eL();Object.defineProperty(Qg,"TargetFile",{enumerable:!0,get:function(){return qvt.TargetFile}});var Wvt=qJ();Object.defineProperty(Qg,"BaseFetcher",{enumerable:!0,get:function(){return Wvt.BaseFetcher}});var Yvt=ZPe();Object.defineProperty(Qg,"Updater",{enumerable:!0,get:function(){return Yvt.Updater}})});var XJ=_(AL=>{"use strict";Object.defineProperty(AL,"__esModule",{value:!0});AL.TUFError=void 0;var zJ=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}};AL.TUFError=zJ});var exe=_(Gb=>{"use strict";var Vvt=Gb&&Gb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Gb,"__esModule",{value:!0});Gb.readTarget=Kvt;var Jvt=Vvt(Ie("fs")),pL=XJ();async function Kvt(t,e){let r=await zvt(t,e);return new Promise((s,a)=>{Jvt.default.readFile(r,"utf-8",(n,c)=>{n?a(new pL.TUFError({code:"TUF_READ_TARGET_ERROR",message:`error reading target ${r}`,cause:n})):s(c)})})}async function zvt(t,e){let r;try{r=await t.getTargetInfo(e)}catch(a){throw new pL.TUFError({code:"TUF_REFRESH_METADATA_ERROR",message:"error refreshing TUF metadata",cause:a})}if(!r)throw new pL.TUFError({code:"TUF_FIND_TARGET_ERROR",message:`target ${e} not found`});let s=await t.findCachedTarget(r);if(!s)try{s=await t.downloadTarget(r)}catch(a){throw new pL.TUFError({code:"TUF_DOWNLOAD_TARGET_ERROR",message:`error downloading target ${s}`,cause:a})}return s}});var txe=_((Uir,Xvt)=>{Xvt.exports={"https://tuf-repo-cdn.sigstore.dev":{"root.json":"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiMzA0NjAyMjEwMDhhYjFmNmYxN2Q0ZjllNmQ3ZGNmMWM4ODkxMmI2YjUzY2MxMDM4ODY0NGFlMWYwOWJjMzdhMDgyY2QwNjAwM2UwMjIxMDBlMTQ1ZWY0YzdiNzgyZDRlODEwN2I1MzQzN2U2NjlkMDQ3Njg5MmNlOTk5OTAzYWUzM2QxNDQ0ODM2Njk5NmU3IgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGM3NjhiMmY4NmRhOTk1NjkwMTljMTYwYTA4MWRhNTRhZTM2YzM0YzBhMzEyMGQzY2I2OWI1M2I3ZDExMzc1OGUwMjIwNGY2NzE1MThmNjE3YjIwZDQ2NTM3ZmFlNmMzYjYzYmFlODkxM2Y0ZjE5NjIxNTYxMDVjYzRmMDE5YWMzNWM2YSIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDUwMjIxMDBiNDQzNGU2OTk1ZDM2OGQyM2U3NDc1OWFjZDBjYjkwMTNjODNhNWQzNTExZjBmOTk3ZWM1NGM0NTZhZTQzNTBhMDIyMDE1YjBlMjY1ZDE4MmQyYjYxZGM3NGUxNTVkOThiM2MzZmJlNTY0YmEwNTI4NmFhMTRjOGRmMDJjOWI3NTY1MTYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgInNpZyI6ICIzMDQ1MDIyMTAwODJjNTg0MTFkOTg5ZWI5Zjg2MTQxMDg1N2Q0MjM4MTU5MGVjOTQyNGRiZGFhNTFlNzhlZDEzNTE1NDMxOTA0ZTAyMjAxMTgxODVkYTZhNmMyOTQ3MTMxYzE3Nzk3ZTJiYjc2MjBjZTI2ZTVmMzAxZDFjZWFjNWYyYTdlNThmOWRjZjJlIgogIH0sCiAgewogICAia2V5aWQiOiAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGM3ODUxMzg1NGNhZTljMzJlYWE2Yjg4ZTE4OTEyZjQ4MDA2YzI3NTdhMjU4ZjkxNzMxMmNhYmE3NTk0OGViOWUwMjIxMDBkOWUxYjRjZTBhZGZlOWZkMmUyMTQ4ZDdmYTI3YTJmNDBiYTExMjJiZDY5ZGE3NjEyZDhkMTc3NmIwMTNjOTFkIgogIH0sCiAgewogICAia2V5aWQiOiAiZmRmYTgzYTA3YjVhODM1ODliODdkZWQ0MWY3N2YzOWQyMzJhZDkxZjdjY2U1Mjg2OGRhY2QwNmJhMDg5ODQ5ZiIsCiAgICJzaWciOiAiMzA0NTAyMjA1NjQ4M2EyZDVkOWVhOWNlYzZlMTFlYWRmYjMzYzQ4NGI2MTQyOThmYWNhMTVhY2YxYzQzMWIxMWVkN2Y3MzRjMDIyMTAwZDBjMWQ3MjZhZjkyYTg3ZTRlNjY0NTljYTVhZGYzOGEwNWI0NGUxZjk0MzE4NDIzZjk1NGJhZThiY2E1YmIyZSIKICB9LAogIHsKICAgImtleWlkIjogImUyZjU5YWNiOTQ4ODUxOTQwN2UxOGNiZmM5MzI5NTEwYmUwM2MwNGFjYTk5MjlkMmYwMzAxMzQzZmVjODU1MjMiLAogICAic2lnIjogIjMwNDYwMjIxMDBkMDA0ZGU4ODAyNGMzMmRjNTY1M2E5ZjQ4NDNjZmM1MjE1NDI3MDQ4YWQ5NjAwZDJjZjljOTY5ZTZlZGZmM2QyMDIyMTAwZDllYmI3OThmNWZjNjZhZjEwODk5ZGVjZTAxNGE4NjI4Y2NmM2M1NDAyY2Q0YTQyNzAyMDc0NzJmOGY2ZTcxMiIKICB9LAogIHsKICAgImtleWlkIjogIjNjMzQ0YWEwNjhmZDRjYzRlODdkYzUwYjYxMmMwMjQzMWZiYzc3MWU5NTAwMzk5MzY4M2EyYjBiZjI2MGNmMGUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiN2IwOTk5NmM0NWNhMmQ0YjA1NjAzZTU2YmFlZmEyOTcxOGEwYjcxMTQ3Y2Y4YzZlNjYzNDliYWE2MTQ3N2RmMDIyMTAwYzRkYTgwYzcxN2I0ZmE3YmJhMGZkNWM3MmRhOGEwNDk5MzU4YjAxMzU4YjIzMDlmNDFkMTQ1NmVhMWU3ZTFkOSIKICB9LAogIHsKICAgImtleWlkIjogImVjODE2Njk3MzRlMDE3OTk2YzViODVmM2QwMmMzZGUxZGQ0NjM3YTE1MjAxOWZlMWFmMTI1ZDJmOTM2OGI5NWUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiZTk3ODJjMzA3NDRlNDExYTgyZmE4NWI1MTM4ZDYwMWNlMTQ4YmMxOTI1OGFlYzY0ZTdlYzI0NDc4ZjM4ODEyMDIyMTAwY2FlZjYzZGNhZjFhNGI5YTUwMGQzYmQwZTNmMTY0ZWMxOGYxYjYzZDdhOTQ2MGQ5YWNhYjEwNjZkYjBmMDE2ZCIKICB9LAogIHsKICAgImtleWlkIjogIjFlMWQ2NWNlOThiMTBhZGRhZDQ3NjRmZWJmN2RkYTJkMDQzNmIzZDNhMzg5MzU3OWMwZGRkYWVhMjBlNTQ4NDkiLAogICAic2lnIjogIjMwNDUwMjIwNzQ2ZWMzZjg1MzRjZTU1NTMxZDBkMDFmZjY0OTY0ZWY0NDBkMWU3ZDJjNGMxNDI0MDliOGU5NzY5ZjFhZGE2ZjAyMjEwMGUzYjkyOWZjZDkzZWExOGZlYWEwODI1ODg3YTcyMTA0ODk4NzlhNjY3ODBjMDdhODNmNGJkNDZlMmYwOWFiM2IiCiAgfQogXSwKICJzaWduZWQiOiB7CiAgIl90eXBlIjogInJvb3QiLAogICJjb25zaXN0ZW50X3NuYXBzaG90IjogdHJ1ZSwKICAiZXhwaXJlcyI6ICIyMDI1LTAyLTE5VDA4OjA0OjMyWiIsCiAgImtleXMiOiB7CiAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFekJ6Vk9tSENQb2pNVkxTSTM2NFdpaVY4TlByRFxuNklnUnhWbGlza3ovdit5M0pFUjVtY1ZHY09ObGlEY1dNQzVKMmxmSG1qUE5QaGI0SDd4bThMemZTQT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBzYW50aWFnb3RvcnJlcyIKICAgfSwKICAgIjYxNjQzODM4MTI1YjQ0MGI0MGRiNjk0MmY1Y2I1YTMxYzBkYzA0MzY4MzE2ZWIyYWFhNThiOTU5MDRhNTgyMjIiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpbmlrU3NBUW1Za05lSDVlWXEvQ25JekxhYWNPXG54bFNhYXdRRE93cUt5L3RDcXhxNXh4UFNKYzIxSzRXSWhzOUd5T2tLZnp1ZVkzR0lMemNNSlo0Y1d3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGJvYmNhbGxhd2F5IgogICB9LAogICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXk4WEtzbWhCWURJOEpjMEd3ekJ4ZUtheDBjbTVcblNUS0VVNjVIUEZ1blVuNDFzVDhwaTBGak00SWtIei9ZVW13bUxVTzBXdDdseGhqNkJrTElLNHFZQXc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAZGxvcmVuYyIKICAgfSwKICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVXUmlHcjUraiszSjVTc0grWnRyNW5FMkgyd083XG5CVituTzNzOTNnTGNhMThxVE96SFkxb1d5QUdEeWtNU3NHVFVCU3Q5RCtBbjBLZktzRDJtZlNNNDJRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2ktb25saW5lLXVyaSI6ICJnY3BrbXM6Ly9wcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wIgogICB9LAogICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTBnaHJoOTJMdzFZcjNpZEdWNVdxQ3RNREI4Q3hcbitEOGhkQzR3MlpMTklwbFZSb1ZHTHNrWWEzZ2hlTXlPamlKOGtQaTE1YVEyLy83UCtvajdVdkpQR3c9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAam9zaHVhZ2wiCiAgIH0sCiAgICJlNzFhNTRkNTQzODM1YmE4NmFkYWQ5NDYwMzc5Yzc2NDFmYjg3MjZkMTY0ZWE3NjY4MDFhMWM1MjJhYmE3ZWEyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVhzejNTWlhGYjhqTVY0Mmo2cEpseWpialI4S1xuTjNCd29jZXhxNkxNSWI1cXNXS09RdkxOMTZOVWVmTGM0SHN3T291bVJzVlZhYWpTcFFTNmZvYmtSdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBtbm02NzgiCiAgIH0KICB9LAogICJyb2xlcyI6IHsKICAgInJvb3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI2ZjI2MDA4OWQ1OTIzZGFmMjAxNjZjYTY1N2M1NDNhZjYxODM0NmFiOTcxODg0YTk5OTYyYjAxOTg4YmJlMGMzIiwKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMwogICB9LAogICAic25hcHNob3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI3MjQ3ZjBkYmFkODViMTQ3ZTE4NjNiYWRlNzYxMjQzY2M3ODVkY2I3YWE0MTBlNzEwNWRkM2QyYjYxYTM2ZDJjIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiAzNjUwLAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogMzY1CiAgIH0sCiAgICJ0YXJnZXRzIjogewogICAgImtleWlkcyI6IFsKICAgICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInRpbWVzdGFtcCI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDEsCiAgICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDcsCiAgICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0CiAgIH0KICB9LAogICJzcGVjX3ZlcnNpb24iOiAiMS4wIiwKICAidmVyc2lvbiI6IDEwLAogICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMTgyLAogICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDMxCiB9Cn0=",targets:{"trusted_root.json":"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjcuMDAwWiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAid05JOWF0UUdseitWV2ZPNkxSeWdINFFVZlkvOFc0UkZ3aVQ1aTVXUmdCMD0iCiAgICAgIH0KICAgIH0KICBdLAogICJjZXJ0aWZpY2F0ZUF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQitEQ0NBWDZnQXdJQkFnSVROVmtEWm9DaW9mUERzeTdkZm02Z2VMYnVoekFLQmdncWhrak9QUVFEQXpBcU1SVXdFd1lEVlFRS0V3eHphV2R6ZEc5eVpTNWtaWFl4RVRBUEJnTlZCQU1UQ0hOcFozTjBiM0psTUI0WERUSXhNRE13TnpBek1qQXlPVm9YRFRNeE1ESXlNekF6TWpBeU9Wb3dLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkxTeUE3SWk1aytwTk84WkVXWTB5bGVtV0Rvd09rTmEza0wrR1pFNVo1R1dlaEw5L0E5YlJOQTNSYnJzWjVpMEpjYXN0YVJMN1NwNWZwL2pENWR4cWMvVWRUVm5sdlMxNmFuKzJZZnN3ZS9RdUxvbFJVQ3JjT0UyKzJpQTUrdHpkNk5tTUdRd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0hRWURWUjBPQkJZRUZNakZIUUJCbWlRcE1sRWs2dzJ1U3UxS0J0UHNNQjhHQTFVZEl3UVlNQmFBRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01IOGxpV0pmTXVpNnZYWEJoakRnWTRNd3NsbU4vVEp4VmUvODNXckZvbXdtTmYwNTZ5MVg0OEY5YzRtM2Ezb3pYQUl4QUtqUmF5NS9hai9qc0tLR0lrbVFhdGpJOHV1cEhyLytDeEZ2YUpXbXBZcU5rTERHUlUrOW9yemg1aEkyUnJjdWFRPT0iCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMDdUMDM6MjA6MjkuMDAwWiIsCiAgICAgICAgImVuZCI6ICIyMDIyLTEyLTMxVDIzOjU5OjU5Ljk5OVoiCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDR2pDQ0FhR2dBd0lCQWdJVUFMblZpVmZuVTBickphc21Sa0hybi9VbmZhUXdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1qQTBNVE15TURBMk1UVmFGdzB6TVRFd01EVXhNelUyTlRoYU1EY3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFZU1Cd0dBMVVFQXhNVmMybG5jM1J2Y21VdGFXNTBaWEp0WldScFlYUmxNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRThSVlMveXNIK05PdnVEWnlQSVp0aWxnVUY5TmxhcllwQWQ5SFAxdkJCSDFVNUNWNzdMU1M3czBaaUg0bkU3SHY3cHRTNkx2dlIvU1RrNzk4TFZnTXpMbEo0SGVJZkYzdEhTYWV4TGNZcFNBU3Ixa1MwTi9SZ0JKei85aldDaVhubzNzd2VUQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFkQmdOVkhRNEVGZ1FVMzlQcHoxWWtFWmI1cU5qcEtGV2l4aTRZWkQ4d0h3WURWUjBqQkJnd0ZvQVVXTUFlWDVGRnBXYXBlc3lRb1pNaTBDckZ4Zm93Q2dZSUtvWkl6ajBFQXdNRFp3QXdaQUl3UENzUUs0RFlpWllEUElhRGk1SEZLbmZ4WHg2QVNTVm1FUmZzeW5ZQmlYMlg2U0pSblpVODQvOURaZG5GdnZ4bUFqQk90NlFwQmxjNEovMER4dmtUQ3FwY2x2emlMNkJDQ1BuamRsSUIzUHUzQnhzUG15Z1VZN0lpMnpiZENkbGlpb3c9IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVQUxaTkFQRmR4SFB3amVEbG9Ed3lZQ2hBTy80d0NnWUlLb1pJemowRUF3TXdLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQWVGdzB5TVRFd01EY3hNelUyTlRsYUZ3MHpNVEV3TURVeE16VTJOVGhhTUNveEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVSTUE4R0ExVUVBeE1JYzJsbmMzUnZjbVV3ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBVDdYZUZUNHJiM1BRR3dTNElhanRMazMvT2xucGdhbmdhQmNsWXBzWUJyNWkrNHluQjA3Y2ViM0xQME9JT1pkeGV4WDY5YzVpVnV5SlJRK0h6MDV5aStVRjN1QldBbEhwaVM1c2gwK0gyR0hFN1NYcmsxRUM1bTFUcjE5TDlnZzkyall6QmhNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFmQmdOVkhTTUVHREFXZ0JSWXdCNWZrVVdsWnFsNnpKQ2hreUxRS3NYRitqQUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUFqMW5IZVhacCsxM05XQk5hK0VEc0RQOEcxV1dnMXRDTVdQL1dIUHFwYVZvMGpoc3dlTkZaZ1NzMGVFN3dZSTRxQWpFQTJXQjlvdDk4c0lrb0YzdlpZZGQzL1Z0V0I1YjlUTk1lYTdJeC9zdEo1VGZjTExlQUJMRTRCTkpPc1E0dm5CSEoiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjItMDQtMTNUMjA6MDY6MTUuMDAwWiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDAuMDAwWiIsCiAgICAgICAgICAiZW5kIjogIjIwMjItMTAtMzFUMjM6NTk6NTkuOTk5WiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAiQ0dDUzhDaFMvMmhGMGRGcko0U2NSV2NZckJZOXd6alNiZWE4SWdZMmIzST0iCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vY3RmZS5zaWdzdG9yZS5kZXYvMjAyMiIsCiAgICAgICJoYXNoQWxnb3JpdGhtIjogIlNIQTJfMjU2IiwKICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAicmF3Qnl0ZXMiOiAiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaVBTbEZpMENtRlRmRWpDVXFGOUh1Q0VjWVhOS0FhWWFsSUptQlo4eXllelBqVHFoeHJLQnBNbmFvY1Z0TEpCSTFlTTN1WG5RelFHQUpkSjRnczlGeXc9PSIsCiAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICJzdGFydCI6ICIyMDIyLTEwLTIwVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgfQogICAgICB9LAogICAgICAibG9nSWQiOiB7CiAgICAgICAgImtleUlkIjogIjNUMHdhc2JIRVRKakdSNGNtV2MzQXFKS1hyamVQSzMvaDRweWdDOHA3bzQ9IgogICAgICB9CiAgICB9CiAgXSwKICAidGltZXN0YW1wQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAiR2l0SHViLCBJbmMuIiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJJbnRlcm5hbCBTZXJ2aWNlcyBSb290IgogICAgICB9LAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCM0RDQ0FXS2dBd0lCQWdJVWNoa05zSDM2WGEwNGIxTHFJYytxcjlEVmVjTXdDZ1lJS29aSXpqMEVBd013TWpFVk1CTUdBMVVFQ2hNTVIybDBTSFZpTENCSmJtTXVNUmt3RndZRFZRUURFeEJVVTBFZ2FXNTBaWEp0WldScFlYUmxNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVEkwTURReE16QXdNREF3TUZvd01qRVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVJrd0Z3WURWUVFERXhCVVUwRWdWR2x0WlhOMFlXMXdhVzVuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFVUQ1Wk5iU3FZTWQ2cjhxcE9PRVg5aWJHblpUOUdzdVhPaHIvZjhVOUZKdWdCR0V4S1lwNDBPVUxTMGVyalpXN3hWOXhWNTJObkpmNU9lRHE0ZTVaS3FOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUlNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVhVzFSdWRPZ1Z0MGxlcVkwV0tZYnVQcjQ3d0F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl3YlVIOUh2RDRlakNaSk9XUW5xQWxrcVVSbGx2dTlNOCtWcUxiaVJLK3pTZlpDWndzaWxqUm44TVFRUlNrWEVFNUFqRUFnK1Z4cXRvamZWZnU4RGh6emhDeDlHS0VUYkpIYjE5aVY3Mm1NS1ViREFGbXpaNmJROGI1NFpiOHRpZHk1YVdlIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUNFRENDQVpXZ0F3SUJBZ0lVWDhaTzVRWFA3dk40ZE1RNWU5c1UzbnViOE9nd0NnWUlLb1pJemowRUF3TXdPREVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1SOHdIUVlEVlFRREV4WkpiblJsY201aGJDQlRaWEoyYVdObGN5QlNiMjkwTUI0WERUSXpNRFF4TkRBd01EQXdNRm9YRFRJNE1EUXhNakF3TURBd01Gb3dNakVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1Sa3dGd1lEVlFRREV4QlVVMEVnYVc1MFpYSnRaV1JwWVhSbE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFdk1MWS9kVFZidklKWUFOQXVzekV3Sm5RRTFsbGZ0eW55TUtJTWhoNDhIbXFiVnI1eWd5YnpzTFJMVktiQldPZFoyMWFlSnorZ1ppeXRaZXRxY3lGOVdsRVI1TkVNZjZKVjdaTm9qUXB4SHE0UkhHb0dTY2VRdi9xdlRpWnhFREtvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVWFXMVJ1ZE9nVnQwbGVxWTBXS1lidVByNDd3QXdId1lEVlIwakJCZ3dGb0FVOU5ZWWxvYm5BRzRjMC9xanh5SC9scS93eitRd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLMUIxODV5Z0NySVlGbElzM0dqc3dqbndTTUc2TFk4d29MVmRha0tEWnhWYThmOGNxTXMxRGhjeEowKzA5dzk1UUl4QU8rdEJ6Wms3dmpVSjlpSmdENFI2WldUeFFXS3FObTc0ak85OW8rbzlzdjRGSS9TWlRaVEZ5TW4wSUpFSGRObXlBPT0iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQjlEQ0NBWHFnQXdJQkFnSVVhL0pBa2RVaks0SlV3c3F0YWlSSkdXaHFMU293Q2dZSUtvWkl6ajBFQXdNd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVE16TURReE1UQXdNREF3TUZvd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRWY5akZBWHh6NGt4NjhBSFJNT2tGQmhmbERjTVR2emFYejR4L0ZDY1hqSi8xcUVLb24vcVBJR25hVVJza0R0eU5iTkRPcGVKVERERnF0NDhpTVBybnpweDZJWndxZW1mVUpONHhCRVpmemErcFl0L2l5b2QrOXRacjIwUlJXU3YvbzBVd1F6QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFqQWRCZ05WSFE0RUZnUVU5TllZbG9ibkFHNGMwL3FqeHlIL2xxL3d6K1F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl4QUxaTFo4QmdSWHpLeExNTU45VklsTytlNGhyQm5OQmdGN3R6N0hucm93djJOZXRaRXJJQUNLRnltQmx2V0R2dE1BSXdaTytraTZzc1ExYnNabzk4TzhtRUFmMk5aN2lpQ2dERFUwVndqZWNvNnp5ZWgwekJUczkvN2dWNkFITlE1M3hEIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIzLTA0LTE0VDAwOjAwOjAwLjAwMFoiCiAgICAgIH0KICAgIH0KICBdCn0K","registry.npmjs.org%2Fkeys.json":"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K"}}}});var nxe=_(y1=>{"use strict";var rxe=y1&&y1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(y1,"__esModule",{value:!0});y1.TUFClient=void 0;var Tg=rxe(Ie("fs")),qb=rxe(Ie("path")),Zvt=$Pe(),$vt=hL(),eSt=exe(),$J="targets",ZJ=class{constructor(e){let r=new URL(e.mirrorURL),s=encodeURIComponent(r.host+r.pathname.replace(/\/$/,"")),a=qb.default.join(e.cachePath,s);tSt(a),rSt({cachePath:a,mirrorURL:e.mirrorURL,tufRootPath:e.rootPath,forceInit:e.forceInit}),this.updater=nSt({mirrorURL:e.mirrorURL,cachePath:a,forceCache:e.forceCache,retry:e.retry,timeout:e.timeout})}async refresh(){return this.updater.refresh()}getTarget(e){return(0,eSt.readTarget)(this.updater,e)}};y1.TUFClient=ZJ;function tSt(t){let e=qb.default.join(t,$J);Tg.default.existsSync(t)||Tg.default.mkdirSync(t,{recursive:!0}),Tg.default.existsSync(e)||Tg.default.mkdirSync(e)}function rSt({cachePath:t,mirrorURL:e,tufRootPath:r,forceInit:s}){let a=qb.default.join(t,"root.json");if(!Tg.default.existsSync(a)||s)if(r)Tg.default.copyFileSync(r,a);else{let c=txe()[e];if(!c)throw new $vt.TUFError({code:"TUF_INIT_CACHE_ERROR",message:`No root.json found for mirror: ${e}`});Tg.default.writeFileSync(a,Buffer.from(c["root.json"],"base64")),Object.entries(c.targets).forEach(([f,p])=>{Tg.default.writeFileSync(qb.default.join(t,$J,f),Buffer.from(p,"base64"))})}}function nSt(t){let e={fetchTimeout:t.timeout,fetchRetry:t.retry};return new Zvt.Updater({metadataBaseUrl:t.mirrorURL,targetBaseUrl:`${t.mirrorURL}/targets`,metadataDir:t.cachePath,targetDir:qb.default.join(t.cachePath,$J),forceCache:t.forceCache,config:e})}});var hL=_(gh=>{"use strict";Object.defineProperty(gh,"__esModule",{value:!0});gh.TUFError=gh.DEFAULT_MIRROR_URL=void 0;gh.getTrustedRoot=fSt;gh.initTUF=ASt;var iSt=yb(),sSt=Obe(),oSt=nxe();gh.DEFAULT_MIRROR_URL="https://tuf-repo-cdn.sigstore.dev";var aSt="sigstore-js",lSt={retries:2},cSt=5e3,uSt="trusted_root.json";async function fSt(t={}){let r=await ixe(t).getTarget(uSt);return iSt.TrustedRoot.fromJSON(JSON.parse(r))}async function ASt(t={}){let e=ixe(t);return e.refresh().then(()=>e)}function ixe(t){return new oSt.TUFClient({cachePath:t.cachePath||(0,sSt.appDataPath)(aSt),rootPath:t.rootPath,mirrorURL:t.mirrorURL||gh.DEFAULT_MIRROR_URL,retry:t.retry??lSt,timeout:t.timeout??cSt,forceCache:t.forceCache??!1,forceInit:t.forceInit??t.force??!1})}var pSt=XJ();Object.defineProperty(gh,"TUFError",{enumerable:!0,get:function(){return pSt.TUFError}})});var sxe=_(gL=>{"use strict";Object.defineProperty(gL,"__esModule",{value:!0});gL.DSSESignatureContent=void 0;var Wb=Cl(),eK=class{constructor(e){this.env=e}compareDigest(e){return Wb.crypto.bufferEqual(e,Wb.crypto.digest("sha256",this.env.payload))}compareSignature(e){return Wb.crypto.bufferEqual(e,this.signature)}verifySignature(e){return Wb.crypto.verify(this.preAuthEncoding,e,this.signature)}get signature(){return this.env.signatures.length>0?this.env.signatures[0].sig:Buffer.from("")}get preAuthEncoding(){return Wb.dsse.preAuthEncoding(this.env.payloadType,this.env.payload)}};gL.DSSESignatureContent=eK});var oxe=_(dL=>{"use strict";Object.defineProperty(dL,"__esModule",{value:!0});dL.MessageSignatureContent=void 0;var tK=Cl(),rK=class{constructor(e,r){this.signature=e.signature,this.messageDigest=e.messageDigest.digest,this.artifact=r}compareSignature(e){return tK.crypto.bufferEqual(e,this.signature)}compareDigest(e){return tK.crypto.bufferEqual(e,this.messageDigest)}verifySignature(e){return tK.crypto.verify(this.artifact,e,this.signature)}};dL.MessageSignatureContent=rK});var lxe=_(mL=>{"use strict";Object.defineProperty(mL,"__esModule",{value:!0});mL.toSignedEntity=dSt;mL.signatureContent=axe;var nK=Cl(),hSt=sxe(),gSt=oxe();function dSt(t,e){let{tlogEntries:r,timestampVerificationData:s}=t.verificationMaterial,a=[];for(let n of r)a.push({$case:"transparency-log",tlogEntry:n});for(let n of s?.rfc3161Timestamps??[])a.push({$case:"timestamp-authority",timestamp:nK.RFC3161Timestamp.parse(n.signedTimestamp)});return{signature:axe(t,e),key:mSt(t),tlogEntries:r,timestamps:a}}function axe(t,e){switch(t.content.$case){case"dsseEnvelope":return new hSt.DSSESignatureContent(t.content.dsseEnvelope);case"messageSignature":return new gSt.MessageSignatureContent(t.content.messageSignature,e)}}function mSt(t){switch(t.verificationMaterial.content.$case){case"publicKey":return{$case:"public-key",hint:t.verificationMaterial.content.publicKey.hint};case"x509CertificateChain":return{$case:"certificate",certificate:nK.X509Certificate.parse(t.verificationMaterial.content.x509CertificateChain.certificates[0].rawBytes)};case"certificate":return{$case:"certificate",certificate:nK.X509Certificate.parse(t.verificationMaterial.content.certificate.rawBytes)}}}});var Eo=_(E1=>{"use strict";Object.defineProperty(E1,"__esModule",{value:!0});E1.PolicyError=E1.VerificationError=void 0;var yL=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}},iK=class extends yL{};E1.VerificationError=iK;var sK=class extends yL{};E1.PolicyError=sK});var cxe=_(EL=>{"use strict";Object.defineProperty(EL,"__esModule",{value:!0});EL.filterCertAuthorities=ySt;EL.filterTLogAuthorities=ESt;function ySt(t,e){return t.filter(r=>r.validFor.start<=e.start&&r.validFor.end>=e.end)}function ESt(t,e){return t.filter(r=>e.logID&&!r.logID.equals(e.logID)?!1:r.validFor.start<=e.targetDate&&e.targetDate<=r.validFor.end)}});var py=_(Ay=>{"use strict";Object.defineProperty(Ay,"__esModule",{value:!0});Ay.filterTLogAuthorities=Ay.filterCertAuthorities=void 0;Ay.toTrustMaterial=CSt;var oK=Cl(),Yb=yb(),ISt=Eo(),aK=new Date(0),lK=new Date(864e13),Axe=cxe();Object.defineProperty(Ay,"filterCertAuthorities",{enumerable:!0,get:function(){return Axe.filterCertAuthorities}});Object.defineProperty(Ay,"filterTLogAuthorities",{enumerable:!0,get:function(){return Axe.filterTLogAuthorities}});function CSt(t,e){let r=typeof e=="function"?e:wSt(e);return{certificateAuthorities:t.certificateAuthorities.map(fxe),timestampAuthorities:t.timestampAuthorities.map(fxe),tlogs:t.tlogs.map(uxe),ctlogs:t.ctlogs.map(uxe),publicKey:r}}function uxe(t){let e=t.publicKey.keyDetails,r=e===Yb.PublicKeyDetails.PKCS1_RSA_PKCS1V5||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V5||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256?"pkcs1":"spki";return{logID:t.logId.keyId,publicKey:oK.crypto.createPublicKey(t.publicKey.rawBytes,r),validFor:{start:t.publicKey.validFor?.start||aK,end:t.publicKey.validFor?.end||lK}}}function fxe(t){return{certChain:t.certChain.certificates.map(e=>oK.X509Certificate.parse(e.rawBytes)),validFor:{start:t.validFor?.start||aK,end:t.validFor?.end||lK}}}function wSt(t){return e=>{let r=(t||{})[e];if(!r)throw new ISt.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:oK.crypto.createPublicKey(r.rawBytes),validFor:s=>(r.validFor?.start||aK)<=s&&(r.validFor?.end||lK)>=s}}}});var cK=_(Vb=>{"use strict";Object.defineProperty(Vb,"__esModule",{value:!0});Vb.CertificateChainVerifier=void 0;Vb.verifyCertificateChain=vSt;var hy=Eo(),BSt=py();function vSt(t,e){let r=(0,BSt.filterCertAuthorities)(e,{start:t.notBefore,end:t.notAfter}),s;for(let a of r)try{return new IL({trustedCerts:a.certChain,untrustedCert:t}).verify()}catch(n){s=n}throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"Failed to verify certificate chain",cause:s})}var IL=class{constructor(e){this.untrustedCert=e.untrustedCert,this.trustedCerts=e.trustedCerts,this.localCerts=SSt([...e.trustedCerts,e.untrustedCert])}verify(){let e=this.sort();return this.checkPath(e),e}sort(){let e=this.untrustedCert,r=this.buildPaths(e);if(r=r.filter(a=>a.some(n=>this.trustedCerts.includes(n))),r.length===0)throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"no trusted certificate path found"});let s=r.reduce((a,n)=>a.length{if(s&&a.extSubjectKeyID){a.extSubjectKeyID.keyIdentifier.equals(s)&&r.push(a);return}a.subject.equals(e.issuer)&&r.push(a)}),r=r.filter(a=>{try{return e.verify(a)}catch{return!1}}),r)}checkPath(e){if(e.length<1)throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate chain must contain at least one certificate"});if(!e.slice(1).every(s=>s.isCA))throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"intermediate certificate is not a CA"});for(let s=e.length-2;s>=0;s--)if(!e[s].issuer.equals(e[s+1].subject))throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"incorrect certificate name chaining"});for(let s=0;s{"use strict";Object.defineProperty(uK,"__esModule",{value:!0});uK.verifySCTs=PSt;var CL=Cl(),DSt=Eo(),bSt=py();function PSt(t,e,r){let s,a=t.clone();for(let p=0;p{if(!(0,bSt.filterTLogAuthorities)(r,{logID:p.logID,targetDate:p.datetime}).some(C=>p.verify(n.buffer,C.publicKey)))throw new DSt.VerificationError({code:"CERTIFICATE_ERROR",message:"SCT verification failed"});return p.logID})}});var gxe=_(wL=>{"use strict";Object.defineProperty(wL,"__esModule",{value:!0});wL.verifyPublicKey=FSt;wL.verifyCertificate=NSt;var xSt=Cl(),hxe=Eo(),kSt=cK(),QSt=pxe(),TSt="1.3.6.1.4.1.57264.1.1",RSt="1.3.6.1.4.1.57264.1.8";function FSt(t,e,r){let s=r.publicKey(t);return e.forEach(a=>{if(!s.validFor(a))throw new hxe.VerificationError({code:"PUBLIC_KEY_ERROR",message:`Public key is not valid for timestamp: ${a.toISOString()}`})}),{key:s.publicKey}}function NSt(t,e,r){let s=(0,kSt.verifyCertificateChain)(t,r.certificateAuthorities);if(!e.every(n=>s.every(c=>c.validForDate(n))))throw new hxe.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate is not valid or expired at the specified date"});return{scts:(0,QSt.verifySCTs)(s[0],s[1],r.ctlogs),signer:OSt(s[0])}}function OSt(t){let e,r=t.extension(RSt);r?e=r.valueObj.subs?.[0]?.value.toString("ascii"):e=t.extension(TSt)?.value.toString("ascii");let s={extensions:{issuer:e},subjectAlternativeName:t.subjectAltName};return{key:xSt.crypto.createPublicKey(t.publicKey),identity:s}}});var mxe=_(BL=>{"use strict";Object.defineProperty(BL,"__esModule",{value:!0});BL.verifySubjectAlternativeName=LSt;BL.verifyExtensions=MSt;var dxe=Eo();function LSt(t,e){if(e===void 0||!e.match(t))throw new dxe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`certificate identity error - expected ${t}, got ${e}`})}function MSt(t,e={}){let r;for(r in t)if(e[r]!==t[r])throw new dxe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`invalid certificate extension - expected ${r}=${t[r]}, got ${r}=${e[r]}`})}});var yxe=_(gK=>{"use strict";Object.defineProperty(gK,"__esModule",{value:!0});gK.verifyCheckpoint=HSt;var AK=Cl(),I1=Eo(),USt=py(),fK=` + +`,_St=/\u2014 (\S+) (\S+)\n/g;function HSt(t,e){let r=(0,USt.filterTLogAuthorities)(e,{targetDate:new Date(Number(t.integratedTime)*1e3)}),s=t.inclusionProof,a=pK.fromString(s.checkpoint.envelope),n=hK.fromString(a.note);if(!jSt(a,r))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid checkpoint signature"});if(!AK.crypto.bufferEqual(n.logHash,s.rootHash))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"root hash mismatch"})}function jSt(t,e){let r=Buffer.from(t.note,"utf-8");return t.signatures.every(s=>{let a=e.find(n=>AK.crypto.bufferEqual(n.logID.subarray(0,4),s.keyHint));return a?AK.crypto.verify(r,a.publicKey,s.signature):!1})}var pK=class t{constructor(e,r){this.note=e,this.signatures=r}static fromString(e){if(!e.includes(fK))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"missing checkpoint separator"});let r=e.indexOf(fK),s=e.slice(0,r+1),n=e.slice(r+fK.length).matchAll(_St),c=Array.from(n,f=>{let[,p,h]=f,E=Buffer.from(h,"base64");if(E.length<5)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"malformed checkpoint signature"});return{name:p,keyHint:E.subarray(0,4),signature:E.subarray(4)}});if(c.length===0)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"no signatures found in checkpoint"});return new t(s,c)}},hK=class t{constructor(e,r,s,a){this.origin=e,this.logSize=r,this.logHash=s,this.rest=a}static fromString(e){let r=e.trimEnd().split(` +`);if(r.length<3)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"too few lines in checkpoint header"});let s=r[0],a=BigInt(r[1]),n=Buffer.from(r[2],"base64"),c=r.slice(3);return new t(s,a,n,c)}}});var Exe=_(EK=>{"use strict";Object.defineProperty(EK,"__esModule",{value:!0});EK.verifyMerkleInclusion=WSt;var yK=Cl(),dK=Eo(),GSt=Buffer.from([0]),qSt=Buffer.from([1]);function WSt(t){let e=t.inclusionProof,r=BigInt(e.logIndex),s=BigInt(e.treeSize);if(r<0n||r>=s)throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:`invalid index: ${r}`});let{inner:a,border:n}=YSt(r,s);if(e.hashes.length!==a+n)throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid hash count"});let c=e.hashes.slice(0,a),f=e.hashes.slice(a),p=ZSt(t.canonicalizedBody),h=JSt(VSt(p,c,r),f);if(!yK.crypto.bufferEqual(h,e.rootHash))throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"calculated root hash does not match inclusion proof"})}function YSt(t,e){let r=KSt(t,e),s=zSt(t>>BigInt(r));return{inner:r,border:s}}function VSt(t,e,r){return e.reduce((s,a,n)=>r>>BigInt(n)&BigInt(1)?mK(a,s):mK(s,a),t)}function JSt(t,e){return e.reduce((r,s)=>mK(s,r),t)}function KSt(t,e){return XSt(t^e-BigInt(1))}function zSt(t){return t.toString(2).split("1").length-1}function XSt(t){return t===0n?0:t.toString(2).length}function mK(t,e){return yK.crypto.digest("sha256",qSt,t,e)}function ZSt(t){return yK.crypto.digest("sha256",GSt,t)}});var Cxe=_(IK=>{"use strict";Object.defineProperty(IK,"__esModule",{value:!0});IK.verifyTLogSET=tDt;var Ixe=Cl(),$St=Eo(),eDt=py();function tDt(t,e){if(!(0,eDt.filterTLogAuthorities)(e,{logID:t.logId.keyId,targetDate:new Date(Number(t.integratedTime)*1e3)}).some(a=>{let n=rDt(t),c=Buffer.from(Ixe.json.canonicalize(n),"utf8"),f=t.inclusionPromise.signedEntryTimestamp;return Ixe.crypto.verify(c,a.publicKey,f)}))throw new $St.VerificationError({code:"TLOG_INCLUSION_PROMISE_ERROR",message:"inclusion promise could not be verified"})}function rDt(t){let{integratedTime:e,logIndex:r,logId:s,canonicalizedBody:a}=t;return{body:a.toString("base64"),integratedTime:Number(e),logIndex:Number(r),logID:s.keyId.toString("hex")}}});var wxe=_(BK=>{"use strict";Object.defineProperty(BK,"__esModule",{value:!0});BK.verifyRFC3161Timestamp=sDt;var CK=Cl(),wK=Eo(),nDt=cK(),iDt=py();function sDt(t,e,r){let s=t.signingTime;if(r=(0,iDt.filterCertAuthorities)(r,{start:s,end:s}),r=aDt(r,{serialNumber:t.signerSerialNumber,issuer:t.signerIssuer}),!r.some(n=>{try{return oDt(t,e,n),!0}catch{return!1}}))throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp could not be verified"})}function oDt(t,e,r){let[s,...a]=r.certChain,n=CK.crypto.createPublicKey(s.publicKey),c=t.signingTime;try{new nDt.CertificateChainVerifier({untrustedCert:s,trustedCerts:a}).verify()}catch{throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"invalid certificate chain"})}if(!r.certChain.every(p=>p.validForDate(c)))throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp was signed with an expired certificate"});t.verify(e,n)}function aDt(t,e){return t.filter(r=>r.certChain.length>0&&CK.crypto.bufferEqual(r.certChain[0].serialNumber,e.serialNumber)&&CK.crypto.bufferEqual(r.certChain[0].issuer,e.issuer))}});var Bxe=_(vL=>{"use strict";Object.defineProperty(vL,"__esModule",{value:!0});vL.verifyTSATimestamp=pDt;vL.verifyTLogTimestamp=hDt;var lDt=Eo(),cDt=yxe(),uDt=Exe(),fDt=Cxe(),ADt=wxe();function pDt(t,e,r){return(0,ADt.verifyRFC3161Timestamp)(t,e,r),{type:"timestamp-authority",logID:t.signerSerialNumber,timestamp:t.signingTime}}function hDt(t,e){let r=!1;if(gDt(t)&&((0,fDt.verifyTLogSET)(t,e),r=!0),dDt(t)&&((0,uDt.verifyMerkleInclusion)(t),(0,cDt.verifyCheckpoint)(t,e),r=!0),!r)throw new lDt.VerificationError({code:"TLOG_MISSING_INCLUSION_ERROR",message:"inclusion could not be verified"});return{type:"transparency-log",logID:t.logId.keyId,timestamp:new Date(Number(t.integratedTime)*1e3)}}function gDt(t){return t.inclusionPromise!==void 0}function dDt(t){return t.inclusionProof!==void 0}});var vxe=_(vK=>{"use strict";Object.defineProperty(vK,"__esModule",{value:!0});vK.verifyDSSETLogBody=mDt;var SL=Eo();function mDt(t,e){switch(t.apiVersion){case"0.0.1":return yDt(t,e);default:throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported dsse version: ${t.apiVersion}`})}}function yDt(t,e){if(t.spec.signatures?.length!==1)throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=t.spec.signatures[0].signature;if(!e.compareSignature(Buffer.from(r,"base64")))throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}});var Sxe=_(DK=>{"use strict";Object.defineProperty(DK,"__esModule",{value:!0});DK.verifyHashedRekordTLogBody=EDt;var SK=Eo();function EDt(t,e){switch(t.apiVersion){case"0.0.1":return IDt(t,e);default:throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported hashedrekord version: ${t.apiVersion}`})}}function IDt(t,e){let r=t.spec.signature.content||"";if(!e.compareSignature(Buffer.from(r,"base64")))throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:"signature mismatch"});let s=t.spec.data.hash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:"digest mismatch"})}});var Dxe=_(bK=>{"use strict";Object.defineProperty(bK,"__esModule",{value:!0});bK.verifyIntotoTLogBody=CDt;var DL=Eo();function CDt(t,e){switch(t.apiVersion){case"0.0.2":return wDt(t,e);default:throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported intoto version: ${t.apiVersion}`})}}function wDt(t,e){if(t.spec.content.envelope.signatures?.length!==1)throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=BDt(t.spec.content.envelope.signatures[0].sig);if(!e.compareSignature(Buffer.from(r,"base64")))throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.content.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}function BDt(t){return Buffer.from(t,"base64").toString("utf-8")}});var Pxe=_(PK=>{"use strict";Object.defineProperty(PK,"__esModule",{value:!0});PK.verifyTLogBody=bDt;var bxe=Eo(),vDt=vxe(),SDt=Sxe(),DDt=Dxe();function bDt(t,e){let{kind:r,version:s}=t.kindVersion,a=JSON.parse(t.canonicalizedBody.toString("utf8"));if(r!==a.kind||s!==a.apiVersion)throw new bxe.VerificationError({code:"TLOG_BODY_ERROR",message:`kind/version mismatch - expected: ${r}/${s}, received: ${a.kind}/${a.apiVersion}`});switch(a.kind){case"dsse":return(0,vDt.verifyDSSETLogBody)(a,e);case"intoto":return(0,DDt.verifyIntotoTLogBody)(a,e);case"hashedrekord":return(0,SDt.verifyHashedRekordTLogBody)(a,e);default:throw new bxe.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported kind: ${r}`})}}});var Rxe=_(bL=>{"use strict";Object.defineProperty(bL,"__esModule",{value:!0});bL.Verifier=void 0;var PDt=Ie("util"),C1=Eo(),xxe=gxe(),kxe=mxe(),Qxe=Bxe(),xDt=Pxe(),xK=class{constructor(e,r={}){this.trustMaterial=e,this.options={ctlogThreshold:r.ctlogThreshold??1,tlogThreshold:r.tlogThreshold??1,tsaThreshold:r.tsaThreshold??0}}verify(e,r){let s=this.verifyTimestamps(e),a=this.verifySigningKey(e,s);return this.verifyTLogs(e),this.verifySignature(e,a),r&&this.verifyPolicy(r,a.identity||{}),a}verifyTimestamps(e){let r=0,s=0,a=e.timestamps.map(n=>{switch(n.$case){case"timestamp-authority":return s++,(0,Qxe.verifyTSATimestamp)(n.timestamp,e.signature.signature,this.trustMaterial.timestampAuthorities);case"transparency-log":return r++,(0,Qxe.verifyTLogTimestamp)(n.tlogEntry,this.trustMaterial.tlogs)}});if(Txe(a))throw new C1.VerificationError({code:"TIMESTAMP_ERROR",message:"duplicate timestamp"});if(rn.timestamp)}verifySigningKey({key:e},r){switch(e.$case){case"public-key":return(0,xxe.verifyPublicKey)(e.hint,r,this.trustMaterial);case"certificate":{let s=(0,xxe.verifyCertificate)(e.certificate,r,this.trustMaterial);if(Txe(s.scts))throw new C1.VerificationError({code:"CERTIFICATE_ERROR",message:"duplicate SCT"});if(s.scts.length(0,xDt.verifyTLogBody)(s,e))}verifySignature(e,r){if(!e.signature.verifySignature(r.key))throw new C1.VerificationError({code:"SIGNATURE_ERROR",message:"signature verification failed"})}verifyPolicy(e,r){e.subjectAlternativeName&&(0,kxe.verifySubjectAlternativeName)(e.subjectAlternativeName,r.subjectAlternativeName),e.extensions&&(0,kxe.verifyExtensions)(e.extensions,r.extensions)}};bL.Verifier=xK;function Txe(t){for(let e=0;e{"use strict";Object.defineProperty(iu,"__esModule",{value:!0});iu.Verifier=iu.toTrustMaterial=iu.VerificationError=iu.PolicyError=iu.toSignedEntity=void 0;var kDt=lxe();Object.defineProperty(iu,"toSignedEntity",{enumerable:!0,get:function(){return kDt.toSignedEntity}});var Fxe=Eo();Object.defineProperty(iu,"PolicyError",{enumerable:!0,get:function(){return Fxe.PolicyError}});Object.defineProperty(iu,"VerificationError",{enumerable:!0,get:function(){return Fxe.VerificationError}});var QDt=py();Object.defineProperty(iu,"toTrustMaterial",{enumerable:!0,get:function(){return QDt.toTrustMaterial}});var TDt=Rxe();Object.defineProperty(iu,"Verifier",{enumerable:!0,get:function(){return TDt.Verifier}})});var Nxe=_(Fa=>{"use strict";Object.defineProperty(Fa,"__esModule",{value:!0});Fa.DEFAULT_TIMEOUT=Fa.DEFAULT_RETRY=void 0;Fa.createBundleBuilder=NDt;Fa.createKeyFinder=ODt;Fa.createVerificationPolicy=LDt;var RDt=Cl(),w1=H7(),FDt=PL();Fa.DEFAULT_RETRY={retries:2};Fa.DEFAULT_TIMEOUT=5e3;function NDt(t,e){let r={signer:MDt(e),witnesses:_Dt(e)};switch(t){case"messageSignature":return new w1.MessageSignatureBundleBuilder(r);case"dsseEnvelope":return new w1.DSSEBundleBuilder({...r,certificateChain:e.legacyCompatibility})}}function ODt(t){return e=>{let r=t(e);if(!r)throw new FDt.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:RDt.crypto.createPublicKey(r),validFor:()=>!0}}}function LDt(t){let e={},r=t.certificateIdentityEmail||t.certificateIdentityURI;return r&&(e.subjectAlternativeName=r),t.certificateIssuer&&(e.extensions={issuer:t.certificateIssuer}),e}function MDt(t){return new w1.FulcioSigner({fulcioBaseURL:t.fulcioURL,identityProvider:t.identityProvider||UDt(t),retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})}function UDt(t){let e=t.identityToken;return e?{getToken:()=>Promise.resolve(e)}:new w1.CIContextProvider("sigstore")}function _Dt(t){let e=[];return HDt(t)&&e.push(new w1.RekorWitness({rekorBaseURL:t.rekorURL,entryType:t.legacyCompatibility?"intoto":"dsse",fetchOnConflict:!1,retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})),jDt(t)&&e.push(new w1.TSAWitness({tsaBaseURL:t.tsaServerURL,retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})),e}function HDt(t){return t.tlogUpload!==!1}function jDt(t){return t.tsaServerURL!==void 0}});var Mxe=_(su=>{"use strict";var GDt=su&&su.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),qDt=su&&su.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Oxe=su&&su.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;aa.verify(t,s))}async function Lxe(t={}){let e=await WDt.getTrustedRoot({mirrorURL:t.tufMirrorURL,rootPath:t.tufRootPath,cachePath:t.tufCachePath,forceCache:t.tufForceCache,retry:t.retry??B1.DEFAULT_RETRY,timeout:t.timeout??B1.DEFAULT_TIMEOUT}),r=t.keySelector?B1.createKeyFinder(t.keySelector):void 0,s=(0,kK.toTrustMaterial)(e,r),a={ctlogThreshold:t.ctLogThreshold,tlogThreshold:t.tlogThreshold},n=new kK.Verifier(s,a),c=B1.createVerificationPolicy(t);return{verify:(f,p)=>{let h=(0,QK.bundleFromJSON)(f),E=(0,kK.toSignedEntity)(h,p);n.verify(E,c)}}}});var _xe=_(Ni=>{"use strict";Object.defineProperty(Ni,"__esModule",{value:!0});Ni.verify=Ni.sign=Ni.createVerifier=Ni.attest=Ni.VerificationError=Ni.PolicyError=Ni.TUFError=Ni.InternalError=Ni.DEFAULT_REKOR_URL=Ni.DEFAULT_FULCIO_URL=Ni.ValidationError=void 0;var KDt=Ib();Object.defineProperty(Ni,"ValidationError",{enumerable:!0,get:function(){return KDt.ValidationError}});var TK=H7();Object.defineProperty(Ni,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return TK.DEFAULT_FULCIO_URL}});Object.defineProperty(Ni,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return TK.DEFAULT_REKOR_URL}});Object.defineProperty(Ni,"InternalError",{enumerable:!0,get:function(){return TK.InternalError}});var zDt=hL();Object.defineProperty(Ni,"TUFError",{enumerable:!0,get:function(){return zDt.TUFError}});var Uxe=PL();Object.defineProperty(Ni,"PolicyError",{enumerable:!0,get:function(){return Uxe.PolicyError}});Object.defineProperty(Ni,"VerificationError",{enumerable:!0,get:function(){return Uxe.VerificationError}});var xL=Mxe();Object.defineProperty(Ni,"attest",{enumerable:!0,get:function(){return xL.attest}});Object.defineProperty(Ni,"createVerifier",{enumerable:!0,get:function(){return xL.createVerifier}});Object.defineProperty(Ni,"sign",{enumerable:!0,get:function(){return xL.sign}});Object.defineProperty(Ni,"verify",{enumerable:!0,get:function(){return xL.verify}})});Dt();Ge();Dt();var dke=Ie("child_process"),mke=ut(Fd());Yt();var $I=new Map([]);var Gv={};Vt(Gv,{BaseCommand:()=>ft,WorkspaceRequiredError:()=>ar,getCli:()=>bde,getDynamicLibs:()=>Dde,getPluginConfiguration:()=>tC,openWorkspace:()=>eC,pluginCommands:()=>$I,runExit:()=>VR});Yt();var ft=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",{hidden:!0})}validateAndExecute(){if(typeof this.cwd<"u")throw new nt("The --cwd option is ambiguous when used anywhere else than the very first parameter provided in the command line, before even the command path");return super.validateAndExecute()}};Ge();Dt();Yt();var ar=class extends nt{constructor(e,r){let s=J.relative(e,r),a=J.join(e,Ut.fileName);super(`This command can only be run from within a workspace of your project (${s} isn't a workspace of ${a}).`)}};Ge();Dt();eA();wc();pv();Yt();var yat=ut(Ai());Ul();var Dde=()=>new Map([["@yarnpkg/cli",Gv],["@yarnpkg/core",jv],["@yarnpkg/fslib",_2],["@yarnpkg/libzip",fv],["@yarnpkg/parsers",J2],["@yarnpkg/shell",mv],["clipanion",oB],["semver",yat],["typanion",Ea]]);Ge();async function eC(t,e){let{project:r,workspace:s}=await Tt.find(t,e);if(!s)throw new ar(r.cwd,e);return s}Ge();Dt();eA();wc();pv();Yt();var IPt=ut(Ai());Ul();var hq={};Vt(hq,{AddCommand:()=>sC,BinCommand:()=>oC,CacheCleanCommand:()=>aC,ClipanionCommand:()=>pC,ConfigCommand:()=>fC,ConfigGetCommand:()=>lC,ConfigSetCommand:()=>cC,ConfigUnsetCommand:()=>uC,DedupeCommand:()=>AC,EntryCommand:()=>gC,ExecCommand:()=>mC,ExplainCommand:()=>IC,ExplainPeerRequirementsCommand:()=>yC,HelpCommand:()=>hC,InfoCommand:()=>CC,LinkCommand:()=>BC,NodeCommand:()=>vC,PluginCheckCommand:()=>SC,PluginImportCommand:()=>PC,PluginImportSourcesCommand:()=>xC,PluginListCommand:()=>DC,PluginRemoveCommand:()=>kC,PluginRuntimeCommand:()=>QC,RebuildCommand:()=>TC,RemoveCommand:()=>RC,RunCommand:()=>NC,RunIndexCommand:()=>FC,SetResolutionCommand:()=>OC,SetVersionCommand:()=>EC,SetVersionSourcesCommand:()=>bC,UnlinkCommand:()=>LC,UpCommand:()=>MC,VersionCommand:()=>dC,WhyCommand:()=>UC,WorkspaceCommand:()=>qC,WorkspacesListCommand:()=>GC,YarnCommand:()=>wC,dedupeUtils:()=>rF,default:()=>Tct,suggestUtils:()=>Xu});var zye=ut(Fd());Ge();Ge();Ge();Yt();var hye=ut(Vv());Ul();var Xu={};Vt(Xu,{Modifier:()=>W5,Strategy:()=>eF,Target:()=>Jv,WorkspaceModifier:()=>cye,applyModifier:()=>Mlt,extractDescriptorFromPath:()=>Y5,extractRangeModifier:()=>uye,fetchDescriptorFrom:()=>V5,findProjectDescriptors:()=>pye,getModifier:()=>Kv,getSuggestedDescriptors:()=>zv,makeWorkspaceDescriptor:()=>Aye,toWorkspaceModifier:()=>fye});Ge();Ge();Dt();var q5=ut(Ai()),Olt="workspace:",Jv=(s=>(s.REGULAR="dependencies",s.DEVELOPMENT="devDependencies",s.PEER="peerDependencies",s))(Jv||{}),W5=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="",s))(W5||{}),cye=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="*",s))(cye||{}),eF=(n=>(n.KEEP="keep",n.REUSE="reuse",n.PROJECT="project",n.LATEST="latest",n.CACHE="cache",n))(eF||{});function Kv(t,e){return t.exact?"":t.caret?"^":t.tilde?"~":e.configuration.get("defaultSemverRangePrefix")}var Llt=/^([\^~]?)[0-9]+(?:\.[0-9]+){0,2}(?:-\S+)?$/;function uye(t,{project:e}){let r=t.match(Llt);return r?r[1]:e.configuration.get("defaultSemverRangePrefix")}function Mlt(t,e){let{protocol:r,source:s,params:a,selector:n}=G.parseRange(t.range);return q5.default.valid(n)&&(n=`${e}${t.range}`),G.makeDescriptor(t,G.makeRange({protocol:r,source:s,params:a,selector:n}))}function fye(t){switch(t){case"^":return"^";case"~":return"~";case"":return"*";default:throw new Error(`Assertion failed: Unknown modifier: "${t}"`)}}function Aye(t,e){return G.makeDescriptor(t.anchoredDescriptor,`${Olt}${fye(e)}`)}async function pye(t,{project:e,target:r}){let s=new Map,a=n=>{let c=s.get(n.descriptorHash);return c||s.set(n.descriptorHash,c={descriptor:n,locators:[]}),c};for(let n of e.workspaces)if(r==="peerDependencies"){let c=n.manifest.peerDependencies.get(t.identHash);c!==void 0&&a(c).locators.push(n.anchoredLocator)}else{let c=n.manifest.dependencies.get(t.identHash),f=n.manifest.devDependencies.get(t.identHash);r==="devDependencies"?f!==void 0?a(f).locators.push(n.anchoredLocator):c!==void 0&&a(c).locators.push(n.anchoredLocator):c!==void 0?a(c).locators.push(n.anchoredLocator):f!==void 0&&a(f).locators.push(n.anchoredLocator)}return s}async function Y5(t,{cwd:e,workspace:r}){return await _lt(async s=>{J.isAbsolute(t)||(t=J.relative(r.cwd,J.resolve(e,t)),t.match(/^\.{0,2}\//)||(t=`./${t}`));let{project:a}=r,n=await V5(G.makeIdent(null,"archive"),t,{project:r.project,cache:s,workspace:r});if(!n)throw new Error("Assertion failed: The descriptor should have been found");let c=new ki,f=a.configuration.makeResolver(),p=a.configuration.makeFetcher(),h={checksums:a.storedChecksums,project:a,cache:s,fetcher:p,report:c,resolver:f},E=f.bindDescriptor(n,r.anchoredLocator,h),C=G.convertDescriptorToLocator(E),S=await p.fetch(C,h),P=await Ut.find(S.prefixPath,{baseFs:S.packageFs});if(!P.name)throw new Error("Target path doesn't have a name");return G.makeDescriptor(P.name,t)})}function Ult(t){if(t.range==="unknown")return{type:"resolve",range:"latest"};if(Fr.validRange(t.range))return{type:"fixed",range:t.range};if(Mp.test(t.range))return{type:"resolve",range:t.range};let e=t.range.match(/^(?:jsr:|npm:)(.*)/);if(!e)return{type:"fixed",range:t.range};let[,r]=e,s=`${G.stringifyIdent(t)}@`;return r.startsWith(s)&&(r=r.slice(s.length)),Fr.validRange(r)?{type:"fixed",range:t.range}:Mp.test(r)?{type:"resolve",range:t.range}:{type:"fixed",range:t.range}}async function zv(t,{project:e,workspace:r,cache:s,target:a,fixed:n,modifier:c,strategies:f,maxResults:p=1/0}){if(!(p>=0))throw new Error(`Invalid maxResults (${p})`);let h=!n||t.range==="unknown"?Ult(t):{type:"fixed",range:t.range};if(h.type==="fixed")return{suggestions:[{descriptor:t,name:`Use ${G.prettyDescriptor(e.configuration,t)}`,reason:"(unambiguous explicit request)"}],rejections:[]};let E=typeof r<"u"&&r!==null&&r.manifest[a].get(t.identHash)||null,C=[],S=[],P=async I=>{try{await I()}catch(R){S.push(R)}};for(let I of f){if(C.length>=p)break;switch(I){case"keep":await P(async()=>{E&&C.push({descriptor:E,name:`Keep ${G.prettyDescriptor(e.configuration,E)}`,reason:"(no changes)"})});break;case"reuse":await P(async()=>{for(let{descriptor:R,locators:N}of(await pye(t,{project:e,target:a})).values()){if(N.length===1&&N[0].locatorHash===r.anchoredLocator.locatorHash&&f.includes("keep"))continue;let U=`(originally used by ${G.prettyLocator(e.configuration,N[0])}`;U+=N.length>1?` and ${N.length-1} other${N.length>2?"s":""})`:")",C.push({descriptor:R,name:`Reuse ${G.prettyDescriptor(e.configuration,R)}`,reason:U})}});break;case"cache":await P(async()=>{for(let R of e.storedDescriptors.values())R.identHash===t.identHash&&C.push({descriptor:R,name:`Reuse ${G.prettyDescriptor(e.configuration,R)}`,reason:"(already used somewhere in the lockfile)"})});break;case"project":await P(async()=>{if(r.manifest.name!==null&&t.identHash===r.manifest.name.identHash)return;let R=e.tryWorkspaceByIdent(t);if(R===null)return;let N=Aye(R,c);C.push({descriptor:N,name:`Attach ${G.prettyDescriptor(e.configuration,N)}`,reason:`(local workspace at ${he.pretty(e.configuration,R.relativeCwd,he.Type.PATH)})`})});break;case"latest":{let R=e.configuration.get("enableNetwork"),N=e.configuration.get("enableOfflineMode");await P(async()=>{if(a==="peerDependencies")C.push({descriptor:G.makeDescriptor(t,"*"),name:"Use *",reason:"(catch-all peer dependency pattern)"});else if(!R&&!N)C.push({descriptor:null,name:"Resolve from latest",reason:he.pretty(e.configuration,"(unavailable because enableNetwork is toggled off)","grey")});else{let U=await V5(t,h.range,{project:e,cache:s,workspace:r,modifier:c});U&&C.push({descriptor:U,name:`Use ${G.prettyDescriptor(e.configuration,U)}`,reason:`(resolved from ${N?"the cache":"latest"})`})}})}break}}return{suggestions:C.slice(0,p),rejections:S.slice(0,p)}}async function V5(t,e,{project:r,cache:s,workspace:a,preserveModifier:n=!0,modifier:c}){let f=r.configuration.normalizeDependency(G.makeDescriptor(t,e)),p=new ki,h=r.configuration.makeFetcher(),E=r.configuration.makeResolver(),C={project:r,fetcher:h,cache:s,checksums:r.storedChecksums,report:p,cacheOptions:{skipIntegrityCheck:!0}},S={...C,resolver:E,fetchOptions:C},P=E.bindDescriptor(f,a.anchoredLocator,S),I=await E.getCandidates(P,{},S);if(I.length===0)return null;let R=I[0],{protocol:N,source:U,params:W,selector:ee}=G.parseRange(G.convertToManifestRange(R.reference));if(N===r.configuration.get("defaultProtocol")&&(N=null),q5.default.valid(ee)){let ie=ee;if(typeof c<"u")ee=c+ee;else if(n!==!1){let me=typeof n=="string"?n:f.range;ee=uye(me,{project:r})+ee}let ue=G.makeDescriptor(R,G.makeRange({protocol:N,source:U,params:W,selector:ee}));(await E.getCandidates(r.configuration.normalizeDependency(ue),{},S)).length!==1&&(ee=ie)}return G.makeDescriptor(R,G.makeRange({protocol:N,source:U,params:W,selector:ee}))}async function _lt(t){return await ce.mktempPromise(async e=>{let r=ze.create(e);return r.useWithSource(e,{enableMirror:!1,compressionLevel:0},e,{overwrite:!0}),await t(new Kr(e,{configuration:r,check:!1,immutable:!1}))})}var sC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.dev=ge.Boolean("-D,--dev",!1,{description:"Add a package as a dev dependency"});this.peer=ge.Boolean("-P,--peer",!1,{description:"Add a package as a peer dependency"});this.optional=ge.Boolean("-O,--optional",!1,{description:"Add / upgrade a package to an optional regular / peer dependency"});this.preferDev=ge.Boolean("--prefer-dev",!1,{description:"Add / upgrade a package to a dev dependency"});this.interactive=ge.Boolean("-i,--interactive",{description:"Reuse the specified package from other workspaces in the project"});this.cached=ge.Boolean("--cached",!1,{description:"Reuse the highest version already used somewhere within the project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.silent=ge.Boolean("--silent",{hidden:!0});this.packages=ge.Rest()}static{this.paths=[["add"]]}static{this.usage=ot.Usage({description:"add dependencies to the project",details:"\n This command adds a package to the package.json for the nearest workspace.\n\n - If it didn't exist before, the package will by default be added to the regular `dependencies` field, but this behavior can be overriden thanks to the `-D,--dev` flag (which will cause the dependency to be added to the `devDependencies` field instead) and the `-P,--peer` flag (which will do the same but for `peerDependencies`).\n\n - If the package was already listed in your dependencies, it will by default be upgraded whether it's part of your `dependencies` or `devDependencies` (it won't ever update `peerDependencies`, though).\n\n - If set, the `--prefer-dev` flag will operate as a more flexible `-D,--dev` in that it will add the package to your `devDependencies` if it isn't already listed in either `dependencies` or `devDependencies`, but it will also happily upgrade your `dependencies` if that's what you already use (whereas `-D,--dev` would throw an exception).\n\n - If set, the `-O,--optional` flag will add the package to the `optionalDependencies` field and, in combination with the `-P,--peer` flag, it will add the package as an optional peer dependency. If the package was already listed in your `dependencies`, it will be upgraded to `optionalDependencies`. If the package was already listed in your `peerDependencies`, in combination with the `-P,--peer` flag, it will be upgraded to an optional peer dependency: `\"peerDependenciesMeta\": { \"\": { \"optional\": true } }`\n\n - If the added package doesn't specify a range at all its `latest` tag will be resolved and the returned version will be used to generate a new semver range (using the `^` modifier by default unless otherwise configured via the `defaultSemverRangePrefix` configuration, or the `~` modifier if `-T,--tilde` is specified, or no modifier at all if `-E,--exact` is specified). Two exceptions to this rule: the first one is that if the package is a workspace then its local version will be used, and the second one is that if you use `-P,--peer` the default range will be `*` and won't be resolved at all.\n\n - If the added package specifies a range (such as `^1.0.0`, `latest`, or `rc`), Yarn will add this range as-is in the resulting package.json entry (in particular, tags such as `rc` will be encoded as-is rather than being converted into a semver range).\n\n If the `--cached` option is used, Yarn will preferably reuse the highest version already used somewhere within the project, even if through a transitive dependency.\n\n If the `-i,--interactive` option is used (or if the `preferInteractive` settings is toggled on) the command will first try to check whether other workspaces in the project use the specified package and, if so, will offer to reuse them.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n For a compilation of all the supported protocols, please consult the dedicated page from our website: https://yarnpkg.com/protocols.\n ",examples:[["Add a regular package to the current workspace","$0 add lodash"],["Add a specific version for a package to the current workspace","$0 add lodash@1.2.3"],["Add a package from a GitHub repository (the master branch) to the current workspace using a URL","$0 add lodash@https://github.com/lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol","$0 add lodash@github:lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol (shorthand)","$0 add lodash@lodash/lodash"],["Add a package from a specific branch of a GitHub repository to the current workspace using the GitHub protocol (shorthand)","$0 add lodash-es@lodash/lodash#es"],["Add a local package (gzipped tarball format) to the current workspace","$0 add local-package-name@file:../path/to/local-package-name-v0.1.2.tgz"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=f||r.get("preferReuse"),h=Kv(this,s),E=[p?"reuse":void 0,"project",this.cached?"cache":void 0,"latest"].filter(W=>typeof W<"u"),C=f?1/0:1,S=W=>{let ee=G.tryParseDescriptor(W.slice(4));return ee?ee.range==="unknown"?G.makeDescriptor(ee,`jsr:${G.stringifyIdent(ee)}@latest`):G.makeDescriptor(ee,`jsr:${ee.range}`):null},P=await Promise.all(this.packages.map(async W=>{let ee=W.match(/^\.{0,2}\//)?await Y5(W,{cwd:this.context.cwd,workspace:a}):W.startsWith("jsr:")?S(W):G.tryParseDescriptor(W),ie=W.match(/^(https?:|git@github)/);if(ie)throw new nt(`It seems you are trying to add a package using a ${he.pretty(r,`${ie[0]}...`,he.Type.RANGE)} url; we now require package names to be explicitly specified. +Try running the command again with the package name prefixed: ${he.pretty(r,"yarn add",he.Type.CODE)} ${he.pretty(r,G.makeDescriptor(G.makeIdent(null,"my-package"),`${ie[0]}...`),he.Type.DESCRIPTOR)}`);if(!ee)throw new nt(`The ${he.pretty(r,W,he.Type.CODE)} string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`);let ue=Hlt(a,ee,{dev:this.dev,peer:this.peer,preferDev:this.preferDev,optional:this.optional});return await Promise.all(ue.map(async me=>{let pe=await zv(ee,{project:s,workspace:a,cache:n,fixed:c,target:me,modifier:h,strategies:E,maxResults:C});return{request:ee,suggestedDescriptors:pe,target:me}}))})).then(W=>W.flat()),I=await lA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async W=>{for(let{request:ee,suggestedDescriptors:{suggestions:ie,rejections:ue}}of P)if(ie.filter(me=>me.descriptor!==null).length===0){let[me]=ue;if(typeof me>"u")throw new Error("Assertion failed: Expected an error to have been set");s.configuration.get("enableNetwork")?W.reportError(27,`${G.prettyDescriptor(r,ee)} can't be resolved to a satisfying range`):W.reportError(27,`${G.prettyDescriptor(r,ee)} can't be resolved to a satisfying range (note: network resolution has been disabled)`),W.reportSeparator(),W.reportExceptionOnce(me)}});if(I.hasErrors())return I.exitCode();let R=!1,N=[],U=[];for(let{suggestedDescriptors:{suggestions:W},target:ee}of P){let ie,ue=W.filter(Be=>Be.descriptor!==null),le=ue[0].descriptor,me=ue.every(Be=>G.areDescriptorsEqual(Be.descriptor,le));ue.length===1||me?ie=le:(R=!0,{answer:ie}=await(0,hye.prompt)({type:"select",name:"answer",message:"Which range do you want to use?",choices:W.map(({descriptor:Be,name:Ce,reason:g})=>Be?{name:Ce,hint:g,descriptor:Be}:{name:Ce,hint:g,disabled:!0}),onCancel:()=>process.exit(130),result(Be){return this.find(Be,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let pe=a.manifest[ee].get(ie.identHash);(typeof pe>"u"||pe.descriptorHash!==ie.descriptorHash)&&(a.manifest[ee].set(ie.identHash,ie),this.optional&&(ee==="dependencies"?a.manifest.ensureDependencyMeta({...ie,range:"unknown"}).optional=!0:ee==="peerDependencies"&&(a.manifest.ensurePeerDependencyMeta({...ie,range:"unknown"}).optional=!0)),typeof pe>"u"?N.push([a,ee,ie,E]):U.push([a,ee,pe,ie]))}return await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyAddition,N),await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyReplacement,U),R&&this.context.stdout.write(` +`),await s.installWithNewReport({json:this.json,stdout:this.context.stdout,quiet:this.context.quiet},{cache:n,mode:this.mode})}};function Hlt(t,e,{dev:r,peer:s,preferDev:a,optional:n}){let c=t.manifest.dependencies.has(e.identHash),f=t.manifest.devDependencies.has(e.identHash),p=t.manifest.peerDependencies.has(e.identHash);if((r||s)&&c)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a regular dependency - remove the -D,-P flags or remove it from your dependencies first`);if(!r&&!s&&p)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - use either of -D or -P, or remove it from your peer dependencies first`);if(n&&f)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a dev dependency - remove the -O flag or remove it from your dev dependencies first`);if(n&&!s&&p)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - remove the -O flag or add the -P flag or remove it from your peer dependencies first`);if((r||a)&&n)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" cannot simultaneously be a dev dependency and an optional dependency`);let h=[];return s&&h.push("peerDependencies"),(r||a)&&h.push("devDependencies"),n&&h.push("dependencies"),h.length>0?h:f?["devDependencies"]:p?["peerDependencies"]:["dependencies"]}Ge();Ge();Yt();var oC=class extends ft{constructor(){super(...arguments);this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Print both the binary name and the locator of the package that provides the binary"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.name=ge.String({required:!1})}static{this.paths=[["bin"]]}static{this.usage=ot.Usage({description:"get the path to a binary script",details:` + When used without arguments, this command will print the list of all the binaries available in the current workspace. Adding the \`-v,--verbose\` flag will cause the output to contain both the binary name and the locator of the package that provides the binary. + + When an argument is specified, this command will just print the path to the binary on the standard output and exit. Note that the reported path may be stored within a zip archive. + `,examples:[["List all the available binaries","$0 bin"],["Print the path to a specific binary","$0 bin eslint"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);if(await s.restoreInstallState(),this.name){let f=(await In.getPackageAccessibleBinaries(a,{project:s})).get(this.name);if(!f)throw new nt(`Couldn't find a binary named "${this.name}" for package "${G.prettyLocator(r,a)}"`);let[,p]=f;return this.context.stdout.write(`${p} +`),0}return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async c=>{let f=await In.getPackageAccessibleBinaries(a,{project:s}),h=Array.from(f.keys()).reduce((E,C)=>Math.max(E,C.length),0);for(let[E,[C,S]]of f)c.reportJson({name:E,source:G.stringifyIdent(C),path:S});if(this.verbose)for(let[E,[C]]of f)c.reportInfo(null,`${E.padEnd(h," ")} ${G.prettyLocator(r,C)}`);else for(let E of f.keys())c.reportInfo(null,E)})).exitCode()}};Ge();Dt();Yt();var aC=class extends ft{constructor(){super(...arguments);this.mirror=ge.Boolean("--mirror",!1,{description:"Remove the global cache files instead of the local cache files"});this.all=ge.Boolean("--all",!1,{description:"Remove both the global cache files and the local cache files of the current project"})}static{this.paths=[["cache","clean"],["cache","clear"]]}static{this.usage=ot.Usage({description:"remove the shared cache files",details:` + This command will remove all the files from the cache. + `,examples:[["Remove all the local archives","$0 cache clean"],["Remove all the archives stored in the ~/.yarn directory","$0 cache clean --mirror"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(!r.get("enableCacheClean"))throw new nt("Cache cleaning is currently disabled. To enable it, set `enableCacheClean: true` in your configuration file. Note: Cache cleaning is typically not required and should be avoided when using Zero-Installs.");let s=await Kr.find(r);return(await Ot.start({configuration:r,stdout:this.context.stdout},async()=>{let n=(this.all||this.mirror)&&s.mirrorCwd!==null,c=!this.mirror;n&&(await ce.removePromise(s.mirrorCwd),await r.triggerHook(f=>f.cleanGlobalArtifacts,r)),c&&await ce.removePromise(s.cwd)})).exitCode()}};Ge();Yt();ql();var J5=Ie("util"),lC=class extends ft{constructor(){super(...arguments);this.why=ge.Boolean("--why",!1,{description:"Print the explanation for why a setting has its value"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.unsafe=ge.Boolean("--no-redacted",!1,{description:"Don't redact secrets (such as tokens) from the output"});this.name=ge.String()}static{this.paths=[["config","get"]]}static{this.usage=ot.Usage({description:"read a configuration settings",details:` + This command will print a configuration setting. + + Secrets (such as tokens) will be redacted from the output by default. If this behavior isn't desired, set the \`--no-redacted\` to get the untransformed value. + `,examples:[["Print a simple configuration setting","yarn config get yarnPath"],["Print a complex configuration setting","yarn config get packageExtensions"],["Print a nested field from the configuration",`yarn config get 'npmScopes["my-company"].npmRegistryServer'`],["Print a token from the configuration","yarn config get npmAuthToken --no-redacted"],["Print a configuration setting as JSON","yarn config get packageExtensions --json"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=this.name.replace(/[.[].*$/,""),a=this.name.replace(/^[^.[]*/,"");if(typeof r.settings.get(s)>"u")throw new nt(`Couldn't find a configuration settings named "${s}"`);let c=r.getSpecial(s,{hideSecrets:!this.unsafe,getNativePaths:!0}),f=je.convertMapsToIndexableObjects(c),p=a?va(f,a):f,h=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async E=>{E.reportJson(p)});if(!this.json){if(typeof p=="string")return this.context.stdout.write(`${p} +`),h.exitCode();J5.inspect.styles.name="cyan",this.context.stdout.write(`${(0,J5.inspect)(p,{depth:1/0,colors:r.get("enableColors"),compact:!1})} +`)}return h.exitCode()}};Ge();Yt();ql();var K5=Ie("util"),cC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Set complex configuration settings to JSON values"});this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String();this.value=ge.String()}static{this.paths=[["config","set"]]}static{this.usage=ot.Usage({description:"change a configuration settings",details:` + This command will set a configuration setting. + + When used without the \`--json\` flag, it can only set a simple configuration setting (a string, a number, or a boolean). + + When used with the \`--json\` flag, it can set both simple and complex configuration settings, including Arrays and Objects. + `,examples:[["Set a simple configuration setting (a string, a number, or a boolean)","yarn config set initScope myScope"],["Set a simple configuration setting (a string, a number, or a boolean) using the `--json` flag",'yarn config set initScope --json \\"myScope\\"'],["Set a complex configuration setting (an Array) using the `--json` flag",`yarn config set unsafeHttpWhitelist --json '["*.example.com", "example.com"]'`],["Set a complex configuration setting (an Object) using the `--json` flag",`yarn config set packageExtensions --json '{ "@babel/parser@*": { "dependencies": { "@babel/types": "*" } } }'`],["Set a nested configuration setting",'yarn config set npmScopes.company.npmRegistryServer "https://npm.example.com"'],["Set a nested configuration setting using indexed access for non-simple keys",`yarn config set 'npmRegistries["//npm.example.com"].npmAuthToken' "ffffffff-ffff-ffff-ffff-ffffffffffff"`]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);if(a==="enableStrictSettings")throw new nt("This setting only affects the file it's in, and thus cannot be set from the CLI");let f=this.json?JSON.parse(this.value):this.value;await(this.home?I=>ze.updateHomeConfiguration(I):I=>ze.updateConfiguration(s(),I))(I=>{if(n){let R=f0(I);return Jd(R,this.name,f),R}else return{...I,[a]:f}});let E=(await ze.find(this.context.cwd,this.context.plugins)).getSpecial(a,{hideSecrets:!0,getNativePaths:!0}),C=je.convertMapsToIndexableObjects(E),S=n?va(C,n):C;return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async I=>{K5.inspect.styles.name="cyan",I.reportInfo(0,`Successfully set ${this.name} to ${(0,K5.inspect)(S,{depth:1/0,colors:r.get("enableColors"),compact:!1})}`)})).exitCode()}};Ge();Yt();ql();var uC=class extends ft{constructor(){super(...arguments);this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String()}static{this.paths=[["config","unset"]]}static{this.usage=ot.Usage({description:"unset a configuration setting",details:` + This command will unset a configuration setting. + `,examples:[["Unset a simple configuration setting","yarn config unset initScope"],["Unset a complex configuration setting","yarn config unset packageExtensions"],["Unset a nested configuration setting","yarn config unset npmScopes.company.npmRegistryServer"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);let f=this.home?h=>ze.updateHomeConfiguration(h):h=>ze.updateConfiguration(s(),h);return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async h=>{let E=!1;await f(C=>{if(!vB(C,this.name))return h.reportWarning(0,`Configuration doesn't contain setting ${this.name}; there is nothing to unset`),E=!0,C;let S=n?f0(C):{...C};return A0(S,this.name),S}),E||h.reportInfo(0,`Successfully unset ${this.name}`)})).exitCode()}};Ge();Dt();Yt();var tF=Ie("util"),fC=class extends ft{constructor(){super(...arguments);this.noDefaults=ge.Boolean("--no-defaults",!1,{description:"Omit the default values from the display"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.verbose=ge.Boolean("-v,--verbose",{hidden:!0});this.why=ge.Boolean("--why",{hidden:!0});this.names=ge.Rest()}static{this.paths=[["config"]]}static{this.usage=ot.Usage({description:"display the current configuration",details:` + This command prints the current active configuration settings. + `,examples:[["Print the active configuration settings","$0 config"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins,{strict:!1}),s=await SI({configuration:r,stdout:this.context.stdout,forceError:this.json},[{option:this.verbose,message:"The --verbose option is deprecated, the settings' descriptions are now always displayed"},{option:this.why,message:"The --why option is deprecated, the settings' sources are now always displayed"}]);if(s!==null)return s;let a=this.names.length>0?[...new Set(this.names)].sort():[...r.settings.keys()].sort(),n,c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async f=>{if(r.invalid.size>0&&!this.json){for(let[p,h]of r.invalid)f.reportError(34,`Invalid configuration key "${p}" in ${h}`);f.reportSeparator()}if(this.json)for(let p of a){if(this.noDefaults&&!r.sources.has(p))continue;let h=r.settings.get(p);typeof h>"u"&&f.reportError(34,`No configuration key named "${p}"`);let E=r.getSpecial(p,{hideSecrets:!0,getNativePaths:!0}),C=r.sources.get(p)??"",S=C&&C[0]!=="<"?fe.fromPortablePath(C):C;f.reportJson({key:p,effective:E,source:S,...h})}else{let p={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},h={},E={children:h};for(let C of a){if(this.noDefaults&&!r.sources.has(C))continue;let S=r.settings.get(C),P=r.sources.get(C)??"",I=r.getSpecial(C,{hideSecrets:!0,getNativePaths:!0}),R={Description:{label:"Description",value:he.tuple(he.Type.MARKDOWN,{text:S.description,format:this.cli.format(),paragraphs:!1})},Source:{label:"Source",value:he.tuple(P[0]==="<"?he.Type.CODE:he.Type.PATH,P)}};h[C]={value:he.tuple(he.Type.CODE,C),children:R};let N=(U,W)=>{for(let[ee,ie]of W)if(ie instanceof Map){let ue={};U[ee]={children:ue},N(ue,ie)}else U[ee]={label:ee,value:he.tuple(he.Type.NO_HINT,(0,tF.inspect)(ie,p))}};I instanceof Map?N(R,I):R.Value={label:"Value",value:he.tuple(he.Type.NO_HINT,(0,tF.inspect)(I,p))}}a.length!==1&&(n=void 0),xs.emitTree(E,{configuration:r,json:this.json,stdout:this.context.stdout,separators:2})}});if(!this.json&&typeof n<"u"){let f=a[0],p=(0,tF.inspect)(r.getSpecial(f,{hideSecrets:!0,getNativePaths:!0}),{colors:r.get("enableColors")});this.context.stdout.write(` +`),this.context.stdout.write(`${p} +`)}return c.exitCode()}};Ge();Yt();Ul();var rF={};Vt(rF,{Strategy:()=>Xv,acceptedStrategies:()=>jlt,dedupe:()=>z5});Ge();Ge();var gye=ut(Go()),Xv=(e=>(e.HIGHEST="highest",e))(Xv||{}),jlt=new Set(Object.values(Xv)),Glt={highest:async(t,e,{resolver:r,fetcher:s,resolveOptions:a,fetchOptions:n})=>{let c=new Map;for(let[p,h]of t.storedResolutions){let E=t.storedDescriptors.get(p);if(typeof E>"u")throw new Error(`Assertion failed: The descriptor (${p}) should have been registered`);je.getSetWithDefault(c,E.identHash).add(h)}let f=new Map(je.mapAndFilter(t.storedDescriptors.values(),p=>G.isVirtualDescriptor(p)?je.mapAndFilter.skip:[p.descriptorHash,je.makeDeferred()]));for(let p of t.storedDescriptors.values()){let h=f.get(p.descriptorHash);if(typeof h>"u")throw new Error(`Assertion failed: The descriptor (${p.descriptorHash}) should have been registered`);let E=t.storedResolutions.get(p.descriptorHash);if(typeof E>"u")throw new Error(`Assertion failed: The resolution (${p.descriptorHash}) should have been registered`);let C=t.originalPackages.get(E);if(typeof C>"u")throw new Error(`Assertion failed: The package (${E}) should have been registered`);Promise.resolve().then(async()=>{let S=r.getResolutionDependencies(p,a),P=Object.fromEntries(await je.allSettledSafe(Object.entries(S).map(async([ee,ie])=>{let ue=f.get(ie.descriptorHash);if(typeof ue>"u")throw new Error(`Assertion failed: The descriptor (${ie.descriptorHash}) should have been registered`);let le=await ue.promise;if(!le)throw new Error("Assertion failed: Expected the dependency to have been through the dedupe process itself");return[ee,le.updatedPackage]})));if(e.length&&!gye.default.isMatch(G.stringifyIdent(p),e)||!r.shouldPersistResolution(C,a))return C;let I=c.get(p.identHash);if(typeof I>"u")throw new Error(`Assertion failed: The resolutions (${p.identHash}) should have been registered`);if(I.size===1)return C;let R=[...I].map(ee=>{let ie=t.originalPackages.get(ee);if(typeof ie>"u")throw new Error(`Assertion failed: The package (${ee}) should have been registered`);return ie}),N=await r.getSatisfying(p,P,R,a),U=N.locators?.[0];if(typeof U>"u"||!N.sorted)return C;let W=t.originalPackages.get(U.locatorHash);if(typeof W>"u")throw new Error(`Assertion failed: The package (${U.locatorHash}) should have been registered`);return W}).then(async S=>{let P=await t.preparePackage(S,{resolver:r,resolveOptions:a});h.resolve({descriptor:p,currentPackage:C,updatedPackage:S,resolvedPackage:P})}).catch(S=>{h.reject(S)})}return[...f.values()].map(p=>p.promise)}};async function z5(t,{strategy:e,patterns:r,cache:s,report:a}){let{configuration:n}=t,c=new ki,f=n.makeResolver(),p=n.makeFetcher(),h={cache:s,checksums:t.storedChecksums,fetcher:p,project:t,report:c,cacheOptions:{skipIntegrityCheck:!0}},E={project:t,resolver:f,report:c,fetchOptions:h};return await a.startTimerPromise("Deduplication step",async()=>{let C=Glt[e],S=await C(t,r,{resolver:f,resolveOptions:E,fetcher:p,fetchOptions:h}),P=Ao.progressViaCounter(S.length);await a.reportProgress(P);let I=0;await Promise.all(S.map(U=>U.then(W=>{if(W===null||W.currentPackage.locatorHash===W.updatedPackage.locatorHash)return;I++;let{descriptor:ee,currentPackage:ie,updatedPackage:ue}=W;a.reportInfo(0,`${G.prettyDescriptor(n,ee)} can be deduped from ${G.prettyLocator(n,ie)} to ${G.prettyLocator(n,ue)}`),a.reportJson({descriptor:G.stringifyDescriptor(ee),currentResolution:G.stringifyLocator(ie),updatedResolution:G.stringifyLocator(ue)}),t.storedResolutions.set(ee.descriptorHash,ue.locatorHash)}).finally(()=>P.tick())));let R;switch(I){case 0:R="No packages";break;case 1:R="One package";break;default:R=`${I} packages`}let N=he.pretty(n,e,he.Type.CODE);return a.reportInfo(0,`${R} can be deduped using the ${N} strategy`),I})}var AC=class extends ft{constructor(){super(...arguments);this.strategy=ge.String("-s,--strategy","highest",{description:"The strategy to use when deduping dependencies",validator:fo(Xv)});this.check=ge.Boolean("-c,--check",!1,{description:"Exit with exit code 1 when duplicates are found, without persisting the dependency tree"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["dedupe"]]}static{this.usage=ot.Usage({description:"deduplicate dependencies with overlapping ranges",details:"\n Duplicates are defined as descriptors with overlapping ranges being resolved and locked to different locators. They are a natural consequence of Yarn's deterministic installs, but they can sometimes pile up and unnecessarily increase the size of your project.\n\n This command dedupes dependencies in the current project using different strategies (only one is implemented at the moment):\n\n - `highest`: Reuses (where possible) the locators with the highest versions. This means that dependencies can only be upgraded, never downgraded. It's also guaranteed that it never takes more than a single pass to dedupe the entire dependency tree.\n\n **Note:** Even though it never produces a wrong dependency tree, this command should be used with caution, as it modifies the dependency tree, which can sometimes cause problems when packages don't strictly follow semver recommendations. Because of this, it is recommended to also review the changes manually.\n\n If set, the `-c,--check` flag will only report the found duplicates, without persisting the modified dependency tree. If changes are found, the command will exit with a non-zero exit code, making it suitable for CI purposes.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n ### In-depth explanation:\n\n Yarn doesn't deduplicate dependencies by default, otherwise installs wouldn't be deterministic and the lockfile would be useless. What it actually does is that it tries to not duplicate dependencies in the first place.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@*`will cause Yarn to reuse `foo@2.3.4`, even if the latest `foo` is actually `foo@2.10.14`, thus preventing unnecessary duplication.\n\n Duplication happens when Yarn can't unlock dependencies that have already been locked inside the lockfile.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@2.10.14` will cause Yarn to install `foo@2.10.14` because the existing resolution doesn't satisfy the range `2.10.14`. This behavior can lead to (sometimes) unwanted duplication, since now the lockfile contains 2 separate resolutions for the 2 `foo` descriptors, even though they have overlapping ranges, which means that the lockfile can be simplified so that both descriptors resolve to `foo@2.10.14`.\n ",examples:[["Dedupe all packages","$0 dedupe"],["Dedupe all packages using a specific strategy","$0 dedupe --strategy highest"],["Dedupe a specific package","$0 dedupe lodash"],["Dedupe all packages with the `@babel/*` scope","$0 dedupe '@babel/*'"],["Check for duplicates (can be used as a CI step)","$0 dedupe --check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=await Kr.find(r);await s.restoreInstallState({restoreResolutions:!1});let n=0,c=await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout,json:this.json},async f=>{n=await z5(s,{strategy:this.strategy,patterns:this.patterns,cache:a,report:f})});return c.hasErrors()?c.exitCode():this.check?n?1:0:await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:a,mode:this.mode})}};Ge();Yt();var pC=class extends ft{static{this.paths=[["--clipanion=definitions"]]}async execute(){let{plugins:e}=await ze.find(this.context.cwd,this.context.plugins),r=[];for(let c of e){let{commands:f}=c[1];if(f){let h=Ca.from(f).definitions();r.push([c[0],h])}}let s=this.cli.definitions(),a=(c,f)=>c.split(" ").slice(1).join()===f.split(" ").slice(1).join(),n=dye()["@yarnpkg/builder"].bundles.standard;for(let c of r){let f=c[1];for(let p of f)s.find(h=>a(h.path,p.path)).plugin={name:c[0],isDefault:n.includes(c[0])}}this.context.stdout.write(`${JSON.stringify(s,null,2)} +`)}};var hC=class extends ft{static{this.paths=[["help"],["--help"],["-h"]]}async execute(){this.context.stdout.write(this.cli.usage(null))}};Ge();Dt();Yt();var gC=class extends ft{constructor(){super(...arguments);this.leadingArgument=ge.String();this.args=ge.Proxy()}async execute(){if(this.leadingArgument.match(/[\\/]/)&&!G.tryParseIdent(this.leadingArgument)){let r=J.resolve(this.context.cwd,fe.toPortablePath(this.leadingArgument));return await this.cli.run(this.args,{cwd:r})}else return await this.cli.run(["run",this.leadingArgument,...this.args])}};Ge();var dC=class extends ft{static{this.paths=[["-v"],["--version"]]}async execute(){this.context.stdout.write(`${fn||""} +`)}};Ge();Ge();Yt();var mC=class extends ft{constructor(){super(...arguments);this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["exec"]]}static{this.usage=ot.Usage({description:"execute a shell script",details:` + This command simply executes a shell script within the context of the root directory of the active workspace using the portable shell. + + It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). + `,examples:[["Execute a single shell command","$0 exec echo Hello World"],["Execute a shell script",'$0 exec "tsc & babel src --out-dir lib"']]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState(),await In.executePackageShellcode(a,this.commandName,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,project:s})}};Ge();Yt();Ul();var yC=class extends ft{constructor(){super(...arguments);this.hash=ge.String({required:!1,validator:Nx(wE(),[Z2(/^p[0-9a-f]{6}$/)])})}static{this.paths=[["explain","peer-requirements"]]}static{this.usage=ot.Usage({description:"explain a set of peer requirements",details:` + A peer requirement represents all peer requests that a subject must satisfy when providing a requested package to requesters. + + When the hash argument is specified, this command prints a detailed explanation of the peer requirement corresponding to the hash and whether it is satisfied or not. + + When used without arguments, this command lists all peer requirements and the corresponding hash that can be used to get detailed information about a given requirement. + + **Note:** A hash is a seven-letter code consisting of the letter 'p' followed by six characters that can be obtained from peer dependency warnings or from the list of all peer requirements(\`yarn explain peer-requirements\`). + `,examples:[["Explain the corresponding peer requirement for a hash","$0 explain peer-requirements p1a4ed"],["List all peer requirements","$0 explain peer-requirements"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState({restoreResolutions:!1}),await s.applyLightResolution(),typeof this.hash<"u"?await Wlt(this.hash,s,{stdout:this.context.stdout}):await Ylt(s,{stdout:this.context.stdout})}};async function Wlt(t,e,r){let s=e.peerRequirementNodes.get(t);if(typeof s>"u")throw new Error(`No peerDependency requirements found for hash: "${t}"`);let a=new Set,n=p=>a.has(p.requester.locatorHash)?{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:p.children.size>0?[{value:he.tuple(he.Type.NO_HINT,"...")}]:[]}:(a.add(p.requester.locatorHash),{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:Object.fromEntries(Array.from(p.children.values(),h=>[G.stringifyLocator(h.requester),n(h)]))}),c=e.peerWarnings.find(p=>p.hash===t);return(await Ot.start({configuration:e.configuration,stdout:r.stdout,includeFooter:!1,includePrefix:!1},async p=>{let h=he.mark(e.configuration),E=c?h.Cross:h.Check;if(p.reportInfo(0,`Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} is requested to provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} by its descendants`),p.reportSeparator(),p.reportInfo(0,he.pretty(e.configuration,s.subject,he.Type.LOCATOR)),xs.emitTree({children:Object.fromEntries(Array.from(s.requests.values(),C=>[G.stringifyLocator(C.requester),n(C)]))},{configuration:e.configuration,stdout:r.stdout,json:!1}),p.reportSeparator(),s.provided.range==="missing:"){let C=c?"":" , but all peer requests are optional";p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} does not provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)}${C}.`)}else{let C=e.storedResolutions.get(s.provided.descriptorHash);if(!C)throw new Error("Assertion failed: Expected the descriptor to be registered");let S=e.storedPackages.get(C);if(!S)throw new Error("Assertion failed: Expected the package to be registered");p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} provides ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} with version ${G.prettyReference(e.configuration,S.version??"0.0.0")}, ${c?"which does not satisfy all requests.":"which satisfies all requests"}`),c?.type===3&&(c.range?p.reportInfo(0,` The combined requested range is ${he.pretty(e.configuration,c.range,he.Type.RANGE)}`):p.reportInfo(0," Unfortunately, the requested ranges have no overlap"))}})).exitCode()}async function Ylt(t,e){return(await Ot.start({configuration:t.configuration,stdout:e.stdout,includeFooter:!1,includePrefix:!1},async s=>{let a=he.mark(t.configuration),n=je.sortMap(t.peerRequirementNodes,[([,c])=>G.stringifyLocator(c.subject),([,c])=>G.stringifyIdent(c.ident)]);for(let[,c]of n.values()){if(!c.root)continue;let f=t.peerWarnings.find(E=>E.hash===c.hash),p=[...G.allPeerRequests(c)],h;if(p.length>2?h=` and ${p.length-1} other dependencies`:p.length===2?h=" and 1 other dependency":h="",c.provided.range!=="missing:"){let E=t.storedResolutions.get(c.provided.descriptorHash);if(!E)throw new Error("Assertion failed: Expected the resolution to have been registered");let C=t.storedPackages.get(E);if(!C)throw new Error("Assertion failed: Expected the provided package to have been registered");let S=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${G.prettyLocator(t.configuration,c.subject)} provides ${G.prettyLocator(t.configuration,C)} to ${G.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,S):s.reportInfo(0,S)}else{let E=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${G.prettyLocator(t.configuration,c.subject)} doesn't provide ${G.prettyIdent(t.configuration,c.ident)} to ${G.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,E):s.reportInfo(0,E)}}})).exitCode()}Ge();Yt();Ul();Ge();Ge();Dt();Yt();var mye=ut(Ai()),EC=class extends ft{constructor(){super(...arguments);this.useYarnPath=ge.Boolean("--yarn-path",{description:"Set the yarnPath setting even if the version can be accessed by Corepack"});this.onlyIfNeeded=ge.Boolean("--only-if-needed",!1,{description:"Only lock the Yarn version if it isn't already locked"});this.version=ge.String()}static{this.paths=[["set","version"]]}static{this.usage=ot.Usage({description:"lock the Yarn version used by the project",details:"\n This command will set a specific release of Yarn to be used by Corepack: https://nodejs.org/api/corepack.html.\n\n By default it only will set the `packageManager` field at the root of your project, but if the referenced release cannot be represented this way, if you already have `yarnPath` configured, or if you set the `--yarn-path` command line flag, then the release will also be downloaded from the Yarn GitHub repository, stored inside your project, and referenced via the `yarnPath` settings from your project `.yarnrc.yml` file.\n\n A very good use case for this command is to enforce the version of Yarn used by any single member of your team inside the same project - by doing this you ensure that you have control over Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting different behavior.\n\n The version specifier can be:\n\n - a tag:\n - `latest` / `berry` / `stable` -> the most recent stable berry (`>=2.0.0`) release\n - `canary` -> the most recent canary (release candidate) berry (`>=2.0.0`) release\n - `classic` -> the most recent classic (`^0.x || ^1.x`) release\n\n - a semver range (e.g. `2.x`) -> the most recent version satisfying the range (limited to berry releases)\n\n - a semver version (e.g. `2.4.1`, `1.22.1`)\n\n - a local file referenced through either a relative or absolute path\n\n - `self` -> the version used to invoke the command\n ",examples:[["Download the latest release from the Yarn repository","$0 set version latest"],["Download the latest canary release from the Yarn repository","$0 set version canary"],["Download the latest classic release from the Yarn repository","$0 set version classic"],["Download the most recent Yarn 3 build","$0 set version 3.x"],["Download a specific Yarn 2 build","$0 set version 2.0.0-rc.30"],["Switch back to a specific Yarn 1 release","$0 set version 1.22.1"],["Use a release from the local filesystem","$0 set version ./yarn.cjs"],["Use a release from a URL","$0 set version https://repo.yarnpkg.com/3.1.0/packages/yarnpkg-cli/bin/yarn.js"],["Download the version used to invoke the command","$0 set version self"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(this.onlyIfNeeded&&r.get("yarnPath")){let f=r.sources.get("yarnPath");if(!f)throw new Error("Assertion failed: Expected 'yarnPath' to have a source");let p=r.projectCwd??r.startingCwd;if(J.contains(p,f))return 0}let s=()=>{if(typeof fn>"u")throw new nt("The --install flag can only be used without explicit version specifier from the Yarn CLI");return`file://${process.argv[1]}`},a,n=(f,p)=>({version:p,url:f.replace(/\{\}/g,p)});if(this.version==="self")a={url:s(),version:fn??"self"};else if(this.version==="latest"||this.version==="berry"||this.version==="stable")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Zv(r,"stable"));else if(this.version==="canary")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Zv(r,"canary"));else if(this.version==="classic")a={url:"https://classic.yarnpkg.com/latest.js",version:"classic"};else if(this.version.match(/^https?:/))a={url:this.version,version:"remote"};else if(this.version.match(/^\.{0,2}[\\/]/)||fe.isAbsolute(this.version))a={url:`file://${J.resolve(fe.toPortablePath(this.version))}`,version:"file"};else if(Fr.satisfiesWithPrereleases(this.version,">=2.0.0"))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",this.version);else if(Fr.satisfiesWithPrereleases(this.version,"^0.x || ^1.x"))a=n("https://github.com/yarnpkg/yarn/releases/download/v{}/yarn-{}.js",this.version);else if(Fr.validRange(this.version))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Vlt(r,this.version));else throw new nt(`Invalid version descriptor "${this.version}"`);return(await Ot.start({configuration:r,stdout:this.context.stdout,includeLogs:!this.context.quiet},async f=>{let p=async()=>{let h="file://";return a.url.startsWith(h)?(f.reportInfo(0,`Retrieving ${he.pretty(r,a.url,he.Type.PATH)}`),await ce.readFilePromise(a.url.slice(h.length))):(f.reportInfo(0,`Downloading ${he.pretty(r,a.url,he.Type.URL)}`),await nn.get(a.url,{configuration:r}))};await X5(r,a.version,p,{report:f,useYarnPath:this.useYarnPath})})).exitCode()}};async function Vlt(t,e){let s=(await nn.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0})).tags.filter(a=>Fr.satisfiesWithPrereleases(a,e));if(s.length===0)throw new nt(`No matching release found for range ${he.pretty(t,e,he.Type.RANGE)}.`);return s[0]}async function Zv(t,e){let r=await nn.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0});if(!r.latest[e])throw new nt(`Tag ${he.pretty(t,e,he.Type.RANGE)} not found`);return r.latest[e]}async function X5(t,e,r,{report:s,useYarnPath:a}){let n,c=async()=>(typeof n>"u"&&(n=await r()),n);if(e===null){let ee=await c();await ce.mktempPromise(async ie=>{let ue=J.join(ie,"yarn.cjs");await ce.writeFilePromise(ue,ee);let{stdout:le}=await qr.execvp(process.execPath,[fe.fromPortablePath(ue),"--version"],{cwd:ie,env:{...t.env,YARN_IGNORE_PATH:"1"}});if(e=le.trim(),!mye.default.valid(e))throw new Error(`Invalid semver version. ${he.pretty(t,"yarn --version",he.Type.CODE)} returned: +${e}`)})}let f=t.projectCwd??t.startingCwd,p=J.resolve(f,".yarn/releases"),h=J.resolve(p,`yarn-${e}.cjs`),E=J.relative(t.startingCwd,h),C=je.isTaggedYarnVersion(e),S=t.get("yarnPath"),P=!C,I=P||!!S||!!a;if(a===!1){if(P)throw new jt(0,"You explicitly opted out of yarnPath usage in your command line, but the version you specified cannot be represented by Corepack");I=!1}else!I&&!process.env.COREPACK_ROOT&&(s.reportWarning(0,`You don't seem to have ${he.applyHyperlink(t,"Corepack","https://nodejs.org/api/corepack.html")} enabled; we'll have to rely on ${he.applyHyperlink(t,"yarnPath","https://yarnpkg.com/configuration/yarnrc#yarnPath")} instead`),I=!0);if(I){let ee=await c();s.reportInfo(0,`Saving the new release in ${he.pretty(t,E,"magenta")}`),await ce.removePromise(J.dirname(h)),await ce.mkdirPromise(J.dirname(h),{recursive:!0}),await ce.writeFilePromise(h,ee,{mode:493}),await ze.updateConfiguration(f,{yarnPath:J.relative(f,h)})}else await ce.removePromise(J.dirname(h)),await ze.updateConfiguration(f,{yarnPath:ze.deleteProperty});let R=await Ut.tryFind(f)||new Ut;R.packageManager=`yarn@${C?e:await Zv(t,"stable")}`;let N={};R.exportTo(N);let U=J.join(f,Ut.fileName),W=`${JSON.stringify(N,null,R.indent)} +`;return await ce.changeFilePromise(U,W,{automaticNewlines:!0}),{bundleVersion:e}}function yye(t){return Br[jx(t)]}var Jlt=/## (?YN[0-9]{4}) - `(?[A-Z_]+)`\n\n(?
(?:.(?!##))+)/gs;async function Klt(t){let r=`https://repo.yarnpkg.com/${je.isTaggedYarnVersion(fn)?fn:await Zv(t,"canary")}/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx`,s=await nn.get(r,{configuration:t});return new Map(Array.from(s.toString().matchAll(Jlt),({groups:a})=>{if(!a)throw new Error("Assertion failed: Expected the match to have been successful");let n=yye(a.code);if(a.name!==n)throw new Error(`Assertion failed: Invalid error code data: Expected "${a.name}" to be named "${n}"`);return[a.code,a.details]}))}var IC=class extends ft{constructor(){super(...arguments);this.code=ge.String({required:!1,validator:$2(wE(),[Z2(/^YN[0-9]{4}$/)])});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["explain"]]}static{this.usage=ot.Usage({description:"explain an error code",details:` + When the code argument is specified, this command prints its name and its details. + + When used without arguments, this command lists all error codes and their names. + `,examples:[["Explain an error code","$0 explain YN0006"],["List all error codes","$0 explain"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(typeof this.code<"u"){let s=yye(this.code),a=he.pretty(r,s,he.Type.CODE),n=this.cli.format().header(`${this.code} - ${a}`),f=(await Klt(r)).get(this.code),p=typeof f<"u"?he.jsonOrPretty(this.json,r,he.tuple(he.Type.MARKDOWN,{text:f,format:this.cli.format(),paragraphs:!0})):`This error code does not have a description. + +You can help us by editing this page on GitHub \u{1F642}: +${he.jsonOrPretty(this.json,r,he.tuple(he.Type.URL,"https://github.com/yarnpkg/berry/blob/master/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx"))} +`;this.json?this.context.stdout.write(`${JSON.stringify({code:this.code,name:s,details:p})} +`):this.context.stdout.write(`${n} + +${p} +`)}else{let s={children:je.mapAndFilter(Object.entries(Br),([a,n])=>Number.isNaN(Number(a))?je.mapAndFilter.skip:{label:Yf(Number(a)),value:he.tuple(he.Type.CODE,n)})};xs.emitTree(s,{configuration:r,stdout:this.context.stdout,json:this.json})}}};Ge();Dt();Yt();var Eye=ut(Go()),CC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Print versions of a package from the whole project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Print information for all packages, including transitive dependencies"});this.extra=ge.Array("-X,--extra",[],{description:"An array of requests of extra data provided by plugins"});this.cache=ge.Boolean("--cache",!1,{description:"Print information about the cache entry of a package (path, size, checksum)"});this.dependents=ge.Boolean("--dependents",!1,{description:"Print all dependents for each matching package"});this.manifest=ge.Boolean("--manifest",!1,{description:"Print data obtained by looking at the package archive (license, homepage, ...)"});this.nameOnly=ge.Boolean("--name-only",!1,{description:"Only print the name for the matching packages"});this.virtuals=ge.Boolean("--virtuals",!1,{description:"Print each instance of the virtual packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["info"]]}static{this.usage=ot.Usage({description:"see information related to packages",details:"\n This command prints various information related to the specified packages, accepting glob patterns.\n\n By default, if the locator reference is missing, Yarn will default to print the information about all the matching direct dependencies of the package for the active workspace. To instead print all versions of the package that are direct dependencies of any of your workspaces, use the `-A,--all` flag. Adding the `-R,--recursive` flag will also report transitive dependencies.\n\n Some fields will be hidden by default in order to keep the output readable, but can be selectively displayed by using additional options (`--dependents`, `--manifest`, `--virtuals`, ...) described in the option descriptions.\n\n Note that this command will only print the information directly related to the selected packages - if you wish to know why the package is there in the first place, use `yarn why` which will do just that (it also provides a `-R,--recursive` flag that may be of some help).\n ",examples:[["Show information about Lodash","$0 info lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a&&!this.all)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=new Set(this.extra);this.cache&&c.add("cache"),this.dependents&&c.add("dependents"),this.manifest&&c.add("manifest");let f=(ie,{recursive:ue})=>{let le=ie.anchoredLocator.locatorHash,me=new Map,pe=[le];for(;pe.length>0;){let Be=pe.shift();if(me.has(Be))continue;let Ce=s.storedPackages.get(Be);if(typeof Ce>"u")throw new Error("Assertion failed: Expected the package to be registered");if(me.set(Be,Ce),G.isVirtualLocator(Ce)&&pe.push(G.devirtualizeLocator(Ce).locatorHash),!(!ue&&Be!==le))for(let g of Ce.dependencies.values()){let we=s.storedResolutions.get(g.descriptorHash);if(typeof we>"u")throw new Error("Assertion failed: Expected the resolution to be registered");pe.push(we)}}return me.values()},p=({recursive:ie})=>{let ue=new Map;for(let le of s.workspaces)for(let me of f(le,{recursive:ie}))ue.set(me.locatorHash,me);return ue.values()},h=({all:ie,recursive:ue})=>ie&&ue?s.storedPackages.values():ie?p({recursive:ue}):f(a,{recursive:ue}),E=({all:ie,recursive:ue})=>{let le=h({all:ie,recursive:ue}),me=this.patterns.map(Ce=>{let g=G.parseLocator(Ce),we=Eye.default.makeRe(G.stringifyIdent(g)),ye=G.isVirtualLocator(g),Ae=ye?G.devirtualizeLocator(g):g;return se=>{let Z=G.stringifyIdent(se);if(!we.test(Z))return!1;if(g.reference==="unknown")return!0;let De=G.isVirtualLocator(se),Re=De?G.devirtualizeLocator(se):se;return!(ye&&De&&g.reference!==se.reference||Ae.reference!==Re.reference)}}),pe=je.sortMap([...le],Ce=>G.stringifyLocator(Ce));return{selection:pe.filter(Ce=>me.length===0||me.some(g=>g(Ce))),sortedLookup:pe}},{selection:C,sortedLookup:S}=E({all:this.all,recursive:this.recursive});if(C.length===0)throw new nt("No package matched your request");let P=new Map;if(this.dependents)for(let ie of S)for(let ue of ie.dependencies.values()){let le=s.storedResolutions.get(ue.descriptorHash);if(typeof le>"u")throw new Error("Assertion failed: Expected the resolution to be registered");je.getArrayWithDefault(P,le).push(ie)}let I=new Map;for(let ie of S){if(!G.isVirtualLocator(ie))continue;let ue=G.devirtualizeLocator(ie);je.getArrayWithDefault(I,ue.locatorHash).push(ie)}let R={},N={children:R},U=r.makeFetcher(),W={project:s,fetcher:U,cache:n,checksums:s.storedChecksums,report:new ki,cacheOptions:{skipIntegrityCheck:!0}},ee=[async(ie,ue,le)=>{if(!ue.has("manifest"))return;let me=await U.fetch(ie,W),pe;try{pe=await Ut.find(me.prefixPath,{baseFs:me.packageFs})}finally{me.releaseFs?.()}le("Manifest",{License:he.tuple(he.Type.NO_HINT,pe.license),Homepage:he.tuple(he.Type.URL,pe.raw.homepage??null)})},async(ie,ue,le)=>{if(!ue.has("cache"))return;let me=s.storedChecksums.get(ie.locatorHash)??null,pe=n.getLocatorPath(ie,me),Be;if(pe!==null)try{Be=await ce.statPromise(pe)}catch{}let Ce=typeof Be<"u"?[Be.size,he.Type.SIZE]:void 0;le("Cache",{Checksum:he.tuple(he.Type.NO_HINT,me),Path:he.tuple(he.Type.PATH,pe),Size:Ce})}];for(let ie of C){let ue=G.isVirtualLocator(ie);if(!this.virtuals&&ue)continue;let le={},me={value:[ie,he.Type.LOCATOR],children:le};if(R[G.stringifyLocator(ie)]=me,this.nameOnly){delete me.children;continue}let pe=I.get(ie.locatorHash);typeof pe<"u"&&(le.Instances={label:"Instances",value:he.tuple(he.Type.NUMBER,pe.length)}),le.Version={label:"Version",value:he.tuple(he.Type.NO_HINT,ie.version)};let Be=(g,we)=>{let ye={};if(le[g]=ye,Array.isArray(we))ye.children=we.map(Ae=>({value:Ae}));else{let Ae={};ye.children=Ae;for(let[se,Z]of Object.entries(we))typeof Z>"u"||(Ae[se]={label:se,value:Z})}};if(!ue){for(let g of ee)await g(ie,c,Be);await r.triggerHook(g=>g.fetchPackageInfo,ie,c,Be)}ie.bin.size>0&&!ue&&Be("Exported Binaries",[...ie.bin.keys()].map(g=>he.tuple(he.Type.PATH,g)));let Ce=P.get(ie.locatorHash);typeof Ce<"u"&&Ce.length>0&&Be("Dependents",Ce.map(g=>he.tuple(he.Type.LOCATOR,g))),ie.dependencies.size>0&&!ue&&Be("Dependencies",[...ie.dependencies.values()].map(g=>{let we=s.storedResolutions.get(g.descriptorHash),ye=typeof we<"u"?s.storedPackages.get(we)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:ye})})),ie.peerDependencies.size>0&&ue&&Be("Peer dependencies",[...ie.peerDependencies.values()].map(g=>{let we=ie.dependencies.get(g.identHash),ye=typeof we<"u"?s.storedResolutions.get(we.descriptorHash)??null:null,Ae=ye!==null?s.storedPackages.get(ye)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:Ae})}))}xs.emitTree(N,{configuration:r,json:this.json,stdout:this.context.stdout,separators:this.nameOnly?0:2})}};Ge();Dt();wc();var nF=ut(Fd());Yt();var Z5=ut(Ai());Ul();var zlt=[{selector:t=>t===-1,name:"nodeLinker",value:"node-modules"},{selector:t=>t!==-1&&t<8,name:"enableGlobalCache",value:!1},{selector:t=>t!==-1&&t<8,name:"compressionLevel",value:"mixed"}],wC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.immutable=ge.Boolean("--immutable",{description:"Abort with an error exit code if the lockfile was to be modified"});this.immutableCache=ge.Boolean("--immutable-cache",{description:"Abort with an error exit code if the cache folder was to be modified"});this.refreshLockfile=ge.Boolean("--refresh-lockfile",{description:"Refresh the package metadata stored in the lockfile"});this.checkCache=ge.Boolean("--check-cache",{description:"Always refetch the packages and ensure that their checksums are consistent"});this.checkResolutions=ge.Boolean("--check-resolutions",{description:"Validates that the package resolutions are coherent"});this.inlineBuilds=ge.Boolean("--inline-builds",{description:"Verbosely print the output of the build steps of dependencies"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.cacheFolder=ge.String("--cache-folder",{hidden:!0});this.frozenLockfile=ge.Boolean("--frozen-lockfile",{hidden:!0});this.ignoreEngines=ge.Boolean("--ignore-engines",{hidden:!0});this.nonInteractive=ge.Boolean("--non-interactive",{hidden:!0});this.preferOffline=ge.Boolean("--prefer-offline",{hidden:!0});this.production=ge.Boolean("--production",{hidden:!0});this.registry=ge.String("--registry",{hidden:!0});this.silent=ge.Boolean("--silent",{hidden:!0});this.networkTimeout=ge.String("--network-timeout",{hidden:!0})}static{this.paths=[["install"],ot.Default]}static{this.usage=ot.Usage({description:"install the project dependencies",details:"\n This command sets up your project if needed. The installation is split into four different steps that each have their own characteristics:\n\n - **Resolution:** First the package manager will resolve your dependencies. The exact way a dependency version is privileged over another isn't standardized outside of the regular semver guarantees. If a package doesn't resolve to what you would expect, check that all dependencies are correctly declared (also check our website for more information: ).\n\n - **Fetch:** Then we download all the dependencies if needed, and make sure that they're all stored within our cache (check the value of `cacheFolder` in `yarn config` to see where the cache files are stored).\n\n - **Link:** Then we send the dependency tree information to internal plugins tasked with writing them on the disk in some form (for example by generating the `.pnp.cjs` file you might know).\n\n - **Build:** Once the dependency tree has been written on the disk, the package manager will now be free to run the build scripts for all packages that might need it, in a topological order compatible with the way they depend on one another. See https://yarnpkg.com/advanced/lifecycle-scripts for detail.\n\n Note that running this command is not part of the recommended workflow. Yarn supports zero-installs, which means that as long as you store your cache and your `.pnp.cjs` file inside your repository, everything will work without requiring any install right after cloning your repository or switching branches.\n\n If the `--immutable` option is set (defaults to true on CI), Yarn will abort with an error exit code if the lockfile was to be modified (other paths can be added using the `immutablePatterns` configuration setting). For backward compatibility we offer an alias under the name of `--frozen-lockfile`, but it will be removed in a later release.\n\n If the `--immutable-cache` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).\n\n If the `--refresh-lockfile` option is set, Yarn will keep the same resolution for the packages currently in the lockfile but will refresh their metadata. If used together with `--immutable`, it can validate that the lockfile information are consistent. This flag is enabled by default when Yarn detects it runs within a pull request context.\n\n If the `--check-cache` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.\n\n If the `--inline-builds` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n ",examples:[["Install the project","$0 install"],["Validate a project when using Zero-Installs","$0 install --immutable --immutable-cache"],["Validate a project when using Zero-Installs (slightly safer if you accept external PRs)","$0 install --immutable --immutable-cache --check-cache"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);typeof this.inlineBuilds<"u"&&r.useWithSource("",{enableInlineBuilds:this.inlineBuilds},r.startingCwd,{overwrite:!0});let s=!!process.env.FUNCTION_TARGET||!!process.env.GOOGLE_RUNTIME,a=await SI({configuration:r,stdout:this.context.stdout},[{option:this.ignoreEngines,message:"The --ignore-engines option is deprecated; engine checking isn't a core feature anymore",error:!nF.default.VERCEL},{option:this.registry,message:"The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file"},{option:this.preferOffline,message:"The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead",error:!nF.default.VERCEL},{option:this.production,message:"The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead",error:!0},{option:this.nonInteractive,message:"The --non-interactive option is deprecated",error:!s},{option:this.frozenLockfile,message:"The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead",callback:()=>this.immutable=this.frozenLockfile},{option:this.cacheFolder,message:"The cache-folder option has been deprecated; use rc settings instead",error:!nF.default.NETLIFY}]);if(a!==null)return a;let n=this.mode==="update-lockfile";if(n&&(this.immutable||this.immutableCache))throw new nt(`${he.pretty(r,"--immutable",he.Type.CODE)} and ${he.pretty(r,"--immutable-cache",he.Type.CODE)} cannot be used with ${he.pretty(r,"--mode=update-lockfile",he.Type.CODE)}`);let c=(this.immutable??r.get("enableImmutableInstalls"))&&!n,f=this.immutableCache&&!n;if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U=!1;await $lt(r,c)&&(N.reportInfo(48,"Automatically removed core plugins that are now builtins \u{1F44D}"),U=!0),await Zlt(r,c)&&(N.reportInfo(48,"Automatically fixed merge conflicts \u{1F44D}"),U=!0),U&&N.reportSeparator()});if(R.hasErrors())return R.exitCode()}if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{if(ze.telemetry?.isNew)ze.telemetry.commitTips(),N.reportInfo(65,"Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry"),N.reportInfo(65,`Run ${he.pretty(r,"yarn config set --home enableTelemetry 0",he.Type.CODE)} to disable`),N.reportSeparator();else if(ze.telemetry?.shouldShowTips){let U=await nn.get("https://repo.yarnpkg.com/tags",{configuration:r,jsonResponse:!0}).catch(()=>null);if(U!==null){let W=null;if(fn!==null){let ie=Z5.default.prerelease(fn)?"canary":"stable",ue=U.latest[ie];Z5.default.gt(ue,fn)&&(W=[ie,ue])}if(W)ze.telemetry.commitTips(),N.reportInfo(88,`${he.applyStyle(r,`A new ${W[0]} version of Yarn is available:`,he.Style.BOLD)} ${G.prettyReference(r,W[1])}!`),N.reportInfo(88,`Upgrade now by running ${he.pretty(r,`yarn set version ${W[1]}`,he.Type.CODE)}`),N.reportSeparator();else{let ee=ze.telemetry.selectTip(U.tips);ee&&(N.reportInfo(89,he.pretty(r,ee.message,he.Type.MARKDOWN_INLINE)),ee.url&&N.reportInfo(89,`Learn more at ${ee.url}`),N.reportSeparator())}}}});if(R.hasErrors())return R.exitCode()}let{project:p,workspace:h}=await Tt.find(r,this.context.cwd),E=p.lockfileLastVersion;if(E!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U={};for(let W of zlt)W.selector(E)&&typeof r.sources.get(W.name)>"u"&&(r.use("",{[W.name]:W.value},p.cwd,{overwrite:!0}),U[W.name]=W.value);Object.keys(U).length>0&&(await ze.updateConfiguration(p.cwd,U),N.reportInfo(87,"Migrated your project to the latest Yarn version \u{1F680}"),N.reportSeparator())});if(R.hasErrors())return R.exitCode()}let C=await Kr.find(r,{immutable:f,check:this.checkCache});if(!h)throw new ar(p.cwd,this.context.cwd);await p.restoreInstallState({restoreResolutions:!1});let S=r.get("enableHardenedMode");S&&typeof r.sources.get("enableHardenedMode")>"u"&&await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async R=>{R.reportWarning(0,"Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled."),R.reportWarning(0,`It will prevent malicious lockfile manipulations, in exchange for a slower install time. You can opt-out if necessary; check our ${he.applyHyperlink(r,"documentation","https://yarnpkg.com/features/security#hardened-mode")} for more details.`),R.reportSeparator()}),(this.refreshLockfile??S)&&(p.lockfileNeedsRefresh=!0);let P=this.checkResolutions??S;return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,forceSectionAlignment:!0,includeLogs:!0,includeVersion:!0},async R=>{await p.install({cache:C,report:R,immutable:c,checkResolutions:P,mode:this.mode})})).exitCode()}},Xlt="<<<<<<<";async function Zlt(t,e){if(!t.projectCwd)return!1;let r=J.join(t.projectCwd,Er.lockfile);if(!await ce.existsPromise(r)||!(await ce.readFilePromise(r,"utf8")).includes(Xlt))return!1;if(e)throw new jt(47,"Cannot autofix a lockfile when running an immutable install");let a=await qr.execvp("git",["rev-parse","MERGE_HEAD","HEAD"],{cwd:t.projectCwd});if(a.code!==0&&(a=await qr.execvp("git",["rev-parse","REBASE_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0&&(a=await qr.execvp("git",["rev-parse","CHERRY_PICK_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0)throw new jt(83,"Git returned an error when trying to find the commits pertaining to the conflict");let n=await Promise.all(a.stdout.trim().split(/\n/).map(async f=>{let p=await qr.execvp("git",["show",`${f}:./${Er.lockfile}`],{cwd:t.projectCwd});if(p.code!==0)throw new jt(83,`Git returned an error when trying to access the lockfile content in ${f}`);try{return ls(p.stdout)}catch{throw new jt(46,"A variant of the conflicting lockfile failed to parse")}}));n=n.filter(f=>!!f.__metadata);for(let f of n){if(f.__metadata.version<7)for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=G.parseDescriptor(p,!0),E=t.normalizeDependency(h),C=G.stringifyDescriptor(E);C!==p&&(f[C]=f[p],delete f[p])}for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=f[p].checksum;typeof h>"u"||h.includes("/")||(f[p].checksum=`${f.__metadata.cacheKey}/${h}`)}}let c=Object.assign({},...n);c.__metadata.version=`${Math.min(...n.map(f=>parseInt(f.__metadata.version??0)))}`,c.__metadata.cacheKey="merged";for(let[f,p]of Object.entries(c))typeof p=="string"&&delete c[f];return await ce.changeFilePromise(r,nl(c),{automaticNewlines:!0}),!0}async function $lt(t,e){if(!t.projectCwd)return!1;let r=[],s=J.join(t.projectCwd,".yarn/plugins/@yarnpkg");return await ze.updateConfiguration(t.projectCwd,{plugins:n=>{if(!Array.isArray(n))return n;let c=n.filter(f=>{if(!f.path)return!0;let p=J.resolve(t.projectCwd,f.path),h=ov.has(f.spec)&&J.contains(s,p);return h&&r.push(p),!h});return c.length===0?ze.deleteProperty:c.length===n.length?n:c}},{immutable:e})?(await Promise.all(r.map(async n=>{await ce.removePromise(n)})),!0):!1}Ge();Dt();Yt();var BC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Link all workspaces belonging to the target projects to the current one"});this.private=ge.Boolean("-p,--private",!1,{description:"Also link private workspaces belonging to the target projects to the current one"});this.relative=ge.Boolean("-r,--relative",!1,{description:"Link workspaces using relative paths instead of absolute paths"});this.destinations=ge.Rest()}static{this.paths=[["link"]]}static{this.usage=ot.Usage({description:"connect the local project to another one",details:"\n This command will set a new `resolutions` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).\n ",examples:[["Register one or more remote workspaces for use in the current project","$0 link ~/ts-loader ~/jest"],["Register all workspaces from a remote project for use in the current project","$0 link ~/jest --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=s.topLevelWorkspace,f=[];for(let p of this.destinations){let h=J.resolve(this.context.cwd,fe.toPortablePath(p)),E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(s.cwd===C.cwd)throw new nt(`Invalid destination '${p}'; Can't link the project to itself`);if(!S)throw new ar(C.cwd,h);if(this.all){let P=!1;for(let I of C.workspaces)I.manifest.name&&(!I.manifest.private||this.private)&&(f.push(I),P=!0);if(!P)throw new nt(`No workspace found to be linked in the target project: ${p}`)}else{if(!S.manifest.name)throw new nt(`The target workspace at '${p}' doesn't have a name and thus cannot be linked`);if(S.manifest.private&&!this.private)throw new nt(`The target workspace at '${p}' is marked private - use the --private flag to link it anyway`);f.push(S)}}for(let p of f){let h=G.stringifyIdent(p.anchoredLocator),E=this.relative?J.relative(s.cwd,p.cwd):p.cwd;c.manifest.resolutions.push({pattern:{descriptor:{fullName:h}},reference:`portal:${E}`})}return await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Yt();var vC=class extends ft{constructor(){super(...arguments);this.args=ge.Proxy()}static{this.paths=[["node"]]}static{this.usage=ot.Usage({description:"run node with the hook already setup",details:` + This command simply runs Node. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). + + The Node process will use the exact same version of Node as the one used to run Yarn itself, which might be a good way to ensure that your commands always use a consistent Node version. + `,examples:[["Run a Node script","$0 node ./my-script.js"]]})}async execute(){return this.cli.run(["exec","node",...this.args])}};Ge();Yt();var SC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","check"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"find all third-party plugins that differ from their own spec",details:` + Check only the plugins from https. + + If this command detects any plugin differences in the CI environment, it will throw an error. + `,examples:[["find all third-party plugins that differ from their own spec","$0 plugin check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await ze.findRcFiles(this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{for(let c of s)if(c.data?.plugins)for(let f of c.data.plugins){if(!f.checksum||!f.spec.match(/^https?:/))continue;let p=await nn.get(f.spec,{configuration:r}),h=Nn.makeHash(p);if(f.checksum===h)continue;let E=he.pretty(r,f.path,he.Type.PATH),C=he.pretty(r,f.spec,he.Type.URL),S=`${E} is different from the file provided by ${C}`;n.reportJson({...f,newChecksum:h}),n.reportError(0,S)}})).exitCode()}};Ge();Ge();Dt();Yt();var vye=Ie("os");Ge();Dt();Yt();var Iye=Ie("os");Ge();wc();Yt();var ect="https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml";async function Sm(t,e){let r=await nn.get(ect,{configuration:t}),s=ls(r.toString());return Object.fromEntries(Object.entries(s).filter(([a,n])=>!e||Fr.satisfiesWithPrereleases(e,n.range??"<4.0.0-rc.1")))}var DC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","list"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the available official plugins",details:"\n This command prints the plugins available directly from the Yarn repository. Only those plugins can be referenced by name in `yarn plugin import`.\n ",examples:[["List the official plugins","$0 plugin list"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{let n=await Sm(r,fn);for(let[c,{experimental:f,...p}]of Object.entries(n)){let h=c;f&&(h+=" [experimental]"),a.reportJson({name:c,experimental:f,...p}),a.reportInfo(null,h)}})).exitCode()}};var tct=/^[0-9]+$/,rct=process.platform==="win32";function Cye(t){return tct.test(t)?`pull/${t}/head`:t}var nct=({repository:t,branch:e},r)=>[["git","init",fe.fromPortablePath(r)],["git","remote","add","origin",t],["git","fetch","origin","--depth=1",Cye(e)],["git","reset","--hard","FETCH_HEAD"]],ict=({branch:t})=>[["git","fetch","origin","--depth=1",Cye(t),"--force"],["git","reset","--hard","FETCH_HEAD"],["git","clean","-dfx","-e","packages/yarnpkg-cli/bundles"]],sct=({plugins:t,noMinify:e},r,s)=>[["yarn","build:cli",...new Array().concat(...t.map(a=>["--plugin",J.resolve(s,a)])),...e?["--no-minify"]:[],"|"],[rct?"move":"mv","packages/yarnpkg-cli/bundles/yarn.js",fe.fromPortablePath(r),"|"]],bC=class extends ft{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.plugins=ge.Array("--plugin",[],{description:"An array of additional plugins that should be included in the bundle"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"If set, the bundle will be built but not added to the project"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a bundle for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.skipPlugins=ge.Boolean("--skip-plugins",!1,{description:"Skip updating the contrib plugins"})}static{this.paths=[["set","version","from","sources"]]}static{this.usage=ot.Usage({description:"build Yarn from master",details:` + This command will clone the Yarn repository into a temporary folder, then build it. The resulting bundle will then be copied into the local project. + + By default, it also updates all contrib plugins to the same commit the bundle is built from. This behavior can be disabled by using the \`--skip-plugins\` flag. + `,examples:[["Build Yarn from master","$0 set version from sources"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.installPath<"u"?J.resolve(this.context.cwd,fe.toPortablePath(this.installPath)):J.resolve(fe.toPortablePath((0,Iye.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{await $5(this,{configuration:r,report:c,target:a}),c.reportSeparator(),c.reportInfo(0,"Building a fresh bundle"),c.reportSeparator();let f=await qr.execvp("git",["rev-parse","--short","HEAD"],{cwd:a,strict:!0}),p=J.join(a,`packages/yarnpkg-cli/bundles/yarn-${f.stdout.trim()}.js`);ce.existsSync(p)||(await $v(sct(this,p,a),{configuration:r,context:this.context,target:a}),c.reportSeparator());let h=await ce.readFilePromise(p);if(!this.dryRun){let{bundleVersion:E}=await X5(r,null,async()=>h,{report:c});this.skipPlugins||await oct(this,E,{project:s,report:c,target:a})}})).exitCode()}};async function $v(t,{configuration:e,context:r,target:s}){for(let[a,...n]of t){let c=n[n.length-1]==="|";if(c&&n.pop(),c)await qr.pipevp(a,n,{cwd:s,stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,strict:!0});else{r.stdout.write(`${he.pretty(e,` $ ${[a,...n].join(" ")}`,"grey")} +`);try{await qr.execvp(a,n,{cwd:s,strict:!0})}catch(f){throw r.stdout.write(f.stdout||f.stack),f}}}}async function $5(t,{configuration:e,report:r,target:s}){let a=!1;if(!t.force&&ce.existsSync(J.join(s,".git"))){r.reportInfo(0,"Fetching the latest commits"),r.reportSeparator();try{await $v(ict(t),{configuration:e,context:t.context,target:s}),a=!0}catch{r.reportSeparator(),r.reportWarning(0,"Repository update failed; we'll try to regenerate it")}}a||(r.reportInfo(0,"Cloning the remote repository"),r.reportSeparator(),await ce.removePromise(s),await ce.mkdirPromise(s,{recursive:!0}),await $v(nct(t,s),{configuration:e,context:t.context,target:s}))}async function oct(t,e,{project:r,report:s,target:a}){let n=await Sm(r.configuration,e),c=new Set(Object.keys(n));for(let f of r.configuration.plugins.keys())c.has(f)&&await eq(f,t,{project:r,report:s,target:a})}Ge();Ge();Dt();Yt();var wye=ut(Ai()),Bye=Ie("vm");var PC=class extends ft{constructor(){super(...arguments);this.name=ge.String();this.checksum=ge.Boolean("--checksum",!0,{description:"Whether to care if this plugin is modified"})}static{this.paths=[["plugin","import"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"download a plugin",details:` + This command downloads the specified plugin from its remote location and updates the configuration to reference it in further CLI invocations. + + Three types of plugin references are accepted: + + - If the plugin is stored within the Yarn repository, it can be referenced by name. + - Third-party plugins can be referenced directly through their public urls. + - Local plugins can be referenced by their path on the disk. + + If the \`--no-checksum\` option is set, Yarn will no longer care if the plugin is modified. + + Plugins cannot be downloaded from the npm registry, and aren't allowed to have dependencies (they need to be bundled into a single file, possibly thanks to the \`@yarnpkg/builder\` package). + `,examples:[['Download and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import @yarnpkg/plugin-exec"],['Download and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import exec"],["Download and activate a community plugin","$0 plugin import https://example.org/path/to/plugin.js"],["Activate a local plugin","$0 plugin import ./path/to/plugin.js"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,stdout:this.context.stdout},async a=>{let{project:n}=await Tt.find(r,this.context.cwd),c,f;if(this.name.match(/^\.{0,2}[\\/]/)||fe.isAbsolute(this.name)){let p=J.resolve(this.context.cwd,fe.toPortablePath(this.name));a.reportInfo(0,`Reading ${he.pretty(r,p,he.Type.PATH)}`),c=J.relative(n.cwd,p),f=await ce.readFilePromise(p)}else{let p;if(this.name.match(/^https?:/)){try{new URL(this.name)}catch{throw new jt(52,`Plugin specifier "${this.name}" is neither a plugin name nor a valid url`)}c=this.name,p=this.name}else{let h=G.parseLocator(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-"));if(h.reference!=="unknown"&&!wye.default.valid(h.reference))throw new jt(0,"Official plugins only accept strict version references. Use an explicit URL if you wish to download them from another location.");let E=G.stringifyIdent(h),C=await Sm(r,fn);if(!Object.hasOwn(C,E)){let S=`Couldn't find a plugin named ${G.prettyIdent(r,h)} on the remote registry. +`;throw r.plugins.has(E)?S+=`A plugin named ${G.prettyIdent(r,h)} is already installed; possibly attempting to import a built-in plugin.`:S+=`Note that only the plugins referenced on our website (${he.pretty(r,"https://github.com/yarnpkg/berry/blob/master/plugins.yml",he.Type.URL)}) can be referenced by their name; any other plugin will have to be referenced through its public url (for example ${he.pretty(r,"https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js",he.Type.URL)}).`,new jt(51,S)}c=E,p=C[E].url,h.reference!=="unknown"?p=p.replace(/\/master\//,`/${E}/${h.reference}/`):fn!==null&&(p=p.replace(/\/master\//,`/@yarnpkg/cli/${fn}/`))}a.reportInfo(0,`Downloading ${he.pretty(r,p,"green")}`),f=await nn.get(p,{configuration:r})}await tq(c,f,{checksum:this.checksum,project:n,report:a})})).exitCode()}};async function tq(t,e,{checksum:r=!0,project:s,report:a}){let{configuration:n}=s,c={},f={exports:c};(0,Bye.runInNewContext)(e.toString(),{module:f,exports:c});let h=`.yarn/plugins/${f.exports.name}.cjs`,E=J.resolve(s.cwd,h);a.reportInfo(0,`Saving the new plugin in ${he.pretty(n,h,"magenta")}`),await ce.mkdirPromise(J.dirname(E),{recursive:!0}),await ce.writeFilePromise(E,e);let C={path:h,spec:t};r&&(C.checksum=Nn.makeHash(e)),await ze.addPlugin(s.cwd,[C])}var act=({pluginName:t,noMinify:e},r)=>[["yarn",`build:${t}`,...e?["--no-minify"]:[],"|"]],xC=class extends ft{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a plugin for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.name=ge.String()}static{this.paths=[["plugin","import","from","sources"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"build a plugin from sources",details:` + This command clones the Yarn repository into a temporary folder, builds the specified contrib plugin and updates the configuration to reference it in further CLI invocations. + + The plugins can be referenced by their short name if sourced from the official Yarn repository. + `,examples:[['Build and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import from sources @yarnpkg/plugin-exec"],['Build and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import from sources exec"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.installPath<"u"?J.resolve(this.context.cwd,fe.toPortablePath(this.installPath)):J.resolve(fe.toPortablePath((0,vye.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let{project:c}=await Tt.find(r,this.context.cwd),f=G.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-")),p=G.stringifyIdent(f),h=await Sm(r,fn);if(!Object.hasOwn(h,p))throw new jt(51,`Couldn't find a plugin named "${p}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);let E=p;await $5(this,{configuration:r,report:n,target:s}),await eq(E,this,{project:c,report:n,target:s})})).exitCode()}};async function eq(t,{context:e,noMinify:r},{project:s,report:a,target:n}){let c=t.replace(/@yarnpkg\//,""),{configuration:f}=s;a.reportSeparator(),a.reportInfo(0,`Building a fresh ${c}`),a.reportSeparator(),await $v(act({pluginName:c,noMinify:r},n),{configuration:f,context:e,target:n}),a.reportSeparator();let p=J.resolve(n,`packages/${c}/bundles/${t}.js`),h=await ce.readFilePromise(p);await tq(t,h,{project:s,report:a})}Ge();Dt();Yt();var kC=class extends ft{constructor(){super(...arguments);this.name=ge.String()}static{this.paths=[["plugin","remove"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"remove a plugin",details:` + This command deletes the specified plugin from the .yarn/plugins folder and removes it from the configuration. + + **Note:** The plugins have to be referenced by their name property, which can be obtained using the \`yarn plugin runtime\` command. Shorthands are not allowed. + `,examples:[["Remove a plugin imported from the Yarn repository","$0 plugin remove @yarnpkg/plugin-typescript"],["Remove a plugin imported from a local file","$0 plugin remove my-local-plugin"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c=this.name,f=G.parseIdent(c);if(!r.plugins.has(c))throw new nt(`${G.prettyIdent(r,f)} isn't referenced by the current configuration`);let p=`.yarn/plugins/${c}.cjs`,h=J.resolve(s.cwd,p);ce.existsSync(h)&&(n.reportInfo(0,`Removing ${he.pretty(r,p,he.Type.PATH)}...`),await ce.removePromise(h)),n.reportInfo(0,"Updating the configuration..."),await ze.updateConfiguration(s.cwd,{plugins:E=>{if(!Array.isArray(E))return E;let C=E.filter(S=>S.path!==p);return C.length===0?ze.deleteProperty:C.length===E.length?E:C}})})).exitCode()}};Ge();Yt();var QC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","runtime"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the active plugins",details:` + This command prints the currently active plugins. Will be displayed both builtin plugins and external plugins. + `,examples:[["List the currently active plugins","$0 plugin runtime"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{for(let n of r.plugins.keys()){let c=this.context.plugins.plugins.has(n),f=n;c&&(f+=" [builtin]"),a.reportJson({name:n,builtin:c}),a.reportInfo(null,`${f}`)}})).exitCode()}};Ge();Ge();Yt();var TC=class extends ft{constructor(){super(...arguments);this.idents=ge.Rest()}static{this.paths=[["rebuild"]]}static{this.usage=ot.Usage({description:"rebuild the project's native packages",details:` + This command will automatically cause Yarn to forget about previous compilations of the given packages and to run them again. + + Note that while Yarn forgets the compilation, the previous artifacts aren't erased from the filesystem and may affect the next builds (in good or bad). To avoid this, you may remove the .yarn/unplugged folder, or any other relevant location where packages might have been stored (Yarn may offer a way to do that automatically in the future). + + By default all packages will be rebuilt, but you can filter the list by specifying the names of the packages you want to clear from memory. + `,examples:[["Rebuild all packages","$0 rebuild"],["Rebuild fsevents only","$0 rebuild fsevents"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=new Set;for(let f of this.idents)c.add(G.parseIdent(f).identHash);if(await s.restoreInstallState({restoreResolutions:!1}),await s.resolveEverything({cache:n,report:new ki}),c.size>0)for(let f of s.storedPackages.values())c.has(f.identHash)&&(s.storedBuildState.delete(f.locatorHash),s.skippedBuilds.delete(f.locatorHash));else s.storedBuildState.clear(),s.skippedBuilds.clear();return await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ge();Ge();Ge();Yt();var rq=ut(Go());Ul();var RC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Apply the operation to all workspaces from the current project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["remove"]]}static{this.usage=ot.Usage({description:"remove dependencies from the project",details:` + This command will remove the packages matching the specified patterns from the current workspace. + + If the \`--mode=\` option is set, Yarn will change which artifacts are generated. The modes currently supported are: + + - \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run. + + - \`update-lockfile\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost. + + This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. + `,examples:[["Remove a dependency from the current project","$0 remove lodash"],["Remove a dependency from all workspaces at once","$0 remove lodash --all"],["Remove all dependencies starting with `eslint-`","$0 remove 'eslint-*'"],["Remove all dependencies with the `@babel` scope","$0 remove '@babel/*'"],["Remove all dependencies matching `react-dom` or `react-helmet`","$0 remove 'react-{dom,helmet}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.all?s.workspaces:[a],f=["dependencies","devDependencies","peerDependencies"],p=[],h=!1,E=[];for(let I of this.patterns){let R=!1,N=G.parseIdent(I);for(let U of c){let W=[...U.manifest.peerDependenciesMeta.keys()];for(let ee of(0,rq.default)(W,I))U.manifest.peerDependenciesMeta.delete(ee),h=!0,R=!0;for(let ee of f){let ie=U.manifest.getForScope(ee),ue=[...ie.values()].map(le=>G.stringifyIdent(le));for(let le of(0,rq.default)(ue,G.stringifyIdent(N))){let{identHash:me}=G.parseIdent(le),pe=ie.get(me);if(typeof pe>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");U.manifest[ee].delete(me),E.push([U,ee,pe]),h=!0,R=!0}}}R||p.push(I)}let C=p.length>1?"Patterns":"Pattern",S=p.length>1?"don't":"doesn't",P=this.all?"any":"this";if(p.length>0)throw new nt(`${C} ${he.prettyList(r,p,he.Type.CODE)} ${S} match any packages referenced by ${P} workspace`);return h?(await r.triggerMultipleHooks(I=>I.afterWorkspaceDependencyRemoval,E),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})):0}};Ge();Ge();Yt();var Sye=Ie("util"),FC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["run"]]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async c=>{let f=a.manifest.scripts,p=je.sortMap(f.keys(),C=>C),h={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},E=p.reduce((C,S)=>Math.max(C,S.length),0);for(let[C,S]of f.entries())c.reportInfo(null,`${C.padEnd(E," ")} ${(0,Sye.inspect)(S,h)}`),c.reportJson({name:C,script:S})})).exitCode()}};Ge();Ge();Yt();var NC=class extends ft{constructor(){super(...arguments);this.inspect=ge.String("--inspect",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.inspectBrk=ge.String("--inspect-brk",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.topLevel=ge.Boolean("-T,--top-level",!1,{description:"Check the root workspace for scripts and/or binaries instead of the current one"});this.binariesOnly=ge.Boolean("-B,--binaries-only",!1,{description:"Ignore any user defined scripts and only check for binaries"});this.require=ge.String("--require",{description:"Forwarded to the underlying Node process when executing a binary"});this.silent=ge.Boolean("--silent",{hidden:!0});this.scriptName=ge.String();this.args=ge.Proxy()}static{this.paths=[["run"]]}static{this.usage=ot.Usage({description:"run a script defined in the package.json",details:` + This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace: + + - If the \`scripts\` field from your local package.json contains a matching script name, its definition will get executed. + + - Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed. + + - Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed. + + Whatever happens, the cwd of the spawned process will be the workspace that declares the script (which makes it possible to call commands cross-workspaces using the third syntax). + `,examples:[["Run the tests from the local workspace","$0 run test"],['Same thing, but without the "run" keyword',"$0 test"],["Inspect Webpack while running","$0 run --inspect-brk webpack"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a,locator:n}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let c=this.topLevel?s.topLevelWorkspace.anchoredLocator:n;if(!this.binariesOnly&&await In.hasPackageScript(c,this.scriptName,{project:s}))return await In.executePackageScript(c,this.scriptName,this.args,{project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});let f=await In.getPackageAccessibleBinaries(c,{project:s});if(f.get(this.scriptName)){let h=[];return this.inspect&&(typeof this.inspect=="string"?h.push(`--inspect=${this.inspect}`):h.push("--inspect")),this.inspectBrk&&(typeof this.inspectBrk=="string"?h.push(`--inspect-brk=${this.inspectBrk}`):h.push("--inspect-brk")),this.require&&h.push(`--require=${this.require}`),await In.executePackageAccessibleBinary(c,this.scriptName,this.args,{cwd:this.context.cwd,project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,nodeArgs:h,packageAccessibleBinaries:f})}if(!this.topLevel&&!this.binariesOnly&&a&&this.scriptName.includes(":")){let E=(await Promise.all(s.workspaces.map(async C=>C.manifest.scripts.has(this.scriptName)?C:null))).filter(C=>C!==null);if(E.length===1)return await In.executeWorkspaceScript(E[0],this.scriptName,this.args,{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}if(this.topLevel)throw this.scriptName==="node-gyp"?new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${G.prettyLocator(r,n)}). This typically happens because some package depends on "node-gyp" to build itself, but didn't list it in their dependencies. To fix that, please run "yarn add node-gyp" into your top-level workspace. You also can open an issue on the repository of the specified package to suggest them to use an optional peer dependency.`):new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${G.prettyLocator(r,n)}).`);{if(this.scriptName==="global")throw new nt("The 'yarn global' commands have been removed in 2.x - consider using 'yarn dlx' or a third-party plugin instead");let h=[this.scriptName].concat(this.args);for(let[E,C]of $I)for(let S of C)if(h.length>=S.length&&JSON.stringify(h.slice(0,S.length))===JSON.stringify(S))throw new nt(`Couldn't find a script named "${this.scriptName}", but a matching command can be found in the ${E} plugin. You can install it with "yarn plugin import ${E}".`);throw new nt(`Couldn't find a script named "${this.scriptName}".`)}}};Ge();Ge();Yt();var OC=class extends ft{constructor(){super(...arguments);this.descriptor=ge.String();this.resolution=ge.String()}static{this.paths=[["set","resolution"]]}static{this.usage=ot.Usage({description:"enforce a package resolution",details:'\n This command updates the resolution table so that `descriptor` is resolved by `resolution`.\n\n Note that by default this command only affect the current resolution table - meaning that this "manual override" will disappear if you remove the lockfile, or if the package disappear from the table. If you wish to make the enforced resolution persist whatever happens, edit the `resolutions` field in your top-level manifest.\n\n Note that no attempt is made at validating that `resolution` is a valid resolution entry for `descriptor`.\n ',examples:[["Force all instances of lodash@npm:^1.2.3 to resolve to 1.5.0","$0 set resolution lodash@npm:^1.2.3 npm:1.5.0"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(await s.restoreInstallState({restoreResolutions:!1}),!a)throw new ar(s.cwd,this.context.cwd);let c=G.parseDescriptor(this.descriptor,!0),f=G.makeDescriptor(c,this.resolution);return s.storedDescriptors.set(c.descriptorHash,c),s.storedDescriptors.set(f.descriptorHash,f),s.resolutionAliases.set(c.descriptorHash,f.descriptorHash),await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Ge();Dt();Yt();var Dye=ut(Go()),LC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unlink all workspaces belonging to the target project from the current one"});this.leadingArguments=ge.Rest()}static{this.paths=[["unlink"]]}static{this.usage=ot.Usage({description:"disconnect the local project from another one",details:` + This command will remove any resolutions in the project-level manifest that would have been added via a yarn link with similar arguments. + `,examples:[["Unregister a remote workspace in the current project","$0 unlink ~/ts-loader"],["Unregister all workspaces from a remote project in the current project","$0 unlink ~/jest --all"],["Unregister all previously linked workspaces","$0 unlink --all"],["Unregister all workspaces matching a glob","$0 unlink '@babel/*' 'pkg-{a,b}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=s.topLevelWorkspace,f=new Set;if(this.leadingArguments.length===0&&this.all)for(let{pattern:p,reference:h}of c.manifest.resolutions)h.startsWith("portal:")&&f.add(p.descriptor.fullName);if(this.leadingArguments.length>0)for(let p of this.leadingArguments){let h=J.resolve(this.context.cwd,fe.toPortablePath(p));if(je.isPathLike(p)){let E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(!S)throw new ar(C.cwd,h);if(this.all){for(let P of C.workspaces)P.manifest.name&&f.add(G.stringifyIdent(P.anchoredLocator));if(f.size===0)throw new nt("No workspace found to be unlinked in the target project")}else{if(!S.manifest.name)throw new nt("The target workspace doesn't have a name and thus cannot be unlinked");f.add(G.stringifyIdent(S.anchoredLocator))}}else{let E=[...c.manifest.resolutions.map(({pattern:C})=>C.descriptor.fullName)];for(let C of(0,Dye.default)(E,p))f.add(C)}}return c.manifest.resolutions=c.manifest.resolutions.filter(({pattern:p})=>!f.has(p.descriptor.fullName)),await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ge();Ge();Ge();Yt();var bye=ut(Vv()),nq=ut(Go());Ul();var MC=class extends ft{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Offer various choices, depending on the detected upgrade paths"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Resolve again ALL resolutions for those packages"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["up"]]}static{this.usage=ot.Usage({description:"upgrade dependencies across the project",details:"\n This command upgrades the packages matching the list of specified patterns to their latest available version across the whole project (regardless of whether they're part of `dependencies` or `devDependencies` - `peerDependencies` won't be affected). This is a project-wide command: all workspaces will be upgraded in the process.\n\n If `-R,--recursive` is set the command will change behavior and no other switch will be allowed. When operating under this mode `yarn up` will force all ranges matching the selected packages to be resolved again (often to the highest available versions) before being stored in the lockfile. It however won't touch your manifests anymore, so depending on your needs you might want to run both `yarn up` and `yarn up -R` to cover all bases.\n\n If `-i,--interactive` is set (or if the `preferInteractive` settings is toggled on) the command will offer various choices, depending on the detected upgrade paths. Some upgrades require this flag in order to resolve ambiguities.\n\n The, `-C,--caret`, `-E,--exact` and `-T,--tilde` options have the same meaning as in the `add` command (they change the modifier used when the range is missing or a tag, and are ignored when the range is explicitly set).\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n Generally you can see `yarn up` as a counterpart to what was `yarn upgrade --latest` in Yarn 1 (ie it ignores the ranges previously listed in your manifests), but unlike `yarn upgrade` which only upgraded dependencies in the current workspace, `yarn up` will upgrade all workspaces at the same time.\n\n This command accepts glob patterns as arguments (if valid Descriptors and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n **Note:** The ranges have to be static, only the package scopes and names can contain glob patterns.\n ",examples:[["Upgrade all instances of lodash to the latest release","$0 up lodash"],["Upgrade all instances of lodash to the latest release, but ask confirmation for each","$0 up lodash -i"],["Upgrade all instances of lodash to 1.2.3","$0 up lodash@1.2.3"],["Upgrade all instances of packages with the `@babel` scope to the latest release","$0 up '@babel/*'"],["Upgrade all instances of packages containing the word `jest` to the latest release","$0 up '*jest*'"],["Upgrade all instances of packages with the `@babel` scope to 7.0.0","$0 up '@babel/*@7.0.0'"]]})}static{this.schema=[tB("recursive",qf.Forbids,["interactive","exact","tilde","caret"],{ignore:[void 0,!1]})]}async execute(){return this.recursive?await this.executeUpRecursive():await this.executeUpClassic()}async executeUpRecursive(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=[...s.storedDescriptors.values()],f=c.map(E=>G.stringifyIdent(E)),p=new Set;for(let E of this.patterns){if(G.parseDescriptor(E).range!=="unknown")throw new nt("Ranges aren't allowed when using --recursive");for(let C of(0,nq.default)(f,E)){let S=G.parseIdent(C);p.add(S.identHash)}}let h=c.filter(E=>p.has(E.identHash));for(let E of h)s.storedDescriptors.delete(E.descriptorHash),s.storedResolutions.delete(E.descriptorHash);return await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}async executeUpClassic(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=Kv(this,s),h=f?["keep","reuse","project","latest"]:["project","latest"],E=[],C=[];for(let N of this.patterns){let U=!1,W=G.parseDescriptor(N),ee=G.stringifyIdent(W);for(let ie of s.workspaces)for(let ue of["dependencies","devDependencies"]){let me=[...ie.manifest.getForScope(ue).values()].map(Be=>G.stringifyIdent(Be)),pe=ee==="*"?me:(0,nq.default)(me,ee);for(let Be of pe){let Ce=G.parseIdent(Be),g=ie.manifest[ue].get(Ce.identHash);if(typeof g>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let we=G.makeDescriptor(Ce,W.range);E.push(Promise.resolve().then(async()=>[ie,ue,g,await zv(we,{project:s,workspace:ie,cache:n,target:ue,fixed:c,modifier:p,strategies:h})])),U=!0}}U||C.push(N)}if(C.length>1)throw new nt(`Patterns ${he.prettyList(r,C,he.Type.CODE)} don't match any packages referenced by any workspace`);if(C.length>0)throw new nt(`Pattern ${he.prettyList(r,C,he.Type.CODE)} doesn't match any packages referenced by any workspace`);let S=await Promise.all(E),P=await lA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async N=>{for(let[,,U,{suggestions:W,rejections:ee}]of S){let ie=W.filter(ue=>ue.descriptor!==null);if(ie.length===0){let[ue]=ee;if(typeof ue>"u")throw new Error("Assertion failed: Expected an error to have been set");let le=this.cli.error(ue);s.configuration.get("enableNetwork")?N.reportError(27,`${G.prettyDescriptor(r,U)} can't be resolved to a satisfying range + +${le}`):N.reportError(27,`${G.prettyDescriptor(r,U)} can't be resolved to a satisfying range (note: network resolution has been disabled) + +${le}`)}else ie.length>1&&!f&&N.reportError(27,`${G.prettyDescriptor(r,U)} has multiple possible upgrade strategies; use -i to disambiguate manually`)}});if(P.hasErrors())return P.exitCode();let I=!1,R=[];for(let[N,U,,{suggestions:W}]of S){let ee,ie=W.filter(pe=>pe.descriptor!==null),ue=ie[0].descriptor,le=ie.every(pe=>G.areDescriptorsEqual(pe.descriptor,ue));ie.length===1||le?ee=ue:(I=!0,{answer:ee}=await(0,bye.prompt)({type:"select",name:"answer",message:`Which range do you want to use in ${G.prettyWorkspace(r,N)} \u276F ${U}?`,choices:W.map(({descriptor:pe,name:Be,reason:Ce})=>pe?{name:Be,hint:Ce,descriptor:pe}:{name:Be,hint:Ce,disabled:!0}),onCancel:()=>process.exit(130),result(pe){return this.find(pe,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let me=N.manifest[U].get(ee.identHash);if(typeof me>"u")throw new Error("Assertion failed: This descriptor should have a matching entry");if(me.descriptorHash!==ee.descriptorHash)N.manifest[U].set(ee.identHash,ee),R.push([N,U,me,ee]);else{let pe=r.makeResolver(),Be={project:s,resolver:pe},Ce=r.normalizeDependency(me),g=pe.bindDescriptor(Ce,N.anchoredLocator,Be);s.forgetResolution(g)}}return await r.triggerMultipleHooks(N=>N.afterWorkspaceDependencyReplacement,R),I&&this.context.stdout.write(` +`),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}};Ge();Ge();Ge();Yt();var UC=class extends ft{constructor(){super(...arguments);this.recursive=ge.Boolean("-R,--recursive",!1,{description:"List, for each workspace, what are all the paths that lead to the dependency"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.peers=ge.Boolean("--peers",!1,{description:"Also print the peer dependencies that match the specified name"});this.package=ge.String()}static{this.paths=[["why"]]}static{this.usage=ot.Usage({description:"display the reason why a package is needed",details:` + This command prints the exact reasons why a package appears in the dependency tree. + + If \`-R,--recursive\` is set, the listing will go in depth and will list, for each workspaces, what are all the paths that lead to the dependency. Note that the display is somewhat optimized in that it will not print the package listing twice for a single package, so if you see a leaf named "Foo" when looking for "Bar", it means that "Foo" already got printed higher in the tree. + `,examples:[["Explain why lodash is used in your project","$0 why lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=G.parseIdent(this.package).identHash,c=this.recursive?cct(s,n,{configuration:r,peers:this.peers}):lct(s,n,{configuration:r,peers:this.peers});xs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1})}};function lct(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.storedPackages.values(),f=>G.stringifyLocator(f)),n={},c={children:n};for(let f of a){let p={};for(let E of f.dependencies.values()){if(!s&&f.peerDependencies.has(E.identHash))continue;let C=t.storedResolutions.get(E.descriptorHash);if(!C)throw new Error("Assertion failed: The resolution should have been registered");let S=t.storedPackages.get(C);if(!S)throw new Error("Assertion failed: The package should have been registered");if(S.identHash!==e)continue;{let I=G.stringifyLocator(f);n[I]={value:[f,he.Type.LOCATOR],children:p}}let P=G.stringifyLocator(S);p[P]={value:[{descriptor:E,locator:S},he.Type.DEPENDENT]}}}return c}function cct(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.workspaces,S=>G.stringifyLocator(S.anchoredLocator)),n=new Set,c=new Set,f=S=>{if(n.has(S.locatorHash))return c.has(S.locatorHash);if(n.add(S.locatorHash),S.identHash===e)return c.add(S.locatorHash),!0;let P=!1;S.identHash===e&&(P=!0);for(let I of S.dependencies.values()){if(!s&&S.peerDependencies.has(I.identHash))continue;let R=t.storedResolutions.get(I.descriptorHash);if(!R)throw new Error("Assertion failed: The resolution should have been registered");let N=t.storedPackages.get(R);if(!N)throw new Error("Assertion failed: The package should have been registered");f(N)&&(P=!0)}return P&&c.add(S.locatorHash),P};for(let S of a)f(S.anchoredPackage);let p=new Set,h={},E={children:h},C=(S,P,I)=>{if(!c.has(S.locatorHash))return;let R=I!==null?he.tuple(he.Type.DEPENDENT,{locator:S,descriptor:I}):he.tuple(he.Type.LOCATOR,S),N={},U={value:R,children:N},W=G.stringifyLocator(S);if(P[W]=U,!(I!==null&&t.tryWorkspaceByLocator(S))&&!p.has(S.locatorHash)){p.add(S.locatorHash);for(let ee of S.dependencies.values()){if(!s&&S.peerDependencies.has(ee.identHash))continue;let ie=t.storedResolutions.get(ee.descriptorHash);if(!ie)throw new Error("Assertion failed: The resolution should have been registered");let ue=t.storedPackages.get(ie);if(!ue)throw new Error("Assertion failed: The package should have been registered");C(ue,N,ee)}}};for(let S of a)C(S.anchoredPackage,h,null);return E}Ge();var pq={};Vt(pq,{GitFetcher:()=>tS,GitResolver:()=>rS,default:()=>kct,gitUtils:()=>ka});Ge();Dt();var ka={};Vt(ka,{TreeishProtocols:()=>eS,clone:()=>Aq,fetchBase:()=>Jye,fetchChangedFiles:()=>Kye,fetchChangedWorkspaces:()=>Pct,fetchRoot:()=>Vye,isGitUrl:()=>jC,lsRemote:()=>Yye,normalizeLocator:()=>bct,normalizeRepoUrl:()=>_C,resolveUrl:()=>fq,splitRepoUrl:()=>W0,validateRepoUrl:()=>uq});Ge();Dt();Yt();ql();var qye=ut(Hye()),HC=ut(Ie("querystring")),lq=ut(Ai());function aq(t,e,r){let s=t.indexOf(r);return t.lastIndexOf(e,s>-1?s:1/0)}function jye(t){try{return new URL(t)}catch{return}}function Sct(t){let e=aq(t,"@","#"),r=aq(t,":","#");return r>e&&(t=`${t.slice(0,r)}/${t.slice(r+1)}`),aq(t,":","#")===-1&&t.indexOf("//")===-1&&(t=`ssh://${t}`),t}function Gye(t){return jye(t)||jye(Sct(t))}function _C(t,{git:e=!1}={}){if(t=t.replace(/^git\+https:/,"https:"),t=t.replace(/^(?:github:|https:\/\/github\.com\/|git:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)(?:\.git)?(#.*)?$/,"https://github.com/$1/$2.git$3"),t=t.replace(/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/,"https://github.com/$1/$2.git#$3"),e){let r=Gye(t);r&&(t=r.href),t=t.replace(/^git\+([^:]+):/,"$1:")}return t}function Wye(){return{...process.env,GIT_SSH_COMMAND:process.env.GIT_SSH_COMMAND||`${process.env.GIT_SSH||"ssh"} -o BatchMode=yes`}}var Dct=[/^ssh:/,/^git(?:\+[^:]+)?:/,/^(?:git\+)?https?:[^#]+\/[^#]+(?:\.git)(?:#.*)?$/,/^git@[^#]+\/[^#]+\.git(?:#.*)?$/,/^(?:github:|https:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z._0-9-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z._0-9-]+?)(?:\.git)?(?:#.*)?$/,/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/],eS=(a=>(a.Commit="commit",a.Head="head",a.Tag="tag",a.Semver="semver",a))(eS||{});function jC(t){return t?Dct.some(e=>!!t.match(e)):!1}function W0(t){t=_C(t);let e=t.indexOf("#");if(e===-1)return{repo:t,treeish:{protocol:"head",request:"HEAD"},extra:{}};let r=t.slice(0,e),s=t.slice(e+1);if(s.match(/^[a-z]+=/)){let a=HC.default.parse(s);for(let[p,h]of Object.entries(a))if(typeof h!="string")throw new Error(`Assertion failed: The ${p} parameter must be a literal string`);let n=Object.values(eS).find(p=>Object.hasOwn(a,p)),[c,f]=typeof n<"u"?[n,a[n]]:["head","HEAD"];for(let p of Object.values(eS))delete a[p];return{repo:r,treeish:{protocol:c,request:f},extra:a}}else{let a=s.indexOf(":"),[n,c]=a===-1?[null,s]:[s.slice(0,a),s.slice(a+1)];return{repo:r,treeish:{protocol:n,request:c},extra:{}}}}function bct(t){return G.makeLocator(t,_C(t.reference))}function uq(t,{configuration:e}){let r=_C(t,{git:!0});if(!nn.getNetworkSettings(`https://${(0,qye.default)(r).resource}`,{configuration:e}).enableNetwork)throw new jt(80,`Request to '${r}' has been blocked because of your configuration settings`);return r}async function Yye(t,e){let r=uq(t,{configuration:e}),s=await cq("listing refs",["ls-remote",r],{cwd:e.startingCwd,env:Wye()},{configuration:e,normalizedRepoUrl:r}),a=new Map,n=/^([a-f0-9]{40})\t([^\n]+)/gm,c;for(;(c=n.exec(s.stdout))!==null;)a.set(c[2],c[1]);return a}async function fq(t,e){let{repo:r,treeish:{protocol:s,request:a},extra:n}=W0(t),c=await Yye(r,e),f=(h,E)=>{switch(h){case"commit":{if(!E.match(/^[a-f0-9]{40}$/))throw new Error("Invalid commit hash");return HC.default.stringify({...n,commit:E})}case"head":{let C=c.get(E==="HEAD"?E:`refs/heads/${E}`);if(typeof C>"u")throw new Error(`Unknown head ("${E}")`);return HC.default.stringify({...n,commit:C})}case"tag":{let C=c.get(`refs/tags/${E}`);if(typeof C>"u")throw new Error(`Unknown tag ("${E}")`);return HC.default.stringify({...n,commit:C})}case"semver":{let C=Fr.validRange(E);if(!C)throw new Error(`Invalid range ("${E}")`);let S=new Map([...c.entries()].filter(([I])=>I.startsWith("refs/tags/")).map(([I,R])=>[lq.default.parse(I.slice(10)),R]).filter(I=>I[0]!==null)),P=lq.default.maxSatisfying([...S.keys()],C);if(P===null)throw new Error(`No matching range ("${E}")`);return HC.default.stringify({...n,commit:S.get(P)})}case null:{let C;if((C=p("commit",E))!==null||(C=p("tag",E))!==null||(C=p("head",E))!==null)return C;throw E.match(/^[a-f0-9]+$/)?new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head - if a commit, use the 40-characters commit hash`):new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head`)}default:throw new Error(`Invalid Git resolution protocol ("${h}")`)}},p=(h,E)=>{try{return f(h,E)}catch{return null}};return _C(`${r}#${f(s,a)}`)}async function Aq(t,e){return await e.getLimit("cloneConcurrency")(async()=>{let{repo:r,treeish:{protocol:s,request:a}}=W0(t);if(s!=="commit")throw new Error("Invalid treeish protocol when cloning");let n=uq(r,{configuration:e}),c=await ce.mktempPromise(),f={cwd:c,env:Wye()};return await cq("cloning the repository",["clone","-c","core.autocrlf=false",n,fe.fromPortablePath(c)],f,{configuration:e,normalizedRepoUrl:n}),await cq("switching branch",["checkout",`${a}`],f,{configuration:e,normalizedRepoUrl:n}),c})}async function Vye(t){let e,r=t;do{if(e=r,await ce.existsPromise(J.join(e,".git")))return e;r=J.dirname(e)}while(r!==e);return null}async function Jye(t,{baseRefs:e}){if(e.length===0)throw new nt("Can't run this command with zero base refs specified.");let r=[];for(let f of e){let{code:p}=await qr.execvp("git",["merge-base",f,"HEAD"],{cwd:t});p===0&&r.push(f)}if(r.length===0)throw new nt(`No ancestor could be found between any of HEAD and ${e.join(", ")}`);let{stdout:s}=await qr.execvp("git",["merge-base","HEAD",...r],{cwd:t,strict:!0}),a=s.trim(),{stdout:n}=await qr.execvp("git",["show","--quiet","--pretty=format:%s",a],{cwd:t,strict:!0}),c=n.trim();return{hash:a,title:c}}async function Kye(t,{base:e,project:r}){let s=je.buildIgnorePattern(r.configuration.get("changesetIgnorePatterns")),{stdout:a}=await qr.execvp("git",["diff","--name-only",`${e}`],{cwd:t,strict:!0}),n=a.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>J.resolve(t,fe.toPortablePath(h))),{stdout:c}=await qr.execvp("git",["ls-files","--others","--exclude-standard"],{cwd:t,strict:!0}),f=c.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>J.resolve(t,fe.toPortablePath(h))),p=[...new Set([...n,...f].sort())];return s?p.filter(h=>!J.relative(r.cwd,h).match(s)):p}async function Pct({ref:t,project:e}){if(e.configuration.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let r=[J.resolve(e.cwd,Er.lockfile),J.resolve(e.cwd,e.configuration.get("cacheFolder")),J.resolve(e.cwd,e.configuration.get("installStatePath")),J.resolve(e.cwd,e.configuration.get("virtualFolder"))];await e.configuration.triggerHook(c=>c.populateYarnPaths,e,c=>{c!=null&&r.push(c)});let s=await Vye(e.configuration.projectCwd);if(s==null)throw new nt("This command can only be run on Git repositories");let a=await Jye(s,{baseRefs:typeof t=="string"?[t]:e.configuration.get("changesetBaseRefs")}),n=await Kye(s,{base:a.hash,project:e});return new Set(je.mapAndFilter(n,c=>{let f=e.tryWorkspaceByFilePath(c);return f===null?je.mapAndFilter.skip:r.some(p=>c.startsWith(p))?je.mapAndFilter.skip:f}))}async function cq(t,e,r,{configuration:s,normalizedRepoUrl:a}){try{return await qr.execvp("git",e,{...r,strict:!0})}catch(n){if(!(n instanceof qr.ExecError))throw n;let c=n.reportExtra,f=n.stderr.toString();throw new jt(1,`Failed ${t}`,p=>{p.reportError(1,` ${he.prettyField(s,{label:"Repository URL",value:he.tuple(he.Type.URL,a)})}`);for(let h of f.matchAll(/^(.+?): (.*)$/gm)){let[,E,C]=h;E=E.toLowerCase();let S=E==="error"?"Error":`${bB(E)} Error`;p.reportError(1,` ${he.prettyField(s,{label:S,value:he.tuple(he.Type.NO_HINT,C)})}`)}c?.(p)})}}var tS=class{supports(e,r){return jC(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,a=new Map(r.checksums);a.set(e.locatorHash,s);let n={...r,checksums:a},c=await this.downloadHosted(e,n);if(c!==null)return c;let[f,p,h]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote repository`),loader:()=>this.cloneFromRemote(e,n),...r.cacheOptions});return{packageFs:f,releaseFs:p,prefixPath:G.getIdentVendorPath(e),checksum:h}}async downloadHosted(e,r){return r.project.configuration.reduceHook(s=>s.fetchHostedRepository,null,e,r)}async cloneFromRemote(e,r){let s=W0(e.reference),a=await Aq(e.reference,r.project.configuration),n=J.resolve(a,s.extra.cwd??vt.dot),c=J.join(n,"package.tgz");await In.prepareExternalProject(n,c,{configuration:r.project.configuration,report:r.report,workspace:s.extra.workspace,locator:e});let f=await ce.readFilePromise(c);return await je.releaseAfterUseAsync(async()=>await ps.convertToZip(f,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1}))}};Ge();Ge();var rS=class{supportsDescriptor(e,r){return jC(e.range)}supportsLocator(e,r){return jC(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=await fq(e.range,s.project.configuration);return[G.makeLocator(e,a)]}async getSatisfying(e,r,s,a){let n=W0(e.range);return{locators:s.filter(f=>{if(f.identHash!==e.identHash)return!1;let p=W0(f.reference);return!(n.repo!==p.repo||n.treeish.protocol==="commit"&&n.treeish.request!==p.treeish.request)}),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var xct={configuration:{changesetBaseRefs:{description:"The base git refs that the current HEAD is compared against when detecting changes. Supports git branches, tags, and commits.",type:"STRING",isArray:!0,isNullable:!1,default:["master","origin/master","upstream/master","main","origin/main","upstream/main"]},changesetIgnorePatterns:{description:"Array of glob patterns; files matching them will be ignored when fetching the changed files",type:"STRING",default:[],isArray:!0},cloneConcurrency:{description:"Maximal number of concurrent clones",type:"NUMBER",default:2}},fetchers:[tS],resolvers:[rS]};var kct=xct;Yt();var GC=class extends ft{constructor(){super(...arguments);this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.noPrivate=ge.Boolean("--no-private",{description:"Exclude workspaces that have the private field set to true"});this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Also return the cross-dependencies between workspaces"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["workspaces","list"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"list all available workspaces",details:"\n This command will print the list of all workspaces in the project.\n\n - If `--since` is set, Yarn will only list workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--no-private` is set, Yarn will not list any workspaces that have the `private` field set to `true`.\n\n - If both the `-v,--verbose` and `--json` options are set, Yarn will also return the cross-dependencies between each workspaces (useful when you wish to automatically generate Buck / Bazel rules).\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{let c=this.since?await ka.fetchChangedWorkspaces({ref:this.since,project:s}):s.workspaces,f=new Set(c);if(this.recursive)for(let p of[...c].map(h=>h.getRecursiveWorkspaceDependents()))for(let h of p)f.add(h);for(let p of f){let{manifest:h}=p;if(h.private&&this.noPrivate)continue;let E;if(this.verbose){let C=new Set,S=new Set;for(let P of Ut.hardDependencies)for(let[I,R]of h.getForScope(P)){let N=s.tryWorkspaceByDescriptor(R);N===null?s.workspacesByIdent.has(I)&&S.add(R):C.add(N)}E={workspaceDependencies:Array.from(C).map(P=>P.relativeCwd),mismatchedWorkspaceDependencies:Array.from(S).map(P=>G.stringifyDescriptor(P))}}n.reportInfo(null,`${p.relativeCwd}`),n.reportJson({location:p.relativeCwd,name:h.name?G.stringifyIdent(h.name):null,...E})}})).exitCode()}};Ge();Ge();Yt();var qC=class extends ft{constructor(){super(...arguments);this.workspaceName=ge.String();this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspace"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command within the specified workspace",details:` + This command will run a given sub-command on a single workspace. + `,examples:[["Add a package to a single workspace","yarn workspace components add -D react"],["Run build script on a single workspace","yarn workspace components run build"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=s.workspaces,c=new Map(n.map(p=>[G.stringifyIdent(p.anchoredLocator),p])),f=c.get(this.workspaceName);if(f===void 0){let p=Array.from(c.keys()).sort();throw new nt(`Workspace '${this.workspaceName}' not found. Did you mean any of the following: + - ${p.join(` + - `)}?`)}return this.cli.run([this.commandName,...this.args],{cwd:f.cwd})}};var Qct={configuration:{enableImmutableInstalls:{description:"If true (the default on CI), prevents the install command from modifying the lockfile",type:"BOOLEAN",default:zye.isCI},defaultSemverRangePrefix:{description:"The default save prefix: '^', '~' or ''",type:"STRING",values:["^","~",""],default:"^"},preferReuse:{description:"If true, `yarn add` will attempt to reuse the most common dependency range in other workspaces.",type:"BOOLEAN",default:!1}},commands:[aC,lC,cC,uC,OC,bC,EC,GC,pC,hC,gC,dC,sC,oC,fC,AC,mC,yC,IC,CC,wC,BC,LC,vC,SC,xC,PC,kC,DC,QC,TC,RC,FC,NC,MC,UC,qC]},Tct=Qct;var yq={};Vt(yq,{default:()=>Oct});Ge();Ge();var gq="catalog:";var dq=t=>t.startsWith(gq),Rct=t=>t.range.slice(gq.length)||null,Xye=t=>t===null?"default catalog":`catalog "${t}"`,Fct=t=>t.scope?`@${t.scope}/${t.name}`:t.name,mq=(t,e,r,s)=>{let a=Rct(e),n;if(a===null)n=t.configuration.get("catalog");else try{let E=t.configuration.get("catalogs");E&&(n=E.get(a))}catch{n=void 0}if(!n||n.size===0)throw new jt(82,`${G.prettyDescriptor(t.configuration,e)}: ${Xye(a)} not found or empty`);let c=Fct(e),f=n.get(c);if(!f)throw new jt(82,`${G.prettyDescriptor(t.configuration,e)}: entry not found in ${Xye(a)}`);let p=t.configuration.normalizeDependency(G.makeDescriptor(e,f));return r.supportsDescriptor(p,s)?r.bindDescriptor(p,t.topLevelWorkspace.anchoredLocator,s):p};var Nct={configuration:{catalog:{description:"The default catalog of packages",type:"MAP",valueDefinition:{description:"The catalog of packages",type:"STRING"}},catalogs:{description:"Named catalogs of packages",type:"MAP",valueDefinition:{description:"A named catalog",type:"MAP",valueDefinition:{description:"Package version in the catalog",type:"STRING"}}}},hooks:{beforeWorkspacePacking:(t,e)=>{let r=t.project,s=r.configuration.makeResolver(),a={project:r,resolver:s,report:new ki};for(let n of Ut.allDependencies){let c=e[n];if(c)for(let[f,p]of Object.entries(c)){if(typeof p!="string"||!dq(p))continue;let h=G.parseIdent(f),E=G.makeDescriptor(h,p),C=mq(r,E,s,a),{protocol:S,source:P,params:I,selector:R}=G.parseRange(G.convertToManifestRange(C.range));S===t.project.configuration.get("defaultProtocol")&&(S=null),c[f]=G.makeRange({protocol:S,source:P,params:I,selector:R})}}},reduceDependency:async(t,e,r,s,{resolver:a,resolveOptions:n})=>dq(t.range)?mq(e,t,a,n):t}},Oct=Nct;var Bq={};Vt(Bq,{default:()=>Mct});Ge();var Qt={optional:!0},Eq=[["@tailwindcss/aspect-ratio@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@tailwindcss/line-clamp@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@fullhuman/postcss-purgecss@3.1.3 || 3.1.3-alpha.0",{peerDependencies:{postcss:"^8.0.0"}}],["@samverschueren/stream-to-observable@<0.3.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["any-observable@<0.5.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["@pm2/agent@<1.0.4",{dependencies:{debug:"*"}}],["debug@<4.2.0",{peerDependenciesMeta:{"supports-color":Qt}}],["got@<11",{dependencies:{"@types/responselike":"^1.0.0","@types/keyv":"^3.1.1"}}],["cacheable-lookup@<4.1.2",{dependencies:{"@types/keyv":"^3.1.1"}}],["http-link-dataloader@*",{peerDependencies:{graphql:"^0.13.1 || ^14.0.0"}}],["typescript-language-server@*",{dependencies:{"vscode-jsonrpc":"^5.0.1","vscode-languageserver-protocol":"^3.15.0"}}],["postcss-syntax@*",{peerDependenciesMeta:{"postcss-html":Qt,"postcss-jsx":Qt,"postcss-less":Qt,"postcss-markdown":Qt,"postcss-scss":Qt}}],["jss-plugin-rule-value-function@<=10.1.1",{dependencies:{"tiny-warning":"^1.0.2"}}],["ink-select-input@<4.1.0",{peerDependencies:{react:"^16.8.2"}}],["license-webpack-plugin@<2.3.18",{peerDependenciesMeta:{webpack:Qt}}],["snowpack@>=3.3.0",{dependencies:{"node-gyp":"^7.1.0"}}],["promise-inflight@*",{peerDependenciesMeta:{bluebird:Qt}}],["reactcss@*",{peerDependencies:{react:"*"}}],["react-color@<=2.19.0",{peerDependencies:{react:"*"}}],["gatsby-plugin-i18n@*",{dependencies:{ramda:"^0.24.1"}}],["useragent@^2.0.0",{dependencies:{request:"^2.88.0",yamlparser:"0.0.x",semver:"5.5.x"}}],["@apollographql/apollo-tools@<=0.5.2",{peerDependencies:{graphql:"^14.2.1 || ^15.0.0"}}],["material-table@^2.0.0",{dependencies:{"@babel/runtime":"^7.11.2"}}],["@babel/parser@*",{dependencies:{"@babel/types":"^7.8.3"}}],["fork-ts-checker-webpack-plugin@<=6.3.4",{peerDependencies:{eslint:">= 6",typescript:">= 2.7",webpack:">= 4","vue-template-compiler":"*"},peerDependenciesMeta:{eslint:Qt,"vue-template-compiler":Qt}}],["rc-animate@<=3.1.1",{peerDependencies:{react:">=16.9.0","react-dom":">=16.9.0"}}],["react-bootstrap-table2-paginator@*",{dependencies:{classnames:"^2.2.6"}}],["react-draggable@<=4.4.3",{peerDependencies:{react:">= 16.3.0","react-dom":">= 16.3.0"}}],["apollo-upload-client@<14",{peerDependencies:{graphql:"14 - 15"}}],["react-instantsearch-core@<=6.7.0",{peerDependencies:{algoliasearch:">= 3.1 < 5"}}],["react-instantsearch-dom@<=6.7.0",{dependencies:{"react-fast-compare":"^3.0.0"}}],["ws@<7.2.1",{peerDependencies:{bufferutil:"^4.0.1","utf-8-validate":"^5.0.2"},peerDependenciesMeta:{bufferutil:Qt,"utf-8-validate":Qt}}],["react-portal@<4.2.2",{peerDependencies:{"react-dom":"^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0"}}],["react-scripts@<=4.0.1",{peerDependencies:{react:"*"}}],["testcafe@<=1.10.1",{dependencies:{"@babel/plugin-transform-for-of":"^7.12.1","@babel/runtime":"^7.12.5"}}],["testcafe-legacy-api@<=4.2.0",{dependencies:{"testcafe-hammerhead":"^17.0.1","read-file-relative":"^1.2.0"}}],["@google-cloud/firestore@<=4.9.3",{dependencies:{protobufjs:"^6.8.6"}}],["gatsby-source-apiserver@*",{dependencies:{"babel-polyfill":"^6.26.0"}}],["@webpack-cli/package-utils@<=1.0.1-alpha.4",{dependencies:{"cross-spawn":"^7.0.3"}}],["gatsby-remark-prismjs@<3.3.28",{dependencies:{lodash:"^4"}}],["gatsby-plugin-favicon@*",{peerDependencies:{webpack:"*"}}],["gatsby-plugin-sharp@<=4.6.0-next.3",{dependencies:{debug:"^4.3.1"}}],["gatsby-react-router-scroll@<=5.6.0-next.0",{dependencies:{"prop-types":"^15.7.2"}}],["@rebass/forms@*",{dependencies:{"@styled-system/should-forward-prop":"^5.0.0"},peerDependencies:{react:"^16.8.6"}}],["rebass@*",{peerDependencies:{react:"^16.8.6"}}],["@ant-design/react-slick@<=0.28.3",{peerDependencies:{react:">=16.0.0"}}],["mqtt@<4.2.7",{dependencies:{duplexify:"^4.1.1"}}],["vue-cli-plugin-vuetify@<=2.0.3",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt,"vuetify-loader":Qt}}],["vue-cli-plugin-vuetify@<=2.0.4",{dependencies:{"null-loader":"^3.0.0"}}],["vue-cli-plugin-vuetify@>=2.4.3",{peerDependencies:{vue:"*"}}],["@vuetify/cli-plugin-utils@<=0.0.4",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt}}],["@vue/cli-plugin-typescript@<=5.0.0-alpha.0",{dependencies:{"babel-loader":"^8.1.0"}}],["@vue/cli-plugin-typescript@<=5.0.0-beta.0",{dependencies:{"@babel/core":"^7.12.16"},peerDependencies:{"vue-template-compiler":"^2.0.0"},peerDependenciesMeta:{"vue-template-compiler":Qt}}],["cordova-ios@<=6.3.0",{dependencies:{underscore:"^1.9.2"}}],["cordova-lib@<=10.0.1",{dependencies:{underscore:"^1.9.2"}}],["git-node-fs@*",{peerDependencies:{"js-git":"^0.7.8"},peerDependenciesMeta:{"js-git":Qt}}],["consolidate@<0.16.0",{peerDependencies:{mustache:"^3.0.0"},peerDependenciesMeta:{mustache:Qt}}],["consolidate@<=0.16.0",{peerDependencies:{velocityjs:"^2.0.1",tinyliquid:"^0.2.34","liquid-node":"^3.0.1",jade:"^1.11.0","then-jade":"*",dust:"^0.3.0","dustjs-helpers":"^1.7.4","dustjs-linkedin":"^2.7.5",swig:"^1.4.2","swig-templates":"^2.0.3","razor-tmpl":"^1.3.1",atpl:">=0.7.6",liquor:"^0.0.5",twig:"^1.15.2",ejs:"^3.1.5",eco:"^1.1.0-rc-3",jazz:"^0.0.18",jqtpl:"~1.1.0",hamljs:"^0.6.2",hamlet:"^0.3.3",whiskers:"^0.4.0","haml-coffee":"^1.14.1","hogan.js":"^3.0.2",templayed:">=0.2.3",handlebars:"^4.7.6",underscore:"^1.11.0",lodash:"^4.17.20",pug:"^3.0.0","then-pug":"*",qejs:"^3.0.5",walrus:"^0.10.1",mustache:"^4.0.1",just:"^0.1.8",ect:"^0.5.9",mote:"^0.2.0",toffee:"^0.3.6",dot:"^1.1.3","bracket-template":"^1.1.5",ractive:"^1.3.12",nunjucks:"^3.2.2",htmling:"^0.0.8","babel-core":"^6.26.3",plates:"~0.4.11","react-dom":"^16.13.1",react:"^16.13.1","arc-templates":"^0.5.3",vash:"^0.13.0",slm:"^2.0.0",marko:"^3.14.4",teacup:"^2.0.0","coffee-script":"^1.12.7",squirrelly:"^5.1.0",twing:"^5.0.2"},peerDependenciesMeta:{velocityjs:Qt,tinyliquid:Qt,"liquid-node":Qt,jade:Qt,"then-jade":Qt,dust:Qt,"dustjs-helpers":Qt,"dustjs-linkedin":Qt,swig:Qt,"swig-templates":Qt,"razor-tmpl":Qt,atpl:Qt,liquor:Qt,twig:Qt,ejs:Qt,eco:Qt,jazz:Qt,jqtpl:Qt,hamljs:Qt,hamlet:Qt,whiskers:Qt,"haml-coffee":Qt,"hogan.js":Qt,templayed:Qt,handlebars:Qt,underscore:Qt,lodash:Qt,pug:Qt,"then-pug":Qt,qejs:Qt,walrus:Qt,mustache:Qt,just:Qt,ect:Qt,mote:Qt,toffee:Qt,dot:Qt,"bracket-template":Qt,ractive:Qt,nunjucks:Qt,htmling:Qt,"babel-core":Qt,plates:Qt,"react-dom":Qt,react:Qt,"arc-templates":Qt,vash:Qt,slm:Qt,marko:Qt,teacup:Qt,"coffee-script":Qt,squirrelly:Qt,twing:Qt}}],["vue-loader@<=16.3.3",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",webpack:"^4.1.0 || ^5.0.0-0"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt}}],["vue-loader@^16.7.0",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",vue:"^3.2.13"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt,vue:Qt}}],["scss-parser@<=1.0.5",{dependencies:{lodash:"^4.17.21"}}],["query-ast@<1.0.5",{dependencies:{lodash:"^4.17.21"}}],["redux-thunk@<=2.3.0",{peerDependencies:{redux:"^4.0.0"}}],["skypack@<=0.3.2",{dependencies:{tar:"^6.1.0"}}],["@npmcli/metavuln-calculator@<2.0.0",{dependencies:{"json-parse-even-better-errors":"^2.3.1"}}],["bin-links@<2.3.0",{dependencies:{"mkdirp-infer-owner":"^1.0.2"}}],["rollup-plugin-polyfill-node@<=0.8.0",{peerDependencies:{rollup:"^1.20.0 || ^2.0.0"}}],["snowpack@<3.8.6",{dependencies:{"magic-string":"^0.25.7"}}],["elm-webpack-loader@*",{dependencies:{temp:"^0.9.4"}}],["winston-transport@<=4.4.0",{dependencies:{logform:"^2.2.0"}}],["jest-vue-preprocessor@*",{dependencies:{"@babel/core":"7.8.7","@babel/template":"7.8.6"},peerDependencies:{pug:"^2.0.4"},peerDependenciesMeta:{pug:Qt}}],["redux-persist@*",{peerDependencies:{react:">=16"},peerDependenciesMeta:{react:Qt}}],["sodium@>=3",{dependencies:{"node-gyp":"^3.8.0"}}],["babel-plugin-graphql-tag@<=3.1.0",{peerDependencies:{graphql:"^14.0.0 || ^15.0.0"}}],["@playwright/test@<=1.14.1",{dependencies:{"jest-matcher-utils":"^26.4.2"}}],...["babel-plugin-remove-graphql-queries@<3.14.0-next.1","babel-preset-gatsby-package@<1.14.0-next.1","create-gatsby@<1.14.0-next.1","gatsby-admin@<0.24.0-next.1","gatsby-cli@<3.14.0-next.1","gatsby-core-utils@<2.14.0-next.1","gatsby-design-tokens@<3.14.0-next.1","gatsby-legacy-polyfills@<1.14.0-next.1","gatsby-plugin-benchmark-reporting@<1.14.0-next.1","gatsby-plugin-graphql-config@<0.23.0-next.1","gatsby-plugin-image@<1.14.0-next.1","gatsby-plugin-mdx@<2.14.0-next.1","gatsby-plugin-netlify-cms@<5.14.0-next.1","gatsby-plugin-no-sourcemaps@<3.14.0-next.1","gatsby-plugin-page-creator@<3.14.0-next.1","gatsby-plugin-preact@<5.14.0-next.1","gatsby-plugin-preload-fonts@<2.14.0-next.1","gatsby-plugin-schema-snapshot@<2.14.0-next.1","gatsby-plugin-styletron@<6.14.0-next.1","gatsby-plugin-subfont@<3.14.0-next.1","gatsby-plugin-utils@<1.14.0-next.1","gatsby-recipes@<0.25.0-next.1","gatsby-source-shopify@<5.6.0-next.1","gatsby-source-wikipedia@<3.14.0-next.1","gatsby-transformer-screenshot@<3.14.0-next.1","gatsby-worker@<0.5.0-next.1"].map(t=>[t,{dependencies:{"@babel/runtime":"^7.14.8"}}]),["gatsby-core-utils@<2.14.0-next.1",{dependencies:{got:"8.3.2"}}],["gatsby-plugin-gatsby-cloud@<=3.1.0-next.0",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["gatsby-plugin-gatsby-cloud@<=3.2.0-next.1",{peerDependencies:{webpack:"*"}}],["babel-plugin-remove-graphql-queries@<=3.14.0-next.1",{dependencies:{"gatsby-core-utils":"^2.8.0-next.1"}}],["gatsby-plugin-netlify@3.13.0-next.1",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["clipanion-v3-codemod@<=0.2.0",{peerDependencies:{jscodeshift:"^0.11.0"}}],["react-live@*",{peerDependencies:{"react-dom":"*",react:"*"}}],["webpack@<4.44.1",{peerDependenciesMeta:{"webpack-cli":Qt,"webpack-command":Qt}}],["webpack@<5.0.0-beta.23",{peerDependenciesMeta:{"webpack-cli":Qt}}],["webpack-dev-server@<3.10.2",{peerDependenciesMeta:{"webpack-cli":Qt}}],["@docusaurus/responsive-loader@<1.5.0",{peerDependenciesMeta:{sharp:Qt,jimp:Qt}}],["eslint-module-utils@*",{peerDependenciesMeta:{"eslint-import-resolver-node":Qt,"eslint-import-resolver-typescript":Qt,"eslint-import-resolver-webpack":Qt,"@typescript-eslint/parser":Qt}}],["eslint-plugin-import@*",{peerDependenciesMeta:{"@typescript-eslint/parser":Qt}}],["critters-webpack-plugin@<3.0.2",{peerDependenciesMeta:{"html-webpack-plugin":Qt}}],["terser@<=5.10.0",{dependencies:{acorn:"^8.5.0"}}],["babel-preset-react-app@10.0.x <10.0.2",{dependencies:{"@babel/plugin-proposal-private-property-in-object":"^7.16.7"}}],["eslint-config-react-app@*",{peerDependenciesMeta:{typescript:Qt}}],["@vue/eslint-config-typescript@<11.0.0",{peerDependenciesMeta:{typescript:Qt}}],["unplugin-vue2-script-setup@<0.9.1",{peerDependencies:{"@vue/composition-api":"^1.4.3","@vue/runtime-dom":"^3.2.26"}}],["@cypress/snapshot@*",{dependencies:{debug:"^3.2.7"}}],["auto-relay@<=0.14.0",{peerDependencies:{"reflect-metadata":"^0.1.13"}}],["vue-template-babel-compiler@<1.2.0",{peerDependencies:{"vue-template-compiler":"^2.6.0"}}],["@parcel/transformer-image@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["@parcel/transformer-js@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["parcel@*",{peerDependenciesMeta:{"@parcel/core":Qt}}],["react-scripts@*",{peerDependencies:{eslint:"*"}}],["focus-trap-react@^8.0.0",{dependencies:{tabbable:"^5.3.2"}}],["react-rnd@<10.3.7",{peerDependencies:{react:">=16.3.0","react-dom":">=16.3.0"}}],["connect-mongo@<5.0.0",{peerDependencies:{"express-session":"^1.17.1"}}],["vue-i18n@<9",{peerDependencies:{vue:"^2"}}],["vue-router@<4",{peerDependencies:{vue:"^2"}}],["unified@<10",{dependencies:{"@types/unist":"^2.0.0"}}],["react-github-btn@<=1.3.0",{peerDependencies:{react:">=16.3.0"}}],["react-dev-utils@*",{peerDependencies:{typescript:">=2.7",webpack:">=4"},peerDependenciesMeta:{typescript:Qt}}],["@asyncapi/react-component@<=1.0.0-next.39",{peerDependencies:{react:">=16.8.0","react-dom":">=16.8.0"}}],["xo@*",{peerDependencies:{webpack:">=1.11.0"},peerDependenciesMeta:{webpack:Qt}}],["babel-plugin-remove-graphql-queries@<=4.20.0-next.0",{dependencies:{"@babel/types":"^7.15.4"}}],["gatsby-plugin-page-creator@<=4.20.0-next.1",{dependencies:{"fs-extra":"^10.1.0"}}],["gatsby-plugin-utils@<=3.14.0-next.1",{dependencies:{fastq:"^1.13.0"},peerDependencies:{graphql:"^15.0.0"}}],["gatsby-plugin-mdx@<3.1.0-next.1",{dependencies:{mkdirp:"^1.0.4"}}],["gatsby-plugin-mdx@^2",{peerDependencies:{gatsby:"^3.0.0-next"}}],["fdir@<=5.2.0",{peerDependencies:{picomatch:"2.x"},peerDependenciesMeta:{picomatch:Qt}}],["babel-plugin-transform-typescript-metadata@<=0.3.2",{peerDependencies:{"@babel/core":"^7","@babel/traverse":"^7"},peerDependenciesMeta:{"@babel/traverse":Qt}}],["graphql-compose@>=9.0.10",{peerDependencies:{graphql:"^14.2.0 || ^15.0.0 || ^16.0.0"}}],["vite-plugin-vuetify@<=1.0.2",{peerDependencies:{vue:"^3.0.0"}}],["webpack-plugin-vuetify@<=2.0.1",{peerDependencies:{vue:"^3.2.6"}}],["eslint-import-resolver-vite@<2.0.1",{dependencies:{debug:"^4.3.4",resolve:"^1.22.8"}}],["notistack@^3.0.0",{dependencies:{csstype:"^3.0.10"}}],["@fastify/type-provider-typebox@^5.0.0",{peerDependencies:{fastify:"^5.0.0"}}],["@fastify/type-provider-typebox@^4.0.0",{peerDependencies:{fastify:"^4.0.0"}}]];var Iq;function Zye(){return typeof Iq>"u"&&(Iq=Ie("zlib").brotliDecompressSync(Buffer.from("G7weAByFTVk3Vs7UfHhq4yykgEM7pbW7TI43SG2S5tvGrwHBAzdz+s/npQ6tgEvobvxisrPIadkXeUAJotBn5bDZ5kAhcRqsIHe3F75Walet5hNalwgFDtxb0BiDUjiUQkjG0yW2hto9HPgiCkm316d6bC0kST72YN7D7rfkhCE9x4J0XwB0yavalxpUu2t9xszHrmtwalOxT7VslsxWcB1qpqZwERUra4psWhTV8BgwWeizurec82Caf1ABL11YMfbf8FJ9JBceZOkgmvrQPbC9DUldX/yMbmX06UQluCEjSwUoyO+EZPIjofr+/oAZUck2enraRD+oWLlnlYnj8xB+gwSo9lmmks4fXv574qSqcWA6z21uYkzMu3EWj+K23RxeQlLqiE35/rC8GcS4CGkKHKKq+zAIQwD9iRDNfiAqueLLpicFFrNsAI4zeTD/eO9MHcnRa5m8UT+M2+V+AkFST4BlKneiAQRSdST8KEAIyFlULt6wa9EBd0Ds28VmpaxquJdVt+nwdEs5xUskI13OVtFyY0UrQIRAlCuvvWivvlSKQfTO+2Q8OyUR1W5RvetaPz4jD27hdtwHFFA1Ptx6Ee/t2cY2rg2G46M1pNDRf2pWhvpy8pqMnuI3++4OF3+7OFIWXGjh+o7Nr2jNvbiYcQdQS1h903/jVFgOpA0yJ78z+x759bFA0rq+6aY5qPB4FzS3oYoLupDUhD9nDz6F6H7hpnlMf18KNKDu4IKjTWwrAnY6MFQw1W6ymOALHlFyCZmQhldg1MQHaMVVQTVgDC60TfaBqG++Y8PEoFhN/PBTZT175KNP/BlHDYGOOBmnBdzqJKplZ/ljiVG0ZBzfqeBRrrUkn6rA54462SgiliKoYVnbeptMdXNfAuaupIEi0bApF10TlgHfmEJAPUVidRVFyDupSem5po5vErPqWKhKbUIp0LozpYsIKK57dM/HKr+nguF+7924IIWMICkQ8JUigs9D+W+c4LnNoRtPPKNRUiCYmP+Jfo2lfKCKw8qpraEeWU3uiNRO6zcyKQoXPR5htmzzLznke7b4YbXW3I1lIRzmgG02Udb58U+7TpwyN7XymCgH+wuPDthZVQvRZuEP+SnLtMicz9m5zASWOBiAcLmkuFlTKuHspSIhCBD0yUPKcxu81A+4YD78rA2vtwsUEday9WNyrShyrl60rWmA+SmbYZkQOwFJWArxRYYc5jGhA5ikxYw1rx3ei4NmeX/lKiwpZ9Ln1tV2Ae7sArvxuVLbJjqJRjW1vFXAyHpvLG+8MJ6T2Ubx5M2KDa2SN6vuIGxJ9WQM9Mk3Q7aCNiZONXllhqq24DmoLbQfW2rYWsOgHWjtOmIQMyMKdiHZDjoyIq5+U700nZ6odJAoYXPQBvFNiQ78d5jaXliBqLTJEqUCwi+LiH2mx92EmNKDsJL74Z613+3lf20pxkV1+erOrjj8pW00vsPaahKUM+05ssd5uwM7K482KWEf3TCwlg/o3e5ngto7qSMz7YteIgCsF1UOcsLk7F7MxWbvrPMY473ew0G+noVL8EPbkmEMftMSeL6HFub/zy+2JQ==","base64")).toString()),Iq}var Cq;function $ye(){return typeof Cq>"u"&&(Cq=Ie("zlib").brotliDecompressSync(Buffer.from("G8MSIIzURnVBnObTcvb3XE6v2S9Qgc2K801Oa5otNKEtK8BINZNcaQHy+9/vf/WXBimwutXC33P2DPc64pps5rz7NGGWaOKNSPL4Y2KRE8twut2lFOIN+OXPtRmPMRhMTILib2bEQx43az2I5d3YS8Roa5UZpF/ujHb3Djd3GDvYUfvFYSUQ39vb2cmifp/rgB4J/65JK3wRBTvMBoNBmn3mbXC63/gbBkW/2IRPri0O8bcsRBsmarF328pAln04nyJFkwUAvNu934supAqLtyerZZpJ8I8suJHhf/ocMV+scKwa8NOiDKIPXw6Ex/EEZD6TEGaW8N5zvNHYF10l6Lfooj7D5W2k3dgvQSbp2Wv8TGOayS978gxlOLVjTGXs66ozewbrjwElLtyrYNnWTfzzdEutgROUFPVMhnMoy8EjJLLlWwIEoySxliim9kYW30JUHiPVyjt0iAw/ZpPmCbUCltYPnq6ZNblIKhTNhqS/oqC9iya5sGKZTOVsTEg34n92uZTf2iPpcZih8rPW8CzA+adIGmyCPcKdLMsBLShd+zuEbTrqpwuh+DLmracZcjPC5Sdf5odDAhKpFuOsQS67RT+1VgWWygSv3YwxDnylc04/PYuaMeIzhBkLrvs7e/OUzRTF56MmfY6rI63QtEjEQzq637zQqJ39nNhu3NmoRRhW/086bHGBUtx0PE0j3aEGvkdh9WJC8y8j8mqqke9/dQ5la+Q3ba4RlhvTbnfQhPDDab3tUifkjKuOsp13mXEmO00Mu88F/M67R7LXfoFDFLNtgCSWjWX+3Jn1371pJTK9xPBiMJafvDjtFyAzu8rxeQ0TKMQXNPs5xxiBOd+BRJP8KP88XPtJIbZKh/cdW8KvBUkpqKpGoiIaA32c3/JnQr4efXt85mXvidOvn/eU3Pase1typLYBalJ14mCso9h79nuMOuCa/kZAOkJHmTjP5RM2WNoPasZUAnT1TAE/NH25hUxcQv6hQWR/m1PKk4ooXMcM4SR1iYU3fUohvqk4RY2hbmTVVIXv6TvqO+0doOjgeVFAcom+RlwJQmOVH7pr1Q9LoJT6n1DeQEB+NHygsATbIwTcOKZlJsY8G4+suX1uQLjUWwLjjs0mvSvZcLTpIGAekeR7GCgl8eo3ndAqEe2XCav4huliHjdbIPBsGJuPX7lrO9HX1UbXRH5opOe1x6JsOSgHZR+EaxuXVhpLLxm6jk1LJtZfHSc6BKPun3CpYYVMJGwEUyk8MTGG0XL5MfEwaXpnc9TKnBmlGn6nHiGREc3ysn47XIBDzA+YvFdjZzVIEDcKGpS6PbUJehFRjEne8D0lVU1XuRtlgszq6pTNlQ/3MzNOEgCWPyTct22V2mEi2krizn5VDo9B19/X2DB3hCGRMM7ONbtnAcIx/OWB1u5uPbW1gsH8irXxT/IzG0PoXWYjhbMsH3KTuoOl5o17PulcgvsfTSnKFM354GWI8luqZnrswWjiXy3G+Vbyo1KMopFmmvBwNELgaS8z8dNZchx/Cl/xjddxhMcyqtzFyONb2Zdu90NkI8pAeufe7YlXrp53v8Dj/l8vWeVspRKBGXScBBPI/HinSTGmLDOGGOCIyH0JFdOZx0gWsacNlQLJMIrBhqRxXxHF/5pseWwejlAAvZ3klZSDSYY8mkToaWejXhgNomeGtx1DTLEUFMRkgF5yFB22WYdJnaWN14r1YJj81hGi45+jrADS5nYRhCiSlCJJ1nL8pYX+HDSMhdTEWyRcgHVp/IsUIZYMfT+YYncUQPgcxNGCHfZ88vDdrcUuaGIl6zhAsiaq7R5dfqrqXH/JcBhfjT8D0azayIyEz75Nxp6YkcyDxlJq3EXnJUpqDohJJOysL1t1uNiHESlvsxPb5cpbW0+ICZqJmUZus1BMW0F5IVBODLIo2zHHjA0=","base64")).toString()),Cq}var wq;function eEe(){return typeof wq>"u"&&(wq=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),wq}var tEe=new Map([[G.makeIdent(null,"fsevents").identHash,Zye],[G.makeIdent(null,"resolve").identHash,$ye],[G.makeIdent(null,"typescript").identHash,eEe]]),Lct={hooks:{registerPackageExtensions:async(t,e)=>{for(let[r,s]of Eq)e(G.parseDescriptor(r,!0),s)},getBuiltinPatch:async(t,e)=>{let r="compat/";if(!e.startsWith(r))return;let s=G.parseIdent(e.slice(r.length)),a=tEe.get(s.identHash)?.();return typeof a<"u"?a:null},reduceDependency:async(t,e,r,s)=>typeof tEe.get(t.identHash)>"u"?t:G.makeDescriptor(t,G.makeRange({protocol:"patch:",source:G.stringifyDescriptor(t),selector:`optional!builtin`,params:null}))}},Mct=Lct;var _q={};Vt(_q,{ConstraintsCheckCommand:()=>ZC,ConstraintsQueryCommand:()=>zC,ConstraintsSourceCommand:()=>XC,default:()=>nut});Ge();Ge();iS();var YC=class{constructor(e){this.project=e}createEnvironment(){let e=new WC(["cwd","ident"]),r=new WC(["workspace","type","ident"]),s=new WC(["ident"]),a={manifestUpdates:new Map,reportedErrors:new Map},n=new Map,c=new Map;for(let f of this.project.storedPackages.values()){let p=Array.from(f.peerDependencies.values(),h=>[G.stringifyIdent(h),h.range]);n.set(f.locatorHash,{workspace:null,ident:G.stringifyIdent(f),version:f.version,dependencies:new Map,peerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional!==!0)),optionalPeerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional===!0))})}for(let f of this.project.storedPackages.values()){let p=n.get(f.locatorHash);p.dependencies=new Map(Array.from(f.dependencies.values(),h=>{let E=this.project.storedResolutions.get(h.descriptorHash);if(typeof E>"u")throw new Error("Assertion failed: The resolution should have been registered");let C=n.get(E);if(typeof C>"u")throw new Error("Assertion failed: The package should have been registered");return[G.stringifyIdent(h),C]})),p.dependencies.delete(p.ident)}for(let f of this.project.workspaces){let p=G.stringifyIdent(f.anchoredLocator),h=f.manifest.exportTo({}),E=n.get(f.anchoredLocator.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");let C=(R,N,{caller:U=Ui.getCaller()}={})=>{let W=nS(R),ee=je.getMapWithDefault(a.manifestUpdates,f.cwd),ie=je.getMapWithDefault(ee,W),ue=je.getSetWithDefault(ie,N);U!==null&&ue.add(U)},S=R=>C(R,void 0,{caller:Ui.getCaller()}),P=R=>{je.getArrayWithDefault(a.reportedErrors,f.cwd).push(R)},I=e.insert({cwd:f.relativeCwd,ident:p,manifest:h,pkg:E,set:C,unset:S,error:P});c.set(f,I);for(let R of Ut.allDependencies)for(let N of f.manifest[R].values()){let U=G.stringifyIdent(N),W=()=>{C([R,U],void 0,{caller:Ui.getCaller()})},ee=ue=>{C([R,U],ue,{caller:Ui.getCaller()})},ie=null;if(R!=="peerDependencies"&&(R!=="dependencies"||!f.manifest.devDependencies.has(N.identHash))){let ue=f.anchoredPackage.dependencies.get(N.identHash);if(ue){if(typeof ue>"u")throw new Error("Assertion failed: The dependency should have been registered");let le=this.project.storedResolutions.get(ue.descriptorHash);if(typeof le>"u")throw new Error("Assertion failed: The resolution should have been registered");let me=n.get(le);if(typeof me>"u")throw new Error("Assertion failed: The package should have been registered");ie=me}}r.insert({workspace:I,ident:U,range:N.range,type:R,resolution:ie,update:ee,delete:W,error:P})}}for(let f of this.project.storedPackages.values()){let p=this.project.tryWorkspaceByLocator(f);if(!p)continue;let h=c.get(p);if(typeof h>"u")throw new Error("Assertion failed: The workspace should have been registered");let E=n.get(f.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");E.workspace=h}return{workspaces:e,dependencies:r,packages:s,result:a}}async process(){let e=this.createEnvironment(),r={Yarn:{workspace:a=>e.workspaces.find(a)[0]??null,workspaces:a=>e.workspaces.find(a),dependency:a=>e.dependencies.find(a)[0]??null,dependencies:a=>e.dependencies.find(a),package:a=>e.packages.find(a)[0]??null,packages:a=>e.packages.find(a)}},s=await this.project.loadUserConfig();return s?.constraints?(await s.constraints(r),e.result):null}};Ge();Ge();Yt();var zC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.query=ge.String()}static{this.paths=[["constraints","query"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"query the constraints fact database",details:` + This command will output all matches to the given prolog query. + `,examples:[["List all dependencies throughout the workspace","yarn constraints query 'workspace_has_dependency(_, DependencyName, _, _).'"]]})}async execute(){let{Constraints:r}=await Promise.resolve().then(()=>(lS(),aS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a),c=this.query;return c.endsWith(".")||(c=`${c}.`),(await Ot.start({configuration:s,json:this.json,stdout:this.context.stdout},async p=>{for await(let h of n.query(c)){let E=Array.from(Object.entries(h)),C=E.length,S=E.reduce((P,[I])=>Math.max(P,I.length),0);for(let P=0;P(lS(),aS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a);this.context.stdout.write(this.verbose?n.fullSource:n.source)}};Ge();Ge();Yt();iS();var ZC=class extends ft{constructor(){super(...arguments);this.fix=ge.Boolean("--fix",!1,{description:"Attempt to automatically fix unambiguous issues, following a multi-pass process"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["constraints"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"check that the project constraints are met",details:` + This command will run constraints on your project and emit errors for each one that is found but isn't met. If any error is emitted the process will exit with a non-zero exit code. + + If the \`--fix\` flag is used, Yarn will attempt to automatically fix the issues the best it can, following a multi-pass process (with a maximum of 10 iterations). Some ambiguous patterns cannot be autofixed, in which case you'll have to manually specify the right resolution. + + For more information as to how to write constraints, please consult our dedicated page on our website: https://yarnpkg.com/features/constraints. + `,examples:[["Check that all constraints are satisfied","yarn constraints"],["Autofix all unmet constraints","yarn constraints --fix"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let a=await s.loadUserConfig(),n;if(a?.constraints)n=new YC(s);else{let{Constraints:h}=await Promise.resolve().then(()=>(lS(),aS));n=await h.find(s)}let c,f=!1,p=!1;for(let h=this.fix?10:1;h>0;--h){let E=await n.process();if(!E)break;let{changedWorkspaces:C,remainingErrors:S}=iF(s,E,{fix:this.fix}),P=[];for(let[I,R]of C){let N=I.manifest.indent;I.manifest=new Ut,I.manifest.indent=N,I.manifest.load(R),P.push(I.persistManifest())}if(await Promise.all(P),!(C.size>0&&h>1)){c=rEe(S,{configuration:r}),f=!1,p=!0;for(let[,I]of S)for(let R of I)R.fixable?f=!0:p=!1}}if(c.children.length===0)return 0;if(f){let h=p?`Those errors can all be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`:`Errors prefixed by '\u2699' can be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`;await Ot.start({configuration:r,stdout:this.context.stdout,includeNames:!1,includeFooter:!1},async E=>{E.reportInfo(0,h),E.reportSeparator()})}return c.children=je.sortMap(c.children,h=>h.value[1]),xs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1}),1}};iS();var rut={configuration:{enableConstraintsChecks:{description:"If true, constraints will run during installs",type:"BOOLEAN",default:!1},constraintsPath:{description:"The path of the constraints file.",type:"ABSOLUTE_PATH",default:"./constraints.pro"}},commands:[zC,XC,ZC],hooks:{async validateProjectAfterInstall(t,{reportError:e}){if(!t.configuration.get("enableConstraintsChecks"))return;let r=await t.loadUserConfig(),s;if(r?.constraints)s=new YC(t);else{let{Constraints:c}=await Promise.resolve().then(()=>(lS(),aS));s=await c.find(t)}let a=await s.process();if(!a)return;let{remainingErrors:n}=iF(t,a);if(n.size!==0)if(t.configuration.isCI)for(let[c,f]of n)for(let p of f)e(84,`${he.pretty(t.configuration,c.anchoredLocator,he.Type.IDENT)}: ${p.text}`);else e(84,`Constraint check failed; run ${he.pretty(t.configuration,"yarn constraints",he.Type.CODE)} for more details`)}}},nut=rut;var Hq={};Vt(Hq,{CreateCommand:()=>$C,DlxCommand:()=>ew,default:()=>sut});Ge();Yt();var $C=class extends ft{constructor(){super(...arguments);this.pkg=ge.String("-p,--package",{description:"The package to run the provided command from"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["create"]]}async execute(){let r=[];this.pkg&&r.push("--package",this.pkg),this.quiet&&r.push("--quiet");let s=this.command.replace(/^(@[^@/]+)(@|$)/,"$1/create$2"),a=G.parseDescriptor(s),n=a.name.match(/^create(-|$)/)?a:a.scope?G.makeIdent(a.scope,`create-${a.name}`):G.makeIdent(null,`create-${a.name}`),c=G.stringifyIdent(n);return a.range!=="unknown"&&(c+=`@${a.range}`),this.cli.run(["dlx",...r,c,...this.args])}};Ge();Ge();Dt();Yt();var ew=class extends ft{constructor(){super(...arguments);this.packages=ge.Array("-p,--package",{description:"The package(s) to install before running the command"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["dlx"]]}static{this.usage=ot.Usage({description:"run a package in a temporary environment",details:"\n This command will install a package within a temporary environment, and run its binary script if it contains any. The binary will run within the current cwd.\n\n By default Yarn will download the package named `command`, but this can be changed through the use of the `-p,--package` flag which will instruct Yarn to still run the same command but from a different package.\n\n Using `yarn dlx` as a replacement of `yarn add` isn't recommended, as it makes your project non-deterministic (Yarn doesn't keep track of the packages installed through `dlx` - neither their name, nor their version).\n ",examples:[["Use create-vite to scaffold a new Vite project","yarn dlx create-vite"],["Install multiple packages for a single command",`yarn dlx -p typescript -p ts-node ts-node --transpile-only -e "console.log('hello!')"`]]})}async execute(){return ze.telemetry=null,await ce.mktempPromise(async r=>{let s=J.join(r,`dlx-${process.pid}`);await ce.mkdirPromise(s),await ce.writeFilePromise(J.join(s,"package.json"),`{} +`),await ce.writeFilePromise(J.join(s,"yarn.lock"),"");let a=J.join(s,".yarnrc.yml"),n=await ze.findProjectCwd(this.context.cwd),f={enableGlobalCache:!(await ze.find(this.context.cwd,null,{strict:!1})).get("enableGlobalCache"),enableTelemetry:!1,logFilters:[{code:Yf(68),level:he.LogLevel.Discard}]},p=n!==null?J.join(n,".yarnrc.yml"):null;p!==null&&ce.existsSync(p)?(await ce.copyFilePromise(p,a),await ze.updateConfiguration(s,N=>{let U=je.toMerged(N,f);return Array.isArray(N.plugins)&&(U.plugins=N.plugins.map(W=>{let ee=typeof W=="string"?W:W.path,ie=fe.isAbsolute(ee)?ee:fe.resolve(fe.fromPortablePath(n),ee);return typeof W=="string"?ie:{path:ie,spec:W.spec}})),U})):await ce.writeJsonPromise(a,f);let h=this.packages??[this.command],E=G.parseDescriptor(this.command).name,C=await this.cli.run(["add","--fixed","--",...h],{cwd:s,quiet:this.quiet});if(C!==0)return C;this.quiet||this.context.stdout.write(` +`);let S=await ze.find(s,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,s);if(I===null)throw new ar(P.cwd,s);await P.restoreInstallState();let R=await In.getWorkspaceAccessibleBinaries(I);return R.has(E)===!1&&R.size===1&&typeof this.packages>"u"&&(E=Array.from(R)[0][0]),await In.executeWorkspaceAccessibleBinary(I,E,this.args,{packageAccessibleBinaries:R,cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})})}};var iut={commands:[$C,ew]},sut=iut;var qq={};Vt(qq,{ExecFetcher:()=>uS,ExecResolver:()=>fS,default:()=>lut,execUtils:()=>lF});Ge();Ge();Dt();var cA="exec:";var lF={};Vt(lF,{loadGeneratorFile:()=>cS,makeLocator:()=>Gq,makeSpec:()=>PEe,parseSpec:()=>jq});Ge();Dt();function jq(t){let{params:e,selector:r}=G.parseRange(t),s=fe.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?G.parseLocator(e.locator):null,path:s}}function PEe({parentLocator:t,path:e,generatorHash:r,protocol:s}){let a=t!==null?{locator:G.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return G.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function Gq(t,{parentLocator:e,path:r,generatorHash:s,protocol:a}){return G.makeLocator(t,PEe({parentLocator:e,path:r,generatorHash:s,protocol:a}))}async function cS(t,e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(t,{protocol:e}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath)}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.join(c.prefixPath,a);return await f.readFilePromise(p,"utf8")}var uS=class{supports(e,r){return!!e.reference.startsWith(cA)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:cA});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){let s=await cS(e.reference,cA,r);return ce.mktempPromise(async a=>{let n=J.join(a,"generator.js");return await ce.writeFilePromise(n,s),ce.mktempPromise(async c=>{if(await this.generatePackage(c,e,n,r),!ce.existsSync(J.join(c,"build")))throw new Error("The script should have generated a build directory");return await ps.makeArchiveFromDirectory(J.join(c,"build"),{prefixPath:G.getIdentVendorPath(e),compressionLevel:r.project.configuration.get("compressionLevel")})})})}async generatePackage(e,r,s,a){return await ce.mktempPromise(async n=>{let c=await In.makeScriptEnv({project:a.project,binFolder:n}),f=J.join(e,"runtime.js");return await ce.mktempPromise(async p=>{let h=J.join(p,"buildfile.log"),E=J.join(e,"generator"),C=J.join(e,"build");await ce.mkdirPromise(E),await ce.mkdirPromise(C);let S={tempDir:fe.fromPortablePath(E),buildDir:fe.fromPortablePath(C),locator:G.stringifyLocator(r)};await ce.writeFilePromise(f,` + // Expose 'Module' as a global variable + Object.defineProperty(global, 'Module', { + get: () => require('module'), + configurable: true, + enumerable: false, + }); + + // Expose non-hidden built-in modules as global variables + for (const name of Module.builtinModules.filter((name) => name !== 'module' && !name.startsWith('_'))) { + Object.defineProperty(global, name, { + get: () => require(name), + configurable: true, + enumerable: false, + }); + } + + // Expose the 'execEnv' global variable + Object.defineProperty(global, 'execEnv', { + value: { + ...${JSON.stringify(S)}, + }, + enumerable: true, + }); + `);let P=c.NODE_OPTIONS||"",I=/\s*--require\s+\S*\.pnp\.c?js\s*/g;P=P.replace(I," ").trim(),c.NODE_OPTIONS=P;let{stdout:R,stderr:N}=a.project.configuration.getSubprocessStreams(h,{header:`# This file contains the result of Yarn generating a package (${G.stringifyLocator(r)}) +`,prefix:G.prettyLocator(a.project.configuration,r),report:a.report}),{code:U}=await qr.pipevp(process.execPath,["--require",fe.fromPortablePath(f),fe.fromPortablePath(s),G.stringifyIdent(r)],{cwd:e,env:c,stdin:null,stdout:R,stderr:N});if(U!==0)throw ce.detachTemp(p),new Error(`Package generation failed (exit code ${U}, logs can be found here: ${he.pretty(a.project.configuration,h,he.Type.PATH)})`)})})}};Ge();Ge();var out=2,fS=class{supportsDescriptor(e,r){return!!e.range.startsWith(cA)}supportsLocator(e,r){return!!e.reference.startsWith(cA)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=jq(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await cS(G.makeRange({protocol:cA,source:a,selector:a,params:{locator:G.stringifyLocator(n)}}),cA,s.fetchOptions),f=Nn.makeHash(`${out}`,c).slice(0,6);return[Gq(e,{parentLocator:n,path:a,generatorHash:f,protocol:cA})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var aut={fetchers:[uS],resolvers:[fS]},lut=aut;var Yq={};Vt(Yq,{FileFetcher:()=>gS,FileResolver:()=>dS,TarballFileFetcher:()=>mS,TarballFileResolver:()=>yS,default:()=>fut,fileUtils:()=>xm});Ge();Dt();var tw=/^(?:[a-zA-Z]:[\\/]|\.{0,2}\/)/,AS=/^[^?]*\.(?:tar\.gz|tgz)(?:::.*)?$/,es="file:";var xm={};Vt(xm,{fetchArchiveFromLocator:()=>hS,makeArchiveFromLocator:()=>cF,makeBufferFromLocator:()=>Wq,makeLocator:()=>rw,makeSpec:()=>xEe,parseSpec:()=>pS});Ge();Dt();function pS(t){let{params:e,selector:r}=G.parseRange(t),s=fe.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?G.parseLocator(e.locator):null,path:s}}function xEe({parentLocator:t,path:e,hash:r,protocol:s}){let a=t!==null?{locator:G.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return G.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function rw(t,{parentLocator:e,path:r,hash:s,protocol:a}){return G.makeLocator(t,xEe({parentLocator:e,path:r,hash:s,protocol:a}))}async function hS(t,e){let{parentLocator:r,path:s}=G.parseFileStyleRange(t.reference,{protocol:es}),a=J.isAbsolute(s)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await e.fetcher.fetch(r,e),n=a.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,a.localPath)}:a;a!==n&&a.releaseFs&&a.releaseFs();let c=n.packageFs,f=J.join(n.prefixPath,s);return await je.releaseAfterUseAsync(async()=>await c.readFilePromise(f),n.releaseFs)}async function cF(t,{protocol:e,fetchOptions:r,inMemory:s=!1}){let{parentLocator:a,path:n}=G.parseFileStyleRange(t.reference,{protocol:e}),c=J.isAbsolute(n)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(a,r),f=c.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,c.localPath)}:c;c!==f&&c.releaseFs&&c.releaseFs();let p=f.packageFs,h=J.join(f.prefixPath,n);return await je.releaseAfterUseAsync(async()=>await ps.makeArchiveFromDirectory(h,{baseFs:p,prefixPath:G.getIdentVendorPath(t),compressionLevel:r.project.configuration.get("compressionLevel"),inMemory:s}),f.releaseFs)}async function Wq(t,{protocol:e,fetchOptions:r}){return(await cF(t,{protocol:e,fetchOptions:r,inMemory:!0})).getBufferAndClose()}var gS=class{supports(e,r){return!!e.reference.startsWith(es)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:es});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){return cF(e,{protocol:es,fetchOptions:r})}};Ge();Ge();var cut=2,dS=class{supportsDescriptor(e,r){return e.range.match(tw)?!0:!!e.range.startsWith(es)}supportsLocator(e,r){return!!e.reference.startsWith(es)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=G.makeDescriptor(e,`${es}${e.range}`)),G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=pS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await Wq(G.makeLocator(e,G.makeRange({protocol:es,source:a,selector:a,params:{locator:G.stringifyLocator(n)}})),{protocol:es,fetchOptions:s.fetchOptions}),f=Nn.makeHash(`${cut}`,c).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:f,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};Ge();var mS=class{supports(e,r){return AS.test(e.reference)?!!e.reference.startsWith(es):!1}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromDisk(e,r){let s=await hS(e,r);return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();Ge();Ge();var yS=class{supportsDescriptor(e,r){return AS.test(e.range)?!!(e.range.startsWith(es)||tw.test(e.range)):!1}supportsLocator(e,r){return AS.test(e.reference)?!!e.reference.startsWith(es):!1}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=G.makeDescriptor(e,`${es}${e.range}`)),G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=pS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=rw(e,{parentLocator:n,path:a,hash:"",protocol:es}),f=await hS(c,s.fetchOptions),p=Nn.makeHash(f).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:p,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var uut={fetchers:[mS,gS],resolvers:[yS,dS]},fut=uut;var Kq={};Vt(Kq,{GithubFetcher:()=>ES,default:()=>put,githubUtils:()=>uF});Ge();Dt();var uF={};Vt(uF,{invalidGithubUrlMessage:()=>TEe,isGithubUrl:()=>Vq,parseGithubUrl:()=>Jq});var kEe=ut(Ie("querystring")),QEe=[/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+)\/tarball\/([^/#]+)(?:#(.*))?$/,/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+?)(?:\.git)?(?:#(.*))?$/];function Vq(t){return t?QEe.some(e=>!!t.match(e)):!1}function Jq(t){let e;for(let f of QEe)if(e=t.match(f),e)break;if(!e)throw new Error(TEe(t));let[,r,s,a,n="master"]=e,{commit:c}=kEe.default.parse(n);return n=c||n.replace(/[^:]*:/,""),{auth:r,username:s,reponame:a,treeish:n}}function TEe(t){return`Input cannot be parsed as a valid GitHub URL ('${t}').`}var ES=class{supports(e,r){return!!Vq(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from GitHub`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await nn.get(this.getLocatorUrl(e,r),{configuration:r.project.configuration});return await ce.mktempPromise(async a=>{let n=new Sn(a);await ps.extractArchiveTo(s,n,{stripComponents:1});let c=ka.splitRepoUrl(e.reference),f=J.join(a,"package.tgz");await In.prepareExternalProject(a,f,{configuration:r.project.configuration,report:r.report,workspace:c.extra.workspace,locator:e});let p=await ce.readFilePromise(f);return await ps.convertToZip(p,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})})}getLocatorUrl(e,r){let{auth:s,username:a,reponame:n,treeish:c}=Jq(e.reference);return`https://${s?`${s}@`:""}github.com/${a}/${n}/archive/${c}.tar.gz`}};var Aut={hooks:{async fetchHostedRepository(t,e,r){if(t!==null)return t;let s=new ES;if(!s.supports(e,r))return null;try{return await s.fetch(e,r)}catch{return null}}}},put=Aut;var zq={};Vt(zq,{TarballHttpFetcher:()=>CS,TarballHttpResolver:()=>wS,default:()=>gut});Ge();function IS(t){let e;try{e=new URL(t)}catch{return!1}return!(e.protocol!=="http:"&&e.protocol!=="https:"||!e.pathname.match(/(\.tar\.gz|\.tgz|\/[^.]+)$/))}var CS=class{supports(e,r){return IS(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await nn.get(e.reference,{configuration:r.project.configuration});return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();Ge();var wS=class{supportsDescriptor(e,r){return IS(e.range)}supportsLocator(e,r){return IS(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[G.convertDescriptorToLocator(e)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var hut={fetchers:[CS],resolvers:[wS]},gut=hut;var Xq={};Vt(Xq,{InitCommand:()=>z0,InitInitializerCommand:()=>nw,default:()=>mut});Yt();Ge();Ge();Dt();Yt();var z0=class extends ft{constructor(){super(...arguments);this.private=ge.Boolean("-p,--private",!1,{description:"Initialize a private package"});this.workspace=ge.Boolean("-w,--workspace",!1,{description:"Initialize a workspace root with a `packages/` directory"});this.install=ge.String("-i,--install",!1,{tolerateBoolean:!0,description:"Initialize a package with a specific bundle that will be locked in the project"});this.name=ge.String("-n,--name",{description:"Initialize a package with the given name"});this.usev2=ge.Boolean("-2",!1,{hidden:!0});this.yes=ge.Boolean("-y,--yes",{hidden:!0})}static{this.paths=[["init"]]}static{this.usage=ot.Usage({description:"create a new package",details:"\n This command will setup a new package in your local directory.\n\n If the `-p,--private` or `-w,--workspace` options are set, the package will be private by default.\n\n If the `-w,--workspace` option is set, the package will be configured to accept a set of workspaces in the `packages/` directory.\n\n If the `-i,--install` option is given a value, Yarn will first download it using `yarn set version` and only then forward the init call to the newly downloaded bundle. Without arguments, the downloaded bundle will be `latest`.\n\n The initial settings of the manifest can be changed by using the `initScope` and `initFields` configuration values. Additionally, Yarn will generate an EditorConfig file whose rules can be altered via `initEditorConfig`, and will initialize a Git repository in the current directory.\n ",examples:[["Create a new package in the local directory","yarn init"],["Create a new private package in the local directory","yarn init -p"],["Create a new package and store the Yarn release inside","yarn init -i=latest"],["Create a new private package and defines it as a workspace root","yarn init -w"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.install=="string"?this.install:this.usev2||this.install===!0?"latest":null;return s!==null?await this.executeProxy(r,s):await this.executeRegular(r)}async executeProxy(r,s){if(r.projectCwd!==null&&r.projectCwd!==this.context.cwd)throw new nt("Cannot use the --install flag from within a project subdirectory");ce.existsSync(this.context.cwd)||await ce.mkdirPromise(this.context.cwd,{recursive:!0});let a=J.join(this.context.cwd,Er.lockfile);ce.existsSync(a)||await ce.writeFilePromise(a,"");let n=await this.cli.run(["set","version",s],{quiet:!0});if(n!==0)return n;let c=[];return this.private&&c.push("-p"),this.workspace&&c.push("-w"),this.name&&c.push(`-n=${this.name}`),this.yes&&c.push("-y"),await ce.mktempPromise(async f=>{let{code:p}=await qr.pipevp("yarn",["init",...c],{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await In.makeScriptEnv({binFolder:f})});return p})}async initialize(){}async executeRegular(r){let s=null;try{s=(await Tt.find(r,this.context.cwd)).project}catch{s=null}ce.existsSync(this.context.cwd)||await ce.mkdirPromise(this.context.cwd,{recursive:!0});let a=await Ut.tryFind(this.context.cwd),n=a??new Ut,c=Object.fromEntries(r.get("initFields").entries());n.load(c),n.name=n.name??G.makeIdent(r.get("initScope"),this.name??J.basename(this.context.cwd)),n.packageManager=fn&&je.isTaggedYarnVersion(fn)?`yarn@${fn}`:null,(!a&&this.workspace||this.private)&&(n.private=!0),this.workspace&&n.workspaceDefinitions.length===0&&(await ce.mkdirPromise(J.join(this.context.cwd,"packages"),{recursive:!0}),n.workspaceDefinitions=[{pattern:"packages/*"}]);let f={};n.exportTo(f);let p=J.join(this.context.cwd,Ut.fileName);await ce.changeFilePromise(p,`${JSON.stringify(f,null,2)} +`,{automaticNewlines:!0});let h=[p],E=J.join(this.context.cwd,"README.md");if(ce.existsSync(E)||(await ce.writeFilePromise(E,`# ${G.stringifyIdent(n.name)} +`),h.push(E)),!s||s.cwd===this.context.cwd){let C=J.join(this.context.cwd,Er.lockfile);ce.existsSync(C)||(await ce.writeFilePromise(C,""),h.push(C));let P=[".yarn/*","!.yarn/patches","!.yarn/plugins","!.yarn/releases","!.yarn/sdks","!.yarn/versions","","# Whether you use PnP or not, the node_modules folder is often used to store","# build artifacts that should be gitignored","node_modules","","# Swap the comments on the following lines if you wish to use zero-installs","# In that case, don't forget to run `yarn config set enableGlobalCache false`!","# Documentation here: https://yarnpkg.com/features/caching#zero-installs","","#!.yarn/cache",".pnp.*"].map(ue=>`${ue} +`).join(""),I=J.join(this.context.cwd,".gitignore");ce.existsSync(I)||(await ce.writeFilePromise(I,P),h.push(I));let N=["/.yarn/** linguist-vendored","/.yarn/releases/* binary","/.yarn/plugins/**/* binary","/.pnp.* binary linguist-generated"].map(ue=>`${ue} +`).join(""),U=J.join(this.context.cwd,".gitattributes");ce.existsSync(U)||(await ce.writeFilePromise(U,N),h.push(U));let W={"*":{charset:"utf-8",endOfLine:"lf",indentSize:2,indentStyle:"space",insertFinalNewline:!0}};je.mergeIntoTarget(W,r.get("initEditorConfig"));let ee=`root = true +`;for(let[ue,le]of Object.entries(W)){ee+=` +[${ue}] +`;for(let[me,pe]of Object.entries(le)){let Be=me.replace(/[A-Z]/g,Ce=>`_${Ce.toLowerCase()}`);ee+=`${Be} = ${pe} +`}}let ie=J.join(this.context.cwd,".editorconfig");ce.existsSync(ie)||(await ce.writeFilePromise(ie,ee),h.push(ie)),await this.cli.run(["install"],{quiet:!0}),await this.initialize(),ce.existsSync(J.join(this.context.cwd,".git"))||(await qr.execvp("git",["init"],{cwd:this.context.cwd}),await qr.execvp("git",["add","--",...h],{cwd:this.context.cwd}),await qr.execvp("git",["commit","--allow-empty","-m","First commit"],{cwd:this.context.cwd}))}}};var nw=class extends z0{constructor(){super(...arguments);this.initializer=ge.String();this.argv=ge.Proxy()}static{this.paths=[["init"]]}async initialize(){this.context.stdout.write(` +`),await this.cli.run(["dlx",this.initializer,...this.argv],{quiet:!0})}};var dut={configuration:{initScope:{description:"Scope used when creating packages via the init command",type:"STRING",default:null},initFields:{description:"Additional fields to set when creating packages via the init command",type:"MAP",valueDefinition:{description:"",type:"ANY"}},initEditorConfig:{description:"Extra rules to define in the generator editorconfig",type:"MAP",valueDefinition:{description:"",type:"ANY"}}},commands:[z0,nw]},mut=dut;var JW={};Vt(JW,{SearchCommand:()=>Iw,UpgradeInteractiveCommand:()=>Cw,default:()=>Dgt});Ge();var FEe=ut(Ie("os"));function iw({stdout:t}){if(FEe.default.endianness()==="BE")throw new Error("Interactive commands cannot be used on big-endian systems because ink depends on yoga-layout-prebuilt which only supports little-endian architectures");if(!t.isTTY)throw new Error("Interactive commands can only be used inside a TTY environment")}Yt();var YIe=ut(g9()),d9={appId:"OFCNCOG2CU",apiKey:"6fe4476ee5a1832882e326b506d14126",indexName:"npm-search"},hAt=(0,YIe.default)(d9.appId,d9.apiKey).initIndex(d9.indexName),m9=async(t,e=0)=>await hAt.search(t,{analyticsTags:["yarn-plugin-interactive-tools"],attributesToRetrieve:["name","version","owner","repository","humanDownloadsLast30Days"],page:e,hitsPerPage:10});var CD=["regular","dev","peer"],Iw=class extends ft{static{this.paths=[["search"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the search interface",details:` + This command opens a fullscreen terminal interface where you can search for and install packages from the npm registry. + `,examples:[["Open the search window","yarn search"]]})}async execute(){iw(this.context);let{Gem:e}=await Promise.resolve().then(()=>(WF(),LW)),{ScrollableItems:r}=await Promise.resolve().then(()=>(KF(),JF)),{useKeypress:s}=await Promise.resolve().then(()=>(yD(),w2e)),{useMinistore:a}=await Promise.resolve().then(()=>(GW(),jW)),{renderForm:n}=await Promise.resolve().then(()=>($F(),ZF)),{default:c}=await Promise.resolve().then(()=>ut(T2e())),{Box:f,Text:p}=await Promise.resolve().then(()=>ut(Wc())),{default:h,useEffect:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),P=()=>h.createElement(f,{flexDirection:"row"},h.createElement(f,{flexDirection:"column",width:48},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move between packages.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select a package.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," again to change the target."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to install the selected packages.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),I=()=>h.createElement(h.Fragment,null,h.createElement(f,{width:15},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Owner")),h.createElement(f,{width:11},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Version")),h.createElement(f,{width:10},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Downloads"))),R=()=>h.createElement(f,{width:17},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Target")),N=({hit:pe,active:Be})=>{let[Ce,g]=a(pe.name,null);s({active:Be},(Ae,se)=>{if(se.name!=="space")return;if(!Ce){g(CD[0]);return}let Z=CD.indexOf(Ce)+1;Z===CD.length?g(null):g(CD[Z])},[Ce,g]);let we=G.parseIdent(pe.name),ye=G.prettyIdent(S,we);return h.createElement(f,null,h.createElement(f,{width:45},h.createElement(p,{bold:!0,wrap:"wrap"},ye)),h.createElement(f,{width:14,marginLeft:1},h.createElement(p,{bold:!0,wrap:"truncate"},pe.owner.name)),h.createElement(f,{width:10,marginLeft:1},h.createElement(p,{italic:!0,wrap:"truncate"},pe.version)),h.createElement(f,{width:16,marginLeft:1},h.createElement(p,null,pe.humanDownloadsLast30Days)))},U=({name:pe,active:Be})=>{let[Ce]=a(pe,null),g=G.parseIdent(pe);return h.createElement(f,null,h.createElement(f,{width:47},h.createElement(p,{bold:!0}," - ",G.prettyIdent(S,g))),CD.map(we=>h.createElement(f,{key:we,width:14,marginLeft:1},h.createElement(p,null," ",h.createElement(e,{active:Ce===we})," ",h.createElement(p,{bold:!0},we)))))},W=()=>h.createElement(f,{marginTop:1},h.createElement(p,null,"Powered by Algolia.")),ie=await n(({useSubmit:pe})=>{let Be=a();pe(Be);let Ce=Array.from(Be.keys()).filter(j=>Be.get(j)!==null),[g,we]=C(""),[ye,Ae]=C(0),[se,Z]=C([]),De=j=>{j.match(/\t| /)||we(j)},Re=async()=>{Ae(0);let j=await m9(g);j.query===g&&Z(j.hits)},mt=async()=>{let j=await m9(g,ye+1);j.query===g&&j.page-1===ye&&(Ae(j.page),Z([...se,...j.hits]))};return E(()=>{g?Re():Z([])},[g]),h.createElement(f,{flexDirection:"column"},h.createElement(P,null),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(p,{bold:!0},"Search: "),h.createElement(f,{width:41},h.createElement(c,{value:g,onChange:De,placeholder:"i.e. babel, webpack, react...",showCursor:!1})),h.createElement(I,null)),se.length?h.createElement(r,{radius:2,loop:!1,children:se.map(j=>h.createElement(N,{key:j.name,hit:j,active:!1})),willReachEnd:mt}):h.createElement(p,{color:"gray"},"Start typing..."),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(f,{width:49},h.createElement(p,{bold:!0},"Selected:")),h.createElement(R,null)),Ce.length?Ce.map(j=>h.createElement(U,{key:j,name:j,active:!1})):h.createElement(p,{color:"gray"},"No selected packages..."),h.createElement(W,null))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ie>"u")return 1;let ue=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="regular"),le=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="dev"),me=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="peer");return ue.length&&await this.cli.run(["add",...ue]),le.length&&await this.cli.run(["add","--dev",...le]),me&&await this.cli.run(["add","--peer",...me]),0}};Ge();Yt();YG();var U2e=ut(Ai()),M2e=/^((?:[\^~]|>=?)?)([0-9]+)(\.[0-9]+)(\.[0-9]+)((?:-\S+)?)$/;function _2e(t,e){return t.length>0?[t.slice(0,e)].concat(_2e(t.slice(e),e)):[]}var Cw=class extends ft{static{this.paths=[["upgrade-interactive"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the upgrade interface",details:` + This command opens a fullscreen terminal interface where you can see any out of date packages used by your application, their status compared to the latest versions available on the remote registry, and select packages to upgrade. + `,examples:[["Open the upgrade window","yarn upgrade-interactive"]]})}async execute(){iw(this.context);let{ItemOptions:e}=await Promise.resolve().then(()=>(L2e(),O2e)),{Pad:r}=await Promise.resolve().then(()=>(VW(),N2e)),{ScrollableItems:s}=await Promise.resolve().then(()=>(KF(),JF)),{useMinistore:a}=await Promise.resolve().then(()=>(GW(),jW)),{renderForm:n}=await Promise.resolve().then(()=>($F(),ZF)),{Box:c,Text:f}=await Promise.resolve().then(()=>ut(Wc())),{default:p,useEffect:h,useRef:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd),R=await Kr.find(S);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState({restoreResolutions:!1});let N=this.context.stdout.rows-7,U=(we,ye)=>{let Ae=mde(we,ye),se="";for(let Z of Ae)Z.added?se+=he.pretty(S,Z.value,"green"):Z.removed||(se+=Z.value);return se},W=(we,ye)=>{if(we===ye)return ye;let Ae=G.parseRange(we),se=G.parseRange(ye),Z=Ae.selector.match(M2e),De=se.selector.match(M2e);if(!Z||!De)return U(we,ye);let Re=["gray","red","yellow","green","magenta"],mt=null,j="";for(let rt=1;rt{let se=await Xu.fetchDescriptorFrom(we,Ae,{project:P,cache:R,preserveModifier:ye,workspace:I});return se!==null?se.range:we.range},ie=async we=>{let ye=U2e.default.valid(we.range)?`^${we.range}`:we.range,[Ae,se]=await Promise.all([ee(we,we.range,ye).catch(()=>null),ee(we,we.range,"latest").catch(()=>null)]),Z=[{value:null,label:we.range}];return Ae&&Ae!==we.range?Z.push({value:Ae,label:W(we.range,Ae)}):Z.push({value:null,label:""}),se&&se!==Ae&&se!==we.range?Z.push({value:se,label:W(we.range,se)}):Z.push({value:null,label:""}),Z},ue=()=>p.createElement(c,{flexDirection:"row"},p.createElement(c,{flexDirection:"column",width:49},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select packages.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select versions."))),p.createElement(c,{flexDirection:"column"},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to install.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to abort.")))),le=()=>p.createElement(c,{flexDirection:"row",paddingTop:1,paddingBottom:1},p.createElement(c,{width:50},p.createElement(f,{bold:!0},p.createElement(f,{color:"greenBright"},"?")," Pick the packages you want to upgrade.")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Current")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Range")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Latest"))),me=({active:we,descriptor:ye,suggestions:Ae})=>{let[se,Z]=a(ye.descriptorHash,null),De=G.stringifyIdent(ye),Re=Math.max(0,45-De.length);return p.createElement(p.Fragment,null,p.createElement(c,null,p.createElement(c,{width:45},p.createElement(f,{bold:!0},G.prettyIdent(S,ye)),p.createElement(r,{active:we,length:Re})),p.createElement(e,{active:we,options:Ae,value:se,skewer:!0,onChange:Z,sizes:[17,17,17]})))},pe=({dependencies:we})=>{let[ye,Ae]=C(we.map(()=>null)),se=E(!0),Z=async De=>{let Re=await ie(De);return Re.filter(mt=>mt.label!=="").length<=1?null:{descriptor:De,suggestions:Re}};return h(()=>()=>{se.current=!1},[]),h(()=>{let De=Math.trunc(N*1.75),Re=we.slice(0,De),mt=we.slice(De),j=_2e(mt,N),rt=Re.map(Z).reduce(async(Fe,Ne)=>{await Fe;let Pe=await Ne;Pe!==null&&se.current&&Ae(Ve=>{let ke=Ve.findIndex(Ue=>Ue===null),it=[...Ve];return it[ke]=Pe,it})},Promise.resolve());j.reduce((Fe,Ne)=>Promise.all(Ne.map(Pe=>Promise.resolve().then(()=>Z(Pe)))).then(async Pe=>{Pe=Pe.filter(Ve=>Ve!==null),await Fe,se.current&&Ae(Ve=>{let ke=Ve.findIndex(it=>it===null);return Ve.slice(0,ke).concat(Pe).concat(Ve.slice(ke+Pe.length))})}),rt).then(()=>{se.current&&Ae(Fe=>Fe.filter(Ne=>Ne!==null))})},[]),ye.length?p.createElement(s,{radius:N>>1,children:ye.map((De,Re)=>De!==null?p.createElement(me,{key:Re,active:!1,descriptor:De.descriptor,suggestions:De.suggestions}):p.createElement(f,{key:Re},"Loading..."))}):p.createElement(f,null,"No upgrades found")},Ce=await n(({useSubmit:we})=>{we(a());let ye=new Map;for(let se of P.workspaces)for(let Z of["dependencies","devDependencies"])for(let De of se.manifest[Z].values())P.tryWorkspaceByDescriptor(De)===null&&(De.range.startsWith("link:")||ye.set(De.descriptorHash,De));let Ae=je.sortMap(ye.values(),se=>G.stringifyDescriptor(se));return p.createElement(c,{flexDirection:"column"},p.createElement(ue,null),p.createElement(le,null),p.createElement(pe,{dependencies:Ae}))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof Ce>"u")return 1;let g=!1;for(let we of P.workspaces)for(let ye of["dependencies","devDependencies"]){let Ae=we.manifest[ye];for(let se of Ae.values()){let Z=Ce.get(se.descriptorHash);typeof Z<"u"&&Z!==null&&(Ae.set(se.identHash,G.makeDescriptor(se,Z)),g=!0)}}return g?await P.installWithNewReport({quiet:this.context.quiet,stdout:this.context.stdout},{cache:R}):0}};var Sgt={commands:[Iw,Cw]},Dgt=Sgt;var zW={};Vt(zW,{default:()=>kgt});Ge();var BD="jsr:";Ge();Ge();function ww(t){let e=t.range.slice(4);if(Fr.validRange(e))return G.makeDescriptor(t,`npm:${G.stringifyIdent(G.wrapIdentIntoScope(t,"jsr"))}@${e}`);let r=G.tryParseDescriptor(e,!0);if(r!==null)return G.makeDescriptor(t,`npm:${G.stringifyIdent(G.wrapIdentIntoScope(r,"jsr"))}@${r.range}`);throw new Error(`Invalid range: ${t.range}`)}function Bw(t){return G.makeLocator(G.wrapIdentIntoScope(t,"jsr"),`npm:${t.reference.slice(4)}`)}function KW(t){return G.makeLocator(G.unwrapIdentFromScope(t,"jsr"),`jsr:${t.reference.slice(4)}`)}var eN=class{supports(e,r){return e.reference.startsWith(BD)}getLocalPath(e,r){let s=Bw(e);return r.fetcher.getLocalPath(s,r)}fetch(e,r){let s=Bw(e);return r.fetcher.fetch(s,r)}};var tN=class{supportsDescriptor(e,r){return!!e.range.startsWith(BD)}supportsLocator(e,r){return!!e.reference.startsWith(BD)}shouldPersistResolution(e,r){let s=Bw(e);return r.resolver.shouldPersistResolution(s,r)}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{inner:ww(e)}}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(ww(e));return(await s.resolver.getCandidates(a,r,s)).map(c=>KW(c))}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(ww(e));return a.resolver.getSatisfying(n,r,s,a)}async resolve(e,r){let s=Bw(e),a=await r.resolver.resolve(s,r);return{...a,...KW(a)}}};var bgt=["dependencies","devDependencies","peerDependencies"];function Pgt(t,e){for(let r of bgt)for(let s of t.manifest.getForScope(r).values()){if(!s.range.startsWith("jsr:"))continue;let a=ww(s),n=r==="dependencies"?G.makeDescriptor(s,"unknown"):null,c=n!==null&&t.manifest.ensureDependencyMeta(n).optional?"optionalDependencies":r;e[c][G.stringifyIdent(s)]=a.range}}var xgt={hooks:{beforeWorkspacePacking:Pgt},resolvers:[tN],fetchers:[eN]},kgt=xgt;var XW={};Vt(XW,{LinkFetcher:()=>vD,LinkResolver:()=>SD,PortalFetcher:()=>DD,PortalResolver:()=>bD,default:()=>Tgt});Ge();Dt();var rh="portal:",nh="link:";var vD=class{supports(e,r){return!!e.reference.startsWith(nh)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:nh});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:nh}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0,localPath:p}:{packageFs:new Hf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0}}};Ge();Dt();var SD=class{supportsDescriptor(e,r){return!!e.range.startsWith(nh)}supportsLocator(e,r){return!!e.reference.startsWith(nh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(nh.length);return[G.makeLocator(e,`${nh}${fe.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){return{...e,version:"0.0.0",languageName:r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:null,dependencies:new Map,peerDependencies:new Map,dependenciesMeta:new Map,peerDependenciesMeta:new Map,bin:new Map}}};Ge();Dt();var DD=class{supports(e,r){return!!e.reference.startsWith(rh)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:rh});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:rh}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,localPath:p}:{packageFs:new Hf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot}}};Ge();Ge();Dt();var bD=class{supportsDescriptor(e,r){return!!e.range.startsWith(rh)}supportsLocator(e,r){return!!e.reference.startsWith(rh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(rh.length);return[G.makeLocator(e,`${rh}${fe.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var Qgt={fetchers:[vD,DD],resolvers:[SD,bD]},Tgt=Qgt;var FY={};Vt(FY,{NodeModulesLinker:()=>jD,NodeModulesMode:()=>kY,PnpLooseLinker:()=>GD,default:()=>Kdt});Dt();Ge();Dt();Dt();var $W=(t,e)=>`${t}@${e}`,H2e=(t,e)=>{let r=e.indexOf("#"),s=r>=0?e.substring(r+1):e;return $W(t,s)};var G2e=(t,e={})=>{let r=e.debugLevel||Number(process.env.NM_DEBUG_LEVEL||-1),s=e.check||r>=9,a=e.hoistingLimits||new Map,n={check:s,debugLevel:r,hoistingLimits:a,fastLookupPossible:!0},c;n.debugLevel>=0&&(c=Date.now());let f=Ugt(t,n),p=!1,h=0;do{let E=eY(f,[f],new Set([f.locator]),new Map,n);p=E.anotherRoundNeeded||E.isGraphChanged,n.fastLookupPossible=!1,h++}while(p);if(n.debugLevel>=0&&console.log(`hoist time: ${Date.now()-c}ms, rounds: ${h}`),n.debugLevel>=1){let E=PD(f);if(eY(f,[f],new Set([f.locator]),new Map,n).isGraphChanged)throw new Error(`The hoisting result is not terminal, prev tree: +${E}, next tree: +${PD(f)}`);let S=q2e(f);if(S)throw new Error(`${S}, after hoisting finished: +${PD(f)}`)}return n.debugLevel>=2&&console.log(PD(f)),_gt(f)},Rgt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=n=>{if(!s.has(n)){s.add(n);for(let c of n.hoistedDependencies.values())r.set(c.name,c);for(let c of n.dependencies.values())n.peerNames.has(c.name)||a(c)}};return a(e),r},Fgt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=new Set,n=(c,f)=>{if(s.has(c))return;s.add(c);for(let h of c.hoistedDependencies.values())if(!f.has(h.name)){let E;for(let C of t)E=C.dependencies.get(h.name),E&&r.set(E.name,E)}let p=new Set;for(let h of c.dependencies.values())p.add(h.name);for(let h of c.dependencies.values())c.peerNames.has(h.name)||n(h,p)};return n(e,a),r},j2e=(t,e)=>{if(e.decoupled)return e;let{name:r,references:s,ident:a,locator:n,dependencies:c,originalDependencies:f,hoistedDependencies:p,peerNames:h,reasons:E,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:I,hoistedTo:R}=e,N={name:r,references:new Set(s),ident:a,locator:n,dependencies:new Map(c),originalDependencies:new Map(f),hoistedDependencies:new Map(p),peerNames:new Set(h),reasons:new Map(E),decoupled:!0,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:new Map(I),hoistedTo:new Map(R)},U=N.dependencies.get(r);return U&&U.ident==N.ident&&N.dependencies.set(r,N),t.dependencies.set(N.name,N),N},Ngt=(t,e)=>{let r=new Map([[t.name,[t.ident]]]);for(let a of t.dependencies.values())t.peerNames.has(a.name)||r.set(a.name,[a.ident]);let s=Array.from(e.keys());s.sort((a,n)=>{let c=e.get(a),f=e.get(n);if(f.hoistPriority!==c.hoistPriority)return f.hoistPriority-c.hoistPriority;{let p=c.dependents.size+c.peerDependents.size;return f.dependents.size+f.peerDependents.size-p}});for(let a of s){let n=a.substring(0,a.indexOf("@",1)),c=a.substring(n.length+1);if(!t.peerNames.has(n)){let f=r.get(n);f||(f=[],r.set(n,f)),f.indexOf(c)<0&&f.push(c)}}return r},ZW=t=>{let e=new Set,r=(s,a=new Set)=>{if(!a.has(s)){a.add(s);for(let n of s.peerNames)if(!t.peerNames.has(n)){let c=t.dependencies.get(n);c&&!e.has(c)&&r(c,a)}e.add(s)}};for(let s of t.dependencies.values())t.peerNames.has(s.name)||r(s);return e},eY=(t,e,r,s,a,n=new Set)=>{let c=e[e.length-1];if(n.has(c))return{anotherRoundNeeded:!1,isGraphChanged:!1};n.add(c);let f=Hgt(c),p=Ngt(c,f),h=t==c?new Map:a.fastLookupPossible?Rgt(e):Fgt(e),E,C=!1,S=!1,P=new Map(Array.from(p.entries()).map(([R,N])=>[R,N[0]])),I=new Map;do{let R=Mgt(t,e,r,h,P,p,s,I,a);R.isGraphChanged&&(S=!0),R.anotherRoundNeeded&&(C=!0),E=!1;for(let[N,U]of p)U.length>1&&!c.dependencies.has(N)&&(P.delete(N),U.shift(),P.set(N,U[0]),E=!0)}while(E);for(let R of c.dependencies.values())if(!c.peerNames.has(R.name)&&!r.has(R.locator)){r.add(R.locator);let N=eY(t,[...e,R],r,I,a);N.isGraphChanged&&(S=!0),N.anotherRoundNeeded&&(C=!0),r.delete(R.locator)}return{anotherRoundNeeded:C,isGraphChanged:S}},Ogt=t=>{for(let[e,r]of t.dependencies)if(!t.peerNames.has(e)&&r.ident!==t.ident)return!0;return!1},Lgt=(t,e,r,s,a,n,c,f,{outputReason:p,fastLookupPossible:h})=>{let E,C=null,S=new Set;p&&(E=`${Array.from(e).map(N=>yo(N)).join("\u2192")}`);let P=r[r.length-1],R=!(s.ident===P.ident);if(p&&!R&&(C="- self-reference"),R&&(R=s.dependencyKind!==1,p&&!R&&(C="- workspace")),R&&s.dependencyKind===2&&(R=!Ogt(s),p&&!R&&(C="- external soft link with unhoisted dependencies")),R&&(R=!t.peerNames.has(s.name),p&&!R&&(C=`- cannot shadow peer: ${yo(t.originalDependencies.get(s.name).locator)} at ${E}`)),R){let N=!1,U=a.get(s.name);if(N=!U||U.ident===s.ident,p&&!N&&(C=`- filled by: ${yo(U.locator)} at ${E}`),N)for(let W=r.length-1;W>=1;W--){let ie=r[W].dependencies.get(s.name);if(ie&&ie.ident!==s.ident){N=!1;let ue=f.get(P);ue||(ue=new Set,f.set(P,ue)),ue.add(s.name),p&&(C=`- filled by ${yo(ie.locator)} at ${r.slice(0,W).map(le=>yo(le.locator)).join("\u2192")}`);break}}R=N}if(R&&(R=n.get(s.name)===s.ident,p&&!R&&(C=`- filled by: ${yo(c.get(s.name)[0])} at ${E}`)),R){let N=!0,U=new Set(s.peerNames);for(let W=r.length-1;W>=1;W--){let ee=r[W];for(let ie of U){if(ee.peerNames.has(ie)&&ee.originalDependencies.has(ie))continue;let ue=ee.dependencies.get(ie);ue&&t.dependencies.get(ie)!==ue&&(W===r.length-1?S.add(ue):(S=null,N=!1,p&&(C=`- peer dependency ${yo(ue.locator)} from parent ${yo(ee.locator)} was not hoisted to ${E}`))),U.delete(ie)}if(!N)break}R=N}if(R&&!h)for(let N of s.hoistedDependencies.values()){let U=a.get(N.name)||t.dependencies.get(N.name);if(!U||N.ident!==U.ident){R=!1,p&&(C=`- previously hoisted dependency mismatch, needed: ${yo(N.locator)}, available: ${yo(U?.locator)}`);break}}return S!==null&&S.size>0?{isHoistable:2,dependsOn:S,reason:C}:{isHoistable:R?0:1,reason:C}},rN=t=>`${t.name}@${t.locator}`,Mgt=(t,e,r,s,a,n,c,f,p)=>{let h=e[e.length-1],E=new Set,C=!1,S=!1,P=(U,W,ee,ie,ue)=>{if(E.has(ie))return;let le=[...W,rN(ie)],me=[...ee,rN(ie)],pe=new Map,Be=new Map;for(let Ae of ZW(ie)){let se=Lgt(h,r,[h,...U,ie],Ae,s,a,n,f,{outputReason:p.debugLevel>=2,fastLookupPossible:p.fastLookupPossible});if(Be.set(Ae,se),se.isHoistable===2)for(let Z of se.dependsOn){let De=pe.get(Z.name)||new Set;De.add(Ae.name),pe.set(Z.name,De)}}let Ce=new Set,g=(Ae,se,Z)=>{if(!Ce.has(Ae)){Ce.add(Ae),Be.set(Ae,{isHoistable:1,reason:Z});for(let De of pe.get(Ae.name)||[])g(ie.dependencies.get(De),se,p.debugLevel>=2?`- peer dependency ${yo(Ae.locator)} from parent ${yo(ie.locator)} was not hoisted`:"")}};for(let[Ae,se]of Be)se.isHoistable===1&&g(Ae,se,se.reason);let we=!1;for(let Ae of Be.keys())if(!Ce.has(Ae)){S=!0;let se=c.get(ie);se&&se.has(Ae.name)&&(C=!0),we=!0,ie.dependencies.delete(Ae.name),ie.hoistedDependencies.set(Ae.name,Ae),ie.reasons.delete(Ae.name);let Z=h.dependencies.get(Ae.name);if(p.debugLevel>=2){let De=Array.from(W).concat([ie.locator]).map(mt=>yo(mt)).join("\u2192"),Re=h.hoistedFrom.get(Ae.name);Re||(Re=[],h.hoistedFrom.set(Ae.name,Re)),Re.push(De),ie.hoistedTo.set(Ae.name,Array.from(e).map(mt=>yo(mt.locator)).join("\u2192"))}if(!Z)h.ident!==Ae.ident&&(h.dependencies.set(Ae.name,Ae),ue.add(Ae));else for(let De of Ae.references)Z.references.add(De)}if(ie.dependencyKind===2&&we&&(C=!0),p.check){let Ae=q2e(t);if(Ae)throw new Error(`${Ae}, after hoisting dependencies of ${[h,...U,ie].map(se=>yo(se.locator)).join("\u2192")}: +${PD(t)}`)}let ye=ZW(ie);for(let Ae of ye)if(Ce.has(Ae)){let se=Be.get(Ae);if((a.get(Ae.name)===Ae.ident||!ie.reasons.has(Ae.name))&&se.isHoistable!==0&&ie.reasons.set(Ae.name,se.reason),!Ae.isHoistBorder&&me.indexOf(rN(Ae))<0){E.add(ie);let De=j2e(ie,Ae);P([...U,ie],le,me,De,R),E.delete(ie)}}},I,R=new Set(ZW(h)),N=Array.from(e).map(U=>rN(U));do{I=R,R=new Set;for(let U of I){if(U.locator===h.locator||U.isHoistBorder)continue;let W=j2e(h,U);P([],Array.from(r),N,W,R)}}while(R.size>0);return{anotherRoundNeeded:C,isGraphChanged:S}},q2e=t=>{let e=[],r=new Set,s=new Set,a=(n,c,f)=>{if(r.has(n)||(r.add(n),s.has(n)))return;let p=new Map(c);for(let h of n.dependencies.values())n.peerNames.has(h.name)||p.set(h.name,h);for(let h of n.originalDependencies.values()){let E=p.get(h.name),C=()=>`${Array.from(s).concat([n]).map(S=>yo(S.locator)).join("\u2192")}`;if(n.peerNames.has(h.name)){let S=c.get(h.name);(S!==E||!S||S.ident!==h.ident)&&e.push(`${C()} - broken peer promise: expected ${h.ident} but found ${S&&S.ident}`)}else{let S=f.hoistedFrom.get(n.name),P=n.hoistedTo.get(h.name),I=`${S?` hoisted from ${S.join(", ")}`:""}`,R=`${P?` hoisted to ${P}`:""}`,N=`${C()}${I}`;E?E.ident!==h.ident&&e.push(`${N} - broken require promise for ${h.name}${R}: expected ${h.ident}, but found: ${E.ident}`):e.push(`${N} - broken require promise: no required dependency ${h.name}${R} found`)}}s.add(n);for(let h of n.dependencies.values())n.peerNames.has(h.name)||a(h,p,n);s.delete(n)};return a(t,t.dependencies,t),e.join(` +`)},Ugt=(t,e)=>{let{identName:r,name:s,reference:a,peerNames:n}=t,c={name:s,references:new Set([a]),locator:$W(r,a),ident:H2e(r,a),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(n),reasons:new Map,decoupled:!0,isHoistBorder:!0,hoistPriority:0,dependencyKind:1,hoistedFrom:new Map,hoistedTo:new Map},f=new Map([[t,c]]),p=(h,E)=>{let C=f.get(h),S=!!C;if(!C){let{name:P,identName:I,reference:R,peerNames:N,hoistPriority:U,dependencyKind:W}=h,ee=e.hoistingLimits.get(E.locator);C={name:P,references:new Set([R]),locator:$W(I,R),ident:H2e(I,R),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(N),reasons:new Map,decoupled:!0,isHoistBorder:ee?ee.has(P):!1,hoistPriority:U||0,dependencyKind:W||0,hoistedFrom:new Map,hoistedTo:new Map},f.set(h,C)}if(E.dependencies.set(h.name,C),E.originalDependencies.set(h.name,C),S){let P=new Set,I=R=>{if(!P.has(R)){P.add(R),R.decoupled=!1;for(let N of R.dependencies.values())R.peerNames.has(N.name)||I(N)}};I(C)}else for(let P of h.dependencies)p(P,C)};for(let h of t.dependencies)p(h,c);return c},tY=t=>t.substring(0,t.indexOf("@",1)),_gt=t=>{let e={name:t.name,identName:tY(t.locator),references:new Set(t.references),dependencies:new Set},r=new Set([t]),s=(a,n,c)=>{let f=r.has(a),p;if(n===a)p=c;else{let{name:h,references:E,locator:C}=a;p={name:h,identName:tY(C),references:E,dependencies:new Set}}if(c.dependencies.add(p),!f){r.add(a);for(let h of a.dependencies.values())a.peerNames.has(h.name)||s(h,a,p);r.delete(a)}};for(let a of t.dependencies.values())s(a,t,e);return e},Hgt=t=>{let e=new Map,r=new Set([t]),s=c=>`${c.name}@${c.ident}`,a=c=>{let f=s(c),p=e.get(f);return p||(p={dependents:new Set,peerDependents:new Set,hoistPriority:0},e.set(f,p)),p},n=(c,f)=>{let p=!!r.has(f);if(a(f).dependents.add(c.ident),!p){r.add(f);for(let E of f.dependencies.values()){let C=a(E);C.hoistPriority=Math.max(C.hoistPriority,E.hoistPriority),f.peerNames.has(E.name)?C.peerDependents.add(f.ident):n(f,E)}}};for(let c of t.dependencies.values())t.peerNames.has(c.name)||n(t,c);return e},yo=t=>{if(!t)return"none";let e=t.indexOf("@",1),r=t.substring(0,e);r.endsWith("$wsroot$")&&(r=`wh:${r.replace("$wsroot$","")}`);let s=t.substring(e+1);if(s==="workspace:.")return".";if(s){let a=(s.indexOf("#")>0?s.split("#")[1]:s).replace("npm:","");return s.startsWith("virtual")&&(r=`v:${r}`),a.startsWith("workspace")&&(r=`w:${r}`,a=""),`${r}${a?`@${a}`:""}`}else return`${r}`};var PD=t=>{let e=0,r=(a,n,c="")=>{if(e>5e4||n.has(a))return"";e++;let f=Array.from(a.dependencies.values()).sort((h,E)=>h.name===E.name?0:h.name>E.name?1:-1),p="";n.add(a);for(let h=0;h":"")+(S!==E.name?`a:${E.name}:`:"")+yo(E.locator)+(C?` ${C}`:"")} +`,p+=r(E,n,`${c}${h5e4?` +Tree is too large, part of the tree has been dunped +`:"")};var xD=(s=>(s.WORKSPACES="workspaces",s.DEPENDENCIES="dependencies",s.NONE="none",s))(xD||{}),W2e="node_modules",rg="$wsroot$";var kD=(t,e)=>{let{packageTree:r,hoistingLimits:s,errors:a,preserveSymlinksRequired:n}=Ggt(t,e),c=null;if(a.length===0){let f=G2e(r,{hoistingLimits:s});c=Wgt(t,f,e)}return{tree:c,errors:a,preserveSymlinksRequired:n}},pA=t=>`${t.name}@${t.reference}`,nY=t=>{let e=new Map;for(let[r,s]of t.entries())if(!s.dirList){let a=e.get(s.locator);a||(a={target:s.target,linkType:s.linkType,locations:[],aliases:s.aliases},e.set(s.locator,a)),a.locations.push(r)}for(let r of e.values())r.locations=r.locations.sort((s,a)=>{let n=s.split(J.delimiter).length,c=a.split(J.delimiter).length;return a===s?0:n!==c?c-n:a>s?1:-1});return e},Y2e=(t,e)=>{let r=G.isVirtualLocator(t)?G.devirtualizeLocator(t):t,s=G.isVirtualLocator(e)?G.devirtualizeLocator(e):e;return G.areLocatorsEqual(r,s)},rY=(t,e,r,s)=>{if(t.linkType!=="SOFT")return!1;let a=fe.toPortablePath(r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation);return J.contains(s,a)===null},jgt=t=>{let e=t.getPackageInformation(t.topLevel);if(e===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");if(t.findPackageLocator(e.packageLocation)===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let s=fe.toPortablePath(e.packageLocation.slice(0,-1)),a=new Map,n={children:new Map},c=t.getDependencyTreeRoots(),f=new Map,p=new Set,h=(S,P)=>{let I=pA(S);if(p.has(I))return;p.add(I);let R=t.getPackageInformation(S);if(R){let N=P?pA(P):"";if(pA(S)!==N&&R.linkType==="SOFT"&&!S.reference.startsWith("link:")&&!rY(R,S,t,s)){let U=V2e(R,S,t);(!f.get(U)||S.reference.startsWith("workspace:"))&&f.set(U,S)}for(let[U,W]of R.packageDependencies)W!==null&&(R.packagePeers.has(U)||h(t.getLocator(U,W),S))}};for(let S of c)h(S,null);let E=s.split(J.sep);for(let S of f.values()){let P=t.getPackageInformation(S),R=fe.toPortablePath(P.packageLocation.slice(0,-1)).split(J.sep).slice(E.length),N=n;for(let U of R){let W=N.children.get(U);W||(W={children:new Map},N.children.set(U,W)),N=W}N.workspaceLocator=S}let C=(S,P)=>{if(S.workspaceLocator){let I=pA(P),R=a.get(I);R||(R=new Set,a.set(I,R)),R.add(S.workspaceLocator)}for(let I of S.children.values())C(I,S.workspaceLocator||P)};for(let S of n.children.values())C(S,n.workspaceLocator);return a},Ggt=(t,e)=>{let r=[],s=!1,a=new Map,n=jgt(t),c=t.getPackageInformation(t.topLevel);if(c===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");let f=t.findPackageLocator(c.packageLocation);if(f===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let p=fe.toPortablePath(c.packageLocation.slice(0,-1)),h={name:f.name,identName:f.name,reference:f.reference,peerNames:c.packagePeers,dependencies:new Set,dependencyKind:1},E=new Map,C=(P,I)=>`${pA(I)}:${P}`,S=(P,I,R,N,U,W,ee,ie)=>{let ue=C(P,R),le=E.get(ue),me=!!le;!me&&R.name===f.name&&R.reference===f.reference&&(le=h,E.set(ue,h));let pe=rY(I,R,t,p);if(!le){let Ae=0;pe?Ae=2:I.linkType==="SOFT"&&R.name.endsWith(rg)&&(Ae=1),le={name:P,identName:R.name,reference:R.reference,dependencies:new Set,peerNames:Ae===1?new Set:I.packagePeers,dependencyKind:Ae},E.set(ue,le)}let Be;if(pe?Be=2:U.linkType==="SOFT"?Be=1:Be=0,le.hoistPriority=Math.max(le.hoistPriority||0,Be),ie&&!pe){let Ae=pA({name:N.identName,reference:N.reference}),se=a.get(Ae)||new Set;a.set(Ae,se),se.add(le.name)}let Ce=new Map(I.packageDependencies);if(e.project){let Ae=e.project.workspacesByCwd.get(fe.toPortablePath(I.packageLocation.slice(0,-1)));if(Ae){let se=new Set([...Array.from(Ae.manifest.peerDependencies.values(),Z=>G.stringifyIdent(Z)),...Array.from(Ae.manifest.peerDependenciesMeta.keys())]);for(let Z of se)Ce.has(Z)||(Ce.set(Z,W.get(Z)||null),le.peerNames.add(Z))}}let g=pA({name:R.name.replace(rg,""),reference:R.reference}),we=n.get(g);if(we)for(let Ae of we)Ce.set(`${Ae.name}${rg}`,Ae.reference);(I!==U||I.linkType!=="SOFT"||!pe&&(!e.selfReferencesByCwd||e.selfReferencesByCwd.get(ee)))&&N.dependencies.add(le);let ye=R!==f&&I.linkType==="SOFT"&&!R.name.endsWith(rg)&&!pe;if(!me&&!ye){let Ae=new Map;for(let[se,Z]of Ce)if(Z!==null){let De=t.getLocator(se,Z),Re=t.getLocator(se.replace(rg,""),Z),mt=t.getPackageInformation(Re);if(mt===null)throw new Error("Assertion failed: Expected the package to have been registered");let j=rY(mt,De,t,p);if(e.validateExternalSoftLinks&&e.project&&j){mt.packageDependencies.size>0&&(s=!0);for(let[Ve,ke]of mt.packageDependencies)if(ke!==null){let it=G.parseLocator(Array.isArray(ke)?`${ke[0]}@${ke[1]}`:`${Ve}@${ke}`);if(pA(it)!==pA(De)){let Ue=Ce.get(Ve);if(Ue){let x=G.parseLocator(Array.isArray(Ue)?`${Ue[0]}@${Ue[1]}`:`${Ve}@${Ue}`);Y2e(x,it)||r.push({messageName:71,text:`Cannot link ${G.prettyIdent(e.project.configuration,G.parseIdent(De.name))} into ${G.prettyLocator(e.project.configuration,G.parseLocator(`${R.name}@${R.reference}`))} dependency ${G.prettyLocator(e.project.configuration,it)} conflicts with parent dependency ${G.prettyLocator(e.project.configuration,x)}`})}else{let x=Ae.get(Ve);if(x){let w=x.target,b=G.parseLocator(Array.isArray(w)?`${w[0]}@${w[1]}`:`${Ve}@${w}`);Y2e(b,it)||r.push({messageName:71,text:`Cannot link ${G.prettyIdent(e.project.configuration,G.parseIdent(De.name))} into ${G.prettyLocator(e.project.configuration,G.parseLocator(`${R.name}@${R.reference}`))} dependency ${G.prettyLocator(e.project.configuration,it)} conflicts with dependency ${G.prettyLocator(e.project.configuration,b)} from sibling portal ${G.prettyIdent(e.project.configuration,G.parseIdent(x.portal.name))}`})}else Ae.set(Ve,{target:it.reference,portal:De})}}}}let rt=e.hoistingLimitsByCwd?.get(ee),Fe=j?ee:J.relative(p,fe.toPortablePath(mt.packageLocation))||vt.dot,Ne=e.hoistingLimitsByCwd?.get(Fe);S(se,mt,De,le,I,Ce,Fe,rt==="dependencies"||Ne==="dependencies"||Ne==="workspaces")}}};return S(f.name,c,f,h,c,c.packageDependencies,vt.dot,!1),{packageTree:h,hoistingLimits:a,errors:r,preserveSymlinksRequired:s}};function V2e(t,e,r){let s=r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation;return fe.toPortablePath(s||t.packageLocation)}function qgt(t,e,r){let s=e.getLocator(t.name.replace(rg,""),t.reference),a=e.getPackageInformation(s);if(a===null)throw new Error("Assertion failed: Expected the package to be registered");return r.pnpifyFs?{linkType:"SOFT",target:fe.toPortablePath(a.packageLocation)}:{linkType:a.linkType,target:V2e(a,t,e)}}var Wgt=(t,e,r)=>{let s=new Map,a=(E,C,S)=>{let{linkType:P,target:I}=qgt(E,t,r);return{locator:pA(E),nodePath:C,target:I,linkType:P,aliases:S}},n=E=>{let[C,S]=E.split("/");return S?{scope:C,name:S}:{scope:null,name:C}},c=new Set,f=(E,C,S)=>{if(c.has(E))return;c.add(E);let P=Array.from(E.references).sort().join("#");for(let I of E.dependencies){let R=Array.from(I.references).sort().join("#");if(I.identName===E.identName.replace(rg,"")&&R===P)continue;let N=Array.from(I.references).sort(),U={name:I.identName,reference:N[0]},{name:W,scope:ee}=n(I.name),ie=ee?[ee,W]:[W],ue=J.join(C,W2e),le=J.join(ue,...ie),me=`${S}/${U.name}`,pe=a(U,S,N.slice(1)),Be=!1;if(pe.linkType==="SOFT"&&r.project){let Ce=r.project.workspacesByCwd.get(pe.target.slice(0,-1));Be=!!(Ce&&!Ce.manifest.name)}if(!I.name.endsWith(rg)&&!Be){let Ce=s.get(le);if(Ce){if(Ce.dirList)throw new Error(`Assertion failed: ${le} cannot merge dir node with leaf node`);{let ye=G.parseLocator(Ce.locator),Ae=G.parseLocator(pe.locator);if(Ce.linkType!==pe.linkType)throw new Error(`Assertion failed: ${le} cannot merge nodes with different link types ${Ce.nodePath}/${G.stringifyLocator(ye)} and ${S}/${G.stringifyLocator(Ae)}`);if(ye.identHash!==Ae.identHash)throw new Error(`Assertion failed: ${le} cannot merge nodes with different idents ${Ce.nodePath}/${G.stringifyLocator(ye)} and ${S}/s${G.stringifyLocator(Ae)}`);pe.aliases=[...pe.aliases,...Ce.aliases,G.parseLocator(Ce.locator).reference]}}s.set(le,pe);let g=le.split("/"),we=g.indexOf(W2e);for(let ye=g.length-1;we>=0&&ye>we;ye--){let Ae=fe.toPortablePath(g.slice(0,ye).join(J.sep)),se=g[ye],Z=s.get(Ae);if(!Z)s.set(Ae,{dirList:new Set([se])});else if(Z.dirList){if(Z.dirList.has(se))break;Z.dirList.add(se)}}}f(I,pe.linkType==="SOFT"?pe.target:le,me)}},p=a({name:e.name,reference:Array.from(e.references)[0]},"",[]),h=p.target;return s.set(h,p),f(e,h,""),s};Ge();Ge();Dt();Dt();eA();wc();var wY={};Vt(wY,{PnpInstaller:()=>Gm,PnpLinker:()=>sg,UnplugCommand:()=>Sw,default:()=>Cdt,getPnpPath:()=>og,jsInstallUtils:()=>gA,pnpUtils:()=>HD,quotePathIfNeeded:()=>QBe});Dt();var kBe=Ie("url");Ge();Ge();Dt();Dt();var J2e={DEFAULT:{collapsed:!1,next:{"*":"DEFAULT"}},TOP_LEVEL:{collapsed:!1,next:{fallbackExclusionList:"FALLBACK_EXCLUSION_LIST",packageRegistryData:"PACKAGE_REGISTRY_DATA","*":"DEFAULT"}},FALLBACK_EXCLUSION_LIST:{collapsed:!1,next:{"*":"FALLBACK_EXCLUSION_ENTRIES"}},FALLBACK_EXCLUSION_ENTRIES:{collapsed:!0,next:{"*":"FALLBACK_EXCLUSION_DATA"}},FALLBACK_EXCLUSION_DATA:{collapsed:!0,next:{"*":"DEFAULT"}},PACKAGE_REGISTRY_DATA:{collapsed:!1,next:{"*":"PACKAGE_REGISTRY_ENTRIES"}},PACKAGE_REGISTRY_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_STORE_DATA"}},PACKAGE_STORE_DATA:{collapsed:!1,next:{"*":"PACKAGE_STORE_ENTRIES"}},PACKAGE_STORE_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_INFORMATION_DATA"}},PACKAGE_INFORMATION_DATA:{collapsed:!1,next:{packageDependencies:"PACKAGE_DEPENDENCIES","*":"DEFAULT"}},PACKAGE_DEPENDENCIES:{collapsed:!1,next:{"*":"PACKAGE_DEPENDENCY"}},PACKAGE_DEPENDENCY:{collapsed:!0,next:{"*":"DEFAULT"}}};function Ygt(t,e,r){let s="";s+="[";for(let a=0,n=t.length;a"u"||(f!==0&&(a+=", "),a+=JSON.stringify(p),a+=": ",a+=nN(p,h,e,r).replace(/^ +/g,""),f+=1)}return a+="}",a}function Kgt(t,e,r){let s=Object.keys(t),a=`${r} `,n="";n+=r,n+=`{ +`;let c=0;for(let f=0,p=s.length;f"u"||(c!==0&&(n+=",",n+=` +`),n+=a,n+=JSON.stringify(h),n+=": ",n+=nN(h,E,e,a).replace(/^ +/g,""),c+=1)}return c!==0&&(n+=` +`),n+=r,n+="}",n}function nN(t,e,r,s){let{next:a}=J2e[r],n=a[t]||a["*"];return K2e(e,n,s)}function K2e(t,e,r){let{collapsed:s}=J2e[e];return Array.isArray(t)?s?Ygt(t,e,r):Vgt(t,e,r):typeof t=="object"&&t!==null?s?Jgt(t,e,r):Kgt(t,e,r):JSON.stringify(t)}function z2e(t){return K2e(t,"TOP_LEVEL","")}function QD(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function zgt(t){let e=new Map,r=QD(t.fallbackExclusionList||[],[({name:s,reference:a})=>s,({name:s,reference:a})=>a]);for(let{name:s,reference:a}of r){let n=e.get(s);typeof n>"u"&&e.set(s,n=new Set),n.add(a)}return Array.from(e).map(([s,a])=>[s,Array.from(a)])}function Xgt(t){return QD(t.fallbackPool||[],([e])=>e)}function Zgt(t){let e=[],r=t.dependencyTreeRoots.find(s=>t.packageRegistry.get(s.name)?.get(s.reference)?.packageLocation==="./");for(let[s,a]of QD(t.packageRegistry,([n])=>n===null?"0":`1${n}`)){if(s===null)continue;let n=[];e.push([s,n]);for(let[c,{packageLocation:f,packageDependencies:p,packagePeers:h,linkType:E,discardFromLookup:C}]of QD(a,([S])=>S===null?"0":`1${S}`)){if(c===null)continue;let S=[];s!==null&&c!==null&&!p.has(s)&&S.push([s,c]);for(let[U,W]of p)S.push([U,W]);let P=QD(S,([U])=>U),I=h&&h.size>0?Array.from(h):void 0,N={packageLocation:f,packageDependencies:P,packagePeers:I,linkType:E,discardFromLookup:C||void 0};n.push([c,N]),r&&s===r.name&&c===r.reference&&e.unshift([null,[[null,N]]])}}return e}function TD(t){return{__info:["This file is automatically generated. Do not touch it, or risk","your modifications being lost."],dependencyTreeRoots:t.dependencyTreeRoots,enableTopLevelFallback:t.enableTopLevelFallback||!1,ignorePatternData:t.ignorePattern||null,pnpZipBackend:t.pnpZipBackend,fallbackExclusionList:zgt(t),fallbackPool:Xgt(t),packageRegistryData:Zgt(t)}}var $2e=ut(Z2e());function eBe(t,e){return[t?`${t} +`:"",`/* eslint-disable */ +`,`// @ts-nocheck +`,`"use strict"; +`,` +`,e,` +`,(0,$2e.default)()].join("")}function $gt(t){return JSON.stringify(t,null,2)}function edt(t){return`'${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,`\\ +`)}'`}function tdt(t){return[`const RAW_RUNTIME_STATE = +`,`${edt(z2e(t))}; + +`,`function $$SETUP_STATE(hydrateRuntimeState, basePath) { +`,` return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE), {basePath: basePath || __dirname}); +`,`} +`].join("")}function rdt(){return[`function $$SETUP_STATE(hydrateRuntimeState, basePath) { +`,` const fs = require('fs'); +`,` const path = require('path'); +`,` const pnpDataFilepath = path.resolve(__dirname, ${JSON.stringify(Er.pnpData)}); +`,` return hydrateRuntimeState(JSON.parse(fs.readFileSync(pnpDataFilepath, 'utf8')), {basePath: basePath || __dirname}); +`,`} +`].join("")}function tBe(t){let e=TD(t),r=tdt(e);return eBe(t.shebang,r)}function rBe(t){let e=TD(t),r=rdt(),s=eBe(t.shebang,r);return{dataFile:$gt(e),loaderFile:s}}Dt();function sY(t,{basePath:e}){let r=fe.toPortablePath(e),s=J.resolve(r),a=t.ignorePatternData!==null?new RegExp(t.ignorePatternData):null,n=new Map,c=new Map(t.packageRegistryData.map(([C,S])=>[C,new Map(S.map(([P,I])=>{if(C===null!=(P===null))throw new Error("Assertion failed: The name and reference should be null, or neither should");let R=I.discardFromLookup??!1,N={name:C,reference:P},U=n.get(I.packageLocation);U?(U.discardFromLookup=U.discardFromLookup&&R,R||(U.locator=N)):n.set(I.packageLocation,{locator:N,discardFromLookup:R});let W=null;return[P,{packageDependencies:new Map(I.packageDependencies),packagePeers:new Set(I.packagePeers),linkType:I.linkType,discardFromLookup:R,get packageLocation(){return W||(W=J.join(s,I.packageLocation))}}]}))])),f=new Map(t.fallbackExclusionList.map(([C,S])=>[C,new Set(S)])),p=new Map(t.fallbackPool),h=t.dependencyTreeRoots,E=t.enableTopLevelFallback;return{basePath:r,dependencyTreeRoots:h,enableTopLevelFallback:E,fallbackExclusionList:f,pnpZipBackend:t.pnpZipBackend,fallbackPool:p,ignorePattern:a,packageLocatorsByLocations:n,packageRegistry:c}}Dt();Dt();var sh=Ie("module"),jm=Ie("url"),gY=Ie("util");var ta=Ie("url");var oBe=ut(Ie("assert"));var oY=Array.isArray,RD=JSON.stringify,FD=Object.getOwnPropertyNames,Hm=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),aY=(t,e)=>RegExp.prototype.exec.call(t,e),lY=(t,...e)=>RegExp.prototype[Symbol.replace].apply(t,e),ng=(t,...e)=>String.prototype.endsWith.apply(t,e),cY=(t,...e)=>String.prototype.includes.apply(t,e),uY=(t,...e)=>String.prototype.lastIndexOf.apply(t,e),ND=(t,...e)=>String.prototype.indexOf.apply(t,e),nBe=(t,...e)=>String.prototype.replace.apply(t,e),ig=(t,...e)=>String.prototype.slice.apply(t,e),hA=(t,...e)=>String.prototype.startsWith.apply(t,e),iBe=Map,sBe=JSON.parse;function OD(t,e,r){return class extends r{constructor(...s){super(e(...s)),this.code=t,this.name=`${r.name} [${t}]`}}}var aBe=OD("ERR_PACKAGE_IMPORT_NOT_DEFINED",(t,e,r)=>`Package import specifier "${t}" is not defined${e?` in package ${e}package.json`:""} imported from ${r}`,TypeError),fY=OD("ERR_INVALID_MODULE_SPECIFIER",(t,e,r=void 0)=>`Invalid module "${t}" ${e}${r?` imported from ${r}`:""}`,TypeError),lBe=OD("ERR_INVALID_PACKAGE_TARGET",(t,e,r,s=!1,a=void 0)=>{let n=typeof r=="string"&&!s&&r.length&&!hA(r,"./");return e==="."?((0,oBe.default)(s===!1),`Invalid "exports" main target ${RD(r)} defined in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`):`Invalid "${s?"imports":"exports"}" target ${RD(r)} defined for '${e}' in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`},Error),LD=OD("ERR_INVALID_PACKAGE_CONFIG",(t,e,r)=>`Invalid package config ${t}${e?` while importing ${e}`:""}${r?`. ${r}`:""}`,Error),cBe=OD("ERR_PACKAGE_PATH_NOT_EXPORTED",(t,e,r=void 0)=>e==="."?`No "exports" main defined in ${t}package.json${r?` imported from ${r}`:""}`:`Package subpath '${e}' is not defined by "exports" in ${t}package.json${r?` imported from ${r}`:""}`,Error);var sN=Ie("url");function uBe(t,e){let r=Object.create(null);for(let s=0;se):t+e}MD(r,t,s,c,a)}aY(ABe,ig(t,2))!==null&&MD(r,t,s,c,a);let p=new URL(t,s),h=p.pathname,E=new URL(".",s).pathname;if(hA(h,E)||MD(r,t,s,c,a),e==="")return p;if(aY(ABe,e)!==null){let C=n?nBe(r,"*",()=>e):r+e;sdt(C,s,c,a)}return n?new URL(lY(pBe,p.href,()=>e)):new URL(e,p)}function adt(t){let e=+t;return`${e}`!==t?!1:e>=0&&e<4294967295}function vw(t,e,r,s,a,n,c,f){if(typeof e=="string")return odt(e,r,s,t,a,n,c,f);if(oY(e)){if(e.length===0)return null;let p;for(let h=0;hn?-1:n>a||r===-1?1:s===-1||t.length>e.length?-1:e.length>t.length?1:0}function ldt(t,e,r){if(typeof t=="string"||oY(t))return!0;if(typeof t!="object"||t===null)return!1;let s=FD(t),a=!1,n=0;for(let c=0;c=h.length&&ng(e,C)&&gBe(n,h)===1&&uY(h,"*")===E&&(n=h,c=ig(e,E,e.length-C.length))}}if(n){let p=r[n],h=vw(t,p,c,n,s,!0,!1,a);return h==null&&AY(e,t,s),h}AY(e,t,s)}function mBe({name:t,base:e,conditions:r,readFileSyncFn:s}){if(t==="#"||hA(t,"#/")||ng(t,"/")){let c="is not a valid internal imports specifier name";throw new fY(t,c,(0,ta.fileURLToPath)(e))}let a,n=fBe(e,s);if(n.exists){a=(0,ta.pathToFileURL)(n.pjsonPath);let c=n.imports;if(c)if(Hm(c,t)&&!cY(t,"*")){let f=vw(a,c[t],"",t,e,!1,!0,r);if(f!=null)return f}else{let f="",p,h=FD(c);for(let E=0;E=C.length&&ng(t,P)&&gBe(f,C)===1&&uY(C,"*")===S&&(f=C,p=ig(t,S,t.length-P.length))}}if(f){let E=c[f],C=vw(a,E,p,f,e,!0,!0,r);if(C!=null)return C}}}idt(t,a,e)}Dt();var udt=new Set(["BUILTIN_NODE_RESOLUTION_FAILED","MISSING_DEPENDENCY","MISSING_PEER_DEPENDENCY","QUALIFIED_PATH_RESOLUTION_FAILED","UNDECLARED_DEPENDENCY"]);function gs(t,e,r={},s){s??=udt.has(t)?"MODULE_NOT_FOUND":t;let a={configurable:!0,writable:!0,enumerable:!1};return Object.defineProperties(new Error(e),{code:{...a,value:s},pnpCode:{...a,value:t},data:{...a,value:r}})}function lf(t){return fe.normalize(fe.fromPortablePath(t))}var CBe=ut(EBe());function wBe(t){return fdt(),hY[t]}var hY;function fdt(){hY||(hY={"--conditions":[],...IBe(Adt()),...IBe(process.execArgv)})}function IBe(t){return(0,CBe.default)({"--conditions":[String],"-C":"--conditions"},{argv:t,permissive:!0})}function Adt(){let t=[],e=pdt(process.env.NODE_OPTIONS||"",t);return t.length,e}function pdt(t,e){let r=[],s=!1,a=!0;for(let n=0;nparseInt(t,10)),BBe=ml>19||ml===19&&ih>=2||ml===18&&ih>=13,UZt=ml===20&&ih<6||ml===19&&ih>=3,_Zt=ml>19||ml===19&&ih>=6,HZt=ml>=21||ml===20&&ih>=10||ml===18&&ih>=19,jZt=ml>=21||ml===20&&ih>=10||ml===18&&ih>=20,GZt=ml>=22;function vBe(t){if(process.env.WATCH_REPORT_DEPENDENCIES&&process.send)if(t=t.map(e=>fe.fromPortablePath(uo.resolveVirtual(fe.toPortablePath(e)))),BBe)process.send({"watch:require":t});else for(let e of t)process.send({"watch:require":e})}function dY(t,e){let r=Number(process.env.PNP_ALWAYS_WARN_ON_FALLBACK)>0,s=Number(process.env.PNP_DEBUG_LEVEL),a=/^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/,n=/^(\/|\.{1,2}(\/|$))/,c=/\/$/,f=/^\.{0,2}\//,p={name:null,reference:null},h=[],E=new Set;if(t.enableTopLevelFallback===!0&&h.push(p),e.compatibilityMode!==!1)for(let Fe of["react-scripts","gatsby"]){let Ne=t.packageRegistry.get(Fe);if(Ne)for(let Pe of Ne.keys()){if(Pe===null)throw new Error("Assertion failed: This reference shouldn't be null");h.push({name:Fe,reference:Pe})}}let{ignorePattern:C,packageRegistry:S,packageLocatorsByLocations:P}=t;function I(Fe,Ne){return{fn:Fe,args:Ne,error:null,result:null}}function R(Fe){let Ne=process.stderr?.hasColors?.()??process.stdout.isTTY,Pe=(it,Ue)=>`\x1B[${it}m${Ue}\x1B[0m`,Ve=Fe.error;console.error(Ve?Pe("31;1",`\u2716 ${Fe.error?.message.replace(/\n.*/s,"")}`):Pe("33;1","\u203C Resolution")),Fe.args.length>0&&console.error();for(let it of Fe.args)console.error(` ${Pe("37;1","In \u2190")} ${(0,gY.inspect)(it,{colors:Ne,compact:!0})}`);Fe.result&&(console.error(),console.error(` ${Pe("37;1","Out \u2192")} ${(0,gY.inspect)(Fe.result,{colors:Ne,compact:!0})}`));let ke=new Error().stack.match(/(?<=^ +)at.*/gm)?.slice(2)??[];if(ke.length>0){console.error();for(let it of ke)console.error(` ${Pe("38;5;244",it)}`)}console.error()}function N(Fe,Ne){if(e.allowDebug===!1)return Ne;if(Number.isFinite(s)){if(s>=2)return(...Pe)=>{let Ve=I(Fe,Pe);try{return Ve.result=Ne(...Pe)}catch(ke){throw Ve.error=ke}finally{R(Ve)}};if(s>=1)return(...Pe)=>{try{return Ne(...Pe)}catch(Ve){let ke=I(Fe,Pe);throw ke.error=Ve,R(ke),Ve}}}return Ne}function U(Fe){let Ne=g(Fe);if(!Ne)throw gs("INTERNAL","Couldn't find a matching entry in the dependency tree for the specified parent (this is probably an internal error)");return Ne}function W(Fe){if(Fe.name===null)return!0;for(let Ne of t.dependencyTreeRoots)if(Ne.name===Fe.name&&Ne.reference===Fe.reference)return!0;return!1}let ee=new Set(["node","require",...wBe("--conditions")]);function ie(Fe,Ne=ee,Pe){let Ve=Ae(J.join(Fe,"internal.js"),{resolveIgnored:!0,includeDiscardFromLookup:!0});if(Ve===null)throw gs("INTERNAL",`The locator that owns the "${Fe}" path can't be found inside the dependency tree (this is probably an internal error)`);let{packageLocation:ke}=U(Ve),it=J.join(ke,Er.manifest);if(!e.fakeFs.existsSync(it))return null;let Ue=JSON.parse(e.fakeFs.readFileSync(it,"utf8"));if(Ue.exports==null)return null;let x=J.contains(ke,Fe);if(x===null)throw gs("INTERNAL","unqualifiedPath doesn't contain the packageLocation (this is probably an internal error)");x!=="."&&!f.test(x)&&(x=`./${x}`);try{let w=dBe({packageJSONUrl:(0,jm.pathToFileURL)(fe.fromPortablePath(it)),packageSubpath:x,exports:Ue.exports,base:Pe?(0,jm.pathToFileURL)(fe.fromPortablePath(Pe)):null,conditions:Ne});return fe.toPortablePath((0,jm.fileURLToPath)(w))}catch(w){throw gs("EXPORTS_RESOLUTION_FAILED",w.message,{unqualifiedPath:lf(Fe),locator:Ve,pkgJson:Ue,subpath:lf(x),conditions:Ne},w.code)}}function ue(Fe,Ne,{extensions:Pe}){let Ve;try{Ne.push(Fe),Ve=e.fakeFs.statSync(Fe)}catch{}if(Ve&&!Ve.isDirectory())return e.fakeFs.realpathSync(Fe);if(Ve&&Ve.isDirectory()){let ke;try{ke=JSON.parse(e.fakeFs.readFileSync(J.join(Fe,Er.manifest),"utf8"))}catch{}let it;if(ke&&ke.main&&(it=J.resolve(Fe,ke.main)),it&&it!==Fe){let Ue=ue(it,Ne,{extensions:Pe});if(Ue!==null)return Ue}}for(let ke=0,it=Pe.length;ke{let x=JSON.stringify(Ue.name);if(Ve.has(x))return;Ve.add(x);let w=we(Ue);for(let b of w)if(U(b).packagePeers.has(Fe))ke(b);else{let F=Pe.get(b.name);typeof F>"u"&&Pe.set(b.name,F=new Set),F.add(b.reference)}};ke(Ne);let it=[];for(let Ue of[...Pe.keys()].sort())for(let x of[...Pe.get(Ue)].sort())it.push({name:Ue,reference:x});return it}function Ae(Fe,{resolveIgnored:Ne=!1,includeDiscardFromLookup:Pe=!1}={}){if(pe(Fe)&&!Ne)return null;let Ve=J.relative(t.basePath,Fe);Ve.match(n)||(Ve=`./${Ve}`),Ve.endsWith("/")||(Ve=`${Ve}/`);do{let ke=P.get(Ve);if(typeof ke>"u"||ke.discardFromLookup&&!Pe){Ve=Ve.substring(0,Ve.lastIndexOf("/",Ve.length-2)+1);continue}return ke.locator}while(Ve!=="");return null}function se(Fe){try{return e.fakeFs.readFileSync(fe.toPortablePath(Fe),"utf8")}catch(Ne){if(Ne.code==="ENOENT")return;throw Ne}}function Z(Fe,Ne,{considerBuiltins:Pe=!0}={}){if(Fe.startsWith("#"))throw new Error("resolveToUnqualified can not handle private import mappings");if(Fe==="pnpapi")return fe.toPortablePath(e.pnpapiResolution);if(Pe&&(0,sh.isBuiltin)(Fe))return null;let Ve=lf(Fe),ke=Ne&&lf(Ne);if(Ne&&pe(Ne)&&(!J.isAbsolute(Fe)||Ae(Fe)===null)){let x=me(Fe,Ne);if(x===!1)throw gs("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer was explicitely ignored by the regexp) + +Require request: "${Ve}" +Required by: ${ke} +`,{request:Ve,issuer:ke});return fe.toPortablePath(x)}let it,Ue=Fe.match(a);if(Ue){if(!Ne)throw gs("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ve,issuer:ke});let[,x,w]=Ue,b=Ae(Ne);if(!b){let Te=me(Fe,Ne);if(Te===!1)throw gs("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer doesn't seem to be part of the Yarn-managed dependency tree). + +Require path: "${Ve}" +Required by: ${ke} +`,{request:Ve,issuer:ke});return fe.toPortablePath(Te)}let F=U(b).packageDependencies.get(x),z=null;if(F==null&&b.name!==null){let Te=t.fallbackExclusionList.get(b.name);if(!Te||!Te.has(b.reference)){for(let Ct=0,qt=h.length;CtW(lt))?X=gs("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} +`).join("")} +`,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te}):X=gs("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) + +${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} +`).join("")} +`,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te})}else F===void 0&&(!Pe&&(0,sh.isBuiltin)(Fe)?W(b)?X=gs("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in your dependencies, this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${ke} +`,{request:Ve,issuer:ke,dependencyName:x}):X=gs("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in ${b.name}'s dependencies, this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${ke} +`,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}):W(b)?X=gs("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${ke} +`,{request:Ve,issuer:ke,dependencyName:x}):X=gs("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +`,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}));if(F==null){if(z===null||X===null)throw X||new Error("Assertion failed: Expected an error to have been set");F=z;let Te=X.message.replace(/\n.*/g,"");X.message=Te,!E.has(Te)&&s!==0&&(E.add(Te),process.emitWarning(X))}let $=Array.isArray(F)?{name:F[0],reference:F[1]}:{name:x,reference:F},oe=U($);if(!oe.packageLocation)throw gs("MISSING_DEPENDENCY",`A dependency seems valid but didn't get installed for some reason. This might be caused by a partial install, such as dev vs prod. + +Required package: ${$.name}@${$.reference}${$.name!==Ve?` (via "${Ve}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +`,{request:Ve,issuer:ke,dependencyLocator:Object.assign({},$)});let xe=oe.packageLocation;w?it=J.join(xe,w):it=xe}else if(J.isAbsolute(Fe))it=J.normalize(Fe);else{if(!Ne)throw gs("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ve,issuer:ke});let x=J.resolve(Ne);Ne.match(c)?it=J.normalize(J.join(x,Fe)):it=J.normalize(J.join(J.dirname(x),Fe))}return J.normalize(it)}function De(Fe,Ne,Pe=ee,Ve){if(n.test(Fe))return Ne;let ke=ie(Ne,Pe,Ve);return ke?J.normalize(ke):Ne}function Re(Fe,{extensions:Ne=Object.keys(sh.Module._extensions)}={}){let Pe=[],Ve=ue(Fe,Pe,{extensions:Ne});if(Ve)return J.normalize(Ve);{vBe(Pe.map(Ue=>fe.fromPortablePath(Ue)));let ke=lf(Fe),it=Ae(Fe);if(it){let{packageLocation:Ue}=U(it),x=!0;try{e.fakeFs.accessSync(Ue)}catch(w){if(w?.code==="ENOENT")x=!1;else{let b=(w?.message??w??"empty exception thrown").replace(/^[A-Z]/,y=>y.toLowerCase());throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`Required package exists but could not be accessed (${b}). + +Missing package: ${it.name}@${it.reference} +Expected package location: ${lf(Ue)} +`,{unqualifiedPath:ke,extensions:Ne})}}if(!x){let w=Ue.includes("/unplugged/")?"Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).":"Required package missing from disk. If you keep your packages inside your repository then restarting the Node process may be enough. Otherwise, try to run an install first.";throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`${w} + +Missing package: ${it.name}@${it.reference} +Expected package location: ${lf(Ue)} +`,{unqualifiedPath:ke,extensions:Ne})}}throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`Qualified path resolution failed: we looked for the following paths, but none could be accessed. + +Source path: ${ke} +${Pe.map(Ue=>`Not found: ${lf(Ue)} +`).join("")}`,{unqualifiedPath:ke,extensions:Ne})}}function mt(Fe,Ne,Pe){if(!Ne)throw new Error("Assertion failed: An issuer is required to resolve private import mappings");let Ve=mBe({name:Fe,base:(0,jm.pathToFileURL)(fe.fromPortablePath(Ne)),conditions:Pe.conditions??ee,readFileSyncFn:se});if(Ve instanceof URL)return Re(fe.toPortablePath((0,jm.fileURLToPath)(Ve)),{extensions:Pe.extensions});if(Ve.startsWith("#"))throw new Error("Mapping from one private import to another isn't allowed");return j(Ve,Ne,Pe)}function j(Fe,Ne,Pe={}){try{if(Fe.startsWith("#"))return mt(Fe,Ne,Pe);let{considerBuiltins:Ve,extensions:ke,conditions:it}=Pe,Ue=Z(Fe,Ne,{considerBuiltins:Ve});if(Fe==="pnpapi")return Ue;if(Ue===null)return null;let x=()=>Ne!==null?pe(Ne):!1,w=(!Ve||!(0,sh.isBuiltin)(Fe))&&!x()?De(Fe,Ue,it,Ne):Ue;return Re(w,{extensions:ke})}catch(Ve){throw Object.hasOwn(Ve,"pnpCode")&&Object.assign(Ve.data,{request:lf(Fe),issuer:Ne&&lf(Ne)}),Ve}}function rt(Fe){let Ne=J.normalize(Fe),Pe=uo.resolveVirtual(Ne);return Pe!==Ne?Pe:null}return{VERSIONS:Be,topLevel:Ce,getLocator:(Fe,Ne)=>Array.isArray(Ne)?{name:Ne[0],reference:Ne[1]}:{name:Fe,reference:Ne},getDependencyTreeRoots:()=>[...t.dependencyTreeRoots],getAllLocators(){let Fe=[];for(let[Ne,Pe]of S)for(let Ve of Pe.keys())Ne!==null&&Ve!==null&&Fe.push({name:Ne,reference:Ve});return Fe},getPackageInformation:Fe=>{let Ne=g(Fe);if(Ne===null)return null;let Pe=fe.fromPortablePath(Ne.packageLocation);return{...Ne,packageLocation:Pe}},findPackageLocator:Fe=>Ae(fe.toPortablePath(Fe)),resolveToUnqualified:N("resolveToUnqualified",(Fe,Ne,Pe)=>{let Ve=Ne!==null?fe.toPortablePath(Ne):null,ke=Z(fe.toPortablePath(Fe),Ve,Pe);return ke===null?null:fe.fromPortablePath(ke)}),resolveUnqualified:N("resolveUnqualified",(Fe,Ne)=>fe.fromPortablePath(Re(fe.toPortablePath(Fe),Ne))),resolveRequest:N("resolveRequest",(Fe,Ne,Pe)=>{let Ve=Ne!==null?fe.toPortablePath(Ne):null,ke=j(fe.toPortablePath(Fe),Ve,Pe);return ke===null?null:fe.fromPortablePath(ke)}),resolveVirtual:N("resolveVirtual",Fe=>{let Ne=rt(fe.toPortablePath(Fe));return Ne!==null?fe.fromPortablePath(Ne):null})}}Dt();var SBe=(t,e,r)=>{let s=TD(t),a=sY(s,{basePath:e}),n=fe.join(e,Er.pnpCjs);return dY(a,{fakeFs:r,pnpapiResolution:n})};var yY=ut(bBe());Yt();var gA={};Vt(gA,{checkManifestCompatibility:()=>PBe,extractBuildRequest:()=>oN,getExtractHint:()=>EY,hasBindingGyp:()=>IY});Ge();Dt();function PBe(t){return G.isPackageCompatible(t,Ui.getArchitectureSet())}function oN(t,e,r,{configuration:s}){let a=[];for(let n of["preinstall","install","postinstall"])e.manifest.scripts.has(n)&&a.push({type:0,script:n});return!e.manifest.scripts.has("install")&&e.misc.hasBindingGyp&&a.push({type:1,script:"node-gyp rebuild"}),a.length===0?null:t.linkType!=="HARD"?{skipped:!0,explain:n=>n.reportWarningOnce(6,`${G.prettyLocator(s,t)} lists build scripts, but is referenced through a soft link. Soft links don't support build scripts, so they'll be ignored.`)}:r&&r.built===!1?{skipped:!0,explain:n=>n.reportInfoOnce(5,`${G.prettyLocator(s,t)} lists build scripts, but its build has been explicitly disabled through configuration.`)}:!s.get("enableScripts")&&!r.built?{skipped:!0,explain:n=>n.reportWarningOnce(4,`${G.prettyLocator(s,t)} lists build scripts, but all build scripts have been disabled.`)}:PBe(t)?{skipped:!1,directives:a}:{skipped:!0,explain:n=>n.reportWarningOnce(76,`${G.prettyLocator(s,t)} The ${Ui.getArchitectureName()} architecture is incompatible with this package, build skipped.`)}}var gdt=new Set([".exe",".bin",".h",".hh",".hpp",".c",".cc",".cpp",".java",".jar",".node"]);function EY(t){return t.packageFs.getExtractHint({relevantExtensions:gdt})}function IY(t){let e=J.join(t.prefixPath,"binding.gyp");return t.packageFs.existsSync(e)}var HD={};Vt(HD,{getUnpluggedPath:()=>_D});Ge();Dt();function _D(t,{configuration:e}){return J.resolve(e.get("pnpUnpluggedFolder"),G.slugifyLocator(t))}var ddt=new Set([G.makeIdent(null,"open").identHash,G.makeIdent(null,"opn").identHash]),sg=class{constructor(){this.mode="strict";this.pnpCache=new Map}getCustomDataKey(){return JSON.stringify({name:"PnpLinker",version:2})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the PnP linker to be enabled");let s=og(r.project).cjs;if(!ce.existsSync(s))throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let a=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})),n={name:G.stringifyIdent(e),reference:e.reference},c=a.getPackageInformation(n);if(!c)throw new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed PnP map - running an install might help`);return fe.toPortablePath(c.packageLocation)}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=og(r.project).cjs;if(!ce.existsSync(s))return null;let n=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})).findPackageLocator(fe.fromPortablePath(e));return n?G.makeLocator(G.parseIdent(n.name),n.reference):null}makeInstaller(e){return new Gm(e)}isEnabled(e){return!(e.project.configuration.get("nodeLinker")!=="pnp"||e.project.configuration.get("pnpMode")!==this.mode)}},Gm=class{constructor(e){this.opts=e;this.mode="strict";this.asyncActions=new je.AsyncActions(10);this.packageRegistry=new Map;this.virtualTemplates=new Map;this.isESMLoaderRequired=!1;this.customData={store:new Map};this.unpluggedPaths=new Set;this.opts=e}attachCustomData(e){this.customData=e}async installPackage(e,r,s){let a=G.stringifyIdent(e),n=e.reference,c=!!this.opts.project.tryWorkspaceByLocator(e),f=G.isVirtualLocator(e),p=e.peerDependencies.size>0&&!f,h=!p&&!c,E=!p&&e.linkType!=="SOFT",C,S;if(h||E){let ee=f?G.devirtualizeLocator(e):e;C=this.customData.store.get(ee.locatorHash),typeof C>"u"&&(C=await mdt(r),e.linkType==="HARD"&&this.customData.store.set(ee.locatorHash,C)),C.manifest.type==="module"&&(this.isESMLoaderRequired=!0),S=this.opts.project.getDependencyMeta(ee,e.version)}let P=h?oN(e,C,S,{configuration:this.opts.project.configuration}):null,I=E?await this.unplugPackageIfNeeded(e,C,r,S,s):r.packageFs;if(J.isAbsolute(r.prefixPath))throw new Error(`Assertion failed: Expected the prefix path (${r.prefixPath}) to be relative to the parent`);let R=J.resolve(I.getRealPath(),r.prefixPath),N=CY(this.opts.project.cwd,R),U=new Map,W=new Set;if(f){for(let ee of e.peerDependencies.values())U.set(G.stringifyIdent(ee),null),W.add(G.stringifyIdent(ee));if(!c){let ee=G.devirtualizeLocator(e);this.virtualTemplates.set(ee.locatorHash,{location:CY(this.opts.project.cwd,uo.resolveVirtual(R)),locator:ee})}}return je.getMapWithDefault(this.packageRegistry,a).set(n,{packageLocation:N,packageDependencies:U,packagePeers:W,linkType:e.linkType,discardFromLookup:r.discardFromLookup||!1}),{packageLocation:R,buildRequest:P}}async attachInternalDependencies(e,r){let s=this.getPackageInformation(e);for(let[a,n]of r){let c=G.areIdentsEqual(a,n)?n.reference:[G.stringifyIdent(n),n.reference];s.packageDependencies.set(G.stringifyIdent(a),c)}}async attachExternalDependents(e,r){for(let s of r)this.getDiskInformation(s).packageDependencies.set(G.stringifyIdent(e),e.reference)}async finalizeInstall(){if(this.opts.project.configuration.get("pnpMode")!==this.mode)return;let e=og(this.opts.project);if(this.isEsmEnabled()||await ce.removePromise(e.esmLoader),this.opts.project.configuration.get("nodeLinker")!=="pnp"){await ce.removePromise(e.cjs),await ce.removePromise(e.data),await ce.removePromise(e.esmLoader),await ce.removePromise(this.opts.project.configuration.get("pnpUnpluggedFolder"));return}for(let{locator:C,location:S}of this.virtualTemplates.values())je.getMapWithDefault(this.packageRegistry,G.stringifyIdent(C)).set(C.reference,{packageLocation:S,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1});let r=this.opts.project.configuration.get("pnpFallbackMode"),s=this.opts.project.workspaces.map(({anchoredLocator:C})=>({name:G.stringifyIdent(C),reference:C.reference})),a=r!=="none",n=[],c=new Map,f=je.buildIgnorePattern([".yarn/sdks/**",...this.opts.project.configuration.get("pnpIgnorePatterns")]),p=this.packageRegistry,h=this.opts.project.configuration.get("pnpShebang"),E=this.opts.project.configuration.get("pnpZipBackend");if(r==="dependencies-only")for(let C of this.opts.project.storedPackages.values())this.opts.project.tryWorkspaceByLocator(C)&&n.push({name:G.stringifyIdent(C),reference:C.reference});return await this.asyncActions.wait(),await this.finalizeInstallWithPnp({dependencyTreeRoots:s,enableTopLevelFallback:a,fallbackExclusionList:n,fallbackPool:c,ignorePattern:f,pnpZipBackend:E,packageRegistry:p,shebang:h}),{customData:this.customData}}async transformPnpSettings(e){}isEsmEnabled(){if(this.opts.project.configuration.sources.has("pnpEnableEsmLoader"))return this.opts.project.configuration.get("pnpEnableEsmLoader");if(this.isESMLoaderRequired)return!0;for(let e of this.opts.project.workspaces)if(e.manifest.type==="module")return!0;return!1}async finalizeInstallWithPnp(e){let r=og(this.opts.project),s=await this.locateNodeModules(e.ignorePattern);if(s.length>0){this.opts.report.reportWarning(31,"One or more node_modules have been detected and will be removed. This operation may take some time.");for(let n of s)await ce.removePromise(n)}if(await this.transformPnpSettings(e),this.opts.project.configuration.get("pnpEnableInlining")){let n=tBe(e);await ce.changeFilePromise(r.cjs,n,{automaticNewlines:!0,mode:493}),await ce.removePromise(r.data)}else{let{dataFile:n,loaderFile:c}=rBe(e);await ce.changeFilePromise(r.cjs,c,{automaticNewlines:!0,mode:493}),await ce.changeFilePromise(r.data,n,{automaticNewlines:!0,mode:420})}this.isEsmEnabled()&&(this.opts.report.reportWarning(0,"ESM support for PnP uses the experimental loader API and is therefore experimental"),await ce.changeFilePromise(r.esmLoader,(0,yY.default)(),{automaticNewlines:!0,mode:420}));let a=this.opts.project.configuration.get("pnpUnpluggedFolder");if(this.unpluggedPaths.size===0)await ce.removePromise(a);else for(let n of await ce.readdirPromise(a)){let c=J.resolve(a,n);this.unpluggedPaths.has(c)||await ce.removePromise(c)}}async locateNodeModules(e){let r=[],s=e?new RegExp(e):null;for(let a of this.opts.project.workspaces){let n=J.join(a.cwd,"node_modules");if(s&&s.test(J.relative(this.opts.project.cwd,a.cwd))||!ce.existsSync(n))continue;let c=await ce.readdirPromise(n,{withFileTypes:!0}),f=c.filter(p=>!p.isDirectory()||p.name===".bin"||!p.name.startsWith("."));if(f.length===c.length)r.push(n);else for(let p of f)r.push(J.join(n,p.name))}return r}async unplugPackageIfNeeded(e,r,s,a,n){return this.shouldBeUnplugged(e,r,a)?this.unplugPackage(e,s,n):s.packageFs}shouldBeUnplugged(e,r,s){return typeof s.unplugged<"u"?s.unplugged:ddt.has(e.identHash)||e.conditions!=null?!0:r.manifest.preferUnplugged!==null?r.manifest.preferUnplugged:!!(oN(e,r,s,{configuration:this.opts.project.configuration})?.skipped===!1||r.misc.extractHint)}async unplugPackage(e,r,s){let a=_D(e,{configuration:this.opts.project.configuration});return this.opts.project.disabledLocators.has(e.locatorHash)?new _f(a,{baseFs:r.packageFs,pathUtils:J}):(this.unpluggedPaths.add(a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{let n=J.join(a,r.prefixPath,".ready");await ce.existsPromise(n)||(this.opts.project.storedBuildState.delete(e.locatorHash),await ce.mkdirPromise(a,{recursive:!0}),await ce.copyPromise(a,vt.dot,{baseFs:r.packageFs,overwrite:!1}),await ce.writeFilePromise(n,""))})),new Sn(a))}getPackageInformation(e){let r=G.stringifyIdent(e),s=e.reference,a=this.packageRegistry.get(r);if(!a)throw new Error(`Assertion failed: The package information store should have been available (for ${G.prettyIdent(this.opts.project.configuration,e)})`);let n=a.get(s);if(!n)throw new Error(`Assertion failed: The package information should have been available (for ${G.prettyLocator(this.opts.project.configuration,e)})`);return n}getDiskInformation(e){let r=je.getMapWithDefault(this.packageRegistry,"@@disk"),s=CY(this.opts.project.cwd,e);return je.getFactoryWithDefault(r,s,()=>({packageLocation:s,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1}))}};function CY(t,e){let r=J.relative(t,e);return r.match(/^\.{0,2}\//)||(r=`./${r}`),r.replace(/\/?$/,"/")}async function mdt(t){let e=await Ut.tryFind(t.prefixPath,{baseFs:t.packageFs})??new Ut,r=new Set(["preinstall","install","postinstall"]);for(let s of e.scripts.keys())r.has(s)||e.scripts.delete(s);return{manifest:{scripts:e.scripts,preferUnplugged:e.preferUnplugged,type:e.type},misc:{extractHint:EY(t),hasBindingGyp:IY(t)}}}Ge();Ge();Yt();var xBe=ut(Go());var Sw=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unplug direct dependencies from the entire project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Unplug both direct and transitive dependencies"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["unplug"]]}static{this.usage=ot.Usage({description:"force the unpacking of a list of packages",details:"\n This command will add the selectors matching the specified patterns to the list of packages that must be unplugged when installed.\n\n A package being unplugged means that instead of being referenced directly through its archive, it will be unpacked at install time in the directory configured via `pnpUnpluggedFolder`. Note that unpacking packages this way is generally not recommended because it'll make it harder to store your packages within the repository. However, it's a good approach to quickly and safely debug some packages, and can even sometimes be required depending on the context (for example when the package contains shellscripts).\n\n Running the command will set a persistent flag inside your top-level `package.json`, in the `dependenciesMeta` field. As such, to undo its effects, you'll need to revert the changes made to the manifest and run `yarn install` to apply the modification.\n\n By default, only direct dependencies from the current workspace are affected. If `-A,--all` is set, direct dependencies from the entire project are affected. Using the `-R,--recursive` flag will affect transitive dependencies as well as direct ones.\n\n This command accepts glob patterns inside the scope and name components (not the range). Make sure to escape the patterns to prevent your own shell from trying to expand them.\n ",examples:[["Unplug the lodash dependency from the active workspace","yarn unplug lodash"],["Unplug all instances of lodash referenced by any workspace","yarn unplug lodash -A"],["Unplug all instances of lodash referenced by the active workspace and its dependencies","yarn unplug lodash -R"],["Unplug all instances of lodash, anywhere","yarn unplug lodash -AR"],["Unplug one specific version of lodash","yarn unplug lodash@1.2.3"],["Unplug all packages with the `@babel` scope","yarn unplug '@babel/*'"],["Unplug all packages (only for testing, not recommended)","yarn unplug -R '*'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);if(r.get("nodeLinker")!=="pnp")throw new nt("This command can only be used if the `nodeLinker` option is set to `pnp`");await s.restoreInstallState();let c=new Set(this.patterns),f=this.patterns.map(P=>{let I=G.parseDescriptor(P),R=I.range!=="unknown"?I:G.makeDescriptor(I,"*");if(!Fr.validRange(R.range))throw new nt(`The range of the descriptor patterns must be a valid semver range (${G.prettyDescriptor(r,R)})`);return N=>{let U=G.stringifyIdent(N);return!xBe.default.isMatch(U,G.stringifyIdent(R))||N.version&&!Fr.satisfiesWithPrereleases(N.version,R.range)?!1:(c.delete(P),!0)}}),p=()=>{let P=[];for(let I of s.storedPackages.values())!s.tryWorkspaceByLocator(I)&&!G.isVirtualLocator(I)&&f.some(R=>R(I))&&P.push(I);return P},h=P=>{let I=new Set,R=[],N=(U,W)=>{if(I.has(U.locatorHash))return;let ee=!!s.tryWorkspaceByLocator(U);if(!(W>0&&!this.recursive&&ee)&&(I.add(U.locatorHash),!s.tryWorkspaceByLocator(U)&&f.some(ie=>ie(U))&&R.push(U),!(W>0&&!this.recursive)))for(let ie of U.dependencies.values()){let ue=s.storedResolutions.get(ie.descriptorHash);if(!ue)throw new Error("Assertion failed: The resolution should have been registered");let le=s.storedPackages.get(ue);if(!le)throw new Error("Assertion failed: The package should have been registered");N(le,W+1)}};for(let U of P)N(U.anchoredPackage,0);return R},E,C;if(this.all&&this.recursive?(E=p(),C="the project"):this.all?(E=h(s.workspaces),C="any workspace"):(E=h([a]),C="this workspace"),c.size>1)throw new nt(`Patterns ${he.prettyList(r,c,he.Type.CODE)} don't match any packages referenced by ${C}`);if(c.size>0)throw new nt(`Pattern ${he.prettyList(r,c,he.Type.CODE)} doesn't match any packages referenced by ${C}`);E=je.sortMap(E,P=>G.stringifyLocator(P));let S=await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async P=>{for(let I of E){let R=I.version??"unknown",N=s.topLevelWorkspace.manifest.ensureDependencyMeta(G.makeDescriptor(I,R));N.unplugged=!0,P.reportInfo(0,`Will unpack ${G.prettyLocator(r,I)} to ${he.pretty(r,_D(I,{configuration:r}),he.Type.PATH)}`),P.reportJson({locator:G.stringifyLocator(I),version:R})}await s.topLevelWorkspace.persistManifest(),this.json||P.reportSeparator()});return S.hasErrors()?S.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};var og=t=>({cjs:J.join(t.cwd,Er.pnpCjs),data:J.join(t.cwd,Er.pnpData),esmLoader:J.join(t.cwd,Er.pnpEsmLoader)}),QBe=t=>/\s/.test(t)?JSON.stringify(t):t;async function ydt(t,e,r){let s=/\s*--require\s+\S*\.pnp\.c?js\s*/g,a=/\s*--experimental-loader\s+\S*\.pnp\.loader\.mjs\s*/,n=(e.NODE_OPTIONS??"").replace(s," ").replace(a," ").trim();if(t.configuration.get("nodeLinker")!=="pnp"){e.NODE_OPTIONS=n||void 0;return}let c=og(t),f=`--require ${QBe(fe.fromPortablePath(c.cjs))}`;ce.existsSync(c.esmLoader)&&(f=`${f} --experimental-loader ${(0,kBe.pathToFileURL)(fe.fromPortablePath(c.esmLoader)).href}`),ce.existsSync(c.cjs)&&(e.NODE_OPTIONS=n?`${f} ${n}`:f)}async function Edt(t,e){let r=og(t);e(r.cjs),e(r.data),e(r.esmLoader),e(t.configuration.get("pnpUnpluggedFolder"))}var Idt={hooks:{populateYarnPaths:Edt,setupScriptEnvironment:ydt},configuration:{nodeLinker:{description:'The linker used for installing Node packages, one of: "pnp", "pnpm", or "node-modules"',type:"STRING",default:"pnp"},minizip:{description:"Whether Yarn should use minizip to extract archives",type:"BOOLEAN",default:!1},winLinkType:{description:"Whether Yarn should use Windows Junctions or symlinks when creating links on Windows.",type:"STRING",values:["junctions","symlinks"],default:"junctions"},pnpMode:{description:"If 'strict', generates standard PnP maps. If 'loose', merges them with the n_m resolution.",type:"STRING",default:"strict"},pnpShebang:{description:"String to prepend to the generated PnP script",type:"STRING",default:"#!/usr/bin/env node"},pnpIgnorePatterns:{description:"Array of glob patterns; files matching them will use the classic resolution",type:"STRING",default:[],isArray:!0},pnpZipBackend:{description:"Whether to use the experimental js implementation for the ZipFS",type:"STRING",values:["libzip","js"],default:"libzip"},pnpEnableEsmLoader:{description:"If true, Yarn will generate an ESM loader (`.pnp.loader.mjs`). If this is not explicitly set Yarn tries to automatically detect whether ESM support is required.",type:"BOOLEAN",default:!1},pnpEnableInlining:{description:"If true, the PnP data will be inlined along with the generated loader",type:"BOOLEAN",default:!0},pnpFallbackMode:{description:"If true, the generated PnP loader will follow the top-level fallback rule",type:"STRING",default:"dependencies-only"},pnpUnpluggedFolder:{description:"Folder where the unplugged packages must be stored",type:"ABSOLUTE_PATH",default:"./.yarn/unplugged"}},linkers:[sg],commands:[Sw]},Cdt=Idt;var UBe=ut(OBe());Yt();var xY=ut(Ie("crypto")),_Be=ut(Ie("fs")),HBe=1,Ti="node_modules",aN=".bin",jBe=".yarn-state.yml",Mdt=1e3,kY=(s=>(s.CLASSIC="classic",s.HARDLINKS_LOCAL="hardlinks-local",s.HARDLINKS_GLOBAL="hardlinks-global",s))(kY||{}),jD=class{constructor(){this.installStateCache=new Map}getCustomDataKey(){return JSON.stringify({name:"NodeModulesLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the node-modules linker to be enabled");let s=r.project.tryWorkspaceByLocator(e);if(s)return s.cwd;let a=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await PY(r.project,{unrollAliases:!0}));if(a===null)throw new nt("Couldn't find the node_modules state file - running an install might help (findPackageLocation)");let n=a.locatorMap.get(G.stringifyLocator(e));if(!n){let p=new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed node_modules map - running an install might help`);throw p.code="LOCATOR_NOT_INSTALLED",p}let c=n.locations.sort((p,h)=>p.split(J.sep).length-h.split(J.sep).length),f=J.join(r.project.configuration.startingCwd,Ti);return c.find(p=>J.contains(f,p))||n.locations[0]}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await PY(r.project,{unrollAliases:!0}));if(s===null)return null;let{locationRoot:a,segments:n}=lN(J.resolve(e),{skipPrefix:r.project.cwd}),c=s.locationTree.get(a);if(!c)return null;let f=c.locator;for(let p of n){if(c=c.children.get(p),!c)break;f=c.locator||f}return G.parseLocator(f)}makeInstaller(e){return new bY(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="node-modules"}},bY=class{constructor(e){this.opts=e;this.localStore=new Map;this.realLocatorChecksums=new Map;this.customData={store:new Map}}attachCustomData(e){this.customData=e}async installPackage(e,r){let s=J.resolve(r.packageFs.getRealPath(),r.prefixPath),a=this.customData.store.get(e.locatorHash);if(typeof a>"u"&&(a=await Udt(e,r),e.linkType==="HARD"&&this.customData.store.set(e.locatorHash,a)),!G.isPackageCompatible(e,this.opts.project.configuration.getSupportedArchitectures()))return{packageLocation:null,buildRequest:null};let n=new Map,c=new Set;n.has(G.stringifyIdent(e))||n.set(G.stringifyIdent(e),e.reference);let f=e;if(G.isVirtualLocator(e)){f=G.devirtualizeLocator(e);for(let E of e.peerDependencies.values())n.set(G.stringifyIdent(E),null),c.add(G.stringifyIdent(E))}let p={packageLocation:`${fe.fromPortablePath(s)}/`,packageDependencies:n,packagePeers:c,linkType:e.linkType,discardFromLookup:r.discardFromLookup??!1};this.localStore.set(e.locatorHash,{pkg:e,customPackageData:a,dependencyMeta:this.opts.project.getDependencyMeta(e,e.version),pnpNode:p});let h=r.checksum?r.checksum.substring(r.checksum.indexOf("/")+1):null;return this.realLocatorChecksums.set(f.locatorHash,h),{packageLocation:s,buildRequest:null}}async attachInternalDependencies(e,r){let s=this.localStore.get(e.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected information object to have been registered");for(let[a,n]of r){let c=G.areIdentsEqual(a,n)?n.reference:[G.stringifyIdent(n),n.reference];s.pnpNode.packageDependencies.set(G.stringifyIdent(a),c)}}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the node-modules linker")}async finalizeInstall(){if(this.opts.project.configuration.get("nodeLinker")!=="node-modules")return;let e=new uo({baseFs:new $f({maxOpenFiles:80,readOnlyArchives:!0})}),r=await PY(this.opts.project),s=this.opts.project.configuration.get("nmMode");(r===null||s!==r.nmMode)&&(this.opts.project.storedBuildState.clear(),r={locatorMap:new Map,binSymlinks:new Map,locationTree:new Map,nmMode:s,mtimeMs:0});let a=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmHoistingLimits");try{P=je.validateEnum(xD,S.manifest.installConfig?.hoistingLimits??P)}catch{let I=G.prettyWorkspace(this.opts.project.configuration,S);this.opts.report.reportWarning(57,`${I}: Invalid 'installConfig.hoistingLimits' value. Expected one of ${Object.values(xD).join(", ")}, using default: "${P}"`)}return[S.relativeCwd,P]})),n=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmSelfReferences");return P=S.manifest.installConfig?.selfReferences??P,[S.relativeCwd,P]})),c={VERSIONS:{std:1},topLevel:{name:null,reference:null},getLocator:(S,P)=>Array.isArray(P)?{name:P[0],reference:P[1]}:{name:S,reference:P},getDependencyTreeRoots:()=>this.opts.project.workspaces.map(S=>{let P=S.anchoredLocator;return{name:G.stringifyIdent(P),reference:P.reference}}),getPackageInformation:S=>{let P=S.reference===null?this.opts.project.topLevelWorkspace.anchoredLocator:G.makeLocator(G.parseIdent(S.name),S.reference),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the package reference to have been registered");return I.pnpNode},findPackageLocator:S=>{let P=this.opts.project.tryWorkspaceByCwd(fe.toPortablePath(S));if(P!==null){let I=P.anchoredLocator;return{name:G.stringifyIdent(I),reference:I.reference}}throw new Error("Assertion failed: Unimplemented")},resolveToUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveRequest:()=>{throw new Error("Assertion failed: Unimplemented")},resolveVirtual:S=>fe.fromPortablePath(uo.resolveVirtual(fe.toPortablePath(S)))},{tree:f,errors:p,preserveSymlinksRequired:h}=kD(c,{pnpifyFs:!1,validateExternalSoftLinks:!0,hoistingLimitsByCwd:a,project:this.opts.project,selfReferencesByCwd:n});if(!f){for(let{messageName:S,text:P}of p)this.opts.report.reportError(S,P);return}let E=nY(f);await Ydt(r,E,{baseFs:e,project:this.opts.project,report:this.opts.report,realLocatorChecksums:this.realLocatorChecksums,loadManifest:async S=>{let P=G.parseLocator(S),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the slot to exist");return I.customPackageData.manifest}});let C=[];for(let[S,P]of E.entries()){if(WBe(S))continue;let I=G.parseLocator(S),R=this.localStore.get(I.locatorHash);if(typeof R>"u")throw new Error("Assertion failed: Expected the slot to exist");if(this.opts.project.tryWorkspaceByLocator(R.pkg))continue;let N=gA.extractBuildRequest(R.pkg,R.customPackageData,R.dependencyMeta,{configuration:this.opts.project.configuration});N&&C.push({buildLocations:P.locations,locator:I,buildRequest:N})}return h&&this.opts.report.reportWarning(72,`The application uses portals and that's why ${he.pretty(this.opts.project.configuration,"--preserve-symlinks",he.Type.CODE)} Node option is required for launching it`),{customData:this.customData,records:C}}};async function Udt(t,e){let r=await Ut.tryFind(e.prefixPath,{baseFs:e.packageFs})??new Ut,s=new Set(["preinstall","install","postinstall"]);for(let a of r.scripts.keys())s.has(a)||r.scripts.delete(a);return{manifest:{bin:r.bin,scripts:r.scripts},misc:{hasBindingGyp:gA.hasBindingGyp(e)}}}async function _dt(t,e,r,s,{installChangedByUser:a}){let n="";n+=`# Warning: This file is automatically generated. Removing it is fine, but will +`,n+=`# cause your node_modules installation to become invalidated. +`,n+=` +`,n+=`__metadata: +`,n+=` version: ${HBe} +`,n+=` nmMode: ${s.value} +`;let c=Array.from(e.keys()).sort(),f=G.stringifyLocator(t.topLevelWorkspace.anchoredLocator);for(let E of c){let C=e.get(E);n+=` +`,n+=`${JSON.stringify(E)}: +`,n+=` locations: +`;for(let S of C.locations){let P=J.contains(t.cwd,S);if(P===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` - ${JSON.stringify(P)} +`}if(C.aliases.length>0){n+=` aliases: +`;for(let S of C.aliases)n+=` - ${JSON.stringify(S)} +`}if(E===f&&r.size>0){n+=` bin: +`;for(let[S,P]of r){let I=J.contains(t.cwd,S);if(I===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` ${JSON.stringify(I)}: +`;for(let[R,N]of P){let U=J.relative(J.join(S,Ti),N);n+=` ${JSON.stringify(R)}: ${JSON.stringify(U)} +`}}}}let p=t.cwd,h=J.join(p,Ti,jBe);a&&await ce.removePromise(h),await ce.changeFilePromise(h,n,{automaticNewlines:!0})}async function PY(t,{unrollAliases:e=!1}={}){let r=t.cwd,s=J.join(r,Ti,jBe),a;try{a=await ce.statPromise(s)}catch{}if(!a)return null;let n=ls(await ce.readFilePromise(s,"utf8"));if(n.__metadata.version>HBe)return null;let c=n.__metadata.nmMode||"classic",f=new Map,p=new Map;delete n.__metadata;for(let[h,E]of Object.entries(n)){let C=E.locations.map(P=>J.join(r,P)),S=E.bin;if(S)for(let[P,I]of Object.entries(S)){let R=J.join(r,fe.toPortablePath(P)),N=je.getMapWithDefault(p,R);for(let[U,W]of Object.entries(I))N.set(U,fe.toPortablePath([R,Ti,W].join(J.sep)))}if(f.set(h,{target:vt.dot,linkType:"HARD",locations:C,aliases:E.aliases||[]}),e&&E.aliases)for(let P of E.aliases){let{scope:I,name:R}=G.parseLocator(h),N=G.makeLocator(G.makeIdent(I,R),P),U=G.stringifyLocator(N);f.set(U,{target:vt.dot,linkType:"HARD",locations:C,aliases:[]})}}return{locatorMap:f,binSymlinks:p,locationTree:GBe(f,{skipPrefix:t.cwd}),nmMode:c,mtimeMs:a.mtimeMs}}var bw=async(t,e)=>{if(t.split(J.sep).indexOf(Ti)<0)throw new Error(`Assertion failed: trying to remove dir that doesn't contain node_modules: ${t}`);try{let r;if(!e.innerLoop&&(r=await ce.lstatPromise(t),!r.isDirectory()&&!r.isSymbolicLink()||r.isSymbolicLink()&&!e.isWorkspaceDir)){await ce.unlinkPromise(t);return}let s=await ce.readdirPromise(t,{withFileTypes:!0});for(let n of s){let c=J.join(t,n.name);n.isDirectory()?(n.name!==Ti||e&&e.innerLoop)&&await bw(c,{innerLoop:!0,contentsOnly:!1}):await ce.unlinkPromise(c)}let a=!e.innerLoop&&e.isWorkspaceDir&&r?.isSymbolicLink();!e.contentsOnly&&!a&&await ce.rmdirPromise(t)}catch(r){if(r.code!=="ENOENT"&&r.code!=="ENOTEMPTY")throw r}},LBe=4,lN=(t,{skipPrefix:e})=>{let r=J.contains(e,t);if(r===null)throw new Error(`Assertion failed: Writing attempt prevented to ${t} which is outside project root: ${e}`);let s=r.split(J.sep).filter(p=>p!==""),a=s.indexOf(Ti),n=s.slice(0,a).join(J.sep),c=J.join(e,n),f=s.slice(a);return{locationRoot:c,segments:f}},GBe=(t,{skipPrefix:e})=>{let r=new Map;if(t===null)return r;let s=()=>({children:new Map,linkType:"HARD"});for(let[a,n]of t.entries()){if(n.linkType==="SOFT"&&J.contains(e,n.target)!==null){let f=je.getFactoryWithDefault(r,n.target,s);f.locator=a,f.linkType=n.linkType}for(let c of n.locations){let{locationRoot:f,segments:p}=lN(c,{skipPrefix:e}),h=je.getFactoryWithDefault(r,f,s);for(let E=0;E{if(process.platform==="win32"&&r==="junctions"){let s;try{s=await ce.lstatPromise(t)}catch{}if(!s||s.isDirectory()){await ce.symlinkPromise(t,e,"junction");return}}await ce.symlinkPromise(J.relative(J.dirname(e),t),e)};async function qBe(t,e,r){let s=J.join(t,`${xY.default.randomBytes(16).toString("hex")}.tmp`);try{await ce.writeFilePromise(s,r);try{await ce.linkPromise(s,e)}catch{}}finally{await ce.unlinkPromise(s)}}async function Hdt({srcPath:t,dstPath:e,entry:r,globalHardlinksStore:s,baseFs:a,nmMode:n}){if(r.kind==="file"){if(n.value==="hardlinks-global"&&s&&r.digest){let f=J.join(s,r.digest.substring(0,2),`${r.digest.substring(2)}.dat`),p;try{let h=await ce.statPromise(f);if(h&&(!r.mtimeMs||h.mtimeMs>r.mtimeMs||h.mtimeMs{await ce.mkdirPromise(t,{recursive:!0});let f=async(E=vt.dot)=>{let C=J.join(e,E),S=await r.readdirPromise(C,{withFileTypes:!0}),P=new Map;for(let I of S){let R=J.join(E,I.name),N,U=J.join(C,I.name);if(I.isFile()){if(N={kind:"file",mode:(await r.lstatPromise(U)).mode},a.value==="hardlinks-global"){let W=await Nn.checksumFile(U,{baseFs:r,algorithm:"sha1"});N.digest=W}}else if(I.isDirectory())N={kind:"directory"};else if(I.isSymbolicLink())N={kind:"symlink",symlinkTo:await r.readlinkPromise(U)};else throw new Error(`Unsupported file type (file: ${U}, mode: 0o${await r.statSync(U).mode.toString(8).padStart(6,"0")})`);if(P.set(R,N),I.isDirectory()&&R!==Ti){let W=await f(R);for(let[ee,ie]of W)P.set(ee,ie)}}return P},p;if(a.value==="hardlinks-global"&&s&&c){let E=J.join(s,c.substring(0,2),`${c.substring(2)}.json`);try{p=new Map(Object.entries(JSON.parse(await ce.readFilePromise(E,"utf8"))))}catch{p=await f()}}else p=await f();let h=!1;for(let[E,C]of p){let S=J.join(e,E),P=J.join(t,E);if(C.kind==="directory")await ce.mkdirPromise(P,{recursive:!0});else if(C.kind==="file"){let I=C.mtimeMs;await Hdt({srcPath:S,dstPath:P,entry:C,nmMode:a,baseFs:r,globalHardlinksStore:s}),C.mtimeMs!==I&&(h=!0)}else C.kind==="symlink"&&await QY(J.resolve(J.dirname(P),C.symlinkTo),P,n)}if(a.value==="hardlinks-global"&&s&&h&&c){let E=J.join(s,c.substring(0,2),`${c.substring(2)}.json`);await ce.removePromise(E),await qBe(s,E,Buffer.from(JSON.stringify(Object.fromEntries(p))))}};function Gdt(t,e,r,s){let a=new Map,n=new Map,c=new Map,f=!1,p=(h,E,C,S,P)=>{let I=!0,R=J.join(h,E),N=new Set;if(E===Ti||E.startsWith("@")){let W;try{W=ce.statSync(R)}catch{}I=!!W,W?W.mtimeMs>r?(f=!0,N=new Set(ce.readdirSync(R))):N=new Set(C.children.get(E).children.keys()):f=!0;let ee=e.get(h);if(ee){let ie=J.join(h,Ti,aN),ue;try{ue=ce.statSync(ie)}catch{}if(!ue)f=!0;else if(ue.mtimeMs>r){f=!0;let le=new Set(ce.readdirSync(ie)),me=new Map;n.set(h,me);for(let[pe,Be]of ee)le.has(pe)&&me.set(pe,Be)}else n.set(h,ee)}}else I=P.has(E);let U=C.children.get(E);if(I){let{linkType:W,locator:ee}=U,ie={children:new Map,linkType:W,locator:ee};if(S.children.set(E,ie),ee){let ue=je.getSetWithDefault(c,ee);ue.add(R),c.set(ee,ue)}for(let ue of U.children.keys())p(R,ue,U,ie,N)}else U.locator&&s.storedBuildState.delete(G.parseLocator(U.locator).locatorHash)};for(let[h,E]of t){let{linkType:C,locator:S}=E,P={children:new Map,linkType:C,locator:S};if(a.set(h,P),S){let I=je.getSetWithDefault(c,E.locator);I.add(h),c.set(E.locator,I)}E.children.has(Ti)&&p(h,Ti,E,P,new Set)}return{locationTree:a,binSymlinks:n,locatorLocations:c,installChangedByUser:f}}function WBe(t){let e=G.parseDescriptor(t);return G.isVirtualDescriptor(e)&&(e=G.devirtualizeDescriptor(e)),e.range.startsWith("link:")}async function qdt(t,e,r,{loadManifest:s}){let a=new Map;for(let[f,{locations:p}]of t){let h=WBe(f)?null:await s(f,p[0]),E=new Map;if(h)for(let[C,S]of h.bin){let P=J.join(p[0],S);S!==""&&ce.existsSync(P)&&E.set(C,S)}a.set(f,E)}let n=new Map,c=(f,p,h)=>{let E=new Map,C=J.contains(r,f);if(h.locator&&C!==null){let S=a.get(h.locator);for(let[P,I]of S){let R=J.join(f,fe.toPortablePath(I));E.set(P,R)}for(let[P,I]of h.children){let R=J.join(f,P),N=c(R,R,I);N.size>0&&n.set(f,new Map([...n.get(f)||new Map,...N]))}}else for(let[S,P]of h.children){let I=c(J.join(f,S),p,P);for(let[R,N]of I)E.set(R,N)}return E};for(let[f,p]of e){let h=c(f,f,p);h.size>0&&n.set(f,new Map([...n.get(f)||new Map,...h]))}return n}var MBe=(t,e)=>{if(!t||!e)return t===e;let r=G.parseLocator(t);G.isVirtualLocator(r)&&(r=G.devirtualizeLocator(r));let s=G.parseLocator(e);return G.isVirtualLocator(s)&&(s=G.devirtualizeLocator(s)),G.areLocatorsEqual(r,s)};function TY(t){return J.join(t.get("globalFolder"),"store")}function Wdt(t,e){let r=s=>{let a=s.split(J.sep),n=a.lastIndexOf(Ti);if(n<0||n==a.length-1)throw new Error(`Assertion failed. Path is outside of any node_modules package ${s}`);return a.slice(0,n+(a[n+1].startsWith("@")?3:2)).join(J.sep)};for(let s of t.values())for(let[a,n]of s)e.has(r(n))&&s.delete(a)}async function Ydt(t,e,{baseFs:r,project:s,report:a,loadManifest:n,realLocatorChecksums:c}){let f=J.join(s.cwd,Ti),{locationTree:p,binSymlinks:h,locatorLocations:E,installChangedByUser:C}=Gdt(t.locationTree,t.binSymlinks,t.mtimeMs,s),S=GBe(e,{skipPrefix:s.cwd}),P=[],I=async({srcDir:Be,dstDir:Ce,linkType:g,globalHardlinksStore:we,nmMode:ye,windowsLinkType:Ae,packageChecksum:se})=>{let Z=(async()=>{try{g==="SOFT"?(await ce.mkdirPromise(J.dirname(Ce),{recursive:!0}),await QY(J.resolve(Be),Ce,Ae)):await jdt(Ce,Be,{baseFs:r,globalHardlinksStore:we,nmMode:ye,windowsLinkType:Ae,packageChecksum:se})}catch(De){throw De.message=`While persisting ${Be} -> ${Ce} ${De.message}`,De}finally{ie.tick()}})().then(()=>P.splice(P.indexOf(Z),1));P.push(Z),P.length>LBe&&await Promise.race(P)},R=async(Be,Ce,g)=>{let we=(async()=>{let ye=async(Ae,se,Z)=>{try{Z.innerLoop||await ce.mkdirPromise(se,{recursive:!0});let De=await ce.readdirPromise(Ae,{withFileTypes:!0});for(let Re of De){if(!Z.innerLoop&&Re.name===aN)continue;let mt=J.join(Ae,Re.name),j=J.join(se,Re.name);Re.isDirectory()?(Re.name!==Ti||Z&&Z.innerLoop)&&(await ce.mkdirPromise(j,{recursive:!0}),await ye(mt,j,{...Z,innerLoop:!0})):me.value==="hardlinks-local"||me.value==="hardlinks-global"?await ce.linkPromise(mt,j):await ce.copyFilePromise(mt,j,_Be.default.constants.COPYFILE_FICLONE)}}catch(De){throw Z.innerLoop||(De.message=`While cloning ${Ae} -> ${se} ${De.message}`),De}finally{Z.innerLoop||ie.tick()}};await ye(Be,Ce,g)})().then(()=>P.splice(P.indexOf(we),1));P.push(we),P.length>LBe&&await Promise.race(P)},N=async(Be,Ce,g)=>{if(g)for(let[we,ye]of Ce.children){let Ae=g.children.get(we);await N(J.join(Be,we),ye,Ae)}else{Ce.children.has(Ti)&&await bw(J.join(Be,Ti),{contentsOnly:!1});let we=J.basename(Be)===Ti&&p.has(J.join(J.dirname(Be)));await bw(Be,{contentsOnly:Be===f,isWorkspaceDir:we})}};for(let[Be,Ce]of p){let g=S.get(Be);for(let[we,ye]of Ce.children){if(we===".")continue;let Ae=g&&g.children.get(we),se=J.join(Be,we);await N(se,ye,Ae)}}let U=async(Be,Ce,g)=>{if(g){MBe(Ce.locator,g.locator)||await bw(Be,{contentsOnly:Ce.linkType==="HARD"});for(let[we,ye]of Ce.children){let Ae=g.children.get(we);await U(J.join(Be,we),ye,Ae)}}else{Ce.children.has(Ti)&&await bw(J.join(Be,Ti),{contentsOnly:!0});let we=J.basename(Be)===Ti&&S.has(J.join(J.dirname(Be)));await bw(Be,{contentsOnly:Ce.linkType==="HARD",isWorkspaceDir:we})}};for(let[Be,Ce]of S){let g=p.get(Be);for(let[we,ye]of Ce.children){if(we===".")continue;let Ae=g&&g.children.get(we);await U(J.join(Be,we),ye,Ae)}}let W=new Map,ee=[];for(let[Be,Ce]of E)for(let g of Ce){let{locationRoot:we,segments:ye}=lN(g,{skipPrefix:s.cwd}),Ae=S.get(we),se=we;if(Ae){for(let Z of ye)if(se=J.join(se,Z),Ae=Ae.children.get(Z),!Ae)break;if(Ae){let Z=MBe(Ae.locator,Be),De=e.get(Ae.locator),Re=De.target,mt=se,j=De.linkType;if(Z)W.has(Re)||W.set(Re,mt);else if(Re!==mt){let rt=G.parseLocator(Ae.locator);G.isVirtualLocator(rt)&&(rt=G.devirtualizeLocator(rt)),ee.push({srcDir:Re,dstDir:mt,linkType:j,realLocatorHash:rt.locatorHash})}}}}for(let[Be,{locations:Ce}]of e.entries())for(let g of Ce){let{locationRoot:we,segments:ye}=lN(g,{skipPrefix:s.cwd}),Ae=p.get(we),se=S.get(we),Z=we,De=e.get(Be),Re=G.parseLocator(Be);G.isVirtualLocator(Re)&&(Re=G.devirtualizeLocator(Re));let mt=Re.locatorHash,j=De.target,rt=g;if(j===rt)continue;let Fe=De.linkType;for(let Ne of ye)se=se.children.get(Ne);if(!Ae)ee.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:mt});else for(let Ne of ye)if(Z=J.join(Z,Ne),Ae=Ae.children.get(Ne),!Ae){ee.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:mt});break}}let ie=Ao.progressViaCounter(ee.length),ue=a.reportProgress(ie),le=s.configuration.get("nmMode"),me={value:le},pe=s.configuration.get("winLinkType");try{let Be=me.value==="hardlinks-global"?`${TY(s.configuration)}/v1`:null;if(Be&&!await ce.existsPromise(Be)){await ce.mkdirpPromise(Be);for(let g=0;g<256;g++)await ce.mkdirPromise(J.join(Be,g.toString(16).padStart(2,"0")))}for(let g of ee)(g.linkType==="SOFT"||!W.has(g.srcDir))&&(W.set(g.srcDir,g.dstDir),await I({...g,globalHardlinksStore:Be,nmMode:me,windowsLinkType:pe,packageChecksum:c.get(g.realLocatorHash)||null}));await Promise.all(P),P.length=0;for(let g of ee){let we=W.get(g.srcDir);g.linkType!=="SOFT"&&g.dstDir!==we&&await R(we,g.dstDir,{nmMode:me})}await Promise.all(P),await ce.mkdirPromise(f,{recursive:!0}),Wdt(h,new Set(ee.map(g=>g.dstDir)));let Ce=await qdt(e,S,s.cwd,{loadManifest:n});await Vdt(h,Ce,s.cwd,pe),await _dt(s,e,Ce,me,{installChangedByUser:C}),le=="hardlinks-global"&&me.value=="hardlinks-local"&&a.reportWarningOnce(74,"'nmMode' has been downgraded to 'hardlinks-local' due to global cache and install folder being on different devices")}finally{ue.stop()}}async function Vdt(t,e,r,s){for(let a of t.keys()){if(J.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);if(!e.has(a)){let n=J.join(a,Ti,aN);await ce.removePromise(n)}}for(let[a,n]of e){if(J.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);let c=J.join(a,Ti,aN),f=t.get(a)||new Map;await ce.mkdirPromise(c,{recursive:!0});for(let p of f.keys())n.has(p)||(await ce.removePromise(J.join(c,p)),process.platform==="win32"&&await ce.removePromise(J.join(c,`${p}.cmd`)));for(let[p,h]of n){let E=f.get(p),C=J.join(c,p);E!==h&&(process.platform==="win32"?await(0,UBe.default)(fe.fromPortablePath(h),fe.fromPortablePath(C),{createPwshFile:!1}):(await ce.removePromise(C),await QY(h,C,s),J.contains(r,await ce.realpathPromise(h))!==null&&await ce.chmodPromise(h,493)))}}}Ge();Dt();eA();var GD=class extends sg{constructor(){super(...arguments);this.mode="loose"}makeInstaller(r){return new RY(r)}},RY=class extends Gm{constructor(){super(...arguments);this.mode="loose"}async transformPnpSettings(r){let s=new uo({baseFs:new $f({maxOpenFiles:80,readOnlyArchives:!0})}),a=SBe(r,this.opts.project.cwd,s),{tree:n,errors:c}=kD(a,{pnpifyFs:!1,project:this.opts.project});if(!n){for(let{messageName:C,text:S}of c)this.opts.report.reportError(C,S);return}let f=new Map;r.fallbackPool=f;let p=(C,S)=>{let P=G.parseLocator(S.locator),I=G.stringifyIdent(P);I===C?f.set(C,P.reference):f.set(C,[I,P.reference])},h=J.join(this.opts.project.cwd,Er.nodeModules),E=n.get(h);if(!(typeof E>"u")){if("target"in E)throw new Error("Assertion failed: Expected the root junction point to be a directory");for(let C of E.dirList){let S=J.join(h,C),P=n.get(S);if(typeof P>"u")throw new Error("Assertion failed: Expected the child to have been registered");if("target"in P)p(C,P);else for(let I of P.dirList){let R=J.join(S,I),N=n.get(R);if(typeof N>"u")throw new Error("Assertion failed: Expected the subchild to have been registered");if("target"in N)p(`${C}/${I}`,N);else throw new Error("Assertion failed: Expected the leaf junction to be a package")}}}}};var Jdt={hooks:{cleanGlobalArtifacts:async t=>{let e=TY(t);await ce.removePromise(e)}},configuration:{nmHoistingLimits:{description:"Prevents packages to be hoisted past specific levels",type:"STRING",values:["workspaces","dependencies","none"],default:"none"},nmMode:{description:"Defines in which measure Yarn must use hardlinks and symlinks when generated `node_modules` directories.",type:"STRING",values:["classic","hardlinks-local","hardlinks-global"],default:"classic"},nmSelfReferences:{description:"Defines whether the linker should generate self-referencing symlinks for workspaces.",type:"BOOLEAN",default:!0}},linkers:[jD,GD]},Kdt=Jdt;var FK={};Vt(FK,{NpmHttpFetcher:()=>VD,NpmRemapResolver:()=>JD,NpmSemverFetcher:()=>oh,NpmSemverResolver:()=>KD,NpmTagResolver:()=>zD,default:()=>ubt,npmConfigUtils:()=>hi,npmHttpUtils:()=>en,npmPublishUtils:()=>v1});Ge();var $Be=ut(Ai());var oi="npm:";var en={};Vt(en,{AuthType:()=>zBe,customPackageError:()=>qm,del:()=>Amt,get:()=>Wm,getIdentUrl:()=>WD,getPackageMetadata:()=>Qw,handleInvalidAuthenticationError:()=>ag,post:()=>umt,put:()=>fmt});Ge();Ge();Dt();var LY=ut(Vv());ql();var KBe=ut(Ai());var hi={};Vt(hi,{RegistryType:()=>VBe,getAuditRegistry:()=>zdt,getAuthConfiguration:()=>OY,getDefaultRegistry:()=>qD,getPublishRegistry:()=>Xdt,getRegistryConfiguration:()=>JBe,getScopeConfiguration:()=>NY,getScopeRegistry:()=>Pw,isPackageApproved:()=>xw,normalizeRegistry:()=>Jc});Ge();var YBe=ut(Go()),VBe=(s=>(s.AUDIT_REGISTRY="npmAuditRegistry",s.FETCH_REGISTRY="npmRegistryServer",s.PUBLISH_REGISTRY="npmPublishRegistry",s))(VBe||{});function Jc(t){return t.replace(/\/$/,"")}function zdt({configuration:t}){return qD({configuration:t,type:"npmAuditRegistry"})}function Xdt(t,{configuration:e}){return t.publishConfig?.registry?Jc(t.publishConfig.registry):t.name?Pw(t.name.scope,{configuration:e,type:"npmPublishRegistry"}):qD({configuration:e,type:"npmPublishRegistry"})}function Pw(t,{configuration:e,type:r="npmRegistryServer"}){let s=NY(t,{configuration:e});if(s===null)return qD({configuration:e,type:r});let a=s.get(r);return a===null?qD({configuration:e,type:r}):Jc(a)}function qD({configuration:t,type:e="npmRegistryServer"}){let r=t.get(e);return Jc(r!==null?r:t.get("npmRegistryServer"))}function JBe(t,{configuration:e}){let r=e.get("npmRegistries"),s=Jc(t),a=r.get(s);if(typeof a<"u")return a;let n=r.get(s.replace(/^[a-z]+:/,""));return typeof n<"u"?n:null}var Zdt=new Map([["npmRegistryServer","https://npm.jsr.io/"]]);function NY(t,{configuration:e}){if(t===null)return null;let s=e.get("npmScopes").get(t);return s||(t==="jsr"?Zdt:null)}function OY(t,{configuration:e,ident:r}){let s=r&&NY(r.scope,{configuration:e});return s?.get("npmAuthIdent")||s?.get("npmAuthToken")?s:JBe(t,{configuration:e})||e}function $dt({configuration:t,version:e,publishTimes:r}){let s=t.get("npmMinimalAgeGate");if(s){let a=r?.[e];if(typeof a>"u"||(new Date().getTime()-new Date(a).getTime())/60/1e3emt(e,r,s))}function xw(t){return!$dt(t)||tmt(t)}var zBe=(a=>(a[a.NO_AUTH=0]="NO_AUTH",a[a.BEST_EFFORT=1]="BEST_EFFORT",a[a.CONFIGURATION=2]="CONFIGURATION",a[a.ALWAYS_AUTH=3]="ALWAYS_AUTH",a))(zBe||{});async function ag(t,{attemptedAs:e,registry:r,headers:s,configuration:a}){if(uN(t))throw new jt(41,"Invalid OTP token");if(t.originalError?.name==="HTTPError"&&t.originalError?.response.statusCode===401)throw new jt(41,`Invalid authentication (${typeof e!="string"?`as ${await hmt(r,s,{configuration:a})}`:`attempted as ${e}`})`)}function qm(t,e){let r=t.response?.statusCode;return r?r===404?"Package not found":r>=500&&r<600?`The registry appears to be down (using a ${he.applyHyperlink(e,"local cache","https://yarnpkg.com/advanced/lexicon#local-cache")} might have protected you against such outages)`:null:null}function WD(t){return t.scope?`/@${t.scope}%2f${t.name}`:`/${t.name}`}var XBe=new Map,rmt=new Map;async function nmt(t){return await je.getFactoryWithDefault(XBe,t,async()=>{let e=null;try{e=await ce.readJsonPromise(t)}catch{}return e})}async function imt(t,e,{configuration:r,cached:s,registry:a,headers:n,version:c,...f}){return await je.getFactoryWithDefault(rmt,t,async()=>await Wm(WD(e),{...f,customErrorMessage:qm,configuration:r,registry:a,ident:e,headers:{...n,"If-None-Match":s?.etag,"If-Modified-Since":s?.lastModified},wrapNetworkRequest:async p=>async()=>{let h=await p();if(h.statusCode===304){if(s===null)throw new Error("Assertion failed: cachedMetadata should not be null");return{...h,body:s.metadata}}let E=omt(JSON.parse(h.body.toString())),C={metadata:E,etag:h.headers.etag,lastModified:h.headers["last-modified"]};return XBe.set(t,Promise.resolve(C)),Promise.resolve().then(async()=>{let S=`${t}-${process.pid}.tmp`;await ce.mkdirPromise(J.dirname(S),{recursive:!0}),await ce.writeJsonPromise(S,C,{compact:!0}),await ce.renamePromise(S,t)}).catch(()=>{}),{...h,body:E}}}))}function smt(t){return t.scope!==null?`@${t.scope}-${t.name}-${t.scope.length}`:t.name}async function Qw(t,{cache:e,project:r,registry:s,headers:a,version:n,...c}){let{configuration:f}=r;s=YD(f,{ident:t,registry:s});let p=lmt(f,s),h=J.join(p,`${smt(t)}.json`),E=null;if(!r.lockfileNeedsRefresh&&(E=await nmt(h),E)){if(typeof n<"u"&&typeof E.metadata.versions[n]<"u")return E.metadata;if(f.get("enableOfflineMode")){let C=structuredClone(E.metadata),S=new Set;if(e){for(let I of Object.keys(C.versions)){let R=G.makeLocator(t,`npm:${I}`),N=e.getLocatorMirrorPath(R);(!N||!ce.existsSync(N))&&(delete C.versions[I],S.add(I))}let P=C["dist-tags"].latest;if(S.has(P)){let I=Object.keys(E.metadata.versions).sort(KBe.default.compare),R=I.indexOf(P);for(;S.has(I[R])&&R>=0;)R-=1;R>=0?C["dist-tags"].latest=I[R]:delete C["dist-tags"].latest}}return C}}return await imt(h,t,{...c,configuration:f,cached:E,registry:s,headers:a,version:n})}var ZBe=["name","dist.tarball","bin","scripts","os","cpu","libc","dependencies","dependenciesMeta","optionalDependencies","peerDependencies","peerDependenciesMeta","deprecated"];function omt(t){return{"dist-tags":t["dist-tags"],versions:Object.fromEntries(Object.entries(t.versions).map(([e,r])=>[e,Kd(r,ZBe)])),time:t.time}}var amt=Nn.makeHash("time",...ZBe).slice(0,6);function lmt(t,e){let r=cmt(t),s=new URL(e);return J.join(r,amt,s.hostname)}function cmt(t){return J.join(t.get("globalFolder"),"metadata/npm")}async function Wm(t,{configuration:e,headers:r,ident:s,authType:a,allowOidc:n,registry:c,...f}){c=YD(e,{ident:s,registry:c}),s&&s.scope&&typeof a>"u"&&(a=1);let p=await cN(c,{authType:a,allowOidc:n,configuration:e,ident:s});p&&(r={...r,authorization:p});try{return await nn.get(t.charAt(0)==="/"?`${c}${t}`:t,{configuration:e,headers:r,...f})}catch(h){throw await ag(h,{registry:c,configuration:e,headers:r}),h}}async function umt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,allowOidc:f,registry:p,otp:h,...E}){p=YD(s,{ident:n,registry:p});let C=await cN(p,{authType:c,allowOidc:f,configuration:s,ident:n});C&&(a={...a,authorization:C}),h&&(a={...a,...kw(h)});try{return await nn.post(p+t,e,{configuration:s,headers:a,...E})}catch(S){if(!uN(S)||h)throw await ag(S,{attemptedAs:r,registry:p,configuration:s,headers:a}),S;h=await MY(S,{configuration:s});let P={...a,...kw(h)};try{return await nn.post(`${p}${t}`,e,{configuration:s,headers:P,...E})}catch(I){throw await ag(I,{attemptedAs:r,registry:p,configuration:s,headers:a}),I}}}async function fmt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,allowOidc:f,registry:p,otp:h,...E}){p=YD(s,{ident:n,registry:p});let C=await cN(p,{authType:c,allowOidc:f,configuration:s,ident:n});C&&(a={...a,authorization:C}),h&&(a={...a,...kw(h)});try{return await nn.put(p+t,e,{configuration:s,headers:a,...E})}catch(S){if(!uN(S))throw await ag(S,{attemptedAs:r,registry:p,configuration:s,headers:a}),S;h=await MY(S,{configuration:s});let P={...a,...kw(h)};try{return await nn.put(`${p}${t}`,e,{configuration:s,headers:P,...E})}catch(I){throw await ag(I,{attemptedAs:r,registry:p,configuration:s,headers:a}),I}}}async function Amt(t,{attemptedAs:e,configuration:r,headers:s,ident:a,authType:n=3,allowOidc:c,registry:f,otp:p,...h}){f=YD(r,{ident:a,registry:f});let E=await cN(f,{authType:n,allowOidc:c,configuration:r,ident:a});E&&(s={...s,authorization:E}),p&&(s={...s,...kw(p)});try{return await nn.del(f+t,{configuration:r,headers:s,...h})}catch(C){if(!uN(C)||p)throw await ag(C,{attemptedAs:e,registry:f,configuration:r,headers:s}),C;p=await MY(C,{configuration:r});let S={...s,...kw(p)};try{return await nn.del(`${f}${t}`,{configuration:r,headers:S,...h})}catch(P){throw await ag(P,{attemptedAs:e,registry:f,configuration:r,headers:s}),P}}}function YD(t,{ident:e,registry:r}){if(typeof r>"u"&&e)return Pw(e.scope,{configuration:t});if(typeof r!="string")throw new Error("Assertion failed: The registry should be a string");return Jc(r)}async function cN(t,{authType:e=2,allowOidc:r=!1,configuration:s,ident:a}){let n=OY(t,{configuration:s,ident:a}),c=pmt(n,e);if(!c)return null;let f=await s.reduceHook(p=>p.getNpmAuthenticationHeader,void 0,t,{configuration:s,ident:a});if(f)return f;if(n.get("npmAuthToken"))return`Bearer ${n.get("npmAuthToken")}`;if(n.get("npmAuthIdent")){let p=n.get("npmAuthIdent");return p.includes(":")?`Basic ${Buffer.from(p).toString("base64")}`:`Basic ${p}`}if(r&&a){let p=await gmt(t,{configuration:s,ident:a});if(p)return`Bearer ${p}`}if(c&&e!==1)throw new jt(33,"No authentication configured for request");return null}function pmt(t,e){switch(e){case 2:return t.get("npmAlwaysAuth");case 1:case 3:return!0;case 0:return!1;default:throw new Error("Unreachable")}}async function hmt(t,e,{configuration:r}){if(typeof e>"u"||typeof e.authorization>"u")return"an anonymous user";try{return(await nn.get(new URL(`${t}/-/whoami`).href,{configuration:r,headers:e,jsonResponse:!0})).username??"an unknown user"}catch{return"an unknown user"}}async function MY(t,{configuration:e}){let r=t.originalError?.response.headers["npm-notice"];if(r&&(await Ot.start({configuration:e,stdout:process.stdout,includeFooter:!1},async a=>{if(a.reportInfo(0,r.replace(/(https?:\/\/\S+)/g,he.pretty(e,"$1",he.Type.URL))),!process.env.YARN_IS_TEST_ENV){let n=r.match(/open (https?:\/\/\S+)/i);if(n&&Ui.openUrl){let{openNow:c}=await(0,LY.prompt)({type:"confirm",name:"openNow",message:"Do you want to try to open this url now?",required:!0,initial:!0,onCancel:()=>process.exit(130)});c&&(await Ui.openUrl(n[1])||(a.reportSeparator(),a.reportWarning(0,"We failed to automatically open the url; you'll have to open it yourself in your browser of choice.")))}}}),process.stdout.write(` +`)),process.env.YARN_IS_TEST_ENV)return process.env.YARN_INJECT_NPM_2FA_TOKEN||"";let{otp:s}=await(0,LY.prompt)({type:"password",name:"otp",message:"One-time password:",required:!0,onCancel:()=>process.exit(130)});return process.stdout.write(` +`),s}function uN(t){if(t.originalError?.name!=="HTTPError")return!1;try{return(t.originalError?.response.headers["www-authenticate"].split(/,\s*/).map(r=>r.toLowerCase())).includes("otp")}catch{return!1}}function kw(t){return{"npm-otp":t}}async function gmt(t,{configuration:e,ident:r}){let s=null;if(process.env.GITLAB_CI)s=process.env.NPM_ID_TOKEN||null;else if(process.env.GITHUB_ACTIONS){if(!(process.env.ACTIONS_ID_TOKEN_REQUEST_URL&&process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN))return null;let a=`npm:${new URL(t).host.replace("registry.yarnpkg.com","registry.npmjs.org").replace("yarn.npmjs.org","registry.npmjs.org")}`,n=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);n.searchParams.append("audience",a),s=(await nn.get(n.href,{configuration:e,jsonResponse:!0,headers:{Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).value}if(!s)return null;try{return(await nn.post(`${t}/-/npm/v1/oidc/token/exchange/package${WD(r)}`,null,{configuration:e,jsonResponse:!0,headers:{Authorization:`Bearer ${s}`}})).token||null}catch{}return null}var VD=class{supports(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s,params:a}=G.parseRange(e.reference);return!(!$Be.default.valid(s)||a===null||typeof a.__archiveUrl!="string")}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let{params:s}=G.parseRange(e.reference);if(s===null||typeof s.__archiveUrl!="string")throw new Error("Assertion failed: The archiveUrl querystring parameter should have been available");let a=await Wm(s.__archiveUrl,{customErrorMessage:qm,configuration:r.project.configuration,ident:e});return await ps.convertToZip(a,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();var JD=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!G.tryParseDescriptor(e.range.slice(oi.length),!0))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){let s=r.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return r.resolver.getResolutionDependencies(s,r)}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return await s.resolver.getCandidates(a,r,s)}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return a.resolver.getSatisfying(n,r,s,a)}resolve(e,r){throw new Error("Unreachable")}};Ge();Ge();var eve=ut(Ai());var oh=class t{supports(e,r){if(!e.reference.startsWith(oi))return!1;let s=new URL(e.reference);return!(!eve.default.valid(s.pathname)||s.searchParams.has("__archiveUrl"))}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote registry`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s;try{s=await Wm(t.getLocatorUrl(e),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}catch{s=await Wm(t.getLocatorUrl(e).replace(/%2f/g,"/"),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}static isConventionalTarballUrl(e,r,{configuration:s}){let a=Pw(e.scope,{configuration:s}),n=t.getLocatorUrl(e);return r=r.replace(/^https?:(\/\/(?:[^/]+\.)?npmjs.org(?:$|\/))/,"https:$1"),a=a.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r=r.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r===a+n||r===a+n.replace(/%2f/g,"/")}static getLocatorUrl(e){let r=Fr.clean(e.reference.slice(oi.length));if(r===null)throw new jt(10,"The npm semver resolver got selected, but the version isn't semver");return`${WD(e)}/-/${e.name}-${r}.tgz`}};Ge();Ge();Ge();var UY=ut(Ai());var fN=G.makeIdent(null,"node-gyp"),dmt=/\b(node-gyp|prebuild-install)\b/,KD=class{supportsDescriptor(e,r){return e.range.startsWith(oi)?!!Fr.validRange(e.range.slice(oi.length)):!1}supportsLocator(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s}=G.parseRange(e.reference);return!!UY.default.valid(s)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=Fr.validRange(e.range.slice(oi.length));if(a===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);let n=await Qw(e,{cache:s.fetchOptions?.cache,project:s.project,version:UY.default.valid(a.raw)?a.raw:void 0}),c=je.mapAndFilter(Object.keys(n.versions),h=>{try{let E=new Fr.SemVer(h);if(a.test(E))return xw({configuration:s.project.configuration,ident:e,version:h,publishTimes:n.time})?E:je.mapAndFilter.skip}catch{}return je.mapAndFilter.skip}),f=c.filter(h=>!n.versions[h.raw].deprecated),p=f.length>0?f:c;return p.sort((h,E)=>-h.compare(E)),p.map(h=>{let E=G.makeLocator(e,`${oi}${h.raw}`),C=n.versions[h.raw].dist.tarball;return oh.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?E:G.bindLocator(E,{__archiveUrl:C})})}async getSatisfying(e,r,s,a){let n=Fr.validRange(e.range.slice(oi.length));if(n===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);return{locators:je.mapAndFilter(s,p=>{if(p.identHash!==e.identHash)return je.mapAndFilter.skip;let h=G.tryParseRange(p.reference,{requireProtocol:oi});if(!h)return je.mapAndFilter.skip;let E=new Fr.SemVer(h.selector);return n.test(E)?{locator:p,version:E}:je.mapAndFilter.skip}).sort((p,h)=>-p.version.compare(h.version)).map(({locator:p})=>p),sorted:!0}}async resolve(e,r){let{selector:s}=G.parseRange(e.reference),a=Fr.clean(s);if(a===null)throw new jt(10,"The npm semver resolver got selected, but the version isn't semver");let n=await Qw(e,{cache:r.fetchOptions?.cache,project:r.project,version:a});if(!Object.hasOwn(n,"versions"))throw new jt(15,'Registry returned invalid data for - missing "versions" field');if(!Object.hasOwn(n.versions,a))throw new jt(16,`Registry failed to return reference "${a}"`);let c=new Ut;if(c.load(n.versions[a]),!c.dependencies.has(fN.identHash)&&!c.peerDependencies.has(fN.identHash)){for(let f of c.scripts.values())if(f.match(dmt)){c.dependencies.set(fN.identHash,G.makeDescriptor(fN,"latest"));break}}return{...e,version:a,languageName:"node",linkType:"HARD",conditions:c.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(c.dependencies),peerDependencies:c.peerDependencies,dependenciesMeta:c.dependenciesMeta,peerDependenciesMeta:c.peerDependenciesMeta,bin:c.bin}}};Ge();Ge();var AN=ut(Ai());var zD=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!Mp.test(e.range.slice(oi.length)))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(oi.length),n=await Qw(e,{cache:s.fetchOptions?.cache,project:s.project});if(!Object.hasOwn(n,"dist-tags"))throw new jt(15,'Registry returned invalid data - missing "dist-tags" field');let c=n["dist-tags"];if(!Object.hasOwn(c,a))throw new jt(16,`Registry failed to return tag "${a}"`);let f=Object.keys(n.versions),p=n.time,h=c[a];if(a==="latest"&&!xw({configuration:s.project.configuration,ident:e,version:h,publishTimes:p})){let S=h.includes("-"),P=AN.default.rsort(f).find(I=>AN.default.lt(I,h)&&(S||!I.includes("-"))&&xw({configuration:s.project.configuration,ident:e,version:I,publishTimes:p}));if(!P)throw new jt(16,`The version for tag "${a}" is quarantined, and no lower version is available`);h=P}let E=G.makeLocator(e,`${oi}${h}`),C=n.versions[h].dist.tarball;return oh.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?[E]:[G.bindLocator(E,{__archiveUrl:C})]}async getSatisfying(e,r,s,a){let n=[];for(let c of s){if(c.identHash!==e.identHash)continue;let f=G.tryParseRange(c.reference,{requireProtocol:oi});if(!(!f||!AN.default.valid(f.selector))){if(f.params?.__archiveUrl){let p=G.makeRange({protocol:oi,selector:f.selector,source:null,params:null}),[h]=await a.resolver.getCandidates(G.makeDescriptor(e,p),r,a);if(c.reference!==h.reference)continue}n.push(c)}}return{locators:n,sorted:!1}}async resolve(e,r){throw new Error("Unreachable")}};var v1={};Vt(v1,{getGitHead:()=>abt,getPublishAccess:()=>qxe,getReadmeContent:()=>Wxe,makePublishBody:()=>obt});Ge();Ge();Dt();var bV={};Vt(bV,{PackCommand:()=>jw,default:()=>KEt,packUtils:()=>yA});Ge();Ge();Ge();Dt();Yt();var yA={};Vt(yA,{genPackList:()=>NN,genPackStream:()=>DV,genPackageManifest:()=>QSe,hasPackScripts:()=>vV,prepareForPack:()=>SV});Ge();Dt();var BV=ut(Go()),xSe=ut(SSe()),kSe=Ie("zlib"),MEt=["/package.json","/readme","/readme.*","/license","/license.*","/licence","/licence.*","/changelog","/changelog.*"],UEt=["/package.tgz",".github",".git",".hg","node_modules",".npmignore",".gitignore",".#*",".DS_Store"];async function vV(t){return!!(In.hasWorkspaceScript(t,"prepack")||In.hasWorkspaceScript(t,"postpack"))}async function SV(t,{report:e},r){await In.maybeExecuteWorkspaceLifecycleScript(t,"prepack",{report:e});try{let s=J.join(t.cwd,Ut.fileName);await ce.existsPromise(s)&&await t.manifest.loadFile(s,{baseFs:ce}),await r()}finally{await In.maybeExecuteWorkspaceLifecycleScript(t,"postpack",{report:e})}}async function DV(t,e){typeof e>"u"&&(e=await NN(t));let r=new Set;for(let n of t.manifest.publishConfig?.executableFiles??new Set)r.add(J.normalize(n));for(let n of t.manifest.bin.values())r.add(J.normalize(n));let s=xSe.default.pack();process.nextTick(async()=>{for(let n of e){let c=J.normalize(n),f=J.resolve(t.cwd,c),p=J.join("package",c),h=await ce.lstatPromise(f),E={name:p,mtime:new Date(fi.SAFE_TIME*1e3)},C=r.has(c)?493:420,S,P,I=new Promise((N,U)=>{S=N,P=U}),R=N=>{N?P(N):S()};if(h.isFile()){let N;c==="package.json"?N=Buffer.from(JSON.stringify(await QSe(t),null,2)):N=await ce.readFilePromise(f),s.entry({...E,mode:C,type:"file"},N,R)}else h.isSymbolicLink()?s.entry({...E,mode:C,type:"symlink",linkname:await ce.readlinkPromise(f)},R):R(new Error(`Unsupported file type ${h.mode} for ${fe.fromPortablePath(c)}`));await I}s.finalize()});let a=(0,kSe.createGzip)();return s.pipe(a),a}async function QSe(t){let e=JSON.parse(JSON.stringify(t.manifest.raw));return await t.project.configuration.triggerHook(r=>r.beforeWorkspacePacking,t,e),e}async function NN(t){let e=t.project,r=e.configuration,s={accept:[],reject:[]};for(let C of UEt)s.reject.push(C);for(let C of MEt)s.accept.push(C);s.reject.push(r.get("rcFilename"));let a=C=>{if(C===null||!C.startsWith(`${t.cwd}/`))return;let S=J.relative(t.cwd,C),P=J.resolve(vt.root,S);s.reject.push(P)};a(J.resolve(e.cwd,Er.lockfile)),a(r.get("cacheFolder")),a(r.get("globalFolder")),a(r.get("installStatePath")),a(r.get("virtualFolder")),a(r.get("yarnPath")),await r.triggerHook(C=>C.populateYarnPaths,e,C=>{a(C)});for(let C of e.workspaces){let S=J.relative(t.cwd,C.cwd);S!==""&&!S.match(/^(\.\.)?\//)&&s.reject.push(`/${S}`)}let n={accept:[],reject:[]},c=t.manifest.publishConfig?.main??t.manifest.main,f=t.manifest.publishConfig?.module??t.manifest.module,p=t.manifest.publishConfig?.browser??t.manifest.browser,h=t.manifest.publishConfig?.bin??t.manifest.bin;c!=null&&n.accept.push(J.resolve(vt.root,c)),f!=null&&n.accept.push(J.resolve(vt.root,f)),typeof p=="string"&&n.accept.push(J.resolve(vt.root,p));for(let C of h.values())n.accept.push(J.resolve(vt.root,C));if(p instanceof Map)for(let[C,S]of p.entries())n.accept.push(J.resolve(vt.root,C)),typeof S=="string"&&n.accept.push(J.resolve(vt.root,S));let E=t.manifest.files!==null;if(E){n.reject.push("/*");for(let C of t.manifest.files)TSe(n.accept,C,{cwd:vt.root})}return await _Et(t.cwd,{hasExplicitFileList:E,globalList:s,ignoreList:n})}async function _Et(t,{hasExplicitFileList:e,globalList:r,ignoreList:s}){let a=[],n=new Hf(t),c=[[vt.root,[s]]];for(;c.length>0;){let[f,p]=c.pop(),h=await n.lstatPromise(f);if(!bSe(f,{globalList:r,ignoreLists:h.isDirectory()?null:p}))if(h.isDirectory()){let E=await n.readdirPromise(f),C=!1,S=!1;if(!e||f!==vt.root)for(let R of E)C=C||R===".gitignore",S=S||R===".npmignore";let P=S?await DSe(n,f,".npmignore"):C?await DSe(n,f,".gitignore"):null,I=P!==null?[P].concat(p):p;bSe(f,{globalList:r,ignoreLists:p})&&(I=[...p,{accept:[],reject:["**/*"]}]);for(let R of E)c.push([J.resolve(f,R),I])}else(h.isFile()||h.isSymbolicLink())&&a.push(J.relative(vt.root,f))}return a.sort()}async function DSe(t,e,r){let s={accept:[],reject:[]},a=await t.readFilePromise(J.join(e,r),"utf8");for(let n of a.split(/\n/g))TSe(s.reject,n,{cwd:e});return s}function HEt(t,{cwd:e}){let r=t[0]==="!";return r&&(t=t.slice(1)),t.match(/\.{0,1}\//)&&(t=J.resolve(e,t)),r&&(t=`!${t}`),t}function TSe(t,e,{cwd:r}){let s=e.trim();s===""||s[0]==="#"||t.push(HEt(s,{cwd:r}))}function bSe(t,{globalList:e,ignoreLists:r}){let s=FN(t,e.accept);if(s!==0)return s===2;let a=FN(t,e.reject);if(a!==0)return a===1;if(r!==null)for(let n of r){let c=FN(t,n.accept);if(c!==0)return c===2;let f=FN(t,n.reject);if(f!==0)return f===1}return!1}function FN(t,e){let r=e,s=[];for(let a=0;a{await SV(a,{report:p},async()=>{p.reportJson({base:fe.fromPortablePath(a.cwd)});let h=await NN(a);for(let E of h)p.reportInfo(null,fe.fromPortablePath(E)),p.reportJson({location:fe.fromPortablePath(E)});if(!this.dryRun){let E=await DV(a,h);await ce.mkdirPromise(J.dirname(c),{recursive:!0});let C=ce.createWriteStream(c);E.pipe(C),await new Promise(S=>{C.on("finish",S)})}}),this.dryRun||(p.reportInfo(0,`Package archive generated in ${he.pretty(r,c,he.Type.PATH)}`),p.reportJson({output:fe.fromPortablePath(c)}))})).exitCode()}};function jEt(t,{workspace:e}){let r=t.replace("%s",GEt(e)).replace("%v",qEt(e));return fe.toPortablePath(r)}function GEt(t){return t.manifest.name!==null?G.slugifyIdent(t.manifest.name):"package"}function qEt(t){return t.manifest.version!==null?t.manifest.version:"unknown"}var WEt=["dependencies","devDependencies","peerDependencies"],YEt="workspace:",VEt=(t,e)=>{e.publishConfig&&(e.publishConfig.type&&(e.type=e.publishConfig.type),e.publishConfig.main&&(e.main=e.publishConfig.main),e.publishConfig.browser&&(e.browser=e.publishConfig.browser),e.publishConfig.module&&(e.module=e.publishConfig.module),e.publishConfig.exports&&(e.exports=e.publishConfig.exports),e.publishConfig.imports&&(e.imports=e.publishConfig.imports),e.publishConfig.bin&&(e.bin=e.publishConfig.bin));let r=t.project;for(let s of WEt)for(let a of t.manifest.getForScope(s).values()){let n=r.tryWorkspaceByDescriptor(a),c=G.parseRange(a.range);if(c.protocol===YEt)if(n===null){if(r.tryWorkspaceByIdent(a)===null)throw new jt(21,`${G.prettyDescriptor(r.configuration,a)}: No local workspace found for this range`)}else{let f;G.areDescriptorsEqual(a,n.anchoredDescriptor)||c.selector==="*"?f=n.manifest.version??"0.0.0":c.selector==="~"||c.selector==="^"?f=`${c.selector}${n.manifest.version??"0.0.0"}`:f=c.selector;let p=s==="dependencies"?G.makeDescriptor(a,"unknown"):null,h=p!==null&&t.manifest.ensureDependencyMeta(p).optional?"optionalDependencies":s;e[h][G.stringifyIdent(a)]=f}}},JEt={hooks:{beforeWorkspacePacking:VEt},commands:[jw]},KEt=JEt;var Gxe=ut(HSe());Ge();var Hxe=ut(_xe()),{env:Bt}=process,XDt="application/vnd.in-toto+json",ZDt="https://in-toto.io/Statement/v0.1",$Dt="https://in-toto.io/Statement/v1",ebt="https://slsa.dev/provenance/v0.2",tbt="https://slsa.dev/provenance/v1",rbt="https://github.com/actions/runner",nbt="https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",ibt="https://github.com/npm/cli/gitlab",sbt="v0alpha1",jxe=async(t,e)=>{let r;if(Bt.GITHUB_ACTIONS){if(!Bt.ACTIONS_ID_TOKEN_REQUEST_URL)throw new jt(91,'Provenance generation in GitHub Actions requires "write" access to the "id-token" permission');let s=(Bt.GITHUB_WORKFLOW_REF||"").replace(`${Bt.GITHUB_REPOSITORY}/`,""),a=s.indexOf("@"),n=s.slice(0,a),c=s.slice(a+1);r={_type:$Dt,subject:t,predicateType:tbt,predicate:{buildDefinition:{buildType:nbt,externalParameters:{workflow:{ref:c,repository:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}`,path:n}},internalParameters:{github:{event_name:Bt.GITHUB_EVENT_NAME,repository_id:Bt.GITHUB_REPOSITORY_ID,repository_owner_id:Bt.GITHUB_REPOSITORY_OWNER_ID}},resolvedDependencies:[{uri:`git+${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}@${Bt.GITHUB_REF}`,digest:{gitCommit:Bt.GITHUB_SHA}}]},runDetails:{builder:{id:`${rbt}/${Bt.RUNNER_ENVIRONMENT}`},metadata:{invocationId:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}/actions/runs/${Bt.GITHUB_RUN_ID}/attempts/${Bt.GITHUB_RUN_ATTEMPT}`}}}}}else if(Bt.GITLAB_CI){if(!Bt.SIGSTORE_ID_TOKEN)throw new jt(91,`Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see: +https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html`);r={_type:ZDt,subject:t,predicateType:ebt,predicate:{buildType:`${ibt}/${sbt}`,builder:{id:`${Bt.CI_PROJECT_URL}/-/runners/${Bt.CI_RUNNER_ID}`},invocation:{configSource:{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA},entryPoint:Bt.CI_JOB_NAME},parameters:{CI:Bt.CI,CI_API_GRAPHQL_URL:Bt.CI_API_GRAPHQL_URL,CI_API_V4_URL:Bt.CI_API_V4_URL,CI_BUILD_BEFORE_SHA:Bt.CI_BUILD_BEFORE_SHA,CI_BUILD_ID:Bt.CI_BUILD_ID,CI_BUILD_NAME:Bt.CI_BUILD_NAME,CI_BUILD_REF:Bt.CI_BUILD_REF,CI_BUILD_REF_NAME:Bt.CI_BUILD_REF_NAME,CI_BUILD_REF_SLUG:Bt.CI_BUILD_REF_SLUG,CI_BUILD_STAGE:Bt.CI_BUILD_STAGE,CI_COMMIT_BEFORE_SHA:Bt.CI_COMMIT_BEFORE_SHA,CI_COMMIT_BRANCH:Bt.CI_COMMIT_BRANCH,CI_COMMIT_REF_NAME:Bt.CI_COMMIT_REF_NAME,CI_COMMIT_REF_PROTECTED:Bt.CI_COMMIT_REF_PROTECTED,CI_COMMIT_REF_SLUG:Bt.CI_COMMIT_REF_SLUG,CI_COMMIT_SHA:Bt.CI_COMMIT_SHA,CI_COMMIT_SHORT_SHA:Bt.CI_COMMIT_SHORT_SHA,CI_COMMIT_TIMESTAMP:Bt.CI_COMMIT_TIMESTAMP,CI_COMMIT_TITLE:Bt.CI_COMMIT_TITLE,CI_CONFIG_PATH:Bt.CI_CONFIG_PATH,CI_DEFAULT_BRANCH:Bt.CI_DEFAULT_BRANCH,CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_SERVER:Bt.CI_DEPENDENCY_PROXY_SERVER,CI_DEPENDENCY_PROXY_USER:Bt.CI_DEPENDENCY_PROXY_USER,CI_JOB_ID:Bt.CI_JOB_ID,CI_JOB_NAME:Bt.CI_JOB_NAME,CI_JOB_NAME_SLUG:Bt.CI_JOB_NAME_SLUG,CI_JOB_STAGE:Bt.CI_JOB_STAGE,CI_JOB_STARTED_AT:Bt.CI_JOB_STARTED_AT,CI_JOB_URL:Bt.CI_JOB_URL,CI_NODE_TOTAL:Bt.CI_NODE_TOTAL,CI_PAGES_DOMAIN:Bt.CI_PAGES_DOMAIN,CI_PAGES_URL:Bt.CI_PAGES_URL,CI_PIPELINE_CREATED_AT:Bt.CI_PIPELINE_CREATED_AT,CI_PIPELINE_ID:Bt.CI_PIPELINE_ID,CI_PIPELINE_IID:Bt.CI_PIPELINE_IID,CI_PIPELINE_SOURCE:Bt.CI_PIPELINE_SOURCE,CI_PIPELINE_URL:Bt.CI_PIPELINE_URL,CI_PROJECT_CLASSIFICATION_LABEL:Bt.CI_PROJECT_CLASSIFICATION_LABEL,CI_PROJECT_DESCRIPTION:Bt.CI_PROJECT_DESCRIPTION,CI_PROJECT_ID:Bt.CI_PROJECT_ID,CI_PROJECT_NAME:Bt.CI_PROJECT_NAME,CI_PROJECT_NAMESPACE:Bt.CI_PROJECT_NAMESPACE,CI_PROJECT_NAMESPACE_ID:Bt.CI_PROJECT_NAMESPACE_ID,CI_PROJECT_PATH:Bt.CI_PROJECT_PATH,CI_PROJECT_PATH_SLUG:Bt.CI_PROJECT_PATH_SLUG,CI_PROJECT_REPOSITORY_LANGUAGES:Bt.CI_PROJECT_REPOSITORY_LANGUAGES,CI_PROJECT_ROOT_NAMESPACE:Bt.CI_PROJECT_ROOT_NAMESPACE,CI_PROJECT_TITLE:Bt.CI_PROJECT_TITLE,CI_PROJECT_URL:Bt.CI_PROJECT_URL,CI_PROJECT_VISIBILITY:Bt.CI_PROJECT_VISIBILITY,CI_REGISTRY:Bt.CI_REGISTRY,CI_REGISTRY_IMAGE:Bt.CI_REGISTRY_IMAGE,CI_REGISTRY_USER:Bt.CI_REGISTRY_USER,CI_RUNNER_DESCRIPTION:Bt.CI_RUNNER_DESCRIPTION,CI_RUNNER_ID:Bt.CI_RUNNER_ID,CI_RUNNER_TAGS:Bt.CI_RUNNER_TAGS,CI_SERVER_HOST:Bt.CI_SERVER_HOST,CI_SERVER_NAME:Bt.CI_SERVER_NAME,CI_SERVER_PORT:Bt.CI_SERVER_PORT,CI_SERVER_PROTOCOL:Bt.CI_SERVER_PROTOCOL,CI_SERVER_REVISION:Bt.CI_SERVER_REVISION,CI_SERVER_SHELL_SSH_HOST:Bt.CI_SERVER_SHELL_SSH_HOST,CI_SERVER_SHELL_SSH_PORT:Bt.CI_SERVER_SHELL_SSH_PORT,CI_SERVER_URL:Bt.CI_SERVER_URL,CI_SERVER_VERSION:Bt.CI_SERVER_VERSION,CI_SERVER_VERSION_MAJOR:Bt.CI_SERVER_VERSION_MAJOR,CI_SERVER_VERSION_MINOR:Bt.CI_SERVER_VERSION_MINOR,CI_SERVER_VERSION_PATCH:Bt.CI_SERVER_VERSION_PATCH,CI_TEMPLATE_REGISTRY_HOST:Bt.CI_TEMPLATE_REGISTRY_HOST,GITLAB_CI:Bt.GITLAB_CI,GITLAB_FEATURES:Bt.GITLAB_FEATURES,GITLAB_USER_ID:Bt.GITLAB_USER_ID,GITLAB_USER_LOGIN:Bt.GITLAB_USER_LOGIN,RUNNER_GENERATE_ARTIFACTS_METADATA:Bt.RUNNER_GENERATE_ARTIFACTS_METADATA},environment:{name:Bt.CI_RUNNER_DESCRIPTION,architecture:Bt.CI_RUNNER_EXECUTABLE_ARCH,server:Bt.CI_SERVER_URL,project:Bt.CI_PROJECT_PATH,job:{id:Bt.CI_JOB_ID},pipeline:{id:Bt.CI_PIPELINE_ID,ref:Bt.CI_CONFIG_PATH}}},metadata:{buildInvocationId:`${Bt.CI_JOB_URL}`,completeness:{parameters:!0,environment:!0,materials:!1},reproducible:!1},materials:[{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA}}]}}}else throw new jt(91,"Provenance generation is only supported in GitHub Actions and GitLab CI");return Hxe.attest(Buffer.from(JSON.stringify(r)),XDt,e)};async function obt(t,e,{access:r,tag:s,registry:a,gitHead:n,provenance:c}){let f=t.manifest.name,p=t.manifest.version,h=G.stringifyIdent(f),E=Gxe.default.fromData(e,{algorithms:["sha1","sha512"]}),C=r??qxe(t,f),S=await Wxe(t),P=await yA.genPackageManifest(t),I=`${h}-${p}.tgz`,R=new URL(`${Jc(a)}/${h}/-/${I}`),N={[I]:{content_type:"application/octet-stream",data:e.toString("base64"),length:e.length}};if(c){let U={name:`pkg:npm/${h.replace(/^@/,"%40")}@${p}`,digest:{sha512:E.sha512[0].hexDigest()}},W=await jxe([U]),ee=JSON.stringify(W);N[`${h}-${p}.sigstore`]={content_type:W.mediaType,data:ee,length:ee.length}}return{_id:h,_attachments:N,name:h,access:C,"dist-tags":{[s]:p},versions:{[p]:{...P,_id:`${h}@${p}`,name:h,version:p,gitHead:n,dist:{shasum:E.sha1[0].hexDigest(),integrity:E.sha512[0].toString(),tarball:R.toString()}}},readme:S}}async function abt(t){try{let{stdout:e}=await qr.execvp("git",["rev-parse","--revs-only","HEAD"],{cwd:t});return e.trim()===""?void 0:e.trim()}catch{return}}function qxe(t,e){let r=t.project.configuration;return t.manifest.publishConfig&&typeof t.manifest.publishConfig.access=="string"?t.manifest.publishConfig.access:r.get("npmPublishAccess")!==null?r.get("npmPublishAccess"):e.scope?"restricted":"public"}async function Wxe(t){let e=fe.toPortablePath(`${t.cwd}/README.md`),r=t.manifest.name,a=`# ${G.stringifyIdent(r)} +`;try{a=await ce.readFilePromise(e,"utf8")}catch(n){if(n.code==="ENOENT")return a;throw n}return a}var RK={npmAlwaysAuth:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"BOOLEAN",default:!1},npmAuthIdent:{description:"Authentication identity for the npm registry (_auth in npm and yarn v1)",type:"SECRET",default:null},npmAuthToken:{description:"Authentication token for the npm registry (_authToken in npm and yarn v1)",type:"SECRET",default:null}},Yxe={npmAuditRegistry:{description:"Registry to query for audit reports",type:"STRING",default:null},npmPublishRegistry:{description:"Registry to push packages to",type:"STRING",default:null},npmRegistryServer:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"STRING",default:"https://registry.yarnpkg.com"}},lbt={npmMinimalAgeGate:{description:"Minimum age of a package version according to the publish date on the npm registry to be considered for installation",type:"DURATION",unit:"m",default:"0m"},npmPreapprovedPackages:{description:"Array of package descriptors or package name glob patterns to exclude from the minimum release age check",type:"STRING",isArray:!0,default:[]}},cbt={configuration:{...RK,...Yxe,...lbt,npmScopes:{description:"Settings per package scope",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{...RK,...Yxe}}},npmRegistries:{description:"Settings per registry",type:"MAP",normalizeKeys:Jc,valueDefinition:{description:"",type:"SHAPE",properties:{...RK}}}},fetchers:[VD,oh],resolvers:[JD,KD,zD]},ubt=cbt;var qK={};Vt(qK,{NpmAuditCommand:()=>D1,NpmInfoCommand:()=>b1,NpmLoginCommand:()=>P1,NpmLogoutCommand:()=>k1,NpmPublishCommand:()=>Q1,NpmTagAddCommand:()=>R1,NpmTagListCommand:()=>T1,NpmTagRemoveCommand:()=>F1,NpmWhoamiCommand:()=>N1,default:()=>wbt,npmAuditTypes:()=>zb,npmAuditUtils:()=>kL});Ge();Ge();Yt();var UK=ut(Go());Ul();var zb={};Vt(zb,{Environment:()=>Jb,Severity:()=>Kb});var Jb=(s=>(s.All="all",s.Production="production",s.Development="development",s))(Jb||{}),Kb=(n=>(n.Info="info",n.Low="low",n.Moderate="moderate",n.High="high",n.Critical="critical",n))(Kb||{});var kL={};Vt(kL,{allSeverities:()=>S1,getPackages:()=>MK,getReportTree:()=>OK,getSeverityInclusions:()=>NK,getTopLevelDependencies:()=>LK});Ge();var Vxe=ut(Ai());var S1=["info","low","moderate","high","critical"];function NK(t){if(typeof t>"u")return new Set(S1);let e=S1.indexOf(t),r=S1.slice(e);return new Set(r)}function OK(t){let e={},r={children:e};for(let[s,a]of je.sortMap(Object.entries(t),n=>n[0]))for(let n of je.sortMap(a,c=>`${c.id}`))e[`${s}/${n.id}`]={value:he.tuple(he.Type.IDENT,G.parseIdent(s)),children:{ID:typeof n.id<"u"&&{label:"ID",value:he.tuple(he.Type.ID,n.id)},Issue:{label:"Issue",value:he.tuple(he.Type.NO_HINT,n.title)},URL:typeof n.url<"u"&&{label:"URL",value:he.tuple(he.Type.URL,n.url)},Severity:{label:"Severity",value:he.tuple(he.Type.NO_HINT,n.severity)},"Vulnerable Versions":{label:"Vulnerable Versions",value:he.tuple(he.Type.RANGE,n.vulnerable_versions)},"Tree Versions":{label:"Tree Versions",children:[...n.versions].sort(Vxe.default.compare).map(c=>({value:he.tuple(he.Type.REFERENCE,c)}))},Dependents:{label:"Dependents",children:je.sortMap(n.dependents,c=>G.stringifyLocator(c)).map(c=>({value:he.tuple(he.Type.LOCATOR,c)}))}}};return r}function LK(t,e,{all:r,environment:s}){let a=[],n=r?t.workspaces:[e],c=["all","production"].includes(s),f=["all","development"].includes(s);for(let p of n)for(let h of p.anchoredPackage.dependencies.values())(p.manifest.devDependencies.has(h.identHash)?!f:!c)||a.push({workspace:p,dependency:h});return a}function MK(t,e,{recursive:r}){let s=new Map,a=new Set,n=[],c=(f,p)=>{let h=t.storedResolutions.get(p.descriptorHash);if(typeof h>"u")throw new Error("Assertion failed: The resolution should have been registered");if(!a.has(h))a.add(h);else return;let E=t.storedPackages.get(h);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");if(G.ensureDevirtualizedLocator(E).reference.startsWith("npm:")&&E.version!==null){let S=G.stringifyIdent(E),P=je.getMapWithDefault(s,S);je.getArrayWithDefault(P,E.version).push(f)}if(r)for(let S of E.dependencies.values())n.push([E,S])};for(let{workspace:f,dependency:p}of e)n.push([f.anchoredLocator,p]);for(;n.length>0;){let[f,p]=n.shift();c(f,p)}return s}var D1=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Audit dependencies from all workspaces"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Audit transitive dependencies as well"});this.environment=ge.String("--environment","all",{description:"Which environments to cover",validator:fo(Jb)});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.noDeprecations=ge.Boolean("--no-deprecations",!1,{description:"Don't warn about deprecated packages"});this.severity=ge.String("--severity","info",{description:"Minimal severity requested for packages to be displayed",validator:fo(Kb)});this.excludes=ge.Array("--exclude",[],{description:"Array of glob patterns of packages to exclude from audit"});this.ignores=ge.Array("--ignore",[],{description:"Array of glob patterns of advisory ID's to ignore in the audit report"})}static{this.paths=[["npm","audit"]]}static{this.usage=ot.Usage({description:"perform a vulnerability audit against the installed packages",details:` + This command checks for known security reports on the packages you use. The reports are by default extracted from the npm registry, and may or may not be relevant to your actual program (not all vulnerabilities affect all code paths). + + For consistency with our other commands the default is to only check the direct dependencies for the active workspace. To extend this search to all workspaces, use \`-A,--all\`. To extend this search to both direct and transitive dependencies, use \`-R,--recursive\`. + + Applying the \`--severity\` flag will limit the audit table to vulnerabilities of the corresponding severity and above. Valid values are ${S1.map(r=>`\`${r}\``).join(", ")}. + + If the \`--json\` flag is set, Yarn will print the output exactly as received from the registry. Regardless of this flag, the process will exit with a non-zero exit code if a report is found for the selected packages. + + If certain packages produce false positives for a particular environment, the \`--exclude\` flag can be used to exclude any number of packages from the audit. This can also be set in the configuration file with the \`npmAuditExcludePackages\` option. + + If particular advisories are needed to be ignored, the \`--ignore\` flag can be used with Advisory ID's to ignore any number of advisories in the audit report. This can also be set in the configuration file with the \`npmAuditIgnoreAdvisories\` option. + + To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why package\` to get more information as to who depends on them. + `,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"],["Exclude certain packages","yarn npm audit --exclude package1 --exclude package2"],["Ignore specific advisories","yarn npm audit --ignore 1234567 --ignore 7654321"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=LK(s,a,{all:this.all,environment:this.environment}),c=MK(s,n,{recursive:this.recursive}),f=Array.from(new Set([...r.get("npmAuditExcludePackages"),...this.excludes])),p=Object.create(null);for(let[N,U]of c)f.some(W=>UK.default.isMatch(N,W))||(p[N]=[...U.keys()]);let h=hi.getAuditRegistry({configuration:r}),E,C=await lA.start({configuration:r,stdout:this.context.stdout},async()=>{let N=en.post("/-/npm/v1/security/advisories/bulk",p,{authType:en.AuthType.BEST_EFFORT,configuration:r,jsonResponse:!0,registry:h}),U=this.noDeprecations?[]:await Promise.all(Array.from(Object.entries(p),async([ee,ie])=>{let ue=await en.getPackageMetadata(G.parseIdent(ee),{project:s});return je.mapAndFilter(ie,le=>{let{deprecated:me}=ue.versions[le];return me?[ee,le,me]:je.mapAndFilter.skip})})),W=await N;for(let[ee,ie,ue]of U.flat(1))Object.hasOwn(W,ee)&&W[ee].some(le=>Fr.satisfiesWithPrereleases(ie,le.vulnerable_versions))||(W[ee]??=[],W[ee].push({id:`${ee} (deprecation)`,title:(typeof ue=="string"?ue:"").trim()||"This package has been deprecated.",severity:"moderate",vulnerable_versions:ie}));E=W});if(C.hasErrors())return C.exitCode();let S=NK(this.severity),P=Array.from(new Set([...r.get("npmAuditIgnoreAdvisories"),...this.ignores])),I=Object.create(null);for(let[N,U]of Object.entries(E)){let W=U.filter(ee=>!UK.default.isMatch(`${ee.id}`,P)&&S.has(ee.severity));W.length>0&&(I[N]=W.map(ee=>{let ie=c.get(N);if(typeof ie>"u")throw new Error("Assertion failed: Expected the registry to only return packages that were requested");let ue=[...ie.keys()].filter(me=>Fr.satisfiesWithPrereleases(me,ee.vulnerable_versions)),le=new Map;for(let me of ue)for(let pe of ie.get(me))le.set(pe.locatorHash,pe);return{...ee,versions:ue,dependents:[...le.values()]}}))}let R=Object.keys(I).length>0;return R?(xs.emitTree(OK(I),{configuration:r,json:this.json,stdout:this.context.stdout,separators:2}),1):(await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async N=>{N.reportInfo(1,"No audit suggestions")}),R?1:0)}};Ge();Ge();Dt();Yt();var _K=ut(Ai()),HK=Ie("util"),b1=class extends ft{constructor(){super(...arguments);this.fields=ge.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.packages=ge.Rest()}static{this.paths=[["npm","info"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command fetches information about a package from the npm registry and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react@16.12.0","yarn npm info react@16.12.0"],["Show all available information about react@next","yarn npm info react@next"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.fields<"u"?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,n=[],c=!1,f=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async p=>{for(let h of this.packages){let E;if(h==="."){let ie=s.topLevelWorkspace;if(!ie.manifest.name)throw new nt(`Missing ${he.pretty(r,"name",he.Type.CODE)} field in ${fe.fromPortablePath(J.join(ie.cwd,Er.manifest))}`);E=G.makeDescriptor(ie.manifest.name,"unknown")}else E=G.parseDescriptor(h);let C=en.getIdentUrl(E),S=jK(await en.get(C,{configuration:r,ident:E,jsonResponse:!0,customErrorMessage:en.customPackageError})),P=Object.keys(S.versions).sort(_K.default.compareLoose),R=S["dist-tags"].latest||P[P.length-1],N=Fr.validRange(E.range);if(N){let ie=_K.default.maxSatisfying(P,N);ie!==null?R=ie:(p.reportWarning(0,`Unmet range ${G.prettyRange(r,E.range)}; falling back to the latest version`),c=!0)}else Object.hasOwn(S["dist-tags"],E.range)?R=S["dist-tags"][E.range]:E.range!=="unknown"&&(p.reportWarning(0,`Unknown tag ${G.prettyRange(r,E.range)}; falling back to the latest version`),c=!0);let U=S.versions[R],W={...S,...U,version:R,versions:P},ee;if(a!==null){ee={};for(let ie of a){let ue=W[ie];if(typeof ue<"u")ee[ie]=ue;else{p.reportWarning(1,`The ${he.pretty(r,ie,he.Type.CODE)} field doesn't exist inside ${G.prettyIdent(r,E)}'s information`),c=!0;continue}}}else this.json||(delete W.dist,delete W.readme,delete W.users),ee=W;p.reportJson(ee),this.json||n.push(ee)}});HK.inspect.styles.name="cyan";for(let p of n)(p!==n[0]||c)&&this.context.stdout.write(` +`),this.context.stdout.write(`${(0,HK.inspect)(p,{depth:1/0,colors:!0,compact:!1})} +`);return f.exitCode()}};function jK(t){if(Array.isArray(t)){let e=[];for(let r of t)r=jK(r),r&&e.push(r);return e}else if(typeof t=="object"&&t!==null){let e={};for(let r of Object.keys(t)){if(r.startsWith("_"))continue;let s=jK(t[r]);s&&(e[r]=s)}return e}else return t||null}Ge();Ge();Yt();var GK=ut(Vv()),P1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Login to the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Login to the publish registry"});this.alwaysAuth=ge.Boolean("--always-auth",{description:"Set the npmAlwaysAuth configuration"});this.webLogin=ge.Boolean("--web-login",{description:"Enable web login"})}static{this.paths=[["npm","login"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --scope my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ot.start({configuration:r,stdout:this.context.stdout,includeFooter:!1},async n=>{let c=await gbt({registry:s,configuration:r,report:n,webLogin:this.webLogin,stdin:this.context.stdin,stdout:this.context.stdout});return await mbt(s,c,{alwaysAuth:this.alwaysAuth,scope:this.scope}),n.reportInfo(0,"Successfully logged in")})).exitCode()}};async function QL({scope:t,publish:e,configuration:r,cwd:s}){return t&&e?hi.getScopeRegistry(t,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):t?hi.getScopeRegistry(t,{configuration:r}):e?hi.getPublishRegistry((await eC(r,s)).manifest,{configuration:r}):hi.getDefaultRegistry({configuration:r})}async function fbt(t,e){let r;try{r=await en.post("/-/v1/login",null,{configuration:e,registry:t,authType:en.AuthType.NO_AUTH,jsonResponse:!0,headers:{"npm-auth-type":"web"}})}catch{return null}return r}async function Abt(t,e){let r=await nn.request(t,null,{configuration:e,jsonResponse:!0});if(r.statusCode===202){let s=r.headers["retry-after"]??"1";return{type:"waiting",sleep:parseInt(s,10)}}return r.statusCode===200?{type:"success",token:r.body.token}:null}async function pbt({registry:t,configuration:e,report:r}){let s=await fbt(t,e);if(!s)return null;if(Ui.openUrl){r.reportInfo(0,"Starting the web login process..."),r.reportSeparator();let{openNow:a}=await(0,GK.prompt)({type:"confirm",name:"openNow",message:"Do you want to try to open your browser now?",required:!0,initial:!0,onCancel:()=>process.exit(130)});r.reportSeparator(),(!a||!await Ui.openUrl(s.loginUrl))&&(r.reportWarning(0,"We failed to automatically open the url; you'll have to open it yourself in your browser of choice:"),r.reportWarning(0,he.pretty(e,s.loginUrl,he.Type.URL)),r.reportSeparator())}for(;;){let a=await Abt(s.doneUrl,e);if(a===null)return null;if(a.type==="waiting")await new Promise(n=>setTimeout(n,a.sleep*1e3));else return a.token}}var hbt=["https://registry.yarnpkg.com","https://registry.npmjs.org"];async function gbt(t){if(t.webLogin??hbt.includes(t.registry)){let e=await pbt(t);if(e!==null)return e}return await dbt(t)}async function dbt({registry:t,configuration:e,report:r,stdin:s,stdout:a}){let n=await ybt({configuration:e,registry:t,report:r,stdin:s,stdout:a}),c=`/-/user/org.couchdb.user:${encodeURIComponent(n.name)}`,f={_id:`org.couchdb.user:${n.name}`,name:n.name,password:n.password,type:"user",roles:[],date:new Date().toISOString()},p={attemptedAs:n.name,configuration:e,registry:t,jsonResponse:!0,authType:en.AuthType.NO_AUTH};try{return(await en.put(c,f,p)).token}catch(P){if(!(P.originalError?.name==="HTTPError"&&P.originalError?.response.statusCode===409))throw P}let h={...p,authType:en.AuthType.NO_AUTH,headers:{authorization:`Basic ${Buffer.from(`${n.name}:${n.password}`).toString("base64")}`}},E=await en.get(c,h);for(let[P,I]of Object.entries(E))(!f[P]||P==="roles")&&(f[P]=I);let C=`${c}/-rev/${f._rev}`;return(await en.put(C,f,h)).token}async function mbt(t,e,{alwaysAuth:r,scope:s}){let a=c=>f=>{let p=je.isIndexableObject(f)?f:{},h=p[c],E=je.isIndexableObject(h)?h:{};return{...p,[c]:{...E,...r!==void 0?{npmAlwaysAuth:r}:{},npmAuthToken:e}}},n=s?{npmScopes:a(s)}:{npmRegistries:a(t)};return await ze.updateHomeConfiguration(n)}async function ybt({configuration:t,registry:e,report:r,stdin:s,stdout:a}){r.reportInfo(0,`Logging in to ${he.pretty(t,e,he.Type.URL)}`);let n=!1;if(e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(r.reportInfo(0,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),n=!0),r.reportSeparator(),t.env.YARN_IS_TEST_ENV)return{name:t.env.YARN_INJECT_NPM_USER||"",password:t.env.YARN_INJECT_NPM_PASSWORD||""};let c=await(0,GK.prompt)([{type:"input",name:"name",message:"Username:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a},{type:"password",name:"password",message:n?"Token:":"Password:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a}]);return r.reportSeparator(),c}Ge();Ge();Yt();var x1=new Set(["npmAuthIdent","npmAuthToken"]),k1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Logout of the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Logout of the publish registry"});this.all=ge.Boolean("-A,--all",!1,{description:"Logout of all registries"})}static{this.paths=[["npm","logout"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"logout of the npm registry",details:"\n This command will log you out by modifying your local configuration (in your home folder, never in the project itself) to delete all credentials linked to a registry.\n\n Adding the `-s,--scope` flag will cause the deletion to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the deletion to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n\n Adding the `-A,--all` flag will cause the deletion to be done against all registries and scopes.\n ",examples:[["Logout of the default registry","yarn npm logout"],["Logout of the @my-scope scope","yarn npm logout --scope my-scope"],["Logout of the publish registry for the current package","yarn npm logout --publish"],["Logout of all registries","yarn npm logout --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=async()=>{let n=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope}),c=await ze.find(this.context.cwd,this.context.plugins),f=G.makeIdent(this.scope??null,"pkg");return!hi.getAuthConfiguration(n,{configuration:c,ident:f}).get("npmAuthToken")};return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{if(this.all&&(await Ibt(),n.reportInfo(0,"Successfully logged out from everything")),this.scope){await Jxe("npmScopes",this.scope),await s()?n.reportInfo(0,`Successfully logged out from ${this.scope}`):n.reportWarning(0,"Scope authentication settings removed, but some other ones settings still apply to it");return}let c=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish});await Jxe("npmRegistries",c),await s()?n.reportInfo(0,`Successfully logged out from ${c}`):n.reportWarning(0,"Registry authentication settings removed, but some other ones settings still apply to it")})).exitCode()}};function Ebt(t,e){let r=t[e];if(!je.isIndexableObject(r))return!1;let s=new Set(Object.keys(r));if([...x1].every(n=>!s.has(n)))return!1;for(let n of x1)s.delete(n);if(s.size===0)return t[e]=void 0,!0;let a={...r};for(let n of x1)delete a[n];return t[e]=a,!0}async function Ibt(){let t=e=>{let r=!1,s=je.isIndexableObject(e)?{...e}:{};s.npmAuthToken&&(delete s.npmAuthToken,r=!0);for(let a of Object.keys(s))Ebt(s,a)&&(r=!0);if(Object.keys(s).length!==0)return r?s:e};return await ze.updateHomeConfiguration({npmRegistries:t,npmScopes:t})}async function Jxe(t,e){return await ze.updateHomeConfiguration({[t]:r=>{let s=je.isIndexableObject(r)?r:{};if(!Object.hasOwn(s,e))return r;let a=s[e],n=je.isIndexableObject(a)?a:{},c=new Set(Object.keys(n));if([...x1].every(p=>!c.has(p)))return r;for(let p of x1)c.delete(p);if(c.size===0)return Object.keys(s).length===1?void 0:{...s,[e]:void 0};let f={};for(let p of x1)f[p]=void 0;return{...s,[e]:{...n,...f}}}})}Ge();Dt();Yt();var Q1=class extends ft{constructor(){super(...arguments);this.access=ge.String("--access",{description:"The access for the published package (public or restricted)"});this.tag=ge.String("--tag","latest",{description:"The tag on the registry that the package should be attached to"});this.tolerateRepublish=ge.Boolean("--tolerate-republish",!1,{description:"Warn and exit when republishing an already existing version of a package"});this.otp=ge.String("--otp",{description:"The OTP token to use with the command"});this.provenance=ge.Boolean("--provenance",!1,{description:"Generate provenance for the package. Only available in GitHub Actions and GitLab CI. Can be set globally through the `npmPublishProvenance` setting or the `YARN_NPM_CONFIG_PROVENANCE` environment variable, or per-package through the `publishConfig.provenance` field in package.json."});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"Show what would be published without actually publishing"});this.json=ge.Boolean("--json",!1,{description:"Output the result in JSON format"})}static{this.paths=[["npm","publish"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"publish the active workspace to the npm registry",details:'\n This command will pack the active workspace into a fresh archive and upload it to the npm registry.\n\n The package will by default be attached to the `latest` tag on the registry, but this behavior can be overridden by using the `--tag` option.\n\n Note that for legacy reasons scoped packages are by default published with an access set to `restricted` (aka "private packages"). This requires you to register for a paid npm plan. In case you simply wish to publish a public scoped package to the registry (for free), just add the `--access public` flag. This behavior can be enabled by default through the `npmPublishAccess` settings.\n ',examples:[["Publish the active workspace","yarn npm publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);if(a.manifest.private)throw new nt("Private workspaces cannot be published");if(a.manifest.name===null||a.manifest.version===null)throw new nt("Workspaces must have valid names and versions to be published on an external registry");await s.restoreInstallState();let n=a.manifest.name,c=a.manifest.version,f=hi.getPublishRegistry(a.manifest,{configuration:r});return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async h=>{if(this.tolerateRepublish)try{let E=await en.get(en.getIdentUrl(n),{configuration:r,registry:f,ident:n,jsonResponse:!0});if(!Object.hasOwn(E,"versions"))throw new jt(15,'Registry returned invalid data for - missing "versions" field');if(Object.hasOwn(E.versions,c)){let C=`Registry already knows about version ${c}; skipping.`;h.reportWarning(0,C),h.reportJson({name:G.stringifyIdent(n),version:c,registry:f,warning:C,skipped:!0});return}}catch(E){if(E.originalError?.response?.statusCode!==404)throw E}await In.maybeExecuteWorkspaceLifecycleScript(a,"prepublish",{report:h}),await yA.prepareForPack(a,{report:h},async()=>{let E=await yA.genPackList(a);for(let W of E)h.reportInfo(null,fe.fromPortablePath(W)),h.reportJson({file:fe.fromPortablePath(W)});let C=await yA.genPackStream(a,E),S=await je.bufferStream(C),P=await v1.getGitHead(a.cwd),I=!1,R="";a.manifest.publishConfig&&"provenance"in a.manifest.publishConfig?(I=!!a.manifest.publishConfig.provenance,R=I?"Generating provenance statement because `publishConfig.provenance` field is set.":"Skipping provenance statement because `publishConfig.provenance` field is set to false."):this.provenance?(I=!0,R="Generating provenance statement because `--provenance` flag is set."):r.get("npmPublishProvenance")&&(I=!0,R="Generating provenance statement because `npmPublishProvenance` setting is set."),R&&(h.reportInfo(null,R),h.reportJson({type:"provenance",enabled:I,provenanceMessage:R}));let N=await v1.makePublishBody(a,S,{access:this.access,tag:this.tag,registry:f,gitHead:P,provenance:I});this.dryRun||await en.put(en.getIdentUrl(n),N,{configuration:r,registry:f,ident:n,otp:this.otp,jsonResponse:!0,allowOidc:!!(process.env.CI&&(process.env.GITHUB_ACTIONS||process.env.GITLAB_CI))});let U=this.dryRun?`[DRY RUN] Package would be published to ${f} with tag ${this.tag}`:"Package archive published";h.reportInfo(0,U),h.reportJson({name:G.stringifyIdent(n),version:c,registry:f,tag:this.tag||"latest",files:E.map(W=>fe.fromPortablePath(W)),access:this.access||null,dryRun:this.dryRun,published:!this.dryRun,message:U,provenance:!!I})})})).exitCode()}};Ge();Yt();var Kxe=ut(Ai());Ge();Dt();Yt();var T1=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String({required:!1})}static{this.paths=[["npm","tag","list"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"list all dist-tags of a package",details:` + This command will list all tags of a package from the npm registry. + + If the package is not specified, Yarn will default to the current workspace. + `,examples:[["List all tags of package `my-pkg`","yarn npm tag list my-pkg"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n;if(typeof this.package<"u")n=G.parseIdent(this.package);else{if(!a)throw new ar(s.cwd,this.context.cwd);if(!a.manifest.name)throw new nt(`Missing 'name' field in ${fe.fromPortablePath(J.join(a.cwd,Er.manifest))}`);n=a.manifest.name}let c=await Xb(n,r),p={children:je.sortMap(Object.entries(c),([h])=>h).map(([h,E])=>({value:he.tuple(he.Type.RESOLUTION,{descriptor:G.makeDescriptor(n,h),locator:G.makeLocator(n,E)})}))};return xs.emitTree(p,{configuration:r,json:this.json,stdout:this.context.stdout})}};async function Xb(t,e){let r=`/-/package${en.getIdentUrl(t)}/dist-tags`;return en.get(r,{configuration:e,ident:t,jsonResponse:!0,customErrorMessage:en.customPackageError})}var R1=class extends ft{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","add"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"add a tag for a specific version of a package",details:` + This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten. + `,examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=G.parseDescriptor(this.package,!0),c=n.range;if(!Kxe.default.valid(c))throw new nt(`The range ${he.pretty(r,n.range,he.Type.RANGE)} must be a valid semver version`);let f=hi.getPublishRegistry(a.manifest,{configuration:r}),p=he.pretty(r,n,he.Type.IDENT),h=he.pretty(r,c,he.Type.RANGE),E=he.pretty(r,this.tag,he.Type.CODE);return(await Ot.start({configuration:r,stdout:this.context.stdout},async S=>{let P=await Xb(n,r);Object.hasOwn(P,this.tag)&&P[this.tag]===c&&S.reportWarning(0,`Tag ${E} is already set to version ${h}`);let I=`/-/package${en.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await en.put(I,c,{configuration:r,registry:f,ident:n,jsonRequest:!0,jsonResponse:!0}),S.reportInfo(0,`Tag ${E} added to version ${h} of package ${p}`)})).exitCode()}};Ge();Yt();var F1=class extends ft{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","remove"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:` + This command will remove a tag from a package from the npm registry. + `,examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]})}async execute(){if(this.tag==="latest")throw new nt("The 'latest' tag cannot be removed.");let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=G.parseIdent(this.package),c=hi.getPublishRegistry(a.manifest,{configuration:r}),f=he.pretty(r,this.tag,he.Type.CODE),p=he.pretty(r,n,he.Type.IDENT),h=await Xb(n,r);if(!Object.hasOwn(h,this.tag))throw new nt(`${f} is not a tag of package ${p}`);return(await Ot.start({configuration:r,stdout:this.context.stdout},async C=>{let S=`/-/package${en.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await en.del(S,{configuration:r,registry:c,ident:n,jsonResponse:!0}),C.reportInfo(0,`Tag ${f} removed from package ${p}`)})).exitCode()}};Ge();Ge();Yt();var N1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Print username for the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Print username for the publish registry"})}static{this.paths=[["npm","whoami"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --scope company"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s;return this.scope&&this.publish?s=hi.getScopeRegistry(this.scope,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):this.scope?s=hi.getScopeRegistry(this.scope,{configuration:r}):this.publish?s=hi.getPublishRegistry((await eC(r,this.context.cwd)).manifest,{configuration:r}):s=hi.getDefaultRegistry({configuration:r}),(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c;try{c=await en.get("/-/whoami",{configuration:r,registry:s,authType:en.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?G.makeIdent(this.scope,""):void 0})}catch(f){if(f.response?.statusCode===401||f.response?.statusCode===403){n.reportError(41,"Authentication failed - your credentials may have expired");return}else throw f}n.reportInfo(0,c.username)})).exitCode()}};var Cbt={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:"STRING",default:null},npmPublishProvenance:{description:"Whether to generate provenance for the published packages",type:"BOOLEAN",default:!1},npmAuditExcludePackages:{description:"Array of glob patterns of packages to exclude from npm audit",type:"STRING",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:"Array of glob patterns of advisory IDs to exclude from npm audit",type:"STRING",default:[],isArray:!0}},commands:[D1,b1,P1,k1,Q1,R1,T1,F1,N1]},wbt=Cbt;var XK={};Vt(XK,{PatchCommand:()=>H1,PatchCommitCommand:()=>_1,PatchFetcher:()=>rP,PatchResolver:()=>nP,default:()=>_bt,patchUtils:()=>gy});Ge();Ge();Dt();eA();var gy={};Vt(gy,{applyPatchFile:()=>RL,diffFolders:()=>KK,ensureUnpatchedDescriptor:()=>WK,ensureUnpatchedLocator:()=>NL,extractPackageToDisk:()=>JK,extractPatchFlags:()=>rke,isParentRequired:()=>VK,isPatchDescriptor:()=>FL,isPatchLocator:()=>Rg,loadPatchFiles:()=>tP,makeDescriptor:()=>OL,makeLocator:()=>YK,makePatchHash:()=>zK,parseDescriptor:()=>$b,parseLocator:()=>eP,parsePatchFile:()=>Zb,unpatchDescriptor:()=>Lbt,unpatchLocator:()=>Mbt});Ge();Dt();Ge();Dt();var Bbt=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function O1(t){return J.relative(vt.root,J.resolve(vt.root,fe.toPortablePath(t)))}function vbt(t){let e=t.trim().match(Bbt);if(!e)throw new Error(`Bad header line: '${t}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var Sbt=420,Dbt=493;var zxe=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),bbt=t=>({header:vbt(t),parts:[]}),Pbt={"@":"header","-":"deletion","+":"insertion"," ":"context","\\":"pragma",undefined:"context"};function xbt(t){let e=[],r=zxe(),s="parsing header",a=null,n=null;function c(){a&&(n&&(a.parts.push(n),n=null),r.hunks.push(a),a=null)}function f(){c(),e.push(r),r=zxe()}for(let p=0;p0?"patch":"mode change",W=null;switch(U){case"rename":{if(!E||!C)throw new Error("Bad parser state: rename from & to not given");e.push({type:"rename",semverExclusivity:s,fromPath:O1(E),toPath:O1(C)}),W=C}break;case"file deletion":{let ee=a||I;if(!ee)throw new Error("Bad parse state: no path given for file deletion");e.push({type:"file deletion",semverExclusivity:s,hunk:N&&N[0]||null,path:O1(ee),mode:TL(p),hash:S})}break;case"file creation":{let ee=n||R;if(!ee)throw new Error("Bad parse state: no path given for file creation");e.push({type:"file creation",semverExclusivity:s,hunk:N&&N[0]||null,path:O1(ee),mode:TL(h),hash:P})}break;case"patch":case"mode change":W=R||n;break;default:je.assertNever(U);break}W&&c&&f&&c!==f&&e.push({type:"mode change",semverExclusivity:s,path:O1(W),oldMode:TL(c),newMode:TL(f)}),W&&N&&N.length&&e.push({type:"patch",semverExclusivity:s,path:O1(W),hunks:N,beforeHash:S,afterHash:P})}if(e.length===0)throw new Error("Unable to parse patch file: No changes found. Make sure the patch is a valid UTF8 encoded string");return e}function TL(t){let e=parseInt(t,8)&511;if(e!==Sbt&&e!==Dbt)throw new Error(`Unexpected file mode string: ${t}`);return e}function Zb(t){let e=t.split(/\n/g);return e[e.length-1]===""&&e.pop(),kbt(xbt(e))}function Qbt(t){let e=0,r=0;for(let{type:s,lines:a}of t.parts)switch(s){case"context":r+=a.length,e+=a.length;break;case"deletion":e+=a.length;break;case"insertion":r+=a.length;break;default:je.assertNever(s);break}if(e!==t.header.original.length||r!==t.header.patched.length){let s=a=>a<0?a:`+${a}`;throw new Error(`hunk header integrity check failed (expected @@ ${s(t.header.original.length)} ${s(t.header.patched.length)} @@, got @@ ${s(e)} ${s(r)} @@)`)}}Ge();Dt();var L1=class extends Error{constructor(r,s){super(`Cannot apply hunk #${r+1}`);this.hunk=s}};async function M1(t,e,r){let s=await t.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await t.lutimesPromise(e,s.atime,s.mtime)}async function RL(t,{baseFs:e=new Yn,dryRun:r=!1,version:s=null}={}){for(let a of t)if(!(a.semverExclusivity!==null&&s!==null&&!Fr.satisfiesWithPrereleases(s,a.semverExclusivity)))switch(a.type){case"file deletion":if(r){if(!e.existsSync(a.path))throw new Error(`Trying to delete a file that doesn't exist: ${a.path}`)}else await M1(e,J.dirname(a.path),async()=>{await e.unlinkPromise(a.path)});break;case"rename":if(r){if(!e.existsSync(a.fromPath))throw new Error(`Trying to move a file that doesn't exist: ${a.fromPath}`)}else await M1(e,J.dirname(a.fromPath),async()=>{await M1(e,J.dirname(a.toPath),async()=>{await M1(e,a.fromPath,async()=>(await e.movePromise(a.fromPath,a.toPath),a.toPath))})});break;case"file creation":if(r){if(e.existsSync(a.path))throw new Error(`Trying to create a file that already exists: ${a.path}`)}else{let n=a.hunk?a.hunk.parts[0].lines.join(` +`)+(a.hunk.parts[0].noNewlineAtEndOfFile?"":` +`):"";await e.mkdirpPromise(J.dirname(a.path),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),await e.writeFilePromise(a.path,n,{mode:a.mode}),await e.utimesPromise(a.path,fi.SAFE_TIME,fi.SAFE_TIME)}break;case"patch":await M1(e,a.path,async()=>{await Fbt(a,{baseFs:e,dryRun:r})});break;case"mode change":{let c=(await e.statPromise(a.path)).mode;if(Xxe(a.newMode)!==Xxe(c))continue;await M1(e,a.path,async()=>{await e.chmodPromise(a.path,a.newMode)})}break;default:je.assertNever(a);break}}function Xxe(t){return(t&64)>0}function Zxe(t){return t.replace(/\s+$/,"")}function Rbt(t,e){return Zxe(t)===Zxe(e)}async function Fbt({hunks:t,path:e},{baseFs:r,dryRun:s=!1}){let a=await r.statSync(e).mode,c=(await r.readFileSync(e,"utf8")).split(/\n/),f=[],p=0,h=0;for(let C of t){let S=Math.max(h,C.header.patched.start+p),P=Math.max(0,S-h),I=Math.max(0,c.length-S-C.header.original.length),R=Math.max(P,I),N=0,U=0,W=null;for(;N<=R;){if(N<=P&&(U=S-N,W=$xe(C,c,U),W!==null)){N=-N;break}if(N<=I&&(U=S+N,W=$xe(C,c,U),W!==null))break;N+=1}if(W===null)throw new L1(t.indexOf(C),C);f.push(W),p+=N,h=U+C.header.original.length}if(s)return;let E=0;for(let C of f)for(let S of C)switch(S.type){case"splice":{let P=S.index+E;c.splice(P,S.numToDelete,...S.linesToInsert),E+=S.linesToInsert.length-S.numToDelete}break;case"pop":c.pop();break;case"push":c.push(S.line);break;default:je.assertNever(S);break}await r.writeFilePromise(e,c.join(` +`),{mode:a})}function $xe(t,e,r){let s=[];for(let a of t.parts)switch(a.type){case"context":case"deletion":{for(let n of a.lines){let c=e[r];if(c==null||!Rbt(c,n))return null;r+=1}a.type==="deletion"&&(s.push({type:"splice",index:r-a.lines.length,numToDelete:a.lines.length,linesToInsert:[]}),a.noNewlineAtEndOfFile&&s.push({type:"push",line:""}))}break;case"insertion":s.push({type:"splice",index:r,numToDelete:0,linesToInsert:a.lines}),a.noNewlineAtEndOfFile&&s.push({type:"pop"});break;default:je.assertNever(a.type);break}return s}var Obt=/^builtin<([^>]+)>$/;function U1(t,e){let{protocol:r,source:s,selector:a,params:n}=G.parseRange(t);if(r!=="patch:")throw new Error("Invalid patch range");if(s===null)throw new Error("Patch locators must explicitly define their source");let c=a?a.split(/&/).map(E=>fe.toPortablePath(E)):[],f=n&&typeof n.locator=="string"?G.parseLocator(n.locator):null,p=n&&typeof n.version=="string"?n.version:null,h=e(s);return{parentLocator:f,sourceItem:h,patchPaths:c,sourceVersion:p}}function FL(t){return t.range.startsWith("patch:")}function Rg(t){return t.reference.startsWith("patch:")}function $b(t){let{sourceItem:e,...r}=U1(t.range,G.parseDescriptor);return{...r,sourceDescriptor:e}}function eP(t){let{sourceItem:e,...r}=U1(t.reference,G.parseLocator);return{...r,sourceLocator:e}}function Lbt(t){let{sourceItem:e}=U1(t.range,G.parseDescriptor);return e}function Mbt(t){let{sourceItem:e}=U1(t.reference,G.parseLocator);return e}function WK(t){if(!FL(t))return t;let{sourceItem:e}=U1(t.range,G.parseDescriptor);return e}function NL(t){if(!Rg(t))return t;let{sourceItem:e}=U1(t.reference,G.parseLocator);return e}function eke({parentLocator:t,sourceItem:e,patchPaths:r,sourceVersion:s,patchHash:a},n){let c=t!==null?{locator:G.stringifyLocator(t)}:{},f=typeof s<"u"?{version:s}:{},p=typeof a<"u"?{hash:a}:{};return G.makeRange({protocol:"patch:",source:n(e),selector:r.join("&"),params:{...f,...p,...c}})}function OL(t,{parentLocator:e,sourceDescriptor:r,patchPaths:s}){return G.makeDescriptor(t,eke({parentLocator:e,sourceItem:r,patchPaths:s},G.stringifyDescriptor))}function YK(t,{parentLocator:e,sourcePackage:r,patchPaths:s,patchHash:a}){return G.makeLocator(t,eke({parentLocator:e,sourceItem:r,sourceVersion:r.version,patchPaths:s,patchHash:a},G.stringifyLocator))}function tke({onAbsolute:t,onRelative:e,onProject:r,onBuiltin:s},a){let n=a.lastIndexOf("!");n!==-1&&(a=a.slice(n+1));let c=a.match(Obt);return c!==null?s(c[1]):a.startsWith("~/")?r(a.slice(2)):J.isAbsolute(a)?t(a):e(a)}function rke(t){let e=t.lastIndexOf("!");return{optional:(e!==-1?new Set(t.slice(0,e).split(/!/)):new Set).has("optional")}}function VK(t){return tke({onAbsolute:()=>!1,onRelative:()=>!0,onProject:()=>!1,onBuiltin:()=>!1},t)}async function tP(t,e,r){let s=t!==null?await r.fetcher.fetch(t,r):null,a=s&&s.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,s.localPath)}:s;s&&s!==a&&s.releaseFs&&s.releaseFs();let n=await je.releaseAfterUseAsync(async()=>await Promise.all(e.map(async c=>{let f=rke(c),p=await tke({onAbsolute:async h=>await ce.readFilePromise(h,"utf8"),onRelative:async h=>{if(a===null)throw new Error("Assertion failed: The parent locator should have been fetched");return await a.packageFs.readFilePromise(J.join(a.prefixPath,h),"utf8")},onProject:async h=>await ce.readFilePromise(J.join(r.project.cwd,h),"utf8"),onBuiltin:async h=>await r.project.configuration.firstHook(E=>E.getBuiltinPatch,r.project,h)},c);return{...f,source:p}})));for(let c of n)typeof c.source=="string"&&(c.source=c.source.replace(/\r\n?/g,` +`));return n}async function JK(t,{cache:e,project:r}){let s=r.storedPackages.get(t.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected the package to be registered");let a=NL(t),n=r.storedChecksums,c=new ki,f=await ce.mktempPromise(),p=J.join(f,"source"),h=J.join(f,"user"),E=J.join(f,".yarn-patch.json"),C=r.configuration.makeFetcher(),S=[];try{let P,I;if(t.locatorHash===a.locatorHash){let R=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c});S.push(()=>R.releaseFs?.()),P=R,I=R}else P=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>P.releaseFs?.()),I=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>I.releaseFs?.());await Promise.all([ce.copyPromise(p,P.prefixPath,{baseFs:P.packageFs}),ce.copyPromise(h,I.prefixPath,{baseFs:I.packageFs}),ce.writeJsonPromise(E,{locator:G.stringifyLocator(t),version:s.version})])}finally{for(let P of S)P()}return ce.detachTemp(f),h}async function KK(t,e){let r=fe.fromPortablePath(t).replace(/\\/g,"/"),s=fe.fromPortablePath(e).replace(/\\/g,"/"),{stdout:a,stderr:n}=await qr.execvp("git",["-c","core.safecrlf=false","diff","--src-prefix=a/","--dst-prefix=b/","--ignore-cr-at-eol","--full-index","--no-index","--no-renames","--text",r,s],{cwd:fe.toPortablePath(process.cwd()),env:{...process.env,GIT_CONFIG_NOSYSTEM:"1",HOME:"",XDG_CONFIG_HOME:"",USERPROFILE:""}});if(n.length>0)throw new Error(`Unable to diff directories. Make sure you have a recent version of 'git' available in PATH. +The following error was reported by 'git': +${n}`);let c=r.startsWith("/")?f=>f.slice(1):f=>f;return a.replace(new RegExp(`(a|b)(${je.escapeRegExp(`/${c(r)}/`)})`,"g"),"$1/").replace(new RegExp(`(a|b)${je.escapeRegExp(`/${c(s)}/`)}`,"g"),"$1/").replace(new RegExp(je.escapeRegExp(`${r}/`),"g"),"").replace(new RegExp(je.escapeRegExp(`${s}/`),"g"),"")}function zK(t,e){let r=[];for(let{source:s}of t){if(s===null)continue;let a=Zb(s);for(let n of a){let{semverExclusivity:c,...f}=n;c!==null&&e!==null&&!Fr.satisfiesWithPrereleases(e,c)||r.push(JSON.stringify(f))}}return Nn.makeHash(`${3}`,...r).slice(0,6)}Ge();function nke(t,{configuration:e,report:r}){for(let s of t.parts)for(let a of s.lines)switch(s.type){case"context":r.reportInfo(null,` ${he.pretty(e,a,"grey")}`);break;case"deletion":r.reportError(28,`- ${he.pretty(e,a,he.Type.REMOVED)}`);break;case"insertion":r.reportError(28,`+ ${he.pretty(e,a,he.Type.ADDED)}`);break;default:je.assertNever(s.type)}}var rP=class{supports(e,r){return!!Rg(e)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.patchPackage(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async patchPackage(e,r){let{parentLocator:s,sourceLocator:a,sourceVersion:n,patchPaths:c}=eP(e),f=await tP(s,c,r),p=await ce.mktempPromise(),h=J.join(p,"current.zip"),E=await r.fetcher.fetch(a,r),C=G.getIdentVendorPath(e),S=new As(h,{create:!0,level:r.project.configuration.get("compressionLevel")});await je.releaseAfterUseAsync(async()=>{await S.copyPromise(C,E.prefixPath,{baseFs:E.packageFs,stableSort:!0})},E.releaseFs),S.saveAndClose();for(let{source:P,optional:I}of f){if(P===null)continue;let R=new As(h,{level:r.project.configuration.get("compressionLevel")}),N=new Sn(J.resolve(vt.root,C),{baseFs:R});try{await RL(Zb(P),{baseFs:N,version:n})}catch(U){if(!(U instanceof L1))throw U;let W=r.project.configuration.get("enableInlineHunks"),ee=!W&&!I?" (set enableInlineHunks for details)":"",ie=`${G.prettyLocator(r.project.configuration,e)}: ${U.message}${ee}`,ue=le=>{W&&nke(U.hunk,{configuration:r.project.configuration,report:le})};if(R.discardAndClose(),I){r.report.reportWarningOnce(66,ie,{reportExtra:ue});continue}else throw new jt(66,ie,ue)}R.saveAndClose()}return new As(h,{level:r.project.configuration.get("compressionLevel")})}};Ge();var nP=class{supportsDescriptor(e,r){return!!FL(e)}supportsLocator(e,r){return!!Rg(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){let{patchPaths:a}=$b(e);return a.every(n=>!VK(n))?e:G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){let{sourceDescriptor:s}=$b(e);return{sourceDescriptor:r.project.configuration.normalizeDependency(s)}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{parentLocator:a,patchPaths:n}=$b(e),c=await tP(a,n,s.fetchOptions),f=r.sourceDescriptor;if(typeof f>"u")throw new Error("Assertion failed: The dependency should have been resolved");let p=zK(c,f.version);return[YK(e,{parentLocator:a,sourcePackage:f,patchPaths:n,patchHash:p})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let{sourceLocator:s}=eP(e);return{...await r.resolver.resolve(s,r),...e}}};Ge();Dt();Yt();var _1=class extends ft{constructor(){super(...arguments);this.save=ge.Boolean("-s,--save",!1,{description:"Add the patch to your resolution entries"});this.patchFolder=ge.String()}static{this.paths=[["patch-commit"]]}static{this.usage=ot.Usage({description:"generate a patch out of a directory",details:"\n By default, this will print a patchfile on stdout based on the diff between the folder passed in and the original version of the package. Such file is suitable for consumption with the `patch:` protocol.\n\n With the `-s,--save` option set, the patchfile won't be printed on stdout anymore and will instead be stored within a local file (by default kept within `.yarn/patches`, but configurable via the `patchFolder` setting). A `resolutions` entry will also be added to your top-level manifest, referencing the patched package via the `patch:` protocol.\n\n Note that only folders generated by `yarn patch` are accepted as valid input for `yarn patch-commit`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=J.resolve(this.context.cwd,fe.toPortablePath(this.patchFolder)),c=J.join(n,"../source"),f=J.join(n,"../.yarn-patch.json");if(!ce.existsSync(c))throw new nt("The argument folder didn't get created by 'yarn patch'");let p=await KK(c,n),h=await ce.readJsonPromise(f),E=G.parseLocator(h.locator,!0);if(!s.storedPackages.has(E.locatorHash))throw new nt("No package found in the project for the given locator");if(!this.save){this.context.stdout.write(p);return}let C=r.get("patchFolder"),S=J.join(C,`${G.slugifyLocator(E)}.patch`);await ce.mkdirPromise(C,{recursive:!0}),await ce.writeFilePromise(S,p);let P=[],I=new Map;for(let R of s.storedPackages.values()){if(G.isVirtualLocator(R))continue;let N=R.dependencies.get(E.identHash);if(!N)continue;let U=G.ensureDevirtualizedDescriptor(N),W=WK(U),ee=s.storedResolutions.get(W.descriptorHash);if(!ee)throw new Error("Assertion failed: Expected the resolution to have been registered");if(!s.storedPackages.get(ee))throw new Error("Assertion failed: Expected the package to have been registered");let ue=s.tryWorkspaceByLocator(R);if(ue)P.push(ue);else{let le=s.originalPackages.get(R.locatorHash);if(!le)throw new Error("Assertion failed: Expected the original package to have been registered");let me=le.dependencies.get(N.identHash);if(!me)throw new Error("Assertion failed: Expected the original dependency to have been registered");I.set(me.descriptorHash,me)}}for(let R of P)for(let N of Ut.hardDependencies){let U=R.manifest[N].get(E.identHash);if(!U)continue;let W=OL(U,{parentLocator:null,sourceDescriptor:G.convertLocatorToDescriptor(E),patchPaths:[J.join(Er.home,J.relative(s.cwd,S))]});R.manifest[N].set(U.identHash,W)}for(let R of I.values()){let N=OL(R,{parentLocator:null,sourceDescriptor:G.convertLocatorToDescriptor(E),patchPaths:[J.join(Er.home,J.relative(s.cwd,S))]});s.topLevelWorkspace.manifest.resolutions.push({pattern:{descriptor:{fullName:G.stringifyIdent(N),description:R.range}},reference:N.range})}await s.persist()}};Ge();Dt();Yt();var H1=class extends ft{constructor(){super(...arguments);this.update=ge.Boolean("-u,--update",!1,{description:"Reapply local patches that already apply to this packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String()}static{this.paths=[["patch"]]}static{this.usage=ot.Usage({description:"prepare a package for patching",details:"\n This command will cause a package to be extracted in a temporary directory intended to be editable at will.\n\n Once you're done with your changes, run `yarn patch-commit -s path` (with `path` being the temporary directory you received) to generate a patchfile and register it into your top-level manifest via the `patch:` protocol. Run `yarn patch-commit -h` for more details.\n\n Calling the command when you already have a patch won't import it by default (in other words, the default behavior is to reset existing patches). However, adding the `-u,--update` flag will import any current patch.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=G.parseLocator(this.package);if(c.reference==="unknown"){let f=je.mapAndFilter([...s.storedPackages.values()],p=>p.identHash!==c.identHash?je.mapAndFilter.skip:G.isVirtualLocator(p)?je.mapAndFilter.skip:Rg(p)!==this.update?je.mapAndFilter.skip:p);if(f.length===0)throw new nt("No package found in the project for the given locator");if(f.length>1)throw new nt(`Multiple candidate packages found; explicitly choose one of them (use \`yarn why \` to get more information as to who depends on them): +${f.map(p=>` +- ${G.prettyLocator(r,p)}`).join("")}`);c=f[0]}if(!s.storedPackages.has(c.locatorHash))throw new nt("No package found in the project for the given locator");await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=NL(c),h=await JK(c,{cache:n,project:s});f.reportJson({locator:G.stringifyLocator(p),path:fe.fromPortablePath(h)});let E=this.update?" along with its current modifications":"";f.reportInfo(0,`Package ${G.prettyLocator(r,p)} got extracted with success${E}!`),f.reportInfo(0,`You can now edit the following folder: ${he.pretty(r,fe.fromPortablePath(h),"magenta")}`),f.reportInfo(0,`Once you are done run ${he.pretty(r,`yarn patch-commit -s ${process.platform==="win32"?'"':""}${fe.fromPortablePath(h)}${process.platform==="win32"?'"':""}`,"cyan")} and Yarn will store a patchfile based on your changes.`)})}};var Ubt={configuration:{enableInlineHunks:{description:"If true, the installs will print unmatched patch hunks",type:"BOOLEAN",default:!1},patchFolder:{description:"Folder where the patch files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/patches"}},commands:[_1,H1],fetchers:[rP],resolvers:[nP]},_bt=Ubt;var ez={};Vt(ez,{PnpmLinker:()=>iP,default:()=>Ybt});Ge();Dt();Yt();var iP=class{getCustomDataKey(){return JSON.stringify({name:"PnpmLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the pnpm linker to be enabled");let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=a.pathsByLocator.get(e.locatorHash);if(typeof n>"u")throw new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed pnpm map - running an install might help`);return n.packageLocation}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=e.match(/(^.*\/node_modules\/(@[^/]*\/)?[^/]+)(\/.*$)/);if(n){let p=a.locatorByPath.get(n[1]);if(p)return p}let c=e,f=e;do{f=c,c=J.dirname(f);let p=a.locatorByPath.get(f);if(p)return p}while(c!==f);return null}makeInstaller(e){return new ZK(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="pnpm"}},ZK=class{constructor(e){this.opts=e;this.asyncActions=new je.AsyncActions(10);this.customData={pathsByLocator:new Map,locatorByPath:new Map};this.indexFolderPromise=$P(ce,{indexPath:J.join(e.project.configuration.get("globalFolder"),"index")})}attachCustomData(e){}async installPackage(e,r,s){switch(e.linkType){case"SOFT":return this.installPackageSoft(e,r,s);case"HARD":return this.installPackageHard(e,r,s)}throw new Error("Assertion failed: Unsupported package link type")}async installPackageSoft(e,r,s){let a=J.resolve(r.packageFs.getRealPath(),r.prefixPath),n=this.opts.project.tryWorkspaceByLocator(e)?J.join(a,Er.nodeModules):null;return this.customData.pathsByLocator.set(e.locatorHash,{packageLocation:a,dependenciesLocation:n}),{packageLocation:a,buildRequest:null}}async installPackageHard(e,r,s){let a=jbt(e,{project:this.opts.project}),n=a.packageLocation;this.customData.locatorByPath.set(n,G.stringifyLocator(e)),this.customData.pathsByLocator.set(e.locatorHash,a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{await ce.mkdirPromise(n,{recursive:!0}),await ce.copyPromise(n,r.prefixPath,{baseFs:r.packageFs,overwrite:!1,linkStrategy:{type:"HardlinkFromIndex",indexPath:await this.indexFolderPromise,autoRepair:!0}})}));let f=G.isVirtualLocator(e)?G.devirtualizeLocator(e):e,p={manifest:await Ut.tryFind(r.prefixPath,{baseFs:r.packageFs})??new Ut,misc:{hasBindingGyp:gA.hasBindingGyp(r)}},h=this.opts.project.getDependencyMeta(f,e.version),E=gA.extractBuildRequest(e,p,h,{configuration:this.opts.project.configuration});return{packageLocation:n,buildRequest:E}}async attachInternalDependencies(e,r){if(this.opts.project.configuration.get("nodeLinker")!=="pnpm"||!ike(e,{project:this.opts.project}))return;let s=this.customData.pathsByLocator.get(e.locatorHash);if(typeof s>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${G.stringifyLocator(e)})`);let{dependenciesLocation:a}=s;a&&this.asyncActions.reduce(e.locatorHash,async n=>{await ce.mkdirPromise(a,{recursive:!0});let c=await Gbt(a),f=new Map(c),p=[n],h=(C,S)=>{let P=S;ike(S,{project:this.opts.project})||(this.opts.report.reportWarningOnce(0,"The pnpm linker doesn't support providing different versions to workspaces' peer dependencies"),P=G.devirtualizeLocator(S));let I=this.customData.pathsByLocator.get(P.locatorHash);if(typeof I>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${G.stringifyLocator(S)})`);let R=G.stringifyIdent(C),N=J.join(a,R),U=J.relative(J.dirname(N),I.packageLocation),W=f.get(R);f.delete(R),p.push(Promise.resolve().then(async()=>{if(W){if(W.isSymbolicLink()&&await ce.readlinkPromise(N)===U)return;await ce.removePromise(N)}await ce.mkdirpPromise(J.dirname(N)),process.platform=="win32"&&this.opts.project.configuration.get("winLinkType")==="junctions"?await ce.symlinkPromise(I.packageLocation,N,"junction"):await ce.symlinkPromise(U,N)}))},E=!1;for(let[C,S]of r)C.identHash===e.identHash&&(E=!0),h(C,S);!E&&!this.opts.project.tryWorkspaceByLocator(e)&&h(G.convertLocatorToDescriptor(e),e),p.push(qbt(a,f)),await Promise.all(p)})}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the pnpm linker")}async finalizeInstall(){let e=ske(this.opts.project);if(this.opts.project.configuration.get("nodeLinker")!=="pnpm")await ce.removePromise(e);else{let r;try{r=new Set(await ce.readdirPromise(e))}catch{r=new Set}for(let{dependenciesLocation:s}of this.customData.pathsByLocator.values()){if(!s)continue;let a=J.contains(e,s);if(a===null)continue;let[n]=a.split(J.sep);r.delete(n)}await Promise.all([...r].map(async s=>{await ce.removePromise(J.join(e,s))}))}return await this.asyncActions.wait(),await $K(e),this.opts.project.configuration.get("nodeLinker")!=="node-modules"&&await $K(Hbt(this.opts.project)),{customData:this.customData}}};function Hbt(t){return J.join(t.cwd,Er.nodeModules)}function ske(t){return t.configuration.get("pnpmStoreFolder")}function jbt(t,{project:e}){let r=G.slugifyLocator(t),s=ske(e),a=J.join(s,r,"package"),n=J.join(s,r,Er.nodeModules);return{packageLocation:a,dependenciesLocation:n}}function ike(t,{project:e}){return!G.isVirtualLocator(t)||!e.tryWorkspaceByLocator(t)}async function Gbt(t){let e=new Map,r=[];try{r=await ce.readdirPromise(t,{withFileTypes:!0})}catch(s){if(s.code!=="ENOENT")throw s}try{for(let s of r)if(!s.name.startsWith("."))if(s.name.startsWith("@")){let a=await ce.readdirPromise(J.join(t,s.name),{withFileTypes:!0});if(a.length===0)e.set(s.name,s);else for(let n of a)e.set(`${s.name}/${n.name}`,n)}else e.set(s.name,s)}catch(s){if(s.code!=="ENOENT")throw s}return e}async function qbt(t,e){let r=[],s=new Set;for(let a of e.keys()){r.push(ce.removePromise(J.join(t,a)));let n=G.tryParseIdent(a)?.scope;n&&s.add(`@${n}`)}return Promise.all(r).then(()=>Promise.all([...s].map(a=>$K(J.join(t,a)))))}async function $K(t){try{await ce.rmdirPromise(t)}catch(e){if(e.code!=="ENOENT"&&e.code!=="ENOTEMPTY"&&e.code!=="EBUSY")throw e}}var Wbt={configuration:{pnpmStoreFolder:{description:"By default, the store is stored in the 'node_modules/.store' of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.",type:"ABSOLUTE_PATH",default:"./node_modules/.store"}},linkers:[iP]},Ybt=Wbt;var az={};Vt(az,{StageCommand:()=>j1,default:()=>nPt,stageUtils:()=>ML});Ge();Dt();Yt();Ge();Dt();var ML={};Vt(ML,{ActionType:()=>tz,checkConsensus:()=>LL,expandDirectory:()=>iz,findConsensus:()=>sz,findVcsRoot:()=>rz,genCommitMessage:()=>oz,getCommitPrefix:()=>oke,isYarnFile:()=>nz});Dt();var tz=(n=>(n[n.CREATE=0]="CREATE",n[n.DELETE=1]="DELETE",n[n.ADD=2]="ADD",n[n.REMOVE=3]="REMOVE",n[n.MODIFY=4]="MODIFY",n))(tz||{});async function rz(t,{marker:e}){do if(!ce.existsSync(J.join(t,e)))t=J.dirname(t);else return t;while(t!=="/");return null}function nz(t,{roots:e,names:r}){if(r.has(J.basename(t)))return!0;do if(!e.has(t))t=J.dirname(t);else return!0;while(t!=="/");return!1}function iz(t){let e=[],r=[t];for(;r.length>0;){let s=r.pop(),a=ce.readdirSync(s);for(let n of a){let c=J.resolve(s,n);ce.lstatSync(c).isDirectory()?r.push(c):e.push(c)}}return e}function LL(t,e){let r=0,s=0;for(let a of t)a!=="wip"&&(e.test(a)?r+=1:s+=1);return r>=s}function sz(t){let e=LL(t,/^(\w\(\w+\):\s*)?\w+s/),r=LL(t,/^(\w\(\w+\):\s*)?[A-Z]/),s=LL(t,/^\w\(\w+\):/);return{useThirdPerson:e,useUpperCase:r,useComponent:s}}function oke(t){return t.useComponent?"chore(yarn): ":""}var Vbt=new Map([[0,"create"],[1,"delete"],[2,"add"],[3,"remove"],[4,"update"]]);function oz(t,e){let r=oke(t),s=[],a=e.slice().sort((n,c)=>n[0]-c[0]);for(;a.length>0;){let[n,c]=a.shift(),f=Vbt.get(n);t.useUpperCase&&s.length===0&&(f=`${f[0].toUpperCase()}${f.slice(1)}`),t.useThirdPerson&&(f+="s");let p=[c];for(;a.length>0&&a[0][0]===n;){let[,E]=a.shift();p.push(E)}p.sort();let h=p.shift();p.length===1?h+=" (and one other)":p.length>1&&(h+=` (and ${p.length} others)`),s.push(`${f} ${h}`)}return`${r}${s.join(", ")}`}var Jbt="Commit generated via `yarn stage`",Kbt=11;async function ake(t){let{code:e,stdout:r}=await qr.execvp("git",["log","-1","--pretty=format:%H"],{cwd:t});return e===0?r.trim():null}async function zbt(t,e){let r=[],s=e.filter(h=>J.basename(h.path)==="package.json");for(let{action:h,path:E}of s){let C=J.relative(t,E);if(h===4){let S=await ake(t),{stdout:P}=await qr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ut.fromText(P),R=await Ut.fromFile(E),N=new Map([...R.dependencies,...R.devDependencies]),U=new Map([...I.dependencies,...I.devDependencies]);for(let[W,ee]of U){let ie=G.stringifyIdent(ee),ue=N.get(W);ue?ue.range!==ee.range&&r.push([4,`${ie} to ${ue.range}`]):r.push([3,ie])}for(let[W,ee]of N)U.has(W)||r.push([2,G.stringifyIdent(ee)])}else if(h===0){let S=await Ut.fromFile(E);S.name?r.push([0,G.stringifyIdent(S.name)]):r.push([0,"a package"])}else if(h===1){let S=await ake(t),{stdout:P}=await qr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ut.fromText(P);I.name?r.push([1,G.stringifyIdent(I.name)]):r.push([1,"a package"])}else throw new Error("Assertion failed: Unsupported action type")}let{code:a,stdout:n}=await qr.execvp("git",["log",`-${Kbt}`,"--pretty=format:%s"],{cwd:t}),c=a===0?n.split(/\n/g).filter(h=>h!==""):[],f=sz(c);return oz(f,r)}var Xbt={0:[" A ","?? "],4:[" M "],1:[" D "]},Zbt={0:["A "],4:["M "],1:["D "]},lke={async findRoot(t){return await rz(t,{marker:".git"})},async filterChanges(t,e,r,s){let{stdout:a}=await qr.execvp("git",["status","-s"],{cwd:t,strict:!0}),n=a.toString().split(/\n/g),c=s?.staged?Zbt:Xbt;return[].concat(...n.map(p=>{if(p==="")return[];let h=p.slice(0,3),E=J.resolve(t,p.slice(3));if(!s?.staged&&h==="?? "&&p.endsWith("/"))return iz(E).map(C=>({action:0,path:C}));{let S=[0,4,1].find(P=>c[P].includes(h));return S!==void 0?[{action:S,path:E}]:[]}})).filter(p=>nz(p.path,{roots:e,names:r}))},async genCommitMessage(t,e){return await zbt(t,e)},async makeStage(t,e){let r=e.map(s=>fe.fromPortablePath(s.path));await qr.execvp("git",["add","--",...r],{cwd:t,strict:!0})},async makeCommit(t,e,r){let s=e.map(a=>fe.fromPortablePath(a.path));await qr.execvp("git",["add","-N","--",...s],{cwd:t,strict:!0}),await qr.execvp("git",["commit","-m",`${r} + +${Jbt} +`,"--",...s],{cwd:t,strict:!0})},async makeReset(t,e){let r=e.map(s=>fe.fromPortablePath(s.path));await qr.execvp("git",["reset","HEAD","--",...r],{cwd:t,strict:!0})}};var $bt=[lke],j1=class extends ft{constructor(){super(...arguments);this.commit=ge.Boolean("-c,--commit",!1,{description:"Commit the staged files"});this.reset=ge.Boolean("-r,--reset",!1,{description:"Remove all files from the staging area"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"Print the commit message and the list of modified files without staging / committing"});this.update=ge.Boolean("-u,--update",!1,{hidden:!0})}static{this.paths=[["stage"]]}static{this.usage=ot.Usage({description:"add all yarn files to your vcs",details:"\n This command will add to your staging area the files belonging to Yarn (typically any modified `package.json` and `.yarnrc.yml` files, but also linker-generated files, cache data, etc). It will take your ignore list into account, so the cache files won't be added if the cache is ignored in a `.gitignore` file (assuming you use Git).\n\n Running `--reset` will instead remove them from the staging area (the changes will still be there, but won't be committed until you stage them back).\n\n Since the staging area is a non-existent concept in Mercurial, Yarn will always create a new commit when running this command on Mercurial repositories. You can get this behavior when using Git by using the `--commit` flag which will directly create a commit.\n ",examples:[["Adds all modified project files to the staging area","yarn stage"],["Creates a new commit containing all modified project files","yarn stage --commit"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),{driver:a,root:n}=await ePt(s.cwd),c=[r.get("cacheFolder"),r.get("globalFolder"),r.get("virtualFolder"),r.get("yarnPath")];await r.triggerHook(C=>C.populateYarnPaths,s,C=>{c.push(C)});let f=new Set;for(let C of c)for(let S of tPt(n,C))f.add(S);let p=new Set([r.get("rcFilename"),Er.lockfile,Er.manifest]),h=await a.filterChanges(n,f,p),E=await a.genCommitMessage(n,h);if(this.dryRun)if(this.commit)this.context.stdout.write(`${E} +`);else for(let C of h)this.context.stdout.write(`${fe.fromPortablePath(C.path)} +`);else if(this.reset){let C=await a.filterChanges(n,f,p,{staged:!0});C.length===0?this.context.stdout.write("No staged changes found!"):await a.makeReset(n,C)}else h.length===0?this.context.stdout.write("No changes found!"):this.commit?await a.makeCommit(n,h,E):(await a.makeStage(n,h),this.context.stdout.write(E))}};async function ePt(t){let e=null,r=null;for(let s of $bt)if((r=await s.findRoot(t))!==null){e=s;break}if(e===null||r===null)throw new nt("No stage driver has been found for your current project");return{driver:e,root:r}}function tPt(t,e){let r=[];if(e===null)return r;for(;;){(e===t||e.startsWith(`${t}/`))&&r.push(e);let s;try{s=ce.statSync(e)}catch{break}if(s.isSymbolicLink())e=J.resolve(J.dirname(e),ce.readlinkSync(e));else break}return r}var rPt={commands:[j1]},nPt=rPt;var lz={};Vt(lz,{default:()=>fPt});Ge();Ge();Dt();var fke=ut(Ai());Ge();var cke=ut(g9()),iPt="e8e1bd300d860104bb8c58453ffa1eb4",sPt="OFCNCOG2CU",uke=async(t,e)=>{let r=G.stringifyIdent(t),a=oPt(e).initIndex("npm-search");try{return(await a.getObject(r,{attributesToRetrieve:["types"]})).types?.ts==="definitely-typed"}catch{return!1}},oPt=t=>(0,cke.default)(sPt,iPt,{requester:{async send(r){try{let s=await nn.request(r.url,r.data||null,{configuration:t,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var Ake=t=>t.scope?`${t.scope}__${t.name}`:`${t.name}`,aPt=async(t,e,r,s)=>{if(r.scope==="types")return;let{project:a}=t,{configuration:n}=a;if(!(n.get("tsEnableAutoTypes")??(ce.existsSync(J.join(t.cwd,"tsconfig.json"))||ce.existsSync(J.join(a.cwd,"tsconfig.json")))))return;let f=n.makeResolver(),p={project:a,resolver:f,report:new ki};if(!await uke(r,n))return;let E=Ake(r),C=G.parseRange(r.range).selector;if(!Fr.validRange(C)){let N=n.normalizeDependency(r),U=await f.getCandidates(N,{},p);C=G.parseRange(U[0].reference).selector}let S=fke.default.coerce(C);if(S===null)return;let P=`${Xu.Modifier.CARET}${S.major}`,I=G.makeDescriptor(G.makeIdent("types",E),P),R=je.mapAndFind(a.workspaces,N=>{let U=N.manifest.dependencies.get(r.identHash)?.descriptorHash,W=N.manifest.devDependencies.get(r.identHash)?.descriptorHash;if(U!==r.descriptorHash&&W!==r.descriptorHash)return je.mapAndFind.skip;let ee=[];for(let ie of Ut.allDependencies){let ue=N.manifest[ie].get(I.identHash);typeof ue>"u"||ee.push([ie,ue])}return ee.length===0?je.mapAndFind.skip:ee});if(typeof R<"u")for(let[N,U]of R)t.manifest[N].set(U.identHash,U);else{try{let N=n.normalizeDependency(I);if((await f.getCandidates(N,{},p)).length===0)return}catch{return}t.manifest[Xu.Target.DEVELOPMENT].set(I.identHash,I)}},lPt=async(t,e,r)=>{if(r.scope==="types")return;let{project:s}=t,{configuration:a}=s;if(!(a.get("tsEnableAutoTypes")??(ce.existsSync(J.join(t.cwd,"tsconfig.json"))||ce.existsSync(J.join(s.cwd,"tsconfig.json")))))return;let c=Ake(r),f=G.makeIdent("types",c);for(let p of Ut.allDependencies)typeof t.manifest[p].get(f.identHash)>"u"||t.manifest[p].delete(f.identHash)},cPt=(t,e)=>{e.publishConfig&&e.publishConfig.typings&&(e.typings=e.publishConfig.typings),e.publishConfig&&e.publishConfig.types&&(e.types=e.publishConfig.types)},uPt={configuration:{tsEnableAutoTypes:{description:"Whether Yarn should auto-install @types/ dependencies on 'yarn add'",type:"BOOLEAN",isNullable:!0,default:null}},hooks:{afterWorkspaceDependencyAddition:aPt,afterWorkspaceDependencyRemoval:lPt,beforeWorkspacePacking:cPt}},fPt=uPt;var pz={};Vt(pz,{VersionApplyCommand:()=>Y1,VersionCheckCommand:()=>V1,VersionCommand:()=>J1,default:()=>dPt,versionUtils:()=>W1});Ge();Ge();Yt();var W1={};Vt(W1,{Decision:()=>G1,applyPrerelease:()=>pke,applyReleases:()=>Az,applyStrategy:()=>sP,clearVersionFiles:()=>cz,getUndecidedDependentWorkspaces:()=>aP,getUndecidedWorkspaces:()=>UL,openVersionFile:()=>q1,requireMoreDecisions:()=>pPt,resolveVersionFiles:()=>oP,suggestStrategy:()=>fz,updateVersionFiles:()=>uz,validateReleaseDecision:()=>dy});Ge();Dt();wc();Yt();ql();var kA=ut(Ai()),APt=/^(>=|[~^]|)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,G1=(h=>(h.UNDECIDED="undecided",h.DECLINE="decline",h.MAJOR="major",h.MINOR="minor",h.PATCH="patch",h.PREMAJOR="premajor",h.PREMINOR="preminor",h.PREPATCH="prepatch",h.PRERELEASE="prerelease",h))(G1||{});function dy(t){let e=kA.default.valid(t);return e||je.validateEnum(O4(G1,"UNDECIDED"),t)}async function oP(t,{prerelease:e=null}={}){let r=new Map,s=t.configuration.get("deferredVersionFolder");if(!ce.existsSync(s))return r;let a=await ce.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=J.join(s,n),f=await ce.readFilePromise(c,"utf8"),p=ls(f);for(let[h,E]of Object.entries(p.releases||{})){if(E==="decline")continue;let C=G.parseIdent(h),S=t.tryWorkspaceByIdent(C);if(S===null)throw new Error(`Assertion failed: Expected a release definition file to only reference existing workspaces (${J.basename(c)} references ${h})`);if(S.manifest.version===null)throw new Error(`Assertion failed: Expected the workspace to have a version (${G.prettyLocator(t.configuration,S.anchoredLocator)})`);let P=S.manifest.raw.stableVersion??S.manifest.version,I=r.get(S),R=sP(E==="prerelease"?S.manifest.version:P,dy(E));if(R===null)throw new Error(`Assertion failed: Expected ${P} to support being bumped via strategy ${E}`);let N=typeof I<"u"?kA.default.gt(R,I)?R:I:R;r.set(S,N)}}return e&&(r=new Map([...r].map(([n,c])=>[n,pke(c,{current:n.manifest.version,prerelease:e})]))),r}async function cz(t){let e=t.configuration.get("deferredVersionFolder");ce.existsSync(e)&&await ce.removePromise(e)}async function uz(t,e){let r=new Set(e),s=t.configuration.get("deferredVersionFolder");if(!ce.existsSync(s))return;let a=await ce.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=J.join(s,n),f=await ce.readFilePromise(c,"utf8"),p=ls(f),h=p?.releases;if(h){for(let E of Object.keys(h)){let C=G.parseIdent(E),S=t.tryWorkspaceByIdent(C);(S===null||r.has(S))&&delete p.releases[E]}Object.keys(p.releases).length>0?await ce.changeFilePromise(c,nl(new nl.PreserveOrdering(p))):await ce.unlinkPromise(c)}}}async function q1(t,{allowEmpty:e=!1}={}){let r=t.configuration;if(r.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let s=await ka.fetchRoot(r.projectCwd),a=s!==null?await ka.fetchBase(s,{baseRefs:r.get("changesetBaseRefs")}):null,n=s!==null?await ka.fetchChangedFiles(s,{base:a.hash,project:t}):[],c=r.get("deferredVersionFolder"),f=n.filter(P=>J.contains(c,P)!==null);if(f.length>1)throw new nt(`Your current branch contains multiple versioning files; this isn't supported: +- ${f.map(P=>fe.fromPortablePath(P)).join(` +- `)}`);let p=new Set(je.mapAndFilter(n,P=>{let I=t.tryWorkspaceByFilePath(P);return I===null?je.mapAndFilter.skip:I}));if(f.length===0&&p.size===0&&!e)return null;let h=f.length===1?f[0]:J.join(c,`${Nn.makeHash(Math.random().toString()).slice(0,8)}.yml`),E=ce.existsSync(h)?await ce.readFilePromise(h,"utf8"):"{}",C=ls(E),S=new Map;for(let P of C.declined||[]){let I=G.parseIdent(P),R=t.getWorkspaceByIdent(I);S.set(R,"decline")}for(let[P,I]of Object.entries(C.releases||{})){let R=G.parseIdent(P),N=t.getWorkspaceByIdent(R);S.set(N,dy(I))}return{project:t,root:s,baseHash:a!==null?a.hash:null,baseTitle:a!==null?a.title:null,changedFiles:new Set(n),changedWorkspaces:p,releaseRoots:new Set([...p].filter(P=>P.manifest.version!==null)),releases:S,async saveAll(){let P={},I=[],R=[];for(let N of t.workspaces){if(N.manifest.version===null)continue;let U=G.stringifyIdent(N.anchoredLocator),W=S.get(N);W==="decline"?I.push(U):typeof W<"u"?P[U]=dy(W):p.has(N)&&R.push(U)}await ce.mkdirPromise(J.dirname(h),{recursive:!0}),await ce.changeFilePromise(h,nl(new nl.PreserveOrdering({releases:Object.keys(P).length>0?P:void 0,declined:I.length>0?I:void 0,undecided:R.length>0?R:void 0})))}}}function pPt(t){return UL(t).size>0||aP(t).length>0}function UL(t){let e=new Set;for(let r of t.changedWorkspaces)r.manifest.version!==null&&(t.releases.has(r)||e.add(r));return e}function aP(t,{include:e=new Set}={}){let r=[],s=new Map(je.mapAndFilter([...t.releases],([n,c])=>c==="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n])),a=new Map(je.mapAndFilter([...t.releases],([n,c])=>c!=="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n]));for(let n of t.project.workspaces)if(!(!e.has(n)&&(a.has(n.anchoredLocator.locatorHash)||s.has(n.anchoredLocator.locatorHash)))&&n.manifest.version!==null)for(let c of Ut.hardDependencies)for(let f of n.manifest.getForScope(c).values()){let p=t.project.tryWorkspaceByDescriptor(f);p!==null&&s.has(p.anchoredLocator.locatorHash)&&r.push([n,p])}return r}function fz(t,e){let r=kA.default.clean(e);for(let s of Object.values(G1))if(s!=="undecided"&&s!=="decline"&&kA.default.inc(t,s)===r)return s;return null}function sP(t,e){if(kA.default.valid(e))return e;if(t===null)throw new nt(`Cannot apply the release strategy "${e}" unless the workspace already has a valid version`);if(!kA.default.valid(t))throw new nt(`Cannot apply the release strategy "${e}" on a non-semver version (${t})`);let r=kA.default.inc(t,e);if(r===null)throw new nt(`Cannot apply the release strategy "${e}" on the specified version (${t})`);return r}function Az(t,e,{report:r,exact:s}){let a=new Map;for(let n of t.workspaces)for(let c of Ut.allDependencies)for(let f of n.manifest[c].values()){let p=t.tryWorkspaceByDescriptor(f);if(p===null||!e.has(p))continue;je.getArrayWithDefault(a,p).push([n,c,f.identHash])}for(let[n,c]of e){let f=n.manifest.version;n.manifest.version=c,kA.default.prerelease(c)===null?delete n.manifest.raw.stableVersion:n.manifest.raw.stableVersion||(n.manifest.raw.stableVersion=f);let p=n.manifest.name!==null?G.stringifyIdent(n.manifest.name):null;r.reportInfo(0,`${G.prettyLocator(t.configuration,n.anchoredLocator)}: Bumped to ${c}`),r.reportJson({cwd:fe.fromPortablePath(n.cwd),ident:p,oldVersion:f,newVersion:c});let h=a.get(n);if(!(typeof h>"u"))for(let[E,C,S]of h){let P=E.manifest[C].get(S);if(typeof P>"u")throw new Error("Assertion failed: The dependency should have existed");let I=P.range,R=!1;if(I.startsWith(Ei.protocol)&&(I=I.slice(Ei.protocol.length),R=!0,I===n.relativeCwd))continue;let N=I.match(APt);if(!N){r.reportWarning(0,`Couldn't auto-upgrade range ${I} (in ${G.prettyLocator(t.configuration,E.anchoredLocator)})`);continue}let U=s?`${c}`:`${N[1]}${c}`;R&&(U=`${Ei.protocol}${U}`);let W=G.makeDescriptor(P,U);E.manifest[C].set(S,W)}}}var hPt=new Map([["%n",{extract:t=>t.length>=1?[t[0],t.slice(1)]:null,generate:(t=0)=>`${t+1}`}]]);function pke(t,{current:e,prerelease:r}){let s=new kA.default.SemVer(e),a=s.prerelease.slice(),n=[];s.prerelease=[],s.format()!==t&&(a.length=0);let c=!0,f=r.split(/\./g);for(let p of f){let h=hPt.get(p);if(typeof h>"u")n.push(p),a[0]===p?a.shift():c=!1;else{let E=c?h.extract(a):null;E!==null&&typeof E[0]=="number"?(n.push(h.generate(E[0])),a=E[1]):(n.push(h.generate()),c=!1)}}return s.prerelease&&(s.prerelease=[]),`${t}-${n.join(".")}`}var Y1=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("--all",!1,{description:"Apply the deferred version changes on all workspaces"});this.dryRun=ge.Boolean("--dry-run",!1,{description:"Print the versions without actually generating the package archive"});this.prerelease=ge.String("--prerelease",{description:"Add a prerelease identifier to new versions",tolerateBoolean:!0});this.exact=ge.Boolean("--exact",!1,{description:"Use the exact version of each package, removes any range. Useful for nightly releases where the range might match another version."});this.recursive=ge.Boolean("-R,--recursive",{description:"Release the transitive workspaces as well"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["version","apply"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply all the deferred version bumps at once",details:` + This command will apply the deferred version changes and remove their definitions from the repository. + + Note that if \`--prerelease\` is set, the given prerelease identifier (by default \`rc.%n\`) will be used on all new versions and the version definitions will be kept as-is. + + By default only the current workspace will be bumped, but you can configure this behavior by using one of: + + - \`--recursive\` to also apply the version bump on its dependencies + - \`--all\` to apply the version bump on all packages in the repository + + Note that this command will also update the \`workspace:\` references across all your local workspaces, thus ensuring that they keep referring to the same workspaces even after the version bump. + `,examples:[["Apply the version change to the local workspace","yarn version apply"],["Apply the version change to all the workspaces in the local workspace","yarn version apply --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=this.prerelease?typeof this.prerelease!="boolean"?this.prerelease:"rc.%n":null,h=await oP(s,{prerelease:p}),E=new Map;if(this.all)E=h;else{let C=this.recursive?a.getRecursiveWorkspaceDependencies():[a];for(let S of C){let P=h.get(S);typeof P<"u"&&E.set(S,P)}}if(E.size===0){let C=h.size>0?" Did you want to add --all?":"";f.reportWarning(0,`The current workspace doesn't seem to require a version bump.${C}`);return}Az(s,E,{report:f,exact:this.exact}),this.dryRun||(p||(this.all?await cz(s):await uz(s,[...E.keys()])),f.reportSeparator())});return this.dryRun||c.hasErrors()?c.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};Ge();Dt();Yt();var _L=ut(Ai());var V1=class extends ft{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Open an interactive interface used to set version bumps"})}static{this.paths=[["version","check"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"check that all the relevant packages have been bumped",details:"\n **Warning:** This command currently requires Git.\n\n This command will check that all the packages covered by the files listed in argument have been properly bumped or declined to bump.\n\n In the case of a bump, the check will also cover transitive packages - meaning that should `Foo` be bumped, a package `Bar` depending on `Foo` will require a decision as to whether `Bar` will need to be bumped. This check doesn't cross packages that have declined to bump.\n\n In case no arguments are passed to the function, the list of modified files will be generated by comparing the HEAD against `master`.\n ",examples:[["Check whether the modified packages need a bump","yarn version check"]]})}async execute(){return this.interactive?await this.executeInteractive():await this.executeStandard()}async executeInteractive(){iw(this.context);let{Gem:r}=await Promise.resolve().then(()=>(WF(),LW)),{ScrollableItems:s}=await Promise.resolve().then(()=>(KF(),JF)),{FocusRequest:a}=await Promise.resolve().then(()=>(UW(),v2e)),{useListInput:n}=await Promise.resolve().then(()=>(VF(),S2e)),{renderForm:c}=await Promise.resolve().then(()=>($F(),ZF)),{Box:f,Text:p}=await Promise.resolve().then(()=>ut(Wc())),{default:h,useCallback:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState();let R=await q1(P);if(R===null||R.releaseRoots.size===0)return 0;if(R.root===null)throw new nt("This command can only be run on Git repositories");let N=()=>h.createElement(f,{flexDirection:"row",paddingBottom:1},h.createElement(f,{flexDirection:"column",width:60},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select workspaces.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select release strategies."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to save.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),U=({workspace:me,active:pe,decision:Be,setDecision:Ce})=>{let g=me.manifest.raw.stableVersion??me.manifest.version;if(g===null)throw new Error(`Assertion failed: The version should have been set (${G.prettyLocator(S,me.anchoredLocator)})`);if(_L.default.prerelease(g)!==null)throw new Error(`Assertion failed: Prerelease identifiers shouldn't be found (${g})`);let we=["undecided","decline","patch","minor","major"];n(Be,we,{active:pe,minus:"left",plus:"right",set:Ce});let ye=Be==="undecided"?h.createElement(p,{color:"yellow"},g):Be==="decline"?h.createElement(p,{color:"green"},g):h.createElement(p,null,h.createElement(p,{color:"magenta"},g)," \u2192 ",h.createElement(p,{color:"green"},_L.default.valid(Be)?Be:_L.default.inc(g,Be)));return h.createElement(f,{flexDirection:"column"},h.createElement(f,null,h.createElement(p,null,G.prettyLocator(S,me.anchoredLocator)," - ",ye)),h.createElement(f,null,we.map(Ae=>h.createElement(f,{key:Ae,paddingLeft:2},h.createElement(p,null,h.createElement(r,{active:Ae===Be})," ",Ae)))))},W=me=>{let pe=new Set(R.releaseRoots),Be=new Map([...me].filter(([Ce])=>pe.has(Ce)));for(;;){let Ce=aP({project:R.project,releases:Be}),g=!1;if(Ce.length>0){for(let[we]of Ce)if(!pe.has(we)){pe.add(we),g=!0;let ye=me.get(we);typeof ye<"u"&&Be.set(we,ye)}}if(!g)break}return{relevantWorkspaces:pe,relevantReleases:Be}},ee=()=>{let[me,pe]=C(()=>new Map(R.releases)),Be=E((Ce,g)=>{let we=new Map(me);g!=="undecided"?we.set(Ce,g):we.delete(Ce);let{relevantReleases:ye}=W(we);pe(ye)},[me,pe]);return[me,Be]},ie=({workspaces:me,releases:pe})=>{let Be=[];Be.push(`${me.size} total`);let Ce=0,g=0;for(let we of me){let ye=pe.get(we);typeof ye>"u"?g+=1:ye!=="decline"&&(Ce+=1)}return Be.push(`${Ce} release${Ce===1?"":"s"}`),Be.push(`${g} remaining`),h.createElement(p,{color:"yellow"},Be.join(", "))},le=await c(({useSubmit:me})=>{let[pe,Be]=ee();me(pe);let{relevantWorkspaces:Ce}=W(pe),g=new Set([...Ce].filter(se=>!R.releaseRoots.has(se))),[we,ye]=C(0),Ae=E(se=>{switch(se){case a.BEFORE:ye(we-1);break;case a.AFTER:ye(we+1);break}},[we,ye]);return h.createElement(f,{flexDirection:"column"},h.createElement(N,null),h.createElement(f,null,h.createElement(p,{wrap:"wrap"},"The following files have been modified in your local checkout.")),h.createElement(f,{flexDirection:"column",marginTop:1,paddingLeft:2},[...R.changedFiles].map(se=>h.createElement(f,{key:se},h.createElement(p,null,h.createElement(p,{color:"grey"},fe.fromPortablePath(R.root)),fe.sep,fe.relative(fe.fromPortablePath(R.root),fe.fromPortablePath(se)))))),R.releaseRoots.size>0&&h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"Because of those files having been modified, the following workspaces may need to be released again (note that private workspaces are also shown here, because even though they won't be published, releasing them will allow us to flag their dependents for potential re-release):")),g.size>3?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:R.releaseRoots,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===0,radius:1,size:2,onFocusRequest:Ae},[...R.releaseRoots].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:Z=>Be(se,Z)}))))),g.size>0?h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"The following workspaces depend on other workspaces that have been marked for release, and thus may need to be released as well:")),h.createElement(f,null,h.createElement(p,null,"(Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move the focus between the workspace groups.)")),g.size>5?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:g,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===1,radius:2,size:2,onFocusRequest:Ae},[...g].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:Z=>Be(se,Z)}))))):null)},{versionFile:R},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof le>"u")return 1;R.releases.clear();for(let[me,pe]of le)R.releases.set(me,pe);await R.saveAll()}async executeStandard(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return await s.restoreInstallState(),(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{let f=await q1(s);if(f===null||f.releaseRoots.size===0)return;if(f.root===null)throw new nt("This command can only be run on Git repositories");if(c.reportInfo(0,`Your PR was started right after ${he.pretty(r,f.baseHash.slice(0,7),"yellow")} ${he.pretty(r,f.baseTitle,"magenta")}`),f.changedFiles.size>0){c.reportInfo(0,"You have changed the following files since then:"),c.reportSeparator();for(let S of f.changedFiles)c.reportInfo(null,`${he.pretty(r,fe.fromPortablePath(f.root),"gray")}${fe.sep}${fe.relative(fe.fromPortablePath(f.root),fe.fromPortablePath(S))}`)}let p=!1,h=!1,E=UL(f);if(E.size>0){p||c.reportSeparator();for(let S of E)c.reportError(0,`${G.prettyLocator(r,S.anchoredLocator)} has been modified but doesn't have a release strategy attached`);p=!0}let C=aP(f);for(let[S,P]of C)h||c.reportSeparator(),c.reportError(0,`${G.prettyLocator(r,S.anchoredLocator)} doesn't have a release strategy attached, but depends on ${G.prettyWorkspace(r,P)} which is planned for release.`),h=!0;(p||h)&&(c.reportSeparator(),c.reportInfo(0,"This command detected that at least some workspaces have received modifications without explicit instructions as to how they had to be released (if needed)."),c.reportInfo(0,"To correct these errors, run `yarn version check --interactive` then follow the instructions."))})).exitCode()}};Ge();Yt();var HL=ut(Ai());var J1=class extends ft{constructor(){super(...arguments);this.deferred=ge.Boolean("-d,--deferred",{description:"Prepare the version to be bumped during the next release cycle"});this.immediate=ge.Boolean("-i,--immediate",{description:"Bump the version immediately"});this.strategy=ge.String()}static{this.paths=[["version"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply a new version to the current package",details:"\n This command will bump the version number for the given package, following the specified strategy:\n\n - If `major`, the first number from the semver range will be increased (`X.0.0`).\n - If `minor`, the second number from the semver range will be increased (`0.X.0`).\n - If `patch`, the third number from the semver range will be increased (`0.0.X`).\n - If prefixed by `pre` (`premajor`, ...), a `-0` suffix will be set (`0.0.0-0`).\n - If `prerelease`, the suffix will be increased (`0.0.0-X`); the third number from the semver range will also be increased if there was no suffix in the previous version.\n - If `decline`, the nonce will be increased for `yarn version check` to pass without version bump.\n - If a valid semver range, it will be used as new version.\n - If unspecified, Yarn will ask you for guidance.\n\n For more information about the `--deferred` flag, consult our documentation (https://yarnpkg.com/features/release-workflow#deferred-versioning).\n ",examples:[["Immediately bump the version to the next major","yarn version major"],["Prepare the version to be bumped to the next major","yarn version major --deferred"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=r.get("preferDeferredVersions");this.deferred&&(n=!0),this.immediate&&(n=!1);let c=HL.default.valid(this.strategy),f=this.strategy==="decline",p;if(c)if(a.manifest.version!==null){let E=fz(a.manifest.version,this.strategy);E!==null?p=E:p=this.strategy}else p=this.strategy;else{let E=a.manifest.version;if(!f){if(E===null)throw new nt("Can't bump the version if there wasn't a version to begin with - use 0.0.0 as initial version then run the command again.");if(typeof E!="string"||!HL.default.valid(E))throw new nt(`Can't bump the version (${E}) if it's not valid semver`)}p=dy(this.strategy)}if(!n){let C=(await oP(s)).get(a);if(typeof C<"u"&&p!=="decline"){let S=sP(a.manifest.version,p);if(HL.default.lt(S,C))throw new nt(`Can't bump the version to one that would be lower than the current deferred one (${C})`)}}let h=await q1(s,{allowEmpty:!0});return h.releases.set(a,p),await h.saveAll(),n?0:await this.cli.run(["version","apply"])}};var gPt={configuration:{deferredVersionFolder:{description:"Folder where are stored the versioning files",type:"ABSOLUTE_PATH",default:"./.yarn/versions"},preferDeferredVersions:{description:"If true, running `yarn version` will assume the `--deferred` flag unless `--immediate` is set",type:"BOOLEAN",default:!1}},commands:[Y1,V1,J1]},dPt=gPt;var hz={};Vt(hz,{WorkspacesFocusCommand:()=>K1,WorkspacesForeachCommand:()=>X1,default:()=>EPt});Ge();Ge();Yt();var K1=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ge.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ge.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ge.Rest()}static{this.paths=[["workspaces","focus"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);await s.restoreInstallState({restoreResolutions:!1});let c;if(this.all)c=new Set(s.workspaces);else if(this.workspaces.length===0){if(!a)throw new ar(s.cwd,this.context.cwd);c=new Set([a])}else c=new Set(this.workspaces.map(f=>s.getWorkspaceByIdent(G.parseIdent(f))));for(let f of c)for(let p of this.production?["dependencies"]:Ut.hardDependencies)for(let h of f.manifest.getForScope(p).values()){let E=s.tryWorkspaceByDescriptor(h);E!==null&&c.add(E)}for(let f of s.workspaces)c.has(f)?this.production&&f.manifest.devDependencies.clear():(f.manifest.installConfig=f.manifest.installConfig||{},f.manifest.installConfig.selfReferences=!1,f.manifest.dependencies.clear(),f.manifest.devDependencies.clear(),f.manifest.peerDependencies.clear(),f.manifest.scripts.clear());return await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n,persistProject:!1})}};Ge();Ge();Ge();Yt();var z1=ut(Go()),gke=ut(Ld());Ul();var X1=class extends ft{constructor(){super(...arguments);this.from=ge.Array("--from",{description:"An array of glob pattern idents or paths from which to base any recursion"});this.all=ge.Boolean("-A,--all",{description:"Run the command on all workspaces of a project"});this.recursive=ge.Boolean("-R,--recursive",{description:"Run the command on the current workspace and all of its recursive dependencies"});this.worktree=ge.Boolean("-W,--worktree",{description:"Run the command on all workspaces of the current worktree"});this.verbose=ge.Counter("-v,--verbose",{description:"Increase level of logging verbosity up to 2 times"});this.parallel=ge.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=ge.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=ge.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:g_([fo(["unlimited"]),$2(h_(),[m_(),d_(1)])])});this.topological=ge.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=ge.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=ge.Array("--include",[],{description:"An array of glob pattern idents or paths; only matching workspaces will be traversed"});this.exclude=ge.Array("--exclude",[],{description:"An array of glob pattern idents or paths; matching workspaces won't be traversed"});this.publicOnly=ge.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.dryRun=ge.Boolean("-n,--dry-run",{description:"Print the commands that would be run, without actually running them"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspaces","foreach"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `-W,--worktree` is set, Yarn will find workspaces to run the command on by looking at the current worktree.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `--dry-run` is set, Yarn will explain what it would do without actually doing anything.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. You can also use the `--no-private` flag to avoid running the command in private workspaces.\n\n The `-v,--verbose` flag can be passed up to twice: once to prefix output lines with the originating workspace's name, and again to include start/finish/timing log lines. Maximum verbosity is enabled by default in terminal environments.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish all packages","yarn workspaces foreach -A --no-private npm publish --tolerate-republish"],["Run the build script on all descendant packages","yarn workspaces foreach -A run build"],["Run the build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -Apt run build"],["Run the build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -Rpt --from '{workspace-a,workspace-b}' run build"]]})}static{this.schema=[tB("all",qf.Forbids,["from","recursive","since","worktree"],{missingIf:"undefined"}),y_(["all","recursive","since","worktree"],{missingIf:"undefined"})]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!this.all&&!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=this.cli.process([this.commandName,...this.args]),c=n.path.length===1&&n.path[0]==="run"&&typeof n.scriptName<"u"?n.scriptName:null;if(n.path.length===0)throw new nt("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let f=Ce=>{this.dryRun&&this.context.stdout.write(`${Ce} +`)},p=()=>{let Ce=this.from.map(g=>z1.default.matcher(g));return s.workspaces.filter(g=>{let we=G.stringifyIdent(g.anchoredLocator),ye=g.relativeCwd;return Ce.some(Ae=>Ae(we)||Ae(ye))})},h=[];if(this.since?(f("Option --since is set; selecting the changed workspaces as root for workspace selection"),h=Array.from(await ka.fetchChangedWorkspaces({ref:this.since,project:s}))):this.from?(f("Option --from is set; selecting the specified workspaces"),h=[...p()]):this.worktree?(f("Option --worktree is set; selecting the current workspace"),h=[a]):this.recursive?(f("Option --recursive is set; selecting the current workspace"),h=[a]):this.all&&(f("Option --all is set; selecting all workspaces"),h=[...s.workspaces]),this.dryRun&&!this.all){for(let Ce of h)f(` +- ${Ce.relativeCwd} + ${G.prettyLocator(r,Ce.anchoredLocator)}`);h.length>0&&f("")}let E;if(this.recursive?this.since?(f("Option --recursive --since is set; recursively selecting all dependent workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependents()]).flat())):(f("Option --recursive is set; recursively selecting all transitive dependencies"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependencies()]).flat())):this.worktree?(f("Option --worktree is set; recursively selecting all nested workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceChildren()]).flat())):E=null,E!==null&&(h=[...new Set([...h,...E])],this.dryRun))for(let Ce of E)f(` +- ${Ce.relativeCwd} + ${G.prettyLocator(r,Ce.anchoredLocator)}`);let C=[],S=!1;if(c?.includes(":")){for(let Ce of s.workspaces)if(Ce.manifest.scripts.has(c)&&(S=!S,S===!1))break}for(let Ce of h){if(c&&!Ce.manifest.scripts.has(c)&&!S&&!(await In.getWorkspaceAccessibleBinaries(Ce)).has(c)){f(`Excluding ${Ce.relativeCwd} because it doesn't have a "${c}" script`);continue}if(!(c===r.env.npm_lifecycle_event&&Ce.cwd===a.cwd)){if(this.include.length>0&&!z1.default.isMatch(G.stringifyIdent(Ce.anchoredLocator),this.include)&&!z1.default.isMatch(Ce.relativeCwd,this.include)){f(`Excluding ${Ce.relativeCwd} because it doesn't match the --include filter`);continue}if(this.exclude.length>0&&(z1.default.isMatch(G.stringifyIdent(Ce.anchoredLocator),this.exclude)||z1.default.isMatch(Ce.relativeCwd,this.exclude))){f(`Excluding ${Ce.relativeCwd} because it matches the --exclude filter`);continue}if(this.publicOnly&&Ce.manifest.private===!0){f(`Excluding ${Ce.relativeCwd} because it's a private workspace and --no-private was set`);continue}C.push(Ce)}}if(this.dryRun)return 0;let P=this.verbose??(this.context.stdout.isTTY?1/0:0),I=P>0,R=P>1,N=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.ceil(Ui.availableParallelism()/2):1,U=N===1?!1:this.parallel,W=U?this.interlaced:!0,ee=(0,gke.default)(N),ie=new Map,ue=new Set,le=0,me=null,pe=!1,Be=await Ot.start({configuration:r,stdout:this.context.stdout,includePrefix:!1},async Ce=>{let g=async(we,{commandIndex:ye})=>{if(pe)return-1;!U&&R&&ye>1&&Ce.reportSeparator();let Ae=mPt(we,{configuration:r,label:I,commandIndex:ye}),[se,Z]=hke(Ce,{prefix:Ae,interlaced:W}),[De,Re]=hke(Ce,{prefix:Ae,interlaced:W});try{R&&Ce.reportInfo(null,`${Ae?`${Ae} `:""}Process started`);let mt=Date.now(),j=await this.cli.run([this.commandName,...this.args],{cwd:we.cwd,stdout:se,stderr:De})||0;se.end(),De.end(),await Z,await Re;let rt=Date.now();if(R){let Fe=r.get("enableTimers")?`, completed in ${he.pretty(r,rt-mt,he.Type.DURATION)}`:"";Ce.reportInfo(null,`${Ae?`${Ae} `:""}Process exited (exit code ${j})${Fe}`)}return j===130&&(pe=!0,me=j),j}catch(mt){throw se.end(),De.end(),await Z,await Re,mt}};for(let we of C)ie.set(we.anchoredLocator.locatorHash,we);for(;ie.size>0&&!Ce.hasErrors();){let we=[];for(let[Z,De]of ie){if(ue.has(De.anchoredDescriptor.descriptorHash))continue;let Re=!0;if(this.topological||this.topologicalDev){let mt=this.topologicalDev?new Map([...De.manifest.dependencies,...De.manifest.devDependencies]):De.manifest.dependencies;for(let j of mt.values()){let rt=s.tryWorkspaceByDescriptor(j);if(Re=rt===null||!ie.has(rt.anchoredLocator.locatorHash),!Re)break}}if(Re&&(ue.add(De.anchoredDescriptor.descriptorHash),we.push(ee(async()=>{let mt=await g(De,{commandIndex:++le});return ie.delete(Z),ue.delete(De.anchoredDescriptor.descriptorHash),{workspace:De,exitCode:mt}})),!U))break}if(we.length===0){let Z=Array.from(ie.values()).map(De=>G.prettyLocator(r,De.anchoredLocator)).join(", ");Ce.reportError(3,`Dependency cycle detected (${Z})`);return}let ye=await Promise.all(we);ye.forEach(({workspace:Z,exitCode:De})=>{De!==0&&Ce.reportError(0,`The command failed in workspace ${G.prettyLocator(r,Z.anchoredLocator)} with exit code ${De}`)});let se=ye.map(Z=>Z.exitCode).find(Z=>Z!==0);(this.topological||this.topologicalDev)&&typeof se<"u"&&Ce.reportError(0,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return me!==null?me:Be.exitCode()}};function hke(t,{prefix:e,interlaced:r}){let s=t.createStreamReporter(e),a=new je.DefaultStream;a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()});let n=new Promise(f=>{s.on("finish",()=>{f(a.active)})});if(r)return[a,n];let c=new je.BufferStream;return c.pipe(a,{end:!1}),c.on("finish",()=>{a.end()}),[c,n]}function mPt(t,{configuration:e,commandIndex:r,label:s}){if(!s)return null;let n=`[${G.stringifyIdent(t.anchoredLocator)}]:`,c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[r%c.length];return he.pretty(e,n,f)}var yPt={commands:[K1,X1]},EPt=yPt;var tC=()=>({modules:new Map([["@yarnpkg/cli",Gv],["@yarnpkg/core",jv],["@yarnpkg/fslib",_2],["@yarnpkg/libzip",fv],["@yarnpkg/parsers",J2],["@yarnpkg/shell",mv],["clipanion",oB],["semver",IPt],["typanion",Ea],["@yarnpkg/plugin-essentials",hq],["@yarnpkg/plugin-catalog",yq],["@yarnpkg/plugin-compat",Bq],["@yarnpkg/plugin-constraints",_q],["@yarnpkg/plugin-dlx",Hq],["@yarnpkg/plugin-exec",qq],["@yarnpkg/plugin-file",Yq],["@yarnpkg/plugin-git",pq],["@yarnpkg/plugin-github",Kq],["@yarnpkg/plugin-http",zq],["@yarnpkg/plugin-init",Xq],["@yarnpkg/plugin-interactive-tools",JW],["@yarnpkg/plugin-jsr",zW],["@yarnpkg/plugin-link",XW],["@yarnpkg/plugin-nm",FY],["@yarnpkg/plugin-npm",FK],["@yarnpkg/plugin-npm-cli",qK],["@yarnpkg/plugin-pack",bV],["@yarnpkg/plugin-patch",XK],["@yarnpkg/plugin-pnp",wY],["@yarnpkg/plugin-pnpm",ez],["@yarnpkg/plugin-stage",az],["@yarnpkg/plugin-typescript",lz],["@yarnpkg/plugin-version",pz],["@yarnpkg/plugin-workspace-tools",hz]]),plugins:new Set(["@yarnpkg/plugin-essentials","@yarnpkg/plugin-catalog","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"])});function yke({cwd:t,pluginConfiguration:e}){let r=new Ca({binaryLabel:"Yarn Package Manager",binaryName:"yarn",binaryVersion:fn??""});return Object.assign(r,{defaultContext:{...Ca.defaultContext,cwd:t,plugins:e,quiet:!1,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr}})}function CPt(t){if(je.parseOptionalBoolean(process.env.YARN_IGNORE_NODE))return!0;let r=process.versions.node,s=">=18.12.0";if(Fr.satisfiesWithPrereleases(r,s))return!0;let a=new nt(`This tool requires a Node version compatible with ${s} (got ${r}). Upgrade Node, or set \`YARN_IGNORE_NODE=1\` in your environment.`);return Ca.defaultContext.stdout.write(t.error(a)),!1}async function Eke({selfPath:t,pluginConfiguration:e}){return await ze.find(fe.toPortablePath(process.cwd()),e,{strict:!1,usePathCheck:t})}function wPt(t,e,{yarnPath:r}){if(!ce.existsSync(r))return t.error(new Error(`The "yarn-path" option has been set, but the specified location doesn't exist (${r}).`)),1;process.on("SIGINT",()=>{});let s={stdio:"inherit",env:{...process.env,YARN_IGNORE_PATH:"1"}};try{(0,dke.execFileSync)(process.execPath,[fe.fromPortablePath(r),...e],s)}catch(a){return a.status??1}return 0}function BPt(t,e){let r=null,s=e;return e.length>=2&&e[0]==="--cwd"?(r=fe.toPortablePath(e[1]),s=e.slice(2)):e.length>=1&&e[0].startsWith("--cwd=")?(r=fe.toPortablePath(e[0].slice(6)),s=e.slice(1)):e[0]==="add"&&e[e.length-2]==="--cwd"&&(r=fe.toPortablePath(e[e.length-1]),s=e.slice(0,e.length-2)),t.defaultContext.cwd=r!==null?J.resolve(r):J.cwd(),s}function vPt(t,{configuration:e}){if(!e.get("enableTelemetry")||mke.isCI||!process.stdout.isTTY)return;ze.telemetry=new ZI(e,"puba9cdc10ec5790a2cf4969dd413a47270");let s=/^@yarnpkg\/plugin-(.*)$/;for(let a of e.plugins.keys())$I.has(a.match(s)?.[1]??"")&&ze.telemetry?.reportPluginName(a);t.binaryVersion&&ze.telemetry.reportVersion(t.binaryVersion)}function Ike(t,{configuration:e}){for(let r of e.plugins.values())for(let s of r.commands||[])t.register(s)}async function SPt(t,e,{selfPath:r,pluginConfiguration:s}){if(!CPt(t))return 1;let a=await Eke({selfPath:r,pluginConfiguration:s}),n=a.get("yarnPath"),c=a.get("ignorePath");if(n&&!c)return wPt(t,e,{yarnPath:n});delete process.env.YARN_IGNORE_PATH;let f=BPt(t,e);vPt(t,{configuration:a}),Ike(t,{configuration:a});let p=t.process(f,t.defaultContext);return p.help||ze.telemetry?.reportCommandName(p.path.join(" ")),await t.run(p,t.defaultContext)}async function bde({cwd:t=J.cwd(),pluginConfiguration:e=tC()}={}){let r=yke({cwd:t,pluginConfiguration:e}),s=await Eke({pluginConfiguration:e,selfPath:null});return Ike(r,{configuration:s}),r}async function VR(t,{cwd:e=J.cwd(),selfPath:r,pluginConfiguration:s}){let a=yke({cwd:e,pluginConfiguration:s});function n(){Ca.defaultContext.stdout.write(`ERROR: Yarn is terminating due to an unexpected empty event loop. +Please report this issue at https://github.com/yarnpkg/berry/issues.`)}process.once("beforeExit",n);try{process.exitCode=42,process.exitCode=await SPt(a,t,{selfPath:r,pluginConfiguration:s})}catch(c){Ca.defaultContext.stdout.write(a.error(c)),process.exitCode=1}finally{process.off("beforeExit",n),await ce.rmtempPromise()}}VR(process.argv.slice(2),{cwd:J.cwd(),selfPath:fe.toPortablePath(fe.resolve(process.argv[1])),pluginConfiguration:tC()});})(); +/** + @license + Copyright (c) 2015, Rebecca Turner + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + */ +/** + @license + Copyright Node.js contributors. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ +/** + @license + The MIT License (MIT) + + Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +/** + @license + Copyright Joyent, Inc. and other Node contributors. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/*! Bundled license information: + +is-number/index.js: + (*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + *) + +to-regex-range/index.js: + (*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + *) + +fill-range/index.js: + (*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + *) + +is-extglob/index.js: + (*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + *) + +is-glob/index.js: + (*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + *) + +queue-microtask/index.js: + (*! queue-microtask. MIT License. Feross Aboukhadijeh *) + +run-parallel/index.js: + (*! run-parallel. MIT License. Feross Aboukhadijeh *) + +git-url-parse/lib/index.js: + (*! + * buildToken + * Builds OAuth token prefix (helper function) + * + * @name buildToken + * @function + * @param {GitUrl} obj The parsed Git url object. + * @return {String} token prefix + *) + +object-assign/index.js: + (* + object-assign + (c) Sindre Sorhus + @license MIT + *) + +react/cjs/react.production.min.js: + (** @license React v17.0.2 + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +scheduler/cjs/scheduler.production.min.js: + (** @license React v0.20.2 + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +react-reconciler/cjs/react-reconciler.production.min.js: + (** @license React v0.26.2 + * react-reconciler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +is-windows/index.js: + (*! + * is-windows + * + * Copyright © 2015-2018, Jon Schlinkert. + * Released under the MIT License. + *) +*/ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 000000000000..03b3254d572c --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,3 @@ +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-4.12.0.cjs diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000000..956a3e341818 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,1301 @@ +# Authors ordered by first contribution. + +Sascha Depold +Israel De La Hoz +Vangelis Tsoumenis +Allan Carroll +corpix +Mark Kornfilt +Laurent Zuijdwijk +Meg Sharkey +Ram +Edward Tsech +Ramesh Nair +Brenden Grace +Gabriel Falcao +nov matake +Chase Geigle +Ross Grayton +dgf +SirUli +Lachèze Alexandre +Ken Perkins +Shawn Woodtke +Shawn Woodtke +Nao Iizuka +Meg Sharkey +Nazar Aziz +Andy Burke +Sorin Stoiana +Daniel Schwartz +Eugene Korbut +Grzegorz Niewisiewicz +Raúl Acuña +Mick Hansen +Jan Aagaard Meier +Rauno56 +Edgar Veiga +Rob Raux +Edward Lin <237lin@gmail.com> +joshm +Gavri Fernandez +Michael Philpott +kbackowski +René Oelke +David Chester +Gabe Hernandez +Jisoo Park +Aslak Hellesøy +Roman Ostolosh +Leif Johnson +kevin.xu +denysonique +Gavri +Joost de Vries +Dominik Lessel +Guilherme Souza +shane adams +slamkajs +Brian Johnson +Shaun Rader +Daniel Durante +Ricardo Graça +Thomas Watson Steen +Daniel Durante +Kevin Martin +carsondarling +Chia-liang Kao +Pasvaz +Konstantinos Vaggelakos +Alex Young +Ivan Popovski +solotimes +Michael Weibel +zanamixx +Jochem Maas +Mason Blier +Jonathan Crossman +James Sapara +Rob Fletcher +Filip Bonnevier +tjmehta +reedog117 +Alexandre Joly +Joshua Frederick +Sergey Klimov +Martin Aspeli +terraflubb +William Riancho +Javier Echenique +Kevin Beaty +Peter Braun +Bart Nagel +sevastos +Elliot Chong +sevastos +Scott Tadman +Jesse Clark +Benjamin Woodruff +Seth Samuel +Domas Lapinskas +freezy +Percy +Percy Perez +sonnym +Indra Gunawan +Michael Storgaard +Antoine Marcadet +Josh Marchán +Lemon +Jonathan M. Altman +Tony DiPasquale +whito +Joe Wilm +Jan Scheurer +hackwaly +Ming-Wei Shih +Andre Cerqueira +Brad Harris +wenyuxiang +David Rivera +Sebastian Hoitz +Po-Ying Chen +Daniel Durante +Goran Gajic +Charles Hill +Thanasis Polychronakis +edolfo +Kevin Jose Martin +Rafael Martins +Josemando Sobral +Will Whitney +Brandon Skari +Elliot Foster +Oliver Walzer +Patrik Björklund +Jan Aagaard Meier +thomascool +Anh-Kiet Ngo +Andreas Franzén +Ben Evans +Andreas Lubbe +mulderr +Andrej Mihajlov +Mick Hansen +Marc-Aurèle DARCHE +Simon Townsend +Steffen Persch +oubushixb +Martin Blumenstingl +maximeCony +Sascha Gehlich +iuri aranda +Yuri Sulyma +dominik zunftmeister +Evan Tahler +Michael Schonfeld +Michael Schonfeld +Overlook Motel +Bulkan Evcimen +Mathieu Amiot +Christian Schuhmann +Dan Kohn +Sohum Banerjea +jValdron +Joseph Verburg +Rui Fortes +Scott Dial +fundon +tomchentw +Rob Skillington +Rob Skillington +Kara +Mark Engel +Daniel Cohen +Tiago Ribeiro +Nuno Sousa +Matt Broadstone +heartsentwined +Loïc Mahieu +José Moreira +Sascha Depold +alexwl +변상필 +Siyang Bi +Terry Xu +Siyang Bi +Joshua Perry +Nick Mitchinson +edgarsilva +pola88 +Whitney Young +tngraessler +Sean McCann +Matt McFarland +stilia +Björn Dahlgren +Matthew Ohlman +Sean Arme +Chris Wilhelm +Jan Scheurer +Joey G +Thaddeus Quintin +SM@K +Dirk Raeder +rogerc +youxiachai +Aleksander Barszczewski +Ryan Pon +Jason Valdron +Thomas Wickham +Brian Romanko +Joe Lutz +Joe Lutz +jchbh-duplicate +Javier Cornejo +ryanking +overlookmotel +Craig Beck +jtschoonhoven +Sveinn Fannar Kristjansson +Mousset Axel +overlookmotel +Malte Legenhausen +Jack <3ch01c@gmail.com> +Victor Pontis +jharia +Ryan Fink +Holger +ekmartin +vvo +Alexander Kuzmin +paul-sh +haches +Daniel Shelton +Bryan Tong +Jacob Lauzier +Mikk Andresen +Jacob Lauzier +Igor Nawrocki +Jeff French +Daniel Hanover +Roman Shtylman +Josh Kalderimis +Simon Gaeremynck +Kyle +Jarkko Mönkkönen +David Pate +Yoni Jah +Teemu Heikkilä +fhc023 +qjroberts +Jerome C +Joel Trost +Jason Jung +Nikita +Jean-Philippe Monette +Dejan Ranisavljevic +Brian Lundin +Domi +Matheus Bratfisch +Mark Wainwright +paulmaszlik +Chris Dibbern +Nick Kleinschmidt +Tiago Ribeiro +Chris White +Garrett Gottlieb +James Irving-Swift +GuilhermeReda +Kara +alubbe +Philip J Briggs +Andrew Smith +James Billingham +Fırat KÜÇÜK +Preston Parry +Pranay S +Matt Conrad +Kiran Rao +Erik van de Ven +ksmithut +Robert Ing +Mike DeLucco +Ruslan Kovtun +Casey Watson +Nicolas Silberman +Thaddeus Quintin +catalint11main +toastynerd +Rui Marinho +Peng Hou +Y Kang +Michael Pauley +mdpauley +Denis Shirokov +Alexis Määttä Vinkler +Luca Moser +Willyham +Nick Acker +Rachel Higley +Amine Mouafik +Jumpei Ogawa +Arturo Guzman +Ryan Lane +Ruben Bridgewater +Jonatan Männchen +Jonatan Männchen +Steven Lu +Calvin Tan +Luc Verdier +Frank0628 +Brad Dunbar +Martin Otten +Matt Broadstone +Manuel Martinez +Robert Falkén +Pavel Karoukin +Stanislav +David Granqvist +Ricardo Lopes +harimohanraj89 +Ruben Bridgewater +Shoshomiga +John Wang +Richard Kho +John Giudici III +Binh Nguyen +Luca Moser +Gennaro Del Sorbo +Manuel Darveau +jub3i +vilicvane +Victor Zeng +Pete Mertz +Matthias Dietrich +Chase +tvoronov +Noah Chase +Alex Turek +Manuel Darveau +Mike Groseclose +XadillaX +Ben Stephenson +mickael couzinet +Derek Barnes +saneki +Pedro Pereira +Brian Lauber +syldor +Idris Mokhtarzada +Luke265 +Francisco Cardoso +hkulkarni +Petar Koretić +Jimmy Gong +Fred K. Schott +Cameron Pitt +Sanketh Katta +Justin Houk +Safonov Nikita +Jonathan Davis +MrVoltz +Geoffrey Plitt +Markus Hedlund +Sunshine Yin +Jason Murad +soundyogi +Chris Rinaldi +Pranav Raj S +John Giudici +Jeff Lee +Joseph Page +shakib bawa +Mike Ringrose +Richard Comley +David Langer +Gustav Pursche +Jamel Toms +Tim Perry +Daniel Friesen +rosston +Eamonn McHugh-Roohr +Tim Perry +joao.bortolozzo +Sami Jaktholm +Kenneth Kouot +wilsonianb +Christian Holm +Urs Wolfer +Alexey Torkhov +Gustav Pursche +Jim O'Brien +oznu +Ghost141 +elvo86 +Sean Herman +Gustavo Henke +Fengda Huang +Fräntz Miccoli +smihica +Sorin Neacsu +Carter Chung +superclarkk +Johannes Würbach +Stan S +Stan +Michael Kearns +Diosney Sarmiento +Joseph Page +Ivan Postic +Kevin Woo +laggingreflex +Alessandro Zanardi +Jaryd Carolin +Rodrigo de Almeida Pereira +Andy White +Aniket Panse +wKich +Brandon Dail +Clement Teule +Hack Reactor Students +jessicalc +Mohamad mehdi Kharatizadeh +Kenji Tayama +festo +Alex Booker += +Ali Taheri +superclarkk +Emil Janitzek +Tommy McLeroy +Matt Liszewski +Cody Rigney +william gueiros +Tyler Mandry +Tore Klock +Andrew Eddie +Nuno Sousa +jonnolen +Tiago Ribeiro +Rob Raux +Alejandro Oviedo +Trey Thomas +Frederik Nordahl Jul Sabroe +Yogesh Choudhary +James Kitamirike +Chris Antaki +User4martin +Samuel Beek +Nick Matenaar +Adam Back +User4martin +Sébastien Requiem +Dannii Willis +Simon Paris +Horcrux +Michael Robinson +Thiago Sigrist +Michael Robinson +Lewis Sobotkiewicz +Brandon +Eric Thompson +Jay Springate +Sebastian Wilzbach +Felix Becker +Limian Wang +Alex Schneider +Jim Cook +Luke Albao +Alrik Hausdorf +Sushant +Gabe Isman +Dallas Read +Shoshomiga +Aaron Nielsen +Aaron Nielsen +Nathan Marks +Jason Ku +Conner McNamara +Matthew Scragg +Osagie +Thalis Kalfigkopoulos +Jay Prakash +Cristian Lupu +zapatek +Ozum Eldogan +oss92 +Sushant +Jérôme Steunou +Sam Kelleher +mliszewski +Stefan Kleeschulte +Matthew Heck +Robert Grimes +Augusto Franzoia +Nuno Sousa +tornillo +Samuel Debionne +zhuqingling +Karthik Viswanathan +Renato Elias +Stuart P. Bentley +Joshua Kennedy +taylorhakes +Ashok Fernandez +Martin Zagora +EdwardGonzalez +Wong Yong Jie +Michael Kearns +Rafis Ganeyev +Austin J. Alexander +Caleb Bauermeister +Zihua Li +Connor Peet +Brendon Boshell +Jozef Hartinger +djstroky +Jonatan Lundqvist +mcarboni-redant +Michael Buckley +Mark Kornblum +Trey Thomas +Alexander Reiff +Kevin Mannix +Kyle Hotchkiss +Juan Hoyos +esteban.wagner +ming.deng +Nick +Alex Plescan +david +Amin Ogarrio +Travis Vander Hoop +Anton Drukh +daniel sim +Kevin Mannix +Marcel Pursche +Michael Haselton +Daniel Pedersen +Philip Patzer +Vadim Kazakov +Ricardo Lopes +Alex Ahn +loulin +Christoph Werner +Will Anderson +K.J. Valencik +Bertola Lucas +Yoyo Zhou +Giancarlo +Denis C de Azevedo +Angelo Paolo Obispo +Erich +gtelljohann +Cristi Scheye +Michael Kelley +Lewis Sobotkiewicz +Junliang Huang +Tom Jenkinson +Randall Crock +overlookmotel +Rui Marinho +Ryan Thomas +Westley Argentum Hennigh-Palermo +Michael D. Stemle, Jr +ivo-stefchev +Ryer Wong +Yaohan Chen +Kevin Simper +cristoforo +Cao Jiannan +mabeebam +Kieron Wiltshire +elliotthill +Jozef Hartinger +Brody Chen +Acker Dawn Apple +ivesdebruycker +Connor Hindley +Eric Koslow +hendrul +Ryan Lewis +GongYi +Simeon Theobald +Emmet McPoland +John +Jonathan +Josh Rickert +Michał Wadas +Alexander Mochalin +fogine +OKNoah +Faris Masad +Kevin Lacker +p512 +turbofoxwave +Erik Seliger +Todd Bluhm +Jeremy Morton +Jonathon Munsell +Paul Bombo +Francesco Infante +Adam Williams +Per Gullberg +Harshith Kashyap +Tim Scott +Nathan Schwartz +Yu Qi +contra +Robert Scheinpflug +Bryan Sapot +Chris Coggburn +legomind +Michael Gauthier +Lukas Spieß +David Tsai +daleoooo +Antonio Begines +yjhuoh +TimMurnaghan +Muhammad AbdulMoiz +Nicholas Drane +Robert Kieffer +Ash Lux +Brian Woodward +Daniel Hayes +Miguel Ruiz +Todd Wolfson +LoneWolfPR +C. T. Lin +Pedro Branco +elasticsearcher +Artem +Maks Nemisj +Nicholas W Fortner +Michael Kaufman +Cyrus Bowman +Youngrok Kim +Jiepei Min +Stefano Dalpiaz +Nate Silva +Fabio Espinosa +Weston Siegenthaler +Jedediah Smith +Cezar Berea +Vu Bui +Daniel Sim +Konstantin Meiklyar +Julian Meyer +Wing Lian +Mentor Palokaj +Seshadri Mahalingam +Bartosz Łęcki +Andrew Hookom +Connor Clark +Viliam Jobko +Jesse Atkinson +Arik Fraimovich +contactakagrawal +Louis-Rémi Babé +Satvik Sharma +Alexsey +Christopher Lis +anthony de silva +Lucas Vieira +Alexander James Phillips +Laurent VB +Martin H. Bramwell +Azeem Bande-Ali +Dana Woodman +YoussefOuirini +Damian Bushong +Even +Nick Schultz +Kevin Kwa +Doug Patti +Ivan Montiel +Alberto Souza +Leroy Witteveen +renaudholcombe +ezelohar +o-pikozh +Pete Peterson +reinerBa +Scott Rocha +ShaunParsons +Keo Lin +Chris Kalmar +clncln1 +Oleg Ozimok +Erik Seliger +Skylar Graika +Joshua Cullen +Garry Polley +Jon Koon +Michael Rosata +Paul Bienkowski +Grigory +Anshul Mittal +Harish K +William Cheung +Eugene Shilin +Jeremy Dagorn +John Schulz +miclill +Nick Slocum +Dan Rumney +Chad Huntley +Timshel Knoll-Miller +Caíque de Castro Soares da Silva +Cameron Reid +Brett Vitaz +Guillaume GRIMBERT +Johan Sandmark +Carlos Weckesser +Lars Tijhuis +木士羽 +Satana Charuwichitratana +Ilia Ermolin +Maurice Ronet Dominguez +Aleksandr +Turanchoks +Axetroy +Gareth Flowers +Yoni Jah +Amjad Masad +Yu Qi +Gabe Gorelick +DC +Matej Jellus +knibb800 <32358232+knibb800@users.noreply.github.com> +Marshall Thompson +Austin deBruyn +Veekas Shrivastava +Pravdomil +ydamani +Damian +Tyler Watson +Brendan Jurd +Dan Chung +netsgnut <284779+netsgnut@users.noreply.github.com> +cbauerme +mrHoliday +David Beitey +up9cloud +wjh000123 +gavrilyak +Jonathan Chen +Mickael Burguet +gfranco93 +Zhanwen "Phil" Chen +Dan Siroky +Andrii Muzalevskyi +TarekAlQaddy +Ali Essam +Laurens +arcdev1 +Mars Wong +Jonas Zhang <106856363@qq.com> +Joe Miller +musicformellons +Grant Carthew +Karthic Madanagopal +Ivan Akulov +markablov +Ilyes Hermellin +willrpike +Ian Sutherland +Oscar Reyes +Martin Gutfeldt +Ratanak Lun +Wanny Miarelli +Peggy Li +Andrew Balakirev +Michael McCabe +木士羽 +Michael McCabe +Jorrit Schippers +Luke Gumbley +Michael Storgaard +Mikko Kautto +Claire L +tim-soft +Gareth Oakley +Christian Holm +cavalor +Krishna Biradar +Jourdan Rodrigues +Rodrigo O +Kirill +Aaron Williams +Alan Bueno +Jeremy Fowler +Håkon Solbjørg +Varga Zsolt +Jacob Robertson +Andrii Stepaniuk +Jaime Pillora +Timothée Alby +tobybro32 +Manan Vaghasiya +Chris Jensen +Dylan Lingelbach +barsheshet +Konstantin Kutsevalov +maayany_h +SLOBYYYY +joshuacullen +Soumya Ranjan Mohanty +Michael Yates +Andrew Hessler-Loch +Andy Edwards +Chris Streeter +Yaroslav Repeta +Alexander Sergyeyev +Alejandro Aguirre +Maxime Suret +Scott BonAmi <1145657+sbonami@users.noreply.github.com> +Jahil Khalfe +Ducky Hong +Matt +Richard Kemp +Mirko Jotic +ylambda +jony89 +Bünyamin Benny Genel +Mrinal Purohit +u9r52sld +Tommy Montgomery +Pavel Kabakin +Tomoyuki Tsujimoto +Paul Mowat +Lawton Spelliscy +David DOLCIMASCOLO +Benjamin Lacy +Ali Ismayilov +Marco Barcellos +Marco Amado +DivX.Hu +andrewAtPaymentRails <39348113+andrewAtPaymentRails@users.noreply.github.com> +Dario +Evans Hauser +Antoine Pham +Manish Demblani +Alex Korn +Alastair Taft +Leonardo Gatica +frlinw +Takashi Sasaki +Benji Koltai +Peter Müller +Tiago Bonatti +Cameron Moss <40439344+c-moss-talk@users.noreply.github.com> +Nicolas Bouvrette +Fernando Fabricio dos Santos +ugogiordano87 +Justin Kalland <43973143+justinkalland@users.noreply.github.com> +Wei-Liang Liou +Pedro Augusto de Paula Barbosa +Simon Schick +Asutosh Palai +nack43 +Pavel +javiertury +Igor Ovsiannikov +Simon Schick +Diogo Simões <36140182+s1moe2@users.noreply.github.com> +Vladlen +Tommy van der Vorst +diego dupin +farisn <45453249+farisn@users.noreply.github.com> +Michal Bryc <1891931+Furchin@users.noreply.github.com> +Bryan Crowe +Samuela Keller +zhangshichun +Miroslav +Ivan Stumpf +joe beuckman +Andrew Schmadel +Petar Ivancevic +Indospace.io +Yazan Medanat +Igor Szymanski +Oliver Sayers <171312+SavageCore@users.noreply.github.com> +Pedro Machado +Rémy Dufour +Val +Travis Haby +Justin Abene +Andrey Goncharov +Julian Hundeloh +Florian Herrengt +Benoit MERIAUX +Yaroslav Admin +Waldir Pimenta +amitaymolko +Jesse Rosenberger +Sam Alexander +Alex +KillWolfVlad +杨奕 +Jeff Bernstein +Philipp +Jonathan Hoyt +Tony Brix +Chocobozzz +Andrew Heuermann +Martin +Mike Fowler +Patrick Geyer +Tony +Thomas Egbert Duursma +Sean Hagstrom +Emmanuel Gautier +Ægir Örn Símonarson +Vitaliy Zaytsev +Abady +Wouter De Schuyter +dy93 +Théo Frasquet +Musiienko Serhii +JP Denford +Levi Bostian +LittleSaya <2472609815@qq.com> +VictorKunzler +Noah Chase +Will Hogben +Corey Buckley +Zachary J. Rollyson +Chocobozzz +Jorge López Fernández +Evgeniy +Matthew Williams +mannol +pch18 +Niyobe <49311570+niyobe@users.noreply.github.com> +Shoya Shiraki <12c1055@gmail.com> +Ariel Barabas <810664+knoid@users.noreply.github.com> +Mike +Evan +Mike Sadowski +bparan +Gabe Gorelick +Yuping Zuo +Aleksandr +supperchong <2267805901@qq.com> +James Jansson +Gregory McLean +Chris Jensen <2920476+chrisjensen@users.noreply.github.com> +Liran Tal +Soonwoo Hong +Roman UNTILOV <51822105+roman-tek@users.noreply.github.com> +Dany Ral +Bill Li +Jordan +Thomas Hoppe +Raphaël Ricard <22248411+mrrinot@users.noreply.github.com> +Pasha Riger +wata-lb +Andreas Tschirpke +warkenji +Felix Wehnert +PeledYuval <50297862+PeledYuval@users.noreply.github.com> +Wender Pinto Machado +supperchong +João Eiras +Kunal Nain <31940639+nainkunal933@users.noreply.github.com> +Gal Jozsef +Matheus Assis +sergey-kudriavtsev +George Maddocks +Hugo Des +danetrata-bvsecurity <53787162+danetrata-bvsecurity@users.noreply.github.com> +Mohammad Tahsin <41298152+tahsinature@users.noreply.github.com> +Awwab Ul Haq +2kindsofcs <42531877+2kindsofcs@users.noreply.github.com> +milkwalk <1810542+milkwalk@users.noreply.github.com> +Ponnar Sankar A +Paul Oldridge +Mark Silverberg +Pedro Luiz Cabral Salomon Prado +Phil Gebauer +Samuli Asmala +ckeboss +BenBenHu +Ricardo Proença +Ivaylo Kirov +Luiz Victor Linhares Rocha +thesame +Esteban Ibarra +SAURABH CHOPRA +Alejandro Corredor +Martin Mena +Jonatan Alexis Anauati +wenpin +James Dietz +brownman +Huned Botee <24231+huned@users.noreply.github.com> +Todd Bealmear +Siddharth Udeniya +Florian Schmidt +Pelle Jacobs +Shahbaz Tariq +Hang Jiang +Ian Mathews +Darren "Dee" Lee +spinlud +Tyler +Oliver Emery +Muhammed Kalkan +Eike Lurz +Andrew Vereshchak +Alan Koger +andres112013 +Emiliano Gaston Delgadillo +Wojtek +segersniels +Colin Cheng +Tim Mensch +iamdi +Fabian Krüger +MartinJLee +Robin Baum +Vidit Kothari +Ben Leith +Chris Chew +McGrady Chen +Mohamed Nainar +Andrew Vereshchak +Kenny D +Sam Dhondt <53523625+sam-dt@users.noreply.github.com> +JacobLey <37151850+JacobLey@users.noreply.github.com> +Luis +Juarez Lustosa +Pankaj Vaghela +SuHun Han +Ivan Rubinson +Yufan Lou <2263580+louy2@users.noreply.github.com> +Vyacheslav Aristov +Sebastian Di Luzio +Thodoris Sotiropoulos +Marquitos +Over Martinez +Mikxail +Jose Miguel Colella +Adam Uhlíř +Rémi Weislinger <2735603+closingin@users.noreply.github.com> +Davide Mauri +Shahar Hadas +Harry Yu +Constantin Metz <58604248+Keimeno@users.noreply.github.com> +Carson McManus +Márton Csordás +Chris Northwood +Patrick Carnahan +Tommy Odom +Daan Roet +Hyunyoung Cho +Bagus Budi Cahyono +Valetek <34715110+Valetek@users.noreply.github.com> +Valentin Almeida--Lemaire +Fernando Maia <2966576+fernandomaia@users.noreply.github.com> +Vishal Sood +Piotr +Stanley Ume +Hiroki.Ihoriya <38400669+ia17011@users.noreply.github.com> +Ajaz Ur Rehman +Ricky Curtice +Tobias Büschel <13087421+tobiasbueschel@users.noreply.github.com> +Jano Kacer +Sébastien BRAMILLE <2752200+oktapodia@users.noreply.github.com> +Shivam Kalra +john gravois +sliterok +William Gurzoni +Augusto Amaral Pereira +Hugo Denizart +김성환 +Lorhan Sohaky +Tobin Brown +KapitanOczywisty <44417092+KapitanOczywisty@users.noreply.github.com> +Amin Mahrami +Sahil Rajput +Khinenw +Emilio Cristalli +markvp <6936351+markvp@users.noreply.github.com> +Daniel Schwartz <505433+danielschwartz@users.noreply.github.com> +Sid +Brian Schardt <35744893+brianschardt@users.noreply.github.com> +AllAwesome497 <47748690+AllAwesome497@users.noreply.github.com> +Mohab Abd El-Dayem +Daren McCulley +Fernando-Rodriguez <62259661+Fernando-Rodriguez@users.noreply.github.com> +Nicolas Padula +Gabriel Ramos +Selenium39 +wantao +roikoren755 <26850796+roikoren755@users.noreply.github.com> +Laure Retru-Chavastel +Abdul Raheem +colpachoque +SunMyeong Lee +Jairo Mancebo +Shreyansh shrey +Peter Timoshevsky +Joel Bradshaw +Anuj Joshi +Zach Azar +Felipe Silva +Bene-Graham <35272170+Bene-Graham@users.noreply.github.com> +Arneesh Aima <44923038+arneesh@users.noreply.github.com> +caolvchong +atrick-speedline <51718553+atrick-speedline@users.noreply.github.com> +George Zhao +Wesley Reed +Sergio Bernal +Alexey Lyakhov +salehdeh76 +Kamalakannan Jayaraman +Kamalakannan Jayaraman +Jan Kleinert +Mykyta Lypniahov <80581736+aboutml@users.noreply.github.com> +Ikko Ashimine +Victor Korzunin +Victor Korzunin +f[nZk] +dror-heller +drorh +Lukas Hroch <223967+lukashroch@users.noreply.github.com> +pahadimunu <72349611+pahadimunu@users.noreply.github.com> +Steve Schmitt +Abhishek Shah +Cryptos <74561974+cryptosbyte@users.noreply.github.com> +Bishwodahal <61019968+Bishwodahal@users.noreply.github.com> +Matthew Blasius +Aman Jain +l2ysho +Richard Solar +Deniz Kanmaz +Mattia Malonni +lzc <92916529+liangzhicheng0423@users.noreply.github.com> +zhicheng.liang +Marces Engel +Marces Engel +Sascha Depold +Constantin Metz +Adrien Boullé +Rick Bergfalk +Daniel Durante +Danny Sullivan +r253.hmdryou +WeRDyin +Jerry Zhou <33133448+Jerry-zhk@users.noreply.github.com> +Sander Mol +sander-mol +vikas gupta +Rik Smale <13023439+WikiRik@users.noreply.github.com> +sschwenker <30476508+sschwenker@users.noreply.github.com> +sschwenker +Mohamed El Mahallawy +Rafi Shamim +Mukesh Suthar +Mukesh Suthar +Matthew Blasius +WeRDyin +Marco Gonzalez +Rik Smale +manish kakoti +Marco Kerwitz +Jesse Peng +Jesse Peng +bishwodahal +Binit Kumar +Binit Kumar +Binit Kumar +Zoé +Sanyam Singhal <42322654+sanyamsinghal@users.noreply.github.com> +Jonathan (JC) Chen +John Mikolay +Adam Krebs +Zoé +Drethic +Дилян Палаузов +Ben Bernstein +Chris Crewdson +Sam Rui +Florian Morello +jesse23 +Shayon Mukherjee +julien <42035819+jlmbrt@users.noreply.github.com> +Anton +Andre Azzolini +Dev Agrawal +Chris Chudzicki +Joel Bradshaw +brano543 <46269759+brano543@users.noreply.github.com> +Vadim Nicolaev +Mark Irish +Nikita Sychev +emilio-notable <60191184+emilio-notable@users.noreply.github.com> +Guy Ellis +Guy Ellis +EtienneTurc <34250388+EtienneTurc@users.noreply.github.com> +KreativJos +Mark Irish +alluvm <44160042+alluvm@users.noreply.github.com> +Gabriel Marmitt Linassi +San Mônico +San 'rdn' Mônico +lbitoy +Andreas Lind +DraftMan +beninsydney <99156635+beninsydney@users.noreply.github.com> +Mike <19392121+mike-usa@users.noreply.github.com> +bowei-hatch <89421372+bowei-hatch@users.noreply.github.com> +PythonCoderAS +Ross Harrison +Johan Mattsson <39247600+mjunix@users.noreply.github.com> +nholmes3 <69873985+nholmes3@users.noreply.github.com> +SeongJaeSong <58547105+SeongJaeSong@users.noreply.github.com> +Nikolay +lishimin +Amit Kumar +vansh +Vansh Uppal <59787404+joker00777@users.noreply.github.com> +martineso +William Bert +Ana Margarida Silva +Evan Rittenhouse <84083822+evanrittenhouse@users.noreply.github.com> +Daryl Chan <6095637+dvrylc@users.noreply.github.com> +Necromansr +JaoodxD +JaoodxD <39469154+JaoodxD@users.noreply.github.com> +Trond Marius Øvstetun +Emmanuel Rodriguez <56197+emo-slido@users.noreply.github.com> +Damian Gadomski +bnorbi95 +Andrii Kostenko +Alex +Reto Fahrni <82324518+retfah@users.noreply.github.com> +Kevin Kee +Sourav Singh Rawat +Rik Smale +Harry Kao +Luke Hart <50547373+lohart13@users.noreply.github.com> +Bailey Kuebelbeck <71611929+baileykue@users.noreply.github.com> +Ricardo Spear +Ricardo Spear +Markus Suonto +Markus Suonto +elijahb +Ethan Setnik +Evan Rittenhouse +Chris +rcronin +lesion +Filip Havel +Edouard Benauw +Mateusz Burzyński +Leon Semmens <35655841+Synthetic-Dev@users.noreply.github.com> +siddhsql <127623723+siddhsql@users.noreply.github.com> +Mhashh <77043440+Mhashh@users.noreply.github.com> +Zayd Krunz <70227235+ShrootBuck@users.noreply.github.com> +Nikolay +Josh Watzman +Andreas Lind +Rakesh Lanjewar <36743306+rakeshlanjewar@users.noreply.github.com> +Rakesh Lanjewar +Hornwitser +Ajani Bilby +Ben Sykes +rcronin +Vinícius Stein Dahmer <30452636+viniciussdahmer@users.noreply.github.com> +Vatroslav Vrbanic +Guillaume Gautreau +Guillaume Gautreau +Lucas Bazetto +Lucas Bazetto +ckvv +Katerina Skroumpelou +Navid Naseri +webdiscus <6752572+webdiscus@users.noreply.github.com> +SEMICS <47321195+semics-tech@users.noreply.github.com> +Oliver Breeden +Libright +Leandro Costa +Martin Slota <46676886+martinslota@users.noreply.github.com> +Thiago F Pappacena +Dazhuang <159422091+dazhuangc@users.noreply.github.com> +Alex Escobedo +Miro Markaravanes +Andrew Sidhu +Andrew Sidhu +James Kiefer +Lukasz Jagielski +Seho Lee <31482043+seho0808@users.noreply.github.com> +Murli Prajapati +Murli Prajapati +Mohammed Mortaga + +# Generated by dev/update-authors.js diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 000000000000..99c16d134c0d --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -0,0 +1,68 @@ +# Code of Conduct + +All participants of Sequelize are expected to abide by our Code of Conduct, +both online (be it on GitHub, Slack, and others) and during in-person events (such as meetups) that are hosted and/or associated with Sequelize. + +## The Pledge + +In the interest of fostering an open and welcoming environment, we pledge to make participation in our project +and our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, +ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion (or lack thereof), or sexual identity and orientation. + +## The Standards + +Examples of behaviour that contributes to creating a positive environment include: + +- Using welcoming and inclusive language. +- Demonstrating empathy and kindness toward other people. +- Referring to people by their preferred pronouns and using gender-neutral pronouns when uncertain. +- Being respectful of differing viewpoints and experiences. +- Giving and gracefully accepting constructive feedback. +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience. + +Examples of unacceptable behaviour by participants include: + +- Trolling, insulting or derogatory comments, and personal or political attacks. +- Public or private harassment. +- Publishing others' private information, such as a physical or electronic address, without explicit permission. +- Not being respectful to reasonable communication boundaries, such as 'leave me alone,' 'go away,' or 'I’m not discussing this with you.'. +- The use of sexualized language or imagery, and sexual attention or advances of any kind. +- The use of disturbing language or imagery. +- Assuming or promoting any kind of inequality including but not limited to: age, body size, disability, ethnicity, + gender identity and expression, nationality and race, skin color, personal appearance, religion, or sexual identity and orientation. +- Other conduct which you know could reasonably be considered inappropriate in a professional setting. + +We pledge to prioritize marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding: + +- ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’ +- Reasonable communication of boundaries, such as 'leave me alone,' 'go away,' or 'I’m not discussing this with you.' +- Someone’s refusal to explain or debate social justice concepts +- Criticisms of racist, sexist, cissexist, or otherwise oppressive behavior or assumptions + +## Enforcement + +Violations of the Code of Conduct may be reported by, but is not limited to, [contacting a member of the team](./CONTACT.md), and using the "report content" feature on GitHub. +All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to the reporter of an incident. + +The project team is responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate +and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. +Corrective actions may go up to and including a temporary ban or permanent expulsion from the community without warning. + +The project team has the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, +and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify one of the members of the team +with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. + +## Attribution + +This Code of Conduct is adapted from: + +- [dev.to's code of conduct](https://dev.to/code-of-conduct) +- [Geek Feminism](https://geekfeminismdotorg.wordpress.com/about/code-of-conduct/) +- [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) +- [Berlin Code of Conduct](https://berlincodeofconduct.org/) diff --git a/CONTACT.md b/CONTACT.md index 0f7c3b636c8d..06e981b39fe8 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -1,10 +1,14 @@ -## Contact Us +# Contact Us In some cases you might want to reach the maintainers privately. These can be reporting a security vulnerability or any other specific cases. You can use the information below to contact maintainers directly. We will try to get back to you as soon as possible. -### Via Email +## Via Email -- **Pedro Augusto de Paula Barbosa** papb1996@gmail.com -- **Jan Aagaard Meier** janzeh@gmail.com - **Sascha Depold** sascha@depold.com +- **Zoé Cox** zoe@ephys.dev (accepts CoC violation reports, security reports) +- **Rik Smale** sequelize@riksmale.info (accepts CoC violation reports, security reports) + +## Via Slack + +Most of the team is also reachable privately through [our Slack](https://sequelize.org/slack) diff --git a/CONTRIBUTING.DOCS.md b/CONTRIBUTING.DOCS.md index 844993d92094..459e341214bb 100644 --- a/CONTRIBUTING.DOCS.md +++ b/CONTRIBUTING.DOCS.md @@ -3,18 +3,10 @@ The sequelize documentation is divided in two parts: - Tutorials, guides, and example based documentation are written in Markdown -- The API reference is generated automatically from source code comments with [ESDoc](http://esdoc.org) (which uses [JSDoc](http://usejsdoc.org) syntax). +- The API reference is generated automatically from source code comments and types with [TypeDoc](https://typedoc.org/). -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 at the [website repository](https://github.com/sequelize/website). -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 API reference locally, run `yarn docs` and open the generated `.typedoc-build/index.html` in your favorite browser. -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. +The whole documentation is rendered using [Docusaurus](https://docusaurus.io/) and continuously deployed to https://sequelize.org. See the [website repository](https://github.com/sequelize/website) for more information. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a454b3b55ce9..7343d42320f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,14 +2,14 @@ We are happy to see that you might be interested in contributing to Sequelize! There is no need to ask for permission to contribute. For example, anyone can open issues and propose changes to the source code (via Pull Requests). Here are some ways people can contribute: -* Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Opening Pull Requests to fix bugs or make other improvements -* Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them -* Helping to clarify issues opened by others, commenting and asking for clarification -* Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) -* Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/)) +- Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening Pull Requests to fix bugs or make other improvements +- Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them +- Helping to clarify issues opened by others, commenting and asking for clarification +- Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) +- Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](https://sequelize.org/slack)) Sequelize is strongly moved by contributions from people like you. All maintainers also work on their free time here. @@ -25,7 +25,7 @@ Learn to use [GitHub flavored markdown](https://help.github.com/articles/github- ### Opening an issue to report a bug -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. +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 [sequelize-sscce](https://github.com/sequelize/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. 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. @@ -35,28 +35,27 @@ You can also create and execute your SSCCE locally: see [Section 5](https://gith We're more than happy to accept feature requests! Before we get into how you can bring these to our attention, let's talk about our process for evaluating feature requests: -- A feature request can have three states - *approved*, *pending* and *rejected*. - - *Approved* feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. - - *Rejected* feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. - - *Pending* feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). - +- A feature request can have three states - _approved_, _pending_ and _rejected_. + - _Approved_ feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. + - _Rejected_ feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. + - _Pending_ feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). Please be sure to communicate the following: - 1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. +1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. - 2. Under what conditions are you anticipating this feature to be most beneficial? +2. Under what conditions are you anticipating this feature to be most beneficial? - 3. Why does it make sense that Sequelize should integrate this feature? +3. Why does it make sense that Sequelize should integrate this feature? - 4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. +4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. - If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): +If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): - - Something too similar to already exists within Sequelize - - This feature seems out of scope of what Sequelize exists to accomplish +- Something too similar to already exists within Sequelize +- This feature seems out of scope of what Sequelize exists to accomplish - We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! +We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! ### Opening an issue to request improvements to the documentation @@ -68,9 +67,9 @@ A Pull Request is a request for maintainers to "pull" a specific change in code 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). +The target of the Pull Request should be the `main` branch (or in rare cases the `v6` branch, if previously agreed with a maintainer). -Please check the *allow edits from maintainers* box when opening it. Thank you in advance for any pull requests that you open! +Please check the _allow edits from maintainers_ box when opening it. Thank you in advance for any pull requests that you open! If you started to work on something but didn't finish it yet, you can open a draft pull request if you want (by choosing the "draft" option). Maintainers will know that it's not ready to be reviewed yet. @@ -80,60 +79,99 @@ If your pull request implements a new feature, it's better if the feature was al Once you open a pull request, our automated checks will run (they take a few minutes). Make sure they are all passing. If they're not, make new commits to your branch fixing that, and the pull request will pick them up automatically and rerun our automated checks. -Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a *flaky test* that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the *flaky test* and merge your pull request, so don't worry. +Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a _flaky test_ that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the _flaky test_ and merge your pull request, so don't worry. A pull request that fixes a bug or implements a new feature must add at least one automated test that: - Passes - Would not pass if executed without your implementation - ## How to prepare a development environment for Sequelize ### 0. Requirements Most operating systems provide all the needed tools (including Windows, Linux and MacOS): -* Mandatory: +- Mandatory: - * [Node.js](http://nodejs.org) - * [Git](https://git-scm.com/) + - [Node.js](http://nodejs.org), it is preferred to use the current LTS version of Node + - [Git](https://git-scm.com/) -* Optional (recommended): +- Optional (recommended): - * [Docker](https://docs.docker.com/get-docker/) - * It is not mandatory because you can easily locally run tests against SQLite without it. - * It is practically mandatory if you want to locally run tests against any other database engine (MySQL, MariaDB, Postgres and MSSQL), unless you happen to have the engine installed and is willing to make some manual configuration. - * [Visual Studio Code](https://code.visualstudio.com/) - * [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) - * Also run `npm install --global editorconfig` to make sure this extension will work properly - * [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + - [Docker](https://docs.docker.com/get-docker/) and [Docker Compose Plugin](https://docs.docker.com/compose/install/) + - 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,Db2 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` (or `yarn global add editorconfig`) to make sure this extension will work properly + - [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + - [Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) ### 1. Clone the repository -Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the *fork* button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. +Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the _fork_ button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. ### 2. Install the Node.js dependencies -Run `npm install` (or `yarn install`) within the cloned repository folder. +Run `yarn install` within the cloned repository folder to install the dependencies. + +Once installed, run the `yarn build` command to build the project. + +#### 2.1 Adding and updating dependencies + +[Yarn v4](https://yarnpkg.com//) is used in the CI/CD pipeline so adding and updating dependencies must be done with Yarn. + +#### 2.2 Running commands + +Sequelize is a monorepo and uses `lerna` to run scripts in each of the packages. The syntax for the commands is: `yarn lerna run` followed by the script name. For example: + +``` +yarn lerna run test-unit +``` + +By default, the `yarn lerna run` command will run the script in all packages which have a matching script. By appending `--scope=package_name` to the command (where `package_name` is the name of the package you want to run the script on) you can select a specific package to run the script on. For example: + +``` +yarn lerna run test-unit --scope=@sequelize/core +``` + +Lerna caching is enabled and the following commands: + +- `yarn build` +- `yarn test-typings` +- `yarn test-unit` + +Currently the caching is configured to watch the `src` folder and `package.json` and `tsconfig.json` files in each package for production changes and all files in each package for default changes. + +This means that running the `yarn build` command and not making changes to the production files (see above), the output of the commands loaded from cache rather than the command executing. When running `yarn test-typings` or `yarn test-unit`, any changes in the package folder will cause command to run rather than the results loaded from cache. + +If you run into any issues with the cache, running the command `yarn dlx nx reset` will reset the cache. + +For more information about using `lerna` commands, use the [Lerna Documentation](https://lerna.js.org/docs/api-reference/commands). ### 3. Prepare local databases to run tests If you're happy to run tests only against an SQLite database, you can skip this section. -#### 3a. With Docker (recommended) +#### 3.1. With Docker (recommended) If you have Docker installed, use any of the following commands to start fresh local databases of the dialect of your choice: -* `npm run setup-mariadb` -* `npm run setup-mysql` -* `npm run setup-postgres` -* `npm run setup-mssql` +- `yarn start-mariadb-oldest` (for MariaDB 10.4) or `yarn start-mariadb-latest` (for MariaDB 11.3) +- `yarn start-mysql-oldest` (for MySQL 8.0) or `yarn start-mysql-latest` (for MySQL 8.3) +- `yarn start-postgres-oldest` (for Postgres 11) or `yarn start-postgres-latest` (for Postgres 15) +- `yarn start-mssql-oldest` (for MSSQL 2017) or `yarn start-mssql-latest` (for MSSQL 2022) +- `yarn start-db2-oldest` (for Db2 11.5.5.1) or `yarn start-db2-latest` (for Db2 11.5.9.0) -*Note:* if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). +_Note:_ if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests (or an SSCCE). +You can run `yarn stop-X` to stop the servers once you're done. + +The docker containers retain storage in volumes to improve startup time. If you run into to any issues with a container, you can run `yarn reset-{dialect}` or `yarn reset-all` to remove the containers and volumes. + ##### 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: @@ -142,77 +180,102 @@ You can also easily start a local [pgadmin4](https://www.pgadmin.org/docs/pgadmi docker run -d --name pgadmin4 -p 8888:80 -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=sequelize_test' dpage/pgadmin4 ``` -#### 3b. Without Docker - -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). - - +#### 3.2. Without Docker +You will have to manually install and configure each of database engines you want. Check the `dev/dialect-name` folder within this repository and look carefully at how it is defined via Docker and via the auxiliary bash script, and mimic that exactly (except for the database name, username, password, host and port, that you can customize via the `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT` environment variables, respectively). Please refer to the [Version Policy](https://sequelize.org/releases/) for the oldest supported version of each database. ### 4. Running tests Before starting any work, try to run the tests locally in order to be sure your setup is fine. Start by running the SQLite tests: ``` -npm run test-sqlite +yarn lerna run test-sqlite3 ``` Then, if you want to run tests for another dialect, assuming you've set it up as written on section 3, run the corresponding command: -* `npm run test-mysql` -* `npm run test-mariadb` -* `npm run test-postgres` -* `npm run test-mssql` +- `yarn lerna run test-mysql` +- `yarn lerna run test-mariadb` +- `yarn lerna run test-postgres` +- `yarn lerna run test-mssql` +- `yarn lerna run test-db2` + +There are also the `test-unit-*` and `test-integration-*` sets of scripts (for example, `test-integration-postgres`). -There are also the `test-unit-*` and `test-integration-*` sets of npm scripts (for example, `test-integration-postgres`). +_Note:_ when running these test, you will need to run `yarn build` after you have made changes to the source code for these changes to affect the tests. The `yarn lerna run test-{dialect}` command does this for you. #### 4.1. Running only some tests -While you're developing, you may want to execute only a single test (or a few), instead of executing everything (which takes some time). You can easily achieve this by modifying the `.mocharc.jsonc` file (but don't commit those changes!) to use `spec` (and maybe `grep`) from Mocha to specify the desired tests. Then, simply call `DIALECT=some-dialect npx mocha` from your terminal (example: `DIALECT=postgres npx mocha`). +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 inside the package's root directory (but don't commit those changes!) to use `spec` (and maybe `grep`) from Mocha to specify the desired tests. Then, from your terminal, navigate to the package's root directory and simply call `DIALECT=some-dialect yarn mocha` (example: `DIALECT=postgres yarn mocha`). The package root directory is where the package.json file for the package is located. For example with the @sequelize/core package, the package root directory is [packages/core](https://github.com/sequelize/sequelize/tree/main/packages/core). -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)): +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 `yarn setup-*` calls, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended) and you are in the package's root directory): ``` -DIALECT=mariadb npx mocha && DIALECT=mysql npx mocha && DIALECT=postgres npx mocha && DIALECT=sqlite npx mocha && DIALECT=mssql npx mocha +DIALECT=mariadb yarn mocha && DIALECT=mysql yarn mocha && DIALECT=postgres yarn mocha && DIALECT=sqlite3 yarn mocha && DIALECT=mssql yarn mocha && DIALECT=db2 yarn mocha ``` - ### 5. Running an SSCCE -You can modify the `sscce.js` file (at the root of the repository) to create an [SSCCE](http://www.sscce.org/). +What is SSCCE? [find out here](http://www.sscce.org/). + +You can modify the `sscce.ts` file (at the root of the repository) to create an SSCCE. Run it for the dialect of your choice using one of the following commands: -* `npm run sscce-mariadb` -* `npm run sscce-mysql` -* `npm run sscce-postgres` -* `npm run sscce-sqlite` -* `npm run sscce-mssql` +- `npm run sscce-mariadb` / `yarn sscce-mariadb` +- `npm run sscce-mysql` / `yarn sscce-mysql` +- `npm run sscce-postgres` / `yarn sscce-postgres` +- `npm run sscce-sqlite3` / `yarn sscce-sqlite3` +- `npm run sscce-mssql` / `yarn sscce-mssql` +- `npm run sscce-db2` / `yarn sscce-db2` _Note:_ First, you need to set up (once) the database instance for corresponding dialect, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended). #### 5.1. Debugging an SSCCE with Visual Studio Code -If you open the `package.json` file with Visual Studio Code, you will find a small `debug` button rendered right above the `"scripts": {` line. By clicking it, a popup will appear where you can choose which npm script you want to debug. Select one of the `sscce-*` scripts (listed above) and VSCode will immediately launch your SSCCE in debug mode (meaning that it will stop on any breakpoints that you place within `sscce.js` or any other Sequelize source code). - +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 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.ts` 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`. +We squash all commits into a single one when we merge your PR. +That means you don't have to follow any convention in your commit messages, +but you will need to follow the [Conventional Commits Conventions](https://www.conventionalcommits.org/en/v1.0.0/) when writing the title of your PR. + +We will then use the title of your PR as the message of the Squash Commit. It will then be used to automatically generate a changelog and calculate the next [semver](https://semver.org/) version number. + +We use a simple conventional commits convention: + +- The allowed commit types are: `docs`, `feat`, `fix`, `meta`. +- We allow the following commit scopes (they're the list of packages): + - `core` + - `utils` + - `cli` + - `validator.js` + - `postgres` + - `mysql` + - `mariadb` + - `sqlite3` + - `mssql` + - `db2` + - `ibmi` + - `snowflake` +- If your changes impact more than one scope, simply omit the scope. Example: ``` -feat(pencil): add `graphiteWidth` option +feat(postgres): support specifying a custom name for enums ``` -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. +Happy hacking and thank you for contributing. # Coding guidelines -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. +Sequelize uses eslint and prettier to enforce a consistent coding style. +We recommend configuring them in your IDE to automatically format your code on save. + +You can format your code at any point by running `yarn format`. +Any issue not automatically fixed by this command will be printed to the console. # Contributing to the documentation diff --git a/ENGINE.md b/ENGINE.md deleted file mode 100644 index 57f2d36e1066..000000000000 --- a/ENGINE.md +++ /dev/null @@ -1,10 +0,0 @@ -# Database Engine Support - -## v6 -| Engine | Minimum supported version | -| :------------: | :------------: | -| PostgreSQL | [9.5](https://www.postgresql.org/docs/9.5/ ) | -| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | -| MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | -| Microsoft SQL Server | `12.0.2000` | -| SQLite | [3.0](https://www.sqlite.org/version3.html) | diff --git a/LICENSE b/LICENSE index 829433245e37..178c4eeb28ca 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-present Sequelize contributors +Copyright (c) 2014-present Sequelize Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b1ae2d0b4297..e011b37e803d 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,83 @@ -# Sequelize - -[![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) - -[![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) +

Sequelize logo

+

Sequelize

+ +[![npm version](https://badgen.net/npm/v/@sequelize/core)](https://www.npmjs.com/package/@sequelize/core) +[![npm downloads](https://badgen.net/npm/dm/@sequelize/core)](https://www.npmjs.com/package/@sequelize/core) +[![contributors](https://img.shields.io/github/contributors/sequelize/sequelize)](https://github.com/sequelize/sequelize/graphs/contributors) +[![Open Collective](https://img.shields.io/opencollective/backers/sequelize)](https://opencollective.com/sequelize#section-contributors) +[![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) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +Sequelize is an easy-to-use and 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), [DB2](https://en.wikipedia.org/wiki/IBM_Db2_Family), [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server), [Snowflake](https://www.snowflake.com/), [Oracle DB](https://www.oracle.com/database/) and [Db2 for IBM i](https://www.ibm.com/support/pages/db2-ibm-i). It features solid transaction support, relations, eager and lazy loading, read replication and more. + +Would you like to contribute? Read [our contribution guidelines](./CONTRIBUTING.md) to know more. There are many ways to help! 😃 -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. +## 🚀 Seeking New Maintainers for Sequelize! 🚀 -Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. +We're looking for new maintainers to help finalize and release the next major version of Sequelize! If you're passionate about open-source and database ORMs, we'd love to have you onboard. -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). +### 💰 Funding Available -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. +We distribute **$2,500 per quarter** among maintainers and have additional funds for full-time contributions. -### v6 Release +### 🛠️ What You’ll Work On -You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). +- Finalizing and releasing Sequelize’s next major version +- Improving TypeScript support and database integrations +- Fixing critical issues and shaping the ORM’s future -## Note: Looking for maintainers! +### 🤝 How to Get Involved -Recently, a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: +Interested? Join our Slack and reach out to **@WikiRik** or **@sdepold**: +➡️ **[sequelize.org/slack](https://sequelize.org/slack)** -We are looking for more core maintainers who are interested in improving/fixing our TypeScript typings, improving the documentation, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. +We’d love to have you on board! 🚀 -If that sounds interesting to you, please reach out to us on [our Slack channel](https://sequelize.slack.com/) by sending a direct message to *Pedro A P B*. If you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/). We are looking forward to meet you! +## :computer: Getting Started -## Installation +Ready to start using Sequelize? Head to [sequelize.org](https://sequelize.org) to begin! -```sh -$ npm i sequelize # This will install v6 +- [Our Getting Started guide for Sequelize 6 (stable)](https://sequelize.org/docs/v6/getting-started) +- [Our Getting Started guide for Sequelize 7 (alpha)](https://sequelize.org/docs/v7/getting-started) -# And one of the following: -$ npm i pg pg-hstore # Postgres -$ npm i mysql2 -$ npm i mariadb -$ npm i sqlite3 -$ npm i tedious # Microsoft SQL Server -``` +## :money_with_wings: Supporting the project -## Documentation +Do you like Sequelize and would like to give back to the engineering team behind it? -- [v6 Documentation](https://sequelize.org/master) -- [v5/v4/v3 Documentation](https://sequelize.org) -- [Contributing](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) +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. ❤️ -## Responsible disclosure +## :pencil: Major version changelog -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. +Please find upgrade information to major versions here: -## Resources +- [Upgrade from v5 to v6](https://sequelize.org/docs/v6/other-topics/upgrade-to-v6) +- [Upgrade from v6 to v7](https://sequelize.org/docs/v7/other-topics/upgrade-to-v7) +## :book: Resources + +- [Documentation](https://sequelize.org) +- [Databases Compatibility Table](https://sequelize.org/releases/) - [Changelog](https://github.com/sequelize/sequelize/releases) -- [Slack Inviter](http://sequelize-slack.herokuapp.com/) +- [Discussions](https://github.com/sequelize/sequelize/discussions) +- [Slack](https://sequelize.org/slack) - [Stack Overflow](https://stackoverflow.com/questions/tagged/sequelize.js) -### Tools +### :wrench: 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) - [For GraphQL](https://github.com/mickhansen/graphql-sequelize) - [For CockroachDB](https://github.com/cockroachdb/sequelize-cockroachdb) -- [Plugins](https://sequelize.org/master/manual/resources.html) +- [Awesome Sequelize](https://sequelize.org/docs/v7/other-topics/resources/) +- [For YugabyteDB](https://github.com/yugabyte/sequelize-yugabytedb) + +### :speech_balloon: Translations + +- [English](https://sequelize.org) (Official) +- [中文文档](https://github.com/demopark/sequelize-docs-Zh-CN) (Unofficial) -### Translations +## :warning: Responsible disclosure -- [English](https://sequelize.org/master) (OFFICIAL) -- [中文文档](https://github.com/demopark/sequelize-docs-Zh-CN) (UNOFFICIAL) +If you have security issues to report, please refer to our +[Responsible Disclosure Policy](./SECURITY.md) for more details. diff --git a/SECURITY.md b/SECURITY.md index f204f8e8e29e..122cfbf0ae70 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,20 +4,14 @@ The following table describes the versions of this project that are currently supported with security updates: -| Version | Supported | -| ------- | ------------------ | -| 6.x | :heavy_check_mark: | -| 5.x | :heavy_check_mark: | +| Version | Supported | +| ----------- | ------------------ | +| 7.x (alpha) | :heavy_check_mark: | +| 6.x | :heavy_check_mark: | ## Responsible disclosure policy At Sequelize, we prioritize security issues and will try to fix them as soon as they are disclosed. -If you discover a security vulnerability, please reach out to the project maintainers privately. You can find related information in [CONTACT.md](./CONTACT.md). - -After validating & discussing scope of security vulnerability, we will set a time-frame for patch distribution. This time-frame may vary depending upon the nature of vulnerability. - -Once affected versions are patched you may report security issue to any Node.js security vulnerability database. A few which we have worked with in past are listed below. - -- [NPM](https://www.npmjs.com/advisories/report) -- [Snyk.io](https://snyk.io/vulnerability-disclosure) +If you discover a security vulnerability, please create a security advisory [here](https://github.com/sequelize/sequelize/security/advisories/new). +Otherwise, contact the project maintainers privately. You can find related information in [CONTACT.md](./CONTACT.md). diff --git a/build-packages.mjs b/build-packages.mjs new file mode 100755 index 000000000000..7a5263810a13 --- /dev/null +++ b/build-packages.mjs @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +/* eslint-disable unicorn/prefer-top-level-await */ + +import { build } from 'esbuild'; +import glob from 'fast-glob'; +import childProcess from 'node:child_process'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +// if this script is moved, this will need to be adjusted + +const exec = promisify(childProcess.exec); + +const rootDir = path.dirname(fileURLToPath(import.meta.url)); +const packages = await fs.readdir(`${rootDir}/packages`); + +const packageName = process.argv[2]; +if (!packageName || !packages.includes(packageName)) { + console.error( + `Please specify the name of the package to build: node build-packages.mjs (one of ${packages.join(', ')})`, + ); + process.exit(1); +} + +console.info(`Compiling package ${packageName}`); + +const packageDir = `${rootDir}/packages/${packageName}`; +const sourceDir = path.join(packageDir, 'src'); +const libDir = path.join(packageDir, 'lib'); + +const [sourceFiles] = await Promise.all([ + // Find all .js and .ts files from /src. + glob(`${glob.convertPathToPattern(sourceDir)}/**/*.{mjs,cjs,js,mts,cts,ts}`, { + onlyFiles: true, + absolute: false, + }), + // Delete /lib for a full rebuild. + rmDir(libDir), +]); + +const filesToCompile = []; +const filesToCopyToLib = []; + +for (const file of sourceFiles) { + // mjs files cannot be built as they would be compiled to commonjs + if (file.endsWith('.mjs') || file.endsWith('.d.ts')) { + filesToCopyToLib.push(file); + } else { + filesToCompile.push(file); + } +} + +await Promise.all([ + copyFiles(filesToCopyToLib, sourceDir, libDir), + build({ + // Adds source mapping + sourcemap: true, + // The compiled code should be usable in node v18 + target: 'node18', + // The source code's format is commonjs. + format: 'cjs', + + outdir: libDir, + entryPoints: filesToCompile.map(file => path.resolve(file)), + }), + + exec('tsc --emitDeclarationOnly', { + env: { + // binaries installed from modules have symlinks in + // /node_modules/.bin. + PATH: `${process.env.PATH || ''}:${path.join(rootDir, 'node_modules/.bin')}`, + }, + cwd: packageDir, + }), +]); + +const indexFiles = await glob(`${glob.convertPathToPattern(libDir)}/**/index.d.ts`, { + onlyFiles: true, + absolute: false, +}); + +// copy .d.ts files to .d.mts to provide typings for the ESM entrypoint +await Promise.all( + indexFiles.map(async indexFile => { + await fs.copyFile(indexFile, indexFile.replace(/.d.ts$/, '.d.mts')); + }), +); + +async function rmDir(dirName) { + try { + await fs.stat(dirName); + await fs.rm(dirName, { recursive: true }); + } catch { + /* no-op */ + } +} + +async function copyFiles(files, fromFolder, toFolder) { + await Promise.all( + files.map(async file => { + const to = path.join(toFolder, path.relative(fromFolder, file)); + const dir = path.dirname(to); + await fs.mkdir(dir, { recursive: true }); + await fs.copyFile(file, to); + }), + ); +} diff --git a/dev/check-connection.ts b/dev/check-connection.ts new file mode 100644 index 000000000000..968700d13cc7 --- /dev/null +++ b/dev/check-connection.ts @@ -0,0 +1,12 @@ +import { createSequelizeInstance } from '../packages/core/test/support'; + +const sequelize = createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); + + console.info( + `Connected to ${sequelize.dialect.name} ${sequelize.getDatabaseVersion()} successfully`, + ); +})(); diff --git a/dev/db2/.env_list b/dev/db2/.env_list new file mode 100644 index 000000000000..2e70ed2ccb87 --- /dev/null +++ b/dev/db2/.env_list @@ -0,0 +1,14 @@ +LICENSE=accept +DB2INSTANCE=db2inst1 +DB2INST1_PASSWORD=password +BLU=false +ENABLE_ORACLE_COMPATIBILITY=false +UPDATEAVAIL=NO +TO_CREATE_SAMPLEDB=false +REPODB=false +IS_OSXFS=false +PERSISTENT_HOME=false +HADR_ENABLED=false +ETCD_ENDPOINT= +ETCD_USERNAME= +ETCD_PASSWORD= diff --git a/dev/db2/latest/docker-compose.yml b/dev/db2/latest/docker-compose.yml new file mode 100644 index 000000000000..8cd4111f6076 --- /dev/null +++ b/dev/db2/latest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + db2-latest: + container_name: sequelize-db2-latest + image: icr.io/db2_community/db2:12.1.3.0 + privileged: true + env_file: ../.env_list + environment: + DBNAME: testdb + ports: + - 50000:50000 + volumes: + - db2-latest:/database + healthcheck: + test: su - db2inst1 -c "db2 connect to testdb; db2 select 1 from sysibm.sysdummy1" || exit 1 + start_period: 15s + interval: 10s + timeout: 5s + retries: 10 + +networks: + default: + name: sequelize-db2-latest-network + +volumes: + db2-latest: + name: sequelize-db2-latest-volume diff --git a/dev/db2/latest/reset.sh b/dev/db2/latest/reset.sh new file mode 100644 index 000000000000..d06a43222749 --- /dev/null +++ b/dev/db2/latest/reset.sh @@ -0,0 +1,5 @@ +#!/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-db2-latest down --remove-orphans --volumes diff --git a/dev/db2/latest/start.sh b/dev/db2/latest/start.sh new file mode 100644 index 000000000000..d7ac13f62ab9 --- /dev/null +++ b/dev/db2/latest/start.sh @@ -0,0 +1,10 @@ +#!/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-db2-latest down --remove-orphans +docker compose -p sequelize-db2-latest up -d + +./../wait-until-healthy.sh sequelize-db2-latest + +DIALECT=db2 ts-node ../../check-connection.ts diff --git a/dev/db2/latest/stop.sh b/dev/db2/latest/stop.sh new file mode 100644 index 000000000000..bfa3730b6501 --- /dev/null +++ b/dev/db2/latest/stop.sh @@ -0,0 +1,7 @@ +#!/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-db2-latest down --remove-orphans + +echo "Local latest supported Db2 instance stopped (if it was running)." diff --git a/dev/db2/oldest/docker-compose.yml b/dev/db2/oldest/docker-compose.yml new file mode 100644 index 000000000000..5aa0f5edc14e --- /dev/null +++ b/dev/db2/oldest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + db2-oldest: + container_name: sequelize-db2-oldest + image: icr.io/db2_community/db2:11.5.5.1 + privileged: true + env_file: ../.env_list + environment: + DBNAME: testdb + ports: + - 50000:50000 + volumes: + - db2-oldest:/database + healthcheck: + test: su - db2inst1 -c "db2 connect to testdb; db2 select 1 from sysibm.sysdummy1" || exit 1 + start_period: 15s + interval: 10s + timeout: 5s + retries: 10 + +networks: + default: + name: sequelize-db2-oldest-network + +volumes: + db2-oldest: + name: sequelize-db2-oldest-volume diff --git a/dev/db2/oldest/reset.sh b/dev/db2/oldest/reset.sh new file mode 100644 index 000000000000..a3d36a0b32f0 --- /dev/null +++ b/dev/db2/oldest/reset.sh @@ -0,0 +1,5 @@ +#!/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-db2-oldest down --remove-orphans --volumes diff --git a/dev/db2/oldest/start.sh b/dev/db2/oldest/start.sh new file mode 100644 index 000000000000..5f87d55633e6 --- /dev/null +++ b/dev/db2/oldest/start.sh @@ -0,0 +1,10 @@ +#!/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-db2-oldest down --remove-orphans +docker compose -p sequelize-db2-oldest up -d + +./../wait-until-healthy.sh sequelize-db2-oldest + +DIALECT=db2 ts-node ../../check-connection.ts diff --git a/dev/db2/oldest/stop.sh b/dev/db2/oldest/stop.sh new file mode 100644 index 000000000000..9c51e81039fa --- /dev/null +++ b/dev/db2/oldest/stop.sh @@ -0,0 +1,7 @@ +#!/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-db2-oldest down --remove-orphans + +echo "Local oldest supported Db2 instance stopped (if it was running)." diff --git a/dev/db2/wait-until-healthy.sh b/dev/db2/wait-until-healthy.sh new file mode 100755 index 000000000000..06c331ddae72 --- /dev/null +++ b/dev/db2/wait-until-healthy.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 1 ]; then + >&2 echo "Please provide the container name or hash" + exit 1 +fi + +for _ in {1..10} +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!" + sleep 15 + exit 0 + fi + sleep 15 +done + +>&2 echo "Timeout of 150s exceeded when waiting for container to be healthy: $1" +exit 1 diff --git a/dev/delete-changelog.mjs b/dev/delete-changelog.mjs new file mode 100644 index 000000000000..62aada14f3d5 --- /dev/null +++ b/dev/delete-changelog.mjs @@ -0,0 +1,54 @@ +import { execFile } from 'node:child_process'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const changelogPaths = [ + path.resolve(__dirname, '../CHANGELOG.md'), + path.resolve(__dirname, '../packages/core/CHANGELOG.md'), + path.resolve(__dirname, '../packages/utils/CHANGELOG.md'), + path.resolve(__dirname, '../packages/validator-js/CHANGELOG.md'), +]; + +await Promise.all( + changelogPaths.map(async changelogPath => { + if (await tryAccess(changelogPath)) { + await fs.unlink(changelogPath); + const { stderr, stdout } = await execFileAsync(`git`, ['add', changelogPath]); + + if (stdout) { + console.info(`stdout: ${stdout}`); + } + + if (stderr) { + console.error(`stderr: ${stderr}`); + } + + console.info(`Deleted ${changelogPath}`); + } + }), +); + +async function tryAccess(filename) { + try { + await fs.access(filename); + + return true; + } catch { + return false; + } +} + +function execFileAsync(file, args) { + return new Promise((resolve, reject) => { + execFile(file, args, (error, stdout, stderr) => { + if (error) { + reject(error); + } + + resolve({ stdout, stderr }); + }); + }); +} diff --git a/dev/mariadb/10.3/check.js b/dev/mariadb/10.3/check.js deleted file mode 100644 index c364203fd9eb..000000000000 --- a/dev/mariadb/10.3/check.js +++ /dev/null @@ -1,8 +0,0 @@ -'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 deleted file mode 100644 index 94ee2c7293b8..000000000000 --- a/dev/mariadb/10.3/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100755 index 0e80e04f6520..000000000000 --- a/dev/mariadb/10.3/start.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/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;" - -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 deleted file mode 100755 index e2629c115979..000000000000 --- a/dev/mariadb/10.3/stop.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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/mariadb/latest/docker-compose.yml b/dev/mariadb/latest/docker-compose.yml new file mode 100644 index 000000000000..793a3dc94679 --- /dev/null +++ b/dev/mariadb/latest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + mariadb-latest: + container_name: sequelize-mariadb-latest + image: mariadb:11.6.2 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 21103:3306 + volumes: + - mariadb-latest:/var/lib/mysql + healthcheck: + test: ['CMD', 'mariadb-admin', '-usequelize_test', '-psequelize_test', 'status'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mariadb-latest-network + +volumes: + mariadb-latest: + name: sequelize-mariadb-latest-volume diff --git a/dev/mariadb/latest/reset.sh b/dev/mariadb/latest/reset.sh new file mode 100644 index 000000000000..93963d30bcc7 --- /dev/null +++ b/dev/mariadb/latest/reset.sh @@ -0,0 +1,5 @@ +#!/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-latest down --remove-orphans --volumes diff --git a/dev/mariadb/latest/start.sh b/dev/mariadb/latest/start.sh new file mode 100755 index 000000000000..a35b65b64e3e --- /dev/null +++ b/dev/mariadb/latest/start.sh @@ -0,0 +1,13 @@ +#!/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-latest down --remove-orphans +docker compose -p sequelize-mariadb-latest up -d + +./../../wait-until-healthy.sh sequelize-mariadb-latest + +docker exec sequelize-mariadb-latest \ + mariadb --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +DIALECT=mariadb ts-node ../../check-connection.ts diff --git a/dev/mariadb/latest/stop.sh b/dev/mariadb/latest/stop.sh new file mode 100755 index 000000000000..1cd48a65c672 --- /dev/null +++ b/dev/mariadb/latest/stop.sh @@ -0,0 +1,7 @@ +#!/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-latest down --remove-orphans + +echo "Local latest supported MariaDB instance stopped (if it was running)." diff --git a/dev/mariadb/oldest/docker-compose.yml b/dev/mariadb/oldest/docker-compose.yml new file mode 100644 index 000000000000..daae864d64aa --- /dev/null +++ b/dev/mariadb/oldest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + mariadb-oldest: + container_name: sequelize-mariadb-oldest + image: mariadb:10.4.30 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 21103:3306 + volumes: + - mariadb-oldest:/var/lib/mysql + healthcheck: + test: ['CMD', 'mariadb-admin', '-usequelize_test', '-psequelize_test', 'status'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mariadb-oldest-network + +volumes: + mariadb-oldest: + name: sequelize-mariadb-oldest-volume diff --git a/dev/mariadb/oldest/reset.sh b/dev/mariadb/oldest/reset.sh new file mode 100644 index 000000000000..a83919aa3ca2 --- /dev/null +++ b/dev/mariadb/oldest/reset.sh @@ -0,0 +1,5 @@ +#!/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-oldest down --remove-orphans --volumes diff --git a/dev/mariadb/oldest/start.sh b/dev/mariadb/oldest/start.sh new file mode 100755 index 000000000000..15b51b796b51 --- /dev/null +++ b/dev/mariadb/oldest/start.sh @@ -0,0 +1,13 @@ +#!/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-oldest down --remove-orphans +docker compose -p sequelize-mariadb-oldest up -d + +./../../wait-until-healthy.sh sequelize-mariadb-oldest + +docker exec sequelize-mariadb-oldest \ + mariadb --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +DIALECT=mariadb ts-node ../../check-connection.ts diff --git a/dev/mariadb/oldest/stop.sh b/dev/mariadb/oldest/stop.sh new file mode 100755 index 000000000000..bc4665a50587 --- /dev/null +++ b/dev/mariadb/oldest/stop.sh @@ -0,0 +1,7 @@ +#!/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-oldest down --remove-orphans + +echo "Local oldest supported MariaDB instance stopped (if it was running)." diff --git a/dev/mssql/2019/check.js b/dev/mssql/2019/check.js deleted file mode 100644 index c364203fd9eb..000000000000 --- a/dev/mssql/2019/check.js +++ /dev/null @@ -1,8 +0,0 @@ -'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 deleted file mode 100644 index 888932806e8e..000000000000 --- a/dev/mssql/2019/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100755 index 9fe3c2b48997..000000000000 --- a/dev/mssql/2019/start.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/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 deleted file mode 100755 index 0c8d73b3fee1..000000000000 --- a/dev/mssql/2019/stop.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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/mssql/latest/docker-compose.yml b/dev/mssql/latest/docker-compose.yml new file mode 100644 index 000000000000..e0a8553700c6 --- /dev/null +++ b/dev/mssql/latest/docker-compose.yml @@ -0,0 +1,40 @@ +services: + mssql-latest: + container_name: sequelize-mssql-latest + image: mcr.microsoft.com/mssql/server:2025-latest + environment: + ACCEPT_EULA: Y + MSSQL_PID: Developer + MSSQL_SA_PASSWORD: Password12! + ports: + - 22019:1433 + volumes: + - mssql-latest:/var/opt/mssql + healthcheck: + test: + [ + 'CMD', + '/opt/mssql-tools18/bin/sqlcmd', + '-C', + '-S', + 'localhost', + '-U', + 'SA', + '-P', + 'Password12!', + '-l', + '30', + '-Q', + 'SELECT 1', + ] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mssql-latest-network + +volumes: + mssql-latest: + name: sequelize-mssql-latest-volume diff --git a/dev/mssql/latest/reset.sh b/dev/mssql/latest/reset.sh new file mode 100644 index 000000000000..d4d6c0bcde47 --- /dev/null +++ b/dev/mssql/latest/reset.sh @@ -0,0 +1,5 @@ +#!/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-latest down --remove-orphans --volumes diff --git a/dev/mssql/latest/start.sh b/dev/mssql/latest/start.sh new file mode 100755 index 000000000000..5878d9a21a1a --- /dev/null +++ b/dev/mssql/latest/start.sh @@ -0,0 +1,13 @@ +#!/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-latest down --remove-orphans +docker compose -p sequelize-mssql-latest up -d + +./../../wait-until-healthy.sh sequelize-mssql-latest + +docker exec sequelize-mssql-latest \ + /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P "Password12!" -Q "CREATE DATABASE sequelize_test; ALTER DATABASE sequelize_test SET READ_COMMITTED_SNAPSHOT ON;" + +DIALECT=mssql ts-node ../../check-connection.ts diff --git a/dev/mssql/latest/stop.sh b/dev/mssql/latest/stop.sh new file mode 100755 index 000000000000..d671b572350c --- /dev/null +++ b/dev/mssql/latest/stop.sh @@ -0,0 +1,7 @@ +#!/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-latest down --remove-orphans + +echo "Local latest supported MSSQL instance stopped (if it was running)." diff --git a/dev/mssql/oldest/docker-compose.yml b/dev/mssql/oldest/docker-compose.yml new file mode 100644 index 000000000000..30a11cc9e347 --- /dev/null +++ b/dev/mssql/oldest/docker-compose.yml @@ -0,0 +1,39 @@ +services: + mssql-oldest: + container_name: sequelize-mssql-oldest + image: mcr.microsoft.com/mssql/server:2017-latest + environment: + ACCEPT_EULA: Y + MSSQL_PID: Developer + MSSQL_SA_PASSWORD: Password12! + ports: + - 22019:1433 + volumes: + - mssql-oldest:/var/opt/mssql + 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-oldest-network + +volumes: + mssql-oldest: + name: sequelize-mssql-oldest-volume diff --git a/dev/mssql/oldest/reset.sh b/dev/mssql/oldest/reset.sh new file mode 100644 index 000000000000..6b4eb1f27fe0 --- /dev/null +++ b/dev/mssql/oldest/reset.sh @@ -0,0 +1,5 @@ +#!/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-oldest down --remove-orphans --volumes diff --git a/dev/mssql/oldest/start.sh b/dev/mssql/oldest/start.sh new file mode 100755 index 000000000000..eef74ca6e1b2 --- /dev/null +++ b/dev/mssql/oldest/start.sh @@ -0,0 +1,13 @@ +#!/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-oldest down --remove-orphans +docker compose -p sequelize-mssql-oldest up -d + +./../../wait-until-healthy.sh sequelize-mssql-oldest + +docker exec sequelize-mssql-oldest \ + /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;" + +DIALECT=mssql ts-node ../../check-connection.ts diff --git a/dev/mssql/oldest/stop.sh b/dev/mssql/oldest/stop.sh new file mode 100755 index 000000000000..defffb6e6d8b --- /dev/null +++ b/dev/mssql/oldest/stop.sh @@ -0,0 +1,7 @@ +#!/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-oldest down --remove-orphans + +echo "Local oldest supported MSSQL instance stopped (if it was running)." diff --git a/dev/mysql/5.7/check.js b/dev/mysql/5.7/check.js deleted file mode 100644 index c364203fd9eb..000000000000 --- a/dev/mysql/5.7/check.js +++ /dev/null @@ -1,8 +0,0 @@ -'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 deleted file mode 100644 index 9409f78c2fa5..000000000000 --- a/dev/mysql/5.7/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100755 index fb8b02a8b43d..000000000000 --- a/dev/mysql/5.7/start.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/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 deleted file mode 100755 index 36e3e076065e..000000000000 --- a/dev/mysql/5.7/stop.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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/latest/docker-compose.yml b/dev/mysql/latest/docker-compose.yml new file mode 100644 index 000000000000..9a82d68d6443 --- /dev/null +++ b/dev/mysql/latest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + mysql-latest: + container_name: sequelize-mysql-latest + image: mysql:8.4.7 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 20057:3306 + volumes: + - mysql-latest:/var/lib/mysql + healthcheck: + test: ['CMD', 'mysqladmin', '-usequelize_test', '-psequelize_test', 'status'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mysql-latest-network + +volumes: + mysql-latest: + name: sequelize-mysql-latest-volume diff --git a/dev/mysql/latest/reset.sh b/dev/mysql/latest/reset.sh new file mode 100644 index 000000000000..b5b16de21936 --- /dev/null +++ b/dev/mysql/latest/reset.sh @@ -0,0 +1,5 @@ +#!/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-latest down --remove-orphans --volumes diff --git a/dev/mysql/latest/start.sh b/dev/mysql/latest/start.sh new file mode 100755 index 000000000000..d90ed6deb4ea --- /dev/null +++ b/dev/mysql/latest/start.sh @@ -0,0 +1,15 @@ +#!/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-latest down --remove-orphans +docker compose -p sequelize-mysql-latest up -d + +./../../wait-until-healthy.sh sequelize-mysql-latest + +sleep 5s + +docker exec sequelize-mysql-latest \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +DIALECT=mysql ts-node ../../check-connection.ts diff --git a/dev/mysql/latest/stop.sh b/dev/mysql/latest/stop.sh new file mode 100755 index 000000000000..5d646b751988 --- /dev/null +++ b/dev/mysql/latest/stop.sh @@ -0,0 +1,7 @@ +#!/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-latest down --remove-orphans + +echo "Local latest supported MySQL instance stopped (if it was running)." diff --git a/dev/mysql/oldest/docker-compose.yml b/dev/mysql/oldest/docker-compose.yml new file mode 100644 index 000000000000..c9bb9026cfbb --- /dev/null +++ b/dev/mysql/oldest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + mysql-oldest: + container_name: sequelize-mysql-oldest + image: mysql:8.0.19 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 20057:3306 + volumes: + - mysql-oldest:/var/lib/mysql + healthcheck: + test: ['CMD', 'mysqladmin', '-usequelize_test', '-psequelize_test', 'status'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mysql-oldest-network + +volumes: + mysql-oldest: + name: sequelize-mysql-oldest-volume diff --git a/dev/mysql/oldest/reset.sh b/dev/mysql/oldest/reset.sh new file mode 100644 index 000000000000..7007b4d4bdab --- /dev/null +++ b/dev/mysql/oldest/reset.sh @@ -0,0 +1,5 @@ +#!/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-oldest down --remove-orphans --volumes diff --git a/dev/mysql/oldest/start.sh b/dev/mysql/oldest/start.sh new file mode 100755 index 000000000000..2bdf0b068d0b --- /dev/null +++ b/dev/mysql/oldest/start.sh @@ -0,0 +1,13 @@ +#!/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-oldest down --remove-orphans +docker compose -p sequelize-mysql-oldest up -d + +./../../wait-until-healthy.sh sequelize-mysql-oldest + +docker exec sequelize-mysql-oldest \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +DIALECT=mysql ts-node ../../check-connection.ts diff --git a/dev/mysql/oldest/stop.sh b/dev/mysql/oldest/stop.sh new file mode 100755 index 000000000000..7130d700c5db --- /dev/null +++ b/dev/mysql/oldest/stop.sh @@ -0,0 +1,7 @@ +#!/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-oldest down --remove-orphans + +echo "Local oldest supported MySQL instance stopped (if it was running)." diff --git a/dev/postgres/10/check.js b/dev/postgres/10/check.js deleted file mode 100644 index c364203fd9eb..000000000000 --- a/dev/postgres/10/check.js +++ /dev/null @@ -1,8 +0,0 @@ -'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 deleted file mode 100644 index 50dfbc65196f..000000000000 --- a/dev/postgres/10/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100755 index 6a2ea51738e9..000000000000 --- a/dev/postgres/10/start.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/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 deleted file mode 100755 index 907d2074513b..000000000000 --- a/dev/postgres/10/stop.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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/postgres/latest/docker-compose.yml b/dev/postgres/latest/docker-compose.yml new file mode 100644 index 000000000000..61740574b89e --- /dev/null +++ b/dev/postgres/latest/docker-compose.yml @@ -0,0 +1,25 @@ +services: + postgres-latest: + container_name: sequelize-postgres-latest + image: postgis/postgis:17-3.5 + environment: + POSTGRES_USER: sequelize_test + POSTGRES_PASSWORD: sequelize_test + POSTGRES_DB: sequelize_test + ports: + - 23010:5432 + volumes: + - postgres-latest:/var/lib/postgresql/data + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'sequelize_test'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-postgres-latest-network + +volumes: + postgres-latest: + name: sequelize-postgres-latest-volume diff --git a/dev/postgres/latest/reset.sh b/dev/postgres/latest/reset.sh new file mode 100644 index 000000000000..e5c7a123c274 --- /dev/null +++ b/dev/postgres/latest/reset.sh @@ -0,0 +1,5 @@ +#!/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-latest down --remove-orphans --volumes diff --git a/dev/postgres/latest/start.sh b/dev/postgres/latest/start.sh new file mode 100755 index 000000000000..70065d0591b7 --- /dev/null +++ b/dev/postgres/latest/start.sh @@ -0,0 +1,13 @@ +#!/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-latest down --remove-orphans +docker compose -p sequelize-postgres-latest up -d + +./../../wait-until-healthy.sh sequelize-postgres-latest + +DIALECT=postgres ts-node ../../check-connection.ts + +docker exec sequelize-postgres-latest \ + bash -c "export PGPASSWORD=sequelize_test && psql -h localhost -p 5432 -U sequelize_test sequelize_test -c 'CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE EXTENSION IF NOT EXISTS hstore; CREATE EXTENSION IF NOT EXISTS citext;'" diff --git a/dev/postgres/latest/stop.sh b/dev/postgres/latest/stop.sh new file mode 100755 index 000000000000..63e32d896764 --- /dev/null +++ b/dev/postgres/latest/stop.sh @@ -0,0 +1,7 @@ +#!/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-latest down --remove-orphans + +echo "Local latest supported Postgres instance with postgis stopped (if it was running)." diff --git a/dev/postgres/oldest/docker-compose.yml b/dev/postgres/oldest/docker-compose.yml new file mode 100644 index 000000000000..bd44f65cbe63 --- /dev/null +++ b/dev/postgres/oldest/docker-compose.yml @@ -0,0 +1,25 @@ +services: + postgres-oldest: + container_name: sequelize-postgres-oldest + image: postgis/postgis:11-2.5 + environment: + POSTGRES_USER: sequelize_test + POSTGRES_PASSWORD: sequelize_test + POSTGRES_DB: sequelize_test + ports: + - 23010:5432 + volumes: + - postgres-oldest:/var/lib/postgresql/data + healthcheck: + test: ['CMD', 'pg_isready', '-U', 'sequelize_test'] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-postgres-oldest-network + +volumes: + postgres-oldest: + name: sequelize-postgres-oldest-volume diff --git a/dev/postgres/oldest/reset.sh b/dev/postgres/oldest/reset.sh new file mode 100644 index 000000000000..ddbad00c327f --- /dev/null +++ b/dev/postgres/oldest/reset.sh @@ -0,0 +1,5 @@ +#!/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-oldest down --remove-orphans --volumes diff --git a/dev/postgres/oldest/start.sh b/dev/postgres/oldest/start.sh new file mode 100755 index 000000000000..147f0ddeaf83 --- /dev/null +++ b/dev/postgres/oldest/start.sh @@ -0,0 +1,13 @@ +#!/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-oldest down --remove-orphans +docker compose -p sequelize-postgres-oldest up -d + +./../../wait-until-healthy.sh sequelize-postgres-oldest + +DIALECT=postgres ts-node ../../check-connection.ts + +docker exec sequelize-postgres-oldest \ + bash -c "export PGPASSWORD=sequelize_test && psql -h localhost -p 5432 -U sequelize_test sequelize_test -c 'CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE EXTENSION IF NOT EXISTS hstore; CREATE EXTENSION IF NOT EXISTS citext; CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"'" diff --git a/dev/postgres/oldest/stop.sh b/dev/postgres/oldest/stop.sh new file mode 100755 index 000000000000..70210f2e623e --- /dev/null +++ b/dev/postgres/oldest/stop.sh @@ -0,0 +1,7 @@ +#!/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-oldest down --remove-orphans + +echo "Local oldest supported Postgres instance with postgis stopped (if it was running)." diff --git a/dev/sscce-helpers.d.ts b/dev/sscce-helpers.d.ts deleted file mode 100644 index 74df2a20717a..000000000000 --- a/dev/sscce-helpers.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Sequelize, Options } from '..'; - -export declare function createSequelizeInstance(options?: Options): Sequelize; diff --git a/dev/sscce-helpers.js b/dev/sscce-helpers.js deleted file mode 100644 index c15a099b1936..000000000000 --- a/dev/sscce-helpers.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -const Support = require('../test/support'); - -module.exports = { - createSequelizeInstance(options = {}) { - return Support.createSequelizeInstance({ - logging: console.log, - logQueryParameters: true, - ...options - }); - } -}; diff --git a/dev/sscce-helpers.ts b/dev/sscce-helpers.ts new file mode 100644 index 000000000000..3eb048296ae0 --- /dev/null +++ b/dev/sscce-helpers.ts @@ -0,0 +1,13 @@ +import type { AbstractDialect } from '@sequelize/core'; +import type { Options, Sequelize } from '../packages/core'; +import * as Support from '../packages/core/test/support'; + +export function createSequelizeInstance( + options: Options, +): Sequelize { + return Support.createSequelizeInstance({ + logging: console.debug, + logQueryParameters: true, + ...options, + }); +} diff --git a/dev/sync-exports.mjs b/dev/sync-exports.mjs new file mode 100755 index 000000000000..09ecac033c35 --- /dev/null +++ b/dev/sync-exports.mjs @@ -0,0 +1,143 @@ +#!/usr/bin/env node + +import { EMPTY_OBJECT, arrayFromAsync, parallelForEach, pojo } from '@sequelize/utils'; +import { listDirectories, listFilesRecursive, readFileIfExists } from '@sequelize/utils/node'; +import isEqual from 'lodash/isEqual.js'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +/** + * does not modify the contents of the file but exits with code 1 if outdated, 0 if not + */ +const checkOutdated = process.argv.includes('--check-outdated'); + +/** + * The package contains multiple individual exports that each need their own index file + */ +const multipleEntryPoints = process.argv.includes('--multi-entry-points'); + +const requestedSrcDir = process.argv[2]; +if (!requestedSrcDir) { + console.error('Please provide the path to the src folder to synchronize'); +} + +const srcDir = path.normalize(path.join(process.cwd(), requestedSrcDir)); + +console.info( + `${checkOutdated ? 'Testing synchronization of' : 'Synchronizing'} exports of folder ${srcDir}`, +); + +const folders = multipleEntryPoints + ? (await listDirectories(srcDir)).map(folder => path.join(srcDir, folder)) + : [srcDir]; + +const outdatedPaths = []; + +await parallelForEach(folders, async folder => { + const files = await arrayFromAsync(listFilesRecursive(folder)); + + const commonExports = []; + + /** + * You can provide a browser-specific or node-specific implementation by adding ".browser" or ".node" to their filename + */ + const browserExportOverrides = pojo(); + const nodeExportOverrides = pojo(); + + files + .map(file => { + return path.relative(folder, file).replace(/\.ts$/, '.js'); + }) + .filter(pathname => { + return ( + !/(^|\\)index\./.test(pathname) && + !pathname.startsWith('.DS_Store') && + !pathname.endsWith('.spec.js') && + !pathname.endsWith('.test.js') && + !pathname.includes('__tests__/') && + !pathname.includes('_internal/') && + !pathname.includes('.internal') && + !pathname.endsWith('.d.js') + ); + }) + // eslint-disable-next-line unicorn/no-array-for-each -- clearer like this, perf doesn't matter + .forEach(pathname => { + if (pathname.includes('.node.')) { + nodeExportOverrides[pathname.replace('.node.', '.')] = pathname; + } else if (pathname.includes('.browser.')) { + browserExportOverrides[pathname.replace('.browser.', '.')] = pathname; + } else { + commonExports.push(pathname); + } + }); + + const baseExports = getExportsWithOverrides(commonExports, EMPTY_OBJECT); + const browserExports = getExportsWithOverrides(commonExports, browserExportOverrides); + const nodeExports = getExportsWithOverrides(commonExports, nodeExportOverrides); + + const promises = []; + promises.push(outputExports(baseExports, path.join(folder, 'index.ts'))); + if (!isEqual(browserExports, baseExports)) { + promises.push(outputExports(browserExports, path.join(folder, 'index.browser.ts'))); + } + + if (!isEqual(nodeExports, baseExports)) { + promises.push(outputExports(nodeExports, path.join(folder, 'index.node.ts'))); + } + + await Promise.all(promises); +}); + +async function outputExports(exports, indexPath) { + const imports = exports + .map(pathname => { + return `export * from './${pathname}';\n`; + }) + .sort() + .join(''); + + const fileContents = `/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */\n\n${imports}`; + + const file = await readFileIfExists(indexPath, 'utf-8'); + if (file === null || file !== fileContents) { + outdatedPaths.push(indexPath); + } + + if (!checkOutdated) { + await fs.writeFile(indexPath, fileContents, 'utf-8'); + } +} + +function getExportsWithOverrides(commonExports, platformExportOverrides) { + const platformExportKeys = Object.keys(platformExportOverrides); + if (platformExportKeys.length === 0) { + return commonExports; + } + + const platformExports = []; + + /** Add exports that were not replaced by another */ + for (const commonExport of commonExports) { + if (platformExportOverrides[commonExport]) { + continue; + } + + platformExports.push(commonExport); + } + + platformExports.push(...Object.values(platformExportOverrides)); + + return platformExports; +} + +if (outdatedPaths.length === 0) { + console.info('All index files up-to-date'); +} else { + const fileListStr = outdatedPaths.map(pathname => `- ${pathname}\n`).join(''); + if (checkOutdated) { + console.info(`Outdated files:\n${fileListStr}`); + process.exit(1); + } else { + console.info(`Updated files:\n${fileListStr}`); + } +} diff --git a/dev/update-authors.js b/dev/update-authors.js new file mode 100755 index 000000000000..4dfbafa44bf6 --- /dev/null +++ b/dev/update-authors.js @@ -0,0 +1,70 @@ +#!/usr/bin/env node +// original comes from https://github.com/nodejs/node/blob/c18ca140a12b543a3f970ef379f384ebd3297c3d/tools/update-authors.js + +// Usage: dev/update-author.js [--dry] +// Passing --dry will redirect output to stdout rather than write to 'AUTHORS'. +'use strict'; + +const { spawn } = require('node:child_process'); +const fs = require('node:fs'); +const readline = require('node:readline'); + +const log = spawn( + 'git', + // Inspect author name/email and body. + ['log', '--reverse', '--format=Author: %aN <%aE>\n%b'], + { + stdio: ['inherit', 'pipe', 'inherit'], + }, +); +const rl = readline.createInterface({ input: log.stdout }); + +let output; +if (process.argv.includes('--dry')) { + output = process.stdout; +} else { + output = fs.createWriteStream('AUTHORS'); +} + +output.write('# Authors ordered by first contribution.\n\n'); + +const seen = new Set(); + +// exclude emails from /AUTHORS file +const excludeEmails = new Set([ + '', + '', + '', + '', + '', + '', +]); + +// Support regular git author metadata, as well as `Author:` and +// `Co-authored-by:` in the message body. Both have been used in the past +// to indicate multiple authors per commit, with the latter standardized +// by GitHub now. +const authorRe = /(^Author:|^Co-authored-by:)\s+(?[^<]+)\s+(?<[^>]+>)/i; + +rl.on('line', line => { + const match = line.match(authorRe); + if (!match) { + return; + } + + const { author, email } = match.groups; + + const botRegex = /bot@users.noreply.github.com/g; + const botEmail = email.replaceAll(/\[bot.*?\]/g, 'bot'); + + if (seen.has(email) || excludeEmails.has(email) || botRegex.test(botEmail)) { + return; + } + + seen.add(email); + output.write(`${author} ${email}\n`); +}); + +rl.on('close', () => { + output.end('\n# Generated by dev/update-authors.js\n'); +}); diff --git a/dev/wait-until-healthy.sh b/dev/wait-until-healthy.sh index 80815069bb1a..14b2e16a50b0 100755 --- a/dev/wait-until-healthy.sh +++ b/dev/wait-until-healthy.sh @@ -8,6 +8,14 @@ fi for _ in {1..50} do state=$(docker inspect -f '{{ .State.Health.Status }}' $1 2>&1) + echo -ne "$state.\r" + sleep 1 + echo -ne "$state..\r" + sleep 1 + echo -ne "$state...\r" + sleep 1 + echo -ne '\033[K' + return_code=$? if [ ${return_code} -eq 0 ] && [ "$state" == "healthy" ]; then echo "$1 is healthy!" diff --git a/docs/ROUTER.txt b/docs/ROUTER.txt deleted file mode 100644 index da1d58631241..000000000000 --- a/docs/ROUTER.txt +++ /dev/null @@ -1,36 +0,0 @@ -301 /en/v3/ https://sequelize.org/v3/ -301 /en/v3/:foo https://sequelize.org/v3/:foo -301 /en/v3/:foo/:bar https://sequelize.org/v3/:foo/:bar -301 /en/v3/:foo/:bar/:baz https://sequelize.org/v3/:foo/:bar/:baz -301 /en/v3/:foo/:bar/:baz/:quz https://sequelize.org/v3/:foo/:bar/:baz/:quz - -301 /en/latest https://sequelize.org/master -301 /en/latest/ https://sequelize.org/master/ - -301 /en/latest/docs/getting-started/ https://sequelize.org/master/manual/getting-started.html -301 /en/latest/docs/:section/ https://sequelize.org/master/manual/:section.html - -301 /en/latest/api/sequelize/ https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html -301 /en/latest/api/model/ https://sequelize.org/master/class/lib/model.js~Model.html -301 /en/latest/api/instance/ https://sequelize.org/master/class/lib/model.js~Model.html -301 /en/latest/api/associations/ https://sequelize.org/master/class/lib/associations/base.js~Association.html -301 /en/latest/api/associations/belongs-to/ https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html -301 /en/latest/api/associations/belongs-to-many/ https://sequelize.org/master/class/lib/associations/belongs-to-.many.js~BelongsToMany.html -301 /en/latest/api/associations/has-one/ https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html -301 /en/latest/api/associations/has-many/ https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html -301 /en/latest/api/transaction/ https://sequelize.org/master/class/lib/transaction.js~Transaction.html -301 /en/latest/api/datatypes/ https://sequelize.org/master/variable/index.html#static-variable-DataTypes -301 /en/latest/api/deferrable/ https://sequelize.org/master/variable/index.html#static-variable-Deferrable -301 /en/latest/api/errors/ https://sequelize.org/master/class/lib/errors/base-error.js~BaseError.html - -301 /manual/tutorial/:section.html https://sequelize.org/master/manual/:section.html -301 /manual/installation/:section.html https://sequelize.org/master/manual/:section.html -301 /manual/faq/:section.html https://sequelize.org/master/manual/:section.html -301 /manual/advanced/:section.html https://sequelize.org/master/manual/:section.html - -301 /:foo https://sequelize.org/master/:foo -301 /:foo/:bar https://sequelize.org/master/:foo/:bar -301 /:foo/:bar/:baz https://sequelize.org/master/:foo/:bar/:baz -301 /:foo/:bar/:baz/:quz https://sequelize.org/master/:foo/:bar/:baz/:quz - -301 / https://sequelize.org/ \ No newline at end of file diff --git a/docs/css/style.css b/docs/css/style.css deleted file mode 100644 index c99916d7bb00..000000000000 --- a/docs/css/style.css +++ /dev/null @@ -1,178 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Titillium+Web); - -div.logo img { - width: 200px; - height: 200px; - box-shadow: none !important; -} - -div.sequelize { - color: #399af3; - font-size: 60px; - font-family: 'Titillium Web', sans-serif; -} - -.center { - text-align: center; -} - -.manual-root .content img { - box-shadow: none !important; - max-width: 300px; -} - -.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; -} - -.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 { - color: #999 !important; -} - -.manual-group { - font-weight: 600; - margin: 9.6px 0 3.2px 0; - color: black !important; - font-size: 17px; -} - -.no-mouse { - pointer-events: none; -} - -.api-reference-link { - white-space: nowrap; - font-weight: bold; - padding: 0 20px; -} -.api-reference-link:hover { - color: rgb(3, 155, 229); -} - -header a { - display: flex; - align-items: center; -} - -a[href="source.html"], -a[href^="file/lib/"] { - display: none; -} \ No newline at end of file diff --git a/docs/css/theme.css b/docs/css/theme.css deleted file mode 100644 index 6ce454c93ded..000000000000 --- a/docs/css/theme.css +++ /dev/null @@ -1,16 +0,0 @@ -/* Side Nav */ -.manual-color, .kind-class, .manual-color-faq { - background-color: #6eb5f7; -} - -.manual-color a, .kind-class, .manual-color-faq a { - color: #ffffff !important; -} - -.kind-variable, .manual-color-reference { - background-color: #074278; -} - -.kind-variable, .manual-color-reference a, .manual-color-faq a { - color: #ffffff; -} diff --git a/docs/esdoc-config.js b/docs/esdoc-config.js deleted file mode 100644 index 3e2f3436a948..000000000000 --- a/docs/esdoc-config.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const { getDeclaredManuals, checkManuals } = require('./manual-utils'); - -checkManuals(); - -module.exports = { - source: './lib', - destination: './esdoc', - includes: ['\\.js$'], - plugins: [ - { - name: 'esdoc-ecmascript-proposal-plugin', - option: { - all: true - } - }, - { - name: 'esdoc-inject-style-plugin', - option: { - enable: true, - styles: [ - './docs/css/style.css', - './docs/css/theme.css' - ] - } - }, - { - name: 'esdoc-standard-plugin', - option: { - lint: { enable: true }, - coverage: { enable: false }, - accessor: { - access: ['public'], - autoPrivate: true - }, - undocumentIdentifier: { enable: false }, - unexportedIdentifier: { enable: true }, - typeInference: { enable: true }, - brand: { - logo: './docs/images/logo-small.png', - title: 'Sequelize', - description: 'An easy-to-use multi SQL dialect ORM for Node.js', - repository: 'https://github.com/sequelize/sequelize', - site: 'https://sequelize.org/master/' - }, - manual: { - index: './docs/index.md', - globalIndex: true, - asset: './docs/images', - files: getDeclaredManuals() - } - } - } - ] -}; diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 8ffe09b90709..000000000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/images/bitovi-logo.png b/docs/images/bitovi-logo.png deleted file mode 100644 index a0bfca78f930..000000000000 Binary files a/docs/images/bitovi-logo.png and /dev/null differ diff --git a/docs/images/clevertech.png b/docs/images/clevertech.png deleted file mode 100644 index 70bb50fc6ca2..000000000000 Binary files a/docs/images/clevertech.png and /dev/null differ diff --git a/docs/images/connected-cars.png b/docs/images/connected-cars.png deleted file mode 100644 index 807e7fa91b3d..000000000000 Binary files a/docs/images/connected-cars.png and /dev/null differ diff --git a/docs/images/ermeshotels-logo.png b/docs/images/ermeshotels-logo.png deleted file mode 100644 index a1153b476d93..000000000000 Binary files a/docs/images/ermeshotels-logo.png and /dev/null differ diff --git a/docs/images/filsh.png b/docs/images/filsh.png deleted file mode 100644 index 9fa27f81a9a9..000000000000 Binary files a/docs/images/filsh.png and /dev/null differ diff --git a/docs/images/logo-small.png b/docs/images/logo-small.png deleted file mode 100644 index 3317765b1a4f..000000000000 Binary files a/docs/images/logo-small.png and /dev/null differ diff --git a/docs/images/logo-snaplytics-green.png b/docs/images/logo-snaplytics-green.png deleted file mode 100644 index c87113dc4887..000000000000 Binary files a/docs/images/logo-snaplytics-green.png and /dev/null differ diff --git a/docs/images/logo.png b/docs/images/logo.png deleted file mode 100644 index 3317765b1a4f..000000000000 Binary files a/docs/images/logo.png and /dev/null differ diff --git a/docs/images/metamarkets.png b/docs/images/metamarkets.png deleted file mode 100644 index 62180838027a..000000000000 Binary files a/docs/images/metamarkets.png and /dev/null differ diff --git a/docs/images/shutterstock.png b/docs/images/shutterstock.png deleted file mode 100644 index a89b014f2c8e..000000000000 Binary files a/docs/images/shutterstock.png and /dev/null differ diff --git a/docs/images/slack.svg b/docs/images/slack.svg deleted file mode 100644 index c37dc5eb49e3..000000000000 --- a/docs/images/slack.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/walmart-labs-logo.png b/docs/images/walmart-labs-logo.png deleted file mode 100644 index f463966c4efa..000000000000 Binary files a/docs/images/walmart-labs-logo.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 79bf6dc78615..000000000000 --- a/docs/index.md +++ /dev/null @@ -1,48 +0,0 @@ -
- -
Sequelize
-
- -[![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) -[![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) - -[![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/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](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. - -You are currently looking at the **Tutorials and Guides** for Sequelize. You might also be interested in the [API Reference](identifiers.html). - -## Quick example - -```js -const { Sequelize, Model, DataTypes } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory:'); - -class User extends Model {} -User.init({ - username: DataTypes.STRING, - birthday: DataTypes.DATE -}, { sequelize, modelName: 'user' }); - -(async () => { - await sequelize.sync(); - const jane = await User.create({ - username: 'janedoe', - birthday: new Date(1980, 6, 20) - }); - 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.html). diff --git a/docs/manual-groups.json b/docs/manual-groups.json deleted file mode 100644 index 91bf48cc2753..000000000000 --- a/docs/manual-groups.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "Core Concepts": [ - "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": [ - "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" - ] -} diff --git a/docs/manual-utils.js b/docs/manual-utils.js deleted file mode 100644 index a09ce52d216a..000000000000 --- a/docs/manual-utils.js +++ /dev/null @@ -1,37 +0,0 @@ -'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 deleted file mode 100644 index 371f66201f8c..000000000000 --- a/docs/manual/advanced-association-concepts/advanced-many-to-many.md +++ /dev/null @@ -1,667 +0,0 @@ -# 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 deleted file mode 100644 index 42224f5e0cf6..000000000000 --- a/docs/manual/advanced-association-concepts/association-scopes.md +++ /dev/null @@ -1,64 +0,0 @@ -# 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 deleted file mode 100644 index 60f5e80ea71d..000000000000 --- a/docs/manual/advanced-association-concepts/creating-with-associations.md +++ /dev/null @@ -1,130 +0,0 @@ -# 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 deleted file mode 100644 index 9dd787c6727e..000000000000 --- a/docs/manual/advanced-association-concepts/eager-loading.md +++ /dev/null @@ -1,664 +0,0 @@ -# Eager Loading - -As briefly mentioned in [the associations guide](assocs.html), eager Loading is the act of querying data of several models at once (one 'main' model and one or more associated models). At the SQL level, this is a query with one or more [joins](https://en.wikipedia.org/wiki/Join_\(SQL\)). - -When this is done, the associated models will be added by Sequelize in appropriately named, automatically created field(s) in the returned objects. - -In Sequelize, eager loading is mainly done by using the `include` option on a model finder query (such as `findOne`, `findAll`, etc). - -## Basic example - -Let's assume the following setup: - -```js -const User = sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); -const Task = sequelize.define('task', { name: DataTypes.STRING }, { timestamps: false }); -const Tool = sequelize.define('tool', { - name: DataTypes.STRING, - size: DataTypes.STRING -}, { timestamps: false }); -User.hasMany(Task); -Task.belongsTo(User); -User.hasMany(Tool, { as: 'Instruments' }); -``` - -### Fetching a single associated element - -OK. So, first of all, let's load all tasks with their associated user: - -```js -const tasks = await Task.findAll({ include: User }); -console.log(JSON.stringify(tasks, null, 2)); -``` - -Output: - -```json -[{ - "name": "A Task", - "id": 1, - "userId": 1, - "user": { - "name": "John Doe", - "id": 1 - } -}] -``` - -Here, `tasks[0].user instanceof User` is `true`. This shows that when Sequelize fetches associated models, they are added to the output object as model instances. - -Above, the associated model was added to a new field called `user` in the fetched task. The name of this field was automatically chosen by Sequelize based on the name of the associated model, where its pluralized form is used when applicable (i.e., when the association is `hasMany` or `belongsToMany`). In other words, since `Task.belongsTo(User)`, a task is associated to one user, therefore the logical choice is the singular form (which Sequelize follows automatically). - -### Fetching all associated elements - -Now, instead of loading the user that is associated to a given task, we will do the opposite - we will find all tasks associated to a given user. - -The method call is essentially the same. The only difference is that now the extra field created in the query result uses the pluralized form (`tasks` in this case), and its value is an array of task instances (instead of a single instance, as above). - -```js -const users = await User.findAll({ include: Task }); -console.log(JSON.stringify(users, null, 2)); -``` - -Output: - -```json -[{ - "name": "John Doe", - "id": 1, - "tasks": [{ - "name": "A Task", - "id": 1, - "userId": 1 - }] -}] -``` - -Notice that the accessor (the `tasks` property in the resulting instance) is pluralized since the association is one-to-many. - -### Fetching an Aliased association - -If an association is aliased (using the `as` option), you must specify this alias when including the model. Instead of passing the model directly to the `include` option, you should instead provide an object with two options: `model` and `as`. - -Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias: - -```js -const users = await User.findAll({ - include: { model: Tool, as: 'Instruments' } -}); -console.log(JSON.stringify(users, null, 2)); -``` - -Output: - -```json -[{ - "name": "John Doe", - "id": 1, - "Instruments": [{ - "name": "Scissor", - "id": 1, - "userId": 1 - }] -}] -``` - -You can also include by alias name by specifying a string that matches the association alias: - -```js -User.findAll({ include: 'Instruments' }); // Also works -User.findAll({ include: { association: 'Instruments' } }); // Also works -``` - -### Required eager loading - -When eager loading, we can force the query to return only records which have an associated model, effectively converting the query from the default `OUTER JOIN` to an `INNER JOIN`. This is done with the `required: true` option, as follows: - -```js -User.findAll({ - include: { - model: Task, - required: true - } -}); -``` - -This option also works on nested includes. - -### Eager loading filtered at the associated model level - -When eager loading, we can also filter the associated model using the `where` option, as in the following example: - -```js -User.findAll({ - include: { - model: Tool, - as: 'Instruments' - where: { - size: { - [Op.ne]: 'small' - } - } - } -}); -``` - -Generated SQL: - -```sql -SELECT - `user`.`id`, - `user`.`name`, - `Instruments`.`id` AS `Instruments.id`, - `Instruments`.`name` AS `Instruments.name`, - `Instruments`.`size` AS `Instruments.size`, - `Instruments`.`userId` AS `Instruments.userId` -FROM `users` AS `user` -INNER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` AND - `Instruments`.`size` != 'small'; -``` - -Note that the SQL query generated above will only fetch users that have at least one tool that matches the condition (of not being `small`, in this case). This is the case because, when the `where` option is used inside an `include`, Sequelize automatically sets the `required` option to `true`. This means that, instead of an `OUTER JOIN`, an `INNER JOIN` is done, returning only the parent models with at least one matching children. - -Note also that the `where` option used was converted into a condition for the `ON` clause of the `INNER JOIN`. In order to obtain a *top-level* `WHERE` clause, instead of an `ON` clause, something different must be done. This will be shown next. - -#### Referring to other columns - -If you want to apply a `WHERE` clause in an included model referring to a value from an associated model, you can simply use the `Sequelize.col` function, as show in the example below: - -```js -// Find all projects with a least one task where task.state === project.state -Project.findAll({ - include: { - model: Task, - where: { - state: Sequelize.col('project.state') - } - } -}) -``` - -### Complex where clauses at the top-level - -To obtain top-level `WHERE` clauses that involve nested columns, Sequelize provides a way to reference nested columns: the `'$nested.column$'` syntax. - -It can be used, for example, to move the where conditions from an included model from the `ON` condition to a top-level `WHERE` clause. - -```js -User.findAll({ - where: { - '$Instruments.size$': { [Op.ne]: 'small' } - }, - include: [{ - model: Tool, - as: 'Instruments' - }] -}); -``` - -Generated SQL: - -```sql -SELECT - `user`.`id`, - `user`.`name`, - `Instruments`.`id` AS `Instruments.id`, - `Instruments`.`name` AS `Instruments.name`, - `Instruments`.`size` AS `Instruments.size`, - `Instruments`.`userId` AS `Instruments.userId` -FROM `users` AS `user` -LEFT OUTER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` -WHERE `Instruments`.`size` != 'small'; -``` - -The `$nested.column$` syntax also works for columns that are nested several levels deep, such as `$some.super.deeply.nested.column$`. Therefore, you can use this to make complex filters on deeply nested columns. - -For a better understanding of all differences between the inner `where` option (used inside an `include`), with and without the `required` option, and a top-level `where` using the `$nested.column$` syntax, below we have four examples for you: - -```js -// Inner where, with default `required: true` -await User.findAll({ - include: { - model: Tool, - as: 'Instruments', - where: { - size: { [Op.ne]: 'small' } - } - } -}); - -// Inner where, `required: false` -await User.findAll({ - include: { - model: Tool, - as: 'Instruments', - where: { - size: { [Op.ne]: 'small' } - }, - required: false - } -}); - -// Top-level where, with default `required: false` -await User.findAll({ - where: { - '$Instruments.size$': { [Op.ne]: 'small' } - }, - include: { - model: Tool, - as: 'Instruments' - } -}); - -// Top-level where, `required: true` -await User.findAll({ - where: { - '$Instruments.size$': { [Op.ne]: 'small' } - }, - include: { - model: Tool, - as: 'Instruments', - required: true - } -}); -``` - -Generated SQLs, in order: - -```sql --- Inner where, with default `required: true` -SELECT [...] FROM `users` AS `user` -INNER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` - AND `Instruments`.`size` != 'small'; - --- Inner where, `required: false` -SELECT [...] FROM `users` AS `user` -LEFT OUTER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` - AND `Instruments`.`size` != 'small'; - --- Top-level where, with default `required: false` -SELECT [...] FROM `users` AS `user` -LEFT OUTER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` -WHERE `Instruments`.`size` != 'small'; - --- Top-level where, `required: true` -SELECT [...] FROM `users` AS `user` -INNER JOIN `tools` AS `Instruments` ON - `user`.`id` = `Instruments`.`userId` -WHERE `Instruments`.`size` != 'small'; -``` - -### Fetching with `RIGHT OUTER JOIN` (MySQL, MariaDB, PostgreSQL and MSSQL only) - -By default, associations are loaded using a `LEFT OUTER JOIN` - that is to say it only includes records from the parent table. You can change this behavior to a `RIGHT OUTER JOIN` by passing the `right` option, if the dialect you are using supports it. - -Currenly, SQLite does not support [right joins](https://www.sqlite.org/omitted.html). - -*Note:* `right` is only respected if `required` is false. - -```js -User.findAll({ - include: [{ - model: Task // will create a left join - }] -}); -User.findAll({ - include: [{ - model: Task, - right: true // will create a right join - }] -}); -User.findAll({ - include: [{ - model: Task, - required: true, - right: true // has no effect, will create an inner join - }] -}); -User.findAll({ - include: [{ - model: Task, - where: { name: { [Op.ne]: 'empty trash' } }, - right: true // has no effect, will create an inner join - }] -}); -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.ne]: 'empty trash' } }, - required: false // will create a left join - }] -}); -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.ne]: 'empty trash' } }, - required: false - right: true // will create a right join - }] -}); -``` - -## Multiple eager loading - -The `include` option can receive an array in order to fetch multiple associated models at once: - -```js -Foo.findAll({ - include: [ - { - model: Bar, - required: true - }, - { - model: Baz, - where: /* ... */ - }, - Qux // Shorthand syntax for { model: Qux } also works here - ] -}) -``` - -## Eager loading with Many-to-Many relationships - -When you perform eager loading on a model with a Belongs-to-Many relationship, Sequelize will fetch the junction table data as well, by default. For example: - -```js -const Foo = sequelize.define('Foo', { name: DataTypes.TEXT }); -const Bar = sequelize.define('Bar', { name: DataTypes.TEXT }); -Foo.belongsToMany(Bar, { through: 'Foo_Bar' }); -Bar.belongsToMany(Foo, { through: 'Foo_Bar' }); - -await sequelize.sync(); -const foo = await Foo.create({ name: 'foo' }); -const bar = await Bar.create({ name: 'bar' }); -await foo.addBar(bar); -const fetchedFoo = Foo.findOne({ include: Bar }); -console.log(JSON.stringify(fetchedFoo, null, 2)); -``` - -Output: - -```json -{ - "id": 1, - "name": "foo", - "Bars": [ - { - "id": 1, - "name": "bar", - "Foo_Bar": { - "FooId": 1, - "BarId": 1 - } - } - ] -} -``` - -Note that every bar instance eager loaded into the `"Bars"` property has an extra property called `Foo_Bar` which is the relevant Sequelize instance of the junction model. By default, Sequelize fetches all attributes from the junction table in order to build this extra property. - -However, you can specify which attributes you want fetched. This is done with the `attributes` option applied inside the `through` option of the include. For example: - -```js -Foo.findAll({ - include: [{ - model: Bar, - through: { - attributes: [/* list the wanted attributes here */] - } - }] -}); -``` - -If you don't want anything from the junction table, you can explicitly provide an empty array to the `attributes` option, and in this case nothing will be fetched and the extra property will not even be created: - -```js -Foo.findOne({ - include: { - model: Bar, - attributes: [] - } -}); -``` - -Output: - -```json -{ - "id": 1, - "name": "foo", - "Bars": [ - { - "id": 1, - "name": "bar" - } - ] -} -``` - -Whenever including a model from a Many-to-Many relationship, you can also apply a filter on the junction table. This is done with the `where` option applied inside the `through` option of the include. For example: - -```js -User.findAll({ - include: [{ - model: Project, - through: { - where: { - // Here, `completed` is a column present at the junction table - completed: true - } - } - }] -}); -``` - -Generated SQL (using SQLite): - -```sql -SELECT - `User`.`id`, - `User`.`name`, - `Projects`.`id` AS `Projects.id`, - `Projects`.`name` AS `Projects.name`, - `Projects->User_Project`.`completed` AS `Projects.User_Project.completed`, - `Projects->User_Project`.`UserId` AS `Projects.User_Project.UserId`, - `Projects->User_Project`.`ProjectId` AS `Projects.User_Project.ProjectId` -FROM `Users` AS `User` -LEFT OUTER JOIN `User_Projects` AS `Projects->User_Project` ON - `User`.`id` = `Projects->User_Project`.`UserId` -LEFT OUTER JOIN `Projects` AS `Projects` ON - `Projects`.`id` = `Projects->User_Project`.`ProjectId` AND - `Projects->User_Project`.`completed` = 1; -``` - -## Including everything - -To include all associated models, you can use the `all` and `nested` options: - -```js -// Fetch all models associated with User -User.findAll({ include: { all: true }}); - -// Fetch all models associated with User and their nested associations (recursively) -User.findAll({ include: { all: true, nested: true }}); -``` - -## Including soft deleted records - -In case you want to eager load soft deleted records you can do that by setting `include.paranoid` to `false`: - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - where: { size: { [Op.ne]: 'small' } }, - paranoid: false - }] -}); -``` - -## Ordering eager loaded associations - -When you want to apply `ORDER` clauses to eager loaded models, you must use the top-level `order` option with augmented arrays, starting with the specification of the nested model you want to sort. - -This is better understood with examples. - -```js -Company.findAll({ - include: Division, - order: [ - // We start the order array with the model we want to sort - [Division, 'name', 'ASC'] - ] -}); -Company.findAll({ - include: Division, - order: [ - [Division, 'name', 'DESC'] - ] -}); -Company.findAll({ - // If the include uses an alias... - include: { model: Division, as: 'Div' }, - order: [ - // ...we use the same syntax from the include - // in the beginning of the order array - [{ model: Division, as: 'Div' }, 'name', 'DESC'] - ] -}); - -Company.findAll({ - // If we have includes nested in several levels... - include: { - model: Division, - include: Department - }, - order: [ - // ... we replicate the include chain of interest - // at the beginning of the order array - [Division, Department, 'name', 'DESC'] - ] -}); -``` - -In the case of many-to-many relationships, you are also able to sort by attributes in the through table. For example, assuming we have a Many-to-Many relationship between `Division` and `Department` whose junction model is `DepartmentDivision`, you can do: - -```js -Company.findAll({ - include: { - model: Division, - include: Department - }, - order: [ - [Division, DepartmentDivision, 'name', 'ASC'] - ] -}); -``` - -In all the above examples, you have noticed that the `order` option is used at the top-level. The only situation in which `order` also works inside the include option is when `separate: true` is used. In that case, the usage is as follows: - -```js -// This only works for `separate: true` (which in turn -// only works for has-many relationships). -User.findAll({ - include: { - model: Post, - separate: true, - order: [ - ['createdAt', 'DESC'] - ] - } -}); -``` - -### Complex ordering involving sub-queries - -Take a look at the [guide on sub-queries](sub-queries.html) for an example of how to use a sub-query to assist a more complex ordering. - -## Nested eager loading - -You can use nested eager loading to load all related models of a related model: - -```js -const users = await User.findAll({ - include: { - model: Tool, - as: 'Instruments', - include: { - model: Teacher, - include: [ /* etc */ ] - } - } -}); -console.log(JSON.stringify(users, null, 2)); -``` - -Output: - -```json -[{ - "name": "John Doe", - "id": 1, - "Instruments": [{ // 1:M and N:M association - "name": "Scissor", - "id": 1, - "userId": 1, - "Teacher": { // 1:1 association - "name": "Jimi Hendrix" - } - }] -}] -``` - -This will produce an outer join. However, a `where` clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add `required: false`. - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - include: [{ - model: Teacher, - where: { - school: "Woodstock Music School" - }, - required: false - }] - }] -}); -``` - -The query above will return all users, and all their instruments, but only those teachers associated with `Woodstock Music School`. - -## Using `findAndCountAll` with includes - -The `findAndCountAll` utility function supports includes. Only the includes that are marked as `required` will be considered in `count`. For example, if you want to find and count all users who have a profile: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, required: true } - ], - limit: 3 -}); -``` - -Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted. Adding a `where` clause to the include automatically makes it required: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, where: { active: true } } - ], - limit: 3 -}); -``` - -The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/polymorphic-associations.md b/docs/manual/advanced-association-concepts/polymorphic-associations.md deleted file mode 100644 index 39ee46372626..000000000000 --- a/docs/manual/advanced-association-concepts/polymorphic-associations.md +++ /dev/null @@ -1,427 +0,0 @@ -# Polymorphic Associations - -_**Note:** the usage of polymorphic associations in Sequelize, as outlined in this guide, should be done with caution. Don't just copy-paste code from here, otherwise you might easily make mistakes and introduce bugs in your code. Make sure you understand what is going on._ - -## Concept - -A **polymorphic association** consists on two (or more) associations happening with the same foreign key. - -For example, consider the models `Image`, `Video` and `Comment`. The first two represent something that a user might post. We want to allow comments to be placed in both of them. This way, we immediately think of establishing the following associations: - -* A One-to-Many association between `Image` and `Comment`: - - ```js - Image.hasMany(Comment); - Comment.belongsTo(Image); - ``` - -* A One-to-Many association between `Video` and `Comment`: - - ```js - Video.hasMany(Comment); - Comment.belongsTo(Video); - ``` - -However, the above would cause Sequelize to create two foreign keys on the `Comment` table: `ImageId` and `VideoId`. This is not ideal because this structure makes it look like a comment can be attached at the same time to one image and one video, which isn't true. Instead, what we really want here is precisely a polymorphic association, in which a `Comment` points to a single **Commentable**, an abstract polymorphic entity that represents one of `Image` or `Video`. - -Before proceeding to how to configure such an association, let's see how using it looks like: - -```js -const image = await Image.create({ url: "https://placekitten.com/408/287" }); -const comment = await image.createComment({ content: "Awesome!" }); - -console.log(comment.commentableId === image.id); // true - -// We can also retrieve which type of commentable a comment is associated to. -// The following prints the model name of the associated commentable instance. -console.log(comment.commentableType); // "Image" - -// We can use a polymorphic method to retrieve the associated commentable, without -// having to worry whether it's an Image or a Video. -const associatedCommentable = await comment.getCommentable(); - -// In this example, `associatedCommentable` is the same thing as `image`: -const isDeepEqual = require('deep-equal'); -console.log(isDeepEqual(image, commentable)); // true -``` - -## Configuring a One-to-Many polymorphic association - -To setup the polymorphic association for the example above (which is an example of One-to-Many polymorphic association), we have the following steps: - -* Define a string field called `commentableType` in the `Comment` model; -* Define the `hasMany` and `belongsTo` association between `Image`/`Video` and `Comment`: - * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; - * Specifying the appropriate [association scopes](association-scopes.html); -* To properly support lazy loading, define a new instance method on the `Comment` model called `getCommentable` which calls, under the hood, the correct mixin to fetch the appropriate commentable; -* To properly support eager loading, define an `afterFind` hook on the `Comment` model that automatically populates the `commentable` field in every instance; -* To prevent bugs/mistakes in eager loading, you can also delete the concrete fields `image` and `video` from Comment instances in the same `afterFind` hook, leaving only the abstract `commentable` field available. - -Here is an example: - -```js -// Helper function -const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`; - -class Image extends Model {} -Image.init({ - title: DataTypes.STRING, - url: DataTypes.STRING -}, { sequelize, modelName: 'image' }); - -class Video extends Model {} -Video.init({ - title: DataTypes.STRING, - text: DataTypes.STRING -}, { sequelize, modelName: 'video' }); - -class Comment extends Model { - getCommentable(options) { - if (!this.commentableType) return Promise.resolve(null); - const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`; - return this[mixinMethodName](options); - } -} -Comment.init({ - title: DataTypes.STRING, - commentableId: DataTypes.INTEGER, - commentableType: DataTypes.STRING -}, { sequelize, modelName: 'comment' }); - -Image.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentableType: 'image' - } -}); -Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false }); - -Video.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentableType: 'video' - } -}); -Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false }); - -Comment.addHook("afterFind", findResult => { - if (!Array.isArray(findResult)) findResult = [findResult]; - for (const instance of findResult) { - if (instance.commentableType === "image" && instance.image !== undefined) { - instance.commentable = instance.image; - } else if (instance.commentableType === "video" && instance.video !== undefined) { - instance.commentable = instance.video; - } - // To prevent mistakes: - delete instance.image; - delete instance.dataValues.image; - delete instance.video; - delete instance.dataValues.video; - } -}); -``` - -Since the `commentableId` column references several tables (two in this case), we cannot add a `REFERENCES` constraint to it. This is why the `constraints: false` option was used. - -Note that, in the code above: - -* The *Image -> Comment* association defined an association scope: `{ commentableType: 'image' }` -* The *Video -> Comment* association defined an association scope: `{ commentableType: 'video' }` - -These scopes are automatically applied when using the association functions (as explained in the [Association Scopes](association-scopes.html) guide). Some examples are below, with their generated SQL statements: - -* `image.getComments()`: - - ```sql - SELECT "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" - FROM "comments" AS "comment" - WHERE "comment"."commentableType" = 'image' AND "comment"."commentableId" = 1; - ``` - - Here we can see that `` `comment`.`commentableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. - -* `image.createComment({ title: 'Awesome!' })`: - - ```sql - INSERT INTO "comments" ( - "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" - ) VALUES ( - DEFAULT, 'Awesome!', 'image', 1, - '2018-04-17 05:36:40.454 +00:00', '2018-04-17 05:36:40.454 +00:00' - ) RETURNING *; - ``` - -* `image.addComment(comment)`: - - ```sql - UPDATE "comments" - SET "commentableId"=1, "commentableType"='image', "updatedAt"='2018-04-17 05:38:43.948 +00:00' - WHERE "id" IN (1) - ``` - -### Polymorphic lazy loading - -The `getCommentable` instance method on `Comment` provides an abstraction for lazy loading the associated commentable - working whether the comment belongs to an Image or a Video. - -It works by simply converting the `commentableType` string into a call to the correct mixin (either `getImage` or `getVideo`). - -Note that the `getCommentable` implementation above: - -* Returns `null` when no association is present (which is good); -* Allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. - -### Polymorphic eager loading - -Now, we want to perform a polymorphic eager loading of the associated commentables for one (or more) comments. We want to achieve something similar to the following idea: - -```js -const comment = await Comment.findOne({ - include: [ /* What to put here? */ ] -}); -console.log(comment.commentable); // This is our goal -``` - -The solution is to tell Sequelize to include both Images and Videos, so that our `afterFind` hook defined above will do the work, automatically adding the `commentable` field to the instance object, providing the abstraction we want. - -For example: - -```js -const comments = await Comment.findAll({ - include: [Image, Video] -}); -for (const comment of comments) { - const message = `Found comment #${comment.id} with ${comment.commentableType} commentable:`; - console.log(message, comment.commentable.toJSON()); -} -``` - -Output example: - -```text -Found comment #1 with image commentable: { id: 1, - title: 'Meow', - url: 'https://placekitten.com/408/287', - createdAt: 2019-12-26T15:04:53.047Z, - updatedAt: 2019-12-26T15:04:53.047Z } -``` - -### Caution - possibly invalid eager/lazy loading! - -Consider a comment `Foo` whose `commentableId` is 2 and `commentableType` is `image`. Consider also that `Image A` and `Video X` both happen to have an id equal to 2. Conceptually, it is clear that `Video X` is not associated to `Foo`, because even though its id is 2, the `commentableType` of `Foo` is `image`, not `video`. However, this distinction is made by Sequelize only at the level of the abstractions performed by `getCommentable` and the hook we created above. - -This means that if you call `Comment.findAll({ include: Video })` in the situation above, `Video X` will be eager loaded into `Foo`. Thankfully, our `afterFind` hook will delete it automatically, to help prevent bugs, but regardless it is important that you understand what is going on. - -The best way to prevent this kind of mistake is to **avoid using the concrete accessors and mixins directly at all costs** (such as `.image`, `.getVideo()`, `.setImage()`, etc), always preferring the abstractions we created, such as `.getCommentable()` and `.commentable`. If you really need to access eager-loaded `.image` and `.video` for some reason, make sure you wrap that in a type check such as `comment.commentableType === 'image'`. - -## Configuring a Many-to-Many polymorphic association - -In the above example, we had the models `Image` and `Video` being abstractly called *commentables*, with one *commentable* having many comments. However, one given comment would belong to a single *commentable* - this is why the whole situation is a One-to-Many polymorphic association. - -Now, to consider a Many-to-Many polymorphic association, instead of considering comments, we will consider tags. For convenience, instead of calling Image and Video as *commentables*, we will now call them *taggables*. One *taggable* may have several tags, and at the same time one tag can be placed in several *taggables*. - -The setup for this goes as follows: - -* Define the juncion model explicitly, specifying the two foreign keys as `tagId` and `taggableId` (this way it is a junction model for a Many-to-Many relationship between `Tag` and the abstract concept of *taggable*); -* Define a string field called `taggableType` in the junction model; -* Define the `belongsToMany` associations between the two models and `Tag`: - * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; - * Specifying the appropriate [association scopes](association-scopes.html); -* Define a new instance method on the `Tag` model called `getTaggables` which calls, under the hood, the correct mixin to fetch the appropriate taggables. - -Implementation: - -```js -class Tag extends Model { - getTaggables(options) { - const images = await this.getImages(options); - const videos = await this.getVideos(options); - // Concat images and videos in a single array of taggables - return images.concat(videos); - } -} -Tag.init({ - name: DataTypes.STRING -}, { sequelize, modelName: 'tag' }); - -// Here we define the junction model explicitly -class Tag_Taggable extends Model {} -Tag_Taggable.init({ - tagId: { - type: DataTypes.INTEGER, - unique: 'tt_unique_constraint' - }, - taggableId: { - type: DataTypes.INTEGER, - unique: 'tt_unique_constraint', - references: null - }, - taggableType: { - type: DataTypes.STRING, - unique: 'tt_unique_constraint' - } -}, { sequelize, modelName: 'tag_taggable' }); - -Image.belongsToMany(Tag, { - through: { - model: Tag_Taggable, - unique: false, - scope: { - taggableType: 'image' - } - }, - foreignKey: 'taggableId', - constraints: false -}); -Tag.belongsToMany(Image, { - through: { - model: Tag_Taggable, - unique: false - }, - foreignKey: 'tagId', - constraints: false -}); - -Video.belongsToMany(Tag, { - through: { - model: Tag_Taggable, - unique: false, - scope: { - taggableType: 'video' - } - }, - foreignKey: 'taggableId', - constraints: false -}); -Tag.belongsToMany(Video, { - through: { - model: Tag_Taggable, - unique: false - }, - foreignKey: 'tagId', - constraints: false -}); -``` - -The `constraints: false` option disables references constraints, as the `taggableId` column references several tables, we cannot add a `REFERENCES` constraint to it. - -Note that: - -* The *Image -> Tag* association defined an association scope: `{ taggableType: 'image' }` -* The *Video -> Tag* association defined an association scope: `{ taggableType: 'video' }` - -These scopes are automatically applied when using the association functions. Some examples are below, with their generated SQL statements: - -* `image.getTags()`: - - ```sql - SELECT - `tag`.`id`, - `tag`.`name`, - `tag`.`createdAt`, - `tag`.`updatedAt`, - `tag_taggable`.`tagId` AS `tag_taggable.tagId`, - `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, - `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, - `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, - `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` - FROM `tags` AS `tag` - INNER JOIN `tag_taggables` AS `tag_taggable` ON - `tag`.`id` = `tag_taggable`.`tagId` AND - `tag_taggable`.`taggableId` = 1 AND - `tag_taggable`.`taggableType` = 'image'; - ``` - - Here we can see that `` `tag_taggable`.`taggableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. - -* `tag.getTaggables()`: - - ```sql - SELECT - `image`.`id`, - `image`.`url`, - `image`.`createdAt`, - `image`.`updatedAt`, - `tag_taggable`.`tagId` AS `tag_taggable.tagId`, - `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, - `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, - `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, - `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` - FROM `images` AS `image` - INNER JOIN `tag_taggables` AS `tag_taggable` ON - `image`.`id` = `tag_taggable`.`taggableId` AND - `tag_taggable`.`tagId` = 1; - - SELECT - `video`.`id`, - `video`.`url`, - `video`.`createdAt`, - `video`.`updatedAt`, - `tag_taggable`.`tagId` AS `tag_taggable.tagId`, - `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, - `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, - `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, - `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` - FROM `videos` AS `video` - INNER JOIN `tag_taggables` AS `tag_taggable` ON - `video`.`id` = `tag_taggable`.`taggableId` AND - `tag_taggable`.`tagId` = 1; - ``` - -Note that the above implementation of `getTaggables()` allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. - -### Applying scopes on the target model - -In the example above, the `scope` options (such as `scope: { taggableType: 'image' }`) were applied to the *through* model, not the *target* model, since it was used under the `through` option. - -We can also apply an association scope on the target model. We can even do both at the same time. - -To illustrate this, consider an extension of the above example between tags and taggables, where each tag has a status. This way, to get all pending tags of an image, we could establish another `belognsToMany` relationship between `Image` and `Tag`, this time applying a scope on the through model and another scope on the target model: - -```js -Image.belongsToMany(Tag, { - through: { - model: Tag_Taggable, - unique: false, - scope: { - taggableType: 'image' - } - }, - scope: { - status: 'pending' - }, - as: 'pendingTags', - foreignKey: 'taggableId', - constraints: false -}); -``` - -This way, when calling `image.getPendingTags()`, the following SQL query will be generated: - -```sql -SELECT - `tag`.`id`, - `tag`.`name`, - `tag`.`status`, - `tag`.`createdAt`, - `tag`.`updatedAt`, - `tag_taggable`.`tagId` AS `tag_taggable.tagId`, - `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, - `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, - `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, - `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` -FROM `tags` AS `tag` -INNER JOIN `tag_taggables` AS `tag_taggable` ON - `tag`.`id` = `tag_taggable`.`tagId` AND - `tag_taggable`.`taggableId` = 1 AND - `tag_taggable`.`taggableType` = 'image' -WHERE ( - `tag`.`status` = 'pending' -); -``` - -We can see that both scopes were applied automatically: - -* `` `tag_taggable`.`taggableType` = 'image'`` was added automatically to the `INNER JOIN`; -* `` `tag`.`status` = 'pending'`` was added automatically to an outer where clause. \ No newline at end of file diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md deleted file mode 100644 index b511d7c62f80..000000000000 --- a/docs/manual/core-concepts/assocs.md +++ /dev/null @@ -1,784 +0,0 @@ -# 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 deleted file mode 100644 index c280dc3ae344..000000000000 --- a/docs/manual/core-concepts/getters-setters-virtuals.md +++ /dev/null @@ -1,201 +0,0 @@ -# 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 deleted file mode 100644 index 46a9760255d3..000000000000 --- a/docs/manual/core-concepts/getting-started.md +++ /dev/null @@ -1,111 +0,0 @@ -# Getting Started - -In this tutorial you will learn to make a simple setup of Sequelize. - -## Installing - -Sequelize is available via [npm](https://www.npmjs.com/package/sequelize) (or [yarn](https://yarnpkg.com/package/sequelize)). - -```sh -npm install --save sequelize -``` - -You'll also have to manually install the driver for your database of choice: - -```sh -# One of the following: -$ npm install --save pg pg-hstore # Postgres -$ npm install --save mysql2 -$ npm install --save mariadb -$ npm install --save sqlite3 -$ npm install --save tedious # Microsoft SQL Server -``` - -## Connecting to a database - -To connect to the database, you must create a Sequelize instance. This can be done by either passing the connection parameters separately to the Sequelize constructor or by passing a single connection URI: - -```js -const { Sequelize } = require('sequelize'); - -// Option 1: Passing a connection URI -const sequelize = new Sequelize('sqlite::memory:') // Example for sqlite -const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Example for postgres - -// Option 2: Passing parameters separately (sqlite) -const sequelize = new Sequelize({ - dialect: 'sqlite', - storage: 'path/to/database.sqlite' -}); - -// Option 2: Passing parameters separately (other dialects) -const sequelize = new Sequelize('database', 'username', 'password', { - host: 'localhost', - dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ -}); -``` - -The Sequelize constructor accepts a lot of options. They are documented in the [API Reference](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). - -### Testing the connection - -You can use the `.authenticate()` function to test if the connection is OK: - -```js -try { - await sequelize.authenticate(); - console.log('Connection has been established successfully.'); -} catch (error) { - console.error('Unable to connect to the database:', error); -} -``` - -### Closing the connection - -Sequelize will keep the connection open by default, and use the same connection for all queries. If you need to close the connection, call `sequelize.close()` (which is asynchronous and returns a Promise). - -## Terminology convention - -Observe that, in the examples above, `Sequelize` refers to the library itself while `sequelize` refers to an instance of Sequelize, which represents a connection to one database. This is the recommended convention and it will be followed throughout the documentation. - -## Tip for reading the docs - -You are encouraged to run code examples locally while reading the Sequelize docs. This will help you learn faster. The easiest way to do this is using the SQLite dialect: - -```js -const { Sequelize, Op, Model, DataTypes } = require("sequelize"); -const sequelize = new Sequelize("sqlite::memory:"); - -// Code here! It works! -``` - -To experiment with the other dialects, which are harder to setup locally, you can use the [Sequelize SSCCE](https://github.com/papb/sequelize-sscce) GitHub repository, which allows you to run code on all supported dialects directly from GitHub, for free, without any setup! - -## New databases versus existing databases - -If you are starting a project from scratch, and your database does not exist yet, Sequelize can be used since the beginning in order to automate the creation of every table in your database. - -Also, if you want to use Sequelize to connect to a database that is already filled with tables and data, that works as well! Sequelize has got you covered in both cases. - -## Logging - -By default, Sequelize will log to console every SQL query it performs. The `options.logging` option can be used to customize this behavior, by defining the function that gets executed every time Sequelize would log something. The default value is `console.log` and when using that only the first log parameter of log function call is displayed. For example, for query logging the first parameter is the raw query and the second (hidden by default) is the Sequelize object. - -Common useful values for `options.logging`: - -```js -const sequelize = new Sequelize('sqlite::memory:', { - // Choose one of the logging options - logging: console.log, // Default, displays the first parameter of the log function call - logging: (...msg) => console.log(msg), // Displays all log function call parameters - logging: false, // Disables logging - logging: msg => logger.debug(msg), // Use custom logger (e.g. Winston or Bunyan), displays the first parameter - logging: logger.debug.bind(logger) // Alternative way to use custom logger, displays all messages -}); -``` - -## 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 deleted file mode 100644 index db4075280dec..000000000000 --- a/docs/manual/core-concepts/model-basics.md +++ /dev/null @@ -1,437 +0,0 @@ -# Model Basics - -In this tutorial you will learn what models are in Sequelize and how to use them. - -## Concept - -Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends [Model](../class/lib/model.js~Model.html). - -The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). - -A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as `User`) while tables have pluralized names (such as `Users`), although this is fully configurable. - -## Model Definition - -Models can be defined in two equivalent ways in Sequelize: - -* Calling [`sequelize.define(modelName, attributes, options)`](../class/lib/sequelize.js~Sequelize.html#instance-method-define) -* Extending [Model](../class/lib/model.js~Model.html) and calling [`init(attributes, options)`](../class/lib/model.js~Model.html#static-method-init) - -After a model is defined, it is available within `sequelize.models` by its model name. - -To learn with an example, we will consider that we want to create a model to represent users, which have a `firstName` and a `lastName`. We want our model to be called `User`, and the table it represents is called `Users` in the database. - -Both ways to define this model are shown below. After being defined, we can access our model with `sequelize.models.User`. - -### Using [`sequelize.define`](../class/lib/sequelize.js~Sequelize.html#instance-method-define): - -```js -const { Sequelize, DataTypes } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory:'); - -const User = sequelize.define('User', { - // Model attributes are defined here - firstName: { - type: DataTypes.STRING, - allowNull: false - }, - lastName: { - type: DataTypes.STRING - // allowNull defaults to true - } -}, { - // Other model options go here -}); - -// `sequelize.define` also returns the model -console.log(User === sequelize.models.User); // true -``` - -### Extending [Model](../class/lib/model.js~Model.html) - -```js -const { Sequelize, DataTypes, Model } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory'); - -class User extends Model {} - -User.init({ - // Model attributes are defined here - firstName: { - type: DataTypes.STRING, - allowNull: false - }, - lastName: { - type: DataTypes.STRING - // allowNull defaults to true - } -}, { - // Other model options go here - sequelize, // We need to pass the connection instance - modelName: 'User' // We need to choose the model name -}); - -// the defined model is the class itself -console.log(User === sequelize.models.User); // true -``` - -Internally, `sequelize.define` calls `Model.init`, so both approaches are essentially equivalent. - -## Table name inference - -Observe that, in both methods above, the table name (`Users`) was never explicitly defined. However, the model name was given (`User`). - -By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called [inflection](https://www.npmjs.com/package/inflection), so that irregular plurals (such as `person -> people`) are computed correctly. - -Of course, this behavior is easily configurable. - -### Enforcing the table name to be equal to the model name - -You can stop the auto-pluralization performed by Sequelize using the `freezeTableName: true` option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - freezeTableName: true -}); -``` - -The example above will create a model named `User` pointing to a table also named `User`. - -This behavior can also be defined globally for the sequelize instance, when it is created: - -```js -const sequelize = new Sequelize('sqlite::memory:', { - define: { - freezeTableName: true - } -}); -``` - -This way, all tables will use the same name as the model name. - -### Providing the table name directly - -You can simply tell Sequelize the name of the table directly as well: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - tableName: 'Employees' -}); -``` - -## Model synchronization - -When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference? - -This is where model synchronization comes in. A model can be synchronized with the database by calling [`model.sync(options)`](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-sync), an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side. - -* `User.sync()` - This creates the table if it doesn't exist (and does nothing if it already exists) -* `User.sync({ force: true })` - This creates the table, dropping it first if it already existed -* `User.sync({ alter: true })` - This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model. - -Example: - -```js -await User.sync({ force: true }); -console.log("The table for the User model was just (re)created!"); -``` - -### Synchronizing all models at once - -You can use [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync) to automatically synchronize all models. Example: - -```js -await sequelize.sync({ force: true }); -console.log("All models were synchronized successfully."); -``` - -### Dropping tables - -To drop the table related to a model: - -```js -await User.drop(); -console.log("User table dropped!"); -``` - -To drop all tables: - -```js -await sequelize.drop(); -console.log("All tables dropped!"); -``` - -### Database safety check - -As shown above, the `sync` and `drop` operations are destructive. Sequelize accepts a `match` option as an additional safety check, which receives a RegExp: - -```js -// This will run .sync() only if database name ends with '_test' -sequelize.sync({ force: true, match: /_test$/ }); -``` - -### Synchronization in production - -As shown above, `sync({ force: true })` and `sync({ alter: true })` can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of [Migrations](migrations.html), with the help of the [Sequelize CLI](https://github.com/sequelize/cli). - -## Timestamps - -By default, Sequelize automatically adds the fields `createdAt` and `updatedAt` to every model, using the data type `DataTypes.DATE`. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The `createdAt` field will contain the timestamp representing the moment of creation, and the `updatedAt` will contain the timestamp of the latest update. - -**Note:** This is done in the Sequelize level (i.e. not done with *SQL triggers*). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically. - -This behavior can be disabled for a model with the `timestamps: false` option: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - timestamps: false -}); -``` - -It is also possible to enable only one of `createdAt`/`updatedAt`, and to provide a custom name for these columns: - -```js -class Foo extends Model {} -Foo.init({ /* attributes */ }, { - sequelize, - - // don't forget to enable timestamps! - timestamps: true, - - // I don't want createdAt - createdAt: false, - - // I want updatedAt to actually be called updateTimestamp - updatedAt: 'updateTimestamp' -}); -``` - -## Column declaration shorthand syntax - -If the only thing being specified about a column is its data type, the syntax can be shortened: - -```js -// This: -sequelize.define('User', { - name: { - type: DataTypes.STRING - } -}); - -// Can be simplified to: -sequelize.define('User', { name: DataTypes.STRING }); -``` - -## Default Values - -By default, Sequelize assumes that the default value of a column is `NULL`. This behavior can be changed by passing a specific `defaultValue` to the column definition: - -```js -sequelize.define('User', { - name: { - type: DataTypes.STRING, - defaultValue: "John Doe" - } -}); -``` - -Some special values, such as `Sequelize.NOW`, are also accepted: - -```js -sequelize.define('Foo', { - bar: { - type: DataTypes.DATETIME, - defaultValue: Sequelize.NOW - // This way, the current date/time will be used to populate this column (at the moment of insertion) - } -}); -``` - -## Data Types - -Every column you define in your model must have a data type. Sequelize provides [a lot of built-in data types](https://github.com/sequelize/sequelize/blob/main/lib/data-types.js). To access a built-in data type, you must import `DataTypes`: - -```js -const { DataTypes } = require("sequelize"); // Import the built-in data types -``` - -### Strings - -```js -DataTypes.STRING // VARCHAR(255) -DataTypes.STRING(1234) // VARCHAR(1234) -DataTypes.STRING.BINARY // VARCHAR BINARY -DataTypes.TEXT // TEXT -DataTypes.TEXT('tiny') // TINYTEXT -DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. -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 `Sequelize.UUIDV1` or `Sequelize.UUIDV4` as the default value: - -```js -{ - type: DataTypes.UUID, - defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1 -} -``` - -### Others - -There are other data types, covered in a [separate guide](other-data-types.html). - -## Column Options - -When defining a column, apart from specifying the `type` of the column, and the `allowNull` and `defaultValue` options mentioned above, there are a lot more options that can be used. Some examples are below. - -```js -const { Model, DataTypes, Deferrable } = require("sequelize"); - -class Foo extends Model {} -Foo.init({ - // instantiating will automatically set the flag to true if not set - flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, - - // default values for dates => current time - myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - - // setting allowNull to false will add NOT NULL to the column, which means an error will be - // thrown from the DB when the query is executed if the column is null. If you want to check that a value - // is not null before querying the DB, look at the validations section below. - title: { type: DataTypes.STRING, allowNull: false }, - - // Creating two objects with the same value will throw an error. The unique property can be either a - // boolean, or a string. If you provide the same string for multiple columns, they will form a - // composite unique key. - uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, - uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, - - // The unique property is simply a shorthand to create a unique constraint. - someUnique: { type: DataTypes.STRING, unique: true }, - - // Go on reading for further information about primary keys - identifier: { type: DataTypes.STRING, primaryKey: true }, - - // autoIncrement can be used to create auto_incrementing integer columns - incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, - - // You can specify a custom column name via the 'field' attribute: - fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, - - // It is possible to create foreign keys: - bar_id: { - type: DataTypes.INTEGER, - - references: { - // This is a reference to another model - model: Bar, - - // This is the column name of the referenced model - key: 'id', - - // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type. - deferrable: Deferrable.INITIALLY_IMMEDIATE - // Options: - // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints - // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction - // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction - } - }, - - // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL - commentMe: { - type: DataTypes.INTEGER, - comment: 'This is a column name that has a comment' - } -}, { - sequelize, - modelName: 'foo', - - // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options: - indexes: [{ unique: true, fields: ['someUnique'] }] -}); -``` - -## Taking advantage of Models being classes - -The Sequelize models are [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). You can very easily add custom instance or class level methods. - -```js -class User extends Model { - static classLevelMethod() { - return 'foo'; - } - instanceLevelMethod() { - return 'bar'; - } - getFullname() { - return [this.firstname, this.lastname].join(' '); - } -} -User.init({ - firstname: Sequelize.TEXT, - lastname: Sequelize.TEXT -}, { sequelize }); - -console.log(User.classLevelMethod()); // 'foo' -const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); -console.log(user.instanceLevelMethod()); // 'bar' -console.log(user.getFullname()); // 'Jane Doe' -``` diff --git a/docs/manual/core-concepts/model-instances.md b/docs/manual/core-concepts/model-instances.md deleted file mode 100644 index 27699dc2412c..000000000000 --- a/docs/manual/core-concepts/model-instances.md +++ /dev/null @@ -1,172 +0,0 @@ -# Model Instances - -As you already know, a model is an [ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). An instance of the class represents one object from that model (which maps to one row of the table in the database). This way, model instances are [DAOs](https://en.wikipedia.org/wiki/Data_access_object). - -For this guide, the following setup will be assumed: - -```js -const { Sequelize, Model, DataTypes } = require("sequelize"); -const sequelize = new Sequelize("sqlite::memory:"); - -const User = sequelize.define("user", { - name: DataTypes.TEXT, - favoriteColor: { - type: DataTypes.TEXT, - defaultValue: 'green' - }, - age: DataTypes.INTEGER, - cash: DataTypes.INTEGER -}); - -(async () => { - await sequelize.sync({ force: true }); - // Code here -})(); -``` - -## Creating an instance - -Although a model is a class, you should not create instances by using the `new` operator directly. Instead, the [`build`](../class/lib/model.js~Model.html#static-method-build) method should be used: - -```js -const jane = User.build({ name: "Jane" }); -console.log(jane instanceof User); // true -console.log(jane.name); // "Jane" -``` - -However, the code above does not communicate with the database at all (note that it is not even asynchronous)! This is because the [`build`](../class/lib/model.js~Model.html#static-method-build) method only creates an object that *represents* data that *can* be mapped to a database. In order to really save (i.e. persist) this instance in the database, the [`save`](../class/lib/model.js~Model.html#instance-method-save) method should be used: - -```js -await jane.save(); -console.log('Jane was saved to the database!'); -``` - -Note, from the usage of `await` in the snippet above, that `save` is an asynchronous method. In fact, almost every Sequelize method is asynchronous; `build` is one of the very few exceptions. - -### A very useful shortcut: the `create` method - -Sequelize provides the [`create`](../class/lib/model.js~Model.html#static-method-create) method, which combines the `build` and `save` methods shown above into a single method: - -```js -const jane = await User.create({ name: "Jane" }); -// Jane exists in the database now! -console.log(jane instanceof User); // true -console.log(jane.name); // "Jane" -``` - -## Note: logging instances - -Trying to log a model instance directly to `console.log` will produce a lot of clutter, since Sequelize instances have a lot of things attached to them. Instead, you can use the `.toJSON()` method (which, by the way, automatically guarantees the instances to be `JSON.stringify`-ed well). - -```js -const jane = await User.create({ name: "Jane" }); -// console.log(jane); // Don't do this -console.log(jane.toJSON()); // This is good! -console.log(JSON.stringify(jane, null, 4)); // This is also good! -``` - -## Default values - -Built instances will automatically get default values: - -```js -const jane = User.build({ name: "Jane" }); -console.log(jane.favoriteColor); // "green" -``` - -## Updating an instance - -If you change the value of some field of an instance, calling `save` again will update it accordingly: - -```js -const jane = await User.create({ name: "Jane" }); -console.log(jane.name); // "Jane" -jane.name = "Ada"; -// the name is still "Jane" in the database -await jane.save(); -// Now the name was updated to "Ada" in the database! -``` - -## Deleting an instance - -You can delete an instance by calling [`destroy`](../class/lib/model.js~Model.html#instance-method-destroy): - -```js -const jane = await User.create({ name: "Jane" }); -console.log(jane.name); // "Jane" -await jane.destroy(); -// Now this entry was removed from the database -``` - -## Reloading an instance - -You can reload an instance from the database by calling [`reload`](../class/lib/model.js~Model.html#instance-method-reload): - -```js -const jane = await User.create({ name: "Jane" }); -console.log(jane.name); // "Jane" -jane.name = "Ada"; -// the name is still "Jane" in the database -await jane.reload(); -console.log(jane.name); // "Jane" -``` - -The reload call generates a `SELECT` query to get the up-to-date data from the database. - -## Saving only some fields - -It is possible to define which attributes should be saved when calling `save`, by passing an array of column names. - -This is useful when you set attributes based on a previously defined object, for example, when you get the values of an object via a form of a web app. Furthermore, this is used internally in the `update` implementation. This is how it looks like: - -```js -const jane = await User.create({ name: "Jane" }); -console.log(jane.name); // "Jane" -console.log(jane.favoriteColor); // "green" -jane.name = "Jane II"; -jane.favoriteColor = "blue"; -await jane.save({ fields: ['name'] }); -console.log(jane.name); // "Jane II" -console.log(jane.favoriteColor); // "blue" -// The above printed blue because the local object has it set to blue, but -// in the database it is still "green": -await jane.reload(); -console.log(jane.name); // "Jane II" -console.log(jane.favoriteColor); // "green" -``` - -## Change-awareness of save - -The `save` method is optimized internally to only update fields that really changed. This means that if you don't change anything and call `save`, Sequelize will know that the save is superfluous and do nothing, i.e., no query will be generated (it will still return a Promise, but it will resolve immediately). - -Also, if only a few attributes have changed when you call `save`, only those fields will be sent in the `UPDATE` query, to improve performance. - -## Incrementing and decrementing integer values - -In order to increment/decrement values of an instance without running into concurrency issues, Sequelize provides the [`increment`](../class/lib/model.js~Model.html#instance-method-increment) and [`decrement`](../class/lib/model.js~Model.html#instance-method-decrement) instance methods. - -```js -const jane = await User.create({ name: "Jane", age: 100 }); -const incrementResult = await 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 deleted file mode 100644 index cb38b32cd9ae..000000000000 --- a/docs/manual/core-concepts/model-querying-basics.md +++ /dev/null @@ -1,701 +0,0 @@ -# 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 -``` diff --git a/docs/manual/core-concepts/model-querying-finders.md b/docs/manual/core-concepts/model-querying-finders.md deleted file mode 100644 index c5644a9dca57..000000000000 --- a/docs/manual/core-concepts/model-querying-finders.md +++ /dev/null @@ -1,83 +0,0 @@ -# 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 deleted file mode 100644 index dd580d0578a9..000000000000 --- a/docs/manual/core-concepts/paranoid.md +++ /dev/null @@ -1,105 +0,0 @@ -# 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 deleted file mode 100644 index ff18dffcf8d6..000000000000 --- a/docs/manual/core-concepts/raw-queries.md +++ /dev/null @@ -1,186 +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 [`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 deleted file mode 100644 index cdc4139c9640..000000000000 --- a/docs/manual/core-concepts/validations-and-constraints.md +++ /dev/null @@ -1,270 +0,0 @@ -# Validations & Constraints - -In this tutorial you will learn how to setup validations and constraints for your models in Sequelize. - -For this tutorial, the following setup will be assumed: - -```js -const { Sequelize, Op, Model, DataTypes } = require("sequelize"); -const sequelize = new Sequelize("sqlite::memory:"); - -const User = sequelize.define("user", { - username: { - type: DataTypes.TEXT, - allowNull: false, - unique: true - }, - hashedPassword: { - type: DataTypes.STRING(64), - is: /^[0-9a-f]{64}$/i - } -}); - -(async () => { - await sequelize.sync({ force: true }); - // Code here -})(); -``` - -## Difference between Validations and Constraints - -Validations are checks performed in the Sequelize level, in pure JavaScript. They can be arbitrarily complex if you provide a custom validator function, or can be one of the built-in validators offered by Sequelize. If a validation fails, no SQL query will be sent to the database at all. - -On the other hand, constraints are rules defined at SQL level. The most basic example of constraint is an Unique Constraint. If a constraint check fails, an error will be thrown by the database and Sequelize will forward this error to JavaScript (in this example, throwing a `SequelizeUniqueConstraintError`). Note that in this case, the SQL query was performed, unlike the case for validations. - -## Unique Constraint - -Our code example above defines a unique constraint on the `username` field: - -```js -/* ... */ { - username: { - type: DataTypes.TEXT, - allowNull: false, - unique: true - }, -} /* ... */ -``` - -When this model is synchronized (by calling `sequelize.sync` for example), the `username` field will be created in the table as `` `name` TEXT UNIQUE``, and an attempt to insert an username that already exists there will throw a `SequelizeUniqueConstraintError`. - -## Allowing/disallowing null values - -By default, `null` is an allowed value for every column of a model. This can be disabled setting the `allowNull: false` option for a column, as it was done in the `username` field from our code example: - -```js -/* ... */ { - username: { - type: DataTypes.TEXT, - allowNull: false, - unique: true - }, -} /* ... */ -``` - -Without `allowNull: false`, the call `User.create({})` would work. - -### Note about `allowNull` implementation - -The `allowNull` check is the only check in Sequelize that is a mix of a *validation* and a *constraint* in the senses described at the beginning of this tutorial. This is because: - -* If an attempt is made to set `null` to a field that does not allow null, a `ValidationError` will be thrown *without any SQL query being performed*. -* In addition, after `sequelize.sync`, the column that has `allowNull: false` will be defined with a `NOT NULL` SQL constraint. This way, direct SQL queries that attempt to set the value to `null` will also fail. - -## Validators - -Model validators allow you to specify format/content/inheritance validations for each attribute of the model. Validations are automatically run on `create`, `update` and `save`. You can also call `validate()` to manually validate an instance. - -### Per-attribute validations - -You can define your custom validators or use several built-in validators, implemented by [validator.js (10.11.0)](https://github.com/chriso/validator.js), as shown below. - -```js -sequelize.define('foo', { - bar: { - type: DataTypes.STRING, - validate: { - is: /^[a-z]+$/i, // matches this RegExp - is: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string - not: /^[a-z]+$/i, // does not match this RegExp - not: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string - isEmail: true, // checks for email format (foo@bar.com) - isUrl: true, // checks for url format (http://foo.com) - isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format - isIPv4: true, // checks for IPv4 (129.89.23.1) - isIPv6: true, // checks for IPv6 format - isAlpha: true, // will only allow letters - isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail - isNumeric: true, // will only allow numbers - isInt: true, // checks for valid integers - isFloat: true, // checks for valid floating point numbers - isDecimal: true, // checks for any numbers - isLowercase: true, // checks for lowercase - isUppercase: true, // checks for uppercase - notNull: true, // won't allow null - isNull: true, // only allows null - notEmpty: true, // don't allow empty strings - equals: 'specific value', // only allow a specific value - contains: 'foo', // force specific substrings - notIn: [['foo', 'bar']], // check the value is not one of these - isIn: [['foo', 'bar']], // check the value is one of these - notContains: 'bar', // don't allow specific substrings - len: [2,10], // only allow values with length between 2 and 10 - isUUID: 4, // only allow uuids - isDate: true, // only allow date strings - isAfter: "2011-11-05", // only allow date strings after a specific date - isBefore: "2011-11-05", // only allow date strings before a specific date - max: 23, // only allow values <= 23 - min: 23, // only allow values >= 23 - isCreditCard: true, // check for valid credit card numbers - - // Examples of custom validators: - isEven(value) { - if (parseInt(value) % 2 !== 0) { - throw new Error('Only even values are allowed!'); - } - } - isGreaterThanOtherField(value) { - if (parseInt(value) <= parseInt(this.otherField)) { - throw new Error('Bar must be greater than otherField.'); - } - } - } - } -}); -``` - -Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for `isIn`, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as `[['foo', 'bar']]` as shown above. - -To use a custom error message instead of that provided by [validator.js](https://github.com/chriso/validator.js), use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with - -```js -isInt: { - msg: "Must be an integer number of pennies" -} -``` - -or if arguments need to also be passed add an `args` property: - -```js -isIn: { - args: [['en', 'zh']], - msg: "Must be English or Chinese" -} -``` - -When using custom validator functions the error message will be whatever message the thrown `Error` object holds. - -See [the validator.js project](https://github.com/chriso/validator.js) for more details on the built in validation methods. - -**Hint:** You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged. - -### `allowNull` interaction with other validators - -If a particular field of a model is set to not allow null (with `allowNull: false`) and that value has been set to `null`, all validators will be skipped and a `ValidationError` will be thrown. - -On the other hand, if it is set to allow null (with `allowNull: true`) and that value has been set to `null`, only the built-in validators will be skipped, while the custom validators will still run. - -This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows `null` (since the length validator will be skipped automatically when the value is `null`): - -```js -class User extends Model {} -User.init({ - username: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [5, 10] - } - } -}, { sequelize }); -``` - -You also can conditionally allow `null` values, with a custom validator, since it won't be skipped: - -```js -class User extends Model {} -User.init({ - age: Sequelize.INTEGER, - name: { - type: DataTypes.STRING, - allowNull: true, - validate: { - customValidator(value) { - if (value === null && this.age !== 10) { - throw new Error("name can't be null unless age is 10"); - } - }) - } - } -}, { sequelize }); -``` - -You can customize `allowNull` error message by setting the `notNull` validator: - -```js -class User extends Model {} -User.init({ - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - notNull: { - msg: 'Please enter your name' - } - } - } -}, { sequelize }); -``` - -### Model-wide validations - -Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of `latitude` and `longitude` are set or both, and fail if one but not the other is set. - -Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators. - -Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the `validate` option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors. - -An example: - -```js -class Place extends Model {} -Place.init({ - name: Sequelize.STRING, - address: Sequelize.STRING, - latitude: { - type: DataTypes.INTEGER, - validate: { - min: -90, - max: 90 - } - }, - longitude: { - type: DataTypes.INTEGER, - validate: { - min: -180, - max: 180 - } - }, -}, { - sequelize, - validate: { - bothCoordsOrNone() { - if ((this.latitude === null) !== (this.longitude === null)) { - throw new Error('Either both latitude and longitude, or neither!'); - } - } - } -}) -``` - -In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, `somePlace.validate()` might return: - -```js -{ - 'latitude': ['Invalid number: latitude'], - 'bothCoordsOrNone': ['Either both latitude and longitude, or neither!'] -} -``` - -Such validation could have also been done with a custom validator defined on a single attribute (such as the `latitude` attribute, by checking `(value === null) !== (this.longitude === null)`), but the model-wide validation approach is cleaner. diff --git a/docs/manual/moved/associations.md b/docs/manual/moved/associations.md deleted file mode 100644 index cf001aac731f..000000000000 --- a/docs/manual/moved/associations.md +++ /dev/null @@ -1,16 +0,0 @@ -# \[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 deleted file mode 100644 index 4ba48b9798d9..000000000000 --- a/docs/manual/moved/data-types.md +++ /dev/null @@ -1,12 +0,0 @@ -# \[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 deleted file mode 100644 index 177e8a28dcc1..000000000000 --- a/docs/manual/moved/models-definition.md +++ /dev/null @@ -1,55 +0,0 @@ -# \[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 deleted file mode 100644 index 020eeacab726..000000000000 --- a/docs/manual/moved/models-usage.md +++ /dev/null @@ -1,12 +0,0 @@ -# \[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 deleted file mode 100644 index 94b8d8ae9c99..000000000000 --- a/docs/manual/moved/querying.md +++ /dev/null @@ -1,13 +0,0 @@ -# \[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 deleted file mode 100644 index d8501ef5ef3c..000000000000 --- a/docs/manual/other-topics/connection-pool.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index c48708a0168f..000000000000 --- a/docs/manual/other-topics/constraints-and-circularities.md +++ /dev/null @@ -1,113 +0,0 @@ -# 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 deleted file mode 100644 index d7c5e9427609..000000000000 --- a/docs/manual/other-topics/dialect-specific-things.md +++ /dev/null @@ -1,218 +0,0 @@ -# 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 deleted file mode 100644 index 2e0938916faf..000000000000 --- a/docs/manual/other-topics/extending-data-types.md +++ /dev/null @@ -1,113 +0,0 @@ -# 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 deleted file mode 100644 index 71791719bb4f..000000000000 --- a/docs/manual/other-topics/hooks.md +++ /dev/null @@ -1,385 +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._ - -## 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 deleted file mode 100644 index 123ec878457e..000000000000 --- a/docs/manual/other-topics/indexes.md +++ /dev/null @@ -1,47 +0,0 @@ -# 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/other-topics/legacy.md b/docs/manual/other-topics/legacy.md deleted file mode 100644 index 249f5a9638de..000000000000 --- a/docs/manual/other-topics/legacy.md +++ /dev/null @@ -1,71 +0,0 @@ -# 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. - -## Tables - -```js -class User extends Model {} -User.init({ - // ... -}, { - modelName: 'user', - tableName: 'users', - sequelize, -}); -``` - -## Fields - -```js -class MyModel extends Model {} -MyModel.init({ - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - } -}, { sequelize }); -``` - -## Primary keys - -Sequelize will assume your table has a `id` primary key property by default. - -To define your own primary key: - -```js -class Collection extends Model {} -Collection.init({ - uid: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true // Automatically gets converted to SERIAL for postgres - } -}, { sequelize }); - -class Collection extends Model {} -Collection.init({ - uuid: { - type: DataTypes.UUID, - primaryKey: true - } -}, { sequelize }); -``` - -And if your model has no primary key at all you can use `Model.removeAttribute('id');` - -## Foreign keys - -```js -// 1:1 -Organization.belongsTo(User, { foreignKey: 'owner_id' }); -User.hasOne(Organization, { foreignKey: 'owner_id' }); - -// 1:M -Project.hasMany(Task, { foreignKey: 'tasks_pk' }); -Task.belongsTo(Project, { foreignKey: 'tasks_pk' }); - -// N:M -User.belongsToMany(Role, { through: 'user_has_roles', foreignKey: 'user_role_user_id' }); -Role.belongsToMany(User, { through: 'user_has_roles', foreignKey: 'roles_identifier' }); -``` diff --git a/docs/manual/other-topics/legal.md b/docs/manual/other-topics/legal.md deleted file mode 100644 index 0d8d4b428b2a..000000000000 --- a/docs/manual/other-topics/legal.md +++ /dev/null @@ -1,49 +0,0 @@ -# Legal Notice - -## 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 - -Copyright (c) 2014-present Sequelize contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -## AUTHOR(S) - -```text -Main author: - -Sascha Depold -Uhlandstr. 160 -10719 Berlin -sascha [at] depold [dot] com -[plus] 49 152 [slash] 03878582 - -``` - -## INHALTLICHE VERANTWORTUNG - -```text -Ich übernehme keine Haftung für ausgehende Links. -Daher musst du dich bei Problemen an deren Betreiber wenden! -``` diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md deleted file mode 100644 index 7b869738146c..000000000000 --- a/docs/manual/other-topics/migrations.md +++ /dev/null @@ -1,565 +0,0 @@ -# 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 deleted file mode 100644 index 9daf8b1a3567..000000000000 --- a/docs/manual/other-topics/naming-strategies.md +++ /dev/null @@ -1,157 +0,0 @@ -# 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 deleted file mode 100644 index 7db529e43319..000000000000 --- a/docs/manual/other-topics/optimistic-locking.md +++ /dev/null @@ -1,7 +0,0 @@ -# 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 deleted file mode 100644 index fa0561385520..000000000000 --- a/docs/manual/other-topics/other-data-types.md +++ /dev/null @@ -1,192 +0,0 @@ -# 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 deleted file mode 100644 index 5225d0adf1d3..000000000000 --- a/docs/manual/other-topics/query-interface.md +++ /dev/null @@ -1,152 +0,0 @@ -# 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/other-topics/read-replication.md b/docs/manual/other-topics/read-replication.md deleted file mode 100644 index 1c16fef1ad74..000000000000 --- a/docs/manual/other-topics/read-replication.md +++ /dev/null @@ -1,29 +0,0 @@ -# Read Replication - -Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the main writer, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). - -```js -const sequelize = new Sequelize('database', null, null, { - dialect: 'mysql', - port: 3306, - replication: { - read: [ - { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, - { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } - ], - write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } - }, - pool: { // If you want to override the options used for the read/write pool you can do so here - max: 20, - idle: 30000 - }, -}) -``` - -If you have any general settings that apply to all replicas you do not need to provide them for each instance. In the code above, database name and port is propagated to all replicas. The same will happen for user and password, if you leave them out for any of the replicas. Each replica has the following options:`host`,`port`,`username`,`password`,`database`. - -Sequelize uses a pool to manage connections to your replicas. Internally Sequelize will maintain two pools created using `pool` configuration. - -If you want to modify these, you can pass pool as an options when instantiating Sequelize, as shown above. - -Each `write` or `useMaster: true` query will use write pool. For `SELECT` read pool will be used. Read replica are switched using a basic round robin scheduling. diff --git a/docs/manual/other-topics/resources.md b/docs/manual/other-topics/resources.md deleted file mode 100644 index 291f25712526..000000000000 --- a/docs/manual/other-topics/resources.md +++ /dev/null @@ -1,62 +0,0 @@ -# Resources - -## Addons & Plugins - -### ACL - -* [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 - -* [meteor modeler](https://www.datensen.com/) - Desktop tool for visual definition of Sequelize models and asssociations. -* [sequelize-ui](https://github.com/tomjschuster/sequelize-ui) - Online tool for building models, relations and more. -* [sequelizer](https://github.com/andyforever/sequelizer) - A GUI Desktop App for generating Sequelize models. Support for Mysql, Mariadb, Postgres, Sqlite, Mssql. -* [sequelize-auto](https://github.com/sequelize/sequelize-auto) Generating models for SequelizeJS via the command line is another choice. -* [pg-generator](http://www.pg-generator.com/builtin-templates/sequelize/) - Auto generate/scaffold Sequelize models for PostgreSQL database. -* [sequelizejs-decorators](https://www.npmjs.com/package/sequelizejs-decorators) decorators for composing sequelize models - -### Autoloader - -* [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/). - -### Caching - -* [sequelize-transparent-cache](https://github.com/DanielHreben/sequelize-transparent-cache) - -### Filters - -* [sequelize-transforms](https://www.npmjs.com/package/sequelize-transforms) - Add configurable attribute transforms. - -### Fixtures / mock data - -* [Fixer](https://github.com/olalonde/fixer) -* [Sequelize-fixtures](https://github.com/domasx2/sequelize-fixtures) -* [Sequelize-fixture](https://github.com/xudejian/sequelize-fixture) - -### Hierarchies - -* [sequelize-hierarchy](https://www.npmjs.com/package/sequelize-hierarchy) - Nested hierarchies for Sequelize. - -### Historical records / Time travel - -* [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) - Temporal tables (aka historical records) - -### Migrations - -* [umzug](https://github.com/sequelize/umzug) - -### Slugification - -* [sequelize-slugify](https://www.npmjs.com/package/sequelize-slugify) - Add slugs to sequelize models - -### Tokens - -* [sequelize-tokenify](https://github.com/pipll/sequelize-tokenify) - Add unique tokens to sequelize models - -### Miscellaneous - -* [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. diff --git a/docs/manual/other-topics/scopes.md b/docs/manual/other-topics/scopes.md deleted file mode 100644 index 73eb380364af..000000000000 --- a/docs/manual/other-topics/scopes.md +++ /dev/null @@ -1,284 +0,0 @@ -# Scopes - -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 - -Scopes are defined in the model definition and can be finder objects, or functions returning finder objects - except for the default scope, which can only be an object: - -```js -class Project extends Model {} -Project.init({ - // Attributes -}, { - defaultScope: { - where: { - active: true - } - }, - scopes: { - deleted: { - where: { - deleted: true - } - }, - activeUsers: { - include: [ - { model: User, where: { active: true } } - ] - }, - random() { - return { - where: { - someNumber: Math.random() - } - } - }, - accessLevel(value) { - return { - where: { - accessLevel: { - [Op.gte]: value - } - } - } - } - sequelize, - modelName: 'project' - } -}); -``` - -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: - -```sql -SELECT * FROM projects WHERE active = true -``` - -The default scope can be removed by calling `.unscoped()`, `.scope(null)`, or by invoking another scope: - -```js -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): - -```js -// The `activeUsers` scope defined in the example above could also have been defined this way: -Project.addScope('activeUsers', { - include: [ - { model: User.scope('active') } - ] -}); -``` - -## Usage - -Scopes are applied by calling `.scope` on the model definition, passing the name of one or more scopes. `.scope` returns a fully functional model instance with all the regular methods: `.findAll`, `.update`, `.count`, `.destroy` etc. You can save this model instance and reuse it later: - -```js -const DeletedProjects = Project.scope('deleted'); -await DeletedProjects.findAll(); - -// The above is equivalent to: -await Project.findAll({ - where: { - deleted: true - } -}); -``` - -Scopes apply to `.find`, `.findAll`, `.count`, `.update`, `.increment` and `.destroy`. - -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 -await Project.scope('random', { method: ['accessLevel', 19] }).findAll(); -``` - -Generated SQL: - -```sql -SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19 -``` - -## Merging - -Several scopes can be applied simultaneously by passing an array of scopes to `.scope`, or by passing the scopes as consecutive arguments. - -```js -// These two are equivalent -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 -WHERE projects.deleted = true -AND users.active = true -``` - -If you want to apply another scope alongside the default scope, pass the key `defaultScope` to `.scope`: - -```js -await Project.scope('defaultScope', 'deleted').findAll(); -``` - -Generated SQL: - -```sql -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 -YourMode.addScope('scope1', { - where: { - firstName: 'bob', - age: { - [Op.gt]: 20 - } - }, - limit: 2 -}); -YourMode.addScope('scope2', { - where: { - age: { - [Op.gt]: 30 - } - }, - limit: 10 -}); -``` - -Using `.scope('scope1', 'scope2')` will yield the following WHERE clause: - -```sql -WHERE firstName = 'bob' AND age > 30 LIMIT 10 -``` - -Note how `limit` and `age` are overwritten by `scope2`, while `firstName` is preserved. The `limit`, `offset`, `order`, `paranoid`, `lock` and `raw` fields are overwritten, while `where` is shallowly merged (meaning that identical keys will be overwritten). The merge strategy for `include` will be discussed later on. - -Note that `attributes` keys of multiple applied scopes are merged in such a way that `attributes.exclude` are always preserved. This allows merging several scopes and never leaking sensitive fields in final scope. - -The same merge logic applies when passing a find object directly to `findAll` (and similar finders) on a scoped model: - -```js -Project.scope('deleted').findAll({ - where: { - firstName: 'john' - } -}) -``` - -Generated where clause: - -```sql -WHERE deleted = true AND firstName = 'john' -``` - -Here the `deleted` scope is merged with the finder. If we were to pass `where: { firstName: 'john', deleted: false }` to the finder, the `deleted` scope would be overwritten. - -### Merging includes - -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 the models `Foo`, `Bar`, `Baz` and `Qux`, with One-to-Many associations as follows: - -```js -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' }); -``` - -Now, consider the following four scopes defined on Foo: - -```js -Foo.addScope('includeEverything', { - include: { - model: Bar, - include: [{ - model: Baz, - include: Qux - }] - } -}); - -Foo.addScope('limitedBars', { - include: [{ - model: Bar, - limit: 2 - }] -}); - -Foo.addScope('limitedBazs', { - include: [{ - model: Bar, - include: [{ - model: Baz, - limit: 2 - }] - }] -}); - -Foo.addScope('excludeBazName', { - include: [{ - model: Bar, - include: [{ - 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 -await Foo.findAll({ - include: { - model: Bar, - limit: 2, - include: [{ - model: Baz, - limit: 2, - attributes: { - exclude: ['name'] - }, - include: Qux - }] - } -}); - -// The above is equivalent to: -await Foo.scope([ - 'includeEverything', - 'limitedBars', - 'limitedBazs', - 'excludeBazName' -]).findAll(); -``` - -Observe how the four scopes were merged into one. The includes of scopes are merged based on the model being included. If one scope includes model A and another includes model B, the merged result will include both models A and B. On the other hand, if both scopes include the same model A, but with different options (such as nested includes or other attributes), those will be merged recursively, as shown above. - -The merge illustrated above works in the exact same way regardless of the order applied to the scopes. The order would only make a difference if a certain option was set by two different scopes - which is not the case of the above example, since each scope does a different thing. - -This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. \ No newline at end of file diff --git a/docs/manual/other-topics/sub-queries.md b/docs/manual/other-topics/sub-queries.md deleted file mode 100644 index 213e4ec3a1ab..000000000000 --- a/docs/manual/other-topics/sub-queries.md +++ /dev/null @@ -1,164 +0,0 @@ -# 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 deleted file mode 100644 index e24f6d216781..000000000000 --- a/docs/manual/other-topics/transactions.md +++ /dev/null @@ -1,311 +0,0 @@ -# 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 deleted file mode 100644 index 836963e20de6..000000000000 --- a/docs/manual/other-topics/typescript.md +++ /dev/null @@ -1,365 +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 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 deleted file mode 100644 index fd047d5cd694..000000000000 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ /dev/null @@ -1,236 +0,0 @@ -# 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/other-topics/whos-using.md b/docs/manual/other-topics/whos-using.md deleted file mode 100644 index b872943a7bac..000000000000 --- a/docs/manual/other-topics/whos-using.md +++ /dev/null @@ -1,27 +0,0 @@ -# Who's using sequelize? - -[![Walmart labs logo](asset/walmart-labs-logo.png)](http://www.walmartlabs.com/) - -> ... we are avid users of sequelize (and have been for the past 18 months) (Feb 2017) - -
- -[![Snaplytics logo](asset/logo-snaplytics-green.png)](https://snaplytics.io) - -> We've been using sequelize since we started in the beginning of 2015. We use it for our graphql servers (in connection with [graphql-sequelize](http://github.com/mickhansen/graphql-sequelize)), and for all our background workers. - -
- -[![Connected Cars logo](asset/connected-cars.png)](https://connectedcars.io/) - -
- -[![Bitovi Logo](asset/bitovi-logo.png)](https://bitovi.com) - -> We have used Sequelize in enterprise projects for some of our Fortune 100 and Fortune 500 clients. It is used in deployments that are depended on by hundreds of millions of devices every year. - -
- -[![ErmesHotels Logo](asset/ermeshotels-logo.png)](https://www.ermeshotels.com) - -> Using Sequelize in production for two different apps with 30k+ daily users by 2 years. I doubt there is something better at this moment in terms of productivity and features. diff --git a/docs/redirects.json b/docs/redirects.json deleted file mode 100644 index 6701da309294..000000000000 --- a/docs/redirects.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 5e0e0c68c791..000000000000 --- a/docs/redirects/create-redirects.js +++ /dev/null @@ -1,18 +0,0 @@ -'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/run-docs-transforms.js b/docs/run-docs-transforms.js deleted file mode 100644 index a556451d6a5f..000000000000 --- a/docs/run-docs-transforms.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const jetpack = require('fs-jetpack'); -const cheerio = require('cheerio'); - -if (jetpack.exists('esdoc') !== 'dir') { - throw new Error('./esdoc folder not found.'); -} - -const htmlFiles = jetpack.find('./esdoc', { matching: '*.html' }); -const transformFiles = jetpack.find('./docs/transforms', { matching: '*.js' }); -const transforms = transformFiles.map(file => require(jetpack.path('.', file))); - -for (const htmlFile of htmlFiles) { - console.log(`Transform: ${htmlFile}`); - const $ = cheerio.load(jetpack.read(htmlFile)); - for (const transform of transforms) { - transform($, htmlFile); - } - jetpack.write(htmlFile, $.html()); -} \ No newline at end of file diff --git a/docs/scripts/.eslintrc b/docs/scripts/.eslintrc deleted file mode 100644 index b99c1118dd5e..000000000000 --- a/docs/scripts/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "browser": true - } -} diff --git a/docs/scripts/menu-groups.js b/docs/scripts/menu-groups.js deleted file mode 100644 index bec7591e5589..000000000000 --- a/docs/scripts/menu-groups.js +++ /dev/null @@ -1,26 +0,0 @@ -'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 deleted file mode 100644 index 127b4cc71ee9..000000000000 --- a/docs/transforms/fix-ids.js +++ /dev/null @@ -1,46 +0,0 @@ -'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/group-data-types.js b/docs/transforms/group-data-types.js deleted file mode 100644 index 2aebe09b5c2e..000000000000 --- a/docs/transforms/group-data-types.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -function groupDataTypes($, path) { - let firstLi; - $('nav a').each(function() { - /* eslint-disable no-invalid-this */ - if ($(this).attr('href').startsWith('class/lib/data-types.js~')) { - const li = $(this).closest('li'); - if (!firstLi) { - firstLi = li; - firstLi.prepend('datatypes'); - firstLi.appendTo(firstLi.parent()); - } else { - firstLi.after(li); - } - } - }); - - if (path.endsWith('identifiers.html')) { - const rowsToDelete = []; - $('table.summary td a').each(function() { - if ($(this).attr('href').startsWith('class/lib/data-types.js~')) { - rowsToDelete.push($(this).closest('tr')); - } - }); - for (const row of rowsToDelete) { - $(row).remove(); - } - } -} - -module.exports = function transform($, path) { - groupDataTypes($, path); - $('nav li[data-ice=doc]:first-child').css('margin-top', '15px'); -}; \ No newline at end of file diff --git a/docs/transforms/header-customization.js b/docs/transforms/header-customization.js deleted file mode 100644 index 4f9dfcf1477b..000000000000 --- a/docs/transforms/header-customization.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -module.exports = function transform($) { - const apiReferenceLink = $('header a:nth-child(2)'); - const githubLogoImage = $('header a img[src="./image/github.png"]'); - - apiReferenceLink - .text('API Reference') - .addClass('api-reference-link'); - - githubLogoImage - .css('width', '30px') - .attr('width', '30px'); - - githubLogoImage.closest('a') - .css('position', '') - .css('top', '') - .after(` - - - - `); -}; diff --git a/docs/transforms/menu-groups.js b/docs/transforms/menu-groups.js deleted file mode 100644 index d8a40b5d5b3c..000000000000 --- a/docs/transforms/menu-groups.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const _ = require('lodash'); -const manualGroups = require('./../manual-groups.json'); - -function extractFileNameFromPath(path) { - if (/\.\w+$/.test(path)) { - return /([^/]*)\.\w+$/.exec(path)[1]; - } - return /[^/]*$/.exec(path)[0]; -} - -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(` -
- Home -
- `); - - let count = 0; - _.each(manualGroups, (manuals, 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 deleted file mode 100644 index 62bb58e10404..000000000000 --- a/docs/transforms/meta-tags.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function transform($) { - $('head').append(''); -}; diff --git a/index.js b/index.js deleted file mode 100644 index 31e6c23becdd..000000000000 --- a/index.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -/** - * The entry point. - * - * @module Sequelize - */ -module.exports = require('./lib/sequelize'); diff --git a/lerna.json b/lerna.json new file mode 100644 index 000000000000..f8b394d90ad4 --- /dev/null +++ b/lerna.json @@ -0,0 +1,4 @@ +{ + "$schema": "node_modules/lerna/schemas/lerna-schema.json", + "version": "7.0.0-alpha.47" +} diff --git a/lib/associations/base.js b/lib/associations/base.js deleted file mode 100644 index 40ce304445c4..000000000000 --- a/lib/associations/base.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict'; - -const { AssociationError } = require('./../errors'); - -/** - * Creating associations in sequelize is done by calling one of the belongsTo / hasOne / hasMany / belongsToMany functions on a model (the source), and providing another model as the first argument to the function (the target). - * - * * hasOne - adds a foreign key to the target and singular association mixins to the source. - * * belongsTo - add a foreign key and singular association mixins to the source. - * * hasMany - adds a foreign key to target and plural association mixins to the source. - * * belongsToMany - creates an N:M association with a join table and adds plural association mixins to the source. The junction table is created with sourceId and targetId. - * - * Creating an association will add a foreign key constraint to the attributes. All associations use `CASCADE` on update and `SET NULL` on delete, except for n:m, which also uses `CASCADE` on delete. - * - * When creating associations, you can provide an alias, via the `as` option. This is useful if the same model is associated twice, or you want your association to be called something other than the name of the target model. - * - * As an example, consider the case where users have many pictures, one of which is their profile picture. All pictures have a `userId`, but in addition the user model also has a `profilePictureId`, to be able to easily load the user's profile picture. - * - * ```js - * User.hasMany(Picture) - * User.belongsTo(Picture, { as: 'ProfilePicture', constraints: false }) - * - * user.getPictures() // gets you all pictures - * user.getProfilePicture() // gets you only the profile picture - * - * User.findAll({ - * where: ..., - * include: [ - * { model: Picture }, // load all pictures - * { model: Picture, as: 'ProfilePicture' }, // load the profile picture. - * // Notice that the spelling must be the exact same as the one in the association - * ] - * }) - * ``` - * To get full control over the foreign key column added by sequelize, you can use the `foreignKey` option. It can either be a string, that specifies the name, or and object type definition, - * equivalent to those passed to `sequelize.define`. - * - * ```js - * User.hasMany(Picture, { foreignKey: 'uid' }) - * ``` - * - * The foreign key column in Picture will now be called `uid` instead of the default `userId`. - * - * ```js - * User.hasMany(Picture, { - * foreignKey: { - * name: 'uid', - * allowNull: false - * } - * }) - * ``` - * - * This specifies that the `uid` column cannot be null. In most cases this will already be covered by the foreign key constraints, which sequelize creates automatically, but can be useful in case where the foreign keys are disabled, e.g. due to circular references (see `constraints: false` below). - * - * When fetching associated models, you can limit your query to only load some models. These queries are written in the same way as queries to `find`/`findAll`. To only get pictures in JPG, you can do: - * - * ```js - * user.getPictures({ - * where: { - * format: 'jpg' - * } - * }) - * ``` - * - * There are several ways to update and add new associations. Continuing with our example of users and pictures: - * ```js - * user.addPicture(p) // Add a single picture - * user.setPictures([p1, p2]) // Associate user with ONLY these two picture, all other associations will be deleted - * user.addPictures([p1, p2]) // Associate user with these two pictures, but don't touch any current associations - * ``` - * - * You don't have to pass in a complete object to the association functions, if your associated model has a single primary key: - * - * ```js - * user.addPicture(req.query.pid) // Here pid is just an integer, representing the primary key of the picture - * ``` - * - * In the example above we have specified that a user belongs to his profile picture. Conceptually, this might not make sense, but since we want to add the foreign key to the user model this is the way to do it. - * - * Note how we also specified `constraints: false` for profile picture. This is because we add a foreign key from user to picture (profilePictureId), and from picture to user (userId). If we were to add foreign keys to both, it would create a cyclic dependency, and sequelize would not know which table to create first, since user depends on picture, and picture depends on user. These kinds of problems are detected by sequelize before the models are synced to the database, and you will get an error along the lines of `Error: Cyclic dependency found. 'users' is dependent of itself`. If you encounter this, you should either disable some constraints, or rethink your associations completely. - */ -class Association { - constructor(source, target, options = {}) { - /** - * @type {Model} - */ - this.source = source; - - /** - * @type {Model} - */ - this.target = target; - - this.options = options; - this.scope = options.scope; - this.isSelfAssociation = this.source === this.target; - this.as = options.as; - - /** - * The type of the association. One of `HasMany`, `BelongsTo`, `HasOne`, `BelongsToMany` - * - * @type {string} - */ - this.associationType = ''; - - if (source.hasAlias(options.as)) { - throw new AssociationError(`You have used the alias ${options.as} in two separate associations. ` + - 'Aliased associations must have unique aliases.' - ); - } - } - - /** - * Normalize input - * - * @param {Array|string} input it may be array or single obj, instance or primary key - * - * @private - * @returns {Array} built objects - */ - toInstanceArray(input) { - if (!Array.isArray(input)) { - input = [input]; - } - - return input.map(element => { - if (element instanceof this.target) return element; - - const tmpInstance = {}; - tmpInstance[this.target.primaryKeyAttribute] = element; - - return this.target.build(tmpInstance, { isNewRecord: false }); - }); - } - - [Symbol.for('nodejs.util.inspect.custom')]() { - return this.as; - } -} - -module.exports = Association; diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js deleted file mode 100644 index 27f58701b052..000000000000 --- a/lib/associations/belongs-to-many.js +++ /dev/null @@ -1,823 +0,0 @@ -'use strict'; - -const Utils = require('./../utils'); -const Helpers = require('./helpers'); -const _ = require('lodash'); -const Association = require('./base'); -const BelongsTo = require('./belongs-to'); -const HasMany = require('./has-many'); -const HasOne = require('./has-one'); -const AssociationError = require('../errors').AssociationError; -const EmptyResultError = require('../errors').EmptyResultError; -const Op = require('../operators'); - -/** - * Many-to-many association with a join table. - * - * When the join table has additional attributes, these can be passed in the options object: - * - * ```js - * UserProject = sequelize.define('user_project', { - * role: Sequelize.STRING - * }); - * User.belongsToMany(Project, { through: UserProject }); - * Project.belongsToMany(User, { through: UserProject }); - * // through is required! - * - * user.addProject(project, { through: { role: 'manager' }}); - * ``` - * - * All methods allow you to pass either a persisted instance, its primary key, or a mixture: - * - * ```js - * 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: - * - * ```js - * p1.UserProjects = { - * started: true - * } - * user.setProjects([p1, p2], { through: { started: false }}) // The default value is false, but p1 overrides that. - * ``` - * - * 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 - * 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()`. - * - * @see {@link Model.belongsToMany} - */ -class BelongsToMany extends Association { - constructor(source, target, options) { - super(source, target, options); - - if (this.options.through === undefined || this.options.through === true || this.options.through === null) { - throw new AssociationError(`${source.name}.belongsToMany(${target.name}) requires through option, pass either a string or a model`); - } - - if (!this.options.through.model) { - this.options.through = { - model: options.through - }; - } - - this.associationType = 'BelongsToMany'; - this.targetAssociation = null; - this.sequelize = source.sequelize; - this.through = { ...this.options.through }; - this.isMultiAssociation = true; - this.doubleLinked = false; - - if (!this.as && this.isSelfAssociation) { - throw new AssociationError('\'as\' must be defined for many-to-many self-associations'); - } - - if (this.as) { - this.isAliased = true; - - if (_.isPlainObject(this.as)) { - this.options.name = this.as; - this.as = this.as.plural; - } else { - this.options.name = { - plural: this.as, - singular: Utils.singularize(this.as) - }; - } - } else { - this.as = this.target.options.name.plural; - this.options.name = this.target.options.name; - } - - this.combinedTableName = Utils.combineTableNames( - this.source.tableName, - this.isSelfAssociation ? this.as || this.target.tableName : this.target.tableName - ); - - /* - * If self association, this is the target association - Unless we find a pairing association - */ - if (this.isSelfAssociation) { - this.targetAssociation = this; - } - - /* - * Find paired association (if exists) - */ - _.each(this.target.associations, association => { - if (association.associationType !== 'BelongsToMany') return; - if (association.target !== this.source) return; - - if (this.options.through.model === association.options.through.model) { - this.paired = association; - association.paired = this; - } - }); - - /* - * Default/generated source/target keys - */ - this.sourceKey = this.options.sourceKey || this.source.primaryKeyAttribute; - this.sourceKeyField = this.source.rawAttributes[this.sourceKey].field || this.sourceKey; - - if (this.options.targetKey) { - this.targetKey = this.options.targetKey; - this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey; - } else { - this.targetKeyDefault = true; - this.targetKey = this.target.primaryKeyAttribute; - this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey; - } - - this._createForeignAndOtherKeys(); - - if (typeof this.through.model === 'string') { - if (!this.sequelize.isDefined(this.through.model)) { - 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: this.through.paranoid ? this.through.paranoid : false, // Default to non-paranoid join (referenced in #11991) - validate: {} // Don't propagate model-level validations - })); - } else { - this.through.model = this.sequelize.model(this.through.model); - } - } - - Object.assign(this.options, _.pick(this.through.model.options, [ - 'timestamps', 'createdAt', 'updatedAt', 'deletedAt', 'paranoid' - ])); - - if (this.paired) { - let needInjectPaired = false; - - if (this.targetKeyDefault) { - this.targetKey = this.paired.sourceKey; - this.targetKeyField = this.paired.sourceKeyField; - this._createForeignAndOtherKeys(); - } - if (this.paired.targetKeyDefault) { - // in this case paired.otherKey depends on paired.targetKey, - // so cleanup previously wrong generated otherKey - if (this.paired.targetKey !== this.sourceKey) { - delete this.through.model.rawAttributes[this.paired.otherKey]; - this.paired.targetKey = this.sourceKey; - this.paired.targetKeyField = this.sourceKeyField; - this.paired._createForeignAndOtherKeys(); - needInjectPaired = true; - } - } - - if (this.otherKeyDefault) { - this.otherKey = this.paired.foreignKey; - } - if (this.paired.otherKeyDefault) { - // If paired otherKey was inferred we should make sure to clean it up - // before adding a new one that matches the foreignKey - if (this.paired.otherKey !== this.foreignKey) { - delete this.through.model.rawAttributes[this.paired.otherKey]; - this.paired.otherKey = this.foreignKey; - needInjectPaired = true; - } - } - - if (needInjectPaired) { - this.paired._injectAttributes(); - } - } - - if (this.through) { - this.throughModel = this.through.model; - } - - this.options.tableName = this.combinedName = this.through.model === Object(this.through.model) ? this.through.model.tableName : this.through.model; - - this.associationAccessor = this.as; - - // Get singular and plural names, trying to uppercase the first letter, unless the model forbids it - const plural = _.upperFirst(this.options.name.plural); - const singular = _.upperFirst(this.options.name.singular); - - this.accessors = { - get: `get${plural}`, - set: `set${plural}`, - addMultiple: `add${plural}`, - add: `add${singular}`, - create: `create${singular}`, - remove: `remove${singular}`, - removeMultiple: `remove${plural}`, - hasSingle: `has${singular}`, - hasAll: `has${plural}`, - count: `count${plural}` - }; - } - - _createForeignAndOtherKeys() { - /* - * Default/generated foreign/other keys - */ - if (_.isObject(this.options.foreignKey)) { - this.foreignKeyAttribute = this.options.foreignKey; - this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName; - } else { - this.foreignKeyAttribute = {}; - this.foreignKey = this.options.foreignKey || Utils.camelize( - [ - this.source.options.name.singular, - this.sourceKey - ].join('_') - ); - } - - if (_.isObject(this.options.otherKey)) { - this.otherKeyAttribute = this.options.otherKey; - this.otherKey = this.otherKeyAttribute.name || this.otherKeyAttribute.fieldName; - } else { - if (!this.options.otherKey) { - this.otherKeyDefault = true; - } - - this.otherKeyAttribute = {}; - this.otherKey = this.options.otherKey || Utils.camelize( - [ - this.isSelfAssociation ? Utils.singularize(this.as) : this.target.options.name.singular, - this.targetKey - ].join('_') - ); - } - } - - // the id is in the target table - // or in an extra table which connects two tables - _injectAttributes() { - this.identifier = this.foreignKey; - this.foreignIdentifier = this.otherKey; - - // remove any PKs previously defined by sequelize - // but ignore any keys that are part of this association (#5865) - _.each(this.through.model.rawAttributes, (attribute, attributeName) => { - if (attribute.primaryKey === true && attribute._autoGenerated === true) { - if (attributeName === this.foreignKey || attributeName === this.otherKey) { - // this key is still needed as it's part of the association - // so just set primaryKey to false - attribute.primaryKey = false; - } - else { - delete this.through.model.rawAttributes[attributeName]; - } - this.primaryKeyDeleted = true; - } - }); - - const sourceKey = this.source.rawAttributes[this.sourceKey]; - const sourceKeyType = sourceKey.type; - const sourceKeyField = this.sourceKeyField; - const targetKey = this.target.rawAttributes[this.targetKey]; - const targetKeyType = targetKey.type; - const targetKeyField = this.targetKeyField; - const sourceAttribute = { type: sourceKeyType, ...this.foreignKeyAttribute }; - const targetAttribute = { type: targetKeyType, ...this.otherKeyAttribute }; - - if (this.primaryKeyDeleted === true) { - targetAttribute.primaryKey = sourceAttribute.primaryKey = true; - } else if (this.through.unique !== false) { - let uniqueKey; - if (typeof this.options.uniqueKey === 'string' && this.options.uniqueKey !== '') { - uniqueKey = this.options.uniqueKey; - } else { - uniqueKey = [this.through.model.tableName, this.foreignKey, this.otherKey, 'unique'].join('_'); - } - targetAttribute.unique = sourceAttribute.unique = uniqueKey; - } - - if (!this.through.model.rawAttributes[this.foreignKey]) { - this.through.model.rawAttributes[this.foreignKey] = { - _autoGenerated: true - }; - } - - if (!this.through.model.rawAttributes[this.otherKey]) { - this.through.model.rawAttributes[this.otherKey] = { - _autoGenerated: true - }; - } - - if (this.options.constraints !== false) { - sourceAttribute.references = { - model: this.source.getTableName(), - key: sourceKeyField - }; - // For the source attribute the passed option is the priority - sourceAttribute.onDelete = this.options.onDelete || this.through.model.rawAttributes[this.foreignKey].onDelete; - sourceAttribute.onUpdate = this.options.onUpdate || this.through.model.rawAttributes[this.foreignKey].onUpdate; - - if (!sourceAttribute.onDelete) sourceAttribute.onDelete = 'CASCADE'; - if (!sourceAttribute.onUpdate) sourceAttribute.onUpdate = 'CASCADE'; - - targetAttribute.references = { - model: this.target.getTableName(), - key: targetKeyField - }; - // But the for target attribute the previously defined option is the priority (since it could've been set by another belongsToMany call) - targetAttribute.onDelete = this.through.model.rawAttributes[this.otherKey].onDelete || this.options.onDelete; - targetAttribute.onUpdate = this.through.model.rawAttributes[this.otherKey].onUpdate || this.options.onUpdate; - - if (!targetAttribute.onDelete) targetAttribute.onDelete = 'CASCADE'; - if (!targetAttribute.onUpdate) targetAttribute.onUpdate = 'CASCADE'; - } - - Object.assign(this.through.model.rawAttributes[this.foreignKey], sourceAttribute); - Object.assign(this.through.model.rawAttributes[this.otherKey], targetAttribute); - - this.through.model.refreshAttributes(); - - this.identifierField = this.through.model.rawAttributes[this.foreignKey].field || this.foreignKey; - this.foreignIdentifierField = this.through.model.rawAttributes[this.otherKey].field || this.otherKey; - - if (this.paired && !this.paired.foreignIdentifierField) { - this.paired.foreignIdentifierField = this.through.model.rawAttributes[this.paired.otherKey].field || this.paired.otherKey; - } - - this.toSource = new BelongsTo(this.through.model, this.source, { - foreignKey: this.foreignKey - }); - this.manyFromSource = new HasMany(this.source, this.through.model, { - foreignKey: this.foreignKey - }); - this.oneFromSource = new HasOne(this.source, this.through.model, { - foreignKey: this.foreignKey, - sourceKey: this.sourceKey, - as: this.through.model.name - }); - - this.toTarget = new BelongsTo(this.through.model, this.target, { - foreignKey: this.otherKey - }); - this.manyFromTarget = new HasMany(this.target, this.through.model, { - foreignKey: this.otherKey - }); - this.oneFromTarget = new HasOne(this.target, this.through.model, { - foreignKey: this.otherKey, - sourceKey: this.targetKey, - as: this.through.model.name - }); - - if (this.paired && this.paired.otherKeyDefault) { - this.paired.toTarget = new BelongsTo(this.paired.through.model, this.paired.target, { - foreignKey: this.paired.otherKey - }); - - 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 - }); - } - - Helpers.checkNamingCollision(this); - - return this; - } - - mixin(obj) { - const methods = ['get', 'count', 'hasSingle', 'hasAll', 'set', 'add', 'addMultiple', 'remove', 'removeMultiple', 'create']; - const aliases = { - hasSingle: 'has', - hasAll: 'has', - addMultiple: 'add', - removeMultiple: 'remove' - }; - - Helpers.mixinMethods(this, obj, methods, aliases); - } - - /** - * Get everything currently associated with this, using an optional where clause. - * - * @see - * {@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 {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>} - */ - async get(instance, options) { - options = Utils.cloneDeep(options) || {}; - - const through = this.through; - let scopeWhere; - let throughWhere; - - if (this.scope) { - scopeWhere = { ...this.scope }; - } - - options.where = { - [Op.and]: [ - scopeWhere, - options.where - ] - }; - - if (Object(through.model) === through.model) { - throughWhere = {}; - throughWhere[this.foreignKey] = instance.get(this.sourceKey); - - if (through.scope) { - Object.assign(throughWhere, through.scope); - } - - //If a user pass a where on the options through options, make an "and" with the current throughWhere - if (options.through && options.through.where) { - throughWhere = { - [Op.and]: [throughWhere, options.through.where] - }; - } - - options.include = options.include || []; - options.include.push({ - association: this.oneFromTarget, - attributes: options.joinTableAttributes, - required: true, - paranoid: _.get(options.through, 'paranoid', true), - where: throughWhere - }); - } - - let model = this.target; - if (Object.prototype.hasOwnProperty.call(options, 'scope')) { - if (!options.scope) { - model = model.unscoped(); - } else { - model = model.scope(options.scope); - } - } - - if (Object.prototype.hasOwnProperty.call(options, 'schema')) { - model = model.schema(options.schema, options.schemaDelimiter); - } - - return model.findAll(options); - } - - /** - * 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 {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false - * - * @returns {Promise} - */ - async count(instance, options) { - const sequelize = this.target.sequelize; - - options = Utils.cloneDeep(options); - options.attributes = [ - [sequelize.fn('COUNT', sequelize.col([this.target.name, this.targetKeyField].join('.'))), 'count'] - ]; - options.joinTableAttributes = []; - options.raw = true; - options.plain = true; - - const result = await this.get(instance, options); - - return parseInt(result.count, 10); - } - - /** - * Check if one or more instance(s) are associated with this. If a list of instances is passed, the function returns true if _all_ instances are associated - * - * @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 - * - * @returns {Promise} - */ - async has(sourceInstance, instances, options) { - if (!Array.isArray(instances)) { - instances = [instances]; - } - - options = { - raw: true, - ...options, - scope: false, - attributes: [this.targetKey], - joinTableAttributes: [] - }; - - const instancePrimaryKeys = instances.map(instance => { - if (instance instanceof this.target) { - return instance.where(); - } - return { - [this.targetKey]: instance - }; - }); - - options.where = { - [Op.and]: [ - { [Op.or]: instancePrimaryKeys }, - options.where - ] - }; - - const associatedObjects = await this.get(sourceInstance, options); - - return _.differenceWith(instancePrimaryKeys, associatedObjects, - (a, b) => _.isEqual(a[this.targetKey], b[this.targetKey])).length === 0; - } - - /** - * Set the associated models by passing an array of instances or their primary keys. - * Everything that it not in the passed array will be un-associated. - * - * @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. - * - * @returns {Promise} - */ - async set(sourceInstance, newAssociatedObjects, options) { - options = options || {}; - - const sourceKey = this.sourceKey; - const targetKey = this.targetKey; - const identifier = this.identifier; - const foreignIdentifier = this.foreignIdentifier; - - if (newAssociatedObjects === null) { - newAssociatedObjects = []; - } else { - newAssociatedObjects = this.toInstanceArray(newAssociatedObjects); - } - const where = { - [identifier]: sourceInstance.get(sourceKey), - ...this.through.scope - }; - - const updateAssociations = currentRows => { - const obsoleteAssociations = []; - const promises = []; - const defaultAttributes = options.through || {}; - - const unassociatedObjects = newAssociatedObjects.filter(obj => - !currentRows.some(currentRow => currentRow[foreignIdentifier] === obj.get(targetKey)) - ); - - for (const currentRow of currentRows) { - const newObj = newAssociatedObjects.find(obj => currentRow[foreignIdentifier] === obj.get(targetKey)); - - if (!newObj) { - obsoleteAssociations.push(currentRow); - } else { - let throughAttributes = newObj[this.through.model.name]; - // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) - if (throughAttributes instanceof this.through.model) { - throughAttributes = {}; - } - - const attributes = { ...defaultAttributes, ...throughAttributes }; - - if (Object.keys(attributes).length) { - promises.push( - this.through.model.update(attributes, Object.assign(options, { - where: { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: newObj.get(targetKey) - } - } - )) - ); - } - } - } - - if (obsoleteAssociations.length > 0) { - promises.push( - 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 => { - return { - ...defaultAttributes, - ...unassociatedObject[this.through.model.name], - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: unassociatedObject.get(targetKey), - ...this.through.scope - }; - }); - - promises.push(this.through.model.bulkCreate(bulk, { validate: true, ...options })); - } - - return Promise.all(promises); - }; - - 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; - } - } - - /** - * Associate one or several rows with source instance. It will not un-associate any already associated instance - * that may be missing from `newInstances`. - * - * @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. - * - * @returns {Promise} - */ - async add(sourceInstance, newInstances, options) { - // If newInstances is null or undefined, no-op - if (!newInstances) return Promise.resolve(); - - options = { ...options }; - - const association = this; - const sourceKey = association.sourceKey; - const targetKey = association.targetKey; - const identifier = association.identifier; - const foreignIdentifier = association.foreignIdentifier; - const defaultAttributes = options.through || {}; - - newInstances = association.toInstanceArray(newInstances); - - const where = { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)), - ...association.through.scope - }; - - const updateAssociations = currentRows => { - const promises = []; - const unassociatedObjects = []; - const changedAssociations = []; - for (const obj of newInstances) { - const existingAssociation = currentRows && currentRows.find(current => current[foreignIdentifier] === obj.get(targetKey)); - - if (!existingAssociation) { - unassociatedObjects.push(obj); - } else { - const throughAttributes = obj[association.through.model.name]; - const attributes = { ...defaultAttributes, ...throughAttributes }; - - if (Object.keys(attributes).some(attribute => attributes[attribute] !== existingAssociation[attribute])) { - changedAssociations.push(obj); - } - } - } - - if (unassociatedObjects.length > 0) { - const bulk = unassociatedObjects.map(unassociatedObject => { - const throughAttributes = unassociatedObject[association.through.model.name]; - const attributes = { ...defaultAttributes, ...throughAttributes }; - - attributes[identifier] = sourceInstance.get(sourceKey); - attributes[foreignIdentifier] = unassociatedObject.get(targetKey); - - Object.assign(attributes, association.through.scope); - - return attributes; - }); - - promises.push(association.through.model.bulkCreate(bulk, { validate: true, ...options })); - } - - for (const assoc of changedAssociations) { - let throughAttributes = assoc[association.through.model.name]; - const attributes = { ...defaultAttributes, ...throughAttributes }; - // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) - if (throughAttributes instanceof association.through.model) { - throughAttributes = {}; - } - - promises.push(association.through.model.update(attributes, Object.assign(options, { where: { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: assoc.get(targetKey) - } }))); - } - - return Promise.all(promises); - }; - - 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; - } - } - - /** - * Un-associate one or more instance(s). - * - * @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` - * - * @returns {Promise} - */ - remove(sourceInstance, oldAssociatedObjects, options) { - const association = this; - - options = options || {}; - - oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects); - - const where = { - [association.identifier]: sourceInstance.get(association.sourceKey), - [association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey)) - }; - - 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 - * - * @returns {Promise} - */ - async create(sourceInstance, values, options) { - const association = this; - - options = options || {}; - values = values || {}; - - if (Array.isArray(options)) { - options = { - fields: options - }; - } - - if (association.scope) { - Object.assign(values, association.scope); - if (options.fields) { - options.fields = options.fields.concat(Object.keys(association.scope)); - } - } - - // Create the related model instance - const newAssociatedObject = await association.target.create(values, options); - - await sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])); - return newAssociatedObject; - } - - verifyAssociationAlias(alias) { - if (typeof alias === 'string') { - return this.as === alias; - } - - if (alias && alias.plural) { - return this.as === alias.plural; - } - - return !this.isAliased; - } -} - -module.exports = BelongsToMany; -module.exports.BelongsToMany = BelongsToMany; -module.exports.default = BelongsToMany; diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js deleted file mode 100644 index cf78bd5b021c..000000000000 --- a/lib/associations/belongs-to.js +++ /dev/null @@ -1,253 +0,0 @@ -'use strict'; - -const Utils = require('./../utils'); -const Helpers = require('./helpers'); -const _ = require('lodash'); -const Association = require('./base'); -const Op = require('../operators'); - -/** - * One-to-one association - * - * In the API reference below, add the name of the association to the method, e.g. for `User.belongsTo(Project)` the getter will be `user.getProject()`. - * - * @see {@link Model.belongsTo} - */ -class BelongsTo extends Association { - constructor(source, target, options) { - super(source, target, options); - - this.associationType = 'BelongsTo'; - this.isSingleAssociation = true; - this.foreignKeyAttribute = {}; - - if (this.as) { - this.isAliased = true; - this.options.name = { - singular: this.as - }; - } else { - this.as = this.target.options.name.singular; - this.options.name = this.target.options.name; - } - - if (_.isObject(this.options.foreignKey)) { - this.foreignKeyAttribute = this.options.foreignKey; - this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName; - } else if (this.options.foreignKey) { - this.foreignKey = this.options.foreignKey; - } - - if (!this.foreignKey) { - this.foreignKey = Utils.camelize( - [ - this.as, - this.target.primaryKeyAttribute - ].join('_') - ); - } - - this.identifier = this.foreignKey; - if (this.source.rawAttributes[this.identifier]) { - this.identifierField = this.source.rawAttributes[this.identifier].field || this.identifier; - } - - if ( - this.options.targetKey - && !this.target.rawAttributes[this.options.targetKey] - ) { - throw new Error(`Unknown attribute "${this.options.targetKey}" passed as targetKey, define this attribute on model "${this.target.name}" first`); - } - - this.targetKey = this.options.targetKey || this.target.primaryKeyAttribute; - this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey; - this.targetKeyIsPrimary = this.targetKey === this.target.primaryKeyAttribute; - this.targetIdentifier = this.targetKey; - - this.associationAccessor = this.as; - this.options.useHooks = options.useHooks; - - // Get singular name, trying to uppercase the first letter, unless the model forbids it - const singular = _.upperFirst(this.options.name.singular); - - this.accessors = { - get: `get${singular}`, - set: `set${singular}`, - create: `create${singular}` - }; - } - - // the id is in the source table - _injectAttributes() { - 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]; - this.options.onDelete = this.options.onDelete || (source.allowNull ? 'SET NULL' : 'NO ACTION'); - this.options.onUpdate = this.options.onUpdate || 'CASCADE'; - } - - Helpers.addForeignKeyConstraints(newAttributes[this.foreignKey], this.target, this.source, this.options, this.targetKeyField); - Utils.mergeDefaults(this.source.rawAttributes, newAttributes); - - this.source.refreshAttributes(); - - this.identifierField = this.source.rawAttributes[this.foreignKey].field || this.foreignKey; - - Helpers.checkNamingCollision(this); - - return this; - } - - mixin(obj) { - const methods = ['get', 'set', 'create']; - - Helpers.mixinMethods(this, obj, methods); - } - - /** - * Get the associated instance. - * - * @param {Model|Array} instances source instances - * @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 - * - * @see - * {@link Model.findOne} for a full explanation of options - * - * @returns {Promise} - */ - async get(instances, options) { - const where = {}; - let Target = this.target; - let instance; - - options = Utils.cloneDeep(options); - - if (Object.prototype.hasOwnProperty.call(options, 'scope')) { - if (!options.scope) { - Target = Target.unscoped(); - } else { - Target = Target.scope(options.scope); - } - } - - if (Object.prototype.hasOwnProperty.call(options, 'schema')) { - Target = Target.schema(options.schema, options.schemaDelimiter); - } - - if (!Array.isArray(instances)) { - instance = instances; - instances = undefined; - } - - if (instances) { - where[this.targetKey] = { - [Op.in]: instances.map(_instance => _instance.get(this.foreignKey)) - }; - } else { - if (this.targetKeyIsPrimary && !options.where) { - return Target.findByPk(instance.get(this.foreignKey), options); - } - where[this.targetKey] = instance.get(this.foreignKey); - options.limit = null; - } - - options.where = options.where ? - { [Op.and]: [where, options.where] } : - where; - - if (instances) { - 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); - } - - /** - * Set the associated model. - * - * @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 {boolean} [options.save=true] Skip saving this after setting the foreign key if false. - * - * @returns {Promise} - */ - async set(sourceInstance, associatedInstance, options = {}) { - let value = associatedInstance; - - if (associatedInstance instanceof this.target) { - value = associatedInstance[this.targetKey]; - } - - sourceInstance.set(this.foreignKey, value); - - if (options.save === false) return; - - options = { - fields: [this.foreignKey], - allowNull: [this.foreignKey], - association: true, - ...options - }; - - // passes the changed field to save, so only that field get updated. - 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. - * - * @see - * {@link Model#create} for a full explanation of options - * - * @returns {Promise} The created target model - */ - async create(sourceInstance, values, options) { - values = values || {}; - options = options || {}; - - const newAssociatedObject = await this.target.create(values, options); - await sourceInstance[this.accessors.set](newAssociatedObject, options); - - return newAssociatedObject; - } - - verifyAssociationAlias(alias) { - if (typeof alias === 'string') { - return this.as === alias; - } - - if (alias && alias.singular) { - return this.as === alias.singular; - } - - return !this.isAliased; - } -} - -module.exports = BelongsTo; -module.exports.BelongsTo = BelongsTo; -module.exports.default = BelongsTo; diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js deleted file mode 100644 index e8c184bb630b..000000000000 --- a/lib/associations/has-many.js +++ /dev/null @@ -1,501 +0,0 @@ -'use strict'; - -const Utils = require('./../utils'); -const Helpers = require('./helpers'); -const _ = require('lodash'); -const Association = require('./base'); -const Op = require('../operators'); - -/** - * One-to-many association - * - * In the API reference below, add the name of the association to the method, e.g. for `User.hasMany(Project)` the getter will be `user.getProjects()`. - * If the association is aliased, use the alias instead, e.g. `User.hasMany(Project, { as: 'jobs' })` will be `user.getJobs()`. - * - * @see {@link Model.hasMany} - */ -class HasMany extends Association { - constructor(source, target, options) { - super(source, target, options); - - this.associationType = 'HasMany'; - this.targetAssociation = null; - this.sequelize = source.sequelize; - this.isMultiAssociation = true; - this.foreignKeyAttribute = {}; - - if (this.options.through) { - throw new Error('N:M associations are not supported with hasMany. Use belongsToMany instead'); - } - - /* - * If self association, this is the target association - */ - if (this.isSelfAssociation) { - this.targetAssociation = this; - } - - if (this.as) { - this.isAliased = true; - - if (_.isPlainObject(this.as)) { - this.options.name = this.as; - this.as = this.as.plural; - } else { - this.options.name = { - plural: this.as, - singular: Utils.singularize(this.as) - }; - } - } else { - this.as = this.target.options.name.plural; - this.options.name = this.target.options.name; - } - - /* - * Foreign key setup - */ - if (_.isObject(this.options.foreignKey)) { - this.foreignKeyAttribute = this.options.foreignKey; - this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName; - } else if (this.options.foreignKey) { - this.foreignKey = this.options.foreignKey; - } - - if (!this.foreignKey) { - this.foreignKey = Utils.camelize( - [ - this.source.options.name.singular, - this.source.primaryKeyAttribute - ].join('_') - ); - } - - if (this.target.rawAttributes[this.foreignKey]) { - this.identifierField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - this.foreignKeyField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - } - - /* - * Source key setup - */ - this.sourceKey = this.options.sourceKey || this.source.primaryKeyAttribute; - - if (this.source.rawAttributes[this.sourceKey]) { - this.sourceKeyAttribute = this.sourceKey; - this.sourceKeyField = this.source.rawAttributes[this.sourceKey].field || this.sourceKey; - } else { - this.sourceKeyAttribute = this.source.primaryKeyAttribute; - this.sourceKeyField = this.source.primaryKeyField; - } - - // Get singular and plural names - // try to uppercase the first letter, unless the model forbids it - const plural = _.upperFirst(this.options.name.plural); - const singular = _.upperFirst(this.options.name.singular); - - this.associationAccessor = this.as; - this.accessors = { - get: `get${plural}`, - set: `set${plural}`, - addMultiple: `add${plural}`, - add: `add${singular}`, - create: `create${singular}`, - remove: `remove${singular}`, - removeMultiple: `remove${plural}`, - hasSingle: `has${singular}`, - hasAll: `has${plural}`, - count: `count${plural}` - }; - } - - // the id is in the target table - // or in an extra table which connects two tables - _injectAttributes() { - const newAttributes = { - [this.foreignKey]: { - type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, - allowNull: true, - ...this.foreignKeyAttribute - } - }; - - // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m - const constraintOptions = { ...this.options }; - - if (this.options.constraints !== false) { - const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; - constraintOptions.onDelete = constraintOptions.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE'); - constraintOptions.onUpdate = constraintOptions.onUpdate || 'CASCADE'; - } - - Helpers.addForeignKeyConstraints(newAttributes[this.foreignKey], this.source, this.target, constraintOptions, this.sourceKeyField); - Utils.mergeDefaults(this.target.rawAttributes, newAttributes); - - this.target.refreshAttributes(); - this.source.refreshAttributes(); - - this.identifierField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - this.foreignKeyField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - this.sourceKeyField = this.source.rawAttributes[this.sourceKey].field || this.sourceKey; - - Helpers.checkNamingCollision(this); - - return this; - } - - mixin(obj) { - const methods = ['get', 'count', 'hasSingle', 'hasAll', 'set', 'add', 'addMultiple', 'remove', 'removeMultiple', 'create']; - const aliases = { - hasSingle: 'has', - hasAll: 'has', - addMultiple: 'add', - removeMultiple: 'remove' - }; - - Helpers.mixinMethods(this, obj, methods, aliases); - } - - /** - * 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 {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 - * - * @see - * {@link Model.findAll} for a full explanation of options - * - * @returns {Promise>} - */ - async get(instances, options = {}) { - const where = {}; - - let Model = this.target; - let instance; - let values; - - if (!Array.isArray(instances)) { - instance = instances; - instances = undefined; - } - - options = { ...options }; - - if (this.scope) { - Object.assign(where, this.scope); - } - - if (instances) { - values = instances.map(_instance => _instance.get(this.sourceKey, { raw: true })); - - if (options.limit && instances.length > 1) { - options.groupedLimit = { - limit: options.limit, - on: this, // association - values - }; - - delete options.limit; - } else { - where[this.foreignKey] = { - [Op.in]: values - }; - delete options.groupedLimit; - } - } else { - where[this.foreignKey] = instance.get(this.sourceKey, { raw: true }); - } - - options.where = options.where ? - { [Op.and]: [where, options.where] } : - where; - - if (Object.prototype.hasOwnProperty.call(options, 'scope')) { - if (!options.scope) { - Model = Model.unscoped(); - } else { - Model = Model.scope(options.scope); - } - } - - if (Object.prototype.hasOwnProperty.call(options, 'schema')) { - Model = Model.schema(options.schema, options.schemaDelimiter); - } - - const results = await Model.findAll(options); - if (instance) return results; - - 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); - } - - 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 {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false - * - * @returns {Promise} - */ - async count(instance, options) { - options = Utils.cloneDeep(options); - - options.attributes = [ - [ - this.sequelize.fn( - 'COUNT', - this.sequelize.col(`${this.target.name}.${this.target.primaryKeyField}`) - ), - 'count' - ] - ]; - options.raw = true; - options.plain = true; - - const result = await this.get(instance, options); - - return parseInt(result.count, 10); - } - - /** - * Check if one or more rows are associated with `this`. - * - * @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 - * - * @returns {Promise} - */ - async has(sourceInstance, targetInstances, options) { - const where = {}; - - if (!Array.isArray(targetInstances)) { - targetInstances = [targetInstances]; - } - - options = { - ...options, - scope: false, - attributes: [this.target.primaryKeyAttribute], - raw: true - }; - - where[Op.or] = targetInstances.map(instance => { - if (instance instanceof this.target) { - return instance.where(); - } - return { - [this.target.primaryKeyAttribute]: instance - }; - }); - - options.where = { - [Op.and]: [ - where, - options.where - ] - }; - - const associatedObjects = await this.get(sourceInstance, options); - - return associatedObjects.length === targetInstances.length; - } - - /** - * Set the associated models by passing an array of persisted instances or their primary keys. Everything that is not in the passed array will be un-associated - * - * @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 - * - * @returns {Promise} - */ - async set(sourceInstance, targetInstances, options) { - if (targetInstances === null) { - targetInstances = []; - } else { - targetInstances = this.toInstanceArray(targetInstances); - } - - 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; - - updateWhere = { - [this.target.primaryKeyAttribute]: obsoleteAssociations.map(associatedObject => - associatedObject[this.target.primaryKeyAttribute] - ) - }; - - - promises.push(this.target.unscoped().update( - update, - { - ...options, - where: updateWhere - } - )); - } - - if (unassociatedObjects.length > 0) { - updateWhere = {}; - - update = {}; - update[this.foreignKey] = sourceInstance.get(this.sourceKey); - - Object.assign(update, this.scope); - updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => - unassociatedObject[this.target.primaryKeyAttribute] - ); - - promises.push(this.target.unscoped().update( - update, - { - ...options, - where: updateWhere - } - )); - } - - await Promise.all(promises); - - return sourceInstance; - } - - /** - * Associate one or more target rows with `this`. This method accepts a Model / string / number to associate a single row, - * or a mixed array of Model / string / numbers to associate multiple rows. - * - * @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`. - * - * @returns {Promise} - */ - async add(sourceInstance, targetInstances, options = {}) { - if (!targetInstances) return Promise.resolve(); - - - targetInstances = this.toInstanceArray(targetInstances); - - const update = { - [this.foreignKey]: sourceInstance.get(this.sourceKey), - ...this.scope - }; - - const where = { - [this.target.primaryKeyAttribute]: targetInstances.map(unassociatedObject => - unassociatedObject.get(this.target.primaryKeyAttribute) - ) - }; - - await this.target.unscoped().update(update, { ...options, where }); - - return sourceInstance; - } - - /** - * Un-associate one or several target rows. - * - * @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` - * - * @returns {Promise} - */ - async remove(sourceInstance, targetInstances, options = {}) { - const update = { - [this.foreignKey]: null - }; - - targetInstances = this.toInstanceArray(targetInstances); - - const where = { - [this.foreignKey]: sourceInstance.get(this.sourceKey), - [this.target.primaryKeyAttribute]: targetInstances.map(targetInstance => - targetInstance.get(this.target.primaryKeyAttribute) - ) - }; - - 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` - * - * @returns {Promise} - */ - async create(sourceInstance, values, options = {}) { - if (Array.isArray(options)) { - options = { - fields: options - }; - } - - if (values === undefined) { - values = {}; - } - - if (this.scope) { - for (const attribute of Object.keys(this.scope)) { - values[attribute] = this.scope[attribute]; - if (options.fields) options.fields.push(attribute); - } - } - - values[this.foreignKey] = sourceInstance.get(this.sourceKey); - if (options.fields) options.fields.push(this.foreignKey); - return await this.target.create(values, options); - } - - verifyAssociationAlias(alias) { - if (typeof alias === 'string') { - return this.as === alias; - } - - if (alias && alias.plural) { - return this.as === alias.plural; - } - - return !this.isAliased; - } -} - -module.exports = HasMany; -module.exports.HasMany = HasMany; -module.exports.default = HasMany; diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js deleted file mode 100644 index 6d19f4263c0a..000000000000 --- a/lib/associations/has-one.js +++ /dev/null @@ -1,276 +0,0 @@ -'use strict'; - -const Utils = require('./../utils'); -const Helpers = require('./helpers'); -const _ = require('lodash'); -const Association = require('./base'); -const Op = require('../operators'); - -/** - * One-to-one association - * - * In the API reference below, add the name of the association to the method, e.g. for `User.hasOne(Project)` the getter will be `user.getProject()`. - * This is almost the same as `belongsTo` with one exception - The foreign key will be defined on the target model. - * - * @see {@link Model.hasOne} - */ -class HasOne extends Association { - constructor(source, target, options) { - super(source, target, options); - - this.associationType = 'HasOne'; - this.isSingleAssociation = true; - this.foreignKeyAttribute = {}; - - if (this.as) { - this.isAliased = true; - this.options.name = { - singular: this.as - }; - } else { - this.as = this.target.options.name.singular; - this.options.name = this.target.options.name; - } - - if (_.isObject(this.options.foreignKey)) { - this.foreignKeyAttribute = this.options.foreignKey; - this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName; - } else if (this.options.foreignKey) { - this.foreignKey = this.options.foreignKey; - } - - if (!this.foreignKey) { - this.foreignKey = Utils.camelize( - [ - Utils.singularize(this.options.as || this.source.name), - this.source.primaryKeyAttribute - ].join('_') - ); - } - - if ( - this.options.sourceKey - && !this.source.rawAttributes[this.options.sourceKey] - ) { - throw new Error(`Unknown attribute "${this.options.sourceKey}" passed as sourceKey, define this attribute on model "${this.source.name}" first`); - } - - this.sourceKey = this.sourceKeyAttribute = this.options.sourceKey || this.source.primaryKeyAttribute; - this.sourceKeyField = this.source.rawAttributes[this.sourceKey].field || this.sourceKey; - this.sourceKeyIsPrimary = this.sourceKey === this.source.primaryKeyAttribute; - - this.associationAccessor = this.as; - this.options.useHooks = options.useHooks; - - if (this.target.rawAttributes[this.foreignKey]) { - this.identifierField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - } - - // Get singular name, trying to uppercase the first letter, unless the model forbids it - const singular = _.upperFirst(this.options.name.singular); - - this.accessors = { - get: `get${singular}`, - set: `set${singular}`, - create: `create${singular}` - }; - } - - // the id is in the target table - _injectAttributes() { - 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]; - this.options.onDelete = this.options.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE'); - this.options.onUpdate = this.options.onUpdate || 'CASCADE'; - } - - Helpers.addForeignKeyConstraints(newAttributes[this.foreignKey], this.source, this.target, this.options, this.sourceKeyField); - Utils.mergeDefaults(this.target.rawAttributes, newAttributes); - - this.target.refreshAttributes(); - - this.identifierField = this.target.rawAttributes[this.foreignKey].field || this.foreignKey; - - Helpers.checkNamingCollision(this); - - return this; - } - - mixin(obj) { - const methods = ['get', 'set', 'create']; - - Helpers.mixinMethods(this, obj, methods); - } - - /** - * Get the associated instance. - * - * @param {Model|Array} instances source instances - * @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 - * - * @see - * {@link Model.findOne} for a full explanation of options - * - * @returns {Promise} - */ - async get(instances, options) { - const where = {}; - - let Target = this.target; - let instance; - - options = Utils.cloneDeep(options); - - if (Object.prototype.hasOwnProperty.call(options, 'scope')) { - if (!options.scope) { - Target = Target.unscoped(); - } else { - Target = Target.scope(options.scope); - } - } - - if (Object.prototype.hasOwnProperty.call(options, 'schema')) { - Target = Target.schema(options.schema, options.schemaDelimiter); - } - - if (!Array.isArray(instances)) { - instance = instances; - instances = undefined; - } - - if (instances) { - where[this.foreignKey] = { - [Op.in]: instances.map(_instance => _instance.get(this.sourceKey)) - }; - } else { - where[this.foreignKey] = instance.get(this.sourceKey); - } - - if (this.scope) { - Object.assign(where, this.scope); - } - - options.where = options.where ? - { [Op.and]: [where, options.where] } : - where; - - if (instances) { - 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; - } - - return result; - } - - return Target.findOne(options); - } - - /** - * Set the associated model. - * - * @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` - * - * @returns {Promise} - */ - 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 - }); - } - - Object.assign(associatedInstance, this.scope); - associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); - - return associatedInstance.save(options); - } - - 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. - * - * @see - * {@link Model#create} for a full explanation of options - * - * @returns {Promise} The created target model - */ - async create(sourceInstance, values, options) { - values = values || {}; - options = options || {}; - - if (this.scope) { - for (const attribute of Object.keys(this.scope)) { - values[attribute] = this.scope[attribute]; - if (options.fields) { - options.fields.push(attribute); - } - } - } - - values[this.foreignKey] = sourceInstance.get(this.sourceKeyAttribute); - if (options.fields) { - options.fields.push(this.foreignKey); - } - - return await this.target.create(values, options); - } - - verifyAssociationAlias(alias) { - if (typeof alias === 'string') { - return this.as === alias; - } - - if (alias && alias.singular) { - return this.as === alias.singular; - } - - return !this.isAliased; - } -} - -module.exports = HasOne; diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js deleted file mode 100644 index 41e2f27a3e08..000000000000 --- a/lib/associations/helpers.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -function checkNamingCollision(association) { - if (Object.prototype.hasOwnProperty.call(association.source.rawAttributes, association.as)) { - throw new Error( - `Naming collision between attribute '${association.as}'` + - ` and association '${association.as}' on model ${association.source.name}` + - '. To remedy this, change either foreignKey or as in your association definition' - ); - } -} -exports.checkNamingCollision = checkNamingCollision; - -function addForeignKeyConstraints(newAttribute, source, target, options, key) { - // FK constraints are opt-in: users must either set `foreignKeyConstraints` - // on the association, or request an `onDelete` or `onUpdate` behavior - - if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) { - // Find primary keys: composite keys not supported with this approach - const primaryKeys = Object.keys(source.primaryKeys) - .map(primaryKeyAttribute => source.rawAttributes[primaryKeyAttribute].field || primaryKeyAttribute); - - if (primaryKeys.length === 1 || !primaryKeys.includes(key)) { - newAttribute.references = { - model: source.getTableName(), - key: key || primaryKeys[0] - }; - - newAttribute.onDelete = options.onDelete; - newAttribute.onUpdate = options.onUpdate; - } - } -} -exports.addForeignKeyConstraints = addForeignKeyConstraints; - -/** - * Mixin (inject) association methods to model prototype - * - * @private - * - * @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 - * - */ -function mixinMethods(association, obj, methods, aliases) { - aliases = aliases || {}; - - for (const method of methods) { - // don't override custom methods - if (!Object.prototype.hasOwnProperty.call(obj, association.accessors[method])) { - const realMethod = aliases[method] || method; - - obj[association.accessors[method]] = function() { - return association[realMethod](this, ...Array.from(arguments)); - }; - } - } -} -exports.mixinMethods = mixinMethods; diff --git a/lib/associations/index.js b/lib/associations/index.js deleted file mode 100644 index f7f2a2b59711..000000000000 --- a/lib/associations/index.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const Association = require('./base'); - -Association.BelongsTo = require('./belongs-to'); -Association.HasOne = require('./has-one'); -Association.HasMany = require('./has-many'); -Association.BelongsToMany = require('./belongs-to-many'); - -module.exports = Association; -module.exports.default = Association; -module.exports.Association = Association; diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js deleted file mode 100644 index 76646e2d93b4..000000000000 --- a/lib/associations/mixin.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const HasOne = require('./has-one'); -const HasMany = require('./has-many'); -const BelongsToMany = require('./belongs-to-many'); -const BelongsTo = require('./belongs-to'); - -function isModel(model, sequelize) { - return model - && model.prototype - && model.prototype instanceof sequelize.Sequelize.Model; -} - -const Mixin = { - hasMany(target, options = {}) { - if (!isModel(target, this.sequelize)) { - throw new Error(`${this.name}.hasMany called with something that's not a subclass of Sequelize.Model`); - } - - const source = this; - - // Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option) - options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); - options.useHooks = options.hooks; - - Object.assign(options, _.omit(source.options, ['hooks'])); - - if (options.useHooks) { - this.runHooks('beforeAssociate', { source, target, type: HasMany }, options); - } - - // the id is in the foreign table or in a connecting table - const association = new HasMany(source, target, options); - source.associations[association.associationAccessor] = association; - - association._injectAttributes(); - association.mixin(source.prototype); - - if (options.useHooks) { - this.runHooks('afterAssociate', { source, target, type: HasMany, association }, options); - } - - return association; - }, - - belongsToMany(target, options = {}) { - if (!isModel(target, this.sequelize)) { - throw new Error(`${this.name}.belongsToMany called with something that's not a subclass of Sequelize.Model`); - } - - const source = this; - - // Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option) - options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); - options.useHooks = options.hooks; - options.timestamps = options.timestamps === undefined ? this.sequelize.options.timestamps : options.timestamps; - Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); - - if (options.useHooks) { - this.runHooks('beforeAssociate', { source, target, type: BelongsToMany }, options); - } - // the id is in the foreign table or in a connecting table - const association = new BelongsToMany(source, target, options); - source.associations[association.associationAccessor] = association; - - association._injectAttributes(); - association.mixin(source.prototype); - - if (options.useHooks) { - this.runHooks('afterAssociate', { source, target, type: BelongsToMany, association }, options); - } - - return association; - }, - - getAssociations(target) { - return Object.values(this.associations).filter(association => association.target.name === target.name); - }, - - getAssociationForAlias(target, alias) { - // Two associations cannot have the same alias, so we can use find instead of filter - return this.getAssociations(target).find(association => association.verifyAssociationAlias(alias)) || null; - } -}; - -// The logic for hasOne and belongsTo is exactly the same -function singleLinked(Type) { - return function(target, options = {}) { - // eslint-disable-next-line no-invalid-this - const source = this; - if (!isModel(target, source.sequelize)) { - throw new Error(`${source.name}.${_.lowerFirst(Type.name)} called with something that's not a subclass of Sequelize.Model`); - } - - - // Since this is a mixin, we'll need a unique letiable name for hooks (since Model will override our hooks option) - options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); - options.useHooks = options.hooks; - - if (options.useHooks) { - source.runHooks('beforeAssociate', { source, target, type: Type }, options); - } - // the id is in the foreign table - const association = new Type(source, target, Object.assign(options, source.options)); - source.associations[association.associationAccessor] = association; - - association._injectAttributes(); - association.mixin(source.prototype); - - if (options.useHooks) { - source.runHooks('afterAssociate', { source, target, type: Type, association }, options); - } - - return association; - }; -} - -Mixin.hasOne = singleLinked(HasOne); -Mixin.belongsTo = singleLinked(BelongsTo); - -module.exports = Mixin; -module.exports.Mixin = Mixin; -module.exports.default = Mixin; diff --git a/lib/data-types.js b/lib/data-types.js deleted file mode 100644 index e96e334180c7..000000000000 --- a/lib/data-types.js +++ /dev/null @@ -1,1077 +0,0 @@ -'use strict'; - -const util = require('util'); -const _ = require('lodash'); -const wkx = require('wkx'); -const sequelizeErrors = require('./errors'); -const Validator = require('./utils/validator-extras').validator; -const momentTz = require('moment-timezone'); -const moment = require('moment'); -const { logger } = require('./utils/logger'); -const warnings = {}; -const { classToInvokable } = require('./utils/class-to-invokable'); -const { joinSQLFragments } = require('./utils/join-sql-fragments'); - -class ABSTRACT { - toString(options) { - return this.toSql(options); - } - toSql() { - return this.key; - } - stringify(value, options) { - if (this._stringify) { - return this._stringify(value, options); - } - return value; - } - bindParam(value, options) { - if (this._bindParam) { - return this._bindParam(value, options); - } - return options.bindParam(this.stringify(value, options)); - } - static toString() { - return this.name; - } - static warn(link, text) { - if (!warnings[text]) { - warnings[text] = true; - logger.warn(`${text} \n>> Check: ${link}`); - } - } - static extend(oldType) { - return new this(oldType.options); - } -} - -ABSTRACT.prototype.dialectTypes = ''; - -/** - * STRING A variable length string - */ -class STRING extends ABSTRACT { - /** - * @param {number} [length=255] length of string - * @param {boolean} [binary=false] Is this binary? - */ - constructor(length, binary) { - super(); - const options = typeof length === 'object' && length || { length, binary }; - this.options = options; - this._binary = options.binary; - this._length = options.length || 255; - } - toSql() { - return joinSQLFragments([ - `VARCHAR(${this._length})`, - this._binary && 'BINARY' - ]); - } - validate(value) { - if (Object.prototype.toString.call(value) !== '[object String]') { - if (this.options.binary && Buffer.isBuffer(value) || typeof value === 'number') { - return true; - } - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value)); - } - return true; - } - - get BINARY() { - this._binary = true; - this.options.binary = true; - return this; - } - - static get BINARY() { - return new this().BINARY; - } -} - -/** - * CHAR A fixed length string - */ -class CHAR extends STRING { - /** - * @param {number} [length=255] length of string - * @param {boolean} [binary=false] Is this binary? - */ - constructor(length, binary) { - super(typeof length === 'object' && length || { length, binary }); - } - toSql() { - return joinSQLFragments([ - `CHAR(${this._length})`, - this._binary && 'BINARY' - ]); - } -} - -/** - * Unlimited length TEXT column - */ -class TEXT extends ABSTRACT { - /** - * @param {string} [length=''] could be tiny, medium, long. - */ - constructor(length) { - super(); - const options = typeof length === 'object' && length || { length }; - this.options = options; - this._length = options.length || ''; - } - toSql() { - switch (this._length.toLowerCase()) { - case 'tiny': - return 'TINYTEXT'; - case 'medium': - return 'MEDIUMTEXT'; - case 'long': - return 'LONGTEXT'; - default: - return this.key; - } - } - validate(value) { - if (typeof value !== 'string') { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value)); - } - return true; - } -} - -/** - * An unlimited length case-insensitive text column. - * Original case is preserved but acts case-insensitive when comparing values (such as when finding or unique constraints). - * Only available in Postgres and SQLite. - * - */ -class CITEXT extends ABSTRACT { - toSql() { - return 'CITEXT'; - } - validate(value) { - if (typeof value !== 'string') { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value)); - } - return true; - } -} - -/** - * Base number type which is used to build other types - */ -class NUMBER extends ABSTRACT { - /** - * @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? - * @param {string|number} [options.decimals] number of decimal points, used with length `FLOAT(5, 4)` - * @param {string|number} [options.precision] defines precision for decimal type - * @param {string|number} [options.scale] defines scale for decimal type - */ - constructor(options = {}) { - super(); - if (typeof options === 'number') { - options = { - length: options - }; - } - this.options = options; - this._length = options.length; - this._zerofill = options.zerofill; - this._decimals = options.decimals; - this._precision = options.precision; - this._scale = options.scale; - this._unsigned = options.unsigned; - } - toSql() { - let result = this.key; - if (this._length) { - result += `(${this._length}`; - if (typeof this._decimals === 'number') { - result += `,${this._decimals}`; - } - result += ')'; - } - if (this._unsigned) { - result += ' UNSIGNED'; - } - if (this._zerofill) { - result += ' ZEROFILL'; - } - return result; - } - validate(value) { - if (!Validator.isFloat(String(value))) { - throw new sequelizeErrors.ValidationError(util.format(`%j is not a valid ${this.key.toLowerCase()}`, value)); - } - return true; - } - _stringify(number) { - if (typeof number === 'number' || typeof number === 'boolean' || number === null || number === undefined) { - return number; - } - if (typeof number.toString === 'function') { - return number.toString(); - } - return number; - } - - get UNSIGNED() { - this._unsigned = true; - this.options.unsigned = true; - return this; - } - - get ZEROFILL() { - this._zerofill = true; - this.options.zerofill = true; - return this; - } - - static get UNSIGNED() { - return new this().UNSIGNED; - } - - static get ZEROFILL() { - return new this().ZEROFILL; - } -} - -/** - * A 32 bit integer - */ -class INTEGER extends NUMBER { - validate(value) { - if (!Validator.isInt(String(value))) { - throw new sequelizeErrors.ValidationError(util.format(`%j is not a valid ${this.key.toLowerCase()}`, value)); - } - return true; - } -} - -/** - * A 8 bit integer - */ -class TINYINT extends INTEGER { -} - -/** - * A 16 bit integer - */ -class SMALLINT extends INTEGER { -} - -/** - * A 24 bit integer - */ -class MEDIUMINT extends INTEGER { -} - -/** - * A 64 bit integer - */ -class BIGINT extends INTEGER { -} - -/** - * Floating point number (4-byte precision). - */ -class FLOAT extends NUMBER { - /** - * @param {string|number} [length] length of type, like `FLOAT(4)` - * @param {string|number} [decimals] number of decimal points, used with length `FLOAT(5, 4)` - */ - constructor(length, decimals) { - super(typeof length === 'object' && length || { length, decimals }); - } - validate(value) { - if (!Validator.isFloat(String(value))) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid float', value)); - } - return true; - } -} - -/** - * Floating point number (4-byte precision). - */ -class REAL extends NUMBER { - /** - * @param {string|number} [length] length of type, like `REAL(4)` - * @param {string|number} [decimals] number of decimal points, used with length `REAL(5, 4)` - */ - constructor(length, decimals) { - super(typeof length === 'object' && length || { length, decimals }); - } -} - -/** - * Floating point number (8-byte precision). - */ -class DOUBLE extends NUMBER { - /** - * @param {string|number} [length] length of type, like `DOUBLE PRECISION(25)` - * @param {string|number} [decimals] number of decimal points, used with length `DOUBLE PRECISION(25, 10)` - */ - constructor(length, decimals) { - super(typeof length === 'object' && length || { length, decimals }); - } -} - -/** - * Decimal type, variable precision, take length as specified by user - */ -class DECIMAL extends NUMBER { - /** - * @param {string|number} [precision] defines precision - * @param {string|number} [scale] defines scale - */ - constructor(precision, scale) { - super(typeof precision === 'object' && precision || { precision, scale }); - } - toSql() { - if (this._precision || this._scale) { - return `DECIMAL(${[this._precision, this._scale].filter(_.identity).join(',')})`; - } - return 'DECIMAL'; - } - validate(value) { - if (!Validator.isDecimal(String(value))) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid decimal', value)); - } - return true; - } -} - -// TODO: Create intermediate class -const protoExtensions = { - escape: false, - _value(value) { - if (isNaN(value)) { - return 'NaN'; - } - if (!isFinite(value)) { - const sign = value < 0 ? '-' : ''; - return `${sign}Infinity`; - } - - return value; - }, - _stringify(value) { - return `'${this._value(value)}'`; - }, - _bindParam(value, options) { - return options.bindParam(this._value(value)); - } -}; - -for (const floating of [FLOAT, DOUBLE, REAL]) { - Object.assign(floating.prototype, protoExtensions); -} - -/** - * A boolean / tinyint column, depending on dialect - */ -class BOOLEAN extends ABSTRACT { - toSql() { - return 'TINYINT(1)'; - } - validate(value) { - if (!Validator.isBoolean(String(value))) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid boolean', value)); - } - return true; - } - _sanitize(value) { - if (value !== null && value !== undefined) { - if (Buffer.isBuffer(value) && value.length === 1) { - // Bit fields are returned as buffers - value = value[0]; - } - const type = typeof value; - if (type === 'string') { - // Only take action on valid boolean strings. - return value === 'true' ? true : value === 'false' ? false : value; - } - if (type === 'number') { - // Only take action on valid boolean integers. - return value === 1 ? true : value === 0 ? false : value; - } - } - return value; - } -} - - -BOOLEAN.parse = BOOLEAN.prototype._sanitize; - -/** - * A time column - * - */ -class TIME extends ABSTRACT { - toSql() { - return 'TIME'; - } -} - -/** - * Date column with timezone, default is UTC - */ -class DATE extends ABSTRACT { - /** - * @param {string|number} [length] precision to allow storing milliseconds - */ - constructor(length) { - super(); - const options = typeof length === 'object' && length || { length }; - this.options = options; - this._length = options.length || ''; - } - toSql() { - return 'DATETIME'; - } - validate(value) { - if (!Validator.isDate(String(value))) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid date', value)); - } - return true; - } - _sanitize(value, options) { - if ((!options || options && !options.raw) && !(value instanceof Date) && !!value) { - return new Date(value); - } - return value; - } - _isChanged(value, originalValue) { - if (originalValue && !!value && - (value === originalValue || - value instanceof Date && originalValue instanceof Date && value.getTime() === originalValue.getTime())) { - return false; - } - // not changed when set to same empty value - if (!originalValue && !value && originalValue === value) { - return false; - } - return true; - } - _applyTimezone(date, options) { - if (options.timezone) { - if (momentTz.tz.zone(options.timezone)) { - return momentTz(date).tz(options.timezone); - } - return date = moment(date).utcOffset(options.timezone); - } - return momentTz(date); - } - _stringify(date, options) { - date = this._applyTimezone(date, options); - // Z here means current timezone, _not_ UTC - return date.format('YYYY-MM-DD HH:mm:ss.SSS Z'); - } -} - -/** - * A date only column (no timestamp) - */ -class DATEONLY extends ABSTRACT { - toSql() { - return 'DATE'; - } - _stringify(date) { - return moment(date).format('YYYY-MM-DD'); - } - _sanitize(value, options) { - if ((!options || options && !options.raw) && !!value) { - return moment(value).format('YYYY-MM-DD'); - } - return value; - } - _isChanged(value, originalValue) { - if (originalValue && !!value && originalValue === value) { - return false; - } - // not changed when set to same empty value - if (!originalValue && !value && originalValue === value) { - return false; - } - return true; - } -} - -/** - * A key / value store column. Only available in Postgres. - */ -class HSTORE extends ABSTRACT { - validate(value) { - if (!_.isPlainObject(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid hstore', value)); - } - return true; - } -} - -/** - * A JSON string column. Available in MySQL, Postgres and SQLite - */ -class JSONTYPE extends ABSTRACT { - validate() { - return true; - } - _stringify(value) { - return JSON.stringify(value); - } -} - -/** - * A binary storage JSON column. Only available in Postgres. - */ -class JSONB extends JSONTYPE { -} - -/** - * A default value of the current timestamp - */ -class NOW extends ABSTRACT { -} - -/** - * Binary storage - */ -class BLOB extends ABSTRACT { - /** - * @param {string} [length=''] could be tiny, medium, long. - */ - constructor(length) { - super(); - const options = typeof length === 'object' && length || { length }; - this.options = options; - this._length = options.length || ''; - } - toSql() { - switch (this._length.toLowerCase()) { - case 'tiny': - return 'TINYBLOB'; - case 'medium': - return 'MEDIUMBLOB'; - case 'long': - return 'LONGBLOB'; - default: - return this.key; - } - } - validate(value) { - if (typeof value !== 'string' && !Buffer.isBuffer(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid blob', value)); - } - return true; - } - _stringify(value) { - if (!Buffer.isBuffer(value)) { - if (Array.isArray(value)) { - value = Buffer.from(value); - } - else { - value = Buffer.from(value.toString()); - } - } - const hex = value.toString('hex'); - return this._hexify(hex); - } - _hexify(hex) { - return `X'${hex}'`; - } - _bindParam(value, options) { - if (!Buffer.isBuffer(value)) { - if (Array.isArray(value)) { - value = Buffer.from(value); - } - else { - value = Buffer.from(value.toString()); - } - } - return options.bindParam(value); - } -} - - -BLOB.prototype.escape = false; - -/** - * Range types are data types representing a range of values of some element type (called the range's subtype). - * Only available in Postgres. See [the Postgres documentation](http://www.postgresql.org/docs/9.4/static/rangetypes.html) for more details - */ -class RANGE extends ABSTRACT { - /** - * @param {ABSTRACT} subtype A subtype for range, like RANGE(DATE) - */ - constructor(subtype) { - super(); - const options = _.isPlainObject(subtype) ? subtype : { subtype }; - if (!options.subtype) - options.subtype = new INTEGER(); - if (typeof options.subtype === 'function') { - options.subtype = new options.subtype(); - } - this._subtype = options.subtype.key; - this.options = options; - } - validate(value) { - if (!Array.isArray(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid range', value)); - } - if (value.length !== 2) { - throw new sequelizeErrors.ValidationError('A range must be an array with two elements'); - } - return true; - } -} - -/** - * A column storing a unique universal identifier. - * Use with `UUIDV1` or `UUIDV4` for default values. - */ -class UUID extends ABSTRACT { - validate(value, options) { - if (typeof value !== 'string' || !Validator.isUUID(value) && (!options || !options.acceptStrings)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value)); - } - return true; - } -} - -/** - * A default unique universal identifier generated following the UUID v1 standard - */ -class UUIDV1 extends ABSTRACT { - validate(value, options) { - if (typeof value !== 'string' || !Validator.isUUID(value) && (!options || !options.acceptStrings)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuid', value)); - } - return true; - } -} - -/** - * A default unique universal identifier generated following the UUID v4 standard - */ -class UUIDV4 extends ABSTRACT { - validate(value, options) { - if (typeof value !== 'string' || !Validator.isUUID(value, 4) && (!options || !options.acceptStrings)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid uuidv4', value)); - } - return true; - } -} - -/** - * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB. - * - * You could also use it to validate a value before permuting and storing it. VIRTUAL also takes a return type and dependency fields as arguments - * If a virtual attribute is present in `attributes` it will automatically pull in the extra fields as well. - * Return type is mostly useful for setups that rely on types like GraphQL. - * - * @example Checking password length before hashing it - * sequelize.define('user', { - * password_hash: DataTypes.STRING, - * password: { - * type: DataTypes.VIRTUAL, - * set: function (val) { - * // Remember to set the data value, otherwise it won't be validated - * this.setDataValue('password', val); - * this.setDataValue('password_hash', this.salt + val); - * }, - * validate: { - * isLongEnough: function (val) { - * if (val.length < 7) { - * throw new Error("Please choose a longer password") - * } - * } - * } - * } - * }) - * - * # In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB. - * - * @example Virtual with dependency fields - * { - * active: { - * type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']), - * get: function() { - * return this.get('createdAt') > Date.now() - (7 * 24 * 60 * 60 * 1000) - * } - * } - * } - * - */ -class VIRTUAL extends ABSTRACT { - /** - * @param {ABSTRACT} [ReturnType] return type for virtual type - * @param {Array} [fields] array of fields this virtual type is dependent on - */ - constructor(ReturnType, fields) { - super(); - if (typeof ReturnType === 'function') - ReturnType = new ReturnType(); - this.returnType = ReturnType; - this.fields = fields; - } -} - -/** - * An enumeration, Postgres Only - * - * @example - * DataTypes.ENUM('value', 'another value') - * DataTypes.ENUM(['value', 'another value']) - * DataTypes.ENUM({ - * values: ['value', 'another value'] - * }) - */ -class ENUM extends ABSTRACT { - /** - * @param {...any|{ values: any[] }|any[]} args either array of values or options object with values array. It also supports variadic values - */ - constructor(...args) { - super(); - const value = args[0]; - const options = typeof value === 'object' && !Array.isArray(value) && value || { - values: args.reduce((result, element) => { - return result.concat(Array.isArray(element) ? element : [element]); - }, []) - }; - this.values = options.values; - this.options = options; - } - validate(value) { - if (!this.values.includes(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid choice in %j', value, this.values)); - } - return true; - } -} - -/** - * An array of `type`. Only available in Postgres. - * - * @example - * DataTypes.ARRAY(DataTypes.DECIMAL) - */ -class ARRAY extends ABSTRACT { - /** - * @param {ABSTRACT} type type of array values - */ - constructor(type) { - super(); - const options = _.isPlainObject(type) ? type : { type }; - this.options = options; - this.type = typeof options.type === 'function' ? new options.type() : options.type; - } - toSql() { - return `${this.type.toSql()}[]`; - } - validate(value) { - if (!Array.isArray(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid array', value)); - } - return true; - } - static is(obj, type) { - return obj instanceof ARRAY && obj.type instanceof type; - } -} - -/** - * A column storing Geometry information. - * It is only available in PostgreSQL (with PostGIS), MariaDB or MySQL. - * - * 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`. - * - * 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 - * DataTypes.GEOMETRY('POINT') - * DataTypes.GEOMETRY('POINT', 4326) - * - * @example Create a new point - * const point = { type: 'Point', coordinates: [39.807222,-76.984722]}; - * - * User.create({username: 'username', geometry: point }); - * - * @example Create a new linestring - * const line = { type: 'LineString', 'coordinates': [ [100.0, 0.0], [101.0, 1.0] ] }; - * - * User.create({username: 'username', geometry: line }); - * - * @example Create a new polygon - * const polygon = { type: 'Polygon', coordinates: [ - * [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - * [100.0, 1.0], [100.0, 0.0] ] - * ]}; - * - * User.create({username: 'username', geometry: polygon }); - * - * @example Create a new point with a custom SRID - * const point = { - * type: 'Point', - * coordinates: [39.807222,-76.984722], - * crs: { type: 'name', properties: { name: 'EPSG:4326'} } - * }; - * - * User.create({username: 'username', geometry: point }) - * - * - * @see {@link DataTypes.GEOGRAPHY} - */ -class GEOMETRY extends ABSTRACT { - /** - * @param {string} [type] Type of geometry data - * @param {string} [srid] SRID of type - */ - constructor(type, srid) { - super(); - const options = _.isPlainObject(type) ? type : { type, srid }; - this.options = options; - this.type = options.type; - this.srid = options.srid; - } - _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; - } - _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; - } -} - -GEOMETRY.prototype.escape = false; - -/** - * A geography datatype represents two dimensional spacial objects in an elliptic coord system. - * - * __The difference from geometry and geography type:__ - * - * PostGIS 1.5 introduced a new spatial type called geography, which uses geodetic measurement instead of Cartesian measurement. - * Coordinate points in the geography type are always represented in WGS 84 lon lat degrees (SRID 4326), - * but measurement functions and relationships ST_Distance, ST_DWithin, ST_Length, and ST_Area always return answers in meters or assume inputs in meters. - * - * __What is best to use? It depends:__ - * - * When choosing between the geometry and geography type for data storage, you should consider what you’ll be using it for. - * If all you do are simple measurements and relationship checks on your data, and your data covers a fairly large area, then most likely you’ll be better off storing your data using the new geography type. - * Although the new geography data type can cover the globe, the geometry type is far from obsolete. - * The geometry type has a much richer set of functions than geography, relationship checks are generally faster, and it has wider support currently across desktop and web-mapping tools - * - * @example Defining a Geography type attribute - * DataTypes.GEOGRAPHY - * DataTypes.GEOGRAPHY('POINT') - * DataTypes.GEOGRAPHY('POINT', 4326) - */ -class GEOGRAPHY extends ABSTRACT { - /** - * @param {string} [type] Type of geography data - * @param {string} [srid] SRID of type - */ - constructor(type, srid) { - super(); - const options = _.isPlainObject(type) ? type : { type, srid }; - this.options = options; - this.type = options.type; - this.srid = options.srid; - } - _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; - } - _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; - } -} - - -GEOGRAPHY.prototype.escape = false; - -/** - * The cidr type holds an IPv4 or IPv6 network specification. Takes 7 or 19 bytes. - * - * Only available for Postgres - */ -class CIDR extends ABSTRACT { - validate(value) { - if (typeof value !== 'string' || !Validator.isIPRange(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid CIDR', value)); - } - return true; - } -} - -/** - * The INET type holds an IPv4 or IPv6 host address, and optionally its subnet. Takes 7 or 19 bytes - * - * Only available for Postgres - */ -class INET extends ABSTRACT { - validate(value) { - if (typeof value !== 'string' || !Validator.isIP(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid INET', value)); - } - return true; - } -} - -/** - * The MACADDR type stores MAC addresses. Takes 6 bytes - * - * Only available for Postgres - * - */ -class MACADDR extends ABSTRACT { - validate(value) { - if (typeof value !== 'string' || !Validator.isMACAddress(value)) { - throw new sequelizeErrors.ValidationError(util.format('%j is not a valid MACADDR', value)); - } - return true; - } -} - -/** - * 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 - * sequelize.define('model', { - * column: DataTypes.INTEGER - * }) - * ``` - * When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean - * that that column will be returned as an instance of `Buffer` when being fetched by sequelize. - * - * To provide a length for the data type, you can invoke it like a function: `INTEGER(2)` - * - * Some data types have special properties that can be accessed in order to change the data type. - * For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`. - * The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well. - * - * * All number types (`INTEGER`, `BIGINT`, `FLOAT`, `DOUBLE`, `REAL`, `DECIMAL`) expose the properties `UNSIGNED` and `ZEROFILL` - * * The `CHAR` and `STRING` types expose the `BINARY` property - * - * Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for - * defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard: - * ```js - * sequelize.define('model', { - * uuid: { - * type: DataTypes.UUID, - * defaultValue: DataTypes.UUIDV1, - * primaryKey: true - * } - * }) - * ``` - * There may be times when you want to generate your own UUID conforming to some other algorithm. This is accomplished - * using the defaultValue property as well, but instead of specifying one of the supplied UUID types, you return a value - * from a function. - * ```js - * sequelize.define('model', { - * uuid: { - * type: DataTypes.UUID, - * defaultValue: function() { - * return generateMyId() - * }, - * primaryKey: true - * } - * }) - * ``` - */ -const DataTypes = module.exports = { - ABSTRACT, - STRING, - CHAR, - TEXT, - NUMBER, - TINYINT, - SMALLINT, - MEDIUMINT, - INTEGER, - BIGINT, - FLOAT, - TIME, - DATE, - DATEONLY, - BOOLEAN, - NOW, - BLOB, - DECIMAL, - NUMERIC: DECIMAL, - UUID, - UUIDV1, - UUIDV4, - HSTORE, - JSON: JSONTYPE, - JSONB, - VIRTUAL, - ARRAY, - ENUM, - RANGE, - REAL, - 'DOUBLE PRECISION': DOUBLE, - DOUBLE, - GEOMETRY, - GEOGRAPHY, - CIDR, - INET, - MACADDR, - CITEXT, - TSVECTOR -}; - -_.each(DataTypes, (dataType, name) => { - // guard for aliases - if (!Object.prototype.hasOwnProperty.call(dataType, 'key')) { - dataType.types = {}; - dataType.key = dataType.prototype.key = name; - } -}); - -const dialectMap = {}; -dialectMap.postgres = require('./dialects/postgres/data-types')(DataTypes); -dialectMap.mysql = require('./dialects/mysql/data-types')(DataTypes); -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 = Object.values(dialectMap); - -for (const dataTypes of dialectList) { - _.each(dataTypes, (DataType, key) => { - if (!DataType.key) { - DataType.key = DataType.prototype.key = key; - } - }); -} - -// Wrap all data types to not require `new` -for (const dataTypes of [DataTypes, ...dialectList]) { - _.each(dataTypes, (DataType, key) => { - dataTypes[key] = classToInvokable(DataType); - }); -} - -Object.assign(DataTypes, dialectMap); diff --git a/lib/deferrable.js b/lib/deferrable.js deleted file mode 100644 index a2379b533da6..000000000000 --- a/lib/deferrable.js +++ /dev/null @@ -1,105 +0,0 @@ -'use strict'; - -const { classToInvokable } = require('./utils'); - -class ABSTRACT { - static toString(...args) { - return new this().toString(...args); - } - - toString(...args) { - return this.toSql(...args); - } - - toSql() { - throw new Error('toSql implementation missing'); - } -} - -class INITIALLY_DEFERRED extends ABSTRACT { - toSql() { - return 'DEFERRABLE INITIALLY DEFERRED'; - } -} - -class INITIALLY_IMMEDIATE extends ABSTRACT { - toSql() { - return 'DEFERRABLE INITIALLY IMMEDIATE'; - } -} - -class NOT extends ABSTRACT { - toSql() { - return 'NOT DEFERRABLE'; - } -} - -class SET_DEFERRED extends ABSTRACT { - constructor(constraints) { - super(); - this.constraints = constraints; - } - - toSql(queryGenerator) { - return queryGenerator.setDeferredQuery(this.constraints); - } -} - -class SET_IMMEDIATE extends ABSTRACT { - constructor(constraints) { - super(); - this.constraints = constraints; - } - - toSql(queryGenerator) { - return queryGenerator.setImmediateQuery(this.constraints); - } -} - -/** - * A collection of properties related to deferrable constraints. It can be used to - * make foreign key constraints deferrable and to set the constraints within a - * transaction. This is only supported in PostgreSQL. - * - * The foreign keys can be configured like this. It will create a foreign key - * that will check the constraints immediately when the data was inserted. - * - * ```js - * sequelize.define('Model', { - * foreign_id: { - * type: Sequelize.INTEGER, - * references: { - * model: OtherModel, - * key: 'id', - * deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE - * } - * } - * }); - * ``` - * - * The constraints can be configured in a transaction like this. It will - * trigger a query once the transaction has been started and set the constraints - * to be checked at the very end of the transaction. - * - * ```js - * sequelize.transaction({ - * deferrable: Sequelize.Deferrable.SET_DEFERRED - * }); - * ``` - * - * @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 = { - 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 deleted file mode 100644 index 8f6ee9f44f99..000000000000 --- a/lib/dialects/abstract/connection-manager.js +++ /dev/null @@ -1,354 +0,0 @@ -'use strict'; - -const { Pool, TimeoutError } = require('sequelize-pool'); -const _ = require('lodash'); -const semver = require('semver'); -const errors = require('../../errors'); -const { logger } = require('../../utils/logger'); -const deprecations = require('../../utils/deprecations'); -const debug = logger.debugContext('pool'); - -/** - * Abstract Connection Manager - * - * Connection manager which handles pooling & replication. - * Uses sequelize-pool for pooling - * - * @private - */ -class ConnectionManager { - constructor(dialect, sequelize) { - const config = _.cloneDeep(sequelize.config); - - this.sequelize = sequelize; - this.config = config; - this.dialect = dialect; - this.versionPromise = null; - this.dialectName = this.sequelize.options.dialect; - - if (config.pool === false) { - throw new Error('Support for pool:false was removed in v4.0'); - } - - config.pool = _.defaults(config.pool || {}, { - max: 5, - min: 0, - idle: 10000, - acquire: 60000, - evict: 1000, - validate: this._validate.bind(this) - }); - - this.initPools(); - } - - refreshTypeParser(dataTypes) { - _.each(dataTypes, dataType => { - if (Object.prototype.hasOwnProperty.call(dataType, 'parse')) { - if (dataType.types[this.dialectName]) { - this._refreshTypeParser(dataType); - } else { - throw new Error(`Parse function not supported for type ${dataType.key} in dialect ${this.dialectName}`); - } - } - }); - } - - /** - * Try to load dialect module from various configured options. - * Priority goes like dialectModulePath > dialectModule > require(default) - * - * @param {string} moduleName Name of dialect module to lookup - * - * @private - * @returns {object} - */ - _loadDialectModule(moduleName) { - try { - if (this.sequelize.config.dialectModulePath) { - return require(this.sequelize.config.dialectModulePath); - } - if (this.sequelize.config.dialectModule) { - return this.sequelize.config.dialectModule; - } - return require(moduleName); - - } catch (err) { - if (err.code === 'MODULE_NOT_FOUND') { - if (this.sequelize.config.dialectModulePath) { - throw new Error(`Unable to find dialect at ${this.sequelize.config.dialectModulePath}`); - } - throw new Error(`Please install ${moduleName} package manually`); - } - - throw err; - } - } - - /** - * Handler which executes on process exit or connection manager shutdown - * - * @private - * @returns {Promise} - */ - async _onProcessExit() { - if (!this.pool) { - return; - } - - await this.pool.drain(); - debug('connection drain due to process exit'); - - return await this.pool.destroyAllNow(); - } - - /** - * Drain the pool and close it permanently - * - * @returns {Promise} - */ - async close() { - // Mark close of pool - this.getConnection = async function getConnection() { - throw new Error('ConnectionManager.getConnection was called after the connection manager was closed!'); - }; - - return await this._onProcessExit(); - } - - /** - * Initialize connection pool. By default pool autostart is set to false, so no connection will be - * be created unless `pool.acquire` is called. - */ - initPools() { - const config = this.config; - - if (!config.replication) { - this.pool = new Pool({ - name: 'sequelize', - create: () => this._connect(config), - 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, - maxUses: config.pool.maxUses - }); - - debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, no replication`); - - return; - } - - if (!Array.isArray(config.replication.read)) { - config.replication.read = [config.replication.read]; - } - - // Map main connection config - config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication')); - - // Apply defaults to each read config - config.replication.read = config.replication.read.map(readConfig => - _.defaults(readConfig, _.omit(this.config, 'replication')) - ); - - // custom pooling for replication (original author @janmeier) - let reads = 0; - this.pool = { - release: client => { - if (client.queryType === 'read') { - this.pool.read.release(client); - } else { - this.pool.write.release(client); - } - }, - acquire: (queryType, useMaster) => { - useMaster = useMaster === undefined ? false : useMaster; - if (queryType === 'SELECT' && !useMaster) { - return this.pool.read.acquire(); - } - return this.pool.write.acquire(); - }, - destroy: connection => { - this.pool[connection.queryType].destroy(connection); - debug('connection destroy'); - }, - destroyAllNow: async () => { - await Promise.all([ - this.pool.read.destroyAllNow(), - this.pool.write.destroyAllNow() - ]); - - debug('all connections destroyed'); - }, - drain: async () => Promise.all([ - this.pool.write.drain(), - this.pool.read.drain() - ]), - read: new Pool({ - name: 'sequelize:read', - create: async () => { - // round robin config - const nextRead = reads++ % config.replication.read.length; - const connection = await this._connect(config.replication.read[nextRead]); - connection.queryType = 'read'; - return connection; - }, - destroy: connection => this._disconnect(connection), - validate: config.pool.validate, - max: config.pool.max, - min: config.pool.min, - acquireTimeoutMillis: config.pool.acquire, - idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict, - maxUses: config.pool.maxUses - }), - write: new Pool({ - name: 'sequelize: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, - max: config.pool.max, - min: config.pool.min, - acquireTimeoutMillis: config.pool.acquire, - idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict, - maxUses: config.pool.maxUses - }) - }; - - debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, with replication`); - } - - /** - * 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 {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} - */ - async getConnection(options) { - options = options || {}; - - if (this.sequelize.options.databaseVersion === 0) { - 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 - _options.logging = () => {}; - _options.logging.__testLoggingFn = true; - - //connection might have set databaseVersion value at initialization, - //avoiding a useless round trip - if (this.sequelize.options.databaseVersion === 0) { - 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 await this._disconnect(connection); - } catch (err) { - this.versionPromise = null; - throw err; - } - })(); - } - await this.versionPromise; - } - - let result; - - try { - result = await this.pool.acquire(options.type, options.useMaster); - } catch (error) { - if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); - throw error; - } - - debug('connection acquired'); - - return result; - } - - /** - * Release a pooled connection so it can be utilized by other connection requests - * - * @param {Connection} connection - * - * @returns {Promise} - */ - async releaseConnection(connection) { - this.pool.release(connection); - debug('connection released'); - } - - /** - * Call dialect library to get connection - * - * @param {*} config Connection config - * @private - * @returns {Promise} - */ - 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; - } - - /** - * Call dialect library to disconnect a connection - * - * @param {Connection} connection - * @private - * @returns {Promise} - */ - async _disconnect(connection) { - await this.sequelize.runHooks('beforeDisconnect', connection); - await this.dialect.connectionManager.disconnect(connection); - return this.sequelize.runHooks('afterDisconnect', connection); - } - - /** - * Determine if a connection is still valid or not - * - * @param {Connection} connection - * - * @returns {boolean} - */ - _validate(connection) { - if (!this.dialect.connectionManager.validate) { - return true; - } - - return this.dialect.connectionManager.validate(connection); - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js deleted file mode 100644 index 57d681fac84b..000000000000 --- a/lib/dialects/abstract/index.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -class AbstractDialect {} - -AbstractDialect.prototype.supports = { - 'DEFAULT': true, - 'DEFAULT VALUES': false, - 'VALUES ()': false, - 'LIMIT ON UPDATE': false, - 'ORDER NULLS': false, - 'UNION': true, - 'UNION ALL': true, - 'RIGHT JOIN': true, - - /* does the dialect support returning values for inserted/updated fields */ - returnValues: false, - - /* features specific to autoIncrement values */ - autoIncrement: { - /* does the dialect require modification of insert queries when inserting auto increment fields */ - identityInsert: false, - - /* does the dialect support inserting default/null values for autoincrement fields */ - defaultValue: true, - - /* does the dialect support updating autoincrement fields */ - update: true - }, - /* Do we need to say DEFAULT for bulk insert */ - bulkDefault: false, - schemas: false, - transactions: true, - settingIsolationLevelDuringTransaction: true, - transactionOptions: { - type: false - }, - migrations: true, - upserts: true, - inserts: { - ignoreDuplicates: '', /* dialect specific words for INSERT IGNORE or DO NOTHING */ - updateOnDuplicate: false, /* whether dialect supports ON DUPLICATE KEY UPDATE */ - onConflictDoNothing: '' /* dialect specific words for ON CONFLICT DO NOTHING */ - }, - constraints: { - restrict: true, - addConstraint: true, - dropConstraint: true, - unique: true, - default: false, - check: true, - foreignKey: true, - primaryKey: true - }, - index: { - collate: true, - length: false, - parser: false, - concurrently: false, - type: false, - using: true, - functionBased: false, - operator: false - }, - joinTableDependent: true, - groupedLimit: true, - indexViaAlter: false, - JSON: false, - deferrableConstraints: false -}; - -module.exports = AbstractDialect; -module.exports.AbstractDialect = AbstractDialect; -module.exports.default = AbstractDialect; diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js deleted file mode 100644 index c9793a45693e..000000000000 --- a/lib/dialects/abstract/query-generator.js +++ /dev/null @@ -1,2744 +0,0 @@ -'use strict'; - -const util = require('util'); -const _ = require('lodash'); -const uuidv4 = require('uuid').v4; - -const Utils = require('../../utils'); -const deprecations = require('../../utils/deprecations'); -const SqlString = require('../../sql-string'); -const DataTypes = require('../../data-types'); -const Model = require('../../model'); -const Association = require('../../associations/base'); -const BelongsTo = require('../../associations/belongs-to'); -const BelongsToMany = require('../../associations/belongs-to-many'); -const HasMany = require('../../associations/has-many'); -const Op = require('../../operators'); -const sequelizeError = require('../../errors'); -const IndexHints = require('../../index-hints'); - -const QuoteHelper = require('./query-generator/helpers/quote'); - -/** - * Abstract Query Generator - * - * @private - */ -class QueryGenerator { - constructor(options) { - if (!options.sequelize) throw new Error('QueryGenerator initialized without options.sequelize'); - if (!options._dialect) throw new Error('QueryGenerator initialized without options._dialect'); - - this.sequelize = options.sequelize; - this.options = options.sequelize.options; - - // dialect name - this.dialect = options._dialect.name; - this._dialect = options._dialect; - } - - extractTableDetails(tableName, options) { - options = options || {}; - tableName = tableName || {}; - return { - schema: tableName.schema || options.schema || 'public', - tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName, - delimiter: tableName.delimiter || options.delimiter || '.' - }; - } - - addSchema(param) { - if (!param._schema) return param.tableName || param; - const self = this; - return { - tableName: param.tableName || param, - table: param.tableName || param, - name: param.name || param, - schema: param._schema, - delimiter: param._schemaDelimiter || '.', - toString() { - return self.quoteTable(this); - } - }; - } - - dropSchema(tableName, options) { - return this.dropTableQuery(tableName, options); - } - - describeTableQuery(tableName, schema, schemaDelimiter) { - const table = this.quoteTable( - this.addSchema({ - tableName, - _schema: schema, - _schemaDelimiter: schemaDelimiter - }) - ); - - return `DESCRIBE ${table};`; - } - - dropTableQuery(tableName) { - return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)};`; - } - - renameTableQuery(before, after) { - return `ALTER TABLE ${this.quoteTable(before)} RENAME TO ${this.quoteTable(after)};`; - } - - /** - * Returns an insert into command - * - * @param {string} table - * @param {object} valueHash attribute value pairs - * @param {object} modelAttributes - * @param {object} [options] - * - * @private - */ - insertQuery(table, valueHash, modelAttributes, options) { - options = options || {}; - _.defaults(options, this.options); - - const modelAttributeMap = {}; - const bind = []; - const fields = []; - const returningModelAttributes = []; - const values = []; - const quotedTable = this.quoteTable(table); - const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam; - let query; - let valueQuery = ''; - let emptyQuery = ''; - let outputFragment = ''; - let returningFragment = ''; - let identityWrapperRequired = false; - let tmpTable = ''; //tmpTable declaration for trigger - - if (modelAttributes) { - _.each(modelAttributes, (attribute, key) => { - modelAttributeMap[key] = attribute; - if (attribute.field) { - modelAttributeMap[attribute.field] = attribute; - } - }); - } - - if (this._dialect.supports['DEFAULT VALUES']) { - emptyQuery += ' DEFAULT VALUES'; - } else if (this._dialect.supports['VALUES ()']) { - emptyQuery += ' VALUES ()'; - } - - if (this._dialect.supports.returnValues && options.returning) { - const returnValues = this.generateReturnValues(modelAttributes, options); - - returningModelAttributes.push(...returnValues.returnFields); - returningFragment = returnValues.returningFragment; - tmpTable = returnValues.tmpTable || ''; - outputFragment = returnValues.outputFragment || ''; - } - - if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) { - // Not currently supported with search path (requires output of multiple queries) - options.bindParam = false; - } - - if (this._dialect.supports.EXCEPTION && options.exception) { - // Not currently supported with bind parameters (requires output of multiple queries) - options.bindParam = false; - } - - valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull); - for (const key in valueHash) { - if (Object.prototype.hasOwnProperty.call(valueHash, key)) { - const value = valueHash[key]; - 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 (!this._dialect.supports.autoIncrement.defaultValue) { - fields.splice(-1, 1); - } else if (this._dialect.supports.DEFAULT) { - values.push('DEFAULT'); - } else { - values.push(this.escape(null)); - } - } else { - if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true) { - identityWrapperRequired = true; - } - - if (value instanceof Utils.SequelizeMethod || options.bindParam === false) { - values.push(this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' })); - } else { - values.push(this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }, bindParam)); - } - } - } - } - - 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 : '', - attributes: fields.join(','), - output: outputFragment, - values: values.join(','), - tmpTable - }; - - valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`; - emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`; - - // Mostly for internal use, so we expect the user to know what he's doing! - // pg_temp functions are private per connection, so we never risk this function interfering with another one. - if (this._dialect.supports.EXCEPTION && options.exception) { - const dropFunction = 'DROP FUNCTION IF EXISTS pg_temp.testfunc()'; - - if (returningModelAttributes.length === 0) { - returningModelAttributes.push('*'); - } - - const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`; - const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; - - options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; - valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter} BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; - } else { - valueQuery += returningFragment; - emptyQuery += returningFragment; - } - - query = `${replacements.attributes.length ? valueQuery : emptyQuery};`; - if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) { - query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`; - } - - // Used by Postgres upsertQuery and calls to here with options.exception set to true - const result = { query }; - if (options.bindParam !== false) { - result.bind = bind; - } - - return result; - } - - /** - * Returns an insert into command for multiple values. - * - * @param {string} tableName - * @param {object} fieldValueHashes - * @param {object} options - * @param {object} fieldMappedAttributes - * - * @private - */ - bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { - options = options || {}; - fieldMappedAttributes = fieldMappedAttributes || {}; - - const tuples = []; - const serials = {}; - const allAttributes = []; - let onDuplicateKeyUpdate = ''; - - for (const fieldValueHash of fieldValueHashes) { - _.forOwn(fieldValueHash, (value, key) => { - if (!allAttributes.includes(key)) { - allAttributes.push(key); - } - if ( - fieldMappedAttributes[key] - && fieldMappedAttributes[key].autoIncrement === true - ) { - serials[key] = true; - } - }); - } - - for (const fieldValueHash of fieldValueHashes) { - const values = allAttributes.map(key => { - if ( - this._dialect.supports.bulkDefault - && serials[key] === true - ) { - return fieldValueHash[key] || 'DEFAULT'; - } - - return this.escape(fieldValueHash[key], fieldMappedAttributes[key], { context: 'INSERT' }); - }); - - tuples.push(`(${values.join(',')})`); - } - - 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 { // mysql / maria - const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`); - onDuplicateKeyUpdate = `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`; - } - } - - const ignoreDuplicates = options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : ''; - const attributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(','); - const onConflictDoNothing = options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : ''; - let returning = ''; - - if (this._dialect.supports.returnValues && options.returning) { - const returnValues = this.generateReturnValues(fieldMappedAttributes, options); - - returning += returnValues.returningFragment; - } - - return Utils.joinSQLFragments([ - 'INSERT', - ignoreDuplicates, - 'INTO', - this.quoteTable(tableName), - `(${attributes})`, - 'VALUES', - tuples.join(','), - onDuplicateKeyUpdate, - onConflictDoNothing, - returning, - ';' - ]); - } - - /** - * 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 - * - * @private - */ - updateQuery(tableName, attrValueHash, where, options, attributes) { - options = options || {}; - _.defaults(options, this.options); - - attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); - - const values = []; - const bind = []; - const modelAttributeMap = {}; - let outputFragment = ''; - let tmpTable = ''; // tmpTable declaration for trigger - let suffix = ''; - - if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) { - // Not currently supported with search path (requires output of multiple queries) - options.bindParam = false; - } - - const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam; - - if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) { - if (this.dialect !== 'mssql') { - suffix = ` LIMIT ${this.escape(options.limit)} `; - } - } - - if (this._dialect.supports.returnValues && options.returning) { - const returnValues = this.generateReturnValues(attributes, options); - - suffix += returnValues.returningFragment; - tmpTable = returnValues.tmpTable || ''; - outputFragment = returnValues.outputFragment || ''; - - // ensure that the return output is properly mapped to model fields. - if (!this._dialect.supports.returnValues.output && options.returning) { - options.mapToModel = true; - } - } - - if (attributes) { - _.each(attributes, (attribute, key) => { - modelAttributeMap[key] = attribute; - if (attribute.field) { - modelAttributeMap[attribute.field] = attribute; - } - }); - } - - for (const key in attrValueHash) { - if (modelAttributeMap && modelAttributeMap[key] && - modelAttributeMap[key].autoIncrement === true && - !this._dialect.supports.autoIncrement.update) { - // not allowed to update identity column - continue; - } - - const value = attrValueHash[key]; - - if (value instanceof Utils.SequelizeMethod || options.bindParam === false) { - values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })}`); - } else { - values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }, bindParam)}`); - } - } - - const whereOptions = { ...options, bindParam }; - - if (values.length === 0) { - return ''; - } - - const query = `${tmpTable}UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where, whereOptions)}${suffix}`.trim(); - // Used by Postgres upsertQuery and calls to here with options.exception set to true - const result = { query }; - if (options.bindParam !== false) { - result.bind = bind; - } - return result; - } - - /** - * 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} 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, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { - options = options || {}; - _.defaults(options, { returning: true }); - - extraAttributesToBeUpdated = Utils.removeNullValuesFromHash(extraAttributesToBeUpdated, this.options.omitNull); - - let outputFragment = ''; - let returningFragment = ''; - - if (this._dialect.supports.returnValues && options.returning) { - const returnValues = this.generateReturnValues(null, options); - - outputFragment = returnValues.outputFragment; - returningFragment = returnValues.returningFragment; - } - - const updateSetSqlFragments = []; - for (const field in incrementAmountsByField) { - const incrementAmount = incrementAmountsByField[field]; - const quotedField = this.quoteIdentifier(field); - const escapedAmount = this.escape(incrementAmount); - updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`); - } - for (const field in extraAttributesToBeUpdated) { - const newValue = extraAttributesToBeUpdated[field]; - const quotedField = this.quoteIdentifier(field); - const escapedValue = this.escape(newValue); - updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); - } - - return Utils.joinSQLFragments([ - 'UPDATE', - this.quoteTable(tableName), - 'SET', - updateSetSqlFragments.join(','), - outputFragment, - this.whereQuery(where), - returningFragment - ]); - } - - /* - Returns an add index query. - Parameters: - - tableName -> Name of an existing table, possibly with schema. - - options: - - type: UNIQUE|FULLTEXT|SPATIAL - - name: The name of the index. Default is __ - - fields: An array of attributes as string or as hash. - If the attribute is a hash, it must have the following content: - - name: The name of the attribute/column - - length: An integer. Optional - - order: 'ASC' or 'DESC'. Optional - - parser - - using - - operator - - concurrently: Pass CONCURRENT so other operations run while the index is created - - rawTablename, the name of the table, without schema. Used to create the name of the index - @private - */ - addIndexQuery(tableName, attributes, options, rawTablename) { - options = options || {}; - - if (!Array.isArray(attributes)) { - options = attributes; - attributes = undefined; - } else { - options.fields = attributes; - } - - options.prefix = options.prefix || rawTablename || tableName; - if (options.prefix && typeof options.prefix === 'string') { - options.prefix = options.prefix.replace(/\./g, '_'); - options.prefix = options.prefix.replace(/("|')/g, ''); - } - - const fieldsSql = options.fields.map(field => { - if (field instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(field); - } - if (typeof field === 'string') { - field = { - name: field - }; - } - let result = ''; - - if (field.attribute) { - field.name = field.attribute; - } - - if (!field.name) { - throw new Error(`The following index field has no name: ${util.inspect(field)}`); - } - - result += this.quoteIdentifier(field.name); - - if (this._dialect.supports.index.collate && field.collate) { - 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})`; - } - - if (field.order) { - result += ` ${field.order}`; - } - - return result; - }); - - if (!options.name) { - // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) - // All calls that go through sequelize should already have a name - options = Utils.nameIndex(options, options.prefix); - } - - options = Model._conformIndex(options); - - if (!this._dialect.supports.index.type) { - delete options.type; - } - - if (options.where) { - options.where = this.whereQuery(options.where); - } - - if (typeof tableName === 'string') { - tableName = this.quoteIdentifiers(tableName); - } else { - tableName = this.quoteTable(tableName); - } - - const concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined; - let ind; - if (this._dialect.supports.indexViaAlter) { - ind = [ - 'ALTER TABLE', - tableName, - concurrently, - 'ADD' - ]; - } else { - ind = ['CREATE']; - } - - ind = ind.concat( - options.unique ? 'UNIQUE' : '', - options.type, 'INDEX', - !this._dialect.supports.indexViaAlter ? concurrently : undefined, - this.quoteIdentifiers(options.name), - 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(', ')})`, - this._dialect.supports.index.parser && options.parser ? `WITH PARSER ${options.parser}` : undefined, - this._dialect.supports.index.where && options.where ? options.where : undefined - ); - - return _.compact(ind).join(' '); - } - - addConstraintQuery(tableName, options) { - if (typeof tableName === 'string') { - tableName = this.quoteIdentifiers(tableName); - } else { - tableName = this.quoteTable(tableName); - } - - return Utils.joinSQLFragments([ - 'ALTER TABLE', - tableName, - 'ADD', - this.getConstraintSnippet(tableName, options || {}), - ';' - ]); - } - - getConstraintSnippet(tableName, options) { - let constraintSnippet, constraintName; - - const fieldsSql = options.fields.map(field => { - if (typeof field === 'string') { - return this.quoteIdentifier(field); - } - if (field instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(field); - } - if (field.attribute) { - field.name = field.attribute; - } - - if (!field.name) { - throw new Error(`The following index field has no name: ${field}`); - } - - return this.quoteIdentifier(field.name); - }); - - const fieldsSqlQuotedString = fieldsSql.join(', '); - const fieldsSqlString = fieldsSql.join('_'); - - switch (options.type.toUpperCase()) { - case 'UNIQUE': - constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_uk`); - constraintSnippet = `CONSTRAINT ${constraintName} UNIQUE (${fieldsSqlQuotedString})`; - break; - case 'CHECK': - options.where = this.whereItemsQuery(options.where); - constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_ck`); - constraintSnippet = `CONSTRAINT ${constraintName} CHECK (${options.where})`; - break; - case 'DEFAULT': - if (options.defaultValue === undefined) { - throw new Error('Default value must be specifed for DEFAULT CONSTRAINT'); - } - - if (this._dialect.name !== 'mssql') { - throw new Error('Default constraints are supported only for MSSQL dialect.'); - } - - constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_df`); - constraintSnippet = `CONSTRAINT ${constraintName} DEFAULT (${this.escape(options.defaultValue)}) FOR ${fieldsSql[0]}`; - break; - case 'PRIMARY KEY': - constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_pk`); - constraintSnippet = `CONSTRAINT ${constraintName} PRIMARY KEY (${fieldsSqlQuotedString})`; - break; - case 'FOREIGN KEY': - const references = options.references; - 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 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) { - constraintSnippet += ` ON UPDATE ${options.onUpdate.toUpperCase()}`; - } - if (options.onDelete) { - constraintSnippet += ` ON DELETE ${options.onDelete.toUpperCase()}`; - } - 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; - } - - removeConstraintQuery(tableName, constraintName) { - if (typeof tableName === 'string') { - tableName = this.quoteIdentifiers(tableName); - } else { - tableName = this.quoteTable(tableName); - } - - return Utils.joinSQLFragments([ - 'ALTER TABLE', - tableName, - 'DROP CONSTRAINT', - this.quoteIdentifiers(constraintName) - ]); - } - - /* - Quote an object based on its type. This is a more general version of quoteIdentifiers - Strings: should proxy to quoteIdentifiers - Arrays: - * Expects array in the form: [ (optional), (optional),... String, String (optional)] - Each can be a model, or an object {model: Model, as: String}, matching include, or an - association object, or the name of an association. - * Zero or more models can be included in the array and are used to trace a path through the tree of - included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL - and quotes it. - * If a single string is appended to end of array, it is quoted. - If two strings appended, the 1st string is quoted, the 2nd string unquoted. - Objects: - * If raw is set, that value should be returned verbatim, without quoting - * If fn is set, the string should start with the value of fn, starting paren, followed by - the values of cols (which is assumed to be an array), quoted and joined with ', ', - unless they are themselves objects - * If direction is set, should be prepended - - Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could - potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values) - @private - */ - quote(collection, parent, connector) { - // init - const validOrderOptions = [ - 'ASC', - 'DESC', - 'ASC NULLS LAST', - 'DESC NULLS LAST', - 'ASC NULLS FIRST', - 'DESC NULLS FIRST', - 'NULLS FIRST', - 'NULLS LAST' - ]; - - // default - connector = connector || '.'; - - // just quote as identifiers if string - if (typeof collection === 'string') { - return this.quoteIdentifiers(collection); - } - if (Array.isArray(collection)) { - // iterate through the collection and mutate objects into associations - collection.forEach((item, index) => { - const previous = collection[index - 1]; - let previousAssociation; - let previousModel; - - // set the previous as the parent when previous is undefined or the target of the association - if (!previous && parent !== undefined) { - previousModel = parent; - } else if (previous && previous instanceof Association) { - previousAssociation = previous; - previousModel = previous.target; - } - - // if the previous item is a model, then attempt getting an association - if (previousModel && previousModel.prototype instanceof Model) { - let model; - let as; - - if (typeof item === 'function' && item.prototype instanceof Model) { - // set - model = item; - } else if (_.isPlainObject(item) && item.model && item.model.prototype instanceof Model) { - // set - model = item.model; - as = item.as; - } - - if (model) { - // set the as to either the through name or the model name - if (!as && previousAssociation && previousAssociation instanceof Association && previousAssociation.through && previousAssociation.through.model === model) { - // get from previous association - item = new Association(previousModel, model, { - as: model.name - }); - } else { - // get association from previous model - item = previousModel.getAssociationForAlias(model, as); - - // attempt to use the model name if the item is still null - if (!item) { - item = previousModel.getAssociationForAlias(model, model.name); - } - } - - // make sure we have an association - if (!(item instanceof Association)) { - throw new Error(util.format('Unable to find a valid association for model, \'%s\'', model.name)); - } - } - } - - if (typeof item === 'string') { - // get order index - const orderIndex = validOrderOptions.indexOf(item.toUpperCase()); - - // see if this is an order - if (index > 0 && orderIndex !== -1) { - item = this.sequelize.literal(` ${validOrderOptions[orderIndex]}`); - } else if (previousModel && previousModel.prototype instanceof Model) { - // only go down this path if we have preivous model and check only once - if (previousModel.associations !== undefined && previousModel.associations[item]) { - // convert the item to an association - item = previousModel.associations[item]; - } else if (previousModel.rawAttributes !== undefined && previousModel.rawAttributes[item] && item !== previousModel.rawAttributes[item].field) { - // convert the item attribute from its alias - item = previousModel.rawAttributes[item].field; - } else if ( - item.includes('.') - && previousModel.rawAttributes !== undefined - ) { - const itemSplit = item.split('.'); - - if (previousModel.rawAttributes[itemSplit[0]].type instanceof DataTypes.JSON) { - // just quote identifiers for now - const identifier = this.quoteIdentifiers(`${previousModel.name}.${previousModel.rawAttributes[itemSplit[0]].field}`); - - // get path - const path = itemSplit.slice(1); - - // extract path - item = this.jsonPathExtractionQuery(identifier, path); - - // literal because we don't want to append the model name when string - item = this.sequelize.literal(item); - } - } - } - } - - collection[index] = item; - }, this); - - // loop through array, adding table names of models to quoted - const collectionLength = collection.length; - const tableNames = []; - let item; - let i = 0; - - for (i = 0; i < collectionLength - 1; i++) { - item = collection[i]; - if (typeof item === 'string' || item._modelAttribute || item instanceof Utils.SequelizeMethod) { - break; - } else if (item instanceof Association) { - tableNames[i] = item.as; - } - } - - // start building sql - let sql = ''; - - if (i > 0) { - sql += `${this.quoteIdentifier(tableNames.join(connector))}.`; - } else if (typeof collection[0] === 'string' && parent) { - sql += `${this.quoteIdentifier(parent.name)}.`; - } - - // loop through everything past i and append to the sql - collection.slice(i).forEach(collectionItem => { - sql += this.quote(collectionItem, parent, connector); - }, this); - - return sql; - } - if (collection._modelAttribute) { - return `${this.quoteTable(collection.Model.name)}.${this.quoteIdentifier(collection.fieldName)}`; - } - if (collection instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(collection); - } - if (_.isPlainObject(collection) && collection.raw) { - // simple objects with raw is no longer supported - throw new Error('The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.'); - } - throw new Error(`Unknown structure passed to order / group: ${util.inspect(collection)}`); - } - - /** - * Split a list of identifiers by "." and quote each part - * - * @param {string} identifier - * @param {boolean} force - * - * @returns {string} - */ - quoteIdentifier(identifier, force) { - return QuoteHelper.quoteIdentifier(this.dialect, identifier, { - force, - quoteIdentifiers: this.options.quoteIdentifiers - }); - } - - quoteIdentifiers(identifiers) { - if (identifiers.includes('.')) { - identifiers = identifiers.split('.'); - - const head = identifiers.slice(0, identifiers.length - 1).join('->'); - const tail = identifiers[identifiers.length - 1]; - - return `${this.quoteIdentifier(head)}.${this.quoteIdentifier(tail)}`; - } - - return this.quoteIdentifier(identifiers); - } - - quoteAttribute(attribute, model) { - if (model && attribute in model.rawAttributes) { - return this.quoteIdentifier(attribute); - } - return this.quoteIdentifiers(attribute); - } - - /** - * Quote table name with optional alias and schema attribution - * - * @param {string|object} param table string or object - * @param {string|boolean} alias alias name - * - * @returns {string} - */ - quoteTable(param, alias) { - let table = ''; - - if (alias === true) { - alias = param.as || param.name || param; - } - - if (_.isObject(param)) { - if (this._dialect.supports.schemas) { - if (param.schema) { - table += `${this.quoteIdentifier(param.schema)}.`; - } - - table += this.quoteIdentifier(param.tableName); - } else { - if (param.schema) { - table += param.schema + (param.delimiter || '.'); - } - - table += param.tableName; - table = this.quoteIdentifier(table); - } - } else { - table = this.quoteIdentifier(param); - } - - if (alias) { - table += ` AS ${this.quoteIdentifier(alias)}`; - } - - return table; - } - - /* - Escape a value (e.g. a string, number or date) - @private - */ - escape(value, field, options) { - options = options || {}; - - if (value !== null && value !== undefined) { - if (value instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(value); - } - if (field && field.type) { - this.validate(value, field, options); - - if (field.type.stringify) { - // Users shouldn't have to worry about these args - just give them a function that takes a single arg - const simpleEscape = escVal => SqlString.escape(escVal, this.options.timezone, this.dialect); - - value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone, operation: options.operation }); - - if (field.type.escape === false) { - // The data-type already did the required escaping - return value; - } - } - } - } - return SqlString.escape(value, this.options.timezone, this.dialect); - } - - bindParam(bind) { - return value => { - bind.push(value); - return `$${bind.length}`; - }; - } - - /* - Returns a bind parameter representation of a value (e.g. a string, number or date) - @private - */ - format(value, field, options, bindParam) { - options = options || {}; - - if (value !== null && value !== undefined) { - if (value instanceof Utils.SequelizeMethod) { - throw new Error('Cannot pass SequelizeMethod as a bind parameter - use escape instead'); - } - if (field && field.type) { - this.validate(value, field, options); - - if (field.type.bindParam) { - return field.type.bindParam(value, { escape: _.identity, field, timezone: this.options.timezone, operation: options.operation, bindParam }); - } - } - } - - return bindParam(value); - } - - /* - Validate a value against a field specification - @private - */ - validate(value, field, options) { - if (this.typeValidation && field.type.validate && value) { - try { - if (options.isList && Array.isArray(value)) { - for (const item of value) { - field.type.validate(item, options); - } - } else { - field.type.validate(value, options); - } - } catch (error) { - if (error instanceof sequelizeError.ValidationError) { - error.errors.push(new sequelizeError.ValidationErrorItem( - error.message, - 'Validation error', - field.fieldName, - value, - null, - `${field.type.key} validator` - )); - } - - throw error; - } - } - } - - isIdentifierQuoted(identifier) { - return QuoteHelper.isIdentifierQuoted(identifier); - } - - /** - * Generates an SQL query that extract JSON property of given path. - * - * @param {string} column The JSON column - * @param {string|Array} [path] The path to extract (optional) - * @returns {string} The generated sql query - * @private - */ - jsonPathExtractionQuery(column, path) { - let paths = _.toPath(path); - let pathStr; - const quotedColumn = this.isIdentifierQuoted(column) - ? column - : this.quoteIdentifier(column); - - switch (this.dialect) { - case 'mysql': - case 'mariadb': - case 'sqlite': - /** - * Non digit sub paths need to be quoted as ECMAScript identifiers - * https://bugs.mysql.com/bug.php?id=81896 - */ - if (this.dialect === 'mysql') { - paths = paths.map(subPath => { - return /\D/.test(subPath) - ? Utils.addTicks(subPath, '"') - : subPath; - }); - } - - pathStr = this.escape(['$'] - .concat(paths) - .join('.') - .replace(/\.(\d+)(?:(?=\.)|$)/g, (__, digit) => `[${digit}]`)); - - if (this.dialect === 'sqlite') { - return `json_extract(${quotedColumn},${pathStr})`; - } - - return `json_unquote(json_extract(${quotedColumn},${pathStr}))`; - - case 'postgres': - pathStr = this.escape(`{${paths.join(',')}}`); - return `(${quotedColumn}#>>${pathStr})`; - - default: - throw new Error(`Unsupported ${this.dialect} for JSON operations`); - } - } - - /* - Returns a query for selecting elements in the table . - Options: - - attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: * - - where -> A hash with conditions (e.g. {name: 'foo'}) - OR an ID as integer - - order -> e.g. 'id DESC' - - group - - limit -> The maximum count you want to get. - - offset -> An offset value to start from. Only useable with limit! - @private - */ - selectQuery(tableName, options, model) { - options = options || {}; - const limit = options.limit; - const mainQueryItems = []; - const subQueryItems = []; - const subQuery = options.subQuery === undefined ? limit && options.hasMultiAssociation : options.subQuery; - const attributes = { - main: options.attributes && options.attributes.slice(), - subQuery: null - }; - const mainTable = { - name: tableName, - quotedName: null, - as: null, - model - }; - const topLevelInfo = { - names: mainTable, - options, - subQuery - }; - let mainJoinQueries = []; - let subJoinQueries = []; - let query; - - // Aliases can be passed through subqueries and we don't want to reset them - if (this.options.minifyAliases && !options.aliasesMapping) { - options.aliasesMapping = new Map(); - options.aliasesByTable = {}; - options.includeAliases = new Map(); - } - - // resolve table name options - if (options.tableAs) { - mainTable.as = this.quoteIdentifier(options.tableAs); - } else if (!Array.isArray(mainTable.name) && mainTable.model) { - mainTable.as = this.quoteIdentifier(mainTable.model.name); - } - - mainTable.quotedName = !Array.isArray(mainTable.name) ? this.quoteTable(mainTable.name) : tableName.map(t => { - return Array.isArray(t) ? this.quoteTable(t[0], t[1]) : this.quoteTable(t, true); - }).join(', '); - - if (subQuery && attributes.main) { - for (const keyAtt of mainTable.model.primaryKeyAttributes) { - // Check if mainAttributes contain the primary key of the model either as a field or an aliased field - if (!attributes.main.some(attr => keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1])) { - attributes.main.push(mainTable.model.rawAttributes[keyAtt].field ? [keyAtt, mainTable.model.rawAttributes[keyAtt].field] : keyAtt); - } - } - } - - attributes.main = this.escapeAttributes(attributes.main, options, mainTable.as); - attributes.main = attributes.main || (options.include ? [`${mainTable.as}.*`] : ['*']); - - // If subquery, we add the mainAttributes to the subQuery and set the mainAttributes to select * from subquery - if (subQuery || options.groupedLimit) { - // We need primary keys - attributes.subQuery = attributes.main; - attributes.main = [`${mainTable.as || mainTable.quotedName}.*`]; - } - - if (options.include) { - for (const include of options.include) { - if (include.separate) { - continue; - } - const joinQueries = this.generateInclude(include, { externalAs: mainTable.as, internalAs: mainTable.as }, topLevelInfo); - - subJoinQueries = subJoinQueries.concat(joinQueries.subQuery); - mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); - - if (joinQueries.attributes.main.length > 0) { - attributes.main = _.uniq(attributes.main.concat(joinQueries.attributes.main)); - } - if (joinQueries.attributes.subQuery.length > 0) { - attributes.subQuery = _.uniq(attributes.subQuery.concat(joinQueries.attributes.subQuery)); - } - } - } - - if (subQuery) { - subQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.subQuery, mainTable.quotedName, mainTable.as)); - subQueryItems.push(subJoinQueries.join('')); - } else { - if (options.groupedLimit) { - if (!mainTable.as) { - mainTable.as = mainTable.quotedName; - } - const where = { ...options.where }; - let groupedLimitOrder, - whereKey, - include, - groupedTableName = mainTable.as; - - if (typeof options.groupedLimit.on === 'string') { - whereKey = options.groupedLimit.on; - } else if (options.groupedLimit.on instanceof HasMany) { - whereKey = options.groupedLimit.on.foreignKeyField; - } - - if (options.groupedLimit.on instanceof BelongsToMany) { - // BTM includes needs to join the through table on to check ID - groupedTableName = options.groupedLimit.on.manyFromSource.as; - const groupedLimitOptions = Model._validateIncludedElements({ - include: [{ - association: options.groupedLimit.on.manyFromSource, - duplicating: false, // The UNION'ed query may contain duplicates, but each sub-query cannot - required: true, - where: { - [Op.placeholder]: true, - ...options.groupedLimit.through && options.groupedLimit.through.where - } - }], - model - }); - - // Make sure attributes from the join table are mapped back to models - options.hasJoin = true; - options.hasMultiAssociation = true; - options.includeMap = Object.assign(groupedLimitOptions.includeMap, options.includeMap); - options.includeNames = groupedLimitOptions.includeNames.concat(options.includeNames || []); - include = groupedLimitOptions.include; - - if (Array.isArray(options.order)) { - // We need to make sure the order by attributes are available to the parent query - options.order.forEach((order, i) => { - if (Array.isArray(order)) { - order = order[0]; - } - - let alias = `subquery_order_${i}`; - options.attributes.push([order, alias]); - - // We don't want to prepend model name when we alias the attributes, so quote them here - alias = this.sequelize.literal(this.quote(alias)); - - if (Array.isArray(options.order[i])) { - options.order[i][0] = alias; - } else { - options.order[i] = alias; - } - }); - groupedLimitOrder = options.order; - } - } else { - // Ordering is handled by the subqueries, so ordering the UNION'ed result is not needed - groupedLimitOrder = options.order; - delete options.order; - where[Op.placeholder] = true; - } - - // Caching the base query and splicing the where part into it is consistently > twice - // as fast than generating from scratch each time for values.length >= 5 - const baseQuery = `SELECT * FROM (${this.selectQuery( - tableName, - { - attributes: options.attributes, - offset: options.offset, - limit: options.groupedLimit.limit, - order: groupedLimitOrder, - aliasesMapping: options.aliasesMapping, - aliasesByTable: options.aliasesByTable, - where, - include, - model - }, - model - ).replace(/;$/, '')}) AS sub`; // Every derived table must have its own alias - const placeHolder = this.whereItemQuery(Op.placeholder, true, { model }); - const splicePos = baseQuery.indexOf(placeHolder); - - mainQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.main, `(${ - options.groupedLimit.values.map(value => { - let groupWhere; - if (whereKey) { - groupWhere = { - [whereKey]: value - }; - } - if (include) { - groupWhere = { - [options.groupedLimit.on.foreignIdentifierField]: value - }; - } - - return Utils.spliceStr(baseQuery, splicePos, placeHolder.length, this.getWhereConditions(groupWhere, groupedTableName)); - }).join( - this._dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ' - ) - })`, mainTable.as)); - } else { - mainQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.main, mainTable.quotedName, mainTable.as)); - } - - mainQueryItems.push(mainJoinQueries.join('')); - } - - // Add WHERE to sub or main query - if (Object.prototype.hasOwnProperty.call(options, 'where') && !options.groupedLimit) { - options.where = this.getWhereConditions(options.where, mainTable.as || tableName, model, options); - if (options.where) { - if (subQuery) { - subQueryItems.push(` WHERE ${options.where}`); - } else { - mainQueryItems.push(` WHERE ${options.where}`); - // Walk the main query to update all selects - mainQueryItems.forEach((value, key) => { - if (value.startsWith('SELECT')) { - mainQueryItems[key] = this.selectFromTableFragment(options, model, attributes.main, mainTable.quotedName, mainTable.as, options.where); - } - }); - } - } - } - - // Add GROUP BY to sub or main query - 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 && options.group) { - subQueryItems.push(` GROUP BY ${options.group}`); - } else if (options.group) { - mainQueryItems.push(` GROUP BY ${options.group}`); - } - } - - // Add HAVING to sub or main query - if (Object.prototype.hasOwnProperty.call(options, 'having')) { - options.having = this.getWhereConditions(options.having, tableName, model, options, false); - if (options.having) { - if (subQuery) { - subQueryItems.push(` HAVING ${options.having}`); - } else { - mainQueryItems.push(` HAVING ${options.having}`); - } - } - } - - // Add ORDER to sub or main query - if (options.order) { - const orders = this.getQueryOrders(options, model, subQuery); - if (orders.mainQueryOrder.length) { - mainQueryItems.push(` ORDER BY ${orders.mainQueryOrder.join(', ')}`); - } - if (orders.subQueryOrder.length) { - subQueryItems.push(` ORDER BY ${orders.subQueryOrder.join(', ')}`); - } - } - - // Add LIMIT, OFFSET to sub or main query - const limitOrder = this.addLimitAndOffset(options, mainTable.model); - if (limitOrder && !options.groupedLimit) { - if (subQuery) { - subQueryItems.push(limitOrder); - } else { - mainQueryItems.push(limitOrder); - } - } - - 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(''); - } - - if (options.lock && this._dialect.supports.lock) { - let lock = options.lock; - if (typeof options.lock === 'object') { - lock = options.lock.level; - } - if (this._dialect.supports.lockKey && (lock === 'KEY SHARE' || lock === 'NO KEY UPDATE')) { - query += ` FOR ${lock}`; - } else if (lock === 'SHARE') { - query += ` ${this._dialect.supports.forShare}`; - } else { - query += ' FOR UPDATE'; - } - if (this._dialect.supports.lockOf && options.lock.of && options.lock.of.prototype instanceof Model) { - query += ` OF ${this.quoteTable(options.lock.of.name)}`; - } - if (this._dialect.supports.skipLocked && options.skipLocked) { - query += ' SKIP LOCKED'; - } - } - - return `${query};`; - } - - aliasGrouping(field, model, tableName, options) { - const src = Array.isArray(field) ? field[0] : field; - - return this.quote(this._getAliasForField(tableName, src, options) || src, model); - } - - escapeAttributes(attributes, options, mainTableAs) { - return attributes && attributes.map(attr => { - let addTable = true; - - if (attr instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(attr); - } - if (Array.isArray(attr)) { - if (attr.length !== 2) { - throw new Error(`${JSON.stringify(attr)} is not a valid attribute definition. Please use the following format: ['attribute definition', 'alias']`); - } - attr = attr.slice(); - - if (attr[0] instanceof Utils.SequelizeMethod) { - attr[0] = this.handleSequelizeMethod(attr[0]); - addTable = false; - } else if (!attr[0].includes('(') && !attr[0].includes(')')) { - attr[0] = this.quoteIdentifier(attr[0]); - } else { - deprecations.noRawAttributes(); - } - let alias = attr[1]; - - if (this.options.minifyAliases) { - alias = this._getMinifiedAlias(alias, mainTableAs, options); - } - - attr = [attr[0], this.quoteIdentifier(alias)].join(' AS '); - } else { - attr = !attr.includes(Utils.TICK_CHAR) && !attr.includes('"') - ? this.quoteAttribute(attr, options.model) - : this.escape(attr); - } - if (!_.isEmpty(options.include) && !attr.includes('.') && addTable) { - attr = `${mainTableAs}.${attr}`; - } - - return attr; - }); - } - - generateInclude(include, parentTableName, topLevelInfo) { - const joinQueries = { - mainQuery: [], - subQuery: [] - }; - const mainChildIncludes = []; - const subChildIncludes = []; - let requiredMismatch = false; - const includeAs = { - internalAs: include.as, - externalAs: include.as - }; - const attributes = { - main: [], - subQuery: [] - }; - let joinQuery; - - topLevelInfo.options.keysEscaped = true; - - if (topLevelInfo.names.name !== parentTableName.externalAs && topLevelInfo.names.as !== parentTableName.externalAs) { - includeAs.internalAs = `${parentTableName.internalAs}->${include.as}`; - includeAs.externalAs = `${parentTableName.externalAs}.${include.as}`; - } - - // includeIgnoreAttributes is used by aggregate functions - if (topLevelInfo.options.includeIgnoreAttributes !== false) { - include.model._expandAttributes(include); - Utils.mapFinderOptions(include, include.model); - - const includeAttributes = include.attributes.map(attr => { - let attrAs = attr; - let verbatim = false; - - if (Array.isArray(attr) && attr.length === 2) { - if (attr[0] instanceof Utils.SequelizeMethod && ( - attr[0] instanceof Utils.Literal || - attr[0] instanceof Utils.Cast || - attr[0] instanceof Utils.Fn - )) { - verbatim = true; - } - - attr = attr.map(attr => attr instanceof Utils.SequelizeMethod ? this.handleSequelizeMethod(attr) : attr); - - attrAs = attr[1]; - attr = attr[0]; - } - if (attr instanceof Utils.Literal) { - return attr.val; // We trust the user to rename the field correctly - } - if (attr instanceof Utils.Cast || attr instanceof Utils.Fn) { - throw new Error( - 'Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. ' + - 'This means the attribute will not be added to the returned instance' - ); - } - - let prefix; - if (verbatim === true) { - prefix = attr; - } else if (/#>>|->>/.test(attr)) { - prefix = `(${this.quoteIdentifier(includeAs.internalAs)}.${attr.replace(/\(|\)/g, '')})`; - } else if (/json_extract\(/.test(attr)) { - prefix = attr.replace(/json_extract\(/i, `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.`); - } else { - prefix = `${this.quoteIdentifier(includeAs.internalAs)}.${this.quoteIdentifier(attr)}`; - } - let alias = `${includeAs.externalAs}.${attrAs}`; - - if (this.options.minifyAliases) { - alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options); - } - - return Utils.joinSQLFragments([ - prefix, - 'AS', - this.quoteIdentifier(alias, true) - ]); - }); - if (include.subQuery && topLevelInfo.subQuery) { - for (const attr of includeAttributes) { - attributes.subQuery.push(attr); - } - } else { - for (const attr of includeAttributes) { - attributes.main.push(attr); - } - } - } - - //through - if (include.through) { - joinQuery = this.generateThroughJoin(include, includeAs, parentTableName.internalAs, topLevelInfo); - } else { - this._generateSubQueryFilter(include, includeAs, topLevelInfo); - joinQuery = this.generateJoin(include, topLevelInfo); - } - - // handle possible new attributes created in join - if (joinQuery.attributes.main.length > 0) { - attributes.main = attributes.main.concat(joinQuery.attributes.main); - } - - if (joinQuery.attributes.subQuery.length > 0) { - attributes.subQuery = attributes.subQuery.concat(joinQuery.attributes.subQuery); - } - - if (include.include) { - for (const childInclude of include.include) { - if (childInclude.separate || childInclude._pseudo) { - continue; - } - - const childJoinQueries = this.generateInclude(childInclude, includeAs, topLevelInfo); - - if (include.required === false && childInclude.required === true) { - requiredMismatch = true; - } - // if the child is a sub query we just give it to the - if (childInclude.subQuery && topLevelInfo.subQuery) { - subChildIncludes.push(childJoinQueries.subQuery); - } - if (childJoinQueries.mainQuery) { - mainChildIncludes.push(childJoinQueries.mainQuery); - } - if (childJoinQueries.attributes.main.length > 0) { - attributes.main = attributes.main.concat(childJoinQueries.attributes.main); - } - if (childJoinQueries.attributes.subQuery.length > 0) { - attributes.subQuery = attributes.subQuery.concat(childJoinQueries.attributes.subQuery); - } - } - } - - if (include.subQuery && topLevelInfo.subQuery) { - if (requiredMismatch && subChildIncludes.length > 0) { - joinQueries.subQuery.push(` ${joinQuery.join} ( ${joinQuery.body}${subChildIncludes.join('')} ) ON ${joinQuery.condition}`); - } else { - joinQueries.subQuery.push(` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`); - if (subChildIncludes.length > 0) { - joinQueries.subQuery.push(subChildIncludes.join('')); - } - } - joinQueries.mainQuery.push(mainChildIncludes.join('')); - } else { - if (requiredMismatch && mainChildIncludes.length > 0) { - joinQueries.mainQuery.push(` ${joinQuery.join} ( ${joinQuery.body}${mainChildIncludes.join('')} ) ON ${joinQuery.condition}`); - } else { - joinQueries.mainQuery.push(` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`); - if (mainChildIncludes.length > 0) { - joinQueries.mainQuery.push(mainChildIncludes.join('')); - } - } - joinQueries.subQuery.push(subChildIncludes.join('')); - } - - return { - mainQuery: joinQueries.mainQuery.join(''), - subQuery: joinQueries.subQuery.join(''), - attributes - }; - } - - _getMinifiedAlias(alias, tableName, options) { - // We do not want to re-alias in case of a subquery - if (options.aliasesByTable[`${tableName}${alias}`]) { - return options.aliasesByTable[`${tableName}${alias}`]; - } - - // Do not alias custom suquery_orders - if (alias.match(/subquery_order_[0-9]/)) { - return alias; - } - - const minifiedAlias = `_${options.aliasesMapping.size}`; - - options.aliasesMapping.set(minifiedAlias, alias); - options.aliasesByTable[`${tableName}${alias}`] = minifiedAlias; - - return minifiedAlias; - } - - _getAliasForField(tableName, field, options) { - if (this.options.minifyAliases) { - if (options.aliasesByTable[`${tableName}${field}`]) { - return options.aliasesByTable[`${tableName}${field}`]; - } - } - return null; - } - - generateJoin(include, topLevelInfo) { - const association = include.association; - const parent = include.parent; - const parentIsTop = !!parent && !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name; - let $parent; - let joinWhere; - /* Attributes for the left side */ - const left = association.source; - const attrLeft = association instanceof BelongsTo ? - association.identifier : - association.sourceKeyAttribute || left.primaryKeyAttribute; - const fieldLeft = association instanceof BelongsTo ? - association.identifierField : - left.rawAttributes[association.sourceKeyAttribute || left.primaryKeyAttribute].field; - let asLeft; - /* Attributes for the right side */ - const right = include.model; - const tableRight = right.getTableName(); - const fieldRight = association instanceof BelongsTo ? - right.rawAttributes[association.targetIdentifier || right.primaryKeyAttribute].field : - association.identifierField; - let asRight = include.as; - - while (($parent = $parent && $parent.parent || include.parent) && $parent.association) { - if (asLeft) { - asLeft = `${$parent.as}->${asLeft}`; - } else { - asLeft = $parent.as; - } - } - - if (!asLeft) asLeft = parent.as || parent.model.name; - else asRight = `${asLeft}->${asRight}`; - - let joinOn = `${this.quoteTable(asLeft)}.${this.quoteIdentifier(fieldLeft)}`; - const subqueryAttributes = []; - - if (topLevelInfo.options.groupedLimit && parentIsTop || topLevelInfo.subQuery && include.parent.subQuery && !include.subQuery) { - if (parentIsTop) { - // The main model attributes is not aliased to a prefix - const tableName = this.quoteTable(parent.as || parent.model.name); - - // Check for potential aliased JOIN condition - joinOn = this._getAliasForField(tableName, attrLeft, topLevelInfo.options) || `${tableName}.${this.quoteIdentifier(attrLeft)}`; - - if (topLevelInfo.subQuery) { - subqueryAttributes.push(`${tableName}.${this.quoteIdentifier(fieldLeft)}`); - } - } else { - const joinSource = `${asLeft.replace(/->/g, '.')}.${attrLeft}`; - - // Check for potential aliased JOIN condition - joinOn = this._getAliasForField(asLeft, joinSource, topLevelInfo.options) || this.quoteIdentifier(joinSource); - } - } - - joinOn += ` = ${this.quoteIdentifier(asRight)}.${this.quoteIdentifier(fieldRight)}`; - - if (include.on) { - joinOn = this.whereItemsQuery(include.on, { - prefix: this.sequelize.literal(this.quoteIdentifier(asRight)), - model: include.model - }); - } - - if (include.where) { - joinWhere = this.whereItemsQuery(include.where, { - prefix: this.sequelize.literal(this.quoteIdentifier(asRight)), - model: include.model - }); - if (joinWhere) { - if (include.or) { - joinOn += ` OR ${joinWhere}`; - } else { - joinOn += ` AND ${joinWhere}`; - } - } - } - - 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), - condition: joinOn, - attributes: { - main: [], - subQuery: subqueryAttributes - } - }; - } - - /** - * Returns the SQL fragments to handle returning the attributes from an insert/update query. - * - * @param {object} modelAttributes An object with the model attributes. - * @param {object} options An object with options. - * - * @private - */ - generateReturnValues(modelAttributes, options) { - const returnFields = []; - const returnTypes = []; - let outputFragment = ''; - let returningFragment = ''; - let tmpTable = ''; - - if (Array.isArray(options.returning)) { - returnFields.push(...options.returning.map(field => this.quoteIdentifier(field))); - } else if (modelAttributes) { - _.each(modelAttributes, attribute => { - if (!(attribute.type instanceof DataTypes.VIRTUAL)) { - returnFields.push(this.quoteIdentifier(attribute.field)); - returnTypes.push(attribute.type); - } - }); - } - - if (_.isEmpty(returnFields)) { - returnFields.push('*'); - } - - if (this._dialect.supports.returnValues.returning) { - returningFragment = ` RETURNING ${returnFields.join(',')}`; - } else if (this._dialect.supports.returnValues.output) { - outputFragment = ` OUTPUT ${returnFields.map(field => `INSERTED.${field}`).join(',')}`; - - //To capture output rows when there is a trigger on MSSQL DB - if (options.hasTrigger && this._dialect.supports.tmpTableTrigger) { - const tmpColumns = returnFields.map((field, i) => `${field} ${returnTypes[i].toSql()}`); - - tmpTable = `DECLARE @tmp TABLE (${tmpColumns.join(',')}); `; - outputFragment += ' INTO @tmp'; - returningFragment = '; SELECT * FROM @tmp'; - } - } - - return { outputFragment, returnFields, returningFragment, tmpTable }; - } - - generateThroughJoin(include, includeAs, parentTableName, topLevelInfo) { - const through = include.through; - const throughTable = through.model.getTableName(); - const throughAs = `${includeAs.internalAs}->${through.as}`; - const externalThroughAs = `${includeAs.externalAs}.${through.as}`; - const throughAttributes = through.attributes.map(attr => { - let alias = `${externalThroughAs}.${Array.isArray(attr) ? attr[1] : attr}`; - - if (this.options.minifyAliases) { - alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options); - } - - 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; - const tableSource = parentTableName; - const identSource = association.identifierField; - const tableTarget = includeAs.internalAs; - const identTarget = association.foreignIdentifierField; - const attrTarget = association.targetKeyField; - - const joinType = include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN'; - let joinBody; - let joinCondition; - const attributes = { - main: [], - subQuery: [] - }; - let attrSource = association.sourceKey; - let sourceJoinOn; - let targetJoinOn; - let throughWhere; - let targetWhere; - - if (topLevelInfo.options.includeIgnoreAttributes !== false) { - // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery) - for (const attr of throughAttributes) { - attributes.main.push(attr); - } - } - - // Figure out if we need to use field or attribute - if (!topLevelInfo.subQuery) { - attrSource = association.sourceKeyField; - } - if (topLevelInfo.subQuery && !include.subQuery && !include.parent.subQuery && include.parent.model !== topLevelInfo.options.mainModel) { - attrSource = association.sourceKeyField; - } - - // Filter statement for left side of through - // Used by both join and subquery where - // If parent include was in a subquery need to join on the aliased attribute - if (topLevelInfo.subQuery && !include.subQuery && include.parent.subQuery && !parentIsTop) { - // If we are minifying aliases and our JOIN target has been minified, we need to use the alias instead of the original column name - const joinSource = this._getAliasForField(tableSource, `${tableSource}.${attrSource}`, topLevelInfo.options) || `${tableSource}.${attrSource}`; - - sourceJoinOn = `${this.quoteIdentifier(joinSource)} = `; - } else { - // If we are minifying aliases and our JOIN target has been minified, we need to use the alias instead of the original column name - const aliasedSource = this._getAliasForField(tableSource, attrSource, topLevelInfo.options) || attrSource; - - sourceJoinOn = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(aliasedSource)} = `; - } - sourceJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identSource)}`; - - // Filter statement for right side of through - // Used by both join and subquery where - targetJoinOn = `${this.quoteIdentifier(tableTarget)}.${this.quoteIdentifier(attrTarget)} = `; - targetJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identTarget)}`; - - if (through.where) { - 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}`; - } - } - - if (include.where || include.through.where) { - if (include.where) { - targetWhere = this.getWhereConditions(include.where, this.sequelize.literal(this.quoteIdentifier(includeAs.internalAs)), include.model, topLevelInfo.options); - if (targetWhere) { - joinCondition += ` AND ${targetWhere}`; - } - } - } - - this._generateSubQueryFilter(include, includeAs, topLevelInfo); - - return { - join: joinType, - body: joinBody, - condition: joinCondition, - attributes - }; - } - - /* - * Generates subQueryFilter - a select nested in the where clause of the subQuery. - * For a given include a query is generated that contains all the way from the subQuery - * table to the include table plus everything that's in required transitive closure of the - * given include. - */ - _generateSubQueryFilter(include, includeAs, topLevelInfo) { - if (!topLevelInfo.subQuery || !include.subQueryFilter) { - return; - } - - if (!topLevelInfo.options.where) { - topLevelInfo.options.where = {}; - } - let parent = include; - let child = include; - let nestedIncludes = this._getRequiredClosure(include).include; - let query; - - while ((parent = parent.parent)) { // eslint-disable-line - if (parent.parent && !parent.required) { - return; // only generate subQueryFilter if all the parents of this include are required - } - - if (parent.subQueryFilter) { - // the include is already handled as this parent has the include on its required closure - // skip to prevent duplicate subQueryFilter - return; - } - - nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }]; - child = parent; - } - - const topInclude = nestedIncludes[0]; - const topParent = topInclude.parent; - const topAssociation = topInclude.association; - topInclude.association = undefined; - - if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) { - query = this.selectQuery(topInclude.through.model.getTableName(), { - attributes: [topInclude.through.model.primaryKeyField], - include: Model._validateIncludedElements({ - model: topInclude.through.model, - include: [{ - association: topAssociation.toTarget, - required: true, - where: topInclude.where, - include: topInclude.include - }] - }).include, - model: topInclude.through.model, - where: { - [Op.and]: [ - this.sequelize.literal([ - `${this.quoteTable(topParent.model.name)}.${this.quoteIdentifier(topParent.model.primaryKeyField)}`, - `${this.quoteIdentifier(topInclude.through.model.name)}.${this.quoteIdentifier(topAssociation.identifierField)}` - ].join(' = ')), - topInclude.through.where - ] - }, - limit: 1, - includeIgnoreAttributes: false - }, topInclude.through.model); - } else { - const isBelongsTo = topAssociation.associationType === 'BelongsTo'; - const sourceField = isBelongsTo ? topAssociation.identifierField : topAssociation.sourceKeyField || topParent.model.primaryKeyField; - const targetField = isBelongsTo ? topAssociation.sourceKeyField || topInclude.model.primaryKeyField : topAssociation.identifierField; - - const join = [ - `${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(targetField)}`, - `${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(sourceField)}` - ].join(' = '); - - query = this.selectQuery(topInclude.model.getTableName(), { - attributes: [targetField], - include: Model._validateIncludedElements(topInclude).include, - model: topInclude.model, - where: { - [Op.and]: [ - topInclude.where, - { [Op.join]: this.sequelize.literal(join) } - ] - }, - limit: 1, - tableAs: topInclude.as, - includeIgnoreAttributes: false - }, topInclude.model); - } - - if (!topLevelInfo.options.where[Op.and]) { - topLevelInfo.options.where[Op.and] = []; - } - - topLevelInfo.options.where[`__${includeAs.internalAs}`] = this.sequelize.literal([ - '(', - query.replace(/;$/, ''), - ')', - 'IS NOT NULL' - ].join(' ')); - } - - /* - * For a given include hierarchy creates a copy of it where only the required includes - * are preserved. - */ - _getRequiredClosure(include) { - const copy = { ...include, attributes: [], include: [] }; - - if (Array.isArray(include.include)) { - copy.include = include.include - .filter(i => i.required) - .map(inc => this._getRequiredClosure(inc)); - } - - return copy; - } - - getQueryOrders(options, model, subQuery) { - const mainQueryOrder = []; - const subQueryOrder = []; - - if (Array.isArray(options.order)) { - for (let order of options.order) { - - // wrap if not array - if (!Array.isArray(order)) { - order = [order]; - } - - if ( - subQuery - && Array.isArray(order) - && order[0] - && !(order[0] instanceof Association) - && !(typeof order[0] === 'function' && order[0].prototype instanceof Model) - && !(typeof order[0].model === 'function' && order[0].model.prototype instanceof Model) - && !(typeof order[0] === 'string' && model && model.associations !== undefined && model.associations[order[0]]) - ) { - subQueryOrder.push(this.quote(order, model, '->')); - } - - if (subQuery) { - // Handle case where sub-query renames attribute we want to order by, - // see https://github.com/sequelize/sequelize/issues/8739 - const subQueryAttribute = options.attributes.find(a => Array.isArray(a) && a[0] === order[0] && a[1]); - if (subQueryAttribute) { - const modelName = this.quoteIdentifier(model.name); - - order[0] = new Utils.Col(this._getAliasForField(modelName, subQueryAttribute[1], options) || subQueryAttribute[1]); - } - } - - mainQueryOrder.push(this.quote(order, model, '->')); - } - } else if (options.order instanceof Utils.SequelizeMethod) { - const sql = this.quote(options.order, model, '->'); - if (subQuery) { - subQueryOrder.push(sql); - } - mainQueryOrder.push(sql); - } else { - throw new Error('Order must be type of array or instance of a valid sequelize method.'); - } - - 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) { - fragment += ` AS ${mainTableAs}`; - } - - if (options.indexHints && this._dialect.supports.indexHints) { - for (const hint of options.indexHints) { - if (IndexHints[hint.type]) { - fragment += ` ${IndexHints[hint.type]} INDEX (${hint.values.map(indexName => this.quoteIdentifiers(indexName)).join(',')})`; - } - } - } - - return fragment; - } - - /** - * Returns an SQL fragment for adding result constraints. - * - * @param {object} options An object with selectQuery options. - * @returns {string} The generated sql query. - * @private - */ - addLimitAndOffset(options) { - let fragment = ''; - - /* eslint-disable */ - if (options.offset != null && options.limit == null) { - fragment += ' LIMIT ' + this.escape(options.offset) + ', ' + 10000000000000; - } else if (options.limit != null) { - if (options.offset != null) { - fragment += ' LIMIT ' + this.escape(options.offset) + ', ' + this.escape(options.limit); - } else { - fragment += ' LIMIT ' + this.escape(options.limit); - } - } - /* eslint-enable */ - - return fragment; - } - - handleSequelizeMethod(smth, tableName, factory, options, prepend) { - let result; - - if (Object.prototype.hasOwnProperty.call(this.OperatorMap, smth.comparator)) { - smth.comparator = this.OperatorMap[smth.comparator]; - } - - if (smth instanceof Utils.Where) { - let value = smth.logic; - let key; - - if (smth.attribute instanceof Utils.SequelizeMethod) { - key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend); - } else { - key = `${this.quoteTable(smth.attribute.Model.name)}.${this.quoteIdentifier(smth.attribute.field || smth.attribute.fieldName)}`; - } - - if (value && value instanceof Utils.SequelizeMethod) { - value = this.getWhereConditions(value, tableName, factory, options, prepend); - - if (value === 'NULL') { - if (smth.comparator === '=') { - smth.comparator = 'IS'; - } - if (smth.comparator === '!=') { - smth.comparator = 'IS NOT'; - } - } - - return [key, value].join(` ${smth.comparator} `); - } - if (_.isPlainObject(value)) { - return this.whereItemQuery(smth.attribute, value, { - model: factory - }); - } - 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); - } - - if (value === 'NULL') { - if (smth.comparator === '=') { - smth.comparator = 'IS'; - } - if (smth.comparator === '!=') { - smth.comparator = 'IS NOT'; - } - } - - return [key, value].join(` ${smth.comparator} `); - } - if (smth instanceof Utils.Literal) { - return smth.val; - } - if (smth instanceof Utils.Cast) { - if (smth.val instanceof Utils.SequelizeMethod) { - result = this.handleSequelizeMethod(smth.val, tableName, factory, options, prepend); - } else if (_.isPlainObject(smth.val)) { - result = this.whereItemsQuery(smth.val); - } else { - result = this.escape(smth.val); - } - - 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(', ') - })`; - } - if (smth instanceof Utils.Col) { - if (Array.isArray(smth.col) && !factory) { - throw new Error('Cannot call Sequelize.col() with array outside of order / group clause'); - } - if (smth.col.startsWith('*')) { - return '*'; - } - return this.quote(smth.col, factory); - } - return smth.toString(this, factory); - } - - whereQuery(where, options) { - const query = this.whereItemsQuery(where, options); - if (query && query.length) { - return `WHERE ${query}`; - } - return ''; - } - - whereItemsQuery(where, options, binding) { - if ( - where === null || - where === undefined || - Utils.getComplexSize(where) === 0 - ) { - // NO OP - return ''; - } - - if (typeof where === 'string') { - throw new Error('Support for `{where: \'raw query\'}` has been removed.'); - } - - const items = []; - - binding = binding || 'AND'; - if (binding[0] !== ' ') binding = ` ${binding} `; - - if (_.isPlainObject(where)) { - Utils.getComplexKeys(where).forEach(prop => { - const item = where[prop]; - items.push(this.whereItemQuery(prop, item, options)); - }); - } else { - items.push(this.whereItemQuery(undefined, where, options)); - } - - return items.length && items.filter(item => item && item.length).join(binding) || ''; - } - - whereItemQuery(key, value, options = {}) { - if (value === undefined) { - throw new Error(`WHERE parameter "${key}" has invalid "undefined" value`); - } - - if (typeof key === 'string' && key.includes('.') && options.model) { - const keyParts = key.split('.'); - if (options.model.rawAttributes[keyParts[0]] && options.model.rawAttributes[keyParts[0]].type instanceof DataTypes.JSON) { - const tmp = {}; - const field = options.model.rawAttributes[keyParts[0]]; - _.set(tmp, keyParts.slice(1), value); - return this.whereItemQuery(field.field || keyParts[0], tmp, { field, ...options }); - } - } - - const field = this._findField(key, options); - const fieldType = field && field.type || options.type; - - const isPlainObject = _.isPlainObject(value); - const isArray = !isPlainObject && Array.isArray(value); - key = this.OperatorsAliasMap && this.OperatorsAliasMap[key] || key; - if (isPlainObject) { - value = this._replaceAliases(value); - } - const valueKeys = isPlainObject && Utils.getComplexKeys(value); - - if (key === undefined) { - if (typeof value === 'string') { - return value; - } - - if (isPlainObject && valueKeys.length === 1) { - return this.whereItemQuery(valueKeys[0], value[valueKeys[0]], options); - } - } - - if (value === null) { - const opValue = options.bindParam ? 'NULL' : this.escape(value, field); - return this._joinKeyValue(key, opValue, this.OperatorMap[Op.is], options.prefix); - } - - if (!value) { - const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field); - return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix); - } - - if (value instanceof Utils.SequelizeMethod && !(key !== undefined && value instanceof Utils.Fn)) { - return this.handleSequelizeMethod(value); - } - - // Convert where: [] to Op.and if possible, else treat as literal/replacements - if (key === undefined && isArray) { - if (Utils.canTreatArrayAsAnd(value)) { - key = Op.and; - } else { - throw new Error('Support for literal replacements in the `where` object has been removed.'); - } - } - - if (key === Op.or || key === Op.and || key === Op.not) { - return this._whereGroupBind(key, value, options); - } - - - if (value[Op.or]) { - return this._whereBind(this.OperatorMap[Op.or], key, value[Op.or], options); - } - - if (value[Op.and]) { - return this._whereBind(this.OperatorMap[Op.and], key, value[Op.and], options); - } - - if (isArray && fieldType instanceof DataTypes.ARRAY) { - const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field); - return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix); - } - - if (isPlainObject && fieldType instanceof DataTypes.JSON && options.json !== false) { - return this._whereJSON(key, value, options); - } - // If multiple keys we combine the different logic conditions - if (isPlainObject && valueKeys.length > 1) { - return this._whereBind(this.OperatorMap[Op.and], key, value, options); - } - - if (isArray) { - return this._whereParseSingleValueObject(key, field, Op.in, value, options); - } - if (isPlainObject) { - if (this.OperatorMap[valueKeys[0]]) { - return this._whereParseSingleValueObject(key, field, valueKeys[0], value[valueKeys[0]], options); - } - return this._whereParseSingleValueObject(key, field, this.OperatorMap[Op.eq], value, options); - } - - if (key === Op.placeholder) { - const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field); - return this._joinKeyValue(this.OperatorMap[key], opValue, this.OperatorMap[Op.eq], options.prefix); - } - - const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field); - return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix); - } - - _findField(key, options) { - if (options.field) { - return options.field; - } - - if (options.model && options.model.rawAttributes && options.model.rawAttributes[key]) { - return options.model.rawAttributes[key]; - } - - if (options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]) { - return options.model.fieldRawAttributesMap[key]; - } - } - - // OR/AND/NOT grouping logic - _whereGroupBind(key, value, options) { - const binding = key === Op.or ? this.OperatorMap[Op.or] : this.OperatorMap[Op.and]; - const outerBinding = key === Op.not ? 'NOT ' : ''; - - if (Array.isArray(value)) { - value = value.map(item => { - let itemQuery = this.whereItemsQuery(item, options, this.OperatorMap[Op.and]); - if (itemQuery && itemQuery.length && (Array.isArray(item) || _.isPlainObject(item)) && Utils.getComplexSize(item) > 1) { - itemQuery = `(${itemQuery})`; - } - return itemQuery; - }).filter(item => item && item.length); - - value = value.length && value.join(binding); - } else { - value = this.whereItemsQuery(value, options, binding); - } - // Op.or: [] should return no data. - // Op.not of no restriction should also return no data - if ((key === Op.or || key === Op.not) && !value) { - return '0 = 1'; - } - - return value ? `${outerBinding}(${value})` : undefined; - } - - _whereBind(binding, key, value, options) { - if (_.isPlainObject(value)) { - value = Utils.getComplexKeys(value).map(prop => { - const item = value[prop]; - return this.whereItemQuery(key, { [prop]: item }, options); - }); - } else { - value = value.map(item => this.whereItemQuery(key, item, options)); - } - - value = value.filter(item => item && item.length); - - return value.length ? `(${value.join(binding)})` : undefined; - } - - _whereJSON(key, value, options) { - const items = []; - let baseKey = this.quoteIdentifier(key); - if (options.prefix) { - if (options.prefix instanceof Utils.Literal) { - baseKey = `${this.handleSequelizeMethod(options.prefix)}.${baseKey}`; - } else { - baseKey = `${this.quoteTable(options.prefix)}.${baseKey}`; - } - } - - Utils.getOperators(value).forEach(op => { - const where = { - [op]: value[op] - }; - items.push(this.whereItemQuery(key, where, { ...options, json: false })); - }); - - _.forOwn(value, (item, prop) => { - this._traverseJSON(items, baseKey, prop, item, [prop]); - }); - - const result = items.join(this.OperatorMap[Op.and]); - return items.length > 1 ? `(${result})` : result; - } - - _traverseJSON(items, baseKey, prop, item, path) { - let cast; - - if (path[path.length - 1].includes('::')) { - const tmp = path[path.length - 1].split('::'); - cast = tmp[1]; - path[path.length - 1] = tmp[0]; - } - - const pathKey = this.jsonPathExtractionQuery(baseKey, path); - - if (_.isPlainObject(item)) { - Utils.getOperators(item).forEach(op => { - const value = this._toJSONValue(item[op]); - items.push(this.whereItemQuery(this._castKey(pathKey, value, cast), { [op]: value })); - }); - _.forOwn(item, (value, itemProp) => { - this._traverseJSON(items, baseKey, itemProp, value, path.concat([itemProp])); - }); - - return; - } - - item = this._toJSONValue(item); - items.push(this.whereItemQuery(this._castKey(pathKey, item, cast), { [Op.eq]: item })); - } - - _toJSONValue(value) { - return value; - } - - _castKey(key, value, cast, json) { - cast = cast || this._getJsonCast(Array.isArray(value) ? value[0] : value); - if (cast) { - return new Utils.Literal(this.handleSequelizeMethod(new Utils.Cast(new Utils.Literal(key), cast, json))); - } - - return new Utils.Literal(key); - } - - _getJsonCast(value) { - if (typeof value === 'number') { - return 'double precision'; - } - if (value instanceof Date) { - return 'timestamptz'; - } - if (typeof value === 'boolean') { - return 'boolean'; - } - return; - } - - _joinKeyValue(key, value, comparator, prefix) { - if (!key) { - return value; - } - if (comparator === undefined) { - throw new Error(`${key} and ${value} has no comparator`); - } - key = this._getSafeKey(key, prefix); - return [key, value].join(` ${comparator} `); - } - - _getSafeKey(key, prefix) { - if (key instanceof Utils.SequelizeMethod) { - key = this.handleSequelizeMethod(key); - return this._prefixKey(this.handleSequelizeMethod(key), prefix); - } - - if (Utils.isColString(key)) { - key = key.substr(1, key.length - 2).split('.'); - - if (key.length > 2) { - key = [ - // join the tables by -> to match out internal namings - key.slice(0, -1).join('->'), - key[key.length - 1] - ]; - } - - return key.map(identifier => this.quoteIdentifier(identifier)).join('.'); - } - - return this._prefixKey(this.quoteIdentifier(key), prefix); - } - - _prefixKey(key, prefix) { - if (prefix) { - if (prefix instanceof Utils.Literal) { - return [this.handleSequelizeMethod(prefix), key].join('.'); - } - - return [this.quoteTable(prefix), key].join('.'); - } - - return key; - } - - _whereParseSingleValueObject(key, field, prop, value, options) { - if (prop === Op.not) { - if (Array.isArray(value)) { - prop = Op.notIn; - } else if (value !== null && value !== true && value !== false) { - prop = Op.ne; - } - } - - let comparator = this.OperatorMap[prop] || this.OperatorMap[Op.eq]; - - switch (prop) { - case Op.in: - case Op.notIn: - if (value instanceof Utils.Literal) { - return this._joinKeyValue(key, value.val, comparator, options.prefix); - } - - if (value.length) { - return this._joinKeyValue(key, `(${value.map(item => this.escape(item, field)).join(', ')})`, comparator, options.prefix); - } - - if (comparator === this.OperatorMap[Op.in]) { - return this._joinKeyValue(key, '(NULL)', comparator, options.prefix); - } - - return ''; - case Op.any: - case Op.all: - comparator = `${this.OperatorMap[Op.eq]} ${comparator}`; - if (value[Op.values]) { - return this._joinKeyValue(key, `(VALUES ${value[Op.values].map(item => `(${this.escape(item)})`).join(', ')})`, comparator, options.prefix); - } - - return this._joinKeyValue(key, `(${this.escape(value, field)})`, comparator, options.prefix); - case Op.between: - case Op.notBetween: - return this._joinKeyValue(key, `${this.escape(value[0], field)} AND ${this.escape(value[1], field)}`, comparator, options.prefix); - case Op.raw: - throw new Error('The `$raw` where property is no longer supported. Use `sequelize.literal` instead.'); - case Op.col: - comparator = this.OperatorMap[Op.eq]; - value = value.split('.'); - - if (value.length > 2) { - value = [ - // join the tables by -> to match out internal namings - value.slice(0, -1).join('->'), - value[value.length - 1] - ]; - } - - return this._joinKeyValue(key, value.map(identifier => this.quoteIdentifier(identifier)).join('.'), comparator, options.prefix); - case Op.startsWith: - case Op.endsWith: - case Op.substring: - comparator = this.OperatorMap[Op.like]; - - 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 = { - acceptStrings: comparator.includes(this.OperatorMap[Op.like]) - }; - - if (_.isPlainObject(value)) { - if (value[Op.col]) { - return this._joinKeyValue(key, this.whereItemQuery(null, value), comparator, options.prefix); - } - if (value[Op.any]) { - escapeOptions.isList = true; - return this._joinKeyValue(key, `(${this.escape(value[Op.any], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.any]}`, options.prefix); - } - if (value[Op.all]) { - escapeOptions.isList = true; - return this._joinKeyValue(key, `(${this.escape(value[Op.all], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.all]}`, options.prefix); - } - } - - if (value === null && comparator === this.OperatorMap[Op.eq]) { - return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.is], options.prefix); - } - if (value === null && comparator === this.OperatorMap[Op.ne]) { - return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.not], options.prefix); - } - - return this._joinKeyValue(key, this.escape(value, field, escapeOptions), comparator, options.prefix); - } - - /* - Takes something and transforms it into values of a where condition. - @private - */ - getWhereConditions(smth, tableName, factory, options, prepend) { - const where = {}; - - if (Array.isArray(tableName)) { - tableName = tableName[0]; - if (Array.isArray(tableName)) { - tableName = tableName[1]; - } - } - - options = options || {}; - - if (prepend === undefined) { - prepend = true; - } - - if (smth && smth instanceof Utils.SequelizeMethod) { // Checking a property is cheaper than a lot of instanceof calls - return this.handleSequelizeMethod(smth, tableName, factory, options, prepend); - } - if (_.isPlainObject(smth)) { - return this.whereItemsQuery(smth, { - model: factory, - prefix: prepend && tableName, - type: options.type - }); - } - if (typeof smth === 'number') { - let primaryKeys = factory ? Object.keys(factory.primaryKeys) : []; - - if (primaryKeys.length > 0) { - // Since we're just a number, assume only the first key - primaryKeys = primaryKeys[0]; - } else { - primaryKeys = 'id'; - } - - where[primaryKeys] = smth; - - return this.whereItemsQuery(where, { - model: factory, - prefix: prepend && tableName - }); - } - if (typeof smth === 'string') { - return this.whereItemsQuery(smth, { - model: factory, - prefix: prepend && tableName - }); - } - if (Buffer.isBuffer(smth)) { - return this.escape(smth); - } - if (Array.isArray(smth)) { - if (smth.length === 0 || smth.length > 0 && smth[0].length === 0) return '1=1'; - if (Utils.canTreatArrayAsAnd(smth)) { - const _smth = { [Op.and]: smth }; - return this.getWhereConditions(_smth, tableName, factory, options, prepend); - } - throw new Error('Support for literal replacements in the `where` object has been removed.'); - } - if (smth === null) { - return this.whereItemsQuery(smth, { - model: factory, - prefix: prepend && tableName - }); - } - - return '1=1'; - } - - // A recursive parser for nested where conditions - parseConditionObject(conditions, path) { - path = path || []; - return _.reduce(conditions, (result, value, key) => { - if (_.isObject(value)) { - return result.concat(this.parseConditionObject(value, path.concat(key))); // Recursively parse objects - } - result.push({ path: path.concat(key), value }); - return result; - }, []); - } - - booleanValue(value) { - return value; - } -} - -Object.assign(QueryGenerator.prototype, require('./query-generator/operators')); -Object.assign(QueryGenerator.prototype, require('./query-generator/transaction')); - -module.exports = QueryGenerator; diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js deleted file mode 100644 index 19a1d983b5e5..000000000000 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Quote helpers implement quote ability for all dialects. - * These are basic block of query building - * - * Its better to implement all dialect implementation together here. Which will allow - * even abstract generator to use them by just specifying dialect type. - * - * Defining these helpers in each query dialect will leave - * code in dual dependency of abstract <-> specific dialect - */ - -'use strict'; - -const Utils = require('../../../../utils'); - -/** - * list of reserved words in PostgreSQL 10 - * source: https://www.postgresql.org/docs/10/static/sql-keywords-appendix.html - * - * @private - */ -const postgresReservedWords = 'all,analyse,analyze,and,any,array,as,asc,asymmetric,authorization,binary,both,case,cast,check,collate,collation,column,concurrently,constraint,create,cross,current_catalog,current_date,current_role,current_schema,current_time,current_timestamp,current_user,default,deferrable,desc,distinct,do,else,end,except,false,fetch,for,foreign,freeze,from,full,grant,group,having,ilike,in,initially,inner,intersect,into,is,isnull,join,lateral,leading,left,like,limit,localtime,localtimestamp,natural,not,notnull,null,offset,on,only,or,order,outer,overlaps,placing,primary,references,returning,right,select,session_user,similar,some,symmetric,table,tablesample,then,to,trailing,true,union,unique,user,using,variadic,verbose,when,where,window,with'.split(','); - -/** - * - * @param {string} dialect Dialect name - * @param {string} identifier Identifier to quote - * @param {object} [options] - * @param {boolean} [options.force=false] - * @param {boolean} [options.quoteIdentifiers=true] - * - * @returns {string} - * @private - */ -function quoteIdentifier(dialect, identifier, options) { - if (identifier === '*') return identifier; - - options = Utils.defaults(options || {}, { - force: false, - quoteIdentifiers: true - }); - - switch (dialect) { - case 'sqlite': - case 'mariadb': - case 'mysql': - return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`'); - - case 'postgres': - const rawIdentifier = Utils.removeTicks(identifier, '"'); - - if ( - options.force !== true && - options.quoteIdentifiers === false && - !identifier.includes('.') && - !identifier.includes('->') && - !postgresReservedWords.includes(rawIdentifier.toLowerCase()) - ) { - // In Postgres, if tables or attributes are created double-quoted, - // they are also case sensitive. If they contain any uppercase - // characters, they must always be double-quoted. This makes it - // impossible to write queries in portable SQL if tables are created in - // this way. Hence, we strip quotes if we don't want case sensitivity. - return rawIdentifier; - } - return Utils.addTicks(rawIdentifier, '"'); - case 'mssql': - return `[${identifier.replace(/[[\]']+/g, '')}]`; - - default: - throw new Error(`Dialect "${dialect}" is not supported`); - } -} -module.exports.quoteIdentifier = quoteIdentifier; - -/** - * Test if a give string is already quoted - * - * @param {string} identifier - * - * @returns {boolean} - * @private - */ -function isIdentifierQuoted(identifier) { - return /^\s*(?:([`"'])(?:(?!\1).|\1{2})*\1\.?)+\s*$/i.test(identifier); -} -module.exports.isIdentifierQuoted = isIdentifierQuoted; diff --git a/lib/dialects/abstract/query-generator/operators.js b/lib/dialects/abstract/query-generator/operators.js deleted file mode 100644 index 91d3caa2bcc0..000000000000 --- a/lib/dialects/abstract/query-generator/operators.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const Op = require('../../../operators'); -const Utils = require('../../../utils'); - -const OperatorHelpers = { - OperatorMap: { - [Op.eq]: '=', - [Op.ne]: '!=', - [Op.gte]: '>=', - [Op.gt]: '>', - [Op.lte]: '<=', - [Op.lt]: '<', - [Op.not]: 'IS NOT', - [Op.is]: 'IS', - [Op.in]: 'IN', - [Op.notIn]: 'NOT IN', - [Op.like]: 'LIKE', - [Op.notLike]: 'NOT LIKE', - [Op.iLike]: 'ILIKE', - [Op.notILike]: 'NOT ILIKE', - [Op.startsWith]: 'LIKE', - [Op.endsWith]: 'LIKE', - [Op.substring]: 'LIKE', - [Op.regexp]: '~', - [Op.notRegexp]: '!~', - [Op.iRegexp]: '~*', - [Op.notIRegexp]: '!~*', - [Op.between]: 'BETWEEN', - [Op.notBetween]: 'NOT BETWEEN', - [Op.overlap]: '&&', - [Op.contains]: '@>', - [Op.contained]: '<@', - [Op.adjacent]: '-|-', - [Op.strictLeft]: '<<', - [Op.strictRight]: '>>', - [Op.noExtendRight]: '&<', - [Op.noExtendLeft]: '&>', - [Op.any]: 'ANY', - [Op.all]: 'ALL', - [Op.and]: ' AND ', - [Op.or]: ' OR ', - [Op.col]: 'COL', - [Op.placeholder]: '$$PLACEHOLDER$$', - [Op.match]: '@@' - }, - - OperatorsAliasMap: {}, - - setOperatorsAliases(aliases) { - if (!aliases || _.isEmpty(aliases)) { - this.OperatorsAliasMap = false; - } else { - this.OperatorsAliasMap = { ...aliases }; - } - }, - - _replaceAliases(orig) { - const obj = {}; - if (!this.OperatorsAliasMap) { - return orig; - } - - Utils.getOperators(orig).forEach(op => { - const item = orig[op]; - if (_.isPlainObject(item)) { - obj[op] = this._replaceAliases(item); - } else { - obj[op] = item; - } - }); - - _.forOwn(orig, (item, prop) => { - prop = this.OperatorsAliasMap[prop] || prop; - if (_.isPlainObject(item)) { - item = this._replaceAliases(item); - } - obj[prop] = item; - }); - return obj; - } -}; - -module.exports = OperatorHelpers; diff --git a/lib/dialects/abstract/query-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js deleted file mode 100644 index c047008f9696..000000000000 --- a/lib/dialects/abstract/query-generator/transaction.js +++ /dev/null @@ -1,80 +0,0 @@ -'use strict'; - -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. - * @returns {string} The generated sql query. - * @private - */ - setIsolationLevelQuery(value, options) { - if (options.parent) { - return; - } - - return `SET TRANSACTION ISOLATION LEVEL ${value};`; - }, - - generateTransactionId() { - return uuidv4(); - }, - - /** - * Returns a query that starts a transaction. - * - * @param {Transaction} transaction - * @returns {string} The generated sql query. - * @private - */ - startTransactionQuery(transaction) { - if (transaction.parent) { - // force quoting of savepoint identifiers for postgres - return `SAVEPOINT ${this.quoteIdentifier(transaction.name, true)};`; - } - - return 'START TRANSACTION;'; - }, - - deferConstraintsQuery() {}, - - setConstraintQuery() {}, - setDeferredQuery() {}, - setImmediateQuery() {}, - - /** - * Returns a query that commits a transaction. - * - * @param {Transaction} transaction An object with options. - * @returns {string} The generated sql query. - * @private - */ - commitTransactionQuery(transaction) { - if (transaction.parent) { - return; - } - - return 'COMMIT;'; - }, - - /** - * Returns a query that rollbacks a transaction. - * - * @param {Transaction} transaction - * @returns {string} The generated sql query. - * @private - */ - rollbackTransactionQuery(transaction) { - if (transaction.parent) { - // force quoting of savepoint identifiers for postgres - return `ROLLBACK TO SAVEPOINT ${this.quoteIdentifier(transaction.name, true)};`; - } - - return 'ROLLBACK;'; - } -}; - -module.exports = TransactionQueries; diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js deleted file mode 100644 index e1876d5cef64..000000000000 --- a/lib/dialects/abstract/query-interface.js +++ /dev/null @@ -1,1258 +0,0 @@ -'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) { - 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 deleted file mode 100644 index 7e640b83fee4..000000000000 --- a/lib/dialects/abstract/query.js +++ /dev/null @@ -1,741 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const SqlString = require('../../sql-string'); -const QueryTypes = require('../../query-types'); -const Dot = require('dottie'); -const deprecations = require('../../utils/deprecations'); -const uuid = require('uuid').v4; - -class AbstractQuery { - - constructor(connection, sequelize, options) { - this.uuid = uuid(); - this.connection = connection; - this.instance = options.instance; - this.model = options.model; - this.sequelize = sequelize; - this.options = { - plain: false, - raw: false, - // eslint-disable-next-line no-console - logging: console.log, - ...options - }; - this.checkLoggingOption(); - } - - /** - * 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, values, dialect, replacementFunc, options) { - if (!values) { - return [sql, []]; - } - - options = options || {}; - if (typeof replacementFunc !== 'function') { - options = replacementFunc || {}; - replacementFunc = undefined; - } - - if (!replacementFunc) { - if (options.skipValueReplace) { - replacementFunc = (match, key, values) => { - if (values[key] !== undefined) { - return match; - } - return undefined; - }; - } else { - replacementFunc = (match, key, values, timeZone, dialect) => { - if (values[key] !== undefined) { - return SqlString.escape(values[key], timeZone, dialect); - } - return undefined; - }; - } - } else if (options.skipValueReplace) { - const origReplacementFunc = replacementFunc; - replacementFunc = (match, key, values, timeZone, dialect, options) => { - if (origReplacementFunc(match, key, values, timeZone, dialect, options) !== undefined) { - return match; - } - return undefined; - }; - } - - const timeZone = null; - const list = Array.isArray(values); - sql = sql.replace(/\B\$(\$|\w+)/g, (match, key) => { - if ('$' === key) { - return options.skipUnescape ? match : key; - } - - let replVal; - if (list) { - if (key.match(/^[1-9]\d*$/)) { - key = key - 1; - replVal = replacementFunc(match, key, values, timeZone, dialect, options); - } - } else if (!key.match(/^\d*$/)) { - replVal = replacementFunc(match, key, values, timeZone, dialect, options); - } - if (replVal === undefined) { - throw new Error(`Named bind parameter "${match}" has no value in the given object.`); - } - return replVal; - }); - return [sql, []]; - } - - /** - * Execute the passed sql query. - * - * Examples: - * - * query.run('SELECT 1') - * - * @private - */ - run() { - throw new Error('The run method wasn\'t overwritten!'); - } - - /** - * Check the logging option of the instance and print deprecation warnings. - * - * @private - */ - checkLoggingOption() { - if (this.options.logging === true) { - deprecations.noTrueLogging(); - // eslint-disable-next-line no-console - this.options.logging = console.log; - } - } - - /** - * Get the attributes of an insert query, which contains the just inserted id. - * - * @returns {string} The field name. - * @private - */ - getInsertIdField() { - return 'insertId'; - } - - getUniqueConstraintErrorMessage(field) { - let message = field ? `${field} must be unique` : 'Must be unique'; - - if (field && this.model) { - for (const key of Object.keys(this.model.uniqueKeys)) { - if (this.model.uniqueKeys[key].fields.includes(field.replace(/"/g, ''))) { - if (this.model.uniqueKeys[key].msg) { - message = this.model.uniqueKeys[key].msg; - } - } - } - } - return message; - } - - isRawQuery() { - return this.options.type === QueryTypes.RAW; - } - - isVersionQuery() { - return this.options.type === QueryTypes.VERSION; - } - - isUpsertQuery() { - return this.options.type === QueryTypes.UPSERT; - } - - isInsertQuery(results, metaData) { - let result = true; - - if (this.options.type === QueryTypes.INSERT) { - return true; - } - - // is insert query if sql contains insert into - result = result && this.sql.toLowerCase().startsWith('insert into'); - - // is insert query if no results are passed or if the result has the inserted id - result = result && (!results || Object.prototype.hasOwnProperty.call(results, this.getInsertIdField())); - - // is insert query if no metadata are passed or if the metadata has the inserted id - result = result && (!metaData || Object.prototype.hasOwnProperty.call(metaData, this.getInsertIdField())); - - return result; - } - - handleInsertQuery(results, metaData) { - if (this.instance) { - // add the inserted row id to the instance - const autoIncrementAttribute = this.model.autoIncrementAttribute; - let id = null; - - id = id || results && results[this.getInsertIdField()]; - id = id || metaData && metaData[this.getInsertIdField()]; - - this.instance[autoIncrementAttribute] = id; - } - } - - isShowTablesQuery() { - return this.options.type === QueryTypes.SHOWTABLES; - } - - handleShowTablesQuery(results) { - return _.flatten(results.map(resultSet => Object.values(resultSet))); - } - - isShowIndexesQuery() { - return this.options.type === QueryTypes.SHOWINDEXES; - } - - isShowConstraintsQuery() { - return this.options.type === QueryTypes.SHOWCONSTRAINTS; - } - - isDescribeQuery() { - return this.options.type === QueryTypes.DESCRIBE; - } - - isSelectQuery() { - return this.options.type === QueryTypes.SELECT; - } - - isBulkUpdateQuery() { - return this.options.type === QueryTypes.BULKUPDATE; - } - - isBulkDeleteQuery() { - return this.options.type === QueryTypes.BULKDELETE; - } - - isForeignKeysQuery() { - return this.options.type === QueryTypes.FOREIGNKEYS; - } - - isUpdateQuery() { - return this.options.type === QueryTypes.UPDATE; - } - - handleSelectQuery(results) { - let result = null; - - // Map raw fields to names if a mapping is provided - if (this.options.fieldMap) { - const fieldMap = this.options.fieldMap; - results = results.map(result => _.reduce(fieldMap, (result, name, field) => { - if (result[field] !== undefined && name !== field) { - result[name] = result[field]; - delete result[field]; - } - return result; - }, result)); - } - - // Raw queries - if (this.options.raw) { - result = results.map(result => { - let o = {}; - - for (const key in result) { - if (Object.prototype.hasOwnProperty.call(result, key)) { - o[key] = result[key]; - } - } - - if (this.options.nest) { - o = Dot.transform(o); - } - - return o; - }); - // Queries with include - } else if (this.options.hasJoin === true) { - results = AbstractQuery._groupJoinData(results, { - model: this.model, - includeMap: this.options.includeMap, - includeNames: this.options.includeNames - }, { - checkExisting: this.options.hasMultiAssociation - }); - - result = this.model.bulkBuild(results, { - isNewRecord: false, - include: this.options.include, - includeNames: this.options.includeNames, - includeMap: this.options.includeMap, - includeValidated: true, - attributes: this.options.originalAttributes || this.options.attributes, - raw: true - }); - // Regular queries - } else { - result = this.model.bulkBuild(results, { - isNewRecord: false, - raw: true, - attributes: this.options.originalAttributes || this.options.attributes - }); - } - - // return the first real model instance if options.plain is set (e.g. Model.find) - if (this.options.plain) { - result = result.length === 0 ? null : result[0]; - } - return result; - } - - isShowOrDescribeQuery() { - let result = false; - - result = result || this.sql.toLowerCase().startsWith('show'); - result = result || this.sql.toLowerCase().startsWith('describe'); - - return result; - } - - isCallQuery() { - return this.sql.toLowerCase().startsWith('call'); - } - - /** - * @param {string} sql - * @param {Function} debugContext - * @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 logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; - const startTime = Date.now(); - let logParameter = ''; - - if (logQueryParameters && parameters) { - const delimiter = sql.endsWith(';') ? '' : ';'; - let paramStr; - if (Array.isArray(parameters)) { - paramStr = parameters.map(p=>JSON.stringify(p)).join(', '); - } else { - paramStr = JSON.stringify(parameters); - } - logParameter = `${delimiter} ${paramStr}`; - } - const fmt = `(${connection.uuid || 'default'}): ${sql}${logParameter}`; - const msg = `Executing ${fmt}`; - debugContext(msg); - if (!benchmark) { - this.sequelize.log(`Executing ${fmt}`, options); - } - return () => { - const afterMsg = `Executed ${fmt}`; - debugContext(afterMsg); - if (benchmark) { - this.sequelize.log(afterMsg, Date.now() - startTime, options); - } - }; - } - - /** - * 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, includeOptions, options) { - - /* - * Assumptions - * ID is not necessarily the first field - * All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible) - * Parent keys will be seen before any include/child keys - * Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other) - */ - - /* - * Author (MH) comment: This code is an unreadable mess, but it's performant. - * groupJoinData is a performance critical function so we prioritize perf over readability. - */ - if (!rows.length) { - return []; - } - - // Generic looping - let i; - let length; - let $i; - let $length; - // Row specific looping - let rowsI; - let row; - const rowsLength = rows.length; - // Key specific looping - let keys; - let key; - let keyI; - let keyLength; - let prevKey; - let values; - let topValues; - let topExists; - const checkExisting = options.checkExisting; - // If we don't have to deduplicate we can pre-allocate the resulting array - let itemHash; - let parentHash; - let topHash; - const results = checkExisting ? [] : new Array(rowsLength); - const resultMap = {}; - const includeMap = {}; - // Result variables for the respective functions - let $keyPrefix; - let $keyPrefixString; - let $prevKeyPrefixString; // eslint-disable-line - let $prevKeyPrefix; - let $lastKeyPrefix; - let $current; - let $parent; - // Map each key to an include option - let previousPiece; - const buildIncludeMap = piece => { - if (Object.prototype.hasOwnProperty.call($current.includeMap, piece)) { - includeMap[key] = $current = $current.includeMap[piece]; - if (previousPiece) { - previousPiece = `${previousPiece}.${piece}`; - } else { - previousPiece = piece; - } - includeMap[previousPiece] = $current; - } - }; - // Calculate the string prefix of a key ('User.Results' for 'User.Results.id') - const keyPrefixStringMemo = {}; - const keyPrefixString = (key, memo) => { - if (!Object.prototype.hasOwnProperty.call(memo, key)) { - memo[key] = key.substr(0, key.lastIndexOf('.')); - } - return memo[key]; - }; - // Removes the prefix from a key ('id' for 'User.Results.id') - const removeKeyPrefixMemo = {}; - const removeKeyPrefix = key => { - if (!Object.prototype.hasOwnProperty.call(removeKeyPrefixMemo, key)) { - const index = key.lastIndexOf('.'); - removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1); - } - return removeKeyPrefixMemo[key]; - }; - // Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id') - const keyPrefixMemo = {}; - const keyPrefix = key => { - // We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values - if (!Object.prototype.hasOwnProperty.call(keyPrefixMemo, key)) { - const prefixString = keyPrefixString(key, keyPrefixStringMemo); - if (!Object.prototype.hasOwnProperty.call(keyPrefixMemo, prefixString)) { - keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : []; - } - keyPrefixMemo[key] = keyPrefixMemo[prefixString]; - } - return keyPrefixMemo[key]; - }; - // Calcuate the last item in the array prefix ('Results' for 'User.Results.id') - const lastKeyPrefixMemo = {}; - const lastKeyPrefix = key => { - if (!Object.prototype.hasOwnProperty.call(lastKeyPrefixMemo, key)) { - const prefix = keyPrefix(key); - const length = prefix.length; - - lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1]; - } - return lastKeyPrefixMemo[key]; - }; - const getUniqueKeyAttributes = model => { - let uniqueKeyAttributes = _.chain(model.uniqueKeys); - uniqueKeyAttributes = uniqueKeyAttributes - .result(`${uniqueKeyAttributes.findKey()}.fields`) - .map(field => _.findKey(model.attributes, chr => chr.field === field)) - .value(); - - return uniqueKeyAttributes; - }; - const stringify = obj => obj instanceof Buffer ? obj.toString('hex') : obj; - let primaryKeyAttributes; - let uniqueKeyAttributes; - let prefix; - - for (rowsI = 0; rowsI < rowsLength; rowsI++) { - row = rows[rowsI]; - - // Keys are the same for all rows, so only need to compute them on the first row - if (rowsI === 0) { - keys = Object.keys(row); - keyLength = keys.length; - } - - if (checkExisting) { - topExists = false; - - // Compute top level hash key (this is usually just the primary key values) - $length = includeOptions.model.primaryKeyAttributes.length; - topHash = ''; - if ($length === 1) { - topHash = stringify(row[includeOptions.model.primaryKeyAttributes[0]]); - } - else if ($length > 1) { - for ($i = 0; $i < $length; $i++) { - topHash += stringify(row[includeOptions.model.primaryKeyAttributes[$i]]); - } - } - else if (!_.isEmpty(includeOptions.model.uniqueKeys)) { - uniqueKeyAttributes = getUniqueKeyAttributes(includeOptions.model); - for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { - topHash += row[uniqueKeyAttributes[$i]]; - } - } - } - - topValues = values = {}; - $prevKeyPrefix = undefined; - for (keyI = 0; keyI < keyLength; keyI++) { - key = keys[keyI]; - - // The string prefix isn't actualy needed - // We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix - // TODO: Find a better way? - $keyPrefixString = keyPrefixString(key, keyPrefixStringMemo); - $keyPrefix = keyPrefix(key); - - // On the first row we compute the includeMap - if (rowsI === 0 && !Object.prototype.hasOwnProperty.call(includeMap, key)) { - if (!$keyPrefix.length) { - includeMap[key] = includeMap[''] = includeOptions; - } else { - $current = includeOptions; - previousPiece = undefined; - $keyPrefix.forEach(buildIncludeMap); - } - } - // End of key set - if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) { - if (checkExisting) { - // Compute hash key for this set instance - // TODO: Optimize - length = $prevKeyPrefix.length; - $parent = null; - parentHash = null; - - if (length) { - for (i = 0; i < length; i++) { - prefix = $parent ? `${$parent}.${$prevKeyPrefix[i]}` : $prevKeyPrefix[i]; - primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; - $length = primaryKeyAttributes.length; - itemHash = prefix; - if ($length === 1) { - itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[0]}`]); - } - else if ($length > 1) { - for ($i = 0; $i < $length; $i++) { - itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[$i]}`]); - } - } - else if (!_.isEmpty(includeMap[prefix].model.uniqueKeys)) { - uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); - for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { - itemHash += row[`${prefix}.${uniqueKeyAttributes[$i]}`]; - } - } - if (!parentHash) { - parentHash = topHash; - } - - itemHash = parentHash + itemHash; - $parent = prefix; - if (i < length - 1) { - parentHash = itemHash; - } - } - } else { - itemHash = topHash; - } - - if (itemHash === topHash) { - if (!resultMap[itemHash]) { - resultMap[itemHash] = values; - } else { - topExists = true; - } - } else if (!resultMap[itemHash]) { - $parent = resultMap[parentHash]; - $lastKeyPrefix = lastKeyPrefix(prevKey); - - if (includeMap[prevKey].association.isSingleAssociation) { - if ($parent) { - $parent[$lastKeyPrefix] = resultMap[itemHash] = values; - } - } else { - if (!$parent[$lastKeyPrefix]) { - $parent[$lastKeyPrefix] = []; - } - $parent[$lastKeyPrefix].push(resultMap[itemHash] = values); - } - } - - // Reset values - values = {}; - } else { - // If checkExisting is false it's because there's only 1:1 associations in this query - // However we still need to map onto the appropriate parent - // For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop - $current = topValues; - length = $keyPrefix.length; - if (length) { - for (i = 0; i < length; i++) { - if (i === length - 1) { - values = $current[$keyPrefix[i]] = {}; - } - $current = $current[$keyPrefix[i]] || {}; - } - } - } - } - - // End of iteration, set value and set prev values (for next iteration) - values[removeKeyPrefix(key)] = row[key]; - prevKey = key; - $prevKeyPrefix = $keyPrefix; - $prevKeyPrefixString = $keyPrefixString; - } - - if (checkExisting) { - length = $prevKeyPrefix.length; - $parent = null; - parentHash = null; - - if (length) { - for (i = 0; i < length; i++) { - prefix = $parent ? `${$parent}.${$prevKeyPrefix[i]}` : $prevKeyPrefix[i]; - primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; - $length = primaryKeyAttributes.length; - itemHash = prefix; - if ($length === 1) { - itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[0]}`]); - } - else if ($length > 0) { - for ($i = 0; $i < $length; $i++) { - itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[$i]}`]); - } - } - else if (!_.isEmpty(includeMap[prefix].model.uniqueKeys)) { - uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); - for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { - itemHash += row[`${prefix}.${uniqueKeyAttributes[$i]}`]; - } - } - if (!parentHash) { - parentHash = topHash; - } - - itemHash = parentHash + itemHash; - $parent = prefix; - if (i < length - 1) { - parentHash = itemHash; - } - } - } else { - itemHash = topHash; - } - - if (itemHash === topHash) { - if (!resultMap[itemHash]) { - resultMap[itemHash] = values; - } else { - topExists = true; - } - } else if (!resultMap[itemHash]) { - $parent = resultMap[parentHash]; - $lastKeyPrefix = lastKeyPrefix(prevKey); - - if (includeMap[prevKey].association.isSingleAssociation) { - if ($parent) { - $parent[$lastKeyPrefix] = resultMap[itemHash] = values; - } - } else { - if (!$parent[$lastKeyPrefix]) { - $parent[$lastKeyPrefix] = []; - } - $parent[$lastKeyPrefix].push(resultMap[itemHash] = values); - } - } - if (!topExists) { - results.push(topValues); - } - } else { - results[rowsI] = topValues; - } - } - - return results; - } -} - -module.exports = AbstractQuery; -module.exports.AbstractQuery = AbstractQuery; -module.exports.default = AbstractQuery; diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js deleted file mode 100644 index 2c163be59005..000000000000 --- a/lib/dialects/mariadb/connection-manager.js +++ /dev/null @@ -1,138 +0,0 @@ -'use strict'; - -const semver = require('semver'); -const AbstractConnectionManager = require('../abstract/connection-manager'); -const SequelizeErrors = require('../../errors'); -const { logger } = require('../../utils/logger'); -const DataTypes = require('../../data-types').mariadb; -const momentTz = require('moment-timezone'); -const debug = logger.debugContext('connection:mariadb'); -const parserStore = require('../parserStore')('mariadb'); - -/** - * MariaDB Connection Manager - * - * Get connections, validate and disconnect them. - * AbstractConnectionManager pooling use it to handle MariaDB specific connections - * Use https://github.com/MariaDB/mariadb-connector-nodejs to connect with MariaDB server - * - * @private - */ -class ConnectionManager extends AbstractConnectionManager { - constructor(dialect, sequelize) { - sequelize.config.port = sequelize.config.port || 3306; - super(dialect, sequelize); - this.lib = this._loadDialectModule('mariadb'); - this.refreshTypeParser(DataTypes); - } - - static _typecast(field, next) { - if (parserStore.get(field.type)) { - return parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - } - - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - - /** - * Connect with MariaDB database based on config, Handle any errors in connection - * Set the pool handlers on connection.error - * Also set proper timezone once connection is connected. - * - * @param {object} config - * @returns {Promise} - * @private - */ - 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') - : tzOffset; - - const connectionConfig = { - host: config.host, - port: config.port, - user: config.username, - password: config.password, - database: config.database, - timezone: tzOffset, - typeCast: ConnectionManager._typecast.bind(this), - bigNumberStrings: false, - supportBigNumbers: true, - foundRows: false, - ...config.dialectOptions - }; - - if (!this.sequelize.config.keepDefaultTimezone) { - // set timezone for this connection - if (connectionConfig.initSql) { - if (!Array.isArray( - connectionConfig.initSql)) { - connectionConfig.initSql = [connectionConfig.initSql]; - } - connectionConfig.initSql.push(`SET time_zone = '${tzOffset}'`); - } else { - connectionConfig.initSql = `SET time_zone = '${tzOffset}'`; - } - } - - 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); - } - } - } - - 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; - } - return await connection.end(); - } - - validate(connection) { - return connection && connection.isValid(); - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/mariadb/data-types.js b/lib/dialects/mariadb/data-types.js deleted file mode 100644 index aa4098535631..000000000000 --- a/lib/dialects/mariadb/data-types.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -const wkx = require('wkx'); -const _ = require('lodash'); -const moment = require('moment-timezone'); - -module.exports = BaseTypes => { - BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://mariadb.com/kb/en/library/resultset/#field-types'; - - /** - * 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 - */ - - BaseTypes.DATE.types.mariadb = ['DATETIME']; - BaseTypes.STRING.types.mariadb = ['VAR_STRING']; - BaseTypes.CHAR.types.mariadb = ['STRING']; - BaseTypes.TEXT.types.mariadb = ['BLOB']; - BaseTypes.TINYINT.types.mariadb = ['TINY']; - BaseTypes.SMALLINT.types.mariadb = ['SHORT']; - BaseTypes.MEDIUMINT.types.mariadb = ['INT24']; - BaseTypes.INTEGER.types.mariadb = ['LONG']; - BaseTypes.BIGINT.types.mariadb = ['LONGLONG']; - BaseTypes.FLOAT.types.mariadb = ['FLOAT']; - BaseTypes.TIME.types.mariadb = ['TIME']; - BaseTypes.DATEONLY.types.mariadb = ['DATE']; - BaseTypes.BOOLEAN.types.mariadb = ['TINY']; - BaseTypes.BLOB.types.mariadb = ['TINYBLOB', 'BLOB', 'LONGBLOB']; - BaseTypes.DECIMAL.types.mariadb = ['NEWDECIMAL']; - BaseTypes.UUID.types.mariadb = false; - BaseTypes.ENUM.types.mariadb = false; - BaseTypes.REAL.types.mariadb = ['DOUBLE']; - BaseTypes.DOUBLE.types.mariadb = ['DOUBLE']; - BaseTypes.GEOMETRY.types.mariadb = ['GEOMETRY']; - BaseTypes.JSON.types.mariadb = ['JSON']; - - class DECIMAL extends BaseTypes.DECIMAL { - toSql() { - let definition = super.toSql(); - if (this._unsigned) { - definition += ' UNSIGNED'; - } - if (this._zerofill) { - definition += ' ZEROFILL'; - } - return definition; - } - } - - class DATE extends BaseTypes.DATE { - toSql() { - return this._length ? `DATETIME(${this._length})` : 'DATETIME'; - } - _stringify(date, options) { - date = this._applyTimezone(date, options); - return date.format('YYYY-MM-DD HH:mm:ss.SSS'); - } - static parse(value, options) { - value = value.string(); - if (value === null) { - return value; - } - if (moment.tz.zone(options.timezone)) { - value = moment.tz(value, options.timezone).toDate(); - } - else { - value = new Date(`${value} ${options.timezone}`); - } - return value; - } - } - - class DATEONLY extends BaseTypes.DATEONLY { - static parse(value) { - return value.string(); - } - } - - class UUID extends BaseTypes.UUID { - toSql() { - return 'CHAR(36) BINARY'; - } - } - - class GEOMETRY extends BaseTypes.GEOMETRY { - constructor(type, srid) { - super(type, srid); - if (_.isEmpty(this.type)) { - this.sqlType = this.key; - } - else { - 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; - } - } - - class ENUM extends BaseTypes.ENUM { - toSql(options) { - return `ENUM(${this.values.map(value => options.escape(value)).join(', ')})`; - } - } - - class JSONTYPE extends BaseTypes.JSON { - _stringify(value, options) { - return options.operation === 'where' && typeof value === 'string' ? value - : JSON.stringify(value); - } - } - - return { - ENUM, - DATE, - DATEONLY, - UUID, - GEOMETRY, - DECIMAL, - JSON: JSONTYPE - }; -}; diff --git a/lib/dialects/mariadb/index.js b/lib/dialects/mariadb/index.js deleted file mode 100644 index 6b6090f9cbe8..000000000000 --- a/lib/dialects/mariadb/index.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -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 { - constructor(sequelize) { - super(); - this.sequelize = sequelize; - this.connectionManager = new ConnectionManager(this, sequelize); - this.queryGenerator = new QueryGenerator({ - _dialect: this, - sequelize - }); - this.queryInterface = new MySQLQueryInterface(sequelize, this.queryGenerator); - } -} - -MariadbDialect.prototype.supports = _.merge( - _.cloneDeep(AbstractDialect.prototype.supports), { - 'VALUES ()': true, - 'LIMIT ON UPDATE': true, - lock: true, - forShare: 'LOCK IN SHARE MODE', - settingIsolationLevelDuringTransaction: false, - schemas: true, - 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 - }); - -MariadbDialect.prototype.defaultVersion = '10.1.44'; -MariadbDialect.prototype.Query = Query; -MariadbDialect.prototype.QueryGenerator = QueryGenerator; -MariadbDialect.prototype.DataTypes = DataTypes; -MariadbDialect.prototype.name = 'mariadb'; -MariadbDialect.prototype.TICK_CHAR = '`'; -MariadbDialect.prototype.TICK_CHAR_LEFT = MariadbDialect.prototype.TICK_CHAR; -MariadbDialect.prototype.TICK_CHAR_RIGHT = MariadbDialect.prototype.TICK_CHAR; - -module.exports = MariadbDialect; diff --git a/lib/dialects/mariadb/query-generator.js b/lib/dialects/mariadb/query-generator.js deleted file mode 100644 index 2d2048dc59ec..000000000000 --- a/lib/dialects/mariadb/query-generator.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const MySQLQueryGenerator = require('../mysql/query-generator'); -const Utils = require('./../../utils'); - -class MariaDBQueryGenerator extends MySQLQueryGenerator { - createSchema(schema, options) { - options = { - charset: null, - 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) { - return `DROP SCHEMA IF EXISTS ${this.quoteIdentifier(schema)};`; - } - - showSchemasQuery(options) { - 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) { - let query = 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\''; - if (database) { - query += ` AND TABLE_SCHEMA = ${this.escape(database)}`; - } else { - query += ' AND TABLE_SCHEMA NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\')'; - } - return `${query};`; - } -} - -module.exports = MariaDBQueryGenerator; diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js deleted file mode 100644 index 2f78761d91bc..000000000000 --- a/lib/dialects/mariadb/query.js +++ /dev/null @@ -1,317 +0,0 @@ -'use strict'; - -const AbstractQuery = require('../abstract/query'); -const sequelizeErrors = require('../../errors'); -const _ = require('lodash'); -const DataTypes = require('../../data-types'); -const { logger } = require('../../utils/logger'); - -const ER_DUP_ENTRY = 1062; -const ER_DEADLOCK = 1213; -const ER_ROW_IS_REFERENCED = 1451; -const ER_NO_REFERENCED_ROW = 1452; - -const debug = logger.debugContext('sql:mariadb'); - -class Query extends AbstractQuery { - constructor(connection, sequelize, 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]); - return '?'; - } - return undefined; - }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; - return [sql, bindParam.length > 0 ? bindParam : undefined]; - } - - async run(sql, parameters) { - this.sql = sql; - const { connection, options } = this; - - const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; - - const complete = this._logQuery(sql, debug, parameters); - - if (parameters) { - debug('parameters(%j)', parameters); - } - - let results; - - 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. - } - - options.transaction.finished = 'rollback'; - } - - error.sql = sql; - error.parameters = parameters; - throw this.formatError(error); - } finally { - complete(); - } - - if (showWarnings && results && results.warningStatus > 0) { - await this.logWarnings(results); - } - return this.formatResults(results); - } - - /** - * High level function that handles the results of a query execution. - * - * - * Example: - * query.formatResults([ - * { - * id: 1, // this is from the main table - * attr2: 'snafu', // this is from the main table - * Tasks.id: 1, // this is from the associated table - * Tasks.title: 'task' // this is from the associated table - * } - * ]) - * - * @param {Array} data - The result of the query execution. - * @private - */ - formatResults(data) { - let result = this.instance; - - 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 - && 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...) - const startId = data[this.getInsertIdField()]; - result = new Array(data.affectedRows); - const pkField = this.model.rawAttributes[this.model.primaryKeyAttribute].field; - for (let i = 0; i < data.affectedRows; i++) { - result[i] = { [pkField]: startId + i }; - } - return [result, data.affectedRows]; - } - - return [data[this.getInsertIdField()], data.affectedRows]; - } - } - - if (this.isSelectQuery()) { - this.handleJsonSelectQuery(data); - return this.handleSelectQuery(data); - } - if (this.isInsertQuery() || this.isUpdateQuery()) { - return [result, data.affectedRows]; - } - if (this.isCallQuery()) { - return data[0]; - } - if (this.isRawQuery()) { - const meta = data.meta; - delete data.meta; - return [data, meta]; - } - if (this.isShowIndexesQuery()) { - return this.handleShowIndexesQuery(data); - } - if (this.isForeignKeysQuery() || this.isShowConstraintsQuery()) { - return data; - } - if (this.isShowTablesQuery()) { - return this.handleShowTablesQuery(data); - } - if (this.isDescribeQuery()) { - result = {}; - - for (const _result of data) { - result[_result.Field] = { - type: _result.Type.toLowerCase().startsWith('enum') ? _result.Type.replace(/^enum/i, - 'ENUM') : _result.Type.toUpperCase(), - allowNull: _result.Null === 'YES', - defaultValue: _result.Default, - primaryKey: _result.Key === 'PRI', - autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') - && _result.Extra.toLowerCase() === 'auto_increment', - comment: _result.Comment ? _result.Comment : null - }; - } - return result; - } - if (this.isVersionQuery()) { - return data[0].version; - } - - return result; - } - - handleJsonSelectQuery(rows) { - if (!this.model || !this.model.fieldRawAttributesMap) { - return; - } - for (const _field of Object.keys(this.model.fieldRawAttributesMap)) { - const modelField = this.model.fieldRawAttributesMap[_field]; - if (modelField.type instanceof DataTypes.JSON) { - // Value is returned as String, not JSON - rows = rows.map(row => { - row[modelField.fieldName] = row[modelField.fieldName] ? JSON.parse( - row[modelField.fieldName]) : null; - if (DataTypes.JSON.parse) { - return DataTypes.JSON.parse(modelField, this.sequelize.options, - row[modelField.fieldName]); - } - return row; - }); - } - } - } - - 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); - - return results; - } - - formatError(err) { - switch (err.errno) { - case ER_DUP_ENTRY: { - const match = err.message.match( - /Duplicate entry '([\s\S]*)' for key '?((.|\s)*?)'?\s.*$/); - - let fields = {}; - let message = 'Validation error'; - const values = match ? match[1].split('-') : undefined; - const fieldKey = match ? match[2] : undefined; - const fieldVal = match ? match[1] : undefined; - const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; - - if (uniqueKey) { - if (uniqueKey.msg) message = uniqueKey.msg; - fields = _.zipObject(uniqueKey.fields, values); - } else { - fields[fieldKey] = fieldVal; - } - - const errors = []; - _.forOwn(fields, (value, field) => { - errors.push(new sequelizeErrors.ValidationErrorItem( - this.getUniqueConstraintErrorMessage(field), - 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB, - field, - value, - this.instance, - 'not_unique' - )); - }); - - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); - } - - case ER_ROW_IS_REFERENCED: - case ER_NO_REFERENCED_ROW: { - // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) - const match = err.message.match( - /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ - ); - const quoteChar = match ? match[1] : '`'; - const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; - - return new sequelizeErrors.ForeignKeyConstraintError({ - 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, - index: match ? match[2] : undefined, - parent: err - }); - } - - default: - return new sequelizeErrors.DatabaseError(err); - } - } - - handleShowTablesQuery(results) { - return results.map(resultSet => ({ - tableName: resultSet.TABLE_NAME, - schema: resultSet.TABLE_SCHEMA - })); - } - - handleShowIndexesQuery(data) { - - let currItem; - const result = []; - - data.forEach(item => { - if (!currItem || currItem.name !== item.Key_name) { - currItem = { - primary: item.Key_name === 'PRIMARY', - fields: [], - name: item.Key_name, - tableName: item.Table, - unique: item.Non_unique !== 1, - type: item.Index_type - }; - result.push(currItem); - } - - currItem.fields[item.Seq_in_index - 1] = { - attribute: item.Column_name, - length: item.Sub_part || undefined, - order: item.Collation === 'A' ? 'ASC' : undefined - }; - }); - - return result; - } -} - -module.exports = Query; diff --git a/lib/dialects/mssql/async-queue.js b/lib/dialects/mssql/async-queue.js deleted file mode 100644 index adebefc08a8d..000000000000 --- a/lib/dialects/mssql/async-queue.js +++ /dev/null @@ -1,46 +0,0 @@ -'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 deleted file mode 100644 index b5de1900d64c..000000000000 --- a/lib/dialects/mssql/connection-manager.js +++ /dev/null @@ -1,172 +0,0 @@ -'use strict'; - -const AbstractConnectionManager = require('../abstract/connection-manager'); -const AsyncQueue = require('./async-queue').default; -const { logger } = require('../../utils/logger'); -const sequelizeErrors = require('../../errors'); -const DataTypes = require('../../data-types').mssql; -const parserStore = require('../parserStore')('mssql'); -const debug = logger.debugContext('connection:mssql'); -const debugTedious = logger.debugContext('connection:mssql:tedious'); - -class ConnectionManager extends AbstractConnectionManager { - constructor(dialect, sequelize) { - sequelize.config.port = sequelize.config.port || 1433; - super(dialect, sequelize); - this.lib = this._loadDialectModule('tedious'); - this.refreshTypeParser(DataTypes); - } - - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - - async connect(config) { - const connectionConfig = { - server: config.host, - authentication: { - type: 'default', - options: { - userName: config.username || undefined, - password: config.password || undefined - } - }, - options: { - port: parseInt(config.port, 10), - database: config.database, - trustServerCertificate: true - } - }; - - if (config.dialectOptions) { - // only set port if no instance name was provided - if ( - config.dialectOptions.options && - config.dialectOptions.options.instanceName - ) { - delete connectionConfig.options.port; - } - - if (config.dialectOptions.authentication) { - Object.assign(connectionConfig.authentication, config.dialectOptions.authentication); - } - - Object.assign(connectionConfig.options, config.dialectOptions.options); - } - - 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 (!error.code) { - throw new sequelizeErrors.ConnectionError(error); - } - - switch (error.code) { - case 'ESOCKET': - if (error.message.includes('connect EHOSTUNREACH')) { - throw new sequelizeErrors.HostNotReachableError(error); - } - if (error.message.includes('connect ENETUNREACH')) { - throw new sequelizeErrors.HostNotReachableError(error); - } - if (error.message.includes('connect EADDRNOTAVAIL')) { - throw new sequelizeErrors.HostNotReachableError(error); - } - if (error.message.includes('getaddrinfo ENOTFOUND')) { - throw new sequelizeErrors.HostNotFoundError(error); - } - if (error.message.includes('connect ECONNREFUSED')) { - throw new sequelizeErrors.ConnectionRefusedError(error); - } - throw new sequelizeErrors.ConnectionError(error); - case 'ER_ACCESS_DENIED_ERROR': - case 'ELOGIN': - throw new sequelizeErrors.AccessDeniedError(error); - case 'EINVAL': - throw new sequelizeErrors.InvalidConnectionError(error); - default: - throw new sequelizeErrors.ConnectionError(error); - } - } - } - - async disconnect(connection) { - // Don't disconnect a connection that is already disconnected - if (connection.closed) { - return; - } - - connection.queue.close(); - - return new Promise(resolve => { - connection.on('end', resolve); - connection.close(); - debug('connection closed'); - }); - } - - validate(connection) { - return connection && connection.loggedIn; - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/mssql/data-types.js b/lib/dialects/mssql/data-types.js deleted file mode 100644 index 4aeb6a495890..000000000000 --- a/lib/dialects/mssql/data-types.js +++ /dev/null @@ -1,217 +0,0 @@ -'use strict'; - -const moment = require('moment'); - -module.exports = BaseTypes => { - const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx'); - - /** - * Removes unsupported MSSQL options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. - * - * @param {object} dataType The base integer data type. - * @private - */ - function removeUnsupportedIntegerOptions(dataType) { - if (dataType._length || dataType.options.length || dataType._unsigned || dataType._zerofill) { - warn(`MSSQL does not support '${dataType.key}' with options. Plain '${dataType.key}' will be used instead.`); - dataType._length = undefined; - dataType.options.length = undefined; - dataType._unsigned = undefined; - dataType._zerofill = undefined; - } - } - - /** - * types: [hex, ...] - * - * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.ts - */ - - BaseTypes.DATE.types.mssql = [43]; - BaseTypes.STRING.types.mssql = [231, 173]; - BaseTypes.CHAR.types.mssql = [175]; - BaseTypes.TEXT.types.mssql = false; - // https://msdn.microsoft.com/en-us/library/ms187745(v=sql.110).aspx - BaseTypes.TINYINT.types.mssql = [30]; - BaseTypes.SMALLINT.types.mssql = [34]; - BaseTypes.MEDIUMINT.types.mssql = false; - BaseTypes.INTEGER.types.mssql = [38]; - BaseTypes.BIGINT.types.mssql = false; - BaseTypes.FLOAT.types.mssql = [109]; - BaseTypes.TIME.types.mssql = [41]; - BaseTypes.DATEONLY.types.mssql = [40]; - BaseTypes.BOOLEAN.types.mssql = [104]; - BaseTypes.BLOB.types.mssql = [165]; - BaseTypes.DECIMAL.types.mssql = [106]; - BaseTypes.UUID.types.mssql = false; - BaseTypes.ENUM.types.mssql = false; - BaseTypes.REAL.types.mssql = [109]; - BaseTypes.DOUBLE.types.mssql = [109]; - // BaseTypes.GEOMETRY.types.mssql = [240]; // not yet supported - BaseTypes.GEOMETRY.types.mssql = false; - - class BLOB extends BaseTypes.BLOB { - toSql() { - if (this._length) { - if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8 - warn('MSSQL does not support BLOB with the `length` = `tiny` option. `VARBINARY(256)` will be used instead.'); - return 'VARBINARY(256)'; - } - warn('MSSQL does not support BLOB with the `length` option. `VARBINARY(MAX)` will be used instead.'); - } - return 'VARBINARY(MAX)'; - } - _hexify(hex) { - return `0x${hex}`; - } - } - - - class STRING extends BaseTypes.STRING { - toSql() { - if (!this._binary) { - return `NVARCHAR(${this._length})`; - } - return `BINARY(${this._length})`; - } - _stringify(value, options) { - if (this._binary) { - return BLOB.prototype._stringify(value); - } - return options.escape(value); - } - _bindParam(value, options) { - return options.bindParam(this._binary ? Buffer.from(value) : value); - } - } - - STRING.prototype.escape = false; - - class TEXT extends BaseTypes.TEXT { - toSql() { - // TEXT is deprecated in mssql and it would normally be saved as a non-unicode string. - // Using unicode is just future proof - if (this._length) { - if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8 - warn('MSSQL does not support TEXT with the `length` = `tiny` option. `NVARCHAR(256)` will be used instead.'); - return 'NVARCHAR(256)'; - } - warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.'); - } - return 'NVARCHAR(MAX)'; - } - } - - class BOOLEAN extends BaseTypes.BOOLEAN { - toSql() { - return 'BIT'; - } - } - - class UUID extends BaseTypes.UUID { - toSql() { - return 'CHAR(36)'; - } - } - - class NOW extends BaseTypes.NOW { - toSql() { - return 'GETDATE()'; - } - } - - class DATE extends BaseTypes.DATE { - toSql() { - return 'DATETIMEOFFSET'; - } - } - - class DATEONLY extends BaseTypes.DATEONLY { - static parse(value) { - return moment(value).format('YYYY-MM-DD'); - } - } - - class INTEGER extends BaseTypes.INTEGER { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - class TINYINT extends BaseTypes.TINYINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - class SMALLINT extends BaseTypes.SMALLINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - class BIGINT extends BaseTypes.BIGINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - class REAL extends BaseTypes.REAL { - constructor(length, decimals) { - super(length, decimals); - // MSSQL does not support any options for real - if (this._length || this.options.length || this._unsigned || this._zerofill) { - warn('MSSQL does not support REAL with options. Plain `REAL` will be used instead.'); - this._length = undefined; - this.options.length = undefined; - this._unsigned = undefined; - this._zerofill = undefined; - } - } - } - class FLOAT extends BaseTypes.FLOAT { - constructor(length, decimals) { - super(length, decimals); - // MSSQL does only support lengths as option. - // Values between 1-24 result in 7 digits precision (4 bytes storage size) - // Values between 25-53 result in 15 digits precision (8 bytes storage size) - // If decimals are provided remove these and print a warning - if (this._decimals) { - warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.'); - this._length = undefined; - this.options.length = undefined; - } - if (this._unsigned) { - warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.'); - this._unsigned = undefined; - } - if (this._zerofill) { - warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.'); - this._zerofill = undefined; - } - } - } - class ENUM extends BaseTypes.ENUM { - toSql() { - return 'VARCHAR(255)'; - } - } - - return { - BLOB, - BOOLEAN, - ENUM, - STRING, - UUID, - DATE, - DATEONLY, - NOW, - TINYINT, - SMALLINT, - INTEGER, - BIGINT, - REAL, - FLOAT, - TEXT - }; -}; diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js deleted file mode 100644 index 5653a2dda7f5..000000000000 --- a/lib/dialects/mssql/index.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AbstractDialect = require('../abstract'); -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({ - _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.defaultVersion = '12.0.2000'; // SQL Server 2014 Express -MssqlDialect.prototype.Query = Query; -MssqlDialect.prototype.name = 'mssql'; -MssqlDialect.prototype.TICK_CHAR = '"'; -MssqlDialect.prototype.TICK_CHAR_LEFT = '['; -MssqlDialect.prototype.TICK_CHAR_RIGHT = ']'; -MssqlDialect.prototype.DataTypes = DataTypes; - -module.exports = MssqlDialect; diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js deleted file mode 100644 index 1c0f71367d53..000000000000 --- a/lib/dialects/mssql/query-generator.js +++ /dev/null @@ -1,953 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const Utils = require('../../utils'); -const DataTypes = require('../../data-types'); -const TableHints = require('../../table-hints'); -const AbstractQueryGenerator = require('../abstract/query-generator'); -const randomBytes = require('crypto').randomBytes; -const semver = require('semver'); -const Op = require('../../operators'); - -/* istanbul ignore next */ -const throwMethodUndefined = function(methodName) { - throw new Error(`The method "${methodName}" is not defined! Please add it to your sql dialect.`); -}; - -class MSSQLQueryGenerator extends AbstractQueryGenerator { - createDatabaseQuery(databaseName, options) { - options = { collate: null, ...options }; - - const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : ''; - - return [ - 'IF NOT EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')', - 'BEGIN', - 'CREATE DATABASE', this.quoteIdentifier(databaseName), - `${collation};`, - 'END;' - ].join(' '); - } - - dropDatabaseQuery(databaseName) { - return [ - 'IF EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')', - 'BEGIN', - 'DROP DATABASE', this.quoteIdentifier(databaseName), ';', - 'END;' - ].join(' '); - } - - createSchema(schema) { - return [ - 'IF NOT EXISTS (SELECT schema_name', - 'FROM information_schema.schemata', - 'WHERE schema_name =', wrapSingleQuote(schema), ')', - 'BEGIN', - "EXEC sp_executesql N'CREATE SCHEMA", - this.quoteIdentifier(schema), - ";'", - 'END;' - ].join(' '); - } - - dropSchema(schema) { - // Mimics Postgres CASCADE, will drop objects belonging to the schema - const quotedSchema = wrapSingleQuote(schema); - return [ - 'IF EXISTS (SELECT schema_name', - 'FROM information_schema.schemata', - 'WHERE schema_name =', quotedSchema, ')', - 'BEGIN', - 'DECLARE @id INT, @ms_sql NVARCHAR(2000);', - 'DECLARE @cascade TABLE (', - 'id INT NOT NULL IDENTITY PRIMARY KEY,', - 'ms_sql NVARCHAR(2000) NOT NULL );', - 'INSERT INTO @cascade ( ms_sql )', - "SELECT CASE WHEN o.type IN ('F','PK')", - "THEN N'ALTER TABLE ['+ s.name + N'].[' + p.name + N'] DROP CONSTRAINT [' + o.name + N']'", - "ELSE N'DROP TABLE ['+ s.name + N'].[' + o.name + N']' END", - 'FROM sys.objects o', - 'JOIN sys.schemas s on o.schema_id = s.schema_id', - 'LEFT OUTER JOIN sys.objects p on o.parent_object_id = p.object_id', - "WHERE o.type IN ('F', 'PK', 'U') AND s.name = ", quotedSchema, - 'ORDER BY o.type ASC;', - 'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;', - 'WHILE @id IS NOT NULL', - 'BEGIN', - 'BEGIN TRY EXEC sp_executesql @ms_sql; END TRY', - 'BEGIN CATCH BREAK; THROW; END CATCH;', - 'DELETE FROM @cascade WHERE id = @id;', - 'SELECT @id = NULL, @ms_sql = NULL;', - 'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;', - 'END', - "EXEC sp_executesql N'DROP SCHEMA", this.quoteIdentifier(schema), ";'", - 'END;' - ].join(' '); - } - - showSchemasQuery() { - return [ - 'SELECT "name" as "schema_name" FROM sys.schemas as s', - 'WHERE "s"."name" NOT IN (', - "'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'", - ')', 'AND', '"s"."name" NOT LIKE', "'db_%'" - ].join(' '); - } - - versionQuery() { - // Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch - return [ - 'DECLARE @ms_ver NVARCHAR(20);', - "SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion')));", - "SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'" - ].join(' '); - } - - createTableQuery(tableName, attributes, options) { - const primaryKeys = [], - foreignKeys = {}, - attributesClauseParts = []; - - let commentStr = ''; - - for (const attr in attributes) { - if (Object.prototype.hasOwnProperty.call(attributes, attr)) { - let dataType = attributes[attr]; - let match; - - if (dataType.includes('COMMENT ')) { - const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/); - const commentText = commentMatch[2].replace('COMMENT', '').trim(); - commentStr += this.commentTemplate(commentText, tableName, attr); - // remove comment related substring from dataType - dataType = commentMatch[1]; - } - - if (dataType.includes('PRIMARY KEY')) { - primaryKeys.push(attr); - - if (dataType.includes('REFERENCES')) { - // MSSQL doesn't support inline REFERENCES declarations: move to the end - match = dataType.match(/^(.+) (REFERENCES.*)$/); - attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); - foreignKeys[attr] = match[2]; - } else { - 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.*)$/); - attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`); - foreignKeys[attr] = match[2]; - } else { - attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`); - } - } - } - - const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); - - if (options.uniqueKeys) { - _.each(options.uniqueKeys, (columns, indexName) => { - if (columns.customIndex) { - if (typeof indexName !== 'string') { - indexName = `uniq_${tableName}_${columns.fields.join('_')}`; - } - attributesClauseParts.push(`CONSTRAINT ${ - this.quoteIdentifier(indexName) - } UNIQUE (${ - columns.fields.map(field => this.quoteIdentifier(field)).join(', ') - })`); - } - }); - } - - if (pkString.length > 0) { - attributesClauseParts.push(`PRIMARY KEY (${pkString})`); - } - - for (const fkey in foreignKeys) { - if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { - attributesClauseParts.push(`FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`); - } - } - - const quotedTableName = this.quoteTable(tableName); - - return Utils.joinSQLFragments([ - `IF OBJECT_ID('${quotedTableName}', 'U') IS NULL`, - `CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`, - ';', - commentStr - ]); - } - - describeTableQuery(tableName, schema) { - let sql = [ - 'SELECT', - "c.COLUMN_NAME AS 'Name',", - "c.DATA_TYPE AS 'Type',", - "c.CHARACTER_MAXIMUM_LENGTH AS 'Length',", - "c.IS_NULLABLE as 'IsNull',", - "COLUMN_DEFAULT AS 'Default',", - "pk.CONSTRAINT_TYPE AS 'Constraint',", - "COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',", - "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 ', - '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 ', - 'ON pk.table_schema=c.table_schema ', - 'AND pk.table_name=c.table_name ', - 'AND pk.column_name=c.column_name ', - 'INNER JOIN sys.columns AS sc', - "ON sc.object_id = object_id(t.table_schema + '.' + t.table_name) AND sc.name = c.column_name", - 'LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id', - 'AND prop.minor_id = sc.column_id', - "AND prop.name = 'MS_Description'", - 'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName) - ].join(' '); - - if (schema) { - sql += `AND t.TABLE_SCHEMA =${wrapSingleQuote(schema)}`; - } - - return sql; - } - - renameTableQuery(before, after) { - return `EXEC sp_rename ${this.quoteTable(before)}, ${this.quoteTable(after)};`; - } - - showTablesQuery() { - return "SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';"; - } - - dropTableQuery(tableName) { - const quoteTbl = this.quoteTable(tableName); - return Utils.joinSQLFragments([ - `IF OBJECT_ID('${quoteTbl}', 'U') IS NOT NULL`, - 'DROP TABLE', - quoteTbl, - ';' - ]); - } - - addColumnQuery(table, key, dataType) { - // FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery - // but instead we need to pass the key along as the field here - dataType.field = key; - let commentStr = ''; - - if (dataType.comment && _.isString(dataType.comment)) { - commentStr = this.commentTemplate(dataType.comment, table, key); - // attributeToSQL will try to include `COMMENT 'Comment Text'` when it returns if the comment key - // is present. This is needed for createTable statement where that part is extracted with regex. - // Here we can intercept the object and remove comment property since we have the original object. - delete dataType['comment']; - } - - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(table), - 'ADD', - this.quoteIdentifier(key), - this.attributeToSQL(dataType, { context: 'addColumn' }), - ';', - commentStr - ]); - } - - commentTemplate(comment, table, column) { - return ' EXEC sp_addextendedproperty ' + - `@name = N'MS_Description', @value = ${this.escape(comment)}, ` + - '@level0type = N\'Schema\', @level0name = \'dbo\', ' + - `@level1type = N'Table', @level1name = ${this.quoteIdentifier(table)}, ` + - `@level2type = N'Column', @level2name = ${this.quoteIdentifier(column)};`; - } - - removeColumnQuery(tableName, attributeName) { - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'DROP COLUMN', - this.quoteIdentifier(attributeName), - ';' - ]); - } - - changeColumnQuery(tableName, attributes) { - const attrString = [], - constraintString = []; - let commentString = ''; - - for (const attributeName in attributes) { - const quotedAttrName = this.quoteIdentifier(attributeName); - let definition = attributes[attributeName]; - if (definition.includes('COMMENT ')) { - const commentMatch = definition.match(/^(.+) (COMMENT.*)$/); - const commentText = commentMatch[2].replace('COMMENT', '').trim(); - commentString += this.commentTemplate(commentText, tableName, attributeName); - // remove comment related substring from dataType - definition = commentMatch[1]; - } - if (definition.includes('REFERENCES')) { - constraintString.push(`FOREIGN KEY (${quotedAttrName}) ${definition.replace(/.+?(?=REFERENCES)/, '')}`); - } else { - attrString.push(`${quotedAttrName} ${definition}`); - } - } - - 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 Utils.joinSQLFragments([ - 'EXEC sp_rename', - `'${this.quoteTable(tableName)}.${attrBefore}',`, - `'${newName}',`, - "'COLUMN'", - ';' - ]); - } - - bulkInsertQuery(tableName, attrValueHashes, options, attributes) { - const quotedTable = this.quoteTable(tableName); - options = options || {}; - attributes = attributes || {}; - - const tuples = []; - const allAttributes = []; - const allQueries = []; - - let needIdentityInsertWrapper = false, - outputFragment = ''; - - if (options.returning) { - const returnValues = this.generateReturnValues(attributes, options); - - outputFragment = returnValues.outputFragment; - } - - const emptyQuery = `INSERT INTO ${quotedTable}${outputFragment} DEFAULT VALUES`; - - attrValueHashes.forEach(attrValueHash => { - // special case for empty objects with primary keys - const fields = Object.keys(attrValueHash); - const firstAttr = attributes[fields[0]]; - if (fields.length === 1 && firstAttr && firstAttr.autoIncrement && attrValueHash[fields[0]] === null) { - allQueries.push(emptyQuery); - return; - } - - // normal case - _.forOwn(attrValueHash, (value, key) => { - if (value !== null && attributes[key] && attributes[key].autoIncrement) { - needIdentityInsertWrapper = true; - } - - if (!allAttributes.includes(key)) { - if (value === null && attributes[key] && attributes[key].autoIncrement) - return; - - allAttributes.push(key); - } - }); - }); - - if (allAttributes.length > 0) { - attrValueHashes.forEach(attrValueHash => { - tuples.push(`(${ - allAttributes.map(key => - this.escape(attrValueHash[key])).join(',') - })`); - }); - - const quotedAttributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(','); - allQueries.push(tupleStr => `INSERT INTO ${quotedTable} (${quotedAttributes})${outputFragment} VALUES ${tupleStr};`); - } - const commands = []; - let offset = 0; - const batch = Math.floor(250 / (allAttributes.length + 1)) + 1; - while (offset < Math.max(tuples.length, 1)) { - const tupleStr = tuples.slice(offset, Math.min(tuples.length, offset + batch)); - let generatedQuery = allQueries.map(v => typeof v === 'string' ? v : v(tupleStr)).join(';'); - if (needIdentityInsertWrapper) { - generatedQuery = `SET IDENTITY_INSERT ${quotedTable} ON; ${generatedQuery}; SET IDENTITY_INSERT ${quotedTable} OFF;`; - } - commands.push(generatedQuery); - offset += batch; - } - return commands.join(';'); - } - - updateQuery(tableName, attrValueHash, where, options, attributes) { - const sql = super.updateQuery(tableName, attrValueHash, where, options, attributes); - if (options.limit) { - const updateArgs = `UPDATE TOP(${this.escape(options.limit)})`; - sql.query = sql.query.replace('UPDATE', updateArgs); - } - return sql; - } - - upsertQuery(tableName, insertValues, updateValues, where, model) { - const targetTableAlias = this.quoteTable(`${tableName}_target`); - const sourceTableAlias = this.quoteTable(`${tableName}_source`); - const primaryKeysAttrs = []; - const identityAttrs = []; - const uniqueAttrs = []; - const tableNameQuoted = this.quoteTable(tableName); - let needIdentityInsertWrapper = false; - - //Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed - for (const key in model.rawAttributes) { - if (model.rawAttributes[key].primaryKey) { - primaryKeysAttrs.push(model.rawAttributes[key].field || key); - } - if (model.rawAttributes[key].unique) { - uniqueAttrs.push(model.rawAttributes[key].field || key); - } - if (model.rawAttributes[key].autoIncrement) { - identityAttrs.push(model.rawAttributes[key].field || key); - } - } - - //Add unique indexes defined by indexes option to uniqueAttrs - for (const index of model._indexes) { - if (index.unique && index.fields) { - for (const field of index.fields) { - const fieldName = typeof field === 'string' ? field : field.name || field.attribute; - if (!uniqueAttrs.includes(fieldName) && model.rawAttributes[fieldName]) { - uniqueAttrs.push(fieldName); - } - } - } - } - - const updateKeys = Object.keys(updateValues); - const insertKeys = Object.keys(insertValues); - const insertKeysQuoted = insertKeys.map(key => this.quoteIdentifier(key)).join(', '); - const insertValuesEscaped = insertKeys.map(key => this.escape(insertValues[key])).join(', '); - const sourceTableQuery = `VALUES(${insertValuesEscaped})`; //Virtual Table - let joinCondition; - - //IDENTITY_INSERT Condition - identityAttrs.forEach(key => { - if (updateValues[key] && updateValues[key] !== null) { - needIdentityInsertWrapper = true; - /* - * IDENTITY_INSERT Column Cannot be updated, only inserted - * http://stackoverflow.com/a/30176254/2254360 - */ - } - }); - - //Filter NULL Clauses - const clauses = where[Op.or].filter(clause => { - let valid = true; - /* - * 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 (typeof clause[key] === 'undefined' || clause[key] == null) { - valid = false; - break; - } - } - return valid; - }); - - /* - * Generate ON condition using PK(s). - * If not, generate using UK(s). Else throw error - */ - const getJoinSnippet = array => { - return array.map(key => { - key = this.quoteIdentifier(key); - return `${targetTableAlias}.${key} = ${sourceTableAlias}.${key}`; - }); - }; - - if (clauses.length === 0) { - throw new Error('Primary Key or Unique key should be passed to upsert query'); - } else { - // Search for primary key attribute in clauses -- Model can have two separate unique keys - for (const key in clauses) { - const keys = Object.keys(clauses[key]); - if (primaryKeysAttrs.includes(keys[0])) { - joinCondition = getJoinSnippet(primaryKeysAttrs).join(' AND '); - break; - } - } - if (!joinCondition) { - joinCondition = getJoinSnippet(uniqueAttrs).join(' AND '); - } - } - - // Remove the IDENTITY_INSERT Column from update - const updateSnippet = updateKeys.filter(key => !identityAttrs.includes(key)) - .map(key => { - const value = this.escape(updateValues[key]); - key = this.quoteIdentifier(key); - return `${targetTableAlias}.${key} = ${value}`; - }).join(', '); - - const insertSnippet = `(${insertKeysQuoted}) VALUES(${insertValuesEscaped})`; - let query = `MERGE INTO ${tableNameQuoted} WITH(HOLDLOCK) AS ${targetTableAlias} USING (${sourceTableQuery}) AS ${sourceTableAlias}(${insertKeysQuoted}) ON ${joinCondition}`; - query += ` WHEN MATCHED THEN UPDATE SET ${updateSnippet} WHEN NOT MATCHED THEN INSERT ${insertSnippet} OUTPUT $action, INSERTED.*;`; - if (needIdentityInsertWrapper) { - query = `SET IDENTITY_INSERT ${tableNameQuoted} ON; ${query} SET IDENTITY_INSERT ${tableNameQuoted} OFF;`; - } - return query; - } - - truncateTableQuery(tableName) { - return `TRUNCATE TABLE ${this.quoteTable(tableName)}`; - } - - deleteQuery(tableName, where, options = {}, model) { - const table = this.quoteTable(tableName); - const whereClause = this.getWhereConditions(where, null, model, options); - - return Utils.joinSQLFragments([ - 'DELETE', - options.limit && `TOP(${this.escape(options.limit)})`, - 'FROM', - table, - whereClause && `WHERE ${whereClause}`, - ';', - 'SELECT @@ROWCOUNT AS AFFECTEDROWS', - ';' - ]); - } - - showIndexesQuery(tableName) { - return `EXEC sys.sp_helpindex @objname = N'${this.quoteTable(tableName)}';`; - } - - showConstraintsQuery(tableName) { - return `EXEC sp_helpconstraint @objname = ${this.escape(this.quoteTable(tableName))};`; - } - - removeIndexQuery(tableName, indexNameOrAttributes) { - let indexName = indexNameOrAttributes; - - if (typeof indexName !== 'string') { - indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); - } - - return `DROP INDEX ${this.quoteIdentifiers(indexName)} ON ${this.quoteIdentifiers(tableName)}`; - } - - attributeToSQL(attribute) { - if (!_.isPlainObject(attribute)) { - attribute = { - type: attribute - }; - } - - // handle self referential constraints - if (attribute.references) { - - if (attribute.Model && attribute.Model.tableName === attribute.references.model) { - this.sequelize.log('MSSQL does not support self referencial constraints, ' - + 'we will remove it but we recommend restructuring your query'); - attribute.onDelete = ''; - attribute.onUpdate = ''; - } - } - - let template; - - if (attribute.type instanceof DataTypes.ENUM) { - if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values; - - // enums are a special case - template = attribute.type.toSql(); - template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.values.map(value => { - return this.escape(value); - }).join(', ') }))`; - return template; - } - template = attribute.type.toString(); - - if (attribute.allowNull === false) { - template += ' NOT NULL'; - } else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) { - template += ' NULL'; - } - - if (attribute.autoIncrement) { - template += ' IDENTITY(1,1)'; - } - - // Blobs/texts cannot have a defaultValue - if (attribute.type !== 'TEXT' && attribute.type._binary !== true && - Utils.defaultValueSchemable(attribute.defaultValue)) { - template += ` DEFAULT ${this.escape(attribute.defaultValue)}`; - } - - if (attribute.unique === true) { - template += ' UNIQUE'; - } - - if (attribute.primaryKey) { - template += ' PRIMARY KEY'; - } - - if (attribute.references) { - template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`; - - if (attribute.references.key) { - template += ` (${this.quoteIdentifier(attribute.references.key)})`; - } else { - template += ` (${this.quoteIdentifier('id')})`; - } - - if (attribute.onDelete) { - template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; - } - - if (attribute.onUpdate) { - template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; - } - } - - if (attribute.comment && typeof attribute.comment === 'string') { - template += ` COMMENT ${attribute.comment}`; - } - - return template; - } - - attributesToSQL(attributes, options) { - const result = {}, - existingConstraints = []; - let key, - attribute; - - for (key in attributes) { - attribute = attributes[key]; - - if (attribute.references) { - if (existingConstraints.includes(attribute.references.model.toString())) { - // no cascading constraints to a table more than once - attribute.onDelete = ''; - attribute.onUpdate = ''; - } else { - existingConstraints.push(attribute.references.model.toString()); - - // NOTE: this really just disables cascading updates for all - // definitions. Can be made more robust to support the - // few cases where MSSQL actually supports them - attribute.onUpdate = ''; - } - - } - - if (key && !attribute.field) attribute.field = key; - result[attribute.field || key] = this.attributeToSQL(attribute, options); - } - - return result; - } - - createTrigger() { - throwMethodUndefined('createTrigger'); - } - - dropTrigger() { - throwMethodUndefined('dropTrigger'); - } - - renameTrigger() { - throwMethodUndefined('renameTrigger'); - } - - createFunction() { - throwMethodUndefined('createFunction'); - } - - dropFunction() { - throwMethodUndefined('dropFunction'); - } - - renameFunction() { - throwMethodUndefined('renameFunction'); - } - - /** - * Generate common SQL prefix for ForeignKeysQuery. - * - * @param {string} catalogName - * @returns {string} - */ - _getForeignKeysQueryPrefix(catalogName) { - return `${'SELECT ' + - 'constraint_name = OBJ.NAME, ' + - 'constraintName = OBJ.NAME, '}${ - catalogName ? `constraintCatalog = '${catalogName}', ` : '' - }constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), ` + - 'tableName = TB.NAME, ' + - `tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), ${ - catalogName ? `tableCatalog = '${catalogName}', ` : '' - }columnName = COL.NAME, ` + - `referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), ${ - catalogName ? `referencedCatalog = '${catalogName}', ` : '' - }referencedTableName = RTB.NAME, ` + - 'referencedColumnName = RCOL.NAME ' + - 'FROM sys.foreign_key_columns FKC ' + - 'INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID ' + - 'INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID ' + - 'INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID ' + - 'INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID ' + - 'INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID'; - } - - /** - * Generates an SQL query that returns all foreign keys details of a table. - * - * @param {string|object} table - * @param {string} catalogName database name - * @returns {string} - */ - getForeignKeysQuery(table, catalogName) { - const tableName = table.tableName || table; - let sql = `${this._getForeignKeysQueryPrefix(catalogName) - } WHERE TB.NAME =${wrapSingleQuote(tableName)}`; - - if (table.schema) { - sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`; - } - return sql; - } - - getForeignKeyQuery(table, attributeName) { - const tableName = table.tableName || table; - 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 Utils.joinSQLFragments([ - 'SELECT K.TABLE_NAME AS tableName,', - 'K.COLUMN_NAME AS columnName,', - 'K.CONSTRAINT_NAME AS constraintName', - 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C', - 'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K', - 'ON C.TABLE_NAME = K.TABLE_NAME', - 'AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG', - 'AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA', - 'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME', - 'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'', - `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`, - `AND K.TABLE_NAME = ${tableName}`, - ';' - ]); - } - - dropForeignKeyQuery(tableName, foreignKey) { - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'DROP', - this.quoteIdentifier(foreignKey) - ]); - } - - getDefaultConstraintQuery(tableName, attributeName) { - const quotedTable = this.quoteTable(tableName); - 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 Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'DROP CONSTRAINT', - this.quoteIdentifier(constraintName), - ';' - ]); - } - - setIsolationLevelQuery() { - - } - - generateTransactionId() { - return randomBytes(10).toString('hex'); - } - - startTransactionQuery(transaction) { - if (transaction.parent) { - return `SAVE TRANSACTION ${this.quoteIdentifier(transaction.name)};`; - } - - return 'BEGIN TRANSACTION;'; - } - - commitTransactionQuery(transaction) { - if (transaction.parent) { - return; - } - - return 'COMMIT TRANSACTION;'; - } - - rollbackTransactionQuery(transaction) { - if (transaction.parent) { - return `ROLLBACK TRANSACTION ${this.quoteIdentifier(transaction.name)};`; - } - - return 'ROLLBACK TRANSACTION;'; - } - - selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { - this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); - - const dbVersion = this.sequelize.options.databaseVersion; - const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0'); - - if (isSQLServer2008 && options.offset) { - // For earlier versions of SQL server, we need to nest several queries - // in order to emulate the OFFSET behavior. - // - // 1. The outermost query selects all items from the inner query block. - // This is due to a limitation in SQL server with the use of computed - // columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. - // 2. The next query handles the LIMIT and OFFSET behavior by getting - // the TOP N rows of the query where the row number is > OFFSET - // 3. The innermost query is the actual set we want information from - - const offset = options.offset || 0; - const isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation; - let orders = { mainQueryOrder: [] }; - if (options.order) { - orders = this.getQueryOrders(options, model, isSubQuery); - } - - if (orders.mainQueryOrder.length === 0) { - orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField)); - } - - const tmpTable = mainTableAs || 'OffsetTable'; - - return Utils.joinSQLFragments([ - 'SELECT TOP 100 PERCENT', - attributes.join(', '), - 'FROM (', - [ - 'SELECT', - options.limit && `TOP ${options.limit}`, - '* FROM (', - [ - 'SELECT ROW_NUMBER() OVER (', - [ - 'ORDER BY', - orders.mainQueryOrder.join(', ') - ], - `) as row_num, * FROM ${tables} AS ${tmpTable}`, - where && `WHERE ${where}` - ], - `) AS ${tmpTable} WHERE row_num > ${offset}` - ], - `) AS ${tmpTable}` - ]); - } - - return Utils.joinSQLFragments([ - 'SELECT', - isSQLServer2008 && options.limit && `TOP ${options.limit}`, - attributes.join(', '), - `FROM ${tables}`, - mainTableAs && `AS ${mainTableAs}`, - options.tableHint && TableHints[options.tableHint] && `WITH (${TableHints[options.tableHint]})` - ]); - } - - addLimitAndOffset(options, model) { - // Skip handling of limit and offset as postfixes for older SQL Server versions - if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) { - return ''; - } - - const offset = options.offset || 0; - const isSubQuery = options.subQuery === undefined - ? options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation - : options.subQuery; - - let fragment = ''; - let orders = {}; - - if (options.order) { - orders = this.getQueryOrders(options, model, isSubQuery); - } - - if (options.limit || options.offset) { - 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) { - fragment += ` OFFSET ${this.escape(offset)} ROWS`; - } - - if (options.limit) { - fragment += ` FETCH NEXT ${this.escape(options.limit)} ROWS ONLY`; - } - } - - return fragment; - } - - booleanValue(value) { - return value ? 1 : 0; - } -} - -// private methods -function wrapSingleQuote(identifier) { - return Utils.addTicks(Utils.removeTicks(identifier, "'"), "'"); -} - -module.exports = MSSQLQueryGenerator; diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js deleted file mode 100644 index 3d91a9796dc3..000000000000 --- a/lib/dialects/mssql/query-interface.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -const Utils = require('../../utils'); -const QueryTypes = require('../../query-types'); -const Op = require('../../operators'); -const { QueryInterface } = require('../abstract/query-interface'); - -/** - * 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); - } - - /** - * @override - */ - async upsert(tableName, insertValues, updateValues, where, options) { - const model = options.model; - const wheres = []; - - options = { ...options }; - - if (!Utils.isWhereEmpty(where)) { - wheres.push(where); - } - - // Lets combine unique keys and indexes into one - let indexes = Object.values(model.uniqueKeys).map(item => item.fields); - indexes = indexes.concat(Object.values(model._indexes).filter(item => item.unique).map(item => item.fields)); - - const attributes = Object.keys(insertValues); - for (const index of indexes) { - if (_.intersection(attributes, index).length === index.length) { - where = {}; - for (const field of index) { - where[field] = insertValues[field]; - } - wheres.push(where); - } - } - - where = { [Op.or]: wheres }; - - options.type = QueryTypes.UPSERT; - options.raw = true; - - const sql = this.queryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); - return await this.sequelize.query(sql, options); - } -} - -exports.MSSqlQueryInterface = MSSqlQueryInterface; diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js deleted file mode 100644 index e95aac6f6f66..000000000000 --- a/lib/dialects/mssql/query.js +++ /dev/null @@ -1,402 +0,0 @@ -'use strict'; - -const AbstractQuery = require('../abstract/query'); -const sequelizeErrors = require('../../errors'); -const parserStore = require('../parserStore')('mssql'); -const _ = require('lodash'); -const { logger } = require('../../utils/logger'); - -const debug = logger.debugContext('sql:mssql'); - -function getScale(aNum) { - if (!Number.isFinite(aNum)) return 0; - let e = 1; - while (Math.round(aNum * e) / e !== aNum) e *= 10; - return Math.log10(e); -} - -class Query extends AbstractQuery { - getInsertIdField() { - return 'id'; - } - - getSQLTypeFromJsType(value, TYPES) { - const paramType = { type: TYPES.VarChar, typeOptions: {} }; - paramType.type = TYPES.NVarChar; - if (typeof value === 'number') { - if (Number.isInteger(value)) { - if (value >= -2147483648 && value <= 2147483647) { - paramType.type = TYPES.Int; - } else { - paramType.type = TYPES.BigInt; - } - } else { - paramType.type = TYPES.Numeric; - //Default to a reasonable numeric precision/scale pending more sophisticated logic - paramType.typeOptions = { precision: 30, scale: getScale(value) }; - } - } else if (typeof value === 'boolean') { - paramType.type = TYPES.Bit; - } - if (Buffer.isBuffer(value)) { - paramType.type = TYPES.VarBinary; - } - return paramType; - } - - async _run(connection, sql, parameters) { - this.sql = sql; - const { options } = this; - - const complete = this._logQuery(sql, debug, parameters); - - const query = new Promise((resolve, reject) => { - // TRANSACTION SUPPORT - if (sql.startsWith('BEGIN TRANSACTION')) { - 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(error => error ? reject(error) : resolve([])); - } - if (sql.startsWith('ROLLBACK TRANSACTION')) { - return connection.rollbackTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); - } - if (sql.startsWith('SAVE TRANSACTION')) { - return connection.saveTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); - } - - const rows = []; - const request = new connection.lib.Request(sql, (err, rowCount) => err ? reject(err) : resolve([rows, rowCount])); - - if (parameters) { - _.forOwn(parameters, (value, key) => { - const paramType = this.getSQLTypeFromJsType(value, connection.lib.TYPES); - request.addParameter(key, paramType.type, value, paramType.typeOptions); - }); - } - - request.on('row', columns => { - rows.push(columns); - }); - - connection.execSql(request); - }); - - let rows, rowCount; - - try { - [rows, rowCount] = await query; - } catch (err) { - err.sql = sql; - err.parameters = parameters; - - throw this.formatError(err); - } - - complete(); - - if (Array.isArray(rows)) { - rows = rows.map(columns => { - const row = {}; - for (const column of columns) { - const typeid = column.metadata.type.id; - const parse = parserStore.get(typeid); - let value = column.value; - - if (value !== null & !!parse) { - value = parse(value); - } - row[column.metadata.colName] = value; - } - return row; - }); - } - - return this.formatResults(rows, rowCount); - } - - run(sql, parameters) { - return this.connection.queue.enqueue(() => this._run(this.connection, sql, parameters)); - } - - static formatBindParameters(sql, values, dialect) { - const bindParam = {}; - const replacementFunc = (match, key, values) => { - if (values[key] !== undefined) { - bindParam[key] = values[key]; - return `@${key}`; - } - return undefined; - }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; - - return [sql, bindParam]; - } - - /** - * High level function that handles the results of a query execution. - * - * @param {Array} data - The result of the query execution. - * @param {number} rowCount - * @private - * @example - * Example: - * query.formatResults([ - * { - * id: 1, // this is from the main table - * attr2: 'snafu', // this is from the main table - * Tasks.id: 1, // this is from the associated table - * Tasks.title: 'task' // this is from the associated table - * } - * ]) - */ - formatResults(data, rowCount) { - if (this.isInsertQuery(data)) { - this.handleInsertQuery(data); - return [this.instance || data, rowCount]; - } - if (this.isShowTablesQuery()) { - return this.handleShowTablesQuery(data); - } - if (this.isDescribeQuery()) { - const result = {}; - for (const _result of data) { - if (_result.Default) { - _result.Default = _result.Default.replace("('", '').replace("')", '').replace(/'/g, ''); - } - - result[_result.Name] = { - type: _result.Type.toUpperCase(), - allowNull: _result.IsNull === 'YES' ? true : false, - defaultValue: _result.Default, - primaryKey: _result.Constraint === 'PRIMARY KEY', - autoIncrement: _result.IsIdentity === 1, - comment: _result.Comment - }; - - if ( - result[_result.Name].type.includes('CHAR') - && _result.Length - ) { - if (_result.Length === -1) { - result[_result.Name].type += '(MAX)'; - } else { - result[_result.Name].type += `(${_result.Length})`; - } - } - } - return result; - } - if (this.isSelectQuery()) { - return this.handleSelectQuery(data); - } - if (this.isShowIndexesQuery()) { - return this.handleShowIndexesQuery(data); - } - if (this.isCallQuery()) { - return data[0]; - } - if (this.isBulkUpdateQuery()) { - if (this.options.returning) { - return this.handleSelectQuery(data); - } - - return rowCount; - } - if (this.isBulkDeleteQuery()) { - return data[0] ? data[0].AFFECTEDROWS : 0; - } - if (this.isVersionQuery()) { - return data[0].version; - } - if (this.isForeignKeysQuery()) { - return data; - } - 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()) { - return [data, rowCount]; - } - return data; - } - - handleShowTablesQuery(results) { - return results.map(resultSet => { - return { - tableName: resultSet.TABLE_NAME, - schema: resultSet.TABLE_SCHEMA - }; - }); - } - - handleShowConstraintsQuery(data) { - //Convert snake_case keys to camelCase as it's generated by stored procedure - return data.slice(1).map(result => { - const constraint = {}; - for (const key in result) { - constraint[_.camelCase(key)] = result[key]; - } - return constraint; - }); - } - - formatError(err) { - let match; - - match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); - match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/); - if (match && match.length > 1) { - let fields = {}; - const uniqueKey = this.model && this.model.uniqueKeys[match[1]]; - let message = 'Validation error'; - - if (uniqueKey && !!uniqueKey.msg) { - message = uniqueKey.msg; - } - if (match[3]) { - const values = match[3].split(',').map(part => part.trim()); - if (uniqueKey) { - fields = _.zipObject(uniqueKey.fields, values); - } else { - fields[match[1]] = match[3]; - } - } - - const errors = []; - _.forOwn(fields, (value, field) => { - errors.push(new sequelizeErrors.ValidationErrorItem( - this.getUniqueConstraintErrorMessage(field), - 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB, - field, - value, - this.instance, - 'not_unique' - )); - }); - - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); - } - - match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./) || - err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./) || - err.message.match(/The (?:INSERT|MERGE|UPDATE) statement conflicted with the FOREIGN KEY constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./); - if (match && match.length > 0) { - return new sequelizeErrors.ForeignKeyConstraintError({ - fields: null, - index: match[1], - parent: err - }); - } - - match = err.message.match(/Could not drop constraint. See previous errors./); - if (match && match.length > 0) { - let constraint = err.sql.match(/(?:constraint|index) \[(.+?)\]/i); - constraint = constraint ? constraint[1] : undefined; - let table = err.sql.match(/table \[(.+?)\]/i); - table = table ? table[1] : undefined; - - return new sequelizeErrors.UnknownConstraintError({ - message: match[1], - constraint, - table, - parent: err - }); - } - - return new sequelizeErrors.DatabaseError(err); - } - - isShowOrDescribeQuery() { - let result = false; - - result = result || this.sql.toLowerCase().startsWith("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'"); - result = result || this.sql.toLowerCase().startsWith('select tablename = t.name, name = ind.name,'); - result = result || this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname'); - - return result; - } - - isShowIndexesQuery() { - return this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname'); - } - - handleShowIndexesQuery(data) { - // Group by index name, and collect all fields - data = data.reduce((acc, item) => { - if (!(item.index_name in acc)) { - acc[item.index_name] = item; - item.fields = []; - } - - item.index_keys.split(',').forEach(column => { - let columnName = column.trim(); - if (columnName.includes('(-)')) { - columnName = columnName.replace('(-)', ''); - } - - acc[item.index_name].fields.push({ - attribute: columnName, - length: undefined, - order: column.includes('(-)') ? 'DESC' : 'ASC', - collate: undefined - }); - }); - delete item.index_keys; - return acc; - }, {}); - - return _.map(data, item => ({ - primary: item.index_name.toLowerCase().startsWith('pk'), - fields: item.fields, - name: item.index_name, - tableName: undefined, - unique: item.index_description.toLowerCase().includes('unique'), - type: undefined - })); - } - - handleInsertQuery(results, metaData) { - if (this.instance) { - // add the inserted row id to the instance - const autoIncrementAttribute = this.model.autoIncrementAttribute; - let id = null; - let autoIncrementAttributeAlias = null; - - if (Object.prototype.hasOwnProperty.call(this.model.rawAttributes, autoIncrementAttribute) && - this.model.rawAttributes[autoIncrementAttribute].field !== undefined) - autoIncrementAttributeAlias = this.model.rawAttributes[autoIncrementAttribute].field; - - id = id || results && results[0][this.getInsertIdField()]; - id = id || metaData && metaData[this.getInsertIdField()]; - id = id || results && results[0][autoIncrementAttribute]; - 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; - } - } - } - - } - } -} - -module.exports = Query; -module.exports.Query = Query; -module.exports.default = Query; diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js deleted file mode 100644 index 1eda51826726..000000000000 --- a/lib/dialects/mysql/connection-manager.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict'; - -const AbstractConnectionManager = require('../abstract/connection-manager'); -const SequelizeErrors = require('../../errors'); -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 - * - * Get connections, validate and disconnect them. - * AbstractConnectionManager pooling use it to handle MySQL specific connections - * Use https://github.com/sidorares/node-mysql2 to connect with MySQL server - * - * @private - */ -class ConnectionManager extends AbstractConnectionManager { - constructor(dialect, sequelize) { - sequelize.config.port = sequelize.config.port || 3306; - super(dialect, sequelize); - this.lib = this._loadDialectModule('mysql2'); - this.refreshTypeParser(DataTypes); - } - - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - - static _typecast(field, next) { - if (parserStore.get(field.type)) { - return parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - } - - /** - * Connect with MySQL database based on config, Handle any errors in connection - * Set the pool handlers on connection.error - * Also set proper timezone once connection is connected. - * - * @param {object} config - * @returns {Promise} - * @private - */ - async connect(config) { - const connectionConfig = { - host: config.host, - port: config.port, - user: config.username, - flags: '-FOUND_ROWS', - password: config.password, - database: config.database, - timezone: this.sequelize.options.timezone, - typeCast: ConnectionManager._typecast.bind(this), - bigNumberStrings: false, - 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); - }; - - // 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); - } - } - } - - 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; - } - - return await promisify(callback => connection.end(callback))(); - } - - validate(connection) { - return connection - && !connection._fatalError - && !connection._protocolError - && !connection._closing - && !connection.stream.destroyed; - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/mysql/data-types.js b/lib/dialects/mysql/data-types.js deleted file mode 100644 index c0beec964da9..000000000000 --- a/lib/dialects/mysql/data-types.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict'; - -const wkx = require('wkx'); -const _ = require('lodash'); -const moment = require('moment-timezone'); -module.exports = BaseTypes => { - BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html'; - - /** - * 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 - */ - - BaseTypes.DATE.types.mysql = ['DATETIME']; - BaseTypes.STRING.types.mysql = ['VAR_STRING']; - BaseTypes.CHAR.types.mysql = ['STRING']; - BaseTypes.TEXT.types.mysql = ['BLOB']; - BaseTypes.TINYINT.types.mysql = ['TINY']; - BaseTypes.SMALLINT.types.mysql = ['SHORT']; - BaseTypes.MEDIUMINT.types.mysql = ['INT24']; - BaseTypes.INTEGER.types.mysql = ['LONG']; - BaseTypes.BIGINT.types.mysql = ['LONGLONG']; - BaseTypes.FLOAT.types.mysql = ['FLOAT']; - BaseTypes.TIME.types.mysql = ['TIME']; - BaseTypes.DATEONLY.types.mysql = ['DATE']; - BaseTypes.BOOLEAN.types.mysql = ['TINY']; - BaseTypes.BLOB.types.mysql = ['TINYBLOB', 'BLOB', 'LONGBLOB']; - BaseTypes.DECIMAL.types.mysql = ['NEWDECIMAL']; - BaseTypes.UUID.types.mysql = false; - BaseTypes.ENUM.types.mysql = false; - BaseTypes.REAL.types.mysql = ['DOUBLE']; - BaseTypes.DOUBLE.types.mysql = ['DOUBLE']; - BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY']; - BaseTypes.JSON.types.mysql = ['JSON']; - - class DECIMAL extends BaseTypes.DECIMAL { - toSql() { - let definition = super.toSql(); - if (this._unsigned) { - definition += ' UNSIGNED'; - } - if (this._zerofill) { - definition += ' ZEROFILL'; - } - return definition; - } - } - - class DATE extends BaseTypes.DATE { - toSql() { - return this._length ? `DATETIME(${this._length})` : 'DATETIME'; - } - _stringify(date, options) { - date = this._applyTimezone(date, options); - // Fractional DATETIMEs only supported on MySQL 5.6.4+ - if (this._length) { - return date.format('YYYY-MM-DD HH:mm:ss.SSS'); - } - return date.format('YYYY-MM-DD HH:mm:ss'); - } - static parse(value, options) { - value = value.string(); - if (value === null) { - return value; - } - if (moment.tz.zone(options.timezone)) { - value = moment.tz(value, options.timezone).toDate(); - } - else { - value = new Date(`${value} ${options.timezone}`); - } - return value; - } - } - - class DATEONLY extends BaseTypes.DATEONLY { - static parse(value) { - return value.string(); - } - } - class UUID extends BaseTypes.UUID { - toSql() { - return 'CHAR(36) BINARY'; - } - } - - const SUPPORTED_GEOMETRY_TYPES = ['POINT', 'LINESTRING', 'POLYGON']; - - class GEOMETRY extends BaseTypes.GEOMETRY { - constructor(type, srid) { - super(type, srid); - if (_.isEmpty(this.type)) { - this.sqlType = this.key; - return; - } - if (SUPPORTED_GEOMETRY_TYPES.includes(this.type)) { - this.sqlType = this.type; - return; - } - throw new Error(`Supported geometry types are: ${SUPPORTED_GEOMETRY_TYPES.join(', ')}`); - } - 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; - } - } - - class ENUM extends BaseTypes.ENUM { - toSql(options) { - return `ENUM(${this.values.map(value => options.escape(value)).join(', ')})`; - } - } - - class JSONTYPE extends BaseTypes.JSON { - _stringify(value, options) { - return options.operation === 'where' && typeof value === 'string' ? value : JSON.stringify(value); - } - } - - return { - ENUM, - DATE, - DATEONLY, - UUID, - GEOMETRY, - DECIMAL, - JSON: JSONTYPE - }; -}; diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js deleted file mode 100644 index 6b3f9cb313a7..000000000000 --- a/lib/dialects/mysql/index.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AbstractDialect = require('../abstract'); -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({ - _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.defaultVersion = '5.7.0'; -MysqlDialect.prototype.Query = Query; -MysqlDialect.prototype.QueryGenerator = QueryGenerator; -MysqlDialect.prototype.DataTypes = DataTypes; -MysqlDialect.prototype.name = 'mysql'; -MysqlDialect.prototype.TICK_CHAR = '`'; -MysqlDialect.prototype.TICK_CHAR_LEFT = MysqlDialect.prototype.TICK_CHAR; -MysqlDialect.prototype.TICK_CHAR_RIGHT = MysqlDialect.prototype.TICK_CHAR; - -module.exports = MysqlDialect; diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js deleted file mode 100644 index a9ccf5d9a29e..000000000000 --- a/lib/dialects/mysql/query-generator.js +++ /dev/null @@ -1,580 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const Utils = require('../../utils'); -const AbstractQueryGenerator = require('../abstract/query-generator'); -const util = require('util'); -const Op = require('../../operators'); - - -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']); - -class MySQLQueryGenerator extends AbstractQueryGenerator { - constructor(options) { - super(options); - - this.OperatorMap = { - ...this.OperatorMap, - [Op.regexp]: 'REGEXP', - [Op.notRegexp]: 'NOT REGEXP' - }; - } - - createDatabaseQuery(databaseName, options) { - options = { - charset: null, - 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)};`; - } - - createSchema() { - return 'SHOW TABLES'; - } - - showSchemasQuery() { - return 'SHOW TABLES'; - } - - versionQuery() { - return 'SELECT VERSION() as `version`'; - } - - createTableQuery(tableName, attributes, options) { - options = { - engine: 'InnoDB', - charset: null, - rowFormat: null, - ...options - }; - - const primaryKeys = []; - const foreignKeys = {}; - const attrStr = []; - - for (const attr in attributes) { - if (!Object.prototype.hasOwnProperty.call(attributes, attr)) continue; - const dataType = attributes[attr]; - let match; - - if (dataType.includes('PRIMARY KEY')) { - primaryKeys.push(attr); - - if (dataType.includes('REFERENCES')) { - // MySQL doesn't support inline REFERENCES declarations: move to the end - match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); - foreignKeys[attr] = match[2]; - } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); - } - } else if (dataType.includes('REFERENCES')) { - // MySQL doesn't support inline REFERENCES declarations: move to the end - match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); - foreignKeys[attr] = match[2]; - } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); - } - } - - const table = this.quoteTable(tableName); - let attributesClause = attrStr.join(', '); - const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); - - if (options.uniqueKeys) { - _.each(options.uniqueKeys, (columns, indexName) => { - if (columns.customIndex) { - if (typeof indexName !== 'string') { - indexName = `uniq_${tableName}_${columns.fields.join('_')}`; - } - attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; - } - }); - } - - if (pkString.length > 0) { - attributesClause += `, PRIMARY KEY (${pkString})`; - } - - for (const fkey in foreignKeys) { - if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { - attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; - } - } - - 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({ - tableName, - _schema: schema, - _schemaDelimiter: schemaDelimiter - }) - ); - - return `SHOW FULL COLUMNS FROM ${table};`; - } - - showTablesQuery(database) { - let query = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\''; - if (database) { - query += ` AND TABLE_SCHEMA = ${this.escape(database)}`; - } else { - query += ' AND TABLE_SCHEMA NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'SYS\')'; - } - return `${query};`; - } - - addColumnQuery(table, key, dataType) { - 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 Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'DROP', - this.quoteIdentifier(attributeName), - ';' - ]); - } - - changeColumnQuery(tableName, attributes) { - const attrString = []; - const constraintString = []; - - for (const attributeName in attributes) { - let definition = attributes[attributeName]; - if (definition.includes('REFERENCES')) { - const attrName = this.quoteIdentifier(attributeName); - definition = definition.replace(/.+?(?=REFERENCES)/, ''); - constraintString.push(`FOREIGN KEY (${attrName}) ${definition}`); - } else { - attrString.push(`\`${attributeName}\` \`${attributeName}\` ${definition}`); - } - } - - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - attrString.length && `CHANGE ${attrString.join(', ')}`, - constraintString.length && `ADD ${constraintString.join(', ')}`, - ';' - ]); - } - - renameColumnQuery(tableName, attrBefore, attributes) { - const attrString = []; - - for (const attrName in attributes) { - const definition = attributes[attrName]; - attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); - } - - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'CHANGE', - attrString.join(', '), - ';' - ]); - } - - handleSequelizeMethod(smth, tableName, factory, options, prepend) { - if (smth instanceof Utils.Json) { - // Parse nested object - if (smth.conditions) { - const conditions = this.parseConditionObject(smth.conditions).map(condition => - `${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'` - ); - - return conditions.join(' AND '); - } - if (smth.path) { - let str; - - // Allow specifying conditions using the sqlite json functions - if (this._checkValidJsonStatement(smth.path)) { - str = smth.path; - } else { - // Also support json property accessors - const paths = _.toPath(smth.path); - const column = paths.shift(); - str = this.jsonPathExtractionQuery(column, paths); - } - - if (smth.value) { - str += util.format(' = %s', this.escape(smth.value)); - } - - return str; - } - } else if (smth instanceof Utils.Cast) { - if (/timestamp/i.test(smth.type)) { - smth.type = 'datetime'; - } else if (smth.json && /boolean/i.test(smth.type)) { - // true or false cannot be casted as booleans within a JSON structure - smth.type = 'char'; - } else if (/double precision/i.test(smth.type) || /boolean/i.test(smth.type) || /integer/i.test(smth.type)) { - smth.type = 'decimal'; - } else if (/text/i.test(smth.type)) { - smth.type = 'char'; - } - } - - return super.handleSequelizeMethod(smth, tableName, factory, options, prepend); - } - - _toJSONValue(value) { - // true/false are stored as strings in mysql - if (typeof value === 'boolean') { - return value.toString(); - } - // null is stored as a string in mysql - if (value === null) { - return 'null'; - } - return value; - } - - truncateTableQuery(tableName) { - return `TRUNCATE ${this.quoteTable(tableName)}`; - } - - deleteQuery(tableName, where, options = {}, model) { - let limit = ''; - let query = `DELETE FROM ${this.quoteTable(tableName)}`; - - if (options.limit) { - limit = ` LIMIT ${this.escape(options.limit)}`; - } - - where = this.getWhereConditions(where, null, model, options); - - if (where) { - query += ` WHERE ${where}`; - } - - return query + limit; - } - - showIndexesQuery(tableName, options) { - return Utils.joinSQLFragments([ - `SHOW INDEX FROM ${this.quoteTable(tableName)}`, - options && options.database && `FROM \`${options.database}\`` - ]); - } - - showConstraintsQuery(table, constraintName) { - const tableName = table.tableName || table; - const schemaName = table.schema; - - return Utils.joinSQLFragments([ - 'SELECT CONSTRAINT_CATALOG AS constraintCatalog,', - 'CONSTRAINT_NAME AS constraintName,', - 'CONSTRAINT_SCHEMA AS constraintSchema,', - 'CONSTRAINT_TYPE AS constraintType,', - 'TABLE_NAME AS tableName,', - 'TABLE_SCHEMA AS tableSchema', - 'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS', - `WHERE table_name='${tableName}'`, - constraintName && `AND constraint_name = '${constraintName}'`, - schemaName && `AND TABLE_SCHEMA = '${schemaName}'`, - ';' - ]); - } - - removeIndexQuery(tableName, indexNameOrAttributes) { - let indexName = indexNameOrAttributes; - - if (typeof indexName !== 'string') { - indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); - } - - return Utils.joinSQLFragments([ - 'DROP INDEX', - this.quoteIdentifier(indexName), - 'ON', - this.quoteTable(tableName) - ]); - } - - attributeToSQL(attribute, options) { - if (!_.isPlainObject(attribute)) { - attribute = { - type: attribute - }; - } - - const attributeString = attribute.type.toString({ escape: this.escape.bind(this) }); - let template = attributeString; - - if (attribute.allowNull === false) { - template += ' NOT NULL'; - } - - if (attribute.autoIncrement) { - template += ' auto_increment'; - } - - // BLOB/TEXT/GEOMETRY/JSON cannot have a default value - if (!typeWithoutDefault.has(attributeString) - && attribute.type._binary !== true - && Utils.defaultValueSchemable(attribute.defaultValue)) { - template += ` DEFAULT ${this.escape(attribute.defaultValue)}`; - } - - if (attribute.unique === true) { - template += ' UNIQUE'; - } - - if (attribute.primaryKey) { - template += ' PRIMARY KEY'; - } - - if (attribute.comment) { - template += ` COMMENT ${this.escape(attribute.comment)}`; - } - - if (attribute.first) { - template += ' FIRST'; - } - if (attribute.after) { - template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; - } - - 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`); - - template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`; - } - - template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`; - - if (attribute.references.key) { - template += ` (${this.quoteIdentifier(attribute.references.key)})`; - } else { - template += ` (${this.quoteIdentifier('id')})`; - } - - if (attribute.onDelete) { - template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; - } - - if (attribute.onUpdate) { - template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; - } - } - - return template; - } - - attributesToSQL(attributes, options) { - const result = {}; - - for (const key in attributes) { - const attribute = attributes[key]; - result[attribute.field || key] = this.attributeToSQL(attribute, options); - } - - return result; - } - - /** - * Check whether the statmement is json function or simple path - * - * @param {string} stmt The statement to validate - * @returns {boolean} true if the given statement is json function - * @throws {Error} throw if the statement looks like json function but has invalid token - * @private - */ - _checkValidJsonStatement(stmt) { - if (typeof stmt !== 'string') { - return false; - } - - let currentIndex = 0; - let openingBrackets = 0; - let closingBrackets = 0; - let hasJsonFunction = false; - let hasInvalidToken = false; - - while (currentIndex < stmt.length) { - const string = stmt.substr(currentIndex); - const functionMatches = JSON_FUNCTION_REGEX.exec(string); - if (functionMatches) { - currentIndex += functionMatches[0].indexOf('('); - hasJsonFunction = true; - continue; - } - - const operatorMatches = JSON_OPERATOR_REGEX.exec(string); - if (operatorMatches) { - currentIndex += operatorMatches[0].length; - hasJsonFunction = true; - continue; - } - - const tokenMatches = TOKEN_CAPTURE_REGEX.exec(string); - if (tokenMatches) { - const capturedToken = tokenMatches[1]; - if (capturedToken === '(') { - openingBrackets++; - } else if (capturedToken === ')') { - closingBrackets++; - } else if (capturedToken === ';') { - hasInvalidToken = true; - break; - } - currentIndex += tokenMatches[0].length; - continue; - } - - break; - } - - // Check invalid json statement - if (hasJsonFunction && (hasInvalidToken || openingBrackets !== closingBrackets)) { - throw new Error(`Invalid json statement: ${stmt}`); - } - - // return true if the statement has valid json function - return hasJsonFunction; - } - - /** - * Generates an SQL query that returns all foreign keys of a 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 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 {string} columnName The name of the column. - * @returns {string} The generated sql query. - * @private - */ - getForeignKeyQuery(table, columnName) { - const quotedSchemaName = table.schema ? wrapSingleQuote(table.schema) : ''; - const quotedTableName = wrapSingleQuote(table.tableName || table); - const quotedColumnName = wrapSingleQuote(columnName); - - 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' - ], - ')' - ]); - } - - /** - * Generates an SQL query that removes a foreign key from a table. - * - * @param {string} tableName The name of the table. - * @param {string} foreignKey The name of the foreign key constraint. - * @returns {string} The generated sql query. - * @private - */ - dropForeignKeyQuery(tableName, foreignKey) { - return Utils.joinSQLFragments([ - 'ALTER TABLE', - this.quoteTable(tableName), - 'DROP FOREIGN KEY', - this.quoteIdentifier(foreignKey), - ';' - ]); - } -} - -// private methods -function wrapSingleQuote(identifier) { - return Utils.addTicks(identifier, '\''); -} - -module.exports = MySQLQueryGenerator; diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js deleted file mode 100644 index bcdec9d2e5a7..000000000000 --- a/lib/dialects/mysql/query-interface.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -const sequelizeErrors = require('../../errors'); -const { QueryInterface } = require('../abstract/query-interface'); -const QueryTypes = require('../../query-types'); - -/** - * The interface that Sequelize uses to talk with MySQL/MariaDB database - */ -class MySQLQueryInterface extends QueryInterface { - /** - * A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. - * - * @override - */ - async removeColumn(tableName, columnName, options) { - options = options || {}; - - const [results] = await this.sequelize.query( - this.queryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { - tableName, - schema: this.sequelize.config.database - }, columnName), - { raw: true, ...options } - ); - - //Exclude primary key constraint - if (results.length && results[0].constraint_name !== 'PRIMARY') { - await Promise.all(results.map(constraint => this.sequelize.query( - this.queryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), - { raw: true, ...options } - ))); - } - - return await this.sequelize.query( - this.queryGenerator.removeColumnQuery(tableName, columnName), - { raw: true, ...options } - ); - } - - /** - * @override - */ - async upsert(tableName, insertValues, updateValues, where, options) { - options = { ...options }; - - options.type = QueryTypes.UPSERT; - options.updateOnDuplicate = Object.keys(updateValues); - - const model = options.model; - const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options); - return await this.sequelize.query(sql, options); - } - - /** - * @override - */ - 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 }); - - const constraint = constraints[0]; - let query; - if (!constraint || !constraint.constraintType) { - throw new sequelizeErrors.UnknownConstraintError( - { - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } - - if (constraint.constraintType === 'FOREIGN KEY') { - query = this.queryGenerator.dropForeignKeyQuery(tableName, constraintName); - } else { - query = this.queryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); - } - - return await this.sequelize.query(query, options); - } -} - -exports.MySQLQueryInterface = MySQLQueryInterface; diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js deleted file mode 100644 index a0745a799bcc..000000000000 --- a/lib/dialects/mysql/query.js +++ /dev/null @@ -1,300 +0,0 @@ -'use strict'; - -const AbstractQuery = require('../abstract/query'); -const sequelizeErrors = require('../../errors'); -const _ = require('lodash'); -const { logger } = require('../../utils/logger'); - -const ER_DUP_ENTRY = 1062; -const ER_DEADLOCK = 1213; -const ER_ROW_IS_REFERENCED = 1451; -const ER_NO_REFERENCED_ROW = 1452; - -const debug = logger.debugContext('sql:mysql'); - -class Query extends AbstractQuery { - constructor(connection, sequelize, 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]); - return '?'; - } - return undefined; - }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; - return [sql, bindParam.length > 0 ? bindParam : undefined]; - } - - async run(sql, parameters) { - this.sql = sql; - const { connection, options } = this; - - const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; - - const complete = this._logQuery(sql, debug, parameters); - - if (parameters) { - debug('parameters(%j)', parameters); - } - - let results; - - 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 { - results = await new Promise((resolve, reject) => { - connection - .query({ sql }, (error, result) => error ? reject(error) : resolve(result)) - .setMaxListeners(100); - }); - } - } catch (error) { - if (options.transaction && error.errno === ER_DEADLOCK) { - // MySQL automatically rolls-back transactions in the event of a deadlock. - // However, we still initiate a manual rollback to ensure the connection gets released - see #13102. - try { - await options.transaction.rollback(); - } catch (error_) { - // Ignore errors - since MySQL automatically rolled back, we're - // not that worried about this redundant rollback failing. - } - - options.transaction.finished = 'rollback'; - } - - error.sql = sql; - error.parameters = parameters; - throw this.formatError(error); - } finally { - complete(); - } - - if (showWarnings && results && results.warningStatus > 0) { - await this.logWarnings(results); - } - return this.formatResults(results); - } - - /** - * High level function that handles the results of a query execution. - * - * - * Example: - * query.formatResults([ - * { - * id: 1, // this is from the main table - * attr2: 'snafu', // this is from the main table - * Tasks.id: 1, // this is from the associated table - * Tasks.title: 'task' // this is from the associated table - * } - * ]) - * - * @param {Array} data - The result of the query execution. - * @private - */ - formatResults(data) { - let result = this.instance; - - if (this.isInsertQuery(data)) { - this.handleInsertQuery(data); - - if (!this.instance) { - // handle bulkCreate AI primary key - if ( - data.constructor.name === 'ResultSetHeader' - && this.model - && this.model.autoIncrementAttribute - && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute - && this.model.rawAttributes[this.model.primaryKeyAttribute] - ) { - const startId = data[this.getInsertIdField()]; - result = []; - for (let i = startId; i < startId + data.affectedRows; i++) { - result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i }); - } - } else { - result = data[this.getInsertIdField()]; - } - } - } - - if (this.isSelectQuery()) { - return this.handleSelectQuery(data); - } - if (this.isShowTablesQuery()) { - return this.handleShowTablesQuery(data); - } - if (this.isDescribeQuery()) { - result = {}; - - for (const _result of data) { - const enumRegex = /^enum/i; - result[_result.Field] = { - type: enumRegex.test(_result.Type) ? _result.Type.replace(enumRegex, 'ENUM') : _result.Type.toUpperCase(), - allowNull: _result.Null === 'YES', - defaultValue: _result.Default, - primaryKey: _result.Key === 'PRI', - autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') - && _result.Extra.toLowerCase() === 'auto_increment', - comment: _result.Comment ? _result.Comment : null - }; - } - return result; - } - if (this.isShowIndexesQuery()) { - return this.handleShowIndexesQuery(data); - } - if (this.isCallQuery()) { - return data[0]; - } - if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) { - return data.affectedRows; - } - if (this.isVersionQuery()) { - return data[0].version; - } - if (this.isForeignKeysQuery()) { - return data; - } - if (this.isUpsertQuery()) { - return [result, data.affectedRows === 1]; - } - if (this.isInsertQuery() || this.isUpdateQuery()) { - return [result, data.affectedRows]; - } - if (this.isShowConstraintsQuery()) { - return data; - } - if (this.isRawQuery()) { - // MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta - return [data, data]; - } - - return result; - } - - 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); - - return results; - } - - formatError(err) { - const errCode = err.errno || err.code; - - switch (errCode) { - 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 fieldVal = match ? match[1] : undefined; - const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; - - if (uniqueKey) { - if (uniqueKey.msg) message = uniqueKey.msg; - fields = _.zipObject(uniqueKey.fields, values); - } else { - fields[fieldKey] = fieldVal; - } - - const errors = []; - _.forOwn(fields, (value, field) => { - errors.push(new sequelizeErrors.ValidationErrorItem( - this.getUniqueConstraintErrorMessage(field), - 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB, - field, - value, - this.instance, - 'not_unique' - )); - }); - - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); - } - - case ER_ROW_IS_REFERENCED: - case ER_NO_REFERENCED_ROW: { - // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) - const match = err.message.match( - /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ - ); - const quoteChar = match ? match[1] : '`'; - const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; - - return new sequelizeErrors.ForeignKeyConstraintError({ - 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 - }); - } - - default: - return new sequelizeErrors.DatabaseError(err); - } - } - - handleShowIndexesQuery(data) { - // Group by index name, and collect all fields - data = data.reduce((acc, item) => { - if (!(item.Key_name in acc)) { - acc[item.Key_name] = item; - item.fields = []; - } - - acc[item.Key_name].fields[item.Seq_in_index - 1] = { - attribute: item.Column_name, - length: item.Sub_part || undefined, - order: item.Collation === 'A' ? 'ASC' : undefined - }; - delete item.column_name; - - return acc; - }, {}); - - return _.map(data, item => ({ - primary: item.Key_name === 'PRIMARY', - fields: item.fields, - name: item.Key_name, - tableName: item.Table, - unique: item.Non_unique !== 1, - type: item.Index_type - })); - } -} - -module.exports = Query; -module.exports.Query = Query; -module.exports.default = Query; diff --git a/lib/dialects/parserStore.js b/lib/dialects/parserStore.js deleted file mode 100644 index b8bbb8df6d15..000000000000 --- a/lib/dialects/parserStore.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const stores = new Map(); - -module.exports = dialect => { - if (!stores.has(dialect)) { - stores.set(dialect, new Map()); - } - - return { - clear() { - stores.get(dialect).clear(); - }, - refresh(dataType) { - for (const type of dataType.types[dialect]) { - stores.get(dialect).set(type, dataType.parse); - } - }, - get(type) { - return stores.get(dialect).get(type); - } - }; -}; diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js deleted file mode 100644 index 0cf2f793743d..000000000000 --- a/lib/dialects/postgres/connection-manager.js +++ /dev/null @@ -1,320 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AbstractConnectionManager = require('../abstract/connection-manager'); -const { logger } = require('../../utils/logger'); -const debug = logger.debugContext('connection:pg'); -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) { - sequelize.config.port = sequelize.config.port || 5432; - super(dialect, sequelize); - - const pgLib = this._loadDialectModule('pg'); - this.lib = this.sequelize.config.native ? pgLib.native : pgLib; - - this._clearDynamicOIDs(); - this._clearTypeParser(); - this.refreshTypeParser(dataTypes.postgres); - } - - // Expose this as a method so that the parsing may be updated when the user has added additional, custom types - _refreshTypeParser(dataType) { - const arrayParserBuilder = parser => { - return value => this.lib.types.arrayParser.create(value, parser).parse(); - }; - const rangeParserBuilder = parser => { - return value => dataType.parse(value, { parser }); - }; - - // Set range parsers - if (dataType.key.toLowerCase() === 'range') { - for (const name in this.nameOidMap) { - const entry = this.nameOidMap[name]; - if (! entry.rangeOid) continue; - - const rangeParser = rangeParserBuilder(this.getTypeParser(entry.oid)); - const arrayRangeParser = arrayParserBuilder(rangeParser); - - this.oidParserMap.set(entry.rangeOid, rangeParser); - if (! entry.arrayRangeOid) continue; - this.oidParserMap.set(entry.arrayRangeOid, arrayRangeParser); - } - return; - } - - // Create parsers for normal or enum data types - const parser = value => dataType.parse(value); - const arrayParser = arrayParserBuilder(parser); - - // Set enum parsers - if (dataType.key.toLowerCase() === 'enum') { - this.enumOids.oids.forEach(oid => { - this.oidParserMap.set(oid, parser); - }); - this.enumOids.arrayOids.forEach(arrayOid => { - this.oidParserMap.set(arrayOid, arrayParser); - }); - return; - } - - // Set parsers for normal data types - dataType.types.postgres.forEach(name => { - if (! this.nameOidMap[name]) return; - this.oidParserMap.set(this.nameOidMap[name].oid, parser); - - if (! this.nameOidMap[name].arrayOid) return; - this.oidParserMap.set(this.nameOidMap[name].arrayOid, arrayParser); - }); - } - - _clearTypeParser() { - this.oidParserMap = new Map(); - } - - getTypeParser(oid, ...args) { - if (this.oidParserMap.get(oid)) return this.oidParserMap.get(oid); - - return this.lib.types.getTypeParser(oid, ...args); - } - - async connect(config) { - config.user = config.username; - const connectionConfig = _.pick(config, [ - 'user', 'password', 'host', 'database', 'port' - ]); - - connectionConfig.types = { - getTypeParser: ConnectionManager.prototype.getTypeParser.bind(this) - }; - - if (config.dialectOptions) { - _.merge(connectionConfig, - _.pick(config.dialectOptions, [ - // 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/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, - // you can use "auto" to determine the right encoding from the - // current locale in the client (LC_CTYPE environment variable on Unix systems) - 'client_encoding', - // !! DO NOT SET THIS TO TRUE !! - // (unless you know what you're doing) - // see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com] - 'binary', - // 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', - // 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' - ])); - } - - const connection = await new Promise((resolve, reject) => { - let responded = false; - - const connection = new this.lib.Client(connectionConfig); - - const parameterHandler = message => { - switch (message.parameterName) { - case 'server_version': - if (this.sequelize.options.databaseVersion === 0) { - const version = semver.coerce(message.parameterValue).version; - this.sequelize.options.databaseVersion = semver.valid(version) - ? version - : this.dialect.defaultVersion; - } - break; - case 'standard_conforming_strings': - connection['standard_conforming_strings'] = message.parameterValue; - break; - } - }; - - const endHandler = () => { - debug('connection timeout'); - if (!responded) { - reject(new sequelizeErrors.ConnectionTimedOutError(new Error('Connection timed out'))); - } - }; - - // If we didn't ever hear from the client.connect() callback the connection timeout - // node-postgres does not treat this as an error since no active query was ever emitted - connection.once('end', endHandler); - - if (!this.sequelize.config.native) { - // Receive various server parameters for further configuration - connection.connection.on('parameterStatus', parameterHandler); - } - - connection.connect(err => { - responded = true; - - if (!this.sequelize.config.native) { - // remove parameter handler - connection.connection.removeListener('parameterStatus', parameterHandler); - } - - if (err) { - if (err.code) { - switch (err.code) { - case 'ECONNREFUSED': - reject(new sequelizeErrors.ConnectionRefusedError(err)); - break; - case 'ENOTFOUND': - reject(new sequelizeErrors.HostNotFoundError(err)); - break; - case 'EHOSTUNREACH': - reject(new sequelizeErrors.HostNotReachableError(err)); - break; - case 'EINVAL': - reject(new sequelizeErrors.InvalidConnectionError(err)); - break; - default: - reject(new sequelizeErrors.ConnectionError(err)); - break; - } - } else { - reject(new sequelizeErrors.ConnectionError(err)); - } - } else { - debug('connection acquired'); - connection.removeListener('end', endHandler); - resolve(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};`; - } - - 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 (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; - } - - async disconnect(connection) { - if (connection._ending) { - debug('connection tried to disconnect but was already at ENDING state'); - return; - } - - return await promisify(callback => connection.end(callback))(); - } - - validate(connection) { - return !connection._invalid && !connection._ending; - } - - 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; - } - - 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' + - ' FROM pg_range LEFT OUTER JOIN pg_type ON pg_type.oid = pg_range.rngtypid' + - ')' + - 'SELECT pg_type.typname, pg_type.typtype, pg_type.oid, pg_type.typarray,' + - ' 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\'));' - ); - - 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: [] }; - - 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 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; - - this.refreshTypeParser(dataTypes.postgres); - } - - _clearDynamicOIDs() { - this.nameOidMap = {}; - this.enumOids = { oids: [], arrayOids: [] }; - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js deleted file mode 100644 index fc9ab0f420a7..000000000000 --- a/lib/dialects/postgres/data-types.js +++ /dev/null @@ -1,528 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const wkx = require('wkx'); - -module.exports = BaseTypes => { - const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'http://www.postgresql.org/docs/9.4/static/datatype.html'); - - /** - * Removes unsupported Postgres options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. - * - * @param {object} dataType The base integer data type. - * @private - */ - function removeUnsupportedIntegerOptions(dataType) { - if (dataType._length || dataType.options.length || dataType._unsigned || dataType._zerofill) { - warn(`PostgresSQL does not support '${dataType.key}' with LENGTH, UNSIGNED or ZEROFILL. Plain '${dataType.key}' will be used instead.`); - dataType._length = undefined; - dataType.options.length = undefined; - dataType._unsigned = undefined; - dataType._zerofill = undefined; - } - } - - /** - * types: - * { - * oids: [oid], - * array_oids: [oid] - * } - * - * @see oid here https://github.com/lib/pq/blob/master/oid/types.go - */ - - BaseTypes.UUID.types.postgres = ['uuid']; - 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']; - - class DATEONLY extends BaseTypes.DATEONLY { - _stringify(value, options) { - if (value === Infinity) { - return 'Infinity'; - } - if (value === -Infinity) { - return '-Infinity'; - } - return super._stringify(value, options); - } - _sanitize(value, options) { - if ((!options || options && !options.raw) && value !== Infinity && value !== -Infinity) { - if (typeof value === 'string') { - const lower = value.toLowerCase(); - if (lower === 'infinity') { - return Infinity; - } - if (lower === '-infinity') { - return -Infinity; - } - } - return super._sanitize(value); - } - return value; - } - static parse(value) { - if (value === 'infinity') { - return Infinity; - } - if (value === '-infinity') { - return -Infinity; - } - return value; - } - } - - BaseTypes.DATEONLY.types.postgres = ['date']; - - class DECIMAL extends BaseTypes.DECIMAL { - static parse(value) { - return value; - } - } - - // numeric - BaseTypes.DECIMAL.types.postgres = ['numeric']; - - class STRING extends BaseTypes.STRING { - toSql() { - if (this._binary) { - return 'BYTEA'; - } - return super.toSql(); - } - } - - BaseTypes.STRING.types.postgres = ['varchar']; - - class TEXT extends BaseTypes.TEXT { - toSql() { - if (this._length) { - warn('PostgreSQL does not support TEXT with options. Plain `TEXT` will be used instead.'); - this._length = undefined; - } - return 'TEXT'; - } - } - - BaseTypes.TEXT.types.postgres = ['text']; - - class CITEXT extends BaseTypes.CITEXT { - static parse(value) { - return value; - } - } - - BaseTypes.CITEXT.types.postgres = ['citext']; - - class CHAR extends BaseTypes.CHAR { - toSql() { - if (this._binary) { - return 'BYTEA'; - } - return super.toSql(); - } - } - - BaseTypes.CHAR.types.postgres = ['char', 'bpchar']; - - class BOOLEAN extends BaseTypes.BOOLEAN { - toSql() { - return 'BOOLEAN'; - } - _sanitize(value) { - if (value !== null && value !== undefined) { - if (Buffer.isBuffer(value) && value.length === 1) { - // Bit fields are returned as buffers - value = value[0]; - } - if (typeof value === 'string') { - // Only take action on valid boolean strings. - return value === 'true' || value === 't' ? true : value === 'false' || value === 'f' ? false : value; - } - if (typeof value === 'number') { - // Only take action on valid boolean integers. - return value === 1 ? true : value === 0 ? false : value; - } - } - return value; - } - } - - BOOLEAN.parse = BOOLEAN.prototype._sanitize; - - BaseTypes.BOOLEAN.types.postgres = ['bool']; - - class DATE extends BaseTypes.DATE { - toSql() { - return 'TIMESTAMP WITH TIME ZONE'; - } - validate(value) { - if (value !== Infinity && value !== -Infinity) { - return super.validate(value); - } - return true; - } - _stringify(value, options) { - if (value === Infinity) { - return 'Infinity'; - } - if (value === -Infinity) { - return '-Infinity'; - } - return super._stringify(value, options); - } - _sanitize(value, options) { - if ((!options || options && !options.raw) && !(value instanceof Date) && !!value && value !== Infinity && value !== -Infinity) { - if (typeof value === 'string') { - const lower = value.toLowerCase(); - if (lower === 'infinity') { - return Infinity; - } - if (lower === '-infinity') { - return -Infinity; - } - } - return new Date(value); - } - return value; - } - } - - BaseTypes.DATE.types.postgres = ['timestamptz']; - - class TINYINT extends BaseTypes.TINYINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - // int2 - BaseTypes.TINYINT.types.postgres = ['int2']; - - class SMALLINT extends BaseTypes.SMALLINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - // int2 - BaseTypes.SMALLINT.types.postgres = ['int2']; - - class INTEGER extends BaseTypes.INTEGER { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - INTEGER.parse = function parse(value) { - return parseInt(value, 10); - }; - - // int4 - BaseTypes.INTEGER.types.postgres = ['int4']; - - class BIGINT extends BaseTypes.BIGINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - // int8 - BaseTypes.BIGINT.types.postgres = ['int8']; - - class REAL extends BaseTypes.REAL { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - // float4 - BaseTypes.REAL.types.postgres = ['float4']; - - class DOUBLE extends BaseTypes.DOUBLE { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - // float8 - BaseTypes.DOUBLE.types.postgres = ['float8']; - - class FLOAT extends BaseTypes.FLOAT { - constructor(length, decimals) { - super(length, decimals); - // POSTGRES does only support lengths as parameter. - // Values between 1-24 result in REAL - // Values between 25-53 result in DOUBLE PRECISION - // If decimals are provided remove these and print a warning - if (this._decimals) { - warn('PostgreSQL does not support FLOAT with decimals. Plain `FLOAT` will be used instead.'); - this._length = undefined; - this.options.length = undefined; - this._decimals = undefined; - } - if (this._unsigned) { - warn('PostgreSQL does not support FLOAT unsigned. `UNSIGNED` was removed.'); - this._unsigned = undefined; - } - if (this._zerofill) { - warn('PostgreSQL does not support FLOAT zerofill. `ZEROFILL` was removed.'); - this._zerofill = undefined; - } - } - } - delete FLOAT.parse; // Float has no separate type in PG - - class BLOB extends BaseTypes.BLOB { - toSql() { - if (this._length) { - warn('PostgreSQL does not support BLOB (BYTEA) with options. Plain `BYTEA` will be used instead.'); - this._length = undefined; - } - return 'BYTEA'; - } - _hexify(hex) { - // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html - return `E'\\\\x${hex}'`; - } - } - - BaseTypes.BLOB.types.postgres = ['bytea']; - - class GEOMETRY extends BaseTypes.GEOMETRY { - toSql() { - let result = this.key; - if (this.type) { - result += `(${this.type}`; - if (this.srid) { - result += `,${this.srid}`; - } - result += ')'; - } - return result; - } - static parse(value) { - const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); - } - _stringify(value, options) { - return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; - } - _bindParam(value, options) { - return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; - } - } - - BaseTypes.GEOMETRY.types.postgres = ['geometry']; - - - class GEOGRAPHY extends BaseTypes.GEOGRAPHY { - toSql() { - let result = 'GEOGRAPHY'; - if (this.type) { - result += `(${this.type}`; - if (this.srid) { - result += `,${this.srid}`; - } - result += ')'; - } - return result; - } - static parse(value) { - const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); - } - _stringify(value, options) { - return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; - } - bindParam(value, options) { - return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; - } - } - - BaseTypes.GEOGRAPHY.types.postgres = ['geography']; - - let hstore; - - class HSTORE extends BaseTypes.HSTORE { - constructor() { - super(); - if (!hstore) { - // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated - hstore = require('./hstore'); - } - } - _value(value) { - if (!hstore) { - // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated - hstore = require('./hstore'); - } - return hstore.stringify(value); - } - _stringify(value) { - return `'${this._value(value)}'`; - } - _bindParam(value, options) { - return options.bindParam(this._value(value)); - } - static parse(value) { - if (!hstore) { - // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated - hstore = require('./hstore'); - } - return hstore.parse(value); - } - } - - HSTORE.prototype.escape = false; - - BaseTypes.HSTORE.types.postgres = ['hstore']; - - class RANGE extends BaseTypes.RANGE { - _value(values, options) { - if (!Array.isArray(values)) { - return this.options.subtype.stringify(values, options); - } - const valueInclusivity = [true, false]; - const valuesStringified = values.map((value, index) => { - if (_.isObject(value) && Object.prototype.hasOwnProperty.call(value, 'value')) { - if (Object.prototype.hasOwnProperty.call(value, 'inclusive')) { - valueInclusivity[index] = value.inclusive; - } - value = value.value; - } - if (value === null || value === -Infinity || value === Infinity) { - // Pass through "unbounded" bounds unchanged - return value; - } - if (this.options.subtype.stringify) { - return this.options.subtype.stringify(value, options); - } - return options.escape(value); - }); - // Array.map does not preserve extra array properties - valuesStringified.inclusive = valueInclusivity; - return range.stringify(valuesStringified); - } - _stringify(values, options) { - const value = this._value(values, options); - if (!Array.isArray(values)) { - return `'${value}'::${this.toCastType()}`; - } - return `'${value}'`; - } - _bindParam(values, options) { - const value = this._value(values, options); - if (!Array.isArray(values)) { - return `${options.bindParam(value)}::${this.toCastType()}`; - } - return options.bindParam(value); - } - toSql() { - return BaseTypes.RANGE.types.postgres.subtypes[this._subtype.toLowerCase()]; - } - toCastType() { - return BaseTypes.RANGE.types.postgres.castTypes[this._subtype.toLowerCase()]; - } - static parse(value, options = { parser: val => val }) { - return range.parse(value, options.parser); - } - } - const range = require('./range'); - - RANGE.prototype.escape = false; - - BaseTypes.RANGE.types.postgres = { - subtypes: { - integer: 'int4range', - decimal: 'numrange', - date: 'tstzrange', - dateonly: 'daterange', - bigint: 'int8range' - }, - castTypes: { - integer: 'int4', - decimal: 'numeric', - date: 'timestamptz', - dateonly: 'date', - bigint: 'int8' - } - }; - - // TODO: Why are base types being manipulated?? - BaseTypes.ARRAY.prototype.escape = false; - BaseTypes.ARRAY.prototype._value = function _value(values, options) { - return values.map(value => { - if (options && options.bindParam && this.type && this.type._value) { - return this.type._value(value, options); - } - if (this.type && this.type.stringify) { - value = this.type.stringify(value, options); - - if (this.type.escape === false) { - return value; - } - } - return options.escape(value); - }, this); - }; - BaseTypes.ARRAY.prototype._stringify = function _stringify(values, options) { - let str = `ARRAY[${this._value(values, options).join(',')}]`; - - if (this.type) { - const Utils = require('../../utils'); - let castKey = this.toSql(); - - if (this.type instanceof BaseTypes.ENUM) { - castKey = `${Utils.addTicks( - Utils.generateEnumName(options.field.Model.getTableName(), options.field.field), - '"' - ) }[]`; - } - - str += `::${castKey}`; - } - - return str; - }; - BaseTypes.ARRAY.prototype._bindParam = function _bindParam(values, options) { - return options.bindParam(this._value(values, options)); - }; - - class ENUM extends BaseTypes.ENUM { - static parse(value) { - return value; - } - } - - BaseTypes.ENUM.types.postgres = [null]; - - return { - DECIMAL, - BLOB, - STRING, - CHAR, - TEXT, - CITEXT, - TINYINT, - SMALLINT, - INTEGER, - BIGINT, - BOOLEAN, - DATE, - DATEONLY, - REAL, - 'DOUBLE PRECISION': DOUBLE, - FLOAT, - GEOMETRY, - GEOGRAPHY, - HSTORE, - RANGE, - ENUM - }; -}; diff --git a/lib/dialects/postgres/hstore.js b/lib/dialects/postgres/hstore.js deleted file mode 100644 index 77a034684aa6..000000000000 --- a/lib/dialects/postgres/hstore.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const hstore = require('pg-hstore')({ sanitize: true }); - -function stringify(data) { - if (data === null) return null; - return hstore.stringify(data); -} -exports.stringify = stringify; - -function parse(value) { - if (value === null) return null; - return hstore.parse(value); -} -exports.parse = parse; diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js deleted file mode 100644 index 3d23f6ed6c6d..000000000000 --- a/lib/dialects/postgres/index.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AbstractDialect = require('../abstract'); -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({ - _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, - operator: true - }, - inserts: { - onConflictDoNothing: ' ON CONFLICT DO NOTHING', - updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' - }, - NUMERIC: true, - ARRAY: true, - RANGE: true, - GEOMETRY: true, - REGEXP: true, - GEOGRAPHY: true, - JSON: true, - JSONB: true, - HSTORE: true, - TSVECTOR: true, - deferrableConstraints: true, - searchPath: true -}); - -PostgresDialect.prototype.defaultVersion = '9.5.0'; -PostgresDialect.prototype.Query = Query; -PostgresDialect.prototype.DataTypes = DataTypes; -PostgresDialect.prototype.name = 'postgres'; -PostgresDialect.prototype.TICK_CHAR = '"'; -PostgresDialect.prototype.TICK_CHAR_LEFT = PostgresDialect.prototype.TICK_CHAR; -PostgresDialect.prototype.TICK_CHAR_RIGHT = PostgresDialect.prototype.TICK_CHAR; - -module.exports = PostgresDialect; -module.exports.default = PostgresDialect; -module.exports.PostgresDialect = PostgresDialect; diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js deleted file mode 100644 index f5d10c949298..000000000000 --- a/lib/dialects/postgres/query-generator.js +++ /dev/null @@ -1,905 +0,0 @@ -'use strict'; - -const Utils = require('../../utils'); -const util = require('util'); -const DataTypes = require('../../data-types'); -const AbstractQueryGenerator = require('../abstract/query-generator'); -const semver = require('semver'); -const _ = require('lodash'); - -class PostgresQueryGenerator extends AbstractQueryGenerator { - setSearchPath(searchPath) { - return `SET search_path to ${searchPath};`; - } - - createDatabaseQuery(databaseName, options) { - options = { - encoding: null, - collate: null, - ...options - }; - - const values = { - database: this.quoteTable(databaseName), - encoding: options.encoding ? ` ENCODING = ${this.escape(options.encoding)}` : '', - collation: options.collate ? ` LC_COLLATE = ${this.escape(options.collate)}` : '', - ctype: options.ctype ? ` LC_CTYPE = ${this.escape(options.ctype)}` : '', - template: options.template ? ` TEMPLATE = ${this.escape(options.template)}` : '' - }; - - return `CREATE DATABASE ${values.database}${values.encoding}${values.collation}${values.ctype}${values.template};`; - } - - dropDatabaseQuery(databaseName) { - return `DROP DATABASE IF EXISTS ${this.quoteTable(databaseName)};`; - } - - createSchema(schema) { - const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0); - - if (databaseVersion && semver.gte(databaseVersion, '9.2.0')) { - return `CREATE SCHEMA IF NOT EXISTS ${schema};`; - } - - return `CREATE SCHEMA ${schema};`; - } - - dropSchema(schema) { - return `DROP SCHEMA IF EXISTS ${schema} CASCADE;`; - } - - showSchemasQuery() { - return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';"; - } - - versionQuery() { - return 'SHOW SERVER_VERSION'; - } - - createTableQuery(tableName, attributes, options) { - options = { ...options }; - - //Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do - const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0); - const attrStr = []; - let comments = ''; - let columnComments = ''; - - const quotedTable = this.quoteTable(tableName); - - if (options.comment && typeof options.comment === 'string') { - comments += `; COMMENT ON TABLE ${quotedTable} IS ${this.escape(options.comment)}`; - } - - for (const attr in attributes) { - const quotedAttr = this.quoteIdentifier(attr); - const i = attributes[attr].indexOf('COMMENT '); - if (i !== -1) { - // Move comment to a separate query - const escapedCommentText = this.escape(attributes[attr].substring(i + 8)); - columnComments += `; COMMENT ON COLUMN ${quotedTable}.${quotedAttr} IS ${escapedCommentText}`; - attributes[attr] = attributes[attr].substring(0, i); - } - - const dataType = this.dataTypeMapping(tableName, attr, attributes[attr]); - attrStr.push(`${quotedAttr} ${dataType}`); - } - - - let attributesClause = attrStr.join(', '); - - if (options.uniqueKeys) { - _.each(options.uniqueKeys, columns => { - if (columns.customIndex) { - attributesClause += `, UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; - } - }); - } - - const pks = _.reduce(attributes, (acc, attribute, key) => { - if (attribute.includes('PRIMARY KEY')) { - acc.push(this.quoteIdentifier(key)); - } - return acc; - }, []).join(','); - - if (pks.length > 0) { - attributesClause += `, PRIMARY KEY (${pks})`; - } - - return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${quotedTable} (${attributesClause})${comments}${columnComments};`; - } - - dropTableQuery(tableName, options) { - options = options || {}; - return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`; - } - - showTablesQuery() { - return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type LIKE '%TABLE' AND table_name != 'spatial_ref_sys';"; - } - - describeTableQuery(tableName, schema) { - if (!schema) schema = 'public'; - - return 'SELECT ' + - 'pk.constraint_type as "Constraint",' + - 'c.column_name as "Field", ' + - 'c.column_default as "Default",' + - 'c.is_nullable as "Null", ' + - '(CASE WHEN c.udt_name = \'hstore\' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN \'(\' || c.character_maximum_length || \')\' ELSE \'\' END) as "Type", ' + - '(SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", ' + - '(SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" ' + - 'FROM information_schema.columns c ' + - '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 ' + - '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 ' + - 'ON pk.table_schema=c.table_schema ' + - 'AND pk.table_name=c.table_name ' + - 'AND pk.column_name=c.column_name ' + - `WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `; - } - - /** - * Check whether the statmement is json function or simple path - * - * @param {string} stmt The statement to validate - * @returns {boolean} true if the given statement is json function - * @throws {Error} throw if the statement looks like json function but has invalid token - */ - _checkValidJsonStatement(stmt) { - if (typeof stmt !== 'string') { - return false; - } - - // https://www.postgresql.org/docs/current/static/functions-json.html - 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; - - let currentIndex = 0; - let openingBrackets = 0; - let closingBrackets = 0; - let hasJsonFunction = false; - let hasInvalidToken = false; - - while (currentIndex < stmt.length) { - const string = stmt.substr(currentIndex); - const functionMatches = jsonFunctionRegex.exec(string); - if (functionMatches) { - currentIndex += functionMatches[0].indexOf('('); - hasJsonFunction = true; - continue; - } - - const operatorMatches = jsonOperatorRegex.exec(string); - if (operatorMatches) { - currentIndex += operatorMatches[0].length; - hasJsonFunction = true; - continue; - } - - const tokenMatches = tokenCaptureRegex.exec(string); - if (tokenMatches) { - const capturedToken = tokenMatches[1]; - if (capturedToken === '(') { - openingBrackets++; - } else if (capturedToken === ')') { - closingBrackets++; - } else if (capturedToken === ';') { - hasInvalidToken = true; - break; - } - currentIndex += tokenMatches[0].length; - continue; - } - - break; - } - - // Check invalid json statement - hasInvalidToken |= openingBrackets !== closingBrackets; - if (hasJsonFunction && hasInvalidToken) { - throw new Error(`Invalid json statement: ${stmt}`); - } - - // return true if the statement has valid json function - return hasJsonFunction; - } - - handleSequelizeMethod(smth, tableName, factory, options, prepend) { - if (smth instanceof Utils.Json) { - // Parse nested object - if (smth.conditions) { - const conditions = this.parseConditionObject(smth.conditions).map(condition => - `${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'` - ); - - return conditions.join(' AND '); - } - if (smth.path) { - let str; - - // Allow specifying conditions using the postgres json syntax - if (this._checkValidJsonStatement(smth.path)) { - str = smth.path; - } else { - // Also support json property accessors - const paths = _.toPath(smth.path); - const column = paths.shift(); - str = this.jsonPathExtractionQuery(column, paths); - } - - if (smth.value) { - str += util.format(' = %s', this.escape(smth.value)); - } - - return str; - } - } - return super.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend); - } - - 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 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; - } - - removeColumnQuery(tableName, attributeName) { - const quotedTableName = this.quoteTable(this.extractTableDetails(tableName)); - const quotedAttributeName = this.quoteIdentifier(attributeName); - return `ALTER TABLE ${quotedTableName} DROP COLUMN ${quotedAttributeName};`; - } - - changeColumnQuery(tableName, attributes) { - const query = subQuery => `ALTER TABLE ${this.quoteTable(tableName)} ALTER COLUMN ${subQuery};`; - const sql = []; - for (const attributeName in attributes) { - let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]); - let attrSql = ''; - - if (definition.includes('NOT NULL')) { - attrSql += query(`${this.quoteIdentifier(attributeName)} SET NOT NULL`); - - definition = definition.replace('NOT NULL', '').trim(); - } else if (!definition.includes('REFERENCES')) { - attrSql += query(`${this.quoteIdentifier(attributeName)} DROP NOT NULL`); - } - - if (definition.includes('DEFAULT')) { - attrSql += query(`${this.quoteIdentifier(attributeName)} SET DEFAULT ${definition.match(/DEFAULT ([^;]+)/)[1]}`); - - definition = definition.replace(/(DEFAULT[^;]+)/, '').trim(); - } else if (!definition.includes('REFERENCES')) { - attrSql += query(`${this.quoteIdentifier(attributeName)} DROP DEFAULT`); - } - - if (attributes[attributeName].startsWith('ENUM(')) { - attrSql += this.pgEnum(tableName, attributeName, attributes[attributeName]); - definition = definition.replace(/^ENUM\(.+\)/, this.pgEnumName(tableName, attributeName, { schema: false })); - definition += ` USING (${this.quoteIdentifier(attributeName)}::${this.pgEnumName(tableName, attributeName)})`; - } - - if (definition.match(/UNIQUE;*$/)) { - definition = definition.replace(/UNIQUE;*$/, ''); - attrSql += query(`ADD UNIQUE (${this.quoteIdentifier(attributeName)})`).replace('ALTER COLUMN', ''); - } - - if (definition.includes('REFERENCES')) { - definition = definition.replace(/.+?(?=REFERENCES)/, ''); - attrSql += query(`ADD FOREIGN KEY (${this.quoteIdentifier(attributeName)}) ${definition}`).replace('ALTER COLUMN', ''); - } else { - attrSql += query(`${this.quoteIdentifier(attributeName)} TYPE ${definition}`); - } - - sql.push(attrSql); - } - - return sql.join(''); - } - - renameColumnQuery(tableName, attrBefore, attributes) { - - const attrString = []; - - for (const attributeName in attributes) { - attrString.push(`${this.quoteIdentifier(attrBefore)} TO ${this.quoteIdentifier(attributeName)}`); - } - - return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`; - } - - fn(fnName, tableName, parameters, body, returns, language) { - fnName = fnName || 'testfunc'; - language = language || 'plpgsql'; - returns = returns ? `RETURNS ${returns}` : ''; - parameters = parameters || ''; - - return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`; - } - - truncateTableQuery(tableName, options = {}) { - return [ - `TRUNCATE ${this.quoteTable(tableName)}`, - options.restartIdentity ? ' RESTART IDENTITY' : '', - options.cascade ? ' CASCADE' : '' - ].join(''); - } - - deleteQuery(tableName, where, options = {}, model) { - const table = this.quoteTable(tableName); - let whereClause = this.getWhereConditions(where, null, model, options); - const limit = options.limit ? ` LIMIT ${this.escape(options.limit)}` : ''; - let primaryKeys = ''; - let primaryKeysSelection = ''; - - if (whereClause) { - whereClause = ` WHERE ${whereClause}`; - } - - if (options.limit) { - if (!model) { - throw new Error('Cannot LIMIT delete without a model.'); - } - - const pks = Object.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(','); - - primaryKeys = model.primaryKeyAttributes.length > 1 ? `(${pks})` : pks; - primaryKeysSelection = pks; - - return `DELETE FROM ${table} WHERE ${primaryKeys} IN (SELECT ${primaryKeysSelection} FROM ${table}${whereClause}${limit})`; - } - return `DELETE FROM ${table}${whereClause}`; - } - - showIndexesQuery(tableName) { - let schemaJoin = ''; - let schemaWhere = ''; - if (typeof tableName !== 'string') { - schemaJoin = ', pg_namespace s'; - schemaWhere = ` AND s.oid = t.relnamespace AND s.nspname = '${tableName.schema}'`; - tableName = tableName.tableName; - } - - // This is ARCANE! - return 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' + - 'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' + - `AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a${schemaJoin} ` + - 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' + - `t.relkind = 'r' and t.relname = '${tableName}'${schemaWhere} ` + - 'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;'; - } - - showConstraintsQuery(tableName) { - //Postgres converts camelCased alias to lowercase unless quoted - return [ - 'SELECT constraint_catalog AS "constraintCatalog",', - 'constraint_schema AS "constraintSchema",', - 'constraint_name AS "constraintName",', - 'table_catalog AS "tableCatalog",', - 'table_schema AS "tableSchema",', - 'table_name AS "tableName",', - 'constraint_type AS "constraintType",', - 'is_deferrable AS "isDeferrable",', - 'initially_deferred AS "initiallyDeferred"', - 'from INFORMATION_SCHEMA.table_constraints', - `WHERE table_name='${tableName}';` - ].join(' '); - } - - removeIndexQuery(tableName, indexNameOrAttributes) { - let indexName = indexNameOrAttributes; - - if (typeof indexName !== 'string') { - indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); - } - - return `DROP INDEX IF EXISTS ${this.quoteIdentifiers(indexName)}`; - } - - addLimitAndOffset(options) { - let fragment = ''; - /* eslint-disable */ - if (options.limit != null) { - fragment += ' LIMIT ' + this.escape(options.limit); - } - if (options.offset != null) { - fragment += ' OFFSET ' + this.escape(options.offset); - } - /* eslint-enable */ - - return fragment; - } - - attributeToSQL(attribute, options) { - if (!_.isPlainObject(attribute)) { - attribute = { - type: attribute - }; - } - - let type; - if ( - attribute.type instanceof DataTypes.ENUM || - attribute.type instanceof DataTypes.ARRAY && attribute.type.type instanceof DataTypes.ENUM - ) { - const enumType = attribute.type.type || attribute.type; - let values = attribute.values; - - if (enumType.values && !attribute.values) { - values = enumType.values; - } - - if (Array.isArray(values) && values.length > 0) { - type = `ENUM(${values.map(value => this.escape(value)).join(', ')})`; - - if (attribute.type instanceof DataTypes.ARRAY) { - type += '[]'; - } - - } else { - throw new Error("Values for ENUM haven't been defined."); - } - } - - if (!type) { - type = attribute.type; - } - - let sql = type.toString(); - - if (Object.prototype.hasOwnProperty.call(attribute, 'allowNull') && !attribute.allowNull) { - sql += ' NOT NULL'; - } - - if (attribute.autoIncrement) { - if (attribute.autoIncrementIdentity) { - sql += ' GENERATED BY DEFAULT AS IDENTITY'; - } else { - sql += ' SERIAL'; - } - } - - if (Utils.defaultValueSchemable(attribute.defaultValue)) { - sql += ` DEFAULT ${this.escape(attribute.defaultValue, attribute)}`; - } - - if (attribute.unique === true) { - sql += ' UNIQUE'; - } - - if (attribute.primaryKey) { - sql += ' PRIMARY KEY'; - } - - if (attribute.references) { - let referencesTable = this.quoteTable(attribute.references.model); - let schema; - - if (options.schema) { - schema = options.schema; - } else if ( - (!attribute.references.model || typeof attribute.references.model == 'string') - && options.table - && options.table.schema - ) { - schema = options.table.schema; - } - - if (schema) { - referencesTable = this.quoteTable(this.addSchema({ - tableName: referencesTable, - _schema: schema - })); - } - - let referencesKey; - - if (attribute.references.key) { - referencesKey = this.quoteIdentifiers(attribute.references.key); - } else { - referencesKey = this.quoteIdentifier('id'); - } - - sql += ` REFERENCES ${referencesTable} (${referencesKey})`; - - if (attribute.onDelete) { - sql += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; - } - - if (attribute.onUpdate) { - sql += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; - } - - if (attribute.references.deferrable) { - sql += ` ${attribute.references.deferrable.toString(this)}`; - } - } - - if (attribute.comment && typeof attribute.comment === 'string') { - if (options && (options.context === 'addColumn' || options.context === 'changeColumn')) { - const quotedAttr = this.quoteIdentifier(options.key); - const escapedCommentText = this.escape(attribute.comment); - sql += `; COMMENT ON COLUMN ${this.quoteTable(options.table)}.${quotedAttr} IS ${escapedCommentText}`; - } else { - // for createTable event which does it's own parsing - // TODO: centralize creation of comment statements here - sql += ` COMMENT ${attribute.comment}`; - } - } - - return sql; - } - - deferConstraintsQuery(options) { - return options.deferrable.toString(this); - } - - setConstraintQuery(columns, type) { - let columnFragment = 'ALL'; - - if (columns) { - columnFragment = columns.map(column => this.quoteIdentifier(column)).join(', '); - } - - return `SET CONSTRAINTS ${columnFragment} ${type}`; - } - - setDeferredQuery(columns) { - return this.setConstraintQuery(columns, 'DEFERRED'); - } - - setImmediateQuery(columns) { - return this.setConstraintQuery(columns, 'IMMEDIATE'); - } - - attributesToSQL(attributes, options) { - const result = {}; - - for (const key in attributes) { - const attribute = attributes[key]; - result[attribute.field || key] = this.attributeToSQL(attribute, { key, ...options }); - } - - return result; - } - - createTrigger(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) { - const decodedEventType = this.decodeTriggerEventType(eventType); - const eventSpec = this.expandTriggerEventSpec(fireOnSpec); - const expandedOptions = this.expandOptions(optionsArray); - 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});`; - } - - dropTrigger(tableName, triggerName) { - return `DROP TRIGGER ${this.quoteIdentifier(triggerName)} ON ${this.quoteTable(tableName)} RESTRICT;`; - } - - renameTrigger(tableName, oldTriggerName, newTriggerName) { - return `ALTER TRIGGER ${this.quoteIdentifier(oldTriggerName)} ON ${this.quoteTable(tableName)} RENAME TO ${this.quoteIdentifier(newTriggerName)};`; - } - - 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 expandedOptionsArray = this.expandOptions(optionsArray); - - const statement = options && options.force ? 'CREATE OR REPLACE FUNCTION' : 'CREATE FUNCTION'; - - return `${statement} ${functionName}(${paramList}) RETURNS ${returnType} AS $func$ ${variableList} BEGIN ${body} END; $func$ language '${language}'${expandedOptionsArray};`; - } - - 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); - return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`; - } - - renameFunction(oldFunctionName, params, newFunctionName) { - const paramList = this._expandFunctionParamList(params); - return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`; - } - - pgEscapeAndQuote(val) { - return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")); - } - - _expandFunctionParamList(params) { - if (params === undefined || !Array.isArray(params)) { - throw new Error('_expandFunctionParamList: function parameters array required, including an empty one for no arguments'); - } - - const paramList = []; - params.forEach(curParam => { - const paramDef = []; - if (curParam.type) { - if (curParam.direction) { paramDef.push(curParam.direction); } - if (curParam.name) { paramDef.push(curParam.name); } - paramDef.push(curParam.type); - } else { - throw new Error('function or trigger used with a parameter without any type'); - } - - const joined = paramDef.join(' '); - if (joined) paramList.push(joined); - - }); - - return paramList.join(', '); - } - - _expandFunctionVariableList(variables) { - if (!Array.isArray(variables)) { - throw new Error('_expandFunctionVariableList: function variables must be an array'); - } - const variableDefinitions = []; - variables.forEach(variable => { - if (!variable.name || !variable.type) { - throw new Error('function variable must have a name and type'); - } - let variableDefinition = `DECLARE ${variable.name} ${variable.type}`; - if (variable.default) { - variableDefinition += ` := ${variable.default}`; - } - variableDefinition += ';'; - variableDefinitions.push(variableDefinition); - }); - return variableDefinitions.join(' '); - } - - expandOptions(options) { - return options === undefined || _.isEmpty(options) ? - '' : options.join(' '); - } - - decodeTriggerEventType(eventSpecifier) { - const EVENT_DECODER = { - 'after': 'AFTER', - 'before': 'BEFORE', - 'instead_of': 'INSTEAD OF', - 'after_constraint': 'AFTER' - }; - - if (!EVENT_DECODER[eventSpecifier]) { - throw new Error(`Invalid trigger event specified: ${eventSpecifier}`); - } - - return EVENT_DECODER[eventSpecifier]; - } - - triggerEventTypeIsConstraint(eventSpecifier) { - return eventSpecifier === 'after_constraint' ? 'CONSTRAINT ' : ''; - } - - expandTriggerEventSpec(fireOnSpec) { - if (_.isEmpty(fireOnSpec)) { - throw new Error('no table change events specified to trigger on'); - } - - return _.map(fireOnSpec, (fireValue, fireKey) => { - const EVENT_MAP = { - 'insert': 'INSERT', - 'update': 'UPDATE', - 'delete': 'DELETE', - 'truncate': 'TRUNCATE' - }; - - if (!EVENT_MAP[fireValue]) { - throw new Error(`parseTriggerEventSpec: undefined trigger event ${fireKey}`); - } - - let eventSpec = EVENT_MAP[fireValue]; - if (eventSpec === 'UPDATE') { - if (Array.isArray(fireValue) && fireValue.length > 0) { - eventSpec += ` OF ${fireValue.join(', ')}`; - } - } - - return eventSpec; - }).join(' OR '); - } - - pgEnumName(tableName, attr, options) { - options = options || {}; - - const tableDetails = this.extractTableDetails(tableName, options); - let enumName = Utils.addTicks(Utils.generateEnumName(tableDetails.tableName, attr), '"'); - - // pgListEnums requires the enum name only, without the schema - if (options.schema !== false && tableDetails.schema) { - enumName = this.quoteIdentifier(tableDetails.schema) + tableDetails.delimiter + enumName; - } - - return enumName; - } - - pgListEnums(tableName, attrName, options) { - let enumName = ''; - const tableDetails = this.extractTableDetails(tableName, options); - - if (tableDetails.tableName && attrName) { - enumName = ` AND t.typname=${this.pgEnumName(tableDetails.tableName, attrName, { schema: false }).replace(/"/g, "'")}`; - } - - return 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t ' + - 'JOIN pg_enum e ON t.oid = e.enumtypid ' + - 'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' + - `WHERE n.nspname = '${tableDetails.schema}'${enumName} GROUP BY 1`; - } - - pgEnum(tableName, attr, dataType, options) { - const enumName = this.pgEnumName(tableName, attr, options); - let values; - - if (dataType.values) { - values = `ENUM(${dataType.values.map(value => this.escape(value)).join(', ')})`; - } else { - values = dataType.toString().match(/^ENUM\(.+\)/)[0]; - } - - let sql = `CREATE TYPE ${enumName} AS ${values};`; - if (!!options && options.force === true) { - sql = this.pgEnumDrop(tableName, attr) + sql; - } - return sql; - } - - pgEnumAdd(tableName, attr, value, options) { - const enumName = this.pgEnumName(tableName, attr); - let sql = `ALTER TYPE ${enumName} ADD VALUE `; - - if (semver.gte(this.sequelize.options.databaseVersion, '9.3.0')) { - sql += 'IF NOT EXISTS '; - } - sql += this.escape(value); - - if (options.before) { - sql += ` BEFORE ${this.escape(options.before)}`; - } else if (options.after) { - sql += ` AFTER ${this.escape(options.after)}`; - } - - return sql; - } - - pgEnumDrop(tableName, attr, enumName) { - enumName = enumName || this.pgEnumName(tableName, attr); - return `DROP TYPE IF EXISTS ${enumName}; `; - } - - fromArray(text) { - text = text.replace(/^{/, '').replace(/}$/, ''); - let matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig); - - if (matches.length < 1) { - return []; - } - - matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/g, '')); - - return matches.slice(0, -1); - } - - dataTypeMapping(tableName, attr, dataType) { - if (dataType.includes('PRIMARY KEY')) { - dataType = dataType.replace('PRIMARY KEY', ''); - } - - if (dataType.includes('SERIAL')) { - if (dataType.includes('BIGINT')) { - dataType = dataType.replace('SERIAL', 'BIGSERIAL'); - dataType = dataType.replace('BIGINT', ''); - } else if (dataType.includes('SMALLINT')) { - dataType = dataType.replace('SERIAL', 'SMALLSERIAL'); - dataType = dataType.replace('SMALLINT', ''); - } else { - dataType = dataType.replace('INTEGER', ''); - } - dataType = dataType.replace('NOT NULL', ''); - } - - if (dataType.startsWith('ENUM(')) { - dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEnumName(tableName, attr)); - } - - return dataType; - } - - /** - * Generates an SQL query that returns all foreign keys of a table. - * - * @param {string} tableName The name of the table. - * @returns {string} The generated sql query. - * @private - */ - getForeignKeysQuery(tableName) { - return 'SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r ' + - `WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '${tableName}' LIMIT 1) AND r.contype = 'f' ORDER BY 1;`; - } - - /** - * Generate common SQL prefix for getForeignKeyReferencesQuery. - * - * @returns {string} - */ - _getForeignKeyReferencesQueryPrefix() { - return 'SELECT ' + - 'DISTINCT tc.constraint_name as constraint_name, ' + - 'tc.constraint_schema as constraint_schema, ' + - 'tc.constraint_catalog as constraint_catalog, ' + - 'tc.table_name as table_name,' + - 'tc.table_schema as table_schema,' + - 'tc.table_catalog as table_catalog,' + - 'kcu.column_name as column_name,' + - 'ccu.table_schema AS referenced_table_schema,' + - 'ccu.table_catalog AS referenced_table_catalog,' + - 'ccu.table_name AS referenced_table_name,' + - 'ccu.column_name AS referenced_column_name ' + - 'FROM information_schema.table_constraints AS tc ' + - 'JOIN information_schema.key_column_usage AS kcu ' + - 'ON tc.constraint_name = kcu.constraint_name ' + - 'JOIN information_schema.constraint_column_usage AS ccu ' + - 'ON ccu.constraint_name = tc.constraint_name '; - } - - /** - * Generates an SQL query that returns all foreign keys details of a table. - * - * As for getForeignKeysQuery is not compatible with getForeignKeyReferencesQuery, so add a new function. - * - * @param {string} tableName - * @param {string} catalogName - * @param {string} schemaName - */ - getForeignKeyReferencesQuery(tableName, catalogName, schemaName) { - return `${this._getForeignKeyReferencesQueryPrefix() - }WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '${tableName}'${ - catalogName ? ` AND tc.table_catalog = '${catalogName}'` : '' - }${schemaName ? ` AND tc.table_schema = '${schemaName}'` : ''}`; - } - - getForeignKeyReferenceQuery(table, columnName) { - const tableName = table.tableName || table; - const schema = table.schema; - return `${this._getForeignKeyReferencesQueryPrefix() - }WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='${tableName}' AND kcu.column_name = '${columnName}'${ - schema ? ` AND tc.table_schema = '${schema}'` : ''}`; - } - - /** - * Generates an SQL query that removes a foreign key from a table. - * - * @param {string} tableName The name of the table. - * @param {string} foreignKey The name of the foreign key constraint. - * @returns {string} The generated sql query. - * @private - */ - dropForeignKeyQuery(tableName, foreignKey) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(foreignKey)};`; - } -} - -module.exports = PostgresQueryGenerator; diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js deleted file mode 100644 index 5aa0c0fde84d..000000000000 --- a/lib/dialects/postgres/query-interface.js +++ /dev/null @@ -1,246 +0,0 @@ -'use strict'; - -const DataTypes = require('../../data-types'); -const QueryTypes = require('../../query-types'); -const { QueryInterface } = require('../abstract/query-interface'); -const Utils = require('../../utils'); - -/** - * The interface that Sequelize uses to talk with Postgres database - */ -class PostgresQueryInterface extends QueryInterface { - /** - * Ensure enum and their values. - * - * @param {string} tableName Name of table to create - * @param {object} attributes Object representing a list of normalized table attributes - * @param {object} [options] - * @param {Model} [model] - * - * @protected - */ - async ensureEnums(tableName, attributes, options, model) { - const keys = Object.keys(attributes); - const keyLen = keys.length; - - let sql = ''; - let promises = []; - let i = 0; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - sql = this.queryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); - promises.push(this.sequelize.query( - sql, - { ...options, plain: true, raw: true, type: QueryTypes.SELECT } - )); - } - } - - const results = await Promise.all(promises); - promises = []; - let enumIdx = 0; - - // This little function allows us to re-use the same code that prepends or appends new value to enum array - const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - const valueOptions = { ...options }; - valueOptions.before = null; - valueOptions.after = null; - - switch (position) { - case 'after': - valueOptions.after = relativeValue; - break; - case 'before': - default: - valueOptions.before = relativeValue; - break; - } - - promises.splice(spliceStart, 0, () => { - return this.sequelize.query(this.queryGenerator.pgEnumAdd( - tableName, field, value, valueOptions - ), valueOptions); - }); - }; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - const enumType = type.type || type; - const field = attribute.field || keys[i]; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - // If the enum type doesn't exist then create it - if (!results[enumIdx]) { - promises.push(() => { - return this.sequelize.query(this.queryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true }); - }); - } else if (!!results[enumIdx] && !!model) { - const enumVals = this.queryGenerator.fromArray(results[enumIdx].enum_value); - const vals = enumType.values; - - // Going through already existing values allows us to make queries that depend on those values - // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values - // Then we append the rest of new values AFTER the latest already existing value - // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] - // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] - // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] - let lastOldEnumValue; - let rightestPosition = -1; - for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { - const enumVal = enumVals[oldIndex]; - const newIdx = vals.indexOf(enumVal); - lastOldEnumValue = enumVal; - - if (newIdx === -1) { - continue; - } - - const newValuesBefore = vals.slice(0, newIdx); - const promisesLength = promises.length; - // we go in reverse order so we could stop when we meet old value - for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { - if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { - break; - } - - addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); - } - - // we detect the most 'right' position of old value in new enum array so we can append new values to it - if (newIdx > rightestPosition) { - rightestPosition = newIdx; - } - } - - if (lastOldEnumValue && rightestPosition < vals.length - 1) { - const remainingEnumValues = vals.slice(rightestPosition + 1); - for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { - addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); - } - } - - enumIdx++; - } - } - } - - const result = await promises - .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve()); - - // If ENUM processed, then refresh OIDs - if (promises.length) { - await this.sequelize.dialect.connectionManager._refreshDynamicOIDs(); - } - return result; - } - - /** - * @override - */ - async getForeignKeyReferencesForTable(tableName, 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(tableName, this.sequelize.config.database); - const result = await this.sequelize.query(query, queryOptions); - return result.map(Utils.camelizeObjectKeys); - } - - /** - * Drop specified enum from database (Postgres only) - * - * @param {string} [enumName] Enum name to drop - * @param {object} options Query options - * - * @returns {Promise} - */ - async dropEnum(enumName, options) { - options = options || {}; - - return this.sequelize.query( - this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(enumName)), - { ...options, raw: true } - ); - } - - /** - * Drop all enums from database (Postgres only) - * - * @param {object} options Query options - * - * @returns {Promise} - */ - async dropAllEnums(options) { - options = options || {}; - - const enums = await this.pgListEnums(null, options); - - return await Promise.all(enums.map(result => this.sequelize.query( - this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(result.enum_name)), - { ...options, raw: true } - ))); - } - - /** - * List all enums (Postgres only) - * - * @param {string} [tableName] Table whose enum to list - * @param {object} [options] Query options - * - * @returns {Promise} - */ - async pgListEnums(tableName, options) { - options = options || {}; - const sql = this.queryGenerator.pgListEnums(tableName); - return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT }); - } - - /** - * 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.PostgresQueryInterface = PostgresQueryInterface; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js deleted file mode 100644 index e4e4135fe6d1..000000000000 --- a/lib/dialects/postgres/query.js +++ /dev/null @@ -1,401 +0,0 @@ -'use strict'; - -const AbstractQuery = require('../abstract/query'); -const QueryTypes = require('../../query-types'); -const sequelizeErrors = require('../../errors'); -const _ = require('lodash'); -const { logger } = require('../../utils/logger'); - -const debug = logger.debugContext('sql:pg'); - - -class Query extends AbstractQuery { - /** - * Rewrite query with parameters. - * - * @param {string} sql - * @param {Array|object} values - * @param {string} dialect - * @private - */ - static formatBindParameters(sql, values, dialect) { - const stringReplaceFunc = value => typeof value === 'string' ? value.replace(/\0/g, '\\0') : value; - - let bindParam; - if (Array.isArray(values)) { - bindParam = values.map(stringReplaceFunc); - sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; - } else { - bindParam = []; - let i = 0; - const seen = {}; - const replacementFunc = (match, key, values) => { - if (seen[key] !== undefined) { - return seen[key]; - } - if (values[key] !== undefined) { - i = i + 1; - bindParam.push(stringReplaceFunc(values[key])); - seen[key] = `$${i}`; - return `$${i}`; - } - return undefined; - }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; - } - return [sql, bindParam]; - } - - async run(sql, parameters) { - const { connection } = this; - - if (!_.isEmpty(this.options.searchPath)) { - sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql; - } - - if (this.sequelize.options.minifyAliases && this.options.includeAliases) { - _.toPairs(this.options.includeAliases) - // Sorting to replace the longest aliases first to prevent alias collision - .sort((a, b) => b[1].length - a[1].length) - .forEach(([alias, original]) => { - const reg = new RegExp(_.escapeRegExp(original), 'g'); - - sql = sql.replace(reg, alias); - }); - } - - this.sql = sql; - - const query = parameters && parameters.length - ? new Promise((resolve, reject) => connection.query(sql, parameters, (error, result) => error ? reject(error) : resolve(result))) - : new Promise((resolve, reject) => connection.query(sql, (error, result) => error ? reject(error) : resolve(result))); - - const complete = this._logQuery(sql, debug, parameters); - - let queryResult; - - try { - queryResult = await query; - } catch (err) { - // set the client so that it will be reaped if the connection resets while executing - if (err.code === 'ECONNRESET') { - connection._invalid = true; - } - - err.sql = sql; - err.parameters = parameters; - throw this.formatError(err); - } - - 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; - }, {}) - ); - } - - const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); - const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); - - if (isRelNameQuery) { - return rows.map(row => ({ - name: row.relname, - tableName: row.relname.split('_')[0] - })); - } - if (isTableNameQuery) { - return rows.map(row => Object.values(row)); - } - - 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; - } - 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; - }); - }); - } - 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 (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.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { - if (this.instance && this.instance.dataValues) { - for (const key in rows[0]) { - if (Object.prototype.hasOwnProperty.call(rows[0], key)) { - const record = rows[0][key]; - - const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); - - this.instance.dataValues[attr && attr.fieldName || key] = record; - } - } - } - - 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) { - let match; - let table; - let index; - let fields; - let errors; - let message; - - const code = err.code || err.sqlState; - const errMessage = err.message || err.messagePrimary; - const errDetail = err.detail || err.messageDetail; - - switch (code) { - case '23503': - index = errMessage.match(/violates foreign key constraint "(.+?)"/); - index = index ? index[1] : undefined; - table = errMessage.match(/on table "(.+?)"/); - table = table ? table[1] : undefined; - - return new sequelizeErrors.ForeignKeyConstraintError({ message: errMessage, fields: null, index, table, parent: err }); - case '23505': - // there are multiple different formats of error messages for this error code - // this regex should check at least two - if (errDetail && (match = errDetail.replace(/"/g, '').match(/Key \((.*?)\)=\((.*?)\)/))) { - fields = _.zipObject(match[1].split(', '), match[2].split(', ')); - errors = []; - message = 'Validation error'; - - _.forOwn(fields, (value, field) => { - errors.push(new sequelizeErrors.ValidationErrorItem( - this.getUniqueConstraintErrorMessage(field), - 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB, - field, - value, - this.instance, - 'not_unique' - )); - }); - - if (this.model && this.model.uniqueKeys) { - _.forOwn(this.model.uniqueKeys, constraint => { - if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) { - message = constraint.msg; - return false; - } - }); - } - - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); - } - - return new sequelizeErrors.UniqueConstraintError({ - message: errMessage, - parent: err - }); - - case '23P01': - match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/); - - if (match) { - fields = _.zipObject(match[1].split(', '), match[2].split(', ')); - } - message = 'Exclusion constraint error'; - - return new sequelizeErrors.ExclusionConstraintError({ - message, - constraint: err.constraint, - fields, - table: err.table, - parent: err - }); - - case '42704': - if (err.sql && /(CONSTRAINT|INDEX)/gi.test(err.sql)) { - message = 'Unknown constraint error'; - index = errMessage.match(/(?:constraint|index) "(.+?)"/i); - index = index ? index[1] : undefined; - table = errMessage.match(/relation "(.+?)"/i); - table = table ? table[1] : undefined; - - throw new sequelizeErrors.UnknownConstraintError({ - message, - constraint: index, - fields, - table, - parent: err - }); - } - // falls through - default: - return new sequelizeErrors.DatabaseError(err); - } - } - - isForeignKeysQuery() { - return /SELECT conname as constraint_name, pg_catalog\.pg_get_constraintdef\(r\.oid, true\) as condef FROM pg_catalog\.pg_constraint r WHERE r\.conrelid = \(SELECT oid FROM pg_class WHERE relname = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql); - } - - getInsertIdField() { - return 'id'; - } -} - -module.exports = Query; -module.exports.Query = Query; -module.exports.default = Query; diff --git a/lib/dialects/postgres/range.js b/lib/dialects/postgres/range.js deleted file mode 100644 index dd0784678248..000000000000 --- a/lib/dialects/postgres/range.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -function stringifyRangeBound(bound) { - if (bound === null) { - return '' ; - } - if (bound === Infinity || bound === -Infinity) { - return bound.toString().toLowerCase(); - } - return JSON.stringify(bound); -} - -function parseRangeBound(bound, parseType) { - if (!bound) { - return null; - } - if (bound === 'infinity') { - return Infinity; - } - if (bound === '-infinity') { - return -Infinity; - } - return parseType(bound); - -} - -function stringify(data) { - if (data === null) return null; - - if (!Array.isArray(data)) throw new Error('range must be an array'); - if (!data.length) return 'empty'; - if (data.length !== 2) throw new Error('range array length must be 0 (empty) or 2 (lower and upper bounds)'); - - if (Object.prototype.hasOwnProperty.call(data, 'inclusive')) { - if (data.inclusive === false) data.inclusive = [false, false]; - else if (!data.inclusive) data.inclusive = [true, false]; - else if (data.inclusive === true) data.inclusive = [true, true]; - } else { - data.inclusive = [true, false]; - } - - _.each(data, (value, index) => { - if (_.isObject(value)) { - if (Object.prototype.hasOwnProperty.call(value, 'inclusive')) data.inclusive[index] = !!value.inclusive; - if (Object.prototype.hasOwnProperty.call(value, 'value')) data[index] = value.value; - } - }); - - const lowerBound = stringifyRangeBound(data[0]); - const upperBound = stringifyRangeBound(data[1]); - - return `${(data.inclusive[0] ? '[' : '(') + lowerBound},${upperBound}${data.inclusive[1] ? ']' : ')'}`; -} -exports.stringify = stringify; - -function parse(value, parser) { - if (value === null) return null; - if (value === 'empty') { - return []; - } - - let result = value - .substring(1, value.length - 1) - .split(',', 2); - - if (result.length !== 2) return value; - - result = result.map((item, index) => { - return { - value: parseRangeBound(item, parser), - inclusive: index === 0 ? value[0] === '[' : value[value.length - 1] === ']' - }; - }); - - return result; -} -exports.parse = parse; diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js deleted file mode 100644 index ecb4371c034a..000000000000 --- a/lib/dialects/sqlite/connection-manager.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const AbstractConnectionManager = require('../abstract/connection-manager'); -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) { - super(dialect, sequelize); - - // We attempt to parse file location from a connection uri - // but we shouldn't match sequelize default host. - if (this.sequelize.options.host === 'localhost') { - delete this.sequelize.options.host; - } - - this.connections = {}; - this.lib = this._loadDialectModule('sqlite3'); - this.refreshTypeParser(dataTypes); - } - - async _onProcessExit() { - await Promise.all( - Object.getOwnPropertyNames(this.connections) - .map(connection => promisify(callback => this.connections[connection].close(callback))()) - ); - return super._onProcessExit.call(this); - } - - // Expose this as a method so that the parsing may be updated when the user has added additional, custom types - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - - async getConnection(options) { - options = options || {}; - options.uuid = options.uuid || 'default'; - options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:'; - options.inMemory = options.storage === ':memory:' ? 1 : 0; - - const dialectOptions = this.sequelize.options.dialectOptions; - const defaultReadWriteMode = this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE; - - options.readWriteMode = dialectOptions && dialectOptions.mode || defaultReadWriteMode; - - if (this.connections[options.inMemory || options.uuid]) { - return this.connections[options.inMemory || options.uuid]; - } - - if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { - // automatic path provision for `options.storage` - fs.mkdirSync(path.dirname(options.storage), { recursive: true }); - } - - const connection = await new Promise((resolve, reject) => { - this.connections[options.inMemory || options.uuid] = new this.lib.Database( - options.storage, - options.readWriteMode, - err => { - if (err) return reject(new sequelizeErrors.ConnectionError(err)); - debug(`connection acquired ${options.uuid}`); - resolve(this.connections[options.inMemory || options.uuid]); - } - ); - }); - - 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) { - if (connection.filename === ':memory:' && force !== true) return; - - if (connection.uuid) { - connection.close(); - debug(`connection released ${connection.uuid}`); - delete this.connections[connection.uuid]; - } - } -} - -module.exports = ConnectionManager; -module.exports.ConnectionManager = ConnectionManager; -module.exports.default = ConnectionManager; diff --git a/lib/dialects/sqlite/data-types.js b/lib/dialects/sqlite/data-types.js deleted file mode 100644 index 30ef4d654023..000000000000 --- a/lib/dialects/sqlite/data-types.js +++ /dev/null @@ -1,213 +0,0 @@ -'use strict'; - -module.exports = BaseTypes => { - const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://www.sqlite.org/datatype3.html'); - - /** - * Removes unsupported SQLite options, i.e., UNSIGNED and ZEROFILL, for the integer data types. - * - * @param {object} dataType The base integer data type. - * @private - */ - function removeUnsupportedIntegerOptions(dataType) { - if (dataType._zerofill || dataType._unsigned) { - warn(`SQLite does not support '${dataType.key}' with UNSIGNED or ZEROFILL. Plain '${dataType.key}' will be used instead.`); - dataType._unsigned = undefined; - dataType._zerofill = undefined; - } - } - - /** - * @see https://sqlite.org/datatype3.html - */ - - BaseTypes.DATE.types.sqlite = ['DATETIME']; - BaseTypes.STRING.types.sqlite = ['VARCHAR', 'VARCHAR BINARY']; - BaseTypes.CHAR.types.sqlite = ['CHAR', 'CHAR BINARY']; - BaseTypes.TEXT.types.sqlite = ['TEXT']; - BaseTypes.TINYINT.types.sqlite = ['TINYINT']; - BaseTypes.SMALLINT.types.sqlite = ['SMALLINT']; - BaseTypes.MEDIUMINT.types.sqlite = ['MEDIUMINT']; - BaseTypes.INTEGER.types.sqlite = ['INTEGER']; - BaseTypes.BIGINT.types.sqlite = ['BIGINT']; - BaseTypes.FLOAT.types.sqlite = ['FLOAT']; - BaseTypes.TIME.types.sqlite = ['TIME']; - BaseTypes.DATEONLY.types.sqlite = ['DATE']; - BaseTypes.BOOLEAN.types.sqlite = ['TINYINT']; - BaseTypes.BLOB.types.sqlite = ['TINYBLOB', 'BLOB', 'LONGBLOB']; - BaseTypes.DECIMAL.types.sqlite = ['DECIMAL']; - BaseTypes.UUID.types.sqlite = ['UUID']; - BaseTypes.ENUM.types.sqlite = false; - BaseTypes.REAL.types.sqlite = ['REAL']; - BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION']; - BaseTypes.GEOMETRY.types.sqlite = false; - BaseTypes.JSON.types.sqlite = ['JSON', 'JSONB']; - - class JSONTYPE extends BaseTypes.JSON { - static parse(data) { - return JSON.parse(data); - } - } - - class DATE extends BaseTypes.DATE { - static parse(date, options) { - if (!date.includes('+')) { - // For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set - return new Date(date + options.timezone); - } - return new Date(date); // We already have a timezone stored in the string - } - } - - class DATEONLY extends BaseTypes.DATEONLY { - static parse(date) { - return date; - } - } - - class STRING extends BaseTypes.STRING { - toSql() { - if (this._binary) { - return `VARCHAR BINARY(${this._length})`; - } - return super.toSql(this); - } - } - - class TEXT extends BaseTypes.TEXT { - toSql() { - if (this._length) { - warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.'); - this._length = undefined; - } - return 'TEXT'; - } - } - - class CITEXT extends BaseTypes.CITEXT { - toSql() { - return 'TEXT COLLATE NOCASE'; - } - } - - class CHAR extends BaseTypes.CHAR { - toSql() { - if (this._binary) { - return `CHAR BINARY(${this._length})`; - } - return super.toSql(); - } - } - - class NUMBER extends BaseTypes.NUMBER { - toSql() { - let result = this.key; - if (this._unsigned) { - result += ' UNSIGNED'; - } - if (this._zerofill) { - result += ' ZEROFILL'; - } - if (this._length) { - result += `(${this._length}`; - if (typeof this._decimals === 'number') { - result += `,${this._decimals}`; - } - result += ')'; - } - return result; - } - } - - class TINYINT extends BaseTypes.TINYINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - - class SMALLINT extends BaseTypes.SMALLINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - - class MEDIUMINT extends BaseTypes.MEDIUMINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - - class INTEGER extends BaseTypes.INTEGER { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - - class BIGINT extends BaseTypes.BIGINT { - constructor(length) { - super(length); - removeUnsupportedIntegerOptions(this); - } - } - - class FLOAT extends BaseTypes.FLOAT { - } - - class DOUBLE extends BaseTypes.DOUBLE { - } - - class REAL extends BaseTypes.REAL { } - - function parseFloating(value) { - if (typeof value !== 'string') { - return value; - } - if (value === 'NaN') { - return NaN; - } - if (value === 'Infinity') { - return Infinity; - } - if (value === '-Infinity') { - return -Infinity; - } - } - for (const floating of [FLOAT, DOUBLE, REAL]) { - floating.parse = parseFloating; - } - - - for (const num of [FLOAT, DOUBLE, REAL, TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT]) { - num.prototype.toSql = NUMBER.prototype.toSql; - } - - class ENUM extends BaseTypes.ENUM { - toSql() { - return 'TEXT'; - } - } - - return { - DATE, - DATEONLY, - STRING, - CHAR, - NUMBER, - FLOAT, - REAL, - 'DOUBLE PRECISION': DOUBLE, - TINYINT, - SMALLINT, - MEDIUMINT, - INTEGER, - BIGINT, - TEXT, - ENUM, - JSON: JSONTYPE, - CITEXT - }; -}; diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js deleted file mode 100644 index 1ed11dd20ae5..000000000000 --- a/lib/dialects/sqlite/index.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const AbstractDialect = require('../abstract'); -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({ - _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.defaultVersion = '3.8.0'; -SqliteDialect.prototype.Query = Query; -SqliteDialect.prototype.DataTypes = DataTypes; -SqliteDialect.prototype.name = 'sqlite'; -SqliteDialect.prototype.TICK_CHAR = '`'; -SqliteDialect.prototype.TICK_CHAR_LEFT = SqliteDialect.prototype.TICK_CHAR; -SqliteDialect.prototype.TICK_CHAR_RIGHT = SqliteDialect.prototype.TICK_CHAR; - -module.exports = SqliteDialect; -module.exports.SqliteDialect = SqliteDialect; -module.exports.default = SqliteDialect; diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js deleted file mode 100644 index 8997bb6a0e9a..000000000000 --- a/lib/dialects/sqlite/query-generator.js +++ /dev/null @@ -1,469 +0,0 @@ -'use strict'; - -const Utils = require('../../utils'); -const Transaction = require('../../transaction'); -const _ = require('lodash'); -const MySqlQueryGenerator = require('../mysql/query-generator'); -const AbstractQueryGenerator = require('../abstract/query-generator'); - -class SQLiteQueryGenerator extends MySqlQueryGenerator { - createSchema() { - return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';"; - } - - showSchemasQuery() { - return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';"; - } - - versionQuery() { - return 'SELECT sqlite_version() as `version`'; - } - - createTableQuery(tableName, attributes, options) { - options = options || {}; - - const primaryKeys = []; - const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; - const attrArray = []; - - for (const attr in attributes) { - if (Object.prototype.hasOwnProperty.call(attributes, attr)) { - const dataType = attributes[attr]; - const containsAutoIncrement = dataType.includes('AUTOINCREMENT'); - - let dataTypeString = dataType; - if (dataType.includes('PRIMARY KEY')) { - if (dataType.includes('INT')) { - // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc) - dataTypeString = containsAutoIncrement ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INTEGER PRIMARY KEY'; - - if (dataType.includes(' REFERENCES')) { - dataTypeString += dataType.substr(dataType.indexOf(' REFERENCES')); - } - } - - if (needsMultiplePrimaryKeys) { - primaryKeys.push(attr); - if (dataType.includes('NOT NULL')) { - dataTypeString = dataType.replace(' PRIMARY KEY', ''); - } else { - dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); - } - } - } - attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`); - } - } - - const table = this.quoteTable(tableName); - let attrStr = attrArray.join(', '); - const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); - - if (options.uniqueKeys) { - _.each(options.uniqueKeys, columns => { - if (columns.customIndex) { - attrStr += `, UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; - } - }); - } - - if (pkString.length > 0) { - attrStr += `, PRIMARY KEY (${pkString})`; - } - - const sql = `CREATE TABLE IF NOT EXISTS ${table} (${attrStr});`; - return this.replaceBooleanDefaults(sql); - } - - booleanValue(value) { - return value ? 1 : 0; - } - - /** - * Check whether the statmement is json function or simple path - * - * @param {string} stmt The statement to validate - * @returns {boolean} true if the given statement is json function - * @throws {Error} throw if the statement looks like json function but has invalid token - */ - _checkValidJsonStatement(stmt) { - if (typeof stmt !== 'string') { - return false; - } - - // https://sqlite.org/json1.html - const jsonFunctionRegex = /^\s*(json(?:_[a-z]+){0,2})\([^)]*\)/i; - const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; - - let currentIndex = 0; - let openingBrackets = 0; - let closingBrackets = 0; - let hasJsonFunction = false; - let hasInvalidToken = false; - - while (currentIndex < stmt.length) { - const string = stmt.substr(currentIndex); - const functionMatches = jsonFunctionRegex.exec(string); - if (functionMatches) { - currentIndex += functionMatches[0].indexOf('('); - hasJsonFunction = true; - continue; - } - - const tokenMatches = tokenCaptureRegex.exec(string); - if (tokenMatches) { - const capturedToken = tokenMatches[1]; - if (capturedToken === '(') { - openingBrackets++; - } else if (capturedToken === ')') { - closingBrackets++; - } else if (capturedToken === ';') { - hasInvalidToken = true; - break; - } - currentIndex += tokenMatches[0].length; - continue; - } - - break; - } - - // Check invalid json statement - hasInvalidToken |= openingBrackets !== closingBrackets; - if (hasJsonFunction && hasInvalidToken) { - throw new Error(`Invalid json statement: ${stmt}`); - } - - // return true if the statement has valid json function - return hasJsonFunction; - } - - //sqlite can't cast to datetime so we need to convert date values to their ISO strings - _toJSONValue(value) { - if (value instanceof Date) { - return value.toISOString(); - } - if (Array.isArray(value) && value[0] instanceof Date) { - return value.map(val => val.toISOString()); - } - return value; - } - - - handleSequelizeMethod(smth, tableName, factory, options, prepend) { - if (smth instanceof Utils.Json) { - return super.handleSequelizeMethod(smth, tableName, factory, options, prepend); - } - - if (smth instanceof Utils.Cast) { - if (/timestamp/i.test(smth.type)) { - smth.type = 'datetime'; - } - } - - return AbstractQueryGenerator.prototype.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend); - } - - addColumnQuery(table, key, dataType) { - const attributes = {}; - attributes[key] = dataType; - const fields = this.attributesToSQL(attributes, { context: 'addColumn' }); - const attribute = `${this.quoteIdentifier(key)} ${fields[key]}`; - - const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`; - - return this.replaceBooleanDefaults(sql); - } - - showTablesQuery() { - return 'SELECT name FROM `sqlite_master` WHERE type=\'table\' and name!=\'sqlite_sequence\';'; - } - - updateQuery(tableName, attrValueHash, where, options, attributes) { - options = options || {}; - _.defaults(options, this.options); - - attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); - - const modelAttributeMap = {}; - const values = []; - const bind = []; - const bindParam = options.bindParam || this.bindParam(bind); - - if (attributes) { - _.each(attributes, (attribute, key) => { - modelAttributeMap[key] = attribute; - if (attribute.field) { - modelAttributeMap[attribute.field] = attribute; - } - }); - } - - for (const key in attrValueHash) { - const value = attrValueHash[key]; - - if (value instanceof Utils.SequelizeMethod || options.bindParam === false) { - values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })}`); - } else { - values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }, bindParam)}`); - } - } - - let query; - 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)})`; - } else { - query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where, whereOptions)}`; - } - - return { query, bind }; - } - - truncateTableQuery(tableName, options = {}) { - return [ - `DELETE FROM ${this.quoteTable(tableName)}`, - options.restartIdentity ? `; DELETE FROM ${this.quoteTable('sqlite_sequence')} WHERE ${this.quoteIdentifier('name')} = ${Utils.addTicks(Utils.removeTicks(this.quoteTable(tableName), '`'), "'")};` : '' - ].join(''); - } - - deleteQuery(tableName, where, options = {}, model) { - _.defaults(options, this.options); - - let whereClause = this.getWhereConditions(where, null, model, options); - - if (whereClause) { - whereClause = `WHERE ${whereClause}`; - } - - if (options.limit) { - whereClause = `WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${whereClause} LIMIT ${this.escape(options.limit)})`; - } - - return `DELETE FROM ${this.quoteTable(tableName)} ${whereClause}`; - } - - attributesToSQL(attributes) { - const result = {}; - for (const name in attributes) { - const dataType = attributes[name]; - const fieldName = dataType.field || name; - - if (_.isObject(dataType)) { - let sql = dataType.type.toString(); - - if (Object.prototype.hasOwnProperty.call(dataType, 'allowNull') && !dataType.allowNull) { - sql += ' NOT NULL'; - } - - if (Utils.defaultValueSchemable(dataType.defaultValue)) { - // TODO thoroughly check that DataTypes.NOW will properly - // get populated on all databases as DEFAULT value - // i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP - sql += ` DEFAULT ${this.escape(dataType.defaultValue, dataType)}`; - } - - if (dataType.unique === true) { - sql += ' UNIQUE'; - } - - if (dataType.primaryKey) { - sql += ' PRIMARY KEY'; - - if (dataType.autoIncrement) { - sql += ' AUTOINCREMENT'; - } - } - - if (dataType.references) { - const referencesTable = this.quoteTable(dataType.references.model); - - let referencesKey; - if (dataType.references.key) { - referencesKey = this.quoteIdentifier(dataType.references.key); - } else { - referencesKey = this.quoteIdentifier('id'); - } - - sql += ` REFERENCES ${referencesTable} (${referencesKey})`; - - if (dataType.onDelete) { - sql += ` ON DELETE ${dataType.onDelete.toUpperCase()}`; - } - - if (dataType.onUpdate) { - sql += ` ON UPDATE ${dataType.onUpdate.toUpperCase()}`; - } - - } - - result[fieldName] = sql; - } else { - result[fieldName] = dataType; - } - } - - return result; - } - - showIndexesQuery(tableName) { - return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`; - } - - showConstraintsQuery(tableName, constraintName) { - let sql = `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}'`; - - if (constraintName) { - sql += ` AND sql LIKE '%${constraintName}%'`; - } - - return `${sql};`; - } - - removeIndexQuery(tableName, indexNameOrAttributes) { - let indexName = indexNameOrAttributes; - - if (typeof indexName !== 'string') { - indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); - } - - return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`; - } - - describeTableQuery(tableName, schema, schemaDelimiter) { - const table = { - _schema: schema, - _schemaDelimiter: schemaDelimiter, - tableName - }; - return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`; - } - - describeCreateTableQuery(tableName) { - return `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}';`; - } - - removeColumnQuery(tableName, attributes) { - - attributes = this.attributesToSQL(attributes); - - let backupTableName; - if (typeof tableName === 'object') { - backupTableName = { - tableName: `${tableName.tableName}_backup`, - schema: tableName.schema - }; - } else { - backupTableName = `${tableName}_backup`; - } - - const quotedTableName = this.quoteTable(tableName); - const quotedBackupTableName = this.quoteTable(backupTableName); - const attributeNames = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', '); - - // Temporary table cannot work for foreign keys. - return `${this.createTableQuery(backupTableName, attributes) - }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};` - + `DROP TABLE ${quotedTableName};${ - this.createTableQuery(tableName, attributes) - }INSERT INTO ${quotedTableName} SELECT ${attributeNames} FROM ${quotedBackupTableName};` - + `DROP TABLE ${quotedBackupTableName};`; - } - - _alterConstraintQuery(tableName, attributes, createTableSql) { - let backupTableName; - - attributes = this.attributesToSQL(attributes); - - if (typeof tableName === 'object') { - backupTableName = { - tableName: `${tableName.tableName}_backup`, - schema: tableName.schema - }; - } else { - backupTableName = `${tableName}_backup`; - } - const quotedTableName = this.quoteTable(tableName); - const quotedBackupTableName = this.quoteTable(backupTableName); - const attributeNames = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', '); - - return `${createTableSql - .replace(`CREATE TABLE ${quotedTableName}`, `CREATE TABLE ${quotedBackupTableName}`) - .replace(`CREATE TABLE ${quotedTableName.replace(/`/g, '"')}`, `CREATE TABLE ${quotedBackupTableName}`) - }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};` - + `DROP TABLE ${quotedTableName};` - + `ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`; - } - - renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) { - - let backupTableName; - - attributes = this.attributesToSQL(attributes); - - if (typeof tableName === 'object') { - backupTableName = { - tableName: `${tableName.tableName}_backup`, - schema: tableName.schema - }; - } else { - backupTableName = `${tableName}_backup`; - } - - const quotedTableName = this.quoteTable(tableName); - const quotedBackupTableName = this.quoteTable(backupTableName); - const attributeNamesImport = Object.keys(attributes).map(attr => - attrNameAfter === attr ? `${this.quoteIdentifier(attrNameBefore)} AS ${this.quoteIdentifier(attr)}` : this.quoteIdentifier(attr) - ).join(', '); - const attributeNamesExport = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', '); - - // 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) - }INSERT INTO ${quotedTableName} SELECT ${attributeNamesExport} FROM ${quotedBackupTableName};` - + `DROP TABLE ${quotedBackupTableName};`; - } - - startTransactionQuery(transaction) { - if (transaction.parent) { - return `SAVEPOINT ${this.quoteIdentifier(transaction.name)};`; - } - - return `BEGIN ${transaction.options.type} TRANSACTION;`; - } - - setIsolationLevelQuery(value) { - switch (value) { - case Transaction.ISOLATION_LEVELS.REPEATABLE_READ: - return '-- SQLite is not able to choose the isolation level REPEATABLE READ.'; - case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED: - return 'PRAGMA read_uncommitted = ON;'; - case Transaction.ISOLATION_LEVELS.READ_COMMITTED: - return 'PRAGMA read_uncommitted = OFF;'; - case Transaction.ISOLATION_LEVELS.SERIALIZABLE: - return '-- SQLite\'s default isolation level is SERIALIZABLE. Nothing to do.'; - default: - throw new Error(`Unknown isolation level: ${value}`); - } - } - - replaceBooleanDefaults(sql) { - return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1'); - } - - /** - * Generates an SQL query that returns all foreign keys of a table. - * - * @param {string} tableName The name of the table. - * @returns {string} The generated sql query. - * @private - */ - getForeignKeysQuery(tableName) { - return `PRAGMA foreign_key_list(${tableName})`; - } -} - -module.exports = SQLiteQueryGenerator; diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js deleted file mode 100644 index ca2b68653d0f..000000000000 --- a/lib/dialects/sqlite/query-interface.js +++ /dev/null @@ -1,238 +0,0 @@ -'use strict'; - -const sequelizeErrors = require('../../errors'); -const QueryTypes = require('../../query-types'); -const { QueryInterface } = require('../abstract/query-interface'); -const { cloneDeep } = require('../../utils'); -const _ = require('lodash'); - -/** - * The interface that Sequelize uses to talk with SQLite database - */ -class SQLiteQueryInterface extends QueryInterface { - /** - * A wrapper that fixes SQLite's inability to remove columns from existing tables. - * It will create a backup of the table, drop the table afterwards and create a - * new table with the same name but without the obsolete column. - * - * @override - */ - async removeColumn(tableName, attributeName, options) { - options = options || {}; - - const fields = await this.describeTable(tableName, options); - delete fields[attributeName]; - - const sql = this.queryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); - - for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); - } - - /** - * A wrapper that fixes SQLite's inability to change columns from existing tables. - * It will create a backup of the table, drop the table afterwards and create a - * new table with the same name but with a modified version of the respective column. - * - * @override - */ - async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { - options = options || {}; - - const fields = await this.describeTable(tableName, options); - Object.assign(fields[attributeName], this.normalizeAttribute(dataTypeOrOptions)); - - const sql = this.queryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); - - for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); - } - - /** - * 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 = this.queryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); - const subQueries = sql.split(';').filter(q => q !== ''); - - for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); - } - - /** - * @override - */ - async removeConstraint(tableName, constraintName, options) { - let createTableSql; - - const constraints = await this.showConstraint(tableName, constraintName); - // sqlite can't show only one constraint, so we find here the one to remove - const constraint = constraints.find(constaint => constaint.constraintName === constraintName); - - if (!constraint) { - throw new sequelizeErrors.UnknownConstraintError({ - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } - createTableSql = constraint.sql; - constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName); - let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; - - if (constraint.constraintType === 'FOREIGN KEY') { - const referenceTableName = this.queryGenerator.quoteTable(constraint.referenceTableName); - constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.queryGenerator.quoteIdentifier(columnName)); - const referenceTableKeys = constraint.referenceTableKeys.join(', '); - constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; - constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; - constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; - } - - createTableSql = createTableSql.replace(constraintSnippet, ''); - createTableSql += ';'; - - const fields = await this.describeTable(tableName, options); - - const sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); - - for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); - } - - /** - * @override - */ - 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 = 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.SQLiteQueryInterface = SQLiteQueryInterface; diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js deleted file mode 100644 index 4047ef4792f1..000000000000 --- a/lib/dialects/sqlite/query.js +++ /dev/null @@ -1,437 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const Utils = require('../../utils'); -const AbstractQuery = require('../abstract/query'); -const QueryTypes = require('../../query-types'); -const sequelizeErrors = require('../../errors'); -const parserStore = require('../parserStore')('sqlite'); -const { logger } = require('../../utils/logger'); - -const debug = logger.debugContext('sql:sqlite'); - - -class Query extends AbstractQuery { - getInsertIdField() { - return 'lastID'; - } - - /** - * rewrite query with parameters. - * - * @param {string} sql - * @param {Array|object} values - * @param {string} dialect - * @private - */ - static formatBindParameters(sql, values, dialect) { - let bindParam; - if (Array.isArray(values)) { - bindParam = {}; - values.forEach((v, i) => { - bindParam[`$${i + 1}`] = v; - }); - sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; - } else { - bindParam = {}; - if (typeof values === 'object') { - for (const k of Object.keys(values)) { - bindParam[`$${k}`] = values[k]; - } - } - sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; - } - return [sql, bindParam]; - } - - _collectModels(include, prefix) { - const ret = {}; - - if (include) { - for (const _include of include) { - let key; - if (!prefix) { - key = _include.as; - } else { - key = `${prefix}.${_include.as}`; - } - ret[key] = _include.model; - - if (_include.include) { - _.merge(ret, this._collectModels(_include.include, key)); - } - } - } - - return ret; - } - - _handleQueryResponse(metaData, columnTypes, err, results) { - if (err) { - err.sql = this.sql; - throw this.formatError(err); - } - let result = this.instance; - - // add the inserted row id to the instance - if (this.isInsertQuery(results, metaData) || this.isUpsertQuery()) { - this.handleInsertQuery(results, metaData); - if (!this.instance) { - // handle bulkCreate AI primary key - if ( - metaData.constructor.name === 'Statement' - && this.model - && this.model.autoIncrementAttribute - && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute - && this.model.rawAttributes[this.model.primaryKeyAttribute] - ) { - const startId = metaData[this.getInsertIdField()] - metaData.changes + 1; - result = []; - for (let i = startId; i < startId + metaData.changes; i++) { - result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i }); - } - } else { - result = metaData[this.getInsertIdField()]; - } - } - } - - if (this.isShowTablesQuery()) { - return results.map(row => row.name); - } - if (this.isShowConstraintsQuery()) { - result = results; - if (results && results[0] && results[0].sql) { - result = this.parseConstraintsFromSql(results[0].sql); - } - return result; - } - if (this.isSelectQuery()) { - if (this.options.raw) { - return this.handleSelectQuery(results); - } - // This is a map of prefix strings to models, e.g. user.projects -> Project model - const prefixes = this._collectModels(this.options.include); - - results = results.map(result => { - return _.mapValues(result, (value, name) => { - let model; - if (name.includes('.')) { - const lastind = name.lastIndexOf('.'); - - model = prefixes[name.substr(0, lastind)]; - - name = name.substr(lastind + 1); - } else { - model = this.options.model; - } - - const tableName = model.getTableName().toString().replace(/`/g, ''); - const tableTypes = columnTypes[tableName] || {}; - - if (tableTypes && !(name in tableTypes)) { - // The column is aliased - _.forOwn(model.rawAttributes, (attribute, key) => { - if (name === key && attribute.field) { - name = attribute.field; - return false; - } - }); - } - - return Object.prototype.hasOwnProperty.call(tableTypes, name) - ? this.applyParsers(tableTypes[name], value) - : value; - }); - }); - - return this.handleSelectQuery(results); - } - if (this.isShowOrDescribeQuery()) { - return results; - } - if (this.sql.includes('PRAGMA INDEX_LIST')) { - return this.handleShowIndexesQuery(results); - } - if (this.sql.includes('PRAGMA INDEX_INFO')) { - return results; - } - if (this.sql.includes('PRAGMA TABLE_INFO')) { - // this is the sqlite way of getting the metadata of a table - result = {}; - - let defaultValue; - for (const _result of results) { - if (_result.dflt_value === null) { - // Column schema omits any "DEFAULT ..." - defaultValue = undefined; - } else if (_result.dflt_value === 'NULL') { - // Column schema is a "DEFAULT NULL" - defaultValue = null; - } else { - defaultValue = _result.dflt_value; - } - - result[_result.name] = { - type: _result.type, - allowNull: _result.notnull === 0, - defaultValue, - primaryKey: _result.pk !== 0 - }; - - if (result[_result.name].type === 'TINYINT(1)') { - result[_result.name].defaultValue = { '0': false, '1': true }[result[_result.name].defaultValue]; - } - - if (typeof result[_result.name].defaultValue === 'string') { - result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, ''); - } - } - return result; - } - if (this.sql.includes('PRAGMA foreign_keys;')) { - return results[0]; - } - if (this.sql.includes('PRAGMA foreign_keys')) { - return results; - } - if (this.sql.includes('PRAGMA foreign_key_list')) { - return results; - } - if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].includes(this.options.type)) { - return metaData.changes; - } - 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; - } - - async run(sql, parameters) { - const conn = this.connection; - this.sql = sql; - const method = this.getDatabaseMethod(); - const complete = this._logQuery(sql, debug, parameters); - - return new Promise((resolve, reject) => conn.serialize(async () => { - const columnTypes = {}; - const executeSql = () => { - if (sql.startsWith('-- ')) { - return resolve(); - } - const query = this; - // cannot use arrow function here because the function is bound to the statement - function afterExecute(executionError, results) { - try { - complete(); - // `this` is passed from sqlite, we have no control over this. - // eslint-disable-next-line no-invalid-this - resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); - return; - } catch (error) { - reject(error); - } - } - - if (!parameters) parameters = []; - conn[method](sql, parameters, afterExecute); - - return null; - }; - - if (this.getDatabaseMethod() === 'all') { - let tableNames = []; - if (this.options && this.options.tableNames) { - tableNames = this.options.tableNames; - } else if (/FROM `(.*?)`/i.exec(this.sql)) { - tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); - } - - // If 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(); - } - 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) { - let constraints = sql.split('CONSTRAINT '); - let referenceTableName, referenceTableKeys, updateAction, deleteAction; - constraints.splice(0, 1); - constraints = constraints.map(constraintSql => { - //Parse foreign key snippets - if (constraintSql.includes('REFERENCES')) { - //Parse out the constraint condition form sql string - updateAction = constraintSql.match(/ON UPDATE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/); - deleteAction = constraintSql.match(/ON DELETE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/); - - if (updateAction) { - updateAction = updateAction[1]; - } - - if (deleteAction) { - deleteAction = deleteAction[1]; - } - - const referencesRegex = /REFERENCES.+\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/; - const referenceConditions = constraintSql.match(referencesRegex)[0].split(' '); - referenceTableName = Utils.removeTicks(referenceConditions[1]); - let columnNames = referenceConditions[2]; - columnNames = columnNames.replace(/\(|\)/g, '').split(', '); - referenceTableKeys = columnNames.map(column => Utils.removeTicks(column)); - } - - const constraintCondition = constraintSql.match(/\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/)[0]; - constraintSql = constraintSql.replace(/\(.+\)/, ''); - const constraint = constraintSql.split(' '); - - if (constraint[1] === 'PRIMARY' || constraint[1] === 'FOREIGN') { - constraint[1] += ' KEY'; - } - - return { - constraintName: Utils.removeTicks(constraint[0]), - constraintType: constraint[1], - updateAction, - deleteAction, - sql: sql.replace(/"/g, '`'), //Sqlite returns double quotes for table name - constraintCondition, - referenceTableName, - referenceTableKeys - }; - }); - - return constraints; - } - - applyParsers(type, value) { - if (type.includes('(')) { - // Remove the length part - type = type.substr(0, type.indexOf('(')); - } - type = type.replace('UNSIGNED', '').replace('ZEROFILL', ''); - type = type.trim().toUpperCase(); - const parse = parserStore.get(type); - - if (value !== null && parse) { - return parse(value, { timezone: this.sequelize.options.timezone }); - } - return value; - } - - formatError(err) { - - switch (err.code) { - case 'SQLITE_CONSTRAINT': { - if (err.message.includes('FOREIGN KEY constraint failed')) { - return new sequelizeErrors.ForeignKeyConstraintError({ - parent: err - }); - } - - let fields = []; - - // Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique - let match = err.message.match(/columns (.*?) are/); - if (match !== null && match.length >= 2) { - fields = match[1].split(', '); - } else { - - // Sqlite post 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y - match = err.message.match(/UNIQUE constraint failed: (.*)/); - if (match !== null && match.length >= 2) { - fields = match[1].split(', ').map(columnWithTable => columnWithTable.split('.')[1]); - } - } - - const errors = []; - let message = 'Validation error'; - - for (const field of fields) { - errors.push(new sequelizeErrors.ValidationErrorItem( - this.getUniqueConstraintErrorMessage(field), - 'unique violation', // sequelizeErrors.ValidationErrorItem.Origins.DB, - field, - this.instance && this.instance[field], - this.instance, - 'not_unique' - )); - } - - if (this.model) { - _.forOwn(this.model.uniqueKeys, constraint => { - if (_.isEqual(constraint.fields, fields) && !!constraint.msg) { - message = constraint.msg; - return false; - } - }); - } - - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); - } - case 'SQLITE_BUSY': - return new sequelizeErrors.TimeoutError(err); - - default: - return new sequelizeErrors.DatabaseError(err); - } - } - - async handleShowIndexesQuery(data) { - // Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that! - return Promise.all(data.reverse().map(async item => { - item.fields = []; - item.primary = false; - item.unique = !!item.unique; - item.constraintName = item.name; - 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; - })); - } - - getDatabaseMethod() { - 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'; - } -} - -module.exports = Query; -module.exports.Query = Query; -module.exports.default = Query; diff --git a/lib/errors/aggregate-error.js b/lib/errors/aggregate-error.js deleted file mode 100644 index 4a6eb94a4311..000000000000 --- a/lib/errors/aggregate-error.js +++ /dev/null @@ -1,34 +0,0 @@ -'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 deleted file mode 100644 index d4ef83f9ae52..000000000000 --- a/lib/errors/association-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when an association is improperly constructed (see message for details) - */ -class AssociationError extends BaseError { - constructor(message) { - super(message); - this.name = 'SequelizeAssociationError'; - } -} - -module.exports = AssociationError; diff --git a/lib/errors/base-error.js b/lib/errors/base-error.js deleted file mode 100644 index ef0060113513..000000000000 --- a/lib/errors/base-error.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -/** - * Sequelize provides a host of custom error classes, to allow you to do easier debugging. All of these errors are exposed on the sequelize object and the sequelize constructor. - * All sequelize errors inherit from the base JS error object. - * - * This means that errors can be accessed using `Sequelize.ValidationError` - * The Base Error all Sequelize Errors inherit from. - */ -class BaseError extends Error { - constructor(message) { - super(message); - this.name = 'SequelizeBaseError'; - } -} - -module.exports = BaseError; diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js deleted file mode 100644 index c095754040ae..000000000000 --- a/lib/errors/bulk-record-error.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when bulk operation fails, it represent per record level error. - * Used with AggregateError - * - * @param {Error} error Error for a given record/instance - * @param {object} record DAO instance that error belongs to - */ -class BulkRecordError extends BaseError { - constructor(error, record) { - super(error.message); - this.name = 'SequelizeBulkRecordError'; - this.errors = error; - this.record = record; - } -} - -module.exports = BulkRecordError; diff --git a/lib/errors/connection-error.js b/lib/errors/connection-error.js deleted file mode 100644 index 4a3a8a38e989..000000000000 --- a/lib/errors/connection-error.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * A base class for all connection related errors. - */ -class ConnectionError extends BaseError { - constructor(parent) { - super(parent ? parent.message : ''); - this.name = 'SequelizeConnectionError'; - /** - * The connection specific error which triggered this one - * - * @type {Error} - */ - this.parent = parent; - this.original = parent; - } -} - -module.exports = ConnectionError; diff --git a/lib/errors/connection/access-denied-error.js b/lib/errors/connection/access-denied-error.js deleted file mode 100644 index c6bc2e8f72f8..000000000000 --- a/lib/errors/connection/access-denied-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database is refused due to insufficient privileges - */ -class AccessDeniedError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeAccessDeniedError'; - } -} - -module.exports = AccessDeniedError; diff --git a/lib/errors/connection/connection-acquire-timeout-error.js b/lib/errors/connection/connection-acquire-timeout-error.js deleted file mode 100644 index 661707b93116..000000000000 --- a/lib/errors/connection/connection-acquire-timeout-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when connection is not acquired due to timeout - */ -class ConnectionAcquireTimeoutError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeConnectionAcquireTimeoutError'; - } -} - -module.exports = ConnectionAcquireTimeoutError; diff --git a/lib/errors/connection/connection-refused-error.js b/lib/errors/connection/connection-refused-error.js deleted file mode 100644 index 0c689c11aab6..000000000000 --- a/lib/errors/connection/connection-refused-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database is refused - */ -class ConnectionRefusedError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeConnectionRefusedError'; - } -} - -module.exports = ConnectionRefusedError; diff --git a/lib/errors/connection/connection-timed-out-error.js b/lib/errors/connection/connection-timed-out-error.js deleted file mode 100644 index 2a2004b9ab70..000000000000 --- a/lib/errors/connection/connection-timed-out-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database times out - */ -class ConnectionTimedOutError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeConnectionTimedOutError'; - } -} - -module.exports = ConnectionTimedOutError; diff --git a/lib/errors/connection/host-not-found-error.js b/lib/errors/connection/host-not-found-error.js deleted file mode 100644 index c0493aff9280..000000000000 --- a/lib/errors/connection/host-not-found-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database has a hostname that was not found - */ -class HostNotFoundError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeHostNotFoundError'; - } -} - -module.exports = HostNotFoundError; diff --git a/lib/errors/connection/host-not-reachable-error.js b/lib/errors/connection/host-not-reachable-error.js deleted file mode 100644 index a6bab854f143..000000000000 --- a/lib/errors/connection/host-not-reachable-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database has a hostname that was not reachable - */ -class HostNotReachableError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeHostNotReachableError'; - } -} - -module.exports = HostNotReachableError; diff --git a/lib/errors/connection/invalid-connection-error.js b/lib/errors/connection/invalid-connection-error.js deleted file mode 100644 index 538303f31c38..000000000000 --- a/lib/errors/connection/invalid-connection-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const ConnectionError = require('./../connection-error'); - -/** - * Thrown when a connection to a database has invalid values for any of the connection parameters - */ -class InvalidConnectionError extends ConnectionError { - constructor(parent) { - super(parent); - this.name = 'SequelizeInvalidConnectionError'; - } -} - -module.exports = InvalidConnectionError; diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js deleted file mode 100644 index c7059d3ba9e0..000000000000 --- a/lib/errors/database-error.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * A base class for all database related errors. - */ -class DatabaseError extends BaseError { - constructor(parent) { - super(parent.message); - this.name = 'SequelizeDatabaseError'; - /** - * @type {Error} - */ - this.parent = parent; - /** - * @type {Error} - */ - 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; - } -} - -module.exports = DatabaseError; diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js deleted file mode 100644 index f77e52aab3ea..000000000000 --- a/lib/errors/database/exclusion-constraint-error.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const DatabaseError = require('./../database-error'); - -/** - * Thrown when an exclusion constraint is violated in the database - */ -class ExclusionConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; - options.parent = options.parent || { sql: '' }; - - super(options.parent); - this.name = 'SequelizeExclusionConstraintError'; - - this.message = options.message || options.parent.message || ''; - this.constraint = options.constraint; - this.fields = options.fields; - this.table = options.table; - } -} - -module.exports = ExclusionConstraintError; diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js deleted file mode 100644 index 9bdf02ad0c14..000000000000 --- a/lib/errors/database/foreign-key-constraint-error.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const DatabaseError = require('./../database-error'); - -/** - * Thrown when a foreign key constraint is violated in the database - */ -class ForeignKeyConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; - options.parent = options.parent || { sql: '' }; - - super(options.parent); - this.name = 'SequelizeForeignKeyConstraintError'; - - this.message = options.message || options.parent.message || 'Database Error'; - this.fields = options.fields; - this.table = options.table; - this.value = options.value; - this.index = options.index; - this.reltype = options.reltype; - } -} - -module.exports = ForeignKeyConstraintError; diff --git a/lib/errors/database/timeout-error.js b/lib/errors/database/timeout-error.js deleted file mode 100644 index b67933b50f77..000000000000 --- a/lib/errors/database/timeout-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const DatabaseError = require('./../database-error'); - -/** - * Thrown when a database query times out because of a deadlock - */ -class TimeoutError extends DatabaseError { - constructor(parent) { - super(parent); - this.name = 'SequelizeTimeoutError'; - } -} - -module.exports = TimeoutError; diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js deleted file mode 100644 index e11fa666c7ef..000000000000 --- a/lib/errors/database/unknown-constraint-error.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const DatabaseError = require('./../database-error'); - -/** - * Thrown when constraint name is not found in the database - */ -class UnknownConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; - options.parent = options.parent || { sql: '' }; - - super(options.parent); - 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; - } -} - -module.exports = UnknownConstraintError; diff --git a/lib/errors/eager-loading-error.js b/lib/errors/eager-loading-error.js deleted file mode 100644 index 928af9c447bd..000000000000 --- a/lib/errors/eager-loading-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when an include statement is improperly constructed (see message for details) - */ -class EagerLoadingError extends BaseError { - constructor(message) { - super(message); - this.name = 'SequelizeEagerLoadingError'; - } -} - -module.exports = EagerLoadingError; diff --git a/lib/errors/empty-result-error.js b/lib/errors/empty-result-error.js deleted file mode 100644 index c967817d0690..000000000000 --- a/lib/errors/empty-result-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when a record was not found, Usually used with rejectOnEmpty mode (see message for details) - */ -class EmptyResultError extends BaseError { - constructor(message) { - super(message); - this.name = 'SequelizeEmptyResultError'; - } -} - -module.exports = EmptyResultError; diff --git a/lib/errors/index.js b/lib/errors/index.js deleted file mode 100644 index 16316a5acad4..000000000000 --- a/lib/errors/index.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -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'); -exports.DatabaseError = require('./database-error'); -exports.EagerLoadingError = require('./eager-loading-error'); -exports.EmptyResultError = require('./empty-result-error'); -exports.InstanceError = require('./instance-error'); -exports.OptimisticLockError = require('./optimistic-lock-error'); -exports.QueryError = require('./query-error'); -exports.SequelizeScopeError = require('./sequelize-scope-error'); -exports.ValidationError = require('./validation-error'); -exports.ValidationErrorItem = exports.ValidationError.ValidationErrorItem; - -exports.AccessDeniedError = require('./connection/access-denied-error'); -exports.ConnectionAcquireTimeoutError = require('./connection/connection-acquire-timeout-error'); -exports.ConnectionRefusedError = require('./connection/connection-refused-error'); -exports.ConnectionTimedOutError = require('./connection/connection-timed-out-error'); -exports.HostNotFoundError = require('./connection/host-not-found-error'); -exports.HostNotReachableError = require('./connection/host-not-reachable-error'); -exports.InvalidConnectionError = require('./connection/invalid-connection-error'); - -exports.ExclusionConstraintError = require('./database/exclusion-constraint-error'); -exports.ForeignKeyConstraintError = require('./database/foreign-key-constraint-error'); -exports.TimeoutError = require('./database/timeout-error'); -exports.UnknownConstraintError = require('./database/unknown-constraint-error'); - -exports.UniqueConstraintError = require('./validation/unique-constraint-error'); diff --git a/lib/errors/instance-error.js b/lib/errors/instance-error.js deleted file mode 100644 index 913ce1e3b3b7..000000000000 --- a/lib/errors/instance-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when a some problem occurred with Instance methods (see message for details) - */ -class InstanceError extends BaseError { - constructor(message) { - super(message); - this.name = 'SequelizeInstanceError'; - } -} - -module.exports = InstanceError; diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js deleted file mode 100644 index 0c0ff3eb7db4..000000000000 --- a/lib/errors/optimistic-lock-error.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when attempting to update a stale model instance - */ -class OptimisticLockError extends BaseError { - constructor(options) { - options = options || {}; - options.message = options.message || `Attempting to update a stale model instance: ${options.modelName}`; - super(options.message); - this.name = 'SequelizeOptimisticLockError'; - /** - * The name of the model on which the update was attempted - * - * @type {string} - */ - this.modelName = options.modelName; - /** - * The values of the attempted update - * - * @type {object} - */ - this.values = options.values; - /** - * - * @type {object} - */ - this.where = options.where; - } -} - -module.exports = OptimisticLockError; diff --git a/lib/errors/query-error.js b/lib/errors/query-error.js deleted file mode 100644 index 3ec05cc3712a..000000000000 --- a/lib/errors/query-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Thrown when a query is passed invalid options (see message for details) - */ -class QueryError extends BaseError { - constructor(message) { - super(message); - this.name = 'SequelizeQueryError'; - } -} - -module.exports = QueryError; diff --git a/lib/errors/sequelize-scope-error.js b/lib/errors/sequelize-scope-error.js deleted file mode 100644 index f7a40ad58c71..000000000000 --- a/lib/errors/sequelize-scope-error.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Scope Error. Thrown when the sequelize cannot query the specified scope. - */ -class SequelizeScopeError extends BaseError { - constructor(parent) { - super(parent); - this.name = 'SequelizeScopeError'; - } -} - -module.exports = SequelizeScopeError; diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js deleted file mode 100644 index 4bb4c9817bc6..000000000000 --- a/lib/errors/validation-error.js +++ /dev/null @@ -1,208 +0,0 @@ -'use strict'; - -const BaseError = require('./base-error'); - -/** - * Validation Error. Thrown when the sequelize validation has failed. The error contains an `errors` property, - * which is an array with 1 or more ValidationErrorItems, one for each validation that failed. - * - * @param {string} message Error message - * @param {Array} [errors] Array of ValidationErrorItem objects describing the validation errors - * - * @property errors {ValidationErrorItems[]} - */ -class ValidationError extends BaseError { - constructor(message, errors) { - super(message); - this.name = 'SequelizeValidationError'; - this.message = 'Validation Error'; - /** - * - * @type {ValidationErrorItem[]} - */ - this.errors = errors || []; - - // Use provided error message if available... - if (message) { - this.message = message; - - // ... otherwise create a concatenated message out of existing errors. - } else if (this.errors.length > 0 && this.errors[0].message) { - this.message = this.errors.map(err => `${err.type || err.origin}: ${err.message}`).join(',\n'); - } - } - - /** - * Gets all validation error items for the path / field specified. - * - * @param {string} path The path to be checked for error items - * - * @returns {Array} Validation error items for the specified path - */ - get(path) { - return this.errors.reduce((reduced, error) => { - if (error.path === path) { - reduced.push(error); - } - return reduced; - }, []); - } -} - -/** - * Validation Error Item - * Instances of this class are included in the `ValidationError.errors` property. - */ -class ValidationErrorItem { - /** - * 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 {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 {Array} [fnArgs] parameters used with the BUILT-IN validator function, if applicable - */ - constructor(message, type, path, value, instance, validatorKey, fnName, fnArgs) { - /** - * An error message - * - * @type {string} message - */ - this.message = message || ''; - - /** - * The type/origin of the validation error - * - * @type {string | null} - */ - this.type = null; - - /** - * The field that triggered the validation error - * - * @type {string | null} - */ - this.path = path || null; - - /** - * The value that generated the error - * - * @type {string | null} - */ - this.value = value !== undefined ? value : null; - - this.origin = null; - - /** - * The DAO instance that caused the validation error - * - * @type {Model | null} - */ - this.instance = instance || null; - - /** - * A validation "key", used for identification - * - * @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 | null} - */ - this.validatorName = fnName || null; - - /** - * Parameters used with the BUILT-IN validator function, if applicable - * - * @type {Array} - */ - this.validatorArgs = fnArgs || []; - - if (type) { - if (ValidationErrorItem.Origins[ type ]) { - this.origin = type; - } else { - const lowercaseType = `${type}`.toLowerCase().trim(); - const realType = ValidationErrorItem.TypeStringMap[ lowercaseType ]; - - if (realType && ValidationErrorItem.Origins[ realType ]) { - this.origin = realType; - this.type = type; - } - } - } - - // This doesn't need captureStackTrace because it's not a subclass of Error - } - - /** - * return a lowercase, trimmed string "key" that identifies the validator. - * - * Note: the string will be empty if the instance has neither a valid `validatorKey` property nor a valid `validatorName` property - * - * @param {boolean} [useTypeAsNS=true] controls whether the returned value is "namespace", - * this parameter is ignored if the validator's `type` is not one of ValidationErrorItem.Origins - * @param {string} [NSSeparator='.'] a separator string for concatenating the namespace, must be not be empty, - * defaults to "." (fullstop). only used and validated if useTypeAsNS is TRUE. - * @throws {Error} thrown if NSSeparator is found to be invalid. - * @returns {string} - * - * @private - */ - getValidatorKey(useTypeAsNS, NSSeparator) { - const useTANS = useTypeAsNS === undefined || !!useTypeAsNS; - const NSSep = NSSeparator === undefined ? '.' : NSSeparator; - - const type = this.origin; - const key = this.validatorKey || this.validatorName; - const useNS = useTANS && type && ValidationErrorItem.Origins[ type ]; - - if (useNS && (typeof NSSep !== 'string' || !NSSep.length)) { - throw new Error('Invalid namespace separator given, must be a non-empty string'); - } - - if (!(typeof key === 'string' && key.length)) { - return ''; - } - - return (useNS ? [type, key].join(NSSep) : key).toLowerCase().trim(); - } -} - -/** - * An enum that defines valid ValidationErrorItem `origin` values - * - * @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 - */ -ValidationErrorItem.Origins = { - CORE: 'CORE', - DB: 'DB', - FUNCTION: 'FUNCTION' -}; - -/** - * An object that is used internally by the `ValidationErrorItem` class - * that maps current `type` strings (as given to ValidationErrorItem.constructor()) to - * our new `origin` values. - * - * @type {object} - */ -ValidationErrorItem.TypeStringMap = { - 'notnull violation': 'CORE', - 'string violation': 'CORE', - 'unique violation': 'DB', - 'validation error': 'FUNCTION' -}; - -module.exports = ValidationError; -module.exports.ValidationErrorItem = ValidationErrorItem; diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js deleted file mode 100644 index a509e88ff4fb..000000000000 --- a/lib/errors/validation/unique-constraint-error.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const ValidationError = require('./../validation-error'); - -/** - * Thrown when a unique constraint is violated in the database - */ -class UniqueConstraintError extends ValidationError { - constructor(options) { - options = options || {}; - options.parent = options.parent || { sql: '' }; - options.message = options.message || options.parent.message || 'Validation Error'; - options.errors = options.errors || {}; - super(options.message, options.errors); - - this.name = 'SequelizeUniqueConstraintError'; - this.errors = options.errors; - this.fields = options.fields; - this.parent = options.parent; - this.original = options.parent; - this.sql = options.parent.sql; - } -} - -module.exports = UniqueConstraintError; diff --git a/lib/hooks.js b/lib/hooks.js deleted file mode 100644 index 69602a048b4d..000000000000 --- a/lib/hooks.js +++ /dev/null @@ -1,596 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const { logger } = require('./utils/logger'); -const debug = logger.debugContext('hooks'); - -const hookTypes = { - beforeValidate: { params: 2 }, - afterValidate: { params: 2 }, - validationFailed: { params: 3 }, - beforeCreate: { params: 2 }, - afterCreate: { params: 2 }, - beforeDestroy: { params: 2 }, - afterDestroy: { params: 2 }, - beforeRestore: { params: 2 }, - afterRestore: { params: 2 }, - beforeUpdate: { params: 2 }, - afterUpdate: { params: 2 }, - beforeSave: { params: 2, proxies: ['beforeUpdate', 'beforeCreate'] }, - afterSave: { params: 2, proxies: ['afterUpdate', 'afterCreate'] }, - beforeUpsert: { params: 2 }, - afterUpsert: { params: 2 }, - beforeBulkCreate: { params: 2 }, - afterBulkCreate: { params: 2 }, - beforeBulkDestroy: { params: 1 }, - afterBulkDestroy: { params: 1 }, - beforeBulkRestore: { params: 1 }, - afterBulkRestore: { params: 1 }, - beforeBulkUpdate: { params: 1 }, - afterBulkUpdate: { params: 1 }, - beforeFind: { params: 1 }, - beforeFindAfterExpandIncludeAll: { params: 1 }, - beforeFindAfterOptions: { params: 1 }, - afterFind: { params: 2 }, - beforeCount: { params: 1 }, - beforeDefine: { params: 2, sync: true, noModel: true }, - afterDefine: { params: 1, sync: true, noModel: true }, - beforeInit: { params: 2, sync: true, noModel: true }, - afterInit: { params: 1, sync: true, noModel: true }, - beforeAssociate: { params: 2, sync: true }, - afterAssociate: { params: 2, sync: true }, - beforeConnect: { params: 1, noModel: true }, - afterConnect: { params: 2, noModel: true }, - beforeDisconnect: { params: 1, noModel: true }, - afterDisconnect: { params: 1, noModel: true }, - beforeSync: { params: 1 }, - afterSync: { params: 1 }, - beforeBulkSync: { params: 1 }, - afterBulkSync: { params: 1 }, - beforeQuery: { params: 2 }, - afterQuery: { params: 2 } -}; -exports.hooks = hookTypes; - - -/** - * get array of current hook and its proxies combined - * - * @param {string} hookType any hook type @see {@link hookTypes} - * - * @private - */ -const getProxiedHooks = hookType => - hookTypes[hookType].proxies - ? hookTypes[hookType].proxies.concat(hookType) - : [hookType] -; - -function getHooks(hooked, hookType) { - return (hooked.options.hooks || {})[hookType] || []; -} - -const Hooks = { - /** - * Process user supplied hooks definition - * - * @param {object} hooks hooks definition - * - * @private - * @memberof Sequelize - * @memberof Sequelize.Model - */ - _setupHooks(hooks) { - this.options.hooks = {}; - _.map(hooks || {}, (hooksArray, hookName) => { - if (!Array.isArray(hooksArray)) hooksArray = [hooksArray]; - hooksArray.forEach(hookFn => this.addHook(hookName, hookFn)); - }); - }, - - async runHooks(hooks, ...hookArgs) { - if (!hooks) throw new Error('runHooks requires at least 1 argument'); - - let hookType; - - if (typeof hooks === 'string') { - hookType = hooks; - hooks = getHooks(this, hookType); - - if (this.sequelize) { - hooks = hooks.concat(getHooks(this.sequelize, hookType)); - } - } - - if (!Array.isArray(hooks)) { - hooks = [hooks]; - } - - // synchronous hooks - if (hookTypes[hookType] && hookTypes[hookType].sync) { - for (let hook of hooks) { - if (typeof hook === 'object') { - hook = hook.fn; - } - - debug(`running hook(sync) ${hookType}`); - hook.apply(this, hookArgs); - } - return; - } - - // asynchronous hooks (default) - for (let hook of hooks) { - if (typeof hook === 'object') { - hook = hook.fn; - } - - debug(`running hook ${hookType}`); - await hook.apply(this, hookArgs); - } - }, - - /** - * Add a hook to the model - * - * @param {string} hookType hook name @see {@link hookTypes} - * @param {string|Function} [name] Provide a name for the hook function. It can be used to remove the hook later or to order hooks based on some sort of priority system in the future. - * @param {Function} fn The hook function - * - * @memberof Sequelize - * @memberof Sequelize.Model - */ - addHook(hookType, name, fn) { - if (typeof name === 'function') { - fn = name; - name = null; - } - - debug(`adding hook ${hookType}`); - // check for proxies, add them too - hookType = getProxiedHooks(hookType); - - hookType.forEach(type => { - const hooks = getHooks(this, type); - hooks.push(name ? { name, fn } : fn); - this.options.hooks[type] = hooks; - }); - - return this; - }, - - /** - * Remove hook from the model - * - * @param {string} hookType @see {@link hookTypes} - * @param {string|Function} name name of hook or function reference which was attached - * - * @memberof Sequelize - * @memberof Sequelize.Model - */ - removeHook(hookType, name) { - const isReference = typeof name === 'function' ? true : false; - - if (!this.hasHook(hookType)) { - return this; - } - - debug(`removing hook ${hookType}`); - - // check for proxies, add them too - hookType = getProxiedHooks(hookType); - - for (const type of hookType) { - this.options.hooks[type] = this.options.hooks[type].filter(hook => { - if (isReference && typeof hook === 'function') { - return hook !== name; // check if same method - } - if (!isReference && typeof hook === 'object') { - return hook.name !== name; - } - return true; - }); - } - - return this; - }, - - /** - * Check whether the mode has any hooks of this type - * - * @param {string} hookType @see {@link hookTypes} - * - * @alias hasHooks - * - * @memberof Sequelize - * @memberof Sequelize.Model - */ - hasHook(hookType) { - return this.options.hooks[hookType] && !!this.options.hooks[hookType].length; - } -}; -Hooks.hasHooks = Hooks.hasHook; - - -function applyTo(target, isModel = false) { - _.mixin(target, Hooks); - - for (const hook of Object.keys(hookTypes)) { - if (isModel && hookTypes[hook].noModel) { - continue; - } - target[hook] = function(name, callback) { - return this.addHook(hook, name, callback); - }; - } -} -exports.applyTo = applyTo; - -/** - * A hook that is run before validation - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name beforeValidate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after validation - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name afterValidate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run when validation fails - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options, error. Error is the - * SequelizeValidationError. If the callback throws an error, it will replace the original validation error. - * @name validationFailed - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name afterCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeSave - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before upserting - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeUpsert - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after upserting - * - * @param {string} name - * @param {Function} fn A callback function that is called with the result of upsert(), options - * @name afterUpsert - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name afterSave - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before destroying a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name beforeDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after destroying a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name afterDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before restoring a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name beforeRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after restoring a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name afterRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before updating a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name beforeUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after updating a single instance - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name afterUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with instances, options - * @name beforeBulkCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with instances, options - * @name afterBulkCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before destroying instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name beforeBulkDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after destroying instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name afterBulkDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before restoring instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name beforeBulkRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after restoring instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name afterBulkRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before updating instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeBulkUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after updating instances in bulk - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name afterBulkUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFind - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFindAfterExpandIncludeAll - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFindAfterOptions - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after a find (select) query - * - * @param {string} name - * @param {Function} fn A callback function that is called with instance(s), options - * @name afterFind - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a count query - * - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeCount - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a define call - * - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeDefine - * @memberof Sequelize - */ - -/** - * A hook that is run after a define call - * - * @param {string} name - * @param {Function} fn A callback function that is called with factory - * @name afterDefine - * @memberof Sequelize - */ - -/** - * A hook that is run before Sequelize() call - * - * @param {string} name - * @param {Function} fn A callback function that is called with config, options - * @name beforeInit - * @memberof Sequelize - */ - -/** - * A hook that is run after Sequelize() call - * - * @param {string} name - * @param {Function} fn A callback function that is called with sequelize - * @name afterInit - * @memberof Sequelize - */ - -/** - * A hook that is run before a connection is created - * - * @param {string} name - * @param {Function} fn A callback function that is called with config passed to connection - * @name beforeConnect - * @memberof Sequelize - */ - -/** - * A hook that is run after a connection is created - * - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object and the config passed to connection - * @name afterConnect - * @memberof Sequelize - */ - -/** - * A hook that is run before a connection is disconnected - * - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object - * @name beforeDisconnect - * @memberof Sequelize - */ - -/** - * A hook that is run after a connection is disconnected - * - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object - * @name afterDisconnect - * @memberof Sequelize - */ - -/** - * A hook that is run before Model.sync call - * - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to Model.sync - * @name beforeSync - * @memberof Sequelize - */ - -/** - * A hook that is run after Model.sync call - * - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to Model.sync - * @name afterSync - * @memberof Sequelize - */ - -/** - * A hook that is run before sequelize.sync call - * - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to sequelize.sync - * @name beforeBulkSync - * @memberof Sequelize - */ - -/** - * A hook that is run after sequelize.sync call - * - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to sequelize.sync - * @name afterBulkSync - * @memberof Sequelize - */ diff --git a/lib/index-hints.js b/lib/index-hints.js deleted file mode 100644 index 9ecf4d64836a..000000000000 --- a/lib/index-hints.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -/** - * An enum of index hints to be used in mysql for querying with index hints - * - * @property USE - * @property FORCE - * @property IGNORE - */ -const IndexHints = module.exports = { // eslint-disable-line - USE: 'USE', - FORCE: 'FORCE', - IGNORE: 'IGNORE' -}; diff --git a/lib/instance-validator.js b/lib/instance-validator.js deleted file mode 100644 index 1e4f343a4160..000000000000 --- a/lib/instance-validator.js +++ /dev/null @@ -1,418 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const Utils = require('./utils'); -const sequelizeError = require('./errors'); -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. - * - * @private - */ -class InstanceValidator { - constructor(modelInstance, 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 || []; - } - - 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 - */ - this.validator = validator; - - /** - * All errors will be stored here from the validations. - * - * @type {Array} Will contain keys that correspond to attributes which will - * be Arrays of Errors. - * @private - */ - this.errors = []; - - /** - * @type {boolean} Indicates if validations are in progress - * @private - */ - this.inProgress = false; - } - - /** - * The main entry point for the Validation module, invoke to start the dance. - * - * @returns {Promise} - * @private - */ - async _validate() { - if (this.inProgress) throw new Error('Validations already in progress.'); - - this.inProgress = true; - - await Promise.all([ - this._perAttributeValidators(), - this._customValidators() - ]); - - if (this.errors.length) { - throw new sequelizeError.ValidationError(null, this.errors); - } - } - - /** - * Invoke the Validation sequence and run validation hooks if defined - * - Before Validation Model Hooks - * - Validation - * - On validation success: After Validation Model Hooks - * - On validation failure: Validation Failed Model Hooks - * - * @returns {Promise} - * @private - */ - async validate() { - return await (this.options.hooks ? this._validateAndRunHooks() : this._validate()); - } - - /** - * Invoke the Validation sequence and run hooks - * - Before Validation Model Hooks - * - Validation - * - On validation success: After Validation Model Hooks - * - On validation failure: Validation Failed Model Hooks - * - * @returns {Promise} - * @private - */ - async _validateAndRunHooks() { - const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); - await runHooks('beforeValidate', this.modelInstance, this.options); - - try { - await this._validate(); - } catch (error) { - const newError = await runHooks('validationFailed', this.modelInstance, this.options, error); - throw newError || error; - } - - await runHooks('afterValidate', this.modelInstance, this.options); - return this.modelInstance; - } - - /** - * Will run all the validators defined per attribute (built-in validators and custom validators) - * - * @returns {Promise} - * @private - */ - async _perAttributeValidators() { - // promisify all attribute invocations - const validators = []; - - _.forIn(this.modelInstance.rawAttributes, (rawAttribute, field) => { - if (this.options.skip.includes(field)) { - return; - } - - const value = this.modelInstance.dataValues[field]; - - if (value instanceof Utils.SequelizeMethod) { - return; - } - - if (!rawAttribute._autoGenerated && !rawAttribute.autoIncrement) { - // perform validations based on schema - this._validateSchema(rawAttribute, field, value); - } - - if (Object.prototype.hasOwnProperty.call(this.modelInstance.validators, field)) { - validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull)); - } - }); - - return await Promise.all(validators); - } - - /** - * Will run all the custom validators defined in the model's options. - * - * @returns {Promise} - * @private - */ - async _customValidators() { - const validators = []; - _.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(() => {}); - - validators.push(valprom); - }); - - return await Promise.all(validators); - } - - /** - * Validate a single attribute with all the defined built-in validators and custom validators. - * - * @private - * - * @param {*} value Anything. - * @param {string} field The field name. - * @param {boolean} allowNull Whether or not the schema allows null values - * - * @returns {Promise} A promise, will always resolve, auto populates error on this.error local object. - */ - 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; - } - - // Promisify each validator - const validators = []; - _.forIn(this.modelInstance.validators[field], (test, validatorType) => { - - if (validatorType === 'isUrl' || validatorType === 'isURL' || validatorType === 'isEmail') { - // Preserve backwards compat. Validator.js now expects the second param to isURL and isEmail to be an object - if (typeof test === 'object' && test !== null && test.msg) { - test = { - msg: test.msg - }; - } else if (test === true) { - test = {}; - } - } - - // 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)); - return; - } - - // If value is null, built-in validators should not run (only custom validators have to run) (see #9134). - if (value === null || value === undefined) { - return; - } - - const validatorPromise = this._invokeBuiltinValidator(value, test, validatorType, field); - // errors are handled in settling, stub this - validatorPromise.catch(() => {}); - validators.push(validatorPromise); - }); - - return Promise - .all(validators.map(validator => validator.catch(rejection => { - const isBuiltIn = !!rejection.validatorName; - this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs); - }))); - } - - /** - * Prepare and invoke a custom validator. - * - * @private - * - * @param {Function} validator The custom validator. - * @param {string} validatorType the custom validator type (name). - * @param {boolean} optAttrDefined Set to true if custom validator was defined from the attribute - * @param {*} optValue value for attribute - * @param {string} optField field for attribute - * - * @returns {Promise} A promise. - */ - async _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { - let isAsync = false; - - const validatorArity = validator.length; - // check if validator is async and requires a callback - let asyncArity = 1; - let errorKey = validatorType; - let invokeArgs; - if (optAttrDefined) { - asyncArity = 2; - invokeArgs = optValue; - errorKey = optField; - } - if (validatorArity === asyncArity) { - isAsync = true; - } - - if (isAsync) { - 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); - } - } - - try { - return await validator.call(this.modelInstance, invokeArgs); - } catch (e) { - return this._pushError(false, errorKey, e, optValue, validatorType); - } - } - - /** - * Prepare and invoke a build-in validator. - * - * @private - * - * @param {*} value Anything. - * @param {*} test The test case. - * @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. - */ - 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); - - if (!validator[validatorType](valueString, ...validatorArgs)) { - throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs }); - } - } - - /** - * Will extract arguments for the validator. - * - * @param {*} test The test case. - * @param {string} validatorType One of known to Sequelize validators. - * @param {string} field The field that is being validated. - * - * @private - */ - _extractValidatorArgs(test, validatorType, field) { - let validatorArgs = test.args || test; - const isLocalizedValidator = typeof validatorArgs !== 'string' && (validatorType === 'isAlpha' || validatorType === 'isAlphanumeric' || validatorType === 'isMobilePhone'); - - if (!Array.isArray(validatorArgs)) { - if (validatorType === 'isImmutable') { - validatorArgs = [validatorArgs, field, this.modelInstance]; - } else if (isLocalizedValidator || validatorType === 'isIP') { - validatorArgs = []; - } else { - validatorArgs = [validatorArgs]; - } - } else { - validatorArgs = validatorArgs.slice(0); - } - return validatorArgs; - } - - /** - * Will validate a single field against its schema definition (isnull). - * - * @param {object} rawAttribute As defined in the Schema. - * @param {string} field The field name. - * @param {*} value anything. - * - * @private - */ - _validateSchema(rawAttribute, field, value) { - if (rawAttribute.allowNull === false && (value === null || value === undefined)) { - 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`); - - this.errors.push(new sequelizeError.ValidationErrorItem( - errMsg, - 'notNull Violation', // sequelizeError.ValidationErrorItem.Origins.CORE, - field, - value, - this.modelInstance, - 'is_null' - )); - } - } - - if (rawAttribute.type instanceof DataTypes.STRING || rawAttribute.type instanceof DataTypes.TEXT || rawAttribute.type instanceof DataTypes.CITEXT) { - if (Array.isArray(value) || _.isObject(value) && !(value instanceof Utils.SequelizeMethod) && !Buffer.isBuffer(value)) { - this.errors.push(new sequelizeError.ValidationErrorItem( - `${field} cannot be an array or an object`, - 'string violation', // sequelizeError.ValidationErrorItem.Origins.CORE, - field, - value, - this.modelInstance, - 'not_a_string' - )); - } - } - } - - /** - * Signs all errors retaining the original. - * - * @param {boolean} isBuiltin - Determines if error is from builtin validator. - * @param {string} errorKey - name of invalid attribute. - * @param {Error|string} rawError - The original error. - * @param {string|number} value - The data that triggered the error. - * @param {string} fnName - Name of the validator, if any - * @param {Array} fnArgs - Arguments for the validator [function], if any - * - * @private - */ - _pushError(isBuiltin, errorKey, rawError, value, fnName, fnArgs) { - const message = rawError.message || rawError || 'Validation error'; - const error = new sequelizeError.ValidationErrorItem( - message, - 'Validation error', // sequelizeError.ValidationErrorItem.Origins.FUNCTION, - errorKey, - value, - this.modelInstance, - fnName, - isBuiltin ? fnName : undefined, - isBuiltin ? fnArgs : undefined - ); - - error[InstanceValidator.RAW_KEY_NAME] = rawError; - - this.errors.push(error); - } -} -/** - * The error key for arguments as passed by custom validators - * - * @type {string} - * @private - */ -InstanceValidator.RAW_KEY_NAME = 'original'; - -module.exports = InstanceValidator; -module.exports.InstanceValidator = InstanceValidator; -module.exports.default = InstanceValidator; diff --git a/lib/model-manager.js b/lib/model-manager.js deleted file mode 100644 index 3f36edcf1e3e..000000000000 --- a/lib/model-manager.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - -const Toposort = require('toposort-class'); -const _ = require('lodash'); - -class ModelManager { - constructor(sequelize) { - this.models = []; - this.sequelize = sequelize; - } - - addModel(model) { - this.models.push(model); - this.sequelize.models[model.name] = model; - - return model; - } - - removeModel(modelToRemove) { - this.models = this.models.filter(model => model.name !== modelToRemove.name); - - delete this.sequelize.models[modelToRemove.name]; - } - - getModel(against, options) { - options = _.defaults(options || {}, { - attribute: 'name' - }); - - return this.models.find(model => model[options.attribute] === against); - } - - get all() { - return this.models; - } - - /** - * Iterate over Models in an order suitable for e.g. creating tables. - * 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 - * @private - */ - forEachModel(iterator, options) { - const models = {}; - const sorter = new Toposort(); - let sorted; - let dep; - - options = _.defaults(options || {}, { - reverse: true - }); - - for (const model of this.models) { - let deps = []; - let tableName = model.getTableName(); - - if (_.isObject(tableName)) { - tableName = `${tableName.schema}.${tableName.tableName}`; - } - - models[tableName] = model; - - for (const attrName in model.rawAttributes) { - if (Object.prototype.hasOwnProperty.call(model.rawAttributes, attrName)) { - const attribute = model.rawAttributes[attrName]; - - if (attribute.references) { - dep = attribute.references.model; - - if (_.isObject(dep)) { - dep = `${dep.schema}.${dep.tableName}`; - } - - deps.push(dep); - } - } - } - - deps = deps.filter(dep => tableName !== dep); - - sorter.add(tableName, deps); - } - - sorted = sorter.sort(); - if (options.reverse) { - sorted = sorted.reverse(); - } - for (const name of sorted) { - iterator(models[name], name); - } - } -} - -module.exports = ModelManager; -module.exports.ModelManager = ModelManager; -module.exports.default = ModelManager; diff --git a/lib/model.js b/lib/model.js deleted file mode 100644 index 595d18147e8d..000000000000 --- a/lib/model.js +++ /dev/null @@ -1,4489 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const _ = require('lodash'); -const Dottie = require('dottie'); - -const Utils = require('./utils'); -const { logger } = require('./utils/logger'); -const BelongsTo = require('./associations/belongs-to'); -const BelongsToMany = require('./associations/belongs-to-many'); -const InstanceValidator = require('./instance-validator'); -const QueryTypes = require('./query-types'); -const sequelizeErrors = require('./errors'); -const Association = require('./associations/base'); -const HasMany = require('./associations/has-many'); -const DataTypes = require('./data-types'); -const Hooks = require('./hooks'); -const associationsMixin = require('./associations/mixin'); -const Op = require('./operators'); -const { noDoubleNestedGroup } = require('./utils/deprecations'); - - -// This list will quickly become dated, but failing to maintain this list just means -// we won't throw a warning when we should. At least most common cases will forever be covered -// so we stop throwing erroneous warnings when we shouldn't. -const validQueryKeywords = new Set(['where', 'attributes', 'paranoid', 'include', 'order', 'limit', 'offset', - 'transaction', 'lock', 'raw', 'logging', 'benchmark', 'having', 'searchPath', 'rejectOnEmpty', 'plain', - 'scope', 'group', 'through', 'defaults', 'distinct', 'primary', 'exception', 'type', 'hooks', 'force', - 'name']); - -// List of attributes that should not be implicitly passed into subqueries/includes. -const nonCascadingOptions = ['include', 'attributes', 'originalAttributes', 'order', 'where', 'limit', 'offset', 'plain', 'group', 'having']; - -/** - * A Model represents a table in the database. Instances of this class represent a database row. - * - * Model instances operate with the concept of a `dataValues` property, which stores the actual values represented by the instance. - * By default, the values from dataValues can also be accessed directly from the Instance, that is: - * ```js - * instance.field - * // is the same as - * instance.get('field') - * // is the same as - * instance.getDataValue('field') - * ``` - * However, if getters and/or setters are defined for `field` they will be invoked, instead of returning the value from `dataValues`. - * Accessing properties directly or using `get` is preferred for regular use, `getDataValue` should only be used for custom getters. - * - * @see - * {@link Sequelize#define} for more information about getters and setters - * @mixes Hooks - */ -class Model { - static get queryInterface() { - return this.sequelize.getQueryInterface(); - } - - static get queryGenerator() { - return this.queryInterface.queryGenerator; - } - - /** - * A reference to the sequelize instance - * - * @see - * {@link Sequelize} - * - * @property sequelize - * - * @returns {Sequelize} - */ - get sequelize() { - return this.constructor.sequelize; - } - - /** - * Builds a new model instance. - * - * @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 = { - isNewRecord: true, - _schema: this.constructor._schema, - _schemaDelimiter: this.constructor._schemaDelimiter, - ...options - }; - - if (options.attributes) { - options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); - } - - if (!options.includeValidated) { - this.constructor._conformIncludes(options, this.constructor); - if (options.include) { - this.constructor._expandIncludeAll(options); - this.constructor._validateIncludedElements(options); - } - } - - this.dataValues = {}; - this._previousDataValues = {}; - this._changed = new Set(); - this._options = options || {}; - - /** - * Returns true if this instance has not yet been persisted to the database - * - * @property isNewRecord - * @returns {boolean} - */ - this.isNewRecord = options.isNewRecord; - - this._initValues(values, options); - } - - _initValues(values, options) { - let defaults; - let key; - - values = { ...values }; - - if (options.isNewRecord) { - defaults = {}; - - if (this.constructor._hasDefaultValues) { - defaults = _.mapValues(this.constructor._defaultValues, valueFn => { - const value = valueFn(); - return value && value instanceof Utils.SequelizeMethod ? value : _.cloneDeep(value); - }); - } - - // set id to null if not passed as value, a newly created dao has no id - // removing this breaks bulkCreate - // do after default values since it might have UUID as a default value - if (this.constructor.primaryKeyAttributes.length) { - this.constructor.primaryKeyAttributes.forEach(primaryKeyAttribute => { - if (!Object.prototype.hasOwnProperty.call(defaults, primaryKeyAttribute)) { - defaults[primaryKeyAttribute] = null; - } - }); - } - - if (this.constructor._timestampAttributes.createdAt && defaults[this.constructor._timestampAttributes.createdAt]) { - this.dataValues[this.constructor._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.createdAt], this.sequelize.options.dialect); - delete defaults[this.constructor._timestampAttributes.createdAt]; - } - - if (this.constructor._timestampAttributes.updatedAt && defaults[this.constructor._timestampAttributes.updatedAt]) { - this.dataValues[this.constructor._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.updatedAt], this.sequelize.options.dialect); - delete defaults[this.constructor._timestampAttributes.updatedAt]; - } - - if (this.constructor._timestampAttributes.deletedAt && defaults[this.constructor._timestampAttributes.deletedAt]) { - this.dataValues[this.constructor._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.constructor._timestampAttributes.deletedAt], this.sequelize.options.dialect); - delete defaults[this.constructor._timestampAttributes.deletedAt]; - } - - for (key in defaults) { - if (values[key] === undefined) { - this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true }); - delete values[key]; - } - } - } - - this.set(values, options); - } - - // validateIncludedElements should have been called before this method - static _paranoidClause(model, options = {}) { - // Apply on each include - // This should be handled before handling where conditions because of logic with returns - // otherwise this code will never run on includes of a already conditionable where - if (options.include) { - for (const include of options.include) { - this._paranoidClause(include.model, include); - } - } - - // apply paranoid when groupedLimit is used - if (_.get(options, 'groupedLimit.on.options.paranoid')) { - const throughModel = _.get(options, 'groupedLimit.on.through.model'); - if (throughModel) { - options.groupedLimit.through = this._paranoidClause(throughModel, options.groupedLimit.through); - } - } - - if (!model.options.timestamps || !model.options.paranoid || options.paranoid === false) { - // This model is not paranoid, nothing to do here; - return options; - } - - const deletedAtCol = model._timestampAttributes.deletedAt; - const deletedAtAttribute = model.rawAttributes[deletedAtCol]; - const deletedAtObject = {}; - - let deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - deletedAtDefaultValue = deletedAtDefaultValue || { - [Op.eq]: null - }; - - deletedAtObject[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; - - if (Utils.isWhereEmpty(options.where)) { - options.where = deletedAtObject; - } else { - options.where = { [Op.and]: [deletedAtObject, options.where] }; - } - - return options; - } - - static _addDefaultAttributes() { - const tail = {}; - let head = {}; - - // Add id if no primary key was manually added to definition - // Can't use this.primaryKeys here, since this function is called before PKs are identified - if (!_.some(this.rawAttributes, 'primaryKey')) { - if ('id' in this.rawAttributes) { - // Something is fishy here! - throw new Error(`A column called 'id' was added to the attributes of '${this.tableName}' but not marked with 'primaryKey: true'`); - } - - head = { - id: { - type: new DataTypes.INTEGER(), - allowNull: false, - primaryKey: true, - autoIncrement: true, - _autoGenerated: true - } - }; - } - - if (this._timestampAttributes.createdAt) { - tail[this._timestampAttributes.createdAt] = { - type: DataTypes.DATE, - allowNull: false, - _autoGenerated: true - }; - } - - if (this._timestampAttributes.updatedAt) { - tail[this._timestampAttributes.updatedAt] = { - type: DataTypes.DATE, - allowNull: false, - _autoGenerated: true - }; - } - - if (this._timestampAttributes.deletedAt) { - tail[this._timestampAttributes.deletedAt] = { - type: DataTypes.DATE, - _autoGenerated: true - }; - } - - if (this._versionAttribute) { - tail[this._versionAttribute] = { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - _autoGenerated: true - }; - } - - const newRawAttributes = { - ...head, - ...this.rawAttributes - }; - _.each(tail, (value, attr) => { - if (newRawAttributes[attr] === undefined) { - newRawAttributes[attr] = value; - } - }); - - this.rawAttributes = newRawAttributes; - - if (!Object.keys(this.primaryKeys).length) { - this.primaryKeys.id = this.rawAttributes.id; - } - } - - static _findAutoIncrementAttribute() { - this.autoIncrementAttribute = null; - - for (const name in this.rawAttributes) { - if (Object.prototype.hasOwnProperty.call(this.rawAttributes, name)) { - const definition = this.rawAttributes[name]; - if (definition && definition.autoIncrement) { - if (this.autoIncrementAttribute) { - throw new Error('Invalid Instance definition. Only one autoincrement field allowed.'); - } - this.autoIncrementAttribute = name; - } - } - } - } - - static _conformIncludes(options, self) { - if (!options.include) return; - - // if include is not an array, wrap in an array - if (!Array.isArray(options.include)) { - options.include = [options.include]; - } else if (!options.include.length) { - delete options.include; - return; - } - - // convert all included elements to { model: Model } form - options.include = options.include.map(include => this._conformInclude(include, self)); - } - - static _transformStringAssociation(include, self) { - if (self && typeof include === 'string') { - if (!Object.prototype.hasOwnProperty.call(self.associations, include)) { - throw new Error(`Association with alias "${include}" does not exist on ${self.name}`); - } - return self.associations[include]; - } - return include; - } - - static _conformInclude(include, self) { - if (include) { - let model; - - if (include._pseudo) return include; - - include = this._transformStringAssociation(include, self); - - if (include instanceof Association) { - if (self && include.target.name === self.name) { - model = include.source; - } else { - model = include.target; - } - - return { model, association: include, as: include.as }; - } - - if (include.prototype && include.prototype instanceof Model) { - return { model: include }; - } - - if (_.isPlainObject(include)) { - if (include.association) { - include.association = this._transformStringAssociation(include.association, self); - - if (self && include.association.target.name === self.name) { - model = include.association.source; - } else { - model = include.association.target; - } - - if (!include.model) include.model = model; - if (!include.as) include.as = include.association.as; - - this._conformIncludes(include, model); - return include; - } - - if (include.model) { - this._conformIncludes(include, include.model); - return include; - } - - if (include.all) { - this._conformIncludes(include); - return include; - } - } - } - - throw new Error('Include unexpected. Element has to be either a Model, an Association or an object.'); - } - - static _expandIncludeAllElement(includes, include) { - // check 'all' attribute provided is valid - let all = include.all; - delete include.all; - - if (all !== true) { - if (!Array.isArray(all)) { - all = [all]; - } - - const validTypes = { - BelongsTo: true, - HasOne: true, - HasMany: true, - One: ['BelongsTo', 'HasOne'], - Has: ['HasOne', 'HasMany'], - Many: ['HasMany'] - }; - - for (let i = 0; i < all.length; i++) { - const type = all[i]; - if (type === 'All') { - all = true; - break; - } - - const types = validTypes[type]; - if (!types) { - throw new sequelizeErrors.EagerLoadingError(`include all '${type}' is not valid - must be BelongsTo, HasOne, HasMany, One, Has, Many or All`); - } - - if (types !== true) { - // replace type placeholder e.g. 'One' with its constituent types e.g. 'HasOne', 'BelongsTo' - all.splice(i, 1); - i--; - for (let j = 0; j < types.length; j++) { - if (!all.includes(types[j])) { - all.unshift(types[j]); - i++; - } - } - } - } - } - - // add all associations of types specified to includes - const nested = include.nested; - if (nested) { - delete include.nested; - - if (!include.include) { - include.include = []; - } else if (!Array.isArray(include.include)) { - include.include = [include.include]; - } - } - - const used = []; - (function addAllIncludes(parent, includes) { - _.forEach(parent.associations, association => { - if (all !== true && !all.includes(association.associationType)) { - return; - } - - // check if model already included, and skip if so - const model = association.target; - const as = association.options.as; - - const predicate = { model }; - if (as) { - // We only add 'as' to the predicate if it actually exists - predicate.as = as; - } - - if (_.some(includes, predicate)) { - return; - } - - // skip if recursing over a model already nested - if (nested && used.includes(model)) { - return; - } - used.push(parent); - - // include this model - const thisInclude = Utils.cloneDeep(include); - thisInclude.model = model; - if (as) { - thisInclude.as = as; - } - includes.push(thisInclude); - - // run recursively if nested - if (nested) { - addAllIncludes(model, thisInclude.include); - if (thisInclude.include.length === 0) delete thisInclude.include; - } - }); - used.pop(); - })(this, includes); - } - - static _validateIncludedElements(options, tableNames) { - if (!options.model) options.model = this; - - tableNames = tableNames || {}; - options.includeNames = []; - options.includeMap = {}; - - /* Legacy */ - options.hasSingleAssociation = false; - options.hasMultiAssociation = false; - - if (!options.parent) { - options.topModel = options.model; - options.topLimit = options.limit; - } - - options.include = options.include.map(include => { - include = this._conformInclude(include); - include.parent = options; - include.topLimit = options.topLimit; - - this._validateIncludedElement.call(options.model, include, tableNames, options); - - if (include.duplicating === undefined) { - include.duplicating = include.association.isMultiAssociation; - } - - include.hasDuplicating = include.hasDuplicating || include.duplicating; - include.hasRequired = include.hasRequired || include.required; - - options.hasDuplicating = options.hasDuplicating || include.hasDuplicating; - options.hasRequired = options.hasRequired || include.required; - - options.hasWhere = options.hasWhere || include.hasWhere || !!include.where; - return include; - }); - - for (const include of options.include) { - include.hasParentWhere = options.hasParentWhere || !!options.where; - include.hasParentRequired = options.hasParentRequired || !!options.required; - - if (include.subQuery !== false && options.hasDuplicating && options.topLimit) { - if (include.duplicating) { - include.subQuery = false; - include.subQueryFilter = include.hasRequired; - } else { - include.subQuery = include.hasRequired; - include.subQueryFilter = false; - } - } else { - include.subQuery = include.subQuery || false; - if (include.duplicating) { - include.subQueryFilter = include.subQuery; - include.subQuery = false; - } else { - include.subQueryFilter = false; - include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired && !include.separate; - } - } - - options.includeMap[include.as] = include; - options.includeNames.push(include.as); - - // Set top level options - if (options.topModel === options.model && options.subQuery === undefined && options.topLimit) { - if (include.subQuery) { - options.subQuery = include.subQuery; - } else if (include.hasDuplicating) { - options.subQuery = true; - } - } - - /* Legacy */ - options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where; - options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required; - - if (include.association.isMultiAssociation || include.hasMultiAssociation) { - options.hasMultiAssociation = true; - } - if (include.association.isSingleAssociation || include.hasSingleAssociation) { - options.hasSingleAssociation = true; - } - } - - if (options.topModel === options.model && options.subQuery === undefined) { - options.subQuery = false; - } - return options; - } - - static _validateIncludedElement(include, tableNames, options) { - tableNames[include.model.getTableName()] = true; - - if (include.attributes && !options.raw) { - include.model._expandAttributes(include); - - include.originalAttributes = include.model._injectDependentVirtualAttributes(include.attributes); - - include = Utils.mapFinderOptions(include, include.model); - - if (include.attributes.length) { - _.each(include.model.primaryKeys, (attr, key) => { - // Include the primary key if it's not already included - take into account that the pk might be aliased (due to a .field prop) - if (!include.attributes.some(includeAttr => { - if (attr.field !== key) { - return Array.isArray(includeAttr) && includeAttr[0] === attr.field && includeAttr[1] === key; - } - return includeAttr === key; - })) { - include.attributes.unshift(key); - } - }); - } - } else { - include = Utils.mapFinderOptions(include, include.model); - } - - // pseudo include just needed the attribute logic, return - if (include._pseudo) { - if (!include.attributes) { - include.attributes = Object.keys(include.model.tableAttributes); - } - return Utils.mapFinderOptions(include, include.model); - } - - // check if the current Model is actually associated with the passed Model - or it's a pseudo include - const association = include.association || this._getIncludedAssociation(include.model, include.as); - - include.association = association; - include.as = association.as; - - // If through, we create a pseudo child include, to ease our parsing later on - if (include.association.through && Object(include.association.through.model) === include.association.through.model) { - if (!include.include) include.include = []; - const through = include.association.through; - - include.through = _.defaults(include.through || {}, { - model: through.model, - as: through.model.name, - association: { - isSingleAssociation: true - }, - _pseudo: true, - parent: include - }); - - - if (through.scope) { - include.through.where = include.through.where ? { [Op.and]: [include.through.where, through.scope] } : through.scope; - } - - include.include.push(include.through); - tableNames[through.tableName] = true; - } - - // include.model may be the main model, while the association target may be scoped - thus we need to look at association.target/source - let model; - if (include.model.scoped === true) { - // If the passed model is already scoped, keep that - model = include.model; - } else { - // Otherwise use the model that was originally passed to the association - model = include.association.target.name === include.model.name ? include.association.target : include.association.source; - } - - model._injectScope(include); - - // This check should happen after injecting the scope, since the scope may contain a .attributes - if (!include.attributes) { - include.attributes = Object.keys(include.model.tableAttributes); - } - - include = Utils.mapFinderOptions(include, include.model); - - if (include.required === undefined) { - include.required = !!include.where; - } - - if (include.association.scope) { - include.where = include.where ? { [Op.and]: [include.where, include.association.scope] } : include.association.scope; - } - - if (include.limit && include.separate === undefined) { - include.separate = true; - } - - if (include.separate === true) { - if (!(include.association instanceof HasMany)) { - throw new Error('Only HasMany associations support include.separate'); - } - - include.duplicating = false; - - if ( - options.attributes - && options.attributes.length - && !_.flattenDepth(options.attributes, 2).includes(association.sourceKey) - ) { - options.attributes.push(association.sourceKey); - } - - if ( - include.attributes - && include.attributes.length - && !_.flattenDepth(include.attributes, 2).includes(association.foreignKey) - ) { - include.attributes.push(association.foreignKey); - } - } - - // Validate child includes - if (Object.prototype.hasOwnProperty.call(include, 'include')) { - this._validateIncludedElements.call(include.model, include, tableNames); - } - - return include; - } - - static _getIncludedAssociation(targetModel, targetAlias) { - const associations = this.getAssociations(targetModel); - let association = null; - if (associations.length === 0) { - throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is not associated to ${this.name}!`); - } - if (associations.length === 1) { - association = this.getAssociationForAlias(targetModel, targetAlias); - if (association) { - return association; - } - if (targetAlias) { - const existingAliases = this.getAssociations(targetModel).map(association => association.as); - throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} using an alias. ` + - `You've included an alias (${targetAlias}), but it does not match the alias(es) defined in your association (${existingAliases.join(', ')}).`); - } - throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} using an alias. ` + - 'You must use the \'as\' keyword to specify the alias within your include statement.'); - } - association = this.getAssociationForAlias(targetModel, targetAlias); - if (!association) { - throw new sequelizeErrors.EagerLoadingError(`${targetModel.name} is associated to ${this.name} multiple times. ` + - 'To identify the correct association, you must use the \'as\' keyword to specify the alias of the association you want to include.'); - } - return association; - } - - - static _expandIncludeAll(options) { - const includes = options.include; - if (!includes) { - return; - } - - for (let index = 0; index < includes.length; index++) { - const include = includes[index]; - - if (include.all) { - includes.splice(index, 1); - index--; - - this._expandIncludeAllElement(includes, include); - } - } - - includes.forEach(include => { - this._expandIncludeAll.call(include.model, include); - }); - } - - static _conformIndex(index) { - if (!index.fields) { - throw new Error('Missing "fields" property for index definition'); - } - - index = _.defaults(index, { - type: '', - parser: null - }); - - if (index.type && index.type.toLowerCase() === 'unique') { - index.unique = true; - delete index.type; - } - - return index; - } - - - static _uniqIncludes(options) { - if (!options.include) return; - - options.include = _(options.include) - .groupBy(include => `${include.model && include.model.name}-${include.as}`) - .map(includes => this._assignOptions(...includes)) - .value(); - } - - static _baseMerge(...args) { - _.assignWith(...args); - this._conformIncludes(args[0], this); - this._uniqIncludes(args[0]); - return args[0]; - } - - static _mergeFunction(objValue, srcValue, key) { - if (Array.isArray(objValue) && Array.isArray(srcValue)) { - return _.union(objValue, srcValue); - } - if (key === 'where' || key === 'having') { - if (srcValue instanceof Utils.SequelizeMethod) { - srcValue = { [Op.and]: srcValue }; - } - if (_.isPlainObject(objValue) && _.isPlainObject(srcValue)) { - return Object.assign(objValue, srcValue); - } - } else if (key === 'attributes' && _.isPlainObject(objValue) && _.isPlainObject(srcValue)) { - return _.assignWith(objValue, srcValue, (objValue, srcValue) => { - if (Array.isArray(objValue) && Array.isArray(srcValue)) { - return _.union(objValue, srcValue); - } - }); - } - // If we have a possible object/array to clone, we try it. - // Otherwise, we return the original value when it's not undefined, - // or the resulting object in that case. - if (srcValue) { - return Utils.cloneDeep(srcValue, true); - } - return srcValue === undefined ? objValue : srcValue; - } - - static _assignOptions(...args) { - return this._baseMerge(...args, this._mergeFunction); - } - - static _defaultsOptions(target, opts) { - return this._baseMerge(target, opts, (srcValue, objValue, key) => { - return this._mergeFunction(objValue, srcValue, key); - }); - } - - /** - * Initialize a model, representing a table in the DB, with attributes and options. - * - * The table columns are defined by the hash that is given as the first argument. - * Each attribute of the hash represents a column. - * - * @example - * Project.init({ - * columnA: { - * type: Sequelize.BOOLEAN, - * validate: { - * is: ['[a-z]','i'], // will only allow letters - * max: 23, // only allow values <= 23 - * isIn: { - * args: [['en', 'zh']], - * msg: "Must be English or Chinese" - * } - * }, - * field: 'column_a' - * // Other attributes here - * }, - * columnB: Sequelize.STRING, - * columnC: 'MY VERY OWN COLUMN TYPE' - * }, {sequelize}) - * - * sequelize.models.modelName // The model will now be available in models under the class name - * - * @see - * Model Basics guide - * - * @see - * Hooks guide - * - * @see - * Validations & Constraints guide - * - * @param {object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: - * @param {string|DataTypes|object} attributes.column The description of a database column - * @param {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`) - * @param {string|boolean} [attributes.column.unique=false] If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index - * @param {boolean} [attributes.column.primaryKey=false] If true, this attribute will be marked as primary key - * @param {string} [attributes.column.field=null] If set, sequelize will map the attribute name to a different name in the database - * @param {boolean} [attributes.column.autoIncrement=false] If true, this column will be set to auto increment - * @param {boolean} [attributes.column.autoIncrementIdentity=false] If true, combined with autoIncrement=true, will use Postgres `GENERATED BY DEFAULT AS IDENTITY` instead of `SERIAL`. Postgres 10+ only. - * @param {string} [attributes.column.comment=null] Comment for this column - * @param {string|Model} [attributes.column.references=null] An object with reference configurations - * @param {string|Model} [attributes.column.references.model] If this column references another table, provide it here as a Model, or a string - * @param {string} [attributes.column.references.key='id'] The column of the foreign table that this column references - * @param {string} [attributes.column.onUpdate] What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION - * @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 {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 {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 {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 {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 {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. - * @param {string} [options.tableName] Defaults to pluralized model name, unless freezeTableName is true, in which case it uses model name verbatim - * @param {string} [options.schema='public'] schema - * @param {string} [options.engine] Specify engine for model's table - * @param {string} [options.charset] Specify charset for model's table - * @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. - * - * @returns {Model} - */ - static init(attributes, options = {}) { - if (!options.sequelize) { - throw new Error('No Sequelize instance passed'); - } - - this.sequelize = options.sequelize; - - const globalOptions = this.sequelize.options; - - options = Utils.merge(_.cloneDeep(globalOptions.define), options); - - if (!options.modelName) { - options.modelName = this.name; - } - - options = Utils.merge({ - name: { - plural: Utils.pluralize(options.modelName), - singular: Utils.singularize(options.modelName) - }, - indexes: [], - omitNull: globalOptions.omitNull, - schema: globalOptions.schema - }, options); - - this.sequelize.runHooks('beforeDefine', attributes, options); - - if (options.modelName !== this.name) { - Object.defineProperty(this, 'name', { value: options.modelName }); - } - delete options.modelName; - - this.options = { - timestamps: true, - validate: {}, - freezeTableName: false, - underscored: false, - paranoid: false, - rejectOnEmpty: false, - whereCollection: null, - schema: null, - schemaDelimiter: '', - defaultScope: {}, - scopes: {}, - indexes: [], - ...options - }; - - // if you call "define" multiple times for the same modelName, do not clutter the factory - if (this.sequelize.isDefined(this.name)) { - this.sequelize.modelManager.removeModel(this.sequelize.modelManager.getModel(this.name)); - } - - this.associations = {}; - this._setupHooks(options.hooks); - - this.underscored = this.options.underscored; - - if (!this.options.tableName) { - this.tableName = this.options.freezeTableName ? this.name : Utils.underscoredIf(Utils.pluralize(this.name), this.underscored); - } else { - this.tableName = this.options.tableName; - } - - this._schema = this.options.schema; - this._schemaDelimiter = this.options.schemaDelimiter; - - // error check options - _.each(options.validate, (validator, validatorType) => { - if (Object.prototype.hasOwnProperty.call(attributes, validatorType)) { - throw new Error(`A model validator function must not have the same name as a field. Model: ${this.name}, field/validation name: ${validatorType}`); - } - - if (typeof validator !== 'function') { - throw new Error(`Members of the validate option must be functions. Model: ${this.name}, error with validate member ${validatorType}`); - } - }); - - this.rawAttributes = _.mapValues(attributes, (attribute, name) => { - attribute = this.sequelize.normalizeAttribute(attribute); - - if (attribute.type === undefined) { - throw new Error(`Unrecognized datatype for attribute "${this.name}.${name}"`); - } - - if (attribute.allowNull !== false && _.get(attribute, 'validate.notNull')) { - throw new Error(`Invalid definition for "${this.name}.${name}", "notNull" validator is only allowed with "allowNull:false"`); - } - - if (_.get(attribute, 'references.model.prototype') instanceof Model) { - attribute.references.model = attribute.references.model.getTableName(); - } - - return attribute; - }); - - const tableName = this.getTableName(); - this._indexes = this.options.indexes - .map(index => Utils.nameIndex(this._conformIndex(index), tableName)); - - this.primaryKeys = {}; - this._readOnlyAttributes = new Set(); - this._timestampAttributes = {}; - - // 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 = - typeof this.options.createdAt === 'string' ? this.options.createdAt : 'createdAt'; - this._readOnlyAttributes.add(this._timestampAttributes.createdAt); - } - if (this.options.updatedAt !== false) { - 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 = - typeof this.options.deletedAt === 'string' ? this.options.deletedAt : 'deletedAt'; - this._readOnlyAttributes.add(this._timestampAttributes.deletedAt); - } - } - - // setup name for version attribute - if (this.options.version) { - this._versionAttribute = typeof this.options.version === 'string' ? this.options.version : 'version'; - this._readOnlyAttributes.add(this._versionAttribute); - } - - this._hasReadOnlyAttributes = this._readOnlyAttributes.size > 0; - - // Add head and tail default attributes (id, timestamps) - this._addDefaultAttributes(); - this.refreshAttributes(); - this._findAutoIncrementAttribute(); - - this._scope = this.options.defaultScope; - this._scopeNames = ['defaultScope']; - - this.sequelize.modelManager.addModel(this); - this.sequelize.runHooks('afterDefine', this); - - return this; - } - - static refreshAttributes() { - const attributeManipulation = {}; - - this.prototype._customGetters = {}; - this.prototype._customSetters = {}; - - ['get', 'set'].forEach(type => { - const opt = `${type}terMethods`; - const funcs = { ...this.options[opt] }; - const _custom = type === 'get' ? this.prototype._customGetters : this.prototype._customSetters; - - _.each(funcs, (method, attribute) => { - _custom[attribute] = method; - - if (type === 'get') { - funcs[attribute] = function() { - return this.get(attribute); - }; - } - if (type === 'set') { - funcs[attribute] = function(value) { - return this.set(attribute, value); - }; - } - }); - - _.each(this.rawAttributes, (options, attribute) => { - if (Object.prototype.hasOwnProperty.call(options, type)) { - _custom[attribute] = options[type]; - } - - if (type === 'get') { - funcs[attribute] = function() { - return this.get(attribute); - }; - } - if (type === 'set') { - funcs[attribute] = function(value) { - return this.set(attribute, value); - }; - } - }); - - _.each(funcs, (fct, name) => { - if (!attributeManipulation[name]) { - attributeManipulation[name] = { - configurable: true - }; - } - attributeManipulation[name][type] = fct; - }); - }); - - this._dataTypeChanges = {}; - this._dataTypeSanitizers = {}; - - this._hasBooleanAttributes = false; - this._hasDateAttributes = false; - this._jsonAttributes = new Set(); - this._virtualAttributes = new Set(); - this._defaultValues = {}; - this.prototype.validators = {}; - - this.fieldRawAttributesMap = {}; - - this.primaryKeys = {}; - this.uniqueKeys = {}; - - _.each(this.rawAttributes, (definition, name) => { - definition.type = this.sequelize.normalizeDataType(definition.type); - - definition.Model = this; - definition.fieldName = name; - definition._modelAttribute = true; - - if (definition.field === undefined) { - definition.field = Utils.underscoredIf(name, this.underscored); - } - - if (definition.primaryKey === true) { - this.primaryKeys[name] = definition; - } - - this.fieldRawAttributesMap[definition.field] = definition; - - if (definition.type._sanitize) { - this._dataTypeSanitizers[name] = definition.type._sanitize; - } - - if (definition.type._isChanged) { - this._dataTypeChanges[name] = definition.type._isChanged; - } - - if (definition.type instanceof DataTypes.BOOLEAN) { - this._hasBooleanAttributes = true; - } else if (definition.type instanceof DataTypes.DATE || definition.type instanceof DataTypes.DATEONLY) { - this._hasDateAttributes = true; - } else if (definition.type instanceof DataTypes.JSON) { - this._jsonAttributes.add(name); - } else if (definition.type instanceof DataTypes.VIRTUAL) { - this._virtualAttributes.add(name); - } - - if (Object.prototype.hasOwnProperty.call(definition, 'defaultValue')) { - this._defaultValues[name] = () => Utils.toDefaultValue(definition.defaultValue, this.sequelize.options.dialect); - } - - if (Object.prototype.hasOwnProperty.call(definition, 'unique') && definition.unique) { - let idxName; - if ( - typeof definition.unique === 'object' && - Object.prototype.hasOwnProperty.call(definition.unique, 'name') - ) { - idxName = definition.unique.name; - } else if (typeof definition.unique === 'string') { - idxName = definition.unique; - } else { - idxName = `${this.tableName}_${name}_unique`; - } - - const idx = this.uniqueKeys[idxName] || { fields: [] }; - - idx.fields.push(definition.field); - idx.msg = idx.msg || definition.unique.msg || null; - idx.name = idxName || false; - idx.column = name; - idx.customIndex = definition.unique !== true; - - this.uniqueKeys[idxName] = idx; - } - - if (Object.prototype.hasOwnProperty.call(definition, 'validate')) { - this.prototype.validators[name] = definition.validate; - } - - if (definition.index === true && definition.type instanceof DataTypes.JSONB) { - this._indexes.push( - Utils.nameIndex( - this._conformIndex({ - fields: [definition.field || name], - using: 'gin' - }), - this.getTableName() - ) - ); - - delete definition.index; - } - }); - - // Create a map of field to attribute names - this.fieldAttributeMap = _.reduce(this.fieldRawAttributesMap, (map, value, key) => { - if (key !== value.fieldName) { - map[key] = value.fieldName; - } - return map; - }, {}); - - this._hasJsonAttributes = !!this._jsonAttributes.size; - - this._hasVirtualAttributes = !!this._virtualAttributes.size; - - this._hasDefaultValues = !_.isEmpty(this._defaultValues); - - this.tableAttributes = _.omitBy(this.rawAttributes, (_a, key) => this._virtualAttributes.has(key)); - - this.prototype._hasCustomGetters = Object.keys(this.prototype._customGetters).length; - this.prototype._hasCustomSetters = Object.keys(this.prototype._customSetters).length; - - for (const key of Object.keys(attributeManipulation)) { - if (Object.prototype.hasOwnProperty.call(Model.prototype, key)) { - this.sequelize.log(`Not overriding built-in method from model attribute: ${key}`); - continue; - } - Object.defineProperty(this.prototype, key, attributeManipulation[key]); - } - - this.prototype.rawAttributes = this.rawAttributes; - this.prototype._isAttribute = key => Object.prototype.hasOwnProperty.call(this.prototype.rawAttributes, key); - - // Primary key convenience constiables - this.primaryKeyAttributes = Object.keys(this.primaryKeys); - this.primaryKeyAttribute = this.primaryKeyAttributes[0]; - if (this.primaryKeyAttribute) { - this.primaryKeyField = this.rawAttributes[this.primaryKeyAttribute].field || this.primaryKeyAttribute; - } - - this._hasPrimaryKeys = this.primaryKeyAttributes.length > 0; - this._isPrimaryKey = key => this.primaryKeyAttributes.includes(key); - } - - /** - * Remove attribute from model definition - * - * @param {string} attribute name of attribute to remove - */ - static removeAttribute(attribute) { - delete this.rawAttributes[attribute]; - this.refreshAttributes(); - } - - /** - * Sync this Model to the DB, that is create the table. - * - * @param {object} [options] sync options - * - * @see - * {@link Sequelize#sync} for options - * - * @returns {Promise} - */ - static async sync(options) { - options = { ...this.options, ...options }; - options.hooks = options.hooks === undefined ? true : !!options.hooks; - - const attributes = this.tableAttributes; - const rawAttributes = this.fieldRawAttributesMap; - - 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); - } - } - - 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; - } - } - } - 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; - }); - - 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 {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 async drop(options) { - return await this.queryInterface.dropTable(this.getTableName(options), options); - } - - static async dropSchema(schema) { - return await this.queryInterface.dropSchema(schema); - } - - /** - * Apply a schema to this model. For postgres, this will actually place the schema in front of the table name - `"schema"."tableName"`, - * while the schema will be prepended to the table name for mysql and sqlite - `'schema.tablename'`. - * - * This method is intended for use cases where the same model is needed in multiple schemas. In such a use case it is important - * to call `model.schema(schema, [options]).sync()` for each model to ensure the models are created in the correct schema. - * - * If a single default schema per model is needed, set the `options.schema='schema'` parameter during the `define()` call - * for the model. - * - * @param {string} schema The name of the schema - * @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). - * - * @see - * {@link Sequelize#define} for more information about setting a default schema. - * - * @returns {Model} - */ - static schema(schema, options) { - - const clone = class extends this {}; - Object.defineProperty(clone, 'name', { value: this.name }); - - clone._schema = schema; - - if (options) { - if (typeof options === 'string') { - clone._schemaDelimiter = options; - } else if (options.schemaDelimiter) { - clone._schemaDelimiter = options.schemaDelimiter; - } - } - - return clone; - } - - /** - * 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} - */ - static getTableName() { - return this.queryGenerator.addSchema(this); - } - - /** - * Get un-scoped model - * - * @returns {Model} - */ - static unscoped() { - return this.scope(); - } - - /** - * Add a new scope to the model. This is especially useful for adding scopes with includes, when the model you want to include is not available at the time this model is defined. - * - * 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 {boolean} [options.override=false] override old scope if already defined - */ - static addScope(name, scope, 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`); - } - - if (name === 'defaultScope') { - this.options.defaultScope = this._scope = scope; - } else { - this.options.scopes[name] = scope; - } - } - - /** - * Apply a scope created in `define` to the model. - * - * @example - * const Model = sequelize.define('model', attributes, { - * defaultScope: { - * where: { - * username: 'dan' - * }, - * limit: 12 - * }, - * scopes: { - * isALie: { - * where: { - * stuff: 'cake' - * } - * }, - * complexFunction: function(email, accessLevel) { - * return { - * where: { - * email: { - * [Op.like]: email - * }, - * access_level { - * [Op.gte]: accessLevel - * } - * } - * } - * } - * } - * }) - * - * # As you have defined a default scope, every time you do Model.find, the default scope is appended to your query. Here's a couple of examples: - * - * Model.findAll() // WHERE username = 'dan' - * Model.findAll({ where: { age: { [Op.gt]: 12 } } }) // WHERE age > 12 AND username = 'dan' - * - * @example - * 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. - * - * @returns {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope. - */ - static scope(option) { - const self = class extends this {}; - let scope; - let scopeName; - - Object.defineProperty(self, 'name', { value: this.name }); - - self._scope = {}; - self._scopeNames = []; - self.scoped = true; - - if (!option) { - return self; - } - - const options = _.flatten(arguments); - - for (const option of options) { - scope = null; - scopeName = null; - - if (_.isPlainObject(option)) { - if (option.method) { - if (Array.isArray(option.method) && !!self.options.scopes[option.method[0]]) { - scopeName = option.method[0]; - scope = self.options.scopes[scopeName].apply(self, option.method.slice(1)); - } - else if (self.options.scopes[option.method]) { - scopeName = option.method; - scope = self.options.scopes[scopeName].apply(self); - } - } else { - scope = option; - } - } else if (option === 'defaultScope' && _.isPlainObject(self.options.defaultScope)) { - scope = self.options.defaultScope; - } else { - scopeName = option; - scope = self.options.scopes[scopeName]; - if (typeof scope === 'function') { - scope = scope(); - } - } - - if (scope) { - this._conformIncludes(scope, this); - // 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.`); - } - } - - return self; - } - - /** - * Search for multiple instances. - * - * @example - * Model.findAll({ - * where: { - * attr1: 42, - * attr2: 'cake' - * } - * }) - * - * # WHERE attr1 = 42 AND attr2 = 'cake' - * - * @example - * const {gt, lte, ne, in: opIn} = Sequelize.Op; - * - * Model.findAll({ - * where: { - * attr1: { - * [gt]: 50 - * }, - * attr2: { - * [lte]: 45 - * }, - * attr3: { - * [opIn]: [1,2,3] - * }, - * attr4: { - * [ne]: 5 - * } - * } - * }) - * - * # WHERE attr1 > 50 AND attr2 <= 45 AND attr3 IN (1,2,3) AND attr4 != 5 - * - * @example - * const {or, and, gt, lt} = Sequelize.Op; - * - * Model.findAll({ - * where: { - * name: 'a project', - * [or]: [ - * {id: [1, 2, 3]}, - * { - * [and]: [ - * {id: {[gt]: 10}}, - * {id: {[lt]: 100}} - * ] - * } - * ] - * } - * }); - * - * # WHERE `Model`.`name` = 'a project' AND (`Model`.`id` IN (1, 2, 3) OR (`Model`.`id` > 10 AND `Model`.`id` < 100)); - * - * @see - * {@link Operators} for possible operators - * __Alias__: _all_ - * - * 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 {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 {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 {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 {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 {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 {boolean} [options.include[].duplicating] Mark the include as duplicating, will prevent a subquery from being used. - * @param {Array|Sequelize.fn|Sequelize.col|Sequelize.literal} [options.order] Specifies an ordering. Using an array, you can provide several columns / functions to order by. Each element can be further wrapped in a two-element array. The first element is the column / function to order by, the second is the direction. For example: `order: [['name', 'DESC']]`. In this way the column will be escaped, but the direction will not. - * @param {number} [options.limit] Limit for result - * @param {number} [options.offset] Offset for result - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string|object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. - * @param {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 {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 - * - * @see - * {@link Sequelize#query} - * - * @returns {Promise>} - */ - 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'); - } - - if (options !== undefined && options.attributes) { - if (!Array.isArray(options.attributes) && !_.isPlainObject(options.attributes)) { - throw new sequelizeErrors.QueryError('The attributes option must be an array of column names or an object'); - } - } - - this.warnOnInvalidOptions(options, Object.keys(this.rawAttributes)); - - const tableNames = {}; - - tableNames[this.getTableName(options)] = true; - options = Utils.cloneDeep(options); - - _.defaults(options, { hooks: true }); - - // set rejectOnEmpty option, defaults to model options - options.rejectOnEmpty = Object.prototype.hasOwnProperty.call(options, 'rejectOnEmpty') - ? options.rejectOnEmpty - : this.options.rejectOnEmpty; - - this._injectScope(options); - - if (options.hooks) { - await this.runHooks('beforeFind', options); - } - this._conformIncludes(options, this); - this._expandAttributes(options); - this._expandIncludeAll(options); - - if (options.hooks) { - await this.runHooks('beforeFindAfterExpandIncludeAll', options); - } - options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); - - if (options.include) { - options.hasJoin = true; - - 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 (!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; - - Utils.mapFinderOptions(options, this); - - options = this._paranoidClause(this, options); - - 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(); - } - - return await Model._findSeparate(results, options); - } - - static warnOnInvalidOptions(options, validColumnNames) { - if (!_.isPlainObject(options)) { - return; - } - - const unrecognizedOptions = Object.keys(options).filter(k => !validQueryKeywords.has(k)); - const unexpectedModelAttributes = _.intersection(unrecognizedOptions, validColumnNames); - if (!options.where && unexpectedModelAttributes.length > 0) { - logger.warn(`Model attributes (${unexpectedModelAttributes.join(', ')}) passed into finder method options of model ${this.name}, but the options.where object is empty. Did you forget to use options.where?`); - } - } - - static _injectDependentVirtualAttributes(attributes) { - if (!this._hasVirtualAttributes) return attributes; - if (!attributes || !Array.isArray(attributes)) return attributes; - - for (const attribute of attributes) { - if ( - this._virtualAttributes.has(attribute) - && this.rawAttributes[attribute].type.fields - ) { - attributes = attributes.concat(this.rawAttributes[attribute].type.fields); - } - } - - attributes = _.uniq(attributes); - - return attributes; - } - - 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; - - await Promise.all(options.include.map(async include => { - if (!include.separate) { - return await Model._findSeparate( - results.reduce((memo, result) => { - let associations = result.get(include.association.as); - - // Might be an empty belongsTo relation - if (!associations) return memo; - - // Force array so we can concat no matter if it's 1:1 or :M - if (!Array.isArray(associations)) associations = [associations]; - - for (let i = 0, len = associations.length; i !== len; ++i) { - memo.push(associations[i]); - } - return memo; - }, []), - { - - ..._.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'), - include: include.include || [] - } - ); - } - - const map = await include.association.get(results, { - - ..._.omit(options, nonCascadingOptions), - ..._.omit(include, ['parent', 'association', 'as', 'originalAttributes']) - }); - - 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 {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 a full explanation of options, Note that options.where is not supported. - * - * @returns {Promise} - */ - static async findByPk(param, options) { - // return Promise resolved with null if no arguments are passed - if ([null, undefined].includes(param)) { - return null; - } - - options = Utils.cloneDeep(options) || {}; - - if (typeof param === 'number' || typeof param === 'string' || Buffer.isBuffer(param)) { - options.where = { - [this.primaryKeyAttribute]: param - }; - } else { - throw new Error(`Argument passed to findByPk is invalid: ${param}`); - } - - // Bypass a possible overloaded findOne - return await this.findOne(options); - } - - /** - * Search for a single instance. Returns the first instance found, or null if none can be found. - * - * @param {object} [options] A hash of options to describe the scope of the search - * @param {Transaction} [options.transaction] Transaction to run query under - * @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} - */ - 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'); - } - options = Utils.cloneDeep(options); - - if (options.limit === undefined) { - const uniqueSingleColumns = _.chain(this.uniqueKeys).values().filter(c => c.fields.length === 1).map('column').value(); - - // Don't add limit if querying directly on the pk or a unique column - if (!options.where || !_.some(options.where, (value, key) => - (key === this.primaryKeyAttribute || uniqueSingleColumns.includes(key)) && - (Utils.isPrimitive(value) || Buffer.isBuffer(value)) - )) { - options.limit = 1; - } - } - - // Bypass a possible overloaded findAll. - return await this.findAll(_.defaults(options, { - plain: true - })); - } - - /** - * Run an aggregation method on the specified field - * - * @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 {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. - * @param {boolean} [options.distinct] Applies DISTINCT to the field being aggregated over - * @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. - */ - 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. - const prevAttributes = options.attributes; - this._injectScope(options); - options.attributes = prevAttributes; - this._conformIncludes(options, this); - - if (options.include) { - this._expandIncludeAll(options); - this._validateIncludedElements(options); - } - - const attrOptions = this.rawAttributes[attribute]; - const field = attrOptions && attrOptions.field || attribute; - let aggregateColumn = this.sequelize.col(field); - - if (options.distinct) { - aggregateColumn = this.sequelize.fn('DISTINCT', aggregateColumn); - } - - let { group } = options; - if (Array.isArray(group) && Array.isArray(group[0])) { - noDoubleNestedGroup(); - group = _.flatten(group); - } - options.attributes = _.unionBy( - options.attributes, - group, - [[this.sequelize.fn(aggregateFunction, aggregateColumn), aggregateFunction]], - a => Array.isArray(a) ? a[1] : a - ); - - if (!options.dataType) { - if (attrOptions) { - options.dataType = attrOptions.type; - } else { - // Use FLOAT as fallback - options.dataType = new DataTypes.FLOAT(); - } - } else { - options.dataType = this.sequelize.normalizeDataType(options.dataType); - } - - Utils.mapOptionFieldNames(options, this); - options = this._paranoidClause(this, options); - - const value = await this.queryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); - if (value === null) { - return 0; - } - return value; - } - - /** - * 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. - * - * @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 - * @param {Array} [options.attributes] Used in conjunction with `group` - * @param {Array} [options.group] For creating complex counts. Will return multiple rows as needed. - * @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} - */ - 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; - - return await this.aggregate(col, 'count', options); - } - - /** - * 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 - * const result = await Model.findAndCountAll({ - * where: ..., - * limit: 12, - * offset: 12 - * }); - * - * # In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return the total number of rows that matched your query. - * - * # When you add includes, only those which are required (either because they have a where clause, or because `required` is explicitly set to true on the include) will be added to the count part. - * - * # Suppose you want to find all users who have a profile attached: - * - * 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 - * - * @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[]}>} - */ - 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'); - } - - const countOptions = Utils.cloneDeep(options); - - if (countOptions.attributes) { - countOptions.attributes = undefined; - } - - const [count, rows] = await Promise.all([ - this.count(countOptions), - this.findAll(options) - ]); - - return { - count, - rows: count === 0 ? [] : rows - }; - } - - /** - * Find the maximum value of field - * - * @param {string} field attribute / field name - * @param {object} [options] See aggregate - * - * @see - * {@link Model.aggregate} for options - * - * @returns {Promise<*>} - */ - 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 - * - * @see - * {@link Model.aggregate} for options - * - * @returns {Promise<*>} - */ - 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 - * - * @see - * {@link Model.aggregate} for options - * - * @returns {Promise} - */ - 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 {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. - * @param {boolean} [options.isNewRecord=true] Is this new record - * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` - * - * @returns {Model|Array} - */ - static build(values, options) { - if (Array.isArray(values)) { - return this.bulkBuild(values, options); - } - - return new this(values, options); - } - - static bulkBuild(valueSets, options) { - options = { isNewRecord: true, ...options }; - - if (!options.includeValidated) { - this._conformIncludes(options, this); - if (options.include) { - this._expandIncludeAll(options); - this._validateIncludedElements(options); - } - } - - if (options.attributes) { - options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); - } - - return valueSets.map(values => this.build(values, options)); - } - - /** - * Builds a new model instance and calls save on it. - * - * @see - * {@link Model.build} - * @see - * {@link Model.save} - * - * @param {object} values Hash of data values to create new record with - * @param {object} [options] Build and query options - * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. - * @param {boolean} [options.isNewRecord=true] Is this new record - * @param {Array} [options.include] An array of include options - Used to build prefetched/included model instances. See `set` - * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {boolean} [options.validate=true] If false, validations won't be run. - * @param {boolean} [options.hooks=true] Run before and after create / update + validate hooks - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * @param {boolean|Array} [options.returning=true] Appends RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) - * - * @returns {Promise} - * - */ - static async create(values, options) { - options = Utils.cloneDeep(options || {}); - - return await this.build(values, { - isNewRecord: true, - attributes: options.fields, - include: options.include, - raw: options.raw, - silent: options.silent - }).save(options); - } - - /** - * 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 - * - * @returns {Promise} - */ - static async findOrBuild(options) { - if (!options || !options.where || arguments.length > 1) { - throw new Error( - 'Missing where attribute in the options parameter passed to findOrBuild. ' + - 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)' - ); - } - - let values; - - 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); - - return [instance, true]; - } - - return [instance, false]; - } - - /** - * Find a row that matches the query, or build and save the row if none is found - * The successful result of the promise will be (instance, created) - * - * If no transaction is passed in the `options` object, a new transaction will be created internally, to prevent the race condition where a matching row is created by another connection after the find but before the insert call. - * However, it is not always possible to handle this case in SQLite, specifically if one transaction inserts and another tries to select before the first one has committed. In this case, an instance of sequelize. TimeoutError will be thrown instead. - * If a transaction is created, a savepoint will be created instead, and any unique constraint violation will be handled internally. - * - * @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 {Transaction} [options.transaction] Transaction to run query under - * - * @returns {Promise} - */ - static async findOrCreate(options) { - if (!options || !options.where || arguments.length > 1) { - throw new Error( - 'Missing where attribute in the options parameter passed to findOrCreate. ' + - 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)' - ); - } - - options = { ...options }; - - if (options.defaults) { - const defaults = Object.keys(options.defaults); - const unknownDefaults = defaults.filter(name => !this.rawAttributes[name]); - - if (unknownDefaults.length) { - logger.warn(`Unknown attributes (${unknownDefaults}) passed to defaults option of findOrCreate`); - } - } - - if (options.transaction === undefined && this.sequelize.constructor._cls) { - const t = this.sequelize.constructor._cls.get('transaction'); - if (t) { - options.transaction = t; - } - } - - const internalTransaction = !options.transaction; - let values; - let transaction; - - try { - const t = await this.sequelize.transaction(options); - transaction = t; - options.transaction = t; - - const found = await this.findOne(Utils.defaults({ transaction }, options)); - if (found !== null) { - return [found, false]; - } - - values = { ...options.defaults }; - if (_.isPlainObject(options.where)) { - values = Utils.defaults(values, options.where); - } - - options.exception = true; - options.returning = true; - - 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 [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)); - const defaultFields = options.defaults && Object.keys(options.defaults) - .filter(name => this.rawAttributes[name]) - .map(name => this.rawAttributes[name].field || name); - - const errFieldKeys = Object.keys(err.fields); - const errFieldsWhereIntersects = Utils.intersects(errFieldKeys, whereFields); - if (defaultFields && !errFieldsWhereIntersects && Utils.intersects(errFieldKeys, defaultFields)) { - throw err; - } - - if (errFieldsWhereIntersects) { - _.each(err.fields, (value, key) => { - const name = this.fieldRawAttributesMap[key].fieldName; - if (value.toString() !== options.where[name].toString()) { - throw new Error(`${this.name}#findOrCreate: value used for ${name} was not equal for both the find and the create calls, '${options.where[name]}' vs '${value}'`); - } - }); - } - - // Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it! - const otherCreated = await this.findOne(Utils.defaults({ - transaction: internalTransaction ? null : transaction - }, 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) { - await transaction.commit(); - } - } - } - - /** - * A more performant findOrCreate that will not work under a transaction (at least not 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 - * - * @returns {Promise} - */ - static async findCreateFind(options) { - if (!options || !options.where) { - throw new Error( - 'Missing where attribute in the options parameter passed to findCreateFind.' - ); - } - - let values = { ...options.defaults }; - if (_.isPlainObject(options.where)) { - values = Utils.defaults(values, options.where); - } - - - const found = await this.findOne(options); - if (found) return [found, false]; - - try { - const created = await this.create(values, options); - return [created, true]; - } catch (err) { - if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; - const foundAgain = await this.findOne(options); - return [foundAgain, false]; - } - } - - /** - * Insert or update a single row. An update will be executed if a row which matches the supplied values on either the primary key or a unique key is found. Note that the unique index must be defined in your sequelize model and not just in the table. Otherwise you may experience a unique constraint violation, because sequelize fails to identify the row that should be updated. - * - * **Implementation details:** - * - * * 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 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=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<[Model, boolean | null]>} returns an array with two elements, the first being the new record and the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which can't detect this and will always return `null` instead of a boolean). - */ - static async upsert(values, options) { - options = { - hooks: true, - 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; - } - - 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); - - // 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; - } - - // 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]; - } - - 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; - } - - /** - * Create and insert multiple instances in bulk. - * - * The success handler is passed an array of instances, but please notice that these may not completely represent the state of the rows in the DB. This is because MySQL - * 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 - * - * @param {Array} records List of objects (key/value pairs) to create instances from - * @param {object} [options] Bulk create options - * @param {Array} [options.fields] Fields to insert (defaults to all fields) - * @param {boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation - * @param {boolean} [options.hooks=true] Run before / after bulk create hooks? - * @param {boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true. - * @param {boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by MSSQL or Postgres < 9.5) - * @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {boolean|Array} [options.returning=false] If true, append RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * - * @returns {Promise>} - */ - static async bulkCreate(records, options = {}) { - if (!records.length) { - return []; - } - - const dialect = this.sequelize.options.dialect; - const now = Utils.now(this.sequelize.options.dialect); - - options.model = this; - - if (!options.includeValidated) { - this._conformIncludes(options, this); - if (options.include) { - this._expandIncludeAll(options); - this._validateIncludedElements(options); - } - } - - const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include })); - - const recursiveBulkCreate = async (instances, options) => { - options = { - validate: false, - hooks: true, - individualHooks: false, - ignoreDuplicates: false, - ...options - }; - - if (options.returning === undefined) { - if (options.association) { - options.returning = false; - } else { - options.returning = true; - } - } - - if (options.ignoreDuplicates && ['mssql'].includes(dialect)) { - throw new Error(`${dialect} does not support the ignoreDuplicates option.`); - } - if (options.updateOnDuplicate && (dialect !== 'mysql' && dialect !== 'mariadb' && dialect !== 'sqlite' && dialect !== 'postgres')) { - throw new Error(`${dialect} does not support the updateOnDuplicate option.`); - } - - const model = options.model; - - options.fields = options.fields || Object.keys(model.rawAttributes); - const createdAtAttr = model._timestampAttributes.createdAt; - const updatedAtAttr = model._timestampAttributes.updatedAt; - - if (options.updateOnDuplicate !== undefined) { - if (Array.isArray(options.updateOnDuplicate) && options.updateOnDuplicate.length) { - options.updateOnDuplicate = _.intersection( - _.without(Object.keys(model.tableAttributes), createdAtAttr), - options.updateOnDuplicate - ); - } else { - throw new Error('updateOnDuplicate option only supports non-empty array.'); - } - } - - // 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)); - } - })); - - 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; - - 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 = []; - - for (const instance of instances) { - const associationInstance = instance.get(include.as); - if (associationInstance) { - associationInstances.push(associationInstance); - associationInstanceIndexToInstanceMap.push(instance); - } - } - - if (!associationInstances.length) { - return; - } - - const includeOptions = _(Utils.cloneDeep(include)) - .omit(['association']) - .defaults({ - transaction: options.transaction, - logging: options.logging - }).value(); - - const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); - for (const idx in createdAssociationInstances) { - const associationInstance = createdAssociationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; - - 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; - - // 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); - } - } - - 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); - - const upsertKeys = []; - - for (const i of model._indexes) { - if (i.unique && !i.where) { // Don't infer partial indexes - upsertKeys.push(...i.fields); - } - } - - const firstUniqueKey = Object.values(model.uniqueKeys).find(c => c.fields.length > 0); - - if (firstUniqueKey && firstUniqueKey.fields) { - upsertKeys.push(...firstUniqueKey.fields); - } - - options.upsertKeys = upsertKeys.length > 0 - ? upsertKeys - : Object.values(model.primaryKeys).map(x => x.field); - } - - // Map returning attributes to fields - if (options.returning && Array.isArray(options.returning)) { - options.returning = options.returning.map(attr => _.get(model.rawAttributes[attr], 'field', attr)); - } - - const results = await model.queryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); - if (Array.isArray(results)) { - results.forEach((result, i) => { - const instance = instances[i]; - - for (const key in result) { - if (!instance || key === model.primaryKeyAttribute && - instance.get(model.primaryKeyAttribute) && - ['mysql', 'mariadb', 'sqlite'].includes(dialect)) { - // The query.js for these DBs is blind, it autoincrements the - // primarykey value, even if it was set manually. Also, it can - // return more results than instances, bug?. - continue; - } - if (Object.prototype.hasOwnProperty.call(result, key)) { - const record = result[key]; - - const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); - - instance.dataValues[attr && attr.fieldName || key] = record; - } - } - }); - } - } - - 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 = []; - - for (const instance of instances) { - let associated = instance.get(include.as); - if (!Array.isArray(associated)) associated = [associated]; - - for (const associationInstance of associated) { - if (associationInstance) { - if (!(include.association instanceof BelongsToMany)) { - associationInstance.set(include.association.foreignKey, instance.get(include.association.sourceKey || instance.constructor.primaryKeyAttribute, { raw: true }), { raw: true }); - Object.assign(associationInstance, include.association.scope); - } - associationInstances.push(associationInstance); - associationInstanceIndexToInstanceMap.push(instance); - } - } - } - - if (!associationInstances.length) { - return; - } - - const includeOptions = _(Utils.cloneDeep(include)) - .omit(['association']) - .defaults({ - transaction: options.transaction, - logging: options.logging - }).value(); - - const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); - if (include.association instanceof BelongsToMany) { - const valueSets = []; - - for (const idx in createdAssociationInstances) { - const associationInstance = createdAssociationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; - - const values = { - [include.association.foreignKey]: instance.get(instance.constructor.primaryKeyAttribute, { raw: true }), - [include.association.otherKey]: associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }), - // Include values defined in the association - ...include.association.through.scope - }; - if (associationInstance[include.association.through.model.name]) { - for (const attr of Object.keys(include.association.through.model.rawAttributes)) { - if (include.association.through.model.rawAttributes[attr]._autoGenerated || - 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); - - await recursiveBulkCreate(throughInstances, throughOptions); - } - })); - } - - // 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); - } - 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 {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 - * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * @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} - * - * @see - * {@link Model.destroy} for more information - */ - static async truncate(options) { - options = Utils.cloneDeep(options) || {}; - options.truncate = true; - 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 {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 - * @param {boolean} [options.force=false] Delete instead of setting deletedAt to current timestamp (only applicable if `paranoid` is enabled) - * @param {boolean} [options.truncate=false] 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 - * @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 {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). - * - * @returns {Promise} The number of destroyed rows - */ - static async destroy(options) { - options = Utils.cloneDeep(options); - - this._injectScope(options); - - if (!options || !(options.where || options.truncate)) { - throw new Error('Missing where or truncate attribute in the options parameter of model.destroy.'); - } - - if (!options.truncate && !_.isPlainObject(options.where) && !Array.isArray(options.where) && !(options.where instanceof Utils.SequelizeMethod)) { - throw new Error('Expected plain object, array or sequelize method in the options.where parameter of model.destroy.'); - } - - options = _.defaults(options, { - hooks: true, - individualHooks: false, - force: false, - cascade: false, - restartIdentity: false - }); - - options.type = QueryTypes.BULKDELETE; - - Utils.mapOptionFieldNames(options, this); - options.model = this; - - - // Run before hook - if (options.hooks) { - await this.runHooks('beforeBulkDestroy', options); - } - let instances; - // Get daos and run beforeDestroy hook on each record individually - if (options.individualHooks) { - instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }); - - 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 {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) - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Transaction} [options.transaction] Transaction to run query under - * - * @returns {Promise} - */ - static async restore(options) { - if (!this._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - - options = { - hooks: true, - individualHooks: false, - ...options - }; - - options.type = QueryTypes.RAW; - options.model = this; - - Utils.mapOptionFieldNames(options, this); - - // Run before hook - if (options.hooks) { - await this.runHooks('beforeBulkRestore', options); - } - - let instances; - // Get daos and run beforeRestore hook on each record individually - if (options.individualHooks) { - instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }); - - await Promise.all(instances.map(instance => this.runHooks('beforeRestore', instance, options))); - } - // Run undelete query - const attrValueHash = {}; - const deletedAtCol = this._timestampAttributes.deletedAt; - const deletedAtAttribute = this.rawAttributes[deletedAtCol]; - const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; - options.omitNull = false; - const result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - await Promise.all( - instances.map(instance => this.runHooks('afterRestore', instance, options)) - ); - } - // Run after hook - if (options.hooks) { - await this.runHooks('afterBulkRestore', options); - } - return result; - } - - /** - * Update multiple instances that match the where options. - * - * @param {object} values hash of values to update - * @param {object} options update options - * @param {object} options.where Options to describe the scope of the search. - * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be updated. If false, both deleted and non-deleted records will be updated. Only applies if `options.paranoid` is true for the model. - * @param {Array} [options.fields] Fields to update (defaults to all fields) - * @param {boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation - * @param {boolean} [options.hooks=true] Run before / after bulk update hooks? - * @param {boolean} [options.sideEffects=true] Whether or not to update the side effects of any virtual setters. - * @param {boolean} [options.individualHooks=false] Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. A select is needed, because the row data needs to be passed to the hooks - * @param {boolean|Array} [options.returning=false] If true, append RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) - * @param {number} [options.limit] How many rows to update (only for mysql and mariadb, implemented as TOP(n) for MSSQL; for sqlite it is supported only when rowid is present) - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * - * @returns {Promise>} The promise returns an array with one or two elements. The first element is always the number - * of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true). - * - */ - static async update(values, options) { - options = Utils.cloneDeep(options); - - this._injectScope(options); - this._optionsMustContainWhere(options); - - options = this._paranoidClause(this, _.defaults(options, { - validate: true, - hooks: true, - individualHooks: false, - returning: false, - force: false, - sideEffects: true - })); - - options.type = QueryTypes.BULKUPDATE; - - // Clone values so it doesn't get modified for caller scope and ignore undefined values - values = _.omitBy(values, value => value === undefined); - - // Remove values that are not in the options.fields - if (options.fields && options.fields instanceof Array) { - for (const key of Object.keys(values)) { - if (!options.fields.includes(key)) { - delete values[key]; - } - } - } else { - const updatedAtAttr = this._timestampAttributes.updatedAt; - options.fields = _.intersection(Object.keys(values), Object.keys(this.tableAttributes)); - if (updatedAtAttr && !options.fields.includes(updatedAtAttr)) { - options.fields.push(updatedAtAttr); - } - } - - if (this._timestampAttributes.updatedAt && !options.silent) { - values[this._timestampAttributes.updatedAt] = this._getDefaultTimestamp(this._timestampAttributes.updatedAt) || Utils.now(this.sequelize.options.dialect); - } - - options.model = this; - - let valuesUse; - // Validate - if (options.validate) { - const build = this.build(values); - build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); - - if (options.sideEffects) { - 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)); - const attributes = await build.validate(options); - options.skip = undefined; - if (attributes && attributes.dataValues) { - values = _.pick(attributes.dataValues, Object.keys(values)); - } - } - // Run before hook - if (options.hooks) { - options.attributes = values; - await this.runHooks('beforeBulkUpdate', options); - values = options.attributes; - delete options.attributes; - } - - valuesUse = values; - - // Get instances and run beforeUpdate hook on each record individually - let instances; - let updateDoneRowByRow = false; - if (options.individualHooks) { - instances = await this.findAll({ - where: options.where, - transaction: options.transaction, - logging: options.logging, - benchmark: options.benchmark, - paranoid: options.paranoid - }); - - if (instances.length) { - // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly - // i.e. whether they change values for each record in the same way - let changedValues; - let different = false; - - instances = await Promise.all(instances.map(async instance => { - // Record updates in instances dataValues - Object.assign(instance.dataValues, values); - // Set the changed fields on the instance - _.forIn(valuesUse, (newValue, attr) => { - if (newValue !== instance._previousDataValues[attr]) { - instance.setDataValue(attr, newValue); - } - }); - - // Run beforeUpdate hook - await this.runHooks('beforeUpdate', instance, options); - if (!different) { - const thisChangedValues = {}; - _.forIn(instance.dataValues, (newValue, attr) => { - if (newValue !== instance._previousDataValues[attr]) { - thisChangedValues[attr] = newValue; - } - }); - - if (!changedValues) { - changedValues = thisChangedValues; - } else { - different = !_.isEqual(changedValues, thisChangedValues); - } - } - - return instance; - })); - - 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; - - 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; - - 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 - * - * @returns {Promise} hash of attributes and their types - */ - static async describe(schema, options) { - return await this.queryInterface.describeTable(this.tableName, { schema: schema || this._schema || undefined, ...options }); - } - - static _getDefaultTimestamp(attr) { - if (!!this.rawAttributes[attr] && !!this.rawAttributes[attr].defaultValue) { - return Utils.toDefaultValue(this.rawAttributes[attr].defaultValue, this.sequelize.options.dialect); - } - return undefined; - } - - static _expandAttributes(options) { - if (!_.isPlainObject(options.attributes)) { - return; - } - let attributes = Object.keys(this.rawAttributes); - - if (options.attributes.exclude) { - attributes = attributes.filter(elem => !options.attributes.exclude.includes(elem)); - } - - if (options.attributes.include) { - attributes = attributes.concat(options.attributes.include); - } - - options.attributes = attributes; - } - - // Inject _scope into options. - static _injectScope(options) { - const scope = Utils.cloneDeep(this._scope); - this._defaultsOptions(options, scope); - } - - static [Symbol.for('nodejs.util.inspect.custom')]() { - return this.name; - } - - static hasAlias(alias) { - return Object.prototype.hasOwnProperty.call(this.associations, alias); - } - - /** - * Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a - * ``` SET column = column + X WHERE foo = 'bar' ``` query. To get the correct value after an increment into the Instance you should do a reload. - * - * @example - * Model.increment('number', { where: { foo: 'bar' }); - * - * @example - * Model.increment(['number', 'count'], { by: 2, where: { foo: 'bar' } }); - * - * @example - * // `by` is ignored, as each column has its own value - * Model.increment({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } }); - * - * @see - * {@link Model#reload} - * - * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {object} options increment options - * @param {object} options.where conditions hash - * @param {number} [options.by=1] The number to increment by - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * - * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect - */ - 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); - - options = Utils.defaults({}, options, { - by: 1, - where: {}, - increment: true - }); - const isSubtraction = !options.increment; - - Utils.mapOptionFieldNames(options, this); - - const where = { ...options.where }; - - // A plain object whose keys are the fields to be incremented and whose values are - // the amounts to be incremented by. - 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 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; - } - - const extraAttributesToBeUpdated = {}; - - const updatedAtAttr = this._timestampAttributes.updatedAt; - if (!options.silent && updatedAtAttr && !incrementAmountsByField[updatedAtAttr]) { - const attrName = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; - extraAttributesToBeUpdated[attrName] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect); - } - - const tableName = this.getTableName(options); - let affectedRows; - if (isSubtraction) { - affectedRows = await this.queryInterface.decrement( - this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options - ); - } else { - affectedRows = await this.queryInterface.increment( - this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options - ); - } - - if (options.returning) { - return [affectedRows, affectedRows.length]; - } - - return [affectedRows]; - } - - /** - * Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a - * ```sql SET column = column - X WHERE foo = 'bar'``` query. To get the correct value after a decrement into the Instance you should do a reload. - * - * @example - * Model.decrement('number', { where: { foo: 'bar' }); - * - * @example - * Model.decrement(['number', 'count'], { by: 2, where: { foo: 'bar' } }); - * - * @example - * // `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 - * - * @see - * {@link Model.increment} - * @see - * {@link Model#reload} - * @since 4.36.0 - * - * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect - */ - static async decrement(fields, options) { - return this.increment(fields, { - by: 1, - ...options, - increment: false - }); - } - - static _optionsMustContainWhere(options) { - assert(options && options.where, 'Missing where attribute in the options parameter'); - assert(_.isPlainObject(options.where) || Array.isArray(options.where) || options.where instanceof Utils.SequelizeMethod, - 'Expected plain object, array or sequelize method in the options.where parameter'); - } - - /** - * Get an object representing the query for this instance, use with `options.where` - * - * @param {boolean} [checkVersion=false] include version attribute in where hash - * - * @returns {object} - */ - where(checkVersion) { - const where = this.constructor.primaryKeyAttributes.reduce((result, attribute) => { - result[attribute] = this.get(attribute, { raw: true }); - return result; - }, {}); - - if (_.size(where) === 0) { - return this.constructor.options.whereCollection; - } - const versionAttr = this.constructor._versionAttribute; - if (checkVersion && versionAttr) { - where[versionAttr] = this.get(versionAttr, { raw: true }); - } - return Utils.mapWhereFieldNames(where, this.constructor); - } - - toString() { - return `[object SequelizeInstance:${this.constructor.name}]`; - } - - /** - * Get the value of the underlying data value - * - * @param {string} key key to look in instance data store - * - * @returns {any} - */ - getDataValue(key) { - return this.dataValues[key]; - } - - /** - * Update the underlying data value - * - * @param {string} key key to set in instance data store - * @param {any} value new value for given key - * - */ - setDataValue(key, value) { - const originalValue = this._previousDataValues[key]; - - if (!_.isEqual(value, originalValue)) { - this.changed(key, true); - } - - this.dataValues[key] = value; - } - - /** - * If no key is given, returns all values of the instance, also invoking virtual getters. - * - * 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 {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} - */ - get(key, options) { - if (options === undefined && typeof key === 'object') { - options = key; - key = undefined; - } - - options = options || {}; - - if (key) { - if (Object.prototype.hasOwnProperty.call(this._customGetters, key) && !options.raw) { - return this._customGetters[key].call(this, key, options); - } - - if (options.plain && this._options.include && this._options.includeNames.includes(key)) { - if (Array.isArray(this.dataValues[key])) { - return this.dataValues[key].map(instance => instance.get(options)); - } - if (this.dataValues[key] instanceof Model) { - return this.dataValues[key].get(options); - } - return this.dataValues[key]; - } - - return this.dataValues[key]; - } - - if ( - this._hasCustomGetters - || options.plain && this._options.include - || options.clone - ) { - const values = {}; - let _key; - - if (this._hasCustomGetters) { - for (_key in this._customGetters) { - if ( - this._options.attributes - && !this._options.attributes.includes(_key) - ) { - continue; - } - - if (Object.prototype.hasOwnProperty.call(this._customGetters, _key)) { - values[_key] = this.get(_key, options); - } - } - } - - for (_key in this.dataValues) { - if ( - !Object.prototype.hasOwnProperty.call(values, _key) - && Object.prototype.hasOwnProperty.call(this.dataValues, _key) - ) { - values[_key] = this.get(_key, options); - } - } - - return values; - } - - return this.dataValues; - } - - /** - * Set is used to update values on the instance (the sequelize representation of the instance that is, remember that nothing will be persisted before you actually call `save`). - * In its most basic form `set` will update a value stored in the underlying `dataValues` object. However, if a custom setter function is defined for the key, that function - * will be called instead. To bypass the setter, you can pass `raw: true` in the options object. - * - * If set is called with an object, it will loop over the object, and call set recursively for each key, value pair. If you set raw to true, the underlying dataValues will either be - * set directly to the object passed, or used to extend dataValues, if dataValues already contain values. - * - * When set is called, the previous value of the field is stored and sets a changed flag(see `changed`). - * - * Set can also be used to build instances for associations, if you have values for those. - * When using set with associations you need to make sure the property key matches the alias of the association - * while also making sure that the proper include options have been set (from .build() or .findOne()) - * - * If called with a dot.separated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as changed. - * - * @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 {any} value value to set - * @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 - * - * @returns {Model} - */ - set(key, value, options) { - let values; - let originalValue; - - if (typeof key === 'object' && key !== null) { - values = key; - options = value || {}; - - if (options.reset) { - this.dataValues = {}; - for (const key in values) { - this.changed(key, false); - } - } - - // 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) { - Object.assign(this.dataValues, values); - } else { - this.dataValues = values; - } - // If raw, .changed() shouldn't be true - this._previousDataValues = { ...this.dataValues }; - } else { - // Loop and call set - if (options.attributes) { - const setKeys = data => { - for (const k of data) { - if (values[k] === undefined) { - continue; - } - this.set(k, values[k], options); - } - }; - setKeys(options.attributes); - if (this.constructor._hasVirtualAttributes) { - setKeys(this.constructor._virtualAttributes); - } - if (this._options.includeNames) { - setKeys(this._options.includeNames); - } - } else { - for (const key in values) { - this.set(key, values[key], options); - } - } - - if (options.raw) { - // If raw, .changed() shouldn't be true - this._previousDataValues = { ...this.dataValues }; - } - } - return this; - } - if (!options) - options = {}; - if (!options.raw) { - originalValue = this.dataValues[key]; - } - - // If not raw, and there's a custom setter - if (!options.raw && this._customSetters[key]) { - this._customSetters[key].call(this, value, key); - // custom setter should have changed value, get that changed value - // TODO: v5 make setters return new value instead of changing internal store - const newValue = this.dataValues[key]; - if (!_.isEqual(newValue, originalValue)) { - this._previousDataValues[key] = originalValue; - this.changed(key, true); - } - } else { - // Check if we have included models, and if this key matches the include model names/aliases - if (this._options && this._options.include && this._options.includeNames.includes(key)) { - // Pass it on to the include handler - this._setInclude(key, value, options); - return this; - } - // Bunch of stuff we won't do when it's raw - if (!options.raw) { - // If attribute is not in model definition, return - if (!this._isAttribute(key)) { - if (key.includes('.') && this.constructor._jsonAttributes.has(key.split('.')[0])) { - const previousNestedValue = Dottie.get(this.dataValues, key); - if (!_.isEqual(previousNestedValue, value)) { - Dottie.set(this.dataValues, key, value); - this.changed(key.split('.')[0], true); - } - } - return this; - } - - // If attempting to set primary key and primary key is already defined, return - if (this.constructor._hasPrimaryKeys && originalValue && this.constructor._isPrimaryKey(key)) { - return this; - } - - // If attempting to set read only attributes, return - if (!this.isNewRecord && this.constructor._hasReadOnlyAttributes && this.constructor._readOnlyAttributes.has(key)) { - return this; - } - } - - // If there's a data type sanitizer - if ( - !(value instanceof Utils.SequelizeMethod) - && Object.prototype.hasOwnProperty.call(this.constructor._dataTypeSanitizers, key) - ) { - value = this.constructor._dataTypeSanitizers[key].call(this, value, options); - } - - // Set when the value has changed and not raw - if ( - !options.raw && - ( - // True when sequelize method - (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)) - ) - ) { - this._previousDataValues[key] = originalValue; - this.changed(key, true); - } - - // set data value - this.dataValues[key] = value; - } - return this; - } - - setAttributes(updates) { - return this.set(updates); - } - - /** - * If changed is called with a string it will return a boolean indicating whether the value of that key in `dataValues` is different from the value in `_previousDataValues`. - * - * If changed is called without an argument, it will return an array of keys that have changed. - * - * If changed is called without an argument and no keys have changed, it will return `false`. - * - * Please note that this function will return `false` when a property from a nested (for example JSON) property - * was edited manually, you must call `changed('key', true)` manually in these cases. - * Writing an entirely new object (eg. deep cloned) will be detected. - * - * @example - * ``` - * const mdl = await MyModel.findOne(); - * mdl.myJsonField.a = 1; - * console.log(mdl.changed()) => false - * mdl.save(); // this will not save anything - * mdl.changed('myJsonField', true); - * console.log(mdl.changed()) => ['myJsonField'] - * mdl.save(); // will save - * ``` - * - * @param {string} [key] key to check or change status of - * @param {any} [value] value to set - * - * @returns {boolean|Array} - */ - changed(key, value) { - if (key === undefined) { - if (this._changed.size > 0) { - return Array.from(this._changed); - } - return false; - } - if (value === true) { - this._changed.add(key); - return this; - } - if (value === false) { - this._changed.delete(key); - return this; - } - return this._changed.has(key); - } - - /** - * Returns the previous value for key from `_previousDataValues`. - * - * If called without a key, returns the previous values for all values which have changed - * - * @param {string} [key] key to get previous value of - * - * @returns {any|Array} - */ - previous(key) { - if (key) { - return this._previousDataValues[key]; - } - - return _.pickBy(this._previousDataValues, (value, key) => this.changed(key)); - } - - _setInclude(key, value, options) { - if (!Array.isArray(value)) value = [value]; - if (value[0] instanceof Model) { - value = value.map(instance => instance.dataValues); - } - - const include = this._options.includeMap[key]; - const association = include.association; - const accessor = key; - const primaryKeyAttribute = include.model.primaryKeyAttribute; - const childOptions = { - isNewRecord: this.isNewRecord, - include: include.include, - includeNames: include.includeNames, - includeMap: include.includeMap, - includeValidated: true, - raw: options.raw, - attributes: include.originalAttributes - }; - let isEmpty; - - if (include.originalAttributes === undefined || include.originalAttributes.length) { - if (association.isSingleAssociation) { - if (Array.isArray(value)) { - value = value[0]; - } - isEmpty = value && value[primaryKeyAttribute] === null || value === null; - this[accessor] = this.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions); - } else { - isEmpty = value[0] && value[0][primaryKeyAttribute] === null; - this[accessor] = this.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions); - } - } - } - - /** - * Validates this instance, and if the validation passes, persists it to the database. - * - * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). - * - * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. - * - * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. - * - * @param {object} [options] save options - * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {boolean} [options.validate=true] If false, validations won't be run. - * @param {boolean} [options.hooks=true] Run before and after create / update + validate hooks - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * @param {boolean} [options.returning] Append RETURNING * to get back auto generated values (Postgres only) - * - * @returns {Promise} - */ - async save(options) { - if (arguments.length > 1) { - throw new Error('The second argument was removed in favor of the options object.'); - } - - options = Utils.cloneDeep(options); - options = _.defaults(options, { - hooks: true, - validate: true - }); - - if (!options.fields) { - if (this.isNewRecord) { - options.fields = Object.keys(this.constructor.rawAttributes); - } else { - options.fields = _.intersection(this.changed(), Object.keys(this.constructor.rawAttributes)); - } - - options.defaultFields = options.fields; - } - - if (options.returning === undefined) { - if (options.association) { - options.returning = false; - } else if (this.isNewRecord) { - options.returning = true; - } - } - - const primaryKeyName = this.constructor.primaryKeyAttribute; - const primaryKeyAttribute = primaryKeyName && this.constructor.rawAttributes[primaryKeyName]; - const createdAtAttr = this.constructor._timestampAttributes.createdAt; - const versionAttr = this.constructor._versionAttribute; - const hook = this.isNewRecord ? 'Create' : 'Update'; - const wasNewRecord = this.isNewRecord; - const now = Utils.now(this.sequelize.options.dialect); - let updatedAtAttr = this.constructor._timestampAttributes.updatedAt; - - if (updatedAtAttr && options.fields.length > 0 && !options.fields.includes(updatedAtAttr)) { - options.fields.push(updatedAtAttr); - } - if (versionAttr && options.fields.length > 0 && !options.fields.includes(versionAttr)) { - options.fields.push(versionAttr); - } - - if (options.silent === true && !(this.isNewRecord && this.get(updatedAtAttr, { raw: true }))) { - // UpdateAtAttr might have been added as a result of Object.keys(Model.rawAttributes). In that case we have to remove it again - _.remove(options.fields, val => val === updatedAtAttr); - updatedAtAttr = false; - } - - if (this.isNewRecord === true) { - if (createdAtAttr && !options.fields.includes(createdAtAttr)) { - options.fields.push(createdAtAttr); - } - - if (primaryKeyAttribute && primaryKeyAttribute.defaultValue && !options.fields.includes(primaryKeyName)) { - options.fields.unshift(primaryKeyName); - } - } - - if (this.isNewRecord === false) { - if (primaryKeyName && this.get(primaryKeyName, { raw: true }) === undefined) { - throw new Error('You attempted to save an instance with no primary key, this is not allowed since it would result in a global update'); - } - } - - if (updatedAtAttr && !options.silent && options.fields.includes(updatedAtAttr)) { - this.dataValues[updatedAtAttr] = this.constructor._getDefaultTimestamp(updatedAtAttr) || now; - } - - if (this.isNewRecord && createdAtAttr && !this.dataValues[createdAtAttr]) { - this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now; - } - - // 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); - } - - 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); - } - } - - options.fields = _.uniq(options.fields.concat(hookChanged)); - } - - if (hookChanged) { - if (options.validate) { - // Validate again - - options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged); - await this.validate(options); - delete options.skip; - } - } - } - 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; - - const includeOptions = _(Utils.cloneDeep(include)) - .omit(['association']) - .defaults({ - transaction: options.transaction, - logging: options.logging, - parentRecord: this - }).value(); - - await instance.save(includeOptions); - - 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]; - } - - 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 { - 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]; - } - } - Object.assign(values, result.dataValues); - - Object.assign(result.dataValues, values); - if (wasNewRecord && this._options.include && this._options.include.length) { - await Promise.all( - this._options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)).map(async include => { - let instances = this.get(include.as); - - if (!instances) return; - if (!Array.isArray(instances)) instances = [instances]; - - 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 - 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; - } - 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); - await instance.save(includeOptions); - } - })); - }) - ); - } - // 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; - } - - /** - * Refresh the current instance in-place, i.e. update the object with current data from the DB and return the same object. - * This is different from doing a `find(Instance.id)`, because that would create and return a new instance. With this method, - * all references to the Instance are updated with the new data and no new objects are created. - * - * @see - * {@link Model.findAll} - * - * @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} - */ - async reload(options) { - options = Utils.defaults({ - where: this.where() - }, options, { - include: this._options.include || undefined - }); - - 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; - } - - /** - * Validate the attributes of this instance according to validation rules set in the model definition. - * - * 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 {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} - */ - async validate(options) { - return new InstanceValidator(this, options).validate(); - } - - /** - * This is the same as calling `set` and then calling `save` but it only saves the - * exact values passed to it, making it more atomic and safer. - * - * @see - * {@link Model#set} - * @see - * {@link Model#save} - * - * @param {object} values See `set` - * @param {object} options See `save` - * - * @returns {Promise} - */ - async update(values, options) { - // Clone values so it doesn't get modified for caller scope and ignore undefined values - values = _.omitBy(values, value => value === undefined); - - const changedBefore = this.changed() || []; - - options = options || {}; - if (Array.isArray(options)) options = { fields: options }; - - options = Utils.cloneDeep(options); - const setOptions = Utils.cloneDeep(options); - setOptions.attributes = options.fields; - this.set(values, setOptions); - - // Now we need to figure out which fields were actually affected by the setter. - const sideEffects = _.without(this.changed(), ...changedBefore); - const fields = _.union(Object.keys(values), sideEffects); - - if (!options.fields) { - options.fields = _.intersection(fields, this.changed()); - options.defaultFields = options.fields; - } - - 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 {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 - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * - * @returns {Promise} - */ - async destroy(options) { - options = { - hooks: true, - force: false, - ...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; - } - - /** - * Helper method to determine if a instance is "soft deleted". This is - * particularly useful if the implementer renamed the `deletedAt` attribute - * to something different. This method requires `paranoid` to be enabled. - * - * @returns {boolean} - */ - isSoftDeleted() { - if (!this.constructor._timestampAttributes.deletedAt) { - throw new Error('Model is not paranoid'); - } - - const deletedAtAttribute = this.constructor.rawAttributes[this.constructor._timestampAttributes.deletedAt]; - const defaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - const deletedAt = this.get(this.constructor._timestampAttributes.deletedAt) || null; - const isSet = deletedAt !== defaultValue; - - return isSet; - } - - /** - * Restore the row corresponding to this instance. Only available for paranoid models. - * - * @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} - */ - async restore(options) { - if (!this.constructor._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - - options = { - hooks: true, - force: false, - ...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; - } - - /** - * Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a - * ```sql - * SET column = column + X - * ``` - * query. The updated instance will be returned by default in Postgres. However, in other dialects, you will need to do a reload to get the new values. - * - * @example - * instance.increment('number') // increment number by 1 - * - * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2 - * - * // increment answer by 42, and tries by 1. - * // `by` is ignored, since each column has its own value - * instance.increment({ answer: 42, tries: 1}, { by: 2 }) - * - * @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 {number} [options.by=1] The number to increment by - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * @param {boolean} [options.returning=true] Append RETURNING * to get back auto generated values (Postgres only) - * - * @returns {Promise} - * @since 4.0.0 - */ - async increment(fields, options) { - const identifier = this.where(); - - options = Utils.cloneDeep(options); - options.where = { ...options.where, ...identifier }; - options.instance = this; - - await this.constructor.increment(fields, options); - - return this; - } - - /** - * Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a - * ```sql - * SET column = column - X - * ``` - * query. The updated instance will be returned by default in Postgres. However, in other dialects, you will need to do a reload to get the new values. - * - * @example - * instance.decrement('number') // decrement number by 1 - * - * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2 - * - * // decrement answer by 42, and tries by 1. - * // `by` is ignored, since each column has its own value - * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) - * - * @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 {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. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * @param {boolean} [options.returning=true] Append RETURNING * to get back auto generated values (Postgres only) - * - * @returns {Promise} - */ - async decrement(fields, options) { - return this.increment(fields, { - by: 1, - ...options, - increment: false - }); - } - - /** - * Check whether this and `other` Instance refer to the same row - * - * @param {Model} other Other instance to compare against - * - * @returns {boolean} - */ - equals(other) { - if (!other || !other.constructor) { - return false; - } - - if (!(other instanceof this.constructor)) { - return false; - } - - return this.constructor.primaryKeyAttributes.every(attribute => this.get(attribute, { raw: true }) === other.get(attribute, { raw: true })); - } - - /** - * Check if this is equal to one of `others` by calling equals - * - * @param {Array} others An array of instances to check against - * - * @returns {boolean} - */ - equalsOneOf(others) { - return others.some(other => this.equals(other)); - } - - setValidators(attribute, validators) { - this.validators[attribute] = validators; - } - - /** - * 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. - * - * @see - * {@link Model#get} - * - * @returns {object} - */ - toJSON() { - return _.cloneDeep( - this.get({ - plain: true - }) - ); - } - - /** - * Creates a 1:m 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] 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} [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 {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. - * - * @returns {HasMany} - * - * @example - * User.hasMany(Profile) // This will add userId to the profile table - */ - static hasMany(target, options) {} // eslint-disable-line - - /** - * 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 {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} [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 {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 {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` - * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. - * - * @returns {BelongsToMany} - * - * @example - * // Automagically generated join model - * User.belongsToMany(Project, { through: 'UserProjects' }) - * Project.belongsToMany(User, { through: 'UserProjects' }) - * - * // Join model with additional attributes - * const UserProjects = sequelize.define('UserProjects', { - * started: Sequelize.BOOLEAN - * }) - * User.belongsToMany(Project, { through: UserProjects }) - * Project.belongsToMany(User, { through: UserProjects }) - */ - static belongsToMany(target, options) {} // eslint-disable-line - - /** - * 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 {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} [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' - * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. - * @param {string} [options.uniqueKey] The custom name for unique constraint. - * - * @returns {HasOne} - * - * @example - * User.hasOne(Profile) // This will add userId to the profile table - */ - static hasOne(target, options) {} // eslint-disable-line - - /** - * 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 {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} [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' - * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. - * - * @returns {BelongsTo} - * - * @example - * Profile.belongsTo(User) // This will add userId to the profile table - */ - static belongsTo(target, options) {} // eslint-disable-line -} - -Object.assign(Model, associationsMixin); -Hooks.applyTo(Model, true); - -module.exports = Model; diff --git a/lib/operators.js b/lib/operators.js deleted file mode 100644 index 7e8bb7cf9cdb..000000000000 --- a/lib/operators.js +++ /dev/null @@ -1,91 +0,0 @@ - -'use strict'; -/** - * Operator symbols to be used when querying data - * - * @see {@link Model#where} - * - * @property eq - * @property ne - * @property gte - * @property gt - * @property lte - * @property lt - * @property not - * @property is - * @property in - * @property notIn - * @property like - * @property notLike - * @property iLike - * @property notILike - * @property startsWith - * @property endsWith - * @property substring - * @property regexp - * @property notRegexp - * @property iRegexp - * @property notIRegexp - * @property between - * @property notBetween - * @property overlap - * @property contains - * @property contained - * @property adjacent - * @property strictLeft - * @property strictRight - * @property noExtendRight - * @property noExtendLeft - * @property and - * @property or - * @property any - * @property all - * @property values - * @property col - * @property placeholder - * @property join - */ -const Op = { - eq: Symbol.for('eq'), - ne: Symbol.for('ne'), - gte: Symbol.for('gte'), - gt: Symbol.for('gt'), - lte: Symbol.for('lte'), - lt: Symbol.for('lt'), - not: Symbol.for('not'), - is: Symbol.for('is'), - in: Symbol.for('in'), - notIn: Symbol.for('notIn'), - like: Symbol.for('like'), - notLike: Symbol.for('notLike'), - iLike: Symbol.for('iLike'), - notILike: Symbol.for('notILike'), - startsWith: Symbol.for('startsWith'), - endsWith: Symbol.for('endsWith'), - substring: Symbol.for('substring'), - regexp: Symbol.for('regexp'), - notRegexp: Symbol.for('notRegexp'), - iRegexp: Symbol.for('iRegexp'), - notIRegexp: Symbol.for('notIRegexp'), - between: Symbol.for('between'), - notBetween: Symbol.for('notBetween'), - overlap: Symbol.for('overlap'), - contains: Symbol.for('contains'), - contained: Symbol.for('contained'), - adjacent: Symbol.for('adjacent'), - strictLeft: Symbol.for('strictLeft'), - strictRight: Symbol.for('strictRight'), - noExtendRight: Symbol.for('noExtendRight'), - noExtendLeft: Symbol.for('noExtendLeft'), - and: Symbol.for('and'), - or: Symbol.for('or'), - any: Symbol.for('any'), - all: Symbol.for('all'), - values: Symbol.for('values'), - col: Symbol.for('col'), - placeholder: Symbol.for('placeholder'), - join: Symbol.for('join'), - match: Symbol.for('match') -}; - -module.exports = Op; diff --git a/lib/query-types.js b/lib/query-types.js deleted file mode 100644 index f4bbb2722bab..000000000000 --- a/lib/query-types.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * An enum of query types used by `sequelize.query` - * - * @see {@link Sequelize#query} - * - * @property SELECT - * @property INSERT - * @property UPDATE - * @property BULKUPDATE - * @property BULKDELETE - * @property DELETE - * @property UPSERT - * @property VERSION - * @property SHOWTABLES - * @property SHOWINDEXES - * @property DESCRIBE - * @property RAW - * @property FOREIGNKEYS - * @property SHOWCONSTRAINTS - */ -const QueryTypes = module.exports = { // eslint-disable-line - SELECT: 'SELECT', - INSERT: 'INSERT', - UPDATE: 'UPDATE', - BULKUPDATE: 'BULKUPDATE', - BULKDELETE: 'BULKDELETE', - DELETE: 'DELETE', - UPSERT: 'UPSERT', - VERSION: 'VERSION', - SHOWTABLES: 'SHOWTABLES', - SHOWINDEXES: 'SHOWINDEXES', - DESCRIBE: 'DESCRIBE', - RAW: 'RAW', - FOREIGNKEYS: 'FOREIGNKEYS', - SHOWCONSTRAINTS: 'SHOWCONSTRAINTS' -}; diff --git a/lib/sequelize.js b/lib/sequelize.js deleted file mode 100644 index b074beef1710..000000000000 --- a/lib/sequelize.js +++ /dev/null @@ -1,1371 +0,0 @@ -'use strict'; - -const url = require('url'); -const path = require('path'); -const retry = require('retry-as-promised'); -const _ = require('lodash'); - -const Utils = require('./utils'); -const Model = require('./model'); -const DataTypes = require('./data-types'); -const Deferrable = require('./deferrable'); -const ModelManager = require('./model-manager'); -const Transaction = require('./transaction'); -const QueryTypes = require('./query-types'); -const TableHints = require('./table-hints'); -const IndexHints = require('./index-hints'); -const sequelizeErrors = require('./errors'); -const Hooks = require('./hooks'); -const Association = require('./associations/index'); -const Validator = require('./utils/validator-extras').validator; -const Op = require('./operators'); -const deprecations = require('./utils/deprecations'); - -/** - * This is the main class, the entry point to sequelize. - */ -class Sequelize { - /** - * Instantiate sequelize with name of database, username and password. - * - * @example - * // without password / with blank password - * const sequelize = new Sequelize('database', 'username', null, { - * dialect: 'mysql' - * }) - * - * // with password and options - * const sequelize = new Sequelize('my_database', 'john', 'doe', { - * dialect: 'postgres' - * }) - * - * // with database, username, and password in the options object - * const sequelize = new Sequelize({ database, username, password, dialect: 'mssql' }); - * - * // with uri - * const sequelize = new Sequelize('mysql://localhost:3306/database', {}) - * - * // option examples - * const sequelize = new Sequelize('database', 'username', 'password', { - * // the sql dialect of the database - * // currently supported: 'mysql', 'sqlite', 'postgres', 'mssql' - * dialect: 'mysql', - * - * // custom host; default: localhost - * host: 'my.server.tld', - * // for postgres, you can also specify an absolute path to a directory - * // containing a UNIX socket to connect over - * // host: '/sockets/psql_sockets'. - * - * // custom port; default: dialect default - * port: 12345, - * - * // custom protocol; default: 'tcp' - * // postgres only, useful for Heroku - * protocol: null, - * - * // disable logging or provide a custom logging function; default: console.log - * logging: false, - * - * // you can also pass any dialect options to the underlying dialect library - * // - default is empty - * // - currently supported: 'mysql', 'postgres', 'mssql' - * dialectOptions: { - * socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock', - * supportBigNumbers: true, - * bigNumberStrings: true - * }, - * - * // the storage engine for sqlite - * // - default ':memory:' - * storage: 'path/to/database.sqlite', - * - * // disable inserting undefined values as NULL - * // - default: false - * omitNull: true, - * - * // a flag for using a native library or not. - * // in the case of 'pg' -- set this to true will allow SSL support - * // - default: false - * native: true, - * - * // Specify options, which are used when sequelize.define is called. - * // The following example: - * // define: { timestamps: false } - * // is basically the same as: - * // Model.init(attributes, { timestamps: false }); - * // sequelize.define(name, attributes, { timestamps: false }); - * // so defining the timestamps for each model will be not necessary - * define: { - * underscored: false, - * freezeTableName: false, - * charset: 'utf8', - * dialectOptions: { - * collate: 'utf8_general_ci' - * }, - * timestamps: true - * }, - * - * // similar for sync: you can define this to always force sync for models - * sync: { force: true }, - * - * // pool configuration used to pool database connections - * pool: { - * max: 5, - * idle: 30000, - * acquire: 60000, - * }, - * - * // isolation level of each transaction - * // defaults to dialect default - * isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ - * }) - * - * @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 {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.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 {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 {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 {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 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 {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 {Array} [options.retry.match] Only retry a query if the error matches one of these strings. - * @param {number} [options.retry.max] How many times a failing query is automatically retried. Set to 0 to disable retrying on SQL_BUSY error. - * @param {boolean} [options.typeValidation=false] Run built-in type validators on insert and update, and select with where clause, e.g. validate that arguments passed to integer fields are integer-like. - * @param {object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. - * @param {object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. - * @param {boolean} [options.minifyAliases=false] A flag that defines if aliases should be minified (mostly useful to avoid Postgres alias character limit of 64) - * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind parameters in log. - */ - constructor(database, username, password, options) { - let config; - - if (arguments.length === 1 && typeof database === 'object') { - // new Sequelize({ ... options }) - options = database; - config = _.pick(options, 'host', 'port', 'database', 'username', 'password'); - } else if (arguments.length === 1 && typeof database === 'string' || arguments.length === 2 && typeof username === 'object') { - // new Sequelize(URI, { ... options }) - - config = {}; - options = username || {}; - - const urlParts = url.parse(arguments[0], true); - - options.dialect = urlParts.protocol.replace(/:$/, ''); - options.host = urlParts.hostname; - - if (options.dialect === 'sqlite' && urlParts.pathname && !urlParts.pathname.startsWith('/:memory')) { - const storagePath = path.join(options.host, urlParts.pathname); - options.storage = path.resolve(options.storage || storagePath); - } - - if (urlParts.pathname) { - config.database = urlParts.pathname.replace(/^\//, ''); - } - - if (urlParts.port) { - options.port = urlParts.port; - } - - if (urlParts.auth) { - const authParts = urlParts.auth.split(':'); - - config.username = authParts[0]; - - if (authParts.length > 1) - config.password = authParts.slice(1).join(':'); - } - - if (urlParts.query) { - // Allow host query argument to override the url host. - // Enables specifying domain socket hosts which cannot be specified via the typical - // host part of a url. - if (urlParts.query.host) { - options.host = urlParts.query.host; - } - - if (options.dialectOptions) { - Object.assign(options.dialectOptions, urlParts.query); - } else { - options.dialectOptions = urlParts.query; - if (urlParts.query.options) { - try { - const o = JSON.parse(urlParts.query.options); - options.dialectOptions.options = o; - } catch (e) { - // Nothing to do, string is not a valid JSON - // an thus does not need any further processing - } - } - } - } - } else { - // new Sequelize(database, username, password, { ... options }) - options = options || {}; - config = { database, username, password }; - } - - Sequelize.runHooks('beforeInit', config, options); - - this.options = { - dialect: null, - dialectModule: null, - dialectModulePath: null, - host: 'localhost', - protocol: 'tcp', - define: {}, - query: {}, - sync: {}, - timezone: '+00:00', - clientMinMessages: 'warning', - standardConformingStrings: true, - // eslint-disable-next-line no-console - logging: console.log, - omitNull: false, - native: false, - replication: false, - ssl: undefined, - pool: {}, - quoteIdentifiers: true, - hooks: {}, - retry: { - max: 5, - match: [ - 'SQLITE_BUSY: database is locked' - ] - }, - transactionType: Transaction.TYPES.DEFERRED, - isolationLevel: null, - databaseVersion: 0, - typeValidation: false, - benchmark: false, - minifyAliases: false, - logQueryParameters: false, - ...options - }; - - if (!this.options.dialect) { - throw new Error('Dialect needs to be explicitly supplied as of v4.0.0'); - } - - if (this.options.dialect === 'postgresql') { - this.options.dialect = 'postgres'; - } - - if (this.options.dialect === 'sqlite' && this.options.timezone !== '+00:00') { - throw new Error('Setting a custom timezone is not supported by SQLite, dates are always returned as UTC. Please remove the custom timezone parameter.'); - } - - if (this.options.logging === true) { - deprecations.noTrueLogging(); - // eslint-disable-next-line no-console - this.options.logging = console.log; - } - - this._setupHooks(options.hooks); - - this.config = { - database: config.database || this.options.database, - username: config.username || this.options.username, - password: config.password || this.options.password || null, - host: config.host || this.options.host, - port: config.port || this.options.port, - pool: this.options.pool, - protocol: this.options.protocol, - native: this.options.native, - ssl: this.options.ssl, - replication: this.options.replication, - dialectModule: this.options.dialectModule, - dialectModulePath: this.options.dialectModulePath, - keepDefaultTimezone: this.options.keepDefaultTimezone, - dialectOptions: this.options.dialectOptions - }; - - let Dialect; - // Requiring the dialect in a switch-case to keep the - // require calls static. (Browserify fix) - switch (this.getDialect()) { - case 'mariadb': - Dialect = require('./dialects/mariadb'); - break; - case 'mssql': - Dialect = require('./dialects/mssql'); - break; - case 'mysql': - Dialect = require('./dialects/mysql'); - break; - case 'postgres': - Dialect = require('./dialects/postgres'); - break; - case 'sqlite': - Dialect = require('./dialects/sqlite'); - break; - default: - throw new Error(`The dialect ${this.getDialect()} is not supported. Supported dialects: mssql, mariadb, mysql, postgres, and sqlite.`); - } - - this.dialect = new Dialect(this); - this.dialect.queryGenerator.typeValidation = options.typeValidation; - - if (_.isPlainObject(this.options.operatorsAliases)) { - deprecations.noStringOperators(); - this.dialect.queryGenerator.setOperatorsAliases(this.options.operatorsAliases); - } else if (typeof this.options.operatorsAliases === 'boolean') { - deprecations.noBoolOperatorAliases(); - } - - this.queryInterface = this.dialect.queryInterface; - - /** - * Models are stored here under the name given to `sequelize.define` - */ - this.models = {}; - this.modelManager = new ModelManager(this); - this.connectionManager = this.dialect.connectionManager; - - Sequelize.runHooks('afterInit', this); - } - - /** - * Refresh data types and parsers. - * - * @private - */ - refreshTypes() { - this.connectionManager.refreshTypeParser(DataTypes); - } - - /** - * Returns the specified dialect. - * - * @returns {string} The specified dialect. - */ - getDialect() { - return this.options.dialect; - } - - /** - * Returns the database name. - * - * @returns {string} The database name. - */ - getDatabaseName() { - return this.config.database; - } - - /** - * Returns an instance of QueryInterface. - * - * @returns {QueryInterface} An instance (singleton) of QueryInterface. - */ - getQueryInterface() { - return this.queryInterface; - } - - /** - * Define a new model, representing a table in the database. - * - * 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() - * - * @see - * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. - * @see - * Model Basics guide - * - * @returns {Model} Newly defined model - * - * @example - * sequelize.define('modelName', { - * columnA: { - * type: Sequelize.BOOLEAN, - * validate: { - * is: ["[a-z]",'i'], // will only allow letters - * max: 23, // only allow values <= 23 - * isIn: { - * args: [['en', 'zh']], - * msg: "Must be English or Chinese" - * } - * }, - * field: 'column_a' - * }, - * columnB: Sequelize.STRING, - * columnC: 'MY VERY OWN COLUMN TYPE' - * }); - * - * sequelize.models.modelName // The model will now be available in models under the name given to define - */ - define(modelName, attributes, options = {}) { - options.modelName = modelName; - options.sequelize = this; - - const model = class extends Model {}; - - model.init(attributes, options); - - return model; - } - - /** - * Fetch a Model which is already defined - * - * @param {string} modelName The name of a model defined with Sequelize.define - * - * @throws Will throw an error if the model is not defined (that is, if sequelize#isDefined returns false) - * @returns {Model} Specified model - */ - model(modelName) { - if (!this.isDefined(modelName)) { - throw new Error(`${modelName} has not been defined`); - } - - return this.modelManager.getModel(modelName); - } - - /** - * Checks whether a model with the given name is defined - * - * @param {string} modelName The name of a model defined with Sequelize.define - * - * @returns {boolean} Returns true if model is already defined, otherwise false - */ - isDefined(modelName) { - return !!this.modelManager.models.find(model => model.name === modelName); - } - - /** - * 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. - * - * 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 - * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring - * - * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring - * ``` - * - * @param {string} sql - * @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 {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 {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. - * - * @returns {Promise} - * - * @see {@link Model.build} for more information about instance option. - */ - - async query(sql, options) { - options = { ...this.options.query, ...options }; - - if (options.instance && !options.model) { - options.model = options.instance.constructor; - } - - if (!options.instance && !options.model) { - options.raw = true; - } - - // map raw fields to model attributes - if (options.mapToModel) { - options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); - } - - options = _.defaults(options, { - // eslint-disable-next-line no-console - logging: Object.prototype.hasOwnProperty.call(this.options, 'logging') ? this.options.logging : console.log, - searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT' - }); - - if (!options.type) { - if (options.model || options.nest || options.plain) { - options.type = QueryTypes.SELECT; - } else { - options.type = QueryTypes.RAW; - } - } - - //if dialect doesn't support search_path or dialect option - //to prepend searchPath is not true delete the searchPath option - if ( - !this.dialect.supports.searchPath || - !this.options.dialectOptions || - !this.options.dialectOptions.prependSearchPath || - options.supportsSearchPath === false - ) { - delete options.searchPath; - } else if (!options.searchPath) { - //if user wants to always prepend searchPath (dialectOptions.preprendSearchPath = true) - //then set to DEFAULT if none is provided - options.searchPath = 'DEFAULT'; - } - - 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.query !== undefined) { - sql = sql.query; - } - } - - 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) { - if (Array.isArray(options.replacements)) { - sql = Utils.format([sql].concat(options.replacements), this.options.dialect); - } else { - sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); - } - } - - let bindParameters; - - if (options.bind) { - [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); - } - - const checkTransaction = () => { - if (options.transaction && options.transaction.finished && !options.completesTransaction) { - const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`); - error.sql = sql; - throw error; - } - }; - - const retryOptions = { ...this.options.retry, ...options.retry }; - - return retry(async () => { - if (options.transaction === undefined && Sequelize._cls) { - options.transaction = Sequelize._cls.get('transaction'); - } - - checkTransaction(); - - const connection = await (options.transaction ? options.transaction.connection : this.connectionManager.getConnection(options)); - const query = new this.dialect.Query(connection, this, options); - - 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 {Transaction} [options.transaction] The transaction that the query should be executed under - * - * @memberof Sequelize - * - * @returns {Promise} - */ - async set(variables, options) { - - // Prepare options - options = { ...this.options.set, ...typeof options === 'object' && options }; - - if (this.options.dialect !== 'mysql') { - throw new Error('sequelize.set is only supported for mysql'); - } - if (!options.transaction || !(options.transaction instanceof Transaction) ) { - throw new TypeError('options.transaction is required'); - } - - // Override some options, since this isn't a SELECT - options.raw = true; - options.plain = true; - options.type = 'SET'; - - // Generate SQL Query - const query = - `SET ${ - _.map(variables, (v, k) => `@${k} := ${typeof v === 'string' ? `"${v}"` : v}`).join(', ')}`; - - return await this.query(query, options); - } - - /** - * Escape value. - * - * @param {string} value string value to escape - * - * @returns {string} - */ - escape(value) { - return this.dialect.queryGenerator.escape(value); - } - - /** - * Create a new database schema. - * - * **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 command will do nothing. - * - * @see - * {@link Model.schema} - * - * @param {string} schema Name of the schema - * @param {object} [options={}] query options - * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * - * @returns {Promise} - */ - async createSchema(schema, options) { - return await this.getQueryInterface().createSchema(schema, options); - } - - /** - * Show all defined schemas - * - * **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 {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * - * @returns {Promise} - */ - async showAllSchemas(options) { - return await this.getQueryInterface().showAllSchemas(options); - } - - /** - * Drop a single schema - * - * **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 drop a table matching the schema name - * - * @param {string} schema Name of the schema - * @param {object} [options={}] query options - * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * - * @returns {Promise} - */ - async dropSchema(schema, options) { - return await this.getQueryInterface().dropSchema(schema, options); - } - - /** - * Drop all schemas. - * - * **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 {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * - * @returns {Promise} - */ - async dropAllSchemas(options) { - return await this.getQueryInterface().dropAllSchemas(options); - } - - /** - * Sync all defined models to the DB. - * - * @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} [options.alter.drop=true] Prevents any drop statements while altering a table when set to `false` - * - * @returns {Promise} - */ - 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)) { - throw new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`); - } - } - - if (options.hooks) { - await this.runHooks('beforeBulkSync', options); - } - if (options.force) { - await this.drop(options); - } - 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) { - 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 {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * @returns {Promise} - * - * @see - * {@link Model.truncate} for more information - */ - async truncate(options) { - const models = []; - - this.modelManager.forEachModel(model => { - if (model) { - models.push(model); - } - }, { reverse: false }); - - if (options && options.cascade) { - for (const model of models) await model.truncate(options); - } else { - await Promise.all(models.map(model => model.truncate(options))); - } - } - - /** - * Drop all tables defined through this sequelize instance. - * This is done by calling Model.drop on each model. - * - * @see - * {@link Model.drop} for options - * - * @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} - */ - async drop(options) { - const models = []; - - this.modelManager.forEachModel(model => { - if (model) { - models.push(model); - } - }, { reverse: false }); - - 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 - * - * @returns {Promise} - */ - async authenticate(options) { - options = { - raw: true, - plain: true, - type: QueryTypes.SELECT, - ...options - }; - - await this.query('SELECT 1+1 AS result', options); - - return; - } - - async databaseVersion(options) { - return await this.getQueryInterface().databaseVersion(options); - } - - /** - * Get the fn for random based on the dialect - * - * @returns {Sequelize.fn} - */ - random() { - const dia = this.getDialect(); - if (dia === 'postgres' || dia === 'sqlite') { - return this.fn('RANDOM'); - } - return this.fn('RAND'); - } - - /** - * Creates an object representing a database function. This can be used in search queries, both in where and order parts, and as default values in column definitions. - * If you want to refer to columns in your function, you should use `sequelize.col`, so that the columns are properly interpreted as columns and not a strings. - * - * @see - * {@link Model.findAll} - * @see - * {@link Sequelize.define} - * @see - * {@link Sequelize.col} - * - * @param {string} fn The function you want to call - * @param {any} args All further arguments will be passed as arguments to the function - * - * @since v2.0.0-dev3 - * @memberof Sequelize - * @returns {Sequelize.fn} - * - * @example - * instance.update({ - * username: sequelize.fn('upper', sequelize.col('username')) - * }); - */ - static fn(fn, ...args) { - return new Utils.Fn(fn, args); - } - - /** - * Creates an object which represents a column in the DB, this allows referencing another column in your query. This is often useful in conjunction with `sequelize.fn`, since raw string arguments to fn will be escaped. - * - * @see - * {@link Sequelize#fn} - * - * @param {string} col The name of the column - * @since v2.0.0-dev3 - * @memberof Sequelize - * - * @returns {Sequelize.col} - */ - static col(col) { - return new Utils.Col(col); - } - - /** - * Creates an object representing a call to the cast function. - * - * @param {any} val The value to cast - * @param {string} type The type to cast it to - * @since v2.0.0-dev3 - * @memberof Sequelize - * - * @returns {Sequelize.cast} - */ - static cast(val, type) { - return new Utils.Cast(val, type); - } - - /** - * Creates an object representing a literal, i.e. something that will not be escaped. - * - * @param {any} val literal value - * @since v2.0.0-dev3 - * @memberof Sequelize - * - * @returns {Sequelize.literal} - */ - static literal(val) { - return new Utils.Literal(val); - } - - /** - * An AND query - * - * @see - * {@link Model.findAll} - * - * @param {...string|object} args Each argument will be joined by AND - * @since v2.0.0-dev3 - * @memberof Sequelize - * - * @returns {Sequelize.and} - */ - static and(...args) { - return { [Op.and]: args }; - } - - /** - * An OR query - * - * @see - * {@link Model.findAll} - * - * @param {...string|object} args Each argument will be joined by OR - * @since v2.0.0-dev3 - * @memberof Sequelize - * - * @returns {Sequelize.or} - */ - static or(...args) { - return { [Op.or]: args }; - } - - /** - * Creates an object representing nested where conditions for postgres/sqlite/mysql json data-type. - * - * @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|number|boolean} [value] An optional value to compare against. Produces a string of the form " = ''". - * @memberof Sequelize - * - * @returns {Sequelize.json} - */ - static json(conditionsOrPath, value) { - return new Utils.Json(conditionsOrPath, value); - } - - /** - * A way of specifying attr = condition. - * - * The attr can either be an object taken from `Model.rawAttributes` (for example `Model.rawAttributes.id` or `Model.rawAttributes.name`). The - * attribute should be defined in your model definition. The attribute can also be an object from one of the sequelize utility functions (`sequelize.fn`, `sequelize.col` etc.) - * - * For string attributes, use the regular `{ where: { attr: something }}` syntax. If you don't want your string to be escaped, use `sequelize.literal`. - * - * @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.) - * @since v2.0.0-dev3 - */ - static where(attr, comparator, logic) { - return new Utils.Where(attr, comparator, logic); - } - - /** - * Start a transaction. When using transactions, you should pass the transaction in the options argument in order for the query to happen under that transaction @see {@link Transaction} - * - * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction will automatically be passed to any query that runs within the callback - * - * @example - * - * try { - * const transaction = await sequelize.transaction(); - * const user = await User.findOne(..., { transaction }); - * await user.update(..., { transaction }); - * await transaction.commit(); - * } catch { - * await transaction.rollback() - * } - * - * @example - * - * try { - * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments - * const user = await User.findOne(..., {transaction}); - * await user.update(..., {transaction}); - * }); - * // Committed - * } catch(err) { - * // Rolled back - * console.error(err); - * } - * @example - * - * const cls = require('cls-hooked'); - * const namespace = cls.createNamespace('....'); - * const Sequelize = require('sequelize'); - * Sequelize.useCLS(namespace); - * - * // Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace - * - * @param {object} [options] Transaction options - * @param {string} [options.type='DEFERRED'] See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. - * @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 - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {Function} [autoCallback] The callback is called with the transaction object, and should return a promise. If the promise is resolved, the transaction commits; if the promise rejects, the transaction rolls back - * - * @returns {Promise} - */ - async transaction(options, autoCallback) { - if (typeof options === 'function') { - autoCallback = options; - options = undefined; - } - - const transaction = new Transaction(this, options); - - if (!autoCallback) { - await transaction.prepareEnvironment(false); - return transaction; - } - - // autoCallback provided - return Sequelize._clsRun(async () => { - try { - await transaction.prepareEnvironment(); - const result = await autoCallback(transaction); - await transaction.commit(); - return await result; - } catch (err) { - try { - if (!transaction.finished) { - await transaction.rollback(); - } else { - // release the connection, even if we don't need to rollback - await transaction.cleanup(); - } - } catch (err0) { - // ignore - } - throw err; - } - }); - } - - /** - * Use CLS (Continuation Local Storage) with Sequelize. With Continuation - * Local Storage, all queries within the transaction callback will - * automatically receive the transaction object. - * - * CLS namespace provided is stored as `Sequelize._cls` - * - * @param {object} ns CLS namespace - * @returns {object} Sequelize constructor - */ - static useCLS(ns) { - // check `ns` is valid CLS namespace - if (!ns || typeof ns !== 'object' || typeof ns.bind !== 'function' || typeof ns.run !== 'function') throw new Error('Must provide CLS namespace'); - - // save namespace as `Sequelize._cls` - this._cls = ns; - - // return Sequelize for chaining - return this; - } - - /** - * Run function in CLS context. - * If no CLS context in use, just runs the function normally - * - * @private - * @param {Function} fn Function to run - * @returns {*} Return value of function - */ - static _clsRun(fn) { - const ns = Sequelize._cls; - if (!ns) return fn(); - - let res; - ns.run(context => res = fn(context)); - return res; - } - - log(...args) { - let options; - - const last = _.last(args); - - if (last && _.isPlainObject(last) && Object.prototype.hasOwnProperty.call(last, 'logging')) { - options = last; - - // remove options from set of logged arguments if options.logging is equal to console.log - // eslint-disable-next-line no-console - if (options.logging === console.log) { - args.splice(args.length - 1, 1); - } - } else { - options = this.options; - } - - if (options.logging) { - if (options.logging === true) { - deprecations.noTrueLogging(); - // eslint-disable-next-line no-console - options.logging = console.log; - } - - // second argument is sql-timings, when benchmarking option enabled - // eslint-disable-next-line no-console - if ((this.options.benchmark || options.benchmark) && options.logging === console.log) { - args = [`${args[0]} Elapsed time: ${args[1]}ms`]; - } - - options.logging(...args); - } - } - - /** - * Close all connections used by this sequelize instance, and free all references so the instance can be garbage collected. - * - * Normally this is done on process exit, so you only need to call this method if you are creating multiple instances, and want - * to garbage collect some of them. - * - * @returns {Promise} - */ - close() { - return this.connectionManager.close(); - } - - normalizeDataType(Type) { - let type = typeof Type === 'function' ? new Type() : Type; - const dialectTypes = this.dialect.DataTypes || {}; - - if (dialectTypes[type.key]) { - type = dialectTypes[type.key].extend(type); - } - - if (type instanceof DataTypes.ARRAY) { - if (!type.type) { - throw new Error('ARRAY is missing type definition for its values.'); - } - if (dialectTypes[type.type.key]) { - type.type = dialectTypes[type.type.key].extend(type.type); - } - } - - return type; - } - - normalizeAttribute(attribute) { - if (!_.isPlainObject(attribute)) { - attribute = { type: attribute }; - } - - if (!attribute.type) return attribute; - - attribute.type = this.normalizeDataType(attribute.type); - - if (Object.prototype.hasOwnProperty.call(attribute, 'defaultValue')) { - if (typeof attribute.defaultValue === 'function' && ( - attribute.defaultValue === DataTypes.NOW || - attribute.defaultValue === DataTypes.UUIDV1 || - attribute.defaultValue === DataTypes.UUIDV4 - )) { - attribute.defaultValue = new attribute.defaultValue(); - } - } - - if (attribute.type instanceof DataTypes.ENUM) { - // The ENUM is a special case where the type is an object containing the values - if (attribute.values) { - attribute.type.values = attribute.type.options.values = attribute.values; - } else { - attribute.values = attribute.type.values; - } - - if (!attribute.values.length) { - throw new Error('Values for ENUM have not been defined.'); - } - } - - return attribute; - } -} - -// Aliases -Sequelize.prototype.fn = Sequelize.fn; -Sequelize.prototype.col = Sequelize.col; -Sequelize.prototype.cast = Sequelize.cast; -Sequelize.prototype.literal = Sequelize.literal; -Sequelize.prototype.and = Sequelize.and; -Sequelize.prototype.or = Sequelize.or; -Sequelize.prototype.json = Sequelize.json; -Sequelize.prototype.where = Sequelize.where; -Sequelize.prototype.validate = Sequelize.prototype.authenticate; - -/** - * Sequelize version number. - */ -Sequelize.version = require('../package.json').version; - -Sequelize.options = { hooks: {} }; - -/** - * @private - */ -Sequelize.Utils = Utils; - -/** - * Operators symbols to be used for querying data - * - * @see {@link Operators} - */ -Sequelize.Op = Op; - -/** - * 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} - */ -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; - -Sequelize.Model = Model; - -Sequelize.DataTypes = DataTypes; -for (const dataType in DataTypes) { - Sequelize[dataType] = DataTypes[dataType]; -} - -/** - * A reference to the deferrable collection. Use this to access the different deferrable options. - * - * @see {@link Transaction.Deferrable} - * @see {@link Sequelize#transaction} - */ -Sequelize.Deferrable = Deferrable; - -/** - * A reference to the sequelize association class. - * - * @see {@link Association} - */ -Sequelize.prototype.Association = Sequelize.Association = Association; - -/** - * Provide alternative version of `inflection` module to be used by `Utils.pluralize` etc. - * - * @param {object} _inflection - `inflection` module - */ -Sequelize.useInflection = Utils.useInflection; - -/** - * Allow hooks to be defined on Sequelize + on sequelize instance as universal hooks to run on all models - * and on Sequelize/sequelize methods e.g. Sequelize(), Sequelize#define() - */ -Hooks.applyTo(Sequelize); -Hooks.applyTo(Sequelize.prototype); - -/** - * Expose various errors available - */ - -// expose alias to BaseError -Sequelize.Error = sequelizeErrors.BaseError; - -for (const error of Object.keys(sequelizeErrors)) { - Sequelize[error] = sequelizeErrors[error]; -} - -module.exports = Sequelize; -module.exports.Sequelize = Sequelize; -module.exports.default = Sequelize; diff --git a/lib/sql-string.js b/lib/sql-string.js deleted file mode 100644 index 2e334309d984..000000000000 --- a/lib/sql-string.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict'; - -const dataTypes = require('./data-types'); -const { logger } = require('./utils/logger'); - -function arrayToList(array, timeZone, dialect, format) { - return array.reduce((sql, val, i) => { - if (i !== 0) { - sql += ', '; - } - if (Array.isArray(val)) { - sql += `(${arrayToList(val, timeZone, dialect, format)})`; - } else { - sql += escape(val, timeZone, dialect, format); - } - return sql; - }, ''); -} -exports.arrayToList = arrayToList; - -function escape(val, timeZone, dialect, format) { - let prependN = false; - if (val === undefined || val === null) { - return 'NULL'; - } - switch (typeof val) { - case 'boolean': - // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0 - // for us. Postgres actually has a boolean type with true/false literals, - // but sequelize doesn't use it yet. - if (dialect === 'sqlite' || dialect === 'mssql') { - return +!!val; - } - return (!!val).toString(); - case 'number': - return val.toString(); - case 'string': - // In mssql, prepend N to all quoted vals which are originally a string (for - // unicode compatibility) - prependN = dialect === 'mssql'; - break; - } - - if (val instanceof Date) { - val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone }); - } - - if (Buffer.isBuffer(val)) { - if (dataTypes[dialect].BLOB) { - return dataTypes[dialect].BLOB.prototype.stringify(val); - } - - return dataTypes.BLOB.prototype.stringify(val); - } - - if (Array.isArray(val)) { - const partialEscape = escVal => escape(escVal, timeZone, dialect, format); - if (dialect === 'postgres' && !format) { - return dataTypes.ARRAY.prototype.stringify(val, { escape: partialEscape }); - } - return arrayToList(val, timeZone, dialect, format); - } - - if (!val.replace) { - throw new Error(`Invalid value ${logger.inspect(val)}`); - } - - if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS - // http://stackoverflow.com/q/603572/130598 - val = val.replace(/'/g, "''"); - - if (dialect === 'postgres') { - // null character is not allowed in Postgres - val = val.replace(/\0/g, '\\0'); - } - } else { - // eslint-disable-next-line no-control-regex - val = val.replace(/[\0\n\r\b\t\\'"\x1a]/g, s => { - switch (s) { - case '\0': return '\\0'; - case '\n': return '\\n'; - case '\r': return '\\r'; - case '\b': return '\\b'; - case '\t': return '\\t'; - case '\x1a': return '\\Z'; - default: return `\\${s}`; - } - }); - } - return `${(prependN ? "N'" : "'") + val}'`; -} -exports.escape = escape; - -function format(sql, values, timeZone, dialect) { - values = [].concat(values); - - if (typeof sql !== 'string') { - throw new Error(`Invalid SQL string provided: ${sql}`); - } - - return sql.replace(/\?/g, match => { - if (!values.length) { - return match; - } - - return escape(values.shift(), timeZone, dialect, true); - }); -} -exports.format = format; - -function formatNamedParameters(sql, values, timeZone, dialect) { - return sql.replace(/:+(?!\d)(\w+)/g, (value, key) => { - if ('postgres' === dialect && '::' === value.slice(0, 2)) { - return value; - } - - if (values[key] !== undefined) { - return escape(values[key], timeZone, dialect, true); - } - throw new Error(`Named parameter "${value}" has no value in the given object.`); - }); -} -exports.formatNamedParameters = formatNamedParameters; diff --git a/lib/table-hints.js b/lib/table-hints.js deleted file mode 100644 index 837b4ae27a21..000000000000 --- a/lib/table-hints.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * An enum of table hints to be used in mssql for querying with table hints - * - * @property NOLOCK - * @property READUNCOMMITTED - * @property UPDLOCK - * @property REPEATABLEREAD - * @property SERIALIZABLE - * @property READCOMMITTED - * @property TABLOCK - * @property TABLOCKX - * @property PAGLOCK - * @property ROWLOCK - * @property NOWAIT - * @property READPAST - * @property XLOCK - * @property SNAPSHOT - * @property NOEXPAND - */ -const TableHints = module.exports = { // eslint-disable-line - NOLOCK: 'NOLOCK', - READUNCOMMITTED: 'READUNCOMMITTED', - UPDLOCK: 'UPDLOCK', - REPEATABLEREAD: 'REPEATABLEREAD', - SERIALIZABLE: 'SERIALIZABLE', - READCOMMITTED: 'READCOMMITTED', - TABLOCK: 'TABLOCK', - TABLOCKX: 'TABLOCKX', - PAGLOCK: 'PAGLOCK', - ROWLOCK: 'ROWLOCK', - NOWAIT: 'NOWAIT', - READPAST: 'READPAST', - XLOCK: 'XLOCK', - SNAPSHOT: 'SNAPSHOT', - NOEXPAND: 'NOEXPAND' -}; diff --git a/lib/transaction.js b/lib/transaction.js deleted file mode 100644 index f8e32f18df92..000000000000 --- a/lib/transaction.js +++ /dev/null @@ -1,317 +0,0 @@ -'use strict'; - -/** - * The transaction object is used to identify a running transaction. - * It is created by calling `Sequelize.transaction()`. - * To run a query under a transaction, you should pass the transaction in the options object. - * - * @class Transaction - * @see {@link Sequelize.transaction} - */ -class Transaction { - /** - * Creates a new transaction instance - * - * @param {Sequelize} sequelize A configured sequelize Instance - * @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 - */ - constructor(sequelize, options) { - this.sequelize = sequelize; - this.savepoints = []; - this._afterCommitHooks = []; - - // get dialect specific transaction options - const generateTransactionId = this.sequelize.dialect.queryGenerator.generateTransactionId; - - this.options = { - type: sequelize.options.transactionType, - isolationLevel: sequelize.options.isolationLevel, - readOnly: false, - ...options - }; - - this.parent = this.options.transaction; - - if (this.parent) { - this.id = this.parent.id; - this.parent.savepoints.push(this); - this.name = `${this.id}-sp-${this.parent.savepoints.length}`; - } else { - this.id = this.name = generateTransactionId(); - } - - delete this.options.transaction; - } - - /** - * Commit the transaction - * - * @returns {Promise} - */ - async commit() { - if (this.finished) { - throw new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`); - } - - 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]); - } - } - } - - /** - * Rollback (abort) the transaction - * - * @returns {Promise} - */ - async rollback() { - if (this.finished) { - throw new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`); - } - - if (!this.connection) { - throw new Error('Transaction cannot be rolled back because it never started'); - } - - try { - return await this - .sequelize - .getQueryInterface() - .rollbackTransaction(this, this.options); - } finally { - this.cleanup(); - } - } - - /** - * Called to acquire a connection to use and set the correct options on the connection. - * We should ensure all of the environment that's set up is cleaned up in `cleanup()` below. - * - * @param {boolean} useCLS Defaults to true: Use CLS (Continuation Local Storage) with Sequelize. With CLS, all queries within the transaction callback will automatically receive the transaction object. - * @returns {Promise} - */ - async prepareEnvironment(useCLS) { - let connectionPromise; - - if (useCLS === undefined) { - useCLS = true; - } - - if (this.parent) { - connectionPromise = Promise.resolve(this.parent.connection); - } else { - const acquireOptions = { uuid: this.id }; - if (this.options.readOnly) { - acquireOptions.type = 'SELECT'; - } - connectionPromise = this.sequelize.connectionManager.getConnection(acquireOptions); - } - - 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; - } - - async setDeferrable() { - if (this.options.deferrable) { - return await this - .sequelize - .getQueryInterface() - .deferConstraints(this, this.options); - } - } - - async begin() { - const queryInterface = this.sequelize.getQueryInterface(); - - if ( this.sequelize.dialect.supports.settingIsolationLevelDuringTransaction ) { - await queryInterface.startTransaction(this, this.options); - return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); - } - - await queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); - - return queryInterface.startTransaction(this, this.options); - } - - cleanup() { - // Don't release the connection if there's a parent transaction or - // if we've already cleaned up - if (this.parent || this.connection.uuid === undefined) return; - - this._clearCls(); - const res = this.sequelize.connectionManager.releaseConnection(this.connection); - this.connection.uuid = undefined; - return res; - } - - _clearCls() { - const cls = this.sequelize.constructor._cls; - - if (cls) { - if (cls.get('transaction') === this) { - cls.set('transaction', null); - } - } - } - - /** - * A hook that is run after a transaction is committed - * - * @param {Function} fn A callback function that is called with the committed transaction - * @name afterCommit - * @memberof Sequelize.Transaction - */ - afterCommit(fn) { - if (!fn || typeof fn !== 'function') { - throw new Error('"fn" must be a function'); - } - this._afterCommitHooks.push(fn); - } - - /** - * Types can be set per-transaction by passing `options.type` to `sequelize.transaction`. - * Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`. - * Sqlite only. - * - * Pass in the desired level as the first argument: - * - * @example - * 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) { - * // do something with the err. - * } - * - * @property DEFERRED - * @property IMMEDIATE - * @property EXCLUSIVE - */ - static get TYPES() { - return { - DEFERRED: 'DEFERRED', - IMMEDIATE: 'IMMEDIATE', - EXCLUSIVE: 'EXCLUSIVE' - }; - } - - /** - * Isolation levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`. - * Sequelize uses the default isolation level of the database, you can override this by passing `options.isolationLevel` in Sequelize constructor options. - * - * Pass in the desired level as the first argument: - * - * @example - * 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) { - * // do something with the err. - * } - * - * @property READ_UNCOMMITTED - * @property READ_COMMITTED - * @property REPEATABLE_READ - * @property SERIALIZABLE - */ - static get ISOLATION_LEVELS() { - return { - READ_UNCOMMITTED: 'READ UNCOMMITTED', - READ_COMMITTED: 'READ COMMITTED', - REPEATABLE_READ: 'REPEATABLE READ', - SERIALIZABLE: 'SERIALIZABLE' - }; - } - - - /** - * Possible options for row locking. Used in conjunction with `find` calls: - * - * @example - * // t1 is a transaction - * Model.findAll({ - * where: ..., - * transaction: t1, - * lock: t1.LOCK... - * }); - * - * @example - * UserModel.findAll({ - * where: ..., - * include: [TaskModel, ...], - * transaction: t1, - * lock: { - * level: t1.LOCK..., - * of: UserModel - * } - * }); - * - * # UserModel will be locked but TaskModel won't! - * - * @example - * // t1 is a transaction - * Model.findAll({ - * where: ..., - * transaction: t1, - * lock: true, - * skipLocked: true - * }); - * # The query will now return any rows that aren't locked by another transaction - * - * @returns {object} - * @property UPDATE - * @property SHARE - * @property KEY_SHARE Postgres 9.3+ only - * @property NO_KEY_UPDATE Postgres 9.3+ only - */ - static get LOCK() { - return { - UPDATE: 'UPDATE', - SHARE: 'SHARE', - KEY_SHARE: 'KEY SHARE', - NO_KEY_UPDATE: 'NO KEY UPDATE' - }; - } - - /** - * Please see {@link Transaction.LOCK} - */ - get LOCK() { - return Transaction.LOCK; - } -} - -module.exports = Transaction; -module.exports.Transaction = Transaction; -module.exports.default = Transaction; diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 65b4c75dcf93..000000000000 --- a/lib/utils.js +++ /dev/null @@ -1,635 +0,0 @@ -'use strict'; - -const DataTypes = require('./data-types'); -const SqlString = require('./sql-string'); -const _ = require('lodash'); -const baseIsNative = require('lodash/_baseIsNative'); -const uuidv1 = require('uuid').v1; -const uuidv4 = require('uuid').v4; -const operators = require('./operators'); -const operatorsSet = new Set(Object.values(operators)); - -let inflection = require('inflection'); - -exports.classToInvokable = require('./utils/class-to-invokable').classToInvokable; -exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments; - -function useInflection(_inflection) { - inflection = _inflection; -} -exports.useInflection = useInflection; - -function camelizeIf(str, condition) { - let result = str; - - if (condition) { - result = camelize(str); - } - - return result; -} -exports.camelizeIf = camelizeIf; - -function underscoredIf(str, condition) { - let result = str; - - if (condition) { - result = underscore(str); - } - - return result; -} -exports.underscoredIf = underscoredIf; - -function isPrimitive(val) { - const type = typeof val; - return type === 'string' || type === 'number' || type === 'boolean'; -} -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, 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; - } - }); -} -exports.mergeDefaults = mergeDefaults; - -// An alternative to _.merge, which doesn't clone its arguments -// Cloning is a bad idea because options arguments may contain references to sequelize -// models - which again reference database libs which don't like to be cloned (in particular pg-native) -function merge() { - const result = {}; - - for (const obj of arguments) { - _.forOwn(obj, (value, key) => { - if (value !== undefined) { - if (!result[key]) { - result[key] = value; - } else if (_.isPlainObject(value) && _.isPlainObject(result[key])) { - result[key] = merge(result[key], value); - } else if (Array.isArray(value) && Array.isArray(result[key])) { - result[key] = value.concat(result[key]); - } else { - result[key] = value; - } - } - }); - } - - return result; -} -exports.merge = merge; - -function spliceStr(str, index, count, add) { - return str.slice(0, index) + add + str.slice(index + count); -} -exports.spliceStr = spliceStr; - -function camelize(str) { - return str.trim().replace(/[-_\s]+(.)?/g, (match, c) => c.toUpperCase()); -} -exports.camelize = camelize; - -function underscore(str) { - return inflection.underscore(str); -} -exports.underscore = underscore; - -function singularize(str) { - return inflection.singularize(str); -} -exports.singularize = singularize; - -function pluralize(str) { - return inflection.pluralize(str); -} -exports.pluralize = pluralize; - -function format(arr, dialect) { - const timeZone = null; - // Make a clone of the array beacuse format modifies the passed args - return SqlString.format(arr[0], arr.slice(1), timeZone, dialect); -} -exports.format = format; - -function formatNamedParameters(sql, parameters, dialect) { - const timeZone = null; - return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect); -} -exports.formatNamedParameters = formatNamedParameters; - -function cloneDeep(obj, onlyPlain) { - obj = obj || {}; - return _.cloneDeepWith(obj, elem => { - // Do not try to customize cloning of arrays or POJOs - if (Array.isArray(elem) || _.isPlainObject(elem)) { - return undefined; - } - - // If we specified to clone only plain objects & arrays, we ignore everyhing else - // In any case, don't clone stuff that's an object, but not a plain one - fx example sequelize models and instances - if (onlyPlain || typeof elem === 'object') { - return elem; - } - - // Preserve special data-types like `fn` across clones. _.get() is used for checking up the prototype chain - if (elem && typeof elem.clone === 'function') { - return elem.clone(); - } - }); -} -exports.cloneDeep = cloneDeep; - -/* Expand and normalize finder options */ -function mapFinderOptions(options, Model) { - if (options.attributes && Array.isArray(options.attributes)) { - options.attributes = Model._injectDependentVirtualAttributes(options.attributes); - options.attributes = options.attributes.filter(v => !Model._virtualAttributes.has(v)); - } - - mapOptionFieldNames(options, Model); - - return options; -} -exports.mapFinderOptions = mapFinderOptions; - -/* Used to map field names in attributes and where conditions */ -function mapOptionFieldNames(options, Model) { - if (Array.isArray(options.attributes)) { - options.attributes = options.attributes.map(attr => { - // Object lookups will force any variable to strings, we don't want that for special objects etc - if (typeof attr !== 'string') return attr; - // Map attributes to aliased syntax attributes - if (Model.rawAttributes[attr] && attr !== Model.rawAttributes[attr].field) { - return [Model.rawAttributes[attr].field, attr]; - } - return attr; - }); - } - - if (options.where && _.isPlainObject(options.where)) { - options.where = mapWhereFieldNames(options.where, Model); - } - - return options; -} -exports.mapOptionFieldNames = mapOptionFieldNames; - -function mapWhereFieldNames(attributes, Model) { - if (attributes) { - attributes = cloneDeep(attributes); - getComplexKeys(attributes).forEach(attribute => { - const rawAttribute = Model.rawAttributes[attribute]; - - if (rawAttribute && rawAttribute.field !== rawAttribute.fieldName) { - attributes[rawAttribute.field] = attributes[attribute]; - delete attributes[attribute]; - } - - if (_.isPlainObject(attributes[attribute]) - && !(rawAttribute && ( - rawAttribute.type instanceof DataTypes.HSTORE - || rawAttribute.type instanceof DataTypes.JSON))) { // Prevent renaming of HSTORE & JSON fields - attributes[attribute] = mapOptionFieldNames({ - where: attributes[attribute] - }, Model).where; - } - - if (Array.isArray(attributes[attribute])) { - attributes[attribute].forEach((where, index) => { - if (_.isPlainObject(where)) { - attributes[attribute][index] = mapWhereFieldNames(where, Model); - } - }); - } - - }); - } - - return attributes; -} -exports.mapWhereFieldNames = mapWhereFieldNames; - -/* Used to map field names in values */ -function mapValueFieldNames(dataValues, fields, Model) { - const values = {}; - - for (const attr of fields) { - if (dataValues[attr] !== undefined && !Model._virtualAttributes.has(attr)) { - // Field name mapping - if (Model.rawAttributes[attr] && Model.rawAttributes[attr].field && Model.rawAttributes[attr].field !== attr) { - values[Model.rawAttributes[attr].field] = dataValues[attr]; - } else { - values[attr] = dataValues[attr]; - } - } - } - - return values; -} -exports.mapValueFieldNames = mapValueFieldNames; - -function isColString(value) { - return typeof value === 'string' && value[0] === '$' && value[value.length - 1] === '$'; -} -exports.isColString = isColString; - -function canTreatArrayAsAnd(arr) { - return arr.some(arg => _.isPlainObject(arg) || arg instanceof Where); -} -exports.canTreatArrayAsAnd = canTreatArrayAsAnd; - -function combineTableNames(tableName1, tableName2) { - return tableName1.toLowerCase() < tableName2.toLowerCase() ? tableName1 + tableName2 : tableName2 + tableName1; -} -exports.combineTableNames = combineTableNames; - -function toDefaultValue(value, dialect) { - if (typeof value === 'function') { - const tmp = value(); - if (tmp instanceof DataTypes.ABSTRACT) { - return tmp.toSql(); - } - return tmp; - } - if (value instanceof DataTypes.UUIDV1) { - return uuidv1(); - } - if (value instanceof DataTypes.UUIDV4) { - return uuidv4(); - } - if (value instanceof DataTypes.NOW) { - return now(dialect); - } - if (Array.isArray(value)) { - return value.slice(); - } - if (_.isPlainObject(value)) { - return { ...value }; - } - return value; -} -exports.toDefaultValue = toDefaultValue; - -/** - * Determine if the default value provided exists and can be described - * in a db schema using the DEFAULT directive. - * - * @param {*} value Any default value. - * @returns {boolean} yes / no. - * @private - */ -function defaultValueSchemable(value) { - if (value === undefined) { return false; } - - // TODO this will be schemable when all supported db - // have been normalized for this case - if (value instanceof DataTypes.NOW) { return false; } - - if (value instanceof DataTypes.UUIDV1 || value instanceof DataTypes.UUIDV4) { return false; } - - return typeof value !== 'function'; -} -exports.defaultValueSchemable = defaultValueSchemable; - -function removeNullValuesFromHash(hash, omitNull, options) { - let result = hash; - - options = options || {}; - options.allowNull = options.allowNull || []; - - if (omitNull) { - const _hash = {}; - - _.forIn(hash, (val, key) => { - if (options.allowNull.includes(key) || key.endsWith('Id') || val !== null && val !== undefined) { - _hash[key] = val; - } - }); - - result = _hash; - } - - return result; -} -exports.removeNullValuesFromHash = removeNullValuesFromHash; - -const dialects = new Set(['mariadb', 'mysql', 'postgres', 'sqlite', 'mssql']); - -function now(dialect) { - const d = new Date(); - if (!dialects.has(dialect)) { - d.setMilliseconds(0); - } - return d; -} -exports.now = now; - -// Note: Use the `quoteIdentifier()` and `escape()` methods on the -// `QueryInterface` instead for more portable code. - -const TICK_CHAR = '`'; -exports.TICK_CHAR = TICK_CHAR; - -function addTicks(s, tickChar) { - tickChar = tickChar || TICK_CHAR; - return tickChar + removeTicks(s, tickChar) + tickChar; -} -exports.addTicks = addTicks; - -function removeTicks(s, tickChar) { - tickChar = tickChar || TICK_CHAR; - return s.replace(new RegExp(tickChar, 'g'), ''); -} -exports.removeTicks = removeTicks; - -/** - * Receives a tree-like object and returns a plain object which depth is 1. - * - * - Input: - * - * { - * name: 'John', - * address: { - * street: 'Fake St. 123', - * coordinates: { - * longitude: 55.6779627, - * latitude: 12.5964313 - * } - * } - * } - * - * - Output: - * - * { - * name: 'John', - * address.street: 'Fake St. 123', - * address.coordinates.latitude: 55.6779627, - * address.coordinates.longitude: 12.5964313 - * } - * - * @param {object} value an Object - * @returns {object} a flattened object - * @private - */ -function flattenObjectDeep(value) { - if (!_.isPlainObject(value)) return value; - const flattenedObj = {}; - - function flattenObject(obj, subPath) { - Object.keys(obj).forEach(key => { - const pathToProperty = subPath ? `${subPath}.${key}` : key; - if (typeof obj[key] === 'object' && obj[key] !== null) { - flattenObject(obj[key], pathToProperty); - } else { - flattenedObj[pathToProperty] = _.get(obj, key); - } - }); - return flattenedObj; - } - - return flattenObject(value, undefined); -} -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 {} -exports.SequelizeMethod = SequelizeMethod; - -class Fn extends SequelizeMethod { - constructor(fn, args) { - super(); - this.fn = fn; - this.args = args; - } - clone() { - return new Fn(this.fn, this.args); - } -} -exports.Fn = Fn; - -class Col extends SequelizeMethod { - constructor(col, ...args) { - super(); - if (args.length > 0) { - col = args; - } - this.col = col; - } -} -exports.Col = Col; - -class Cast extends SequelizeMethod { - constructor(val, type, json) { - super(); - this.val = val; - this.type = (type || '').trim(); - this.json = json || false; - } -} -exports.Cast = Cast; - -class Literal extends SequelizeMethod { - constructor(val) { - super(); - this.val = val; - } -} -exports.Literal = Literal; - -class Json extends SequelizeMethod { - constructor(conditionsOrPath, value) { - super(); - if (_.isObject(conditionsOrPath)) { - this.conditions = conditionsOrPath; - } else { - this.path = conditionsOrPath; - if (value) { - this.value = value; - } - } - } -} -exports.Json = Json; - -class Where extends SequelizeMethod { - constructor(attribute, comparator, logic) { - super(); - if (logic === undefined) { - logic = comparator; - comparator = '='; - } - - this.attribute = attribute; - this.comparator = comparator; - this.logic = logic; - } -} -exports.Where = Where; - -//Collection of helper methods to make it easier to work with symbol operators - -/** - * getOperators - * - * @param {object} obj - * @returns {Array} All operators properties of obj - * @private - */ -function getOperators(obj) { - return Object.getOwnPropertySymbols(obj).filter(s => operatorsSet.has(s)); -} -exports.getOperators = getOperators; - -/** - * getComplexKeys - * - * @param {object} obj - * @returns {Array} All keys including operators - * @private - */ -function getComplexKeys(obj) { - return getOperators(obj).concat(Object.keys(obj)); -} -exports.getComplexKeys = getComplexKeys; - -/** - * getComplexSize - * - * @param {object|Array} obj - * @returns {number} Length of object properties including operators if obj is array returns its length - * @private - */ -function getComplexSize(obj) { - return Array.isArray(obj) ? obj.length : getComplexKeys(obj).length; -} -exports.getComplexSize = getComplexSize; - -/** - * Returns true if a where clause is empty, even with Symbols - * - * @param {object} obj - * @returns {boolean} - * @private - */ -function isWhereEmpty(obj) { - return !!obj && _.isEmpty(obj) && getOperators(obj).length === 0; -} -exports.isWhereEmpty = isWhereEmpty; - -/** - * Returns ENUM name by joining table and column name - * - * @param {string} tableName - * @param {string} columnName - * @returns {string} - * @private - */ -function generateEnumName(tableName, columnName) { - return `enum_${tableName}_${columnName}`; -} -exports.generateEnumName = generateEnumName; - -/** - * Returns an new Object which keys are camelized - * - * @param {object} obj - * @returns {string} - * @private - */ -function camelizeObjectKeys(obj) { - const newObj = new Object(); - Object.keys(obj).forEach(key => { - newObj[camelize(key)] = obj[key]; - }); - return newObj; -} -exports.camelizeObjectKeys = camelizeObjectKeys; - -/** - * Assigns own and inherited enumerable string and symbol keyed properties of source - * objects to the destination object. - * - * https://lodash.com/docs/4.17.4#defaults - * - * **Note:** This method mutates `object`. - * - * @param {object} object The destination object. - * @param {...object} [sources] The source objects. - * @returns {object} Returns `object`. - * @private - */ -function defaults(object, ...sources) { - object = Object(object); - - sources.forEach(source => { - if (source) { - source = Object(source); - - getComplexKeys(source).forEach(key => { - const value = object[key]; - if ( - value === undefined || - _.eq(value, Object.prototype[key]) && - !Object.prototype.hasOwnProperty.call(object, key) - - ) { - object[key] = source[key]; - } - }); - } - }); - - return object; -} -exports.defaults = defaults; - -/** - * - * @param {object} index - * @param {Array} index.fields - * @param {string} [index.name] - * @param {string|object} tableName - * - * @returns {object} - * @private - */ -function nameIndex(index, tableName) { - if (tableName.tableName) tableName = tableName.tableName; - - if (!Object.prototype.hasOwnProperty.call(index, 'name')) { - const fields = index.fields.map( - field => typeof field === 'string' ? field : field.name || field.attribute - ); - index.name = underscore(`${tableName}_${fields.join('_')}`); - } - - return index; -} -exports.nameIndex = nameIndex; - -/** - * Checks if 2 arrays intersect. - * - * @param {Array} arr1 - * @param {Array} arr2 - * @private - */ -function intersects(arr1, arr2) { - return arr1.some(v => arr2.includes(v)); -} -exports.intersects = intersects; diff --git a/lib/utils/class-to-invokable.js b/lib/utils/class-to-invokable.js deleted file mode 100644 index 0bc63c517d94..000000000000 --- a/lib/utils/class-to-invokable.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -/** - * Wraps a constructor to not need the `new` keyword using a proxy. - * Only used for data types. - * - * @param {Function} Class The class instance to wrap as invocable. - * @returns {Proxy} Wrapped class instance. - * @private - */ -function classToInvokable(Class) { - return new Proxy(Class, { - apply(Target, thisArg, args) { - return new Target(...args); - }, - construct(Target, args) { - return new Target(...args); - }, - get(target, p) { - return target[p]; - } - }); -} -exports.classToInvokable = classToInvokable; diff --git a/lib/utils/deprecations.js b/lib/utils/deprecations.js deleted file mode 100644 index ac9d62216c71..000000000000 --- a/lib/utils/deprecations.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const { deprecate } = require('util'); - -const noop = () => {}; - -exports.noRawAttributes = deprecate(noop, 'Use sequelize.fn / sequelize.literal to construct attributes', 'SEQUELIZE0001'); -exports.noTrueLogging = deprecate(noop, 'The logging-option should be either a function or false. Default: console.log', 'SEQUELIZE0002'); -exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators', 'SEQUELIZE0003'); -exports.noBoolOperatorAliases = deprecate(noop, 'A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.', 'SEQUELIZE0004'); -exports.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 deleted file mode 100644 index a53f61b8702f..000000000000 --- a/lib/utils/join-sql-fragments.js +++ /dev/null @@ -1,83 +0,0 @@ -'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 deleted file mode 100644 index 0b2ca26a53dd..000000000000 --- a/lib/utils/logger.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -/** - * Sequelize module for debug and deprecation messages. - * It require a `context` for which messages will be printed. - * - * @module logging - * @private - */ - -const debug = require('debug'); -const util = require('util'); - -class Logger { - constructor(config) { - - this.config = { - context: 'sequelize', - debug: true, - ...config - }; - } - - warn(message) { - // eslint-disable-next-line no-console - console.warn(`(${this.config.context}) Warning: ${message}`); - } - - inspect(value) { - return util.inspect(value, false, 3); - } - - debugContext(name) { - return debug(`${this.config.context}:${name}`); - } -} - -exports.logger = new Logger(); - -exports.Logger = Logger; diff --git a/lib/utils/validator-extras.js b/lib/utils/validator-extras.js deleted file mode 100644 index 6c554aa89bb7..000000000000 --- a/lib/utils/validator-extras.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const validator = _.cloneDeep(require('validator')); -const moment = require('moment'); - -const extensions = { - extend(name, fn) { - this[name] = fn; - - return this; - }, - notEmpty(str) { - return !str.match(/^[\s\t\r\n]*$/); - }, - len(str, min, max) { - return this.isLength(str, min, max); - }, - isUrl(str) { - return this.isURL(str); - }, - isIPv6(str) { - return this.isIP(str, 6); - }, - isIPv4(str) { - return this.isIP(str, 4); - }, - notIn(str, values) { - return !this.isIn(str, values); - }, - regex(str, pattern, modifiers) { - str += ''; - if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') { - pattern = new RegExp(pattern, modifiers); - } - return str.match(pattern); - }, - notRegex(str, pattern, modifiers) { - return !this.regex(str, pattern, modifiers); - }, - isDecimal(str) { - return str !== '' && !!str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][+-]?(?:[0-9]+))?$/); - }, - min(str, val) { - const number = parseFloat(str); - return isNaN(number) || number >= val; - }, - max(str, val) { - const number = parseFloat(str); - return isNaN(number) || number <= val; - }, - not(str, pattern, modifiers) { - return this.notRegex(str, pattern, modifiers); - }, - contains(str, elem) { - return !!elem && str.includes(elem); - }, - notContains(str, elem) { - return !this.contains(str, elem); - }, - is(str, pattern, modifiers) { - return this.regex(str, pattern, modifiers); - } -}; -exports.extensions = extensions; - -// instance based validators -validator.isImmutable = function(value, validatorArgs, field, modelInstance) { - return modelInstance.isNewRecord || modelInstance.dataValues[field] === modelInstance._previousDataValues[field]; -}; - -// extra validators -validator.notNull = function(val) { - return val !== null && val !== undefined; -}; - -// https://github.com/chriso/validator.js/blob/6.2.0/validator.js -_.forEach(extensions, (extend, key) => { - validator[key] = extend; -}); - -// map isNull to isEmpty -// https://github.com/chriso/validator.js/commit/e33d38a26ee2f9666b319adb67c7fc0d3dea7125 -validator.isNull = validator.isEmpty; - -// isDate removed in 7.0.0 -// https://github.com/chriso/validator.js/commit/095509fc707a4dc0e99f85131df1176ad6389fc9 -validator.isDate = function(dateString) { - // avoid http://momentjs.com/guides/#/warnings/js-date/ - // by doing a preliminary check on `dateString` - const parsed = Date.parse(dateString); - if (isNaN(parsed)) { - // fail if we can't parse it - return false; - } - // otherwise convert to ISO 8601 as moment prefers - // http://momentjs.com/docs/#/parsing/string/ - const date = new Date(parsed); - return moment(date.toISOString()).isValid(); -}; - -exports.validator = validator; diff --git a/logo.svg b/logo.svg new file mode 100644 index 000000000000..0ee676a33cc8 --- /dev/null +++ b/logo.svg @@ -0,0 +1,41 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nx.json b/nx.json new file mode 100644 index 000000000000..e14714e8b3ea --- /dev/null +++ b/nx.json @@ -0,0 +1,32 @@ +{ + "$schema": "node_modules/nx/schemas/nx-schema.json", + "namedInputs": { + "default": ["{projectRoot}/**/*"], + "prod": [ + "{projectRoot}/src/**/*", + "{projectRoot}/package.json", + "{projectRoot}/tsconfig.json", + "!{projectRoot}/src/**/*.test.ts" + ] + }, + "targetDefaults": { + "build": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["prod", "^prod"], + "outputs": ["{projectRoot}/lib"] + }, + "test-typings": { + "cache": true, + "inputs": ["default", "^prod"] + }, + "test-unit": { + "cache": true, + "dependsOn": ["build"], + "inputs": ["default", "^prod"] + } + }, + "workspaceLayout": { + "libsDir": "packages" + } +} diff --git a/package-support.json b/package-support.json new file mode 100644 index 000000000000..77ae7b83d775 --- /dev/null +++ b/package-support.json @@ -0,0 +1,13 @@ +{ + "versions": [ + { + "version": "*", + "target": { + "node": "supported" + }, + "response": { + "type": "time-permitting" + } + } + ] +} diff --git a/package.json b/package.json index a21320925b44..ac62ecd2553c 100644 --- a/package.json +++ b/package.json @@ -1,233 +1,108 @@ { - "name": "sequelize", - "description": "Multi dialect ORM for Node.JS", - "version": "0.0.0-development", - "maintainers": [ - "Pedro Augusto de Paula Barbosa " - ], - "repository": { - "type": "git", - "url": "https://github.com/sequelize/sequelize.git" - }, - "bugs": { - "url": "https://github.com/sequelize/sequelize/issues" - }, - "homepage": "https://sequelize.org/", - "main": "index.js", - "types": "types", - "engines": { - "node": ">=10.0.0" + "name": "@sequelize/monorepo", + "private": true, + "scripts": { + "prepare": "husky", + "publish-all": "lerna publish --conventional-commits --no-private --yes --create-release github", + "format": "npm run format:eslint && npm run format:prettier", + "format:eslint": "eslint . --fix --report-unused-disable-directives", + "format:prettier": "prettier --write .", + "test:format": "concurrently \"npm:test:format:*\"", + "test:format:eslint": "eslint . --quiet --report-unused-disable-directives", + "test:format:prettier": "prettier --check .", + "test-typings": "lerna run test-typings", + "test-exports": "lerna run test-exports", + "test-unit": "yarn test-unit-esm && lerna run test-unit", + "test-unit-esm": "mocha test/esm-named-exports.test.js", + "test-integration-mariadb": "yarn lerna run test-integration-mariadb", + "test-integration-mysql": "yarn lerna run test-integration-mysql", + "test-integration-postgres": "yarn lerna run test-integration-postgres", + "test-integration-postgres-native": "yarn lerna run test-integration-postgres-native", + "test-integration-sqlite3": "yarn lerna run test-integration-sqlite3", + "test-integration-mssql": "yarn lerna run test-integration-mssql", + "test-integration-db2": "yarn lerna run test-integration-db2", + "test-integration-ibmi": "yarn lerna run test-integration-ibmi", + "test-integration-snowflake": "yarn lerna run test-integration-snowflake", + "sync-exports": "lerna run sync-exports", + "build": "lerna run build", + "docs": "typedoc", + "delete-changelog": "node dev/delete-changelog.mjs", + "fix-commit": "export TMPFILE=$(mktemp) && grep -v '^#' $(git rev-parse --git-dir)/COMMIT_EDITMSG > $TMPFILE && git commit -e -F $TMPFILE", + "------------------------------------- local test dbs --------------------------------------": "", + "reset-mariadb": "bash dev/mariadb/oldest/reset.sh; bash dev/mariadb/latest/reset.sh", + "reset-mysql": "bash dev/mysql/oldest/reset.sh; bash dev/mysql/latest/reset.sh", + "reset-postgres": "bash dev/postgres/oldest/reset.sh; bash dev/postgres/latest/reset.sh", + "reset-mssql": "bash dev/mssql/oldest/reset.sh; bash dev/mssql/latest/reset.sh", + "reset-db2": "bash dev/db2/oldest/reset.sh; bash dev/db2/latest/reset.sh", + "reset-all": "concurrently \"npm:reset-*(!all)\"", + "start-mariadb-oldest": "bash dev/mariadb/oldest/start.sh", + "start-mariadb-latest": "bash dev/mariadb/latest/start.sh", + "start-mysql-oldest": "bash dev/mysql/oldest/start.sh", + "start-mysql-latest": "bash dev/mysql/latest/start.sh", + "start-postgres-oldest": "bash dev/postgres/oldest/start.sh", + "start-postgres-latest": "bash dev/postgres/latest/start.sh", + "start-mssql-oldest": "bash dev/mssql/oldest/start.sh", + "start-mssql-latest": "bash dev/mssql/latest/start.sh", + "start-db2-oldest": "bash dev/db2/oldest/start.sh", + "start-db2-latest": "bash dev/db2/latest/start.sh", + "start-oldest": "concurrently \"npm:start-*-oldest\"", + "start-latest": "concurrently \"npm:start-*-latest\"", + "stop-mariadb": "bash dev/mariadb/oldest/stop.sh; bash dev/mariadb/latest/stop.sh", + "stop-mysql": "bash dev/mysql/oldest/stop.sh; bash dev/mysql/latest/stop.sh", + "stop-postgres": "bash dev/postgres/oldest/stop.sh; bash dev/postgres/latest/stop.sh", + "stop-mssql": "bash dev/mssql/oldest/stop.sh; bash dev/mssql/latest/stop.sh", + "stop-db2": "bash dev/db2/oldest/stop.sh; bash dev/db2/latest/stop.sh", + "stop-all": "concurrently \"npm:stop-*(!all)\"", + "----------------------------------------- SSCCEs ------------------------------------------": "", + "sscce": "ts-node sscce.ts", + "sscce-mariadb": "cross-env DIALECT=mariadb yarn sscce", + "sscce-mysql": "cross-env DIALECT=mysql yarn sscce", + "sscce-postgres": "cross-env DIALECT=postgres yarn sscce", + "sscce-postgres-native": "cross-env DIALECT=postgres-native yarn sscce", + "sscce-sqlite3": "cross-env DIALECT=sqlite3 yarn sscce", + "sscce-mssql": "cross-env DIALECT=mssql yarn sscce", + "sscce-db2": "cross-env DIALECT=db2 yarn sscce" }, - "files": [ - "lib", - "types/index.d.ts", - "types/lib", - "types/type-helpers" + "workspaces": [ + "packages/*" ], - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.13.1", - "lodash": "^4.17.20", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^13.6.0", - "wkx": "^0.5.0" - }, "devDependencies": { - "@commitlint/cli": "^11.0.0", - "@commitlint/config-angular": "^11.0.0", - "@types/node": "^12.12.42", - "@types/validator": "^13.1.4", - "acorn": "^8.0.4", - "chai": "^4.x", - "chai-as-promised": "^7.x", - "chai-datetime": "^1.6.0", - "cheerio": "^1.0.0-rc.3", - "cls-hooked": "^4.2.2", - "cross-env": "^7.0.2", - "delay": "^4.3.0", - "esdoc": "^1.1.0", - "esdoc-ecmascript-proposal-plugin": "^1.0.0", - "esdoc-inject-style-plugin": "^1.0.0", - "esdoc-standard-plugin": "^1.0.0", - "eslint": "^6.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": "^10.2.6", - "mariadb": "^2.3.1", - "markdownlint-cli": "^0.26.0", - "marked": "^1.1.0", - "mocha": "^7.1.2", - "mysql2": "^2.1.0", - "nyc": "^15.0.0", - "p-map": "^4.0.0", - "p-props": "^4.0.0", - "p-settle": "^4.1.1", - "p-timeout": "^4.0.0", - "pg": "^8.2.1", - "pg-hstore": "^2.x", - "rimraf": "^3.0.2", - "semantic-release": "^17.3.0", - "semantic-release-fail-on-major-bump": "^1.0.0", - "sinon": "^9.0.2", - "sinon-chai": "^3.3.0", - "sqlite3": "^4.2.0", - "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", - "mariadb", - "sqlite", - "postgresql", - "postgres", - "pg", - "mssql", - "sql", - "sqlserver", - "orm", - "nodejs", - "object relational mapper", - "database", - "db" - ], - "commitlint": { - "extends": [ - "@commitlint/config-angular" - ], - "rules": { - "type-enum": [ - 2, - "always", - [ - "build", - "ci", - "docs", - "feat", - "fix", - "perf", - "refactor", - "revert", - "style", - "test", - "meta" - ] - ] - } + "@ephys/eslint-config-typescript": "20.1.4", + "@rushstack/eslint-patch": "1.15.0", + "@sequelize/utils": "workspace:*", + "@types/chai": "4.3.20", + "@types/lodash": "4.17.21", + "@types/mocha": "10.0.10", + "@types/node": "22.19.3", + "chai": "4.5.0", + "concurrently": "9.2.1", + "cross-env": "10.1.0", + "esbuild": "0.27.2", + "eslint": "8.57.1", + "eslint-plugin-jsdoc": "61.4.2", + "eslint-plugin-mocha": "10.5.0", + "fast-glob": "3.3.3", + "husky": "9.1.7", + "lerna": "8.2.4", + "lint-staged": "16.2.7", + "lodash": "4.17.21", + "markdownlint-cli": "0.47.0", + "mocha": "11.7.5", + "node-gyp": "11.5.0", + "node-hook": "1.0.0", + "nx": "21.6.10", + "prettier": "3.5.3", + "prettier-plugin-organize-imports": "4.3.0", + "source-map-support": "0.5.21", + "ts-node": "10.9.2", + "typedoc": "0.27.9", + "typedoc-plugin-mdn-links": "5.0.10", + "typedoc-plugin-missing-exports": "3.1.0", + "typescript": "5.8.3" }, + "packageManager": "yarn@4.12.0", "lint-staged": { - "*.js": "eslint" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged", - "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": { - "----------------------------------------- static analysis -----------------------------------------": "", - "lint": "eslint lib test --quiet", - "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", - "----------------------------------------- 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-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-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-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-postgres-native": "cross-env DIALECT=postgres-native npm test", - "test-mssql": "cross-env DIALECT=mssql npm test", - "----------------------------------------- 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", - "---------------------------------------------------------------------------------------------------": "" + "*.{js,mjs,cjs,ts,mts,cts}": "eslint --fix --report-unused-disable-directives", + "*": "prettier --write --ignore-unknown" } } diff --git a/packages/cli/.eslintrc.cjs b/packages/cli/.eslintrc.cjs new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/cli/.eslintrc.cjs @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 000000000000..f205c164b176 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1,8 @@ +/dist +/lib +/tmp +node_modules +oclif.lock +oclif.manifest.json +/migrations +/seeds diff --git a/packages/cli/.mocharc.jsonc b/packages/cli/.mocharc.jsonc new file mode 100644 index 000000000000..872c1060e101 --- /dev/null +++ b/packages/cli/.mocharc.jsonc @@ -0,0 +1,11 @@ +{ + // Ideally this file should extend the root one and only set "check-leaks" to false + // but extends is broken: https://github.com/mochajs/mocha/issues/3916 + "require": "ts-node/register", + "loader": "ts-node/esm", + "extensions": ["js", "ts"], + "watch-files": ["src"], + "timeout": 30000, + "reporter": "spec", + "exit": true +} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 000000000000..b91743af97ca --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1,37 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +**Note:** Version bump only for package @sequelize/cli + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/cli + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +### Bug Fixes + +- update inquirer ([#17540](https://github.com/sequelize/sequelize/issues/17540)) ([e34acec](https://github.com/sequelize/sequelize/commit/e34acec96e7eed54300fad1aa6c2a543bf76ac91)) + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +**Note:** Version bump only for package @sequelize/cli + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/cli + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +**Note:** Version bump only for package @sequelize/cli + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Features + +- **cli:** add `seed generate` command ([#17262](https://github.com/sequelize/sequelize/issues/17262)) ([b07ad40](https://github.com/sequelize/sequelize/commit/b07ad40f3f55a4a7bc7c924c3349b542b6e705d9)) +- create `@sequelize/cli` as a replacement for `sequelize-cli` ([#17195](https://github.com/sequelize/sequelize/issues/17195)) ([ec00aed](https://github.com/sequelize/sequelize/commit/ec00aed29674bb698445c2c7109b229215e96a0c)) diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 000000000000..c7060ba6c511 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,26 @@ +

Sequelize logo

+

Sequelize

+ +## Installation + +Using npm: + +```sh +npm install @sequelize/cli +``` + +Or using yarn: + +```sh +yarn add @sequelize/cli +``` + +## Usage + +- If installed globally: `sequelize --help` +- If installed locally using `yarn`: `yarn sequelize --help` +- If installed locally using `npm`: `npx sequelize --help` + +## Documentation + +The documentation can be found at https://sequelize.org/docs/v7/cli/ diff --git a/packages/cli/bin/dev.cmd b/packages/cli/bin/dev.cmd new file mode 100644 index 000000000000..cec553be46b8 --- /dev/null +++ b/packages/cli/bin/dev.cmd @@ -0,0 +1,3 @@ +@echo off + +node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %* diff --git a/packages/cli/bin/dev.js b/packages/cli/bin/dev.js new file mode 100755 index 000000000000..714fc26375d1 --- /dev/null +++ b/packages/cli/bin/dev.js @@ -0,0 +1,5 @@ +#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning + +import { execute } from '@oclif/core'; + +await execute({ development: true, dir: import.meta.url }); diff --git a/packages/cli/bin/run.cmd b/packages/cli/bin/run.cmd new file mode 100644 index 000000000000..968fc30758e6 --- /dev/null +++ b/packages/cli/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/packages/cli/bin/run.js b/packages/cli/bin/run.js new file mode 100755 index 000000000000..176d2af58c50 --- /dev/null +++ b/packages/cli/bin/run.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { execute } from '@oclif/core'; + +await execute({ dir: import.meta.url }); diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000000..943825327447 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,79 @@ +{ + "bin": { + "sequelize": "./bin/run.js" + }, + "bugs": "https://github.com/sequelize/sequelize/issues", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/input": "^4.3.1", + "@inquirer/select": "^4.4.2", + "@oclif/core": "^4.8.0", + "@oclif/plugin-help": "^6.2.36", + "@sequelize/utils": "workspace:*", + "ansis": "^3.17.0", + "cosmiconfig": "^9.0.0", + "zod": "^4.2.1" + }, + "description": "The Sequelize CLI\nDocumentation: https://sequelize.org/docs/v7/cli/", + "devDependencies": { + "@oclif/test": "4.1.15", + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "chai": "4.5.0", + "concurrently": "9.2.1", + "mocha": "11.7.5", + "oclif": "4.22.59", + "rimraf": "5.0.10" + }, + "engines": { + "node": ">=18.20.8" + }, + "exports": "./lib/index.js", + "files": [ + "/bin", + "/lib", + "/static", + "/oclif.manifest.json" + ], + "homepage": "https://sequelize.org/docs/v7/cli/", + "keywords": [], + "license": "MIT", + "main": "lib/index.js", + "name": "@sequelize/cli", + "oclif": { + "bin": "sequelize", + "dirname": "sequelize", + "commands": "./lib/commands", + "plugins": [ + "@oclif/plugin-help" + ], + "topicSeparator": " ", + "topics": { + "migration": { + "description": "Commands for managing database migrations" + }, + "seed": { + "description": "Commands for managing database seeding" + } + } + }, + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "rimraf lib && tsc --project tsconfig.build.json", + "postpack": "rimraf oclif.manifest.json", + "prepack": "yarn build && oclif manifest && oclif readme", + "prepare": "yarn build", + "test": "concurrently \"npm:test-*\"", + "test-unit": "mocha ./**/*.test.ts", + "test-typings": "tsc --noEmit", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "module", + "types": "lib/index.d.ts", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/cli/src/_internal/config.ts b/packages/cli/src/_internal/config.ts new file mode 100644 index 000000000000..c10bbf716096 --- /dev/null +++ b/packages/cli/src/_internal/config.ts @@ -0,0 +1,21 @@ +import { cosmiconfig } from 'cosmiconfig'; +import * as path from 'node:path'; +import { z } from 'zod/v3'; + +const explorer = cosmiconfig('sequelize'); +const result = await explorer.search(); + +const projectRoot = result?.filepath ? path.dirname(result.filepath) : process.cwd(); + +const configSchema = z.object({ + migrationFolder: z + .string() + .default('/migrations') + .transform(val => path.join(projectRoot, val)), + seedFolder: z + .string() + .default('/seeds') + .transform(val => path.join(projectRoot, val)), +}); + +export const config = configSchema.parse(result?.config || {}); diff --git a/packages/cli/src/_internal/sequelize-command.ts b/packages/cli/src/_internal/sequelize-command.ts new file mode 100644 index 000000000000..83101a35a3fd --- /dev/null +++ b/packages/cli/src/_internal/sequelize-command.ts @@ -0,0 +1,130 @@ +import checkbox from '@inquirer/checkbox'; +import confirm from '@inquirer/confirm'; +import input from '@inquirer/input'; +import select from '@inquirer/select'; +import type { Interfaces } from '@oclif/core'; +import { Command, Flags } from '@oclif/core'; +import type { FlagInput } from '@oclif/core/parser'; +import { pojo } from '@sequelize/utils'; + +export type CommandFlags = Interfaces.InferredFlags< + (typeof SequelizeCommand)['baseFlags'] & Flags +>; + +export abstract class SequelizeCommand extends Command { + static strict = false; + + static baseFlags = { + interactive: Flags.boolean({ + char: 'i', + description: 'Run command in interactive mode', + default: true, + allowNo: true, + }), + }; + + declare protected flags: CommandFlags; + + async init(): Promise { + await super.init(); + + const strictFlagConfig = this.ctor.flags; + + const looseParseFlagConfig: FlagInput = pojo(); + for (const key of Object.keys(strictFlagConfig)) { + looseParseFlagConfig[key] = { + ...strictFlagConfig[key], + required: false, + }; + } + + const { + flags: { interactive }, + } = await this.parse({ + baseFlags: (super.ctor as typeof SequelizeCommand).baseFlags, + // to access the "interactive" flag, we need to provide all possible flags or + // the cli will throw if an unknown flag is provided + flags: looseParseFlagConfig, + enableJsonFlag: this.ctor.enableJsonFlag, + strict: false, + }); + + if (!interactive) { + // in non-interactive mode, all required flags must be provided. + // re-parse to throw errors for missing required flags + const { flags } = await this.parse({ + flags: strictFlagConfig, + baseFlags: (super.ctor as typeof SequelizeCommand).baseFlags, + enableJsonFlag: this.ctor.enableJsonFlag, + strict: this.ctor.strict, + }); + + this.flags = flags as CommandFlags; + + return; + } + + // In interactive mode, we want to prompt the user for all flags that are not provided. + // Mark all flags as optional and remove their default value before parsing, + // then prompt all missing flags + + const { flags } = await this.parse({ + flags: looseParseFlagConfig, + baseFlags: (super.ctor as typeof SequelizeCommand).baseFlags, + enableJsonFlag: this.ctor.enableJsonFlag, + strict: this.ctor.strict, + }); + + for (const flagKey of Object.keys(strictFlagConfig)) { + if (flagKey in flags) { + continue; + } + + const flag = strictFlagConfig[flagKey]; + switch (flag.type) { + case 'option': { + if (flag.options) { + flags[flagKey] = flag.multiple + ? // eslint-disable-next-line no-await-in-loop + await checkbox({ + message: `${flag.summary}`, + required: flag.required, + choices: flag.options, + }) + : // eslint-disable-next-line no-await-in-loop + await select({ + message: `${flag.summary}`, + choices: flag.options, + default: flag.default, + }); + } else { + // eslint-disable-next-line no-await-in-loop + flags[flagKey] = await input({ + message: `${flag.summary}`, + required: flag.required ?? false, + default: flag.default, + }); + } + + break; + } + + case 'boolean': { + // eslint-disable-next-line no-await-in-loop + flags[flagKey] = await confirm({ + message: `${flag.summary}`, + }); + + break; + } + + default: { + // @ts-expect-error -- just in case + throw new Error(`Unsupported flag type: ${flag.type}`); + } + } + } + + this.flags = flags as CommandFlags; + } +} diff --git a/packages/cli/src/_internal/skeletons.ts b/packages/cli/src/_internal/skeletons.ts new file mode 100644 index 000000000000..a68be63224b9 --- /dev/null +++ b/packages/cli/src/_internal/skeletons.ts @@ -0,0 +1,6 @@ +import { fileUrlToDirname } from '@sequelize/utils/node'; +import * as path from 'node:path'; + +const __dirname = fileUrlToDirname(import.meta.url); + +export const SKELETONS_FOLDER = path.join(__dirname, '..', '..', 'static', 'skeletons'); diff --git a/packages/cli/src/_internal/utils.ts b/packages/cli/src/_internal/utils.ts new file mode 100644 index 000000000000..dbc31180abf9 --- /dev/null +++ b/packages/cli/src/_internal/utils.ts @@ -0,0 +1,26 @@ +export function getCurrentYYYYMMDDHHmms() { + const date = new Date(); + + return `${date.getUTCFullYear()}-${padNumber(date.getUTCMonth() + 1, 2)}-${padNumber( + date.getUTCDate(), + 2, + )}t${padNumber(date.getUTCHours(), 2)}-${padNumber(date.getUTCMinutes(), 2)}-${padNumber( + date.getUTCSeconds(), + 2, + )}`; +} + +export function padNumber(value: number, length: number): string { + return String(value).padStart(length, '0'); +} + +export function slugify(text: string): string { + return text + .toString() + .toLowerCase() + .replaceAll(/[\s.]+/g, '-') // Replace spaces & dots with - + .replaceAll(/[^\w-]+/g, '') // Remove all non-word chars + .replaceAll(/--+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text +} diff --git a/packages/cli/src/api/generate-migration.ts b/packages/cli/src/api/generate-migration.ts new file mode 100644 index 000000000000..402fd75b0a2e --- /dev/null +++ b/packages/cli/src/api/generate-migration.ts @@ -0,0 +1,47 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { SKELETONS_FOLDER } from '../_internal/skeletons.js'; +import { getCurrentYYYYMMDDHHmms, slugify } from '../_internal/utils.js'; + +export const SUPPORTED_MIGRATION_FORMATS = ['sql', 'typescript', 'cjs', 'esm'] as const; +export type SupportedMigrationFormat = (typeof SUPPORTED_MIGRATION_FORMATS)[number]; + +const FORMAT_EXTENSIONS: Record = { + sql: 'sql', + typescript: 'ts', + cjs: 'cjs', + esm: 'mjs', +}; + +export interface GenerateMigrationOptions { + format: SupportedMigrationFormat; + migrationName: string; + migrationFolder: string; +} + +export async function generateMigration(options: GenerateMigrationOptions): Promise { + const { format, migrationName, migrationFolder } = options; + + const migrationFilename = `${getCurrentYYYYMMDDHHmms()}-${slugify(migrationName)}`; + const migrationPath = path.join(migrationFolder, migrationFilename); + + if (format === 'sql') { + await fs.mkdir(migrationPath, { recursive: true }); + await Promise.all([ + fs.writeFile(path.join(migrationPath, 'up.sql'), ''), + fs.writeFile(path.join(migrationPath, 'down.sql'), ''), + ]); + + return migrationPath; + } + + await fs.mkdir(migrationFolder, { recursive: true }); + + const extension = FORMAT_EXTENSIONS[format]; + const targetPath = `${migrationPath}.${extension}`; + const sourcePath = path.join(SKELETONS_FOLDER, `migration.${extension}`); + + await fs.copyFile(sourcePath, targetPath); + + return targetPath; +} diff --git a/packages/cli/src/api/generate-seed.ts b/packages/cli/src/api/generate-seed.ts new file mode 100644 index 000000000000..5fc51332bcc3 --- /dev/null +++ b/packages/cli/src/api/generate-seed.ts @@ -0,0 +1,47 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { SKELETONS_FOLDER } from '../_internal/skeletons.js'; +import { getCurrentYYYYMMDDHHmms, slugify } from '../_internal/utils.js'; + +export const SUPPORTED_SEED_FORMATS = ['sql', 'typescript', 'cjs', 'esm'] as const; +export type SupportedSeedFormat = (typeof SUPPORTED_SEED_FORMATS)[number]; + +const FORMAT_EXTENSIONS: Record = { + sql: 'sql', + typescript: 'ts', + cjs: 'cjs', + esm: 'mjs', +}; + +export interface GenerateSeedOptions { + format: SupportedSeedFormat; + seedName: string; + seedFolder: string; +} + +export async function generateSeed(options: GenerateSeedOptions): Promise { + const { format, seedName, seedFolder } = options; + + const seedFilename = `${getCurrentYYYYMMDDHHmms()}-${slugify(seedName)}`; + const seedPath = path.join(seedFolder, seedFilename); + + if (format === 'sql') { + await fs.mkdir(seedPath, { recursive: true }); + await Promise.all([ + fs.writeFile(path.join(seedPath, 'up.sql'), ''), + fs.writeFile(path.join(seedPath, 'down.sql'), ''), + ]); + + return seedPath; + } + + await fs.mkdir(seedFolder, { recursive: true }); + + const extension = FORMAT_EXTENSIONS[format]; + const targetPath = `${seedPath}.${extension}`; + const sourcePath = path.join(SKELETONS_FOLDER, `seed.${extension}`); + + await fs.copyFile(sourcePath, targetPath); + + return targetPath; +} diff --git a/packages/cli/src/commands/migration/generate.test.ts b/packages/cli/src/commands/migration/generate.test.ts new file mode 100644 index 000000000000..519484ee1f84 --- /dev/null +++ b/packages/cli/src/commands/migration/generate.test.ts @@ -0,0 +1,76 @@ +import { runCommand } from '@oclif/test'; +import { fileUrlToDirname } from '@sequelize/utils/node'; +import { expect } from 'chai'; +import { access, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const __dirname = fileUrlToDirname(import.meta.url); +const packageRoot = join(__dirname, '..', '..', '..'); + +describe('migration:generate', () => { + it('generates an SQL migration', async () => { + const { stdout } = await runCommand( + ['migration:generate', '--format=sql', '--name=test-migration', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/migrations\/[\d\-t]{19}-test-migration/); + expect(await readdir(asJson.path)).to.have.members(['up.sql', 'down.sql']); + }); + + it('generates an TypeScript migration', async () => { + const { stdout } = await runCommand( + ['migration:generate', '--format=typescript', '--name=test-migration', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match( + /migrations\/[\d\-t]{19}-test-migration\.ts/, + ); + await access(asJson.path); + }); + + it('generates an CJS migration', async () => { + const { stdout } = await runCommand( + ['migration:generate', '--format=cjs', '--name=test-migration', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match( + /migrations\/[\d\-t]{19}-test-migration\.cjs/, + ); + await access(asJson.path); + }); + + it('generates an ESM migration', async () => { + const { stdout } = await runCommand( + ['migration:generate', '--format=esm', '--name=test-migration', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match( + /migrations\/[\d\-t]{19}-test-migration\.mjs/, + ); + await access(asJson.path); + }); + + it('supports not specifying a name', async () => { + const { stdout } = await runCommand( + ['migration:generate', '--format=sql', '--no-interactive', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/migrations\/[\d\-t]{19}-unnamed/); + }); +}); diff --git a/packages/cli/src/commands/migration/generate.ts b/packages/cli/src/commands/migration/generate.ts new file mode 100644 index 000000000000..cbc174d9f972 --- /dev/null +++ b/packages/cli/src/commands/migration/generate.ts @@ -0,0 +1,50 @@ +import { Flags } from '@oclif/core'; +import { green } from 'ansis'; +import { config } from '../../_internal/config.js'; +import { SequelizeCommand } from '../../_internal/sequelize-command.js'; +import type { SupportedMigrationFormat } from '../../api/generate-migration.js'; +import { SUPPORTED_MIGRATION_FORMATS, generateMigration } from '../../api/generate-migration.js'; + +export class GenerateMigration extends SequelizeCommand<(typeof GenerateMigration)['flags']> { + static enableJsonFlag = true; + + static flags = { + format: Flags.string({ + options: SUPPORTED_MIGRATION_FORMATS, + summary: 'The format of the migration file to generate', + required: true, + }), + name: Flags.string({ + summary: 'A short name for the migration file', + default: 'unnamed', + }), + }; + + static summary = 'Generates a new migration file'; + + static examples = [ + `<%= config.bin %> <%= command.id %>`, + `<%= config.bin %> <%= command.id %> --format=sql`, + `<%= config.bin %> <%= command.id %> --name="create users table"`, + ]; + + async run(): Promise<{ path: string }> { + const { format, name: migrationName } = this.flags; + const { migrationFolder } = config; + + const migrationPath = await generateMigration({ + format: format as SupportedMigrationFormat, + migrationName, + migrationFolder, + }); + + if (format === 'sql') { + this.log(`SQL migration files generated in ${green(migrationPath)}`); + } else { + this.log(`Migration file generated at ${green(migrationPath)}`); + } + + // JSON output + return { path: migrationPath }; + } +} diff --git a/packages/cli/src/commands/seed/generate.test.ts b/packages/cli/src/commands/seed/generate.test.ts new file mode 100644 index 000000000000..f2ba1cba6f04 --- /dev/null +++ b/packages/cli/src/commands/seed/generate.test.ts @@ -0,0 +1,70 @@ +import { runCommand } from '@oclif/test'; +import { fileUrlToDirname } from '@sequelize/utils/node'; +import { expect } from 'chai'; +import { access, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const __dirname = fileUrlToDirname(import.meta.url); +const packageRoot = join(__dirname, '..', '..', '..'); + +describe('seed:generate', () => { + it('generates an SQL seed', async () => { + const { stdout } = await runCommand( + ['seed:generate', '--format=sql', '--name=test-seed', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/seeds\/[\d\-t]{19}-test-seed/); + expect(await readdir(asJson.path)).to.have.members(['up.sql', 'down.sql']); + }); + + it('generates a TypeScript seed', async () => { + const { stdout } = await runCommand( + ['seed:generate', '--format=typescript', '--name=test-seed', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/seeds\/[\d\-t]{19}-test-seed\.ts/); + await access(asJson.path); + }); + + it('generates a CJS seed', async () => { + const { stdout } = await runCommand( + ['seed:generate', '--format=cjs', '--name=test-seed', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/seeds\/[\d\-t]{19}-test-seed\.cjs/); + await access(asJson.path); + }); + + it('generates a ESM seed', async () => { + const { stdout } = await runCommand( + ['seed:generate', '--format=esm', '--name=test-seed', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/seeds\/[\d\-t]{19}-test-seed\.mjs/); + await access(asJson.path); + }); + + it('supports not specifying a name', async () => { + const { stdout } = await runCommand( + ['seed:generate', '--format=sql', '--no-interactive', '--json'], + { root: packageRoot }, + ); + const asJson = JSON.parse(stdout); + + expect(Object.keys(asJson)).to.deep.eq(['path']); + expect(pathToFileURL(asJson.path).pathname).to.match(/seeds\/[\d\-t]{19}-unnamed/); + }); +}); diff --git a/packages/cli/src/commands/seed/generate.ts b/packages/cli/src/commands/seed/generate.ts new file mode 100644 index 000000000000..e223337e4f74 --- /dev/null +++ b/packages/cli/src/commands/seed/generate.ts @@ -0,0 +1,50 @@ +import { Flags } from '@oclif/core'; +import { green } from 'ansis'; +import { config } from '../../_internal/config.js'; +import { SequelizeCommand } from '../../_internal/sequelize-command.js'; +import type { SupportedSeedFormat } from '../../api/generate-seed.js'; +import { SUPPORTED_SEED_FORMATS, generateSeed } from '../../api/generate-seed.js'; + +export class GenerateSeed extends SequelizeCommand<(typeof GenerateSeed)['flags']> { + static enableJsonFlag = true; + + static flags = { + format: Flags.string({ + options: SUPPORTED_SEED_FORMATS, + summary: 'The format of the seed file to generate', + required: true, + }), + name: Flags.string({ + summary: 'A short name for the seed file', + default: 'unnamed', + }), + }; + + static summary = 'Generates a new seed file'; + + static examples = [ + `<%= config.bin %> <%= command.id %>`, + `<%= config.bin %> <%= command.id %> --format=sql`, + `<%= config.bin %> <%= command.id %> --name="users table test data"`, + ]; + + async run(): Promise<{ path: string }> { + const { format, name: seedName } = this.flags; + const { seedFolder } = config; + + const seedPath = await generateSeed({ + format: format as SupportedSeedFormat, + seedName, + seedFolder, + }); + + if (format === 'sql') { + this.log(`SQL seed files generated in ${green(seedPath)}`); + } else { + this.log(`Seed file generated at ${green(seedPath)}`); + } + + // JSON output + return { path: seedPath }; + } +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 000000000000..d620e709ab9a --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1 @@ +export { run } from '@oclif/core'; diff --git a/packages/cli/static/skeletons/migration.cjs b/packages/cli/static/skeletons/migration.cjs new file mode 100644 index 000000000000..1d3f544d4c78 --- /dev/null +++ b/packages/cli/static/skeletons/migration.cjs @@ -0,0 +1,23 @@ +'use strict'; + +module.exports = { + /** @type {import('@sequelize/cli').MigrationFunction} */ + async up(queryInterface, sequelize) { + /** + * Add altering commands here. + * + * Example: + * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ + }, + + /** @type {import('@sequelize/cli').MigrationFunction} */ + async down(queryInterface, sequelize) { + /** + * Add reverting commands here. + * + * Example: + * await queryInterface.dropTable('users'); + */ + }, +}; diff --git a/packages/cli/static/skeletons/migration.mjs b/packages/cli/static/skeletons/migration.mjs new file mode 100644 index 000000000000..c3daffac7452 --- /dev/null +++ b/packages/cli/static/skeletons/migration.mjs @@ -0,0 +1,19 @@ +/** @type {import('@sequelize/cli').MigrationFunction} */ +export async function up(queryInterface, sequelize) { + /** + * Add altering commands here. + * + * Example: + * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ +} + +/** @type {import('@sequelize/cli').MigrationFunction} */ +export async function down(queryInterface, sequelize) { + /** + * Add reverting commands here. + * + * Example: + * await queryInterface.dropTable('users'); + */ +} diff --git a/packages/cli/static/skeletons/migration.ts b/packages/cli/static/skeletons/migration.ts new file mode 100644 index 000000000000..2199e39303eb --- /dev/null +++ b/packages/cli/static/skeletons/migration.ts @@ -0,0 +1,25 @@ +import { AbstractQueryInterface, Sequelize } from '@sequelize/core'; + +export async function up( + queryInterface: AbstractQueryInterface, + sequelize: Sequelize, +): Promise { + /** + * Add altering commands here. + * + * Example: + * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ +} + +export async function down( + queryInterface: AbstractQueryInterface, + sequelize: Sequelize, +): Promise { + /** + * Add reverting commands here. + * + * Example: + * await queryInterface.dropTable('users'); + */ +} diff --git a/packages/cli/static/skeletons/seed.cjs b/packages/cli/static/skeletons/seed.cjs new file mode 100644 index 000000000000..19d01cbb5621 --- /dev/null +++ b/packages/cli/static/skeletons/seed.cjs @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + /** @type {import('@sequelize/cli').SeedFunction} */ + async up(queryInterface, sequelize) { + /** + * Add seed commands here. + * + * Example: + * await queryInterface.bulkInsert('People', [{ + * name: 'John Doe', + * isBetaMember: false + * }], {}); + */ + }, + + /** @type {import('@sequelize/cli').SeedFunction} */ + async down(queryInterface, sequelize) { + /** + * Add commands to revert seed here. + * + * Example: + * await queryInterface.bulkDelete('People', null, {}); + */ + }, +}; diff --git a/packages/cli/static/skeletons/seed.mjs b/packages/cli/static/skeletons/seed.mjs new file mode 100644 index 000000000000..96a485c585a3 --- /dev/null +++ b/packages/cli/static/skeletons/seed.mjs @@ -0,0 +1,22 @@ +/** @type {import('@sequelize/cli').SeedFunction} */ +export async function up(queryInterface, sequelize) { + /** + * Add seed commands here. + * + * Example: + * await queryInterface.bulkInsert('People', [{ + * name: 'John Doe', + * isBetaMember: false + * }], {}); + */ +} + +/** @type {import('@sequelize/cli').SeedFunction} */ +export async function down(queryInterface, sequelize) { + /** + * Add commands to revert seed here. + * + * Example: + * await queryInterface.bulkDelete('People', null, {}); + */ +} diff --git a/packages/cli/static/skeletons/seed.ts b/packages/cli/static/skeletons/seed.ts new file mode 100644 index 000000000000..c5935e1ab590 --- /dev/null +++ b/packages/cli/static/skeletons/seed.ts @@ -0,0 +1,28 @@ +import { AbstractQueryInterface, Sequelize } from '@sequelize/core'; + +export async function up( + queryInterface: AbstractQueryInterface, + sequelize: Sequelize, +): Promise { + /** + * Add seed commands here. + * + * Example: + * await queryInterface.bulkInsert('People', [{ + * name: 'John Doe', + * isBetaMember: false + * }], {}); + */ +} + +export async function down( + queryInterface: AbstractQueryInterface, + sequelize: Sequelize, +): Promise { + /** + * Add commands to revert seed here. + * + * Example: + * await queryInterface.bulkDelete('People', null, {}); + */ +} diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json new file mode 100644 index 000000000000..9a9add7b1105 --- /dev/null +++ b/packages/cli/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["./**/*.test.ts", "./static"] +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js new file mode 100644 index 000000000000..cfc430a97352 --- /dev/null +++ b/packages/core/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, + overrides: [ + { + files: ['test/**/*'], + parserOptions: { + project: [`${__dirname}/test/tsconfig.json`], + }, + }, + ], +}; diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000000..a2b677eba0ee --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,18 @@ +

Sequelize logo

+

Sequelize

+ +This library contains the core functionality of Sequelize. Head to [sequelize.org](https://sequelize.org) to learn how to use Sequelize. + +## Installation + +Using npm: + +```sh +npm install @sequelize/core +``` + +Or using yarn: + +```sh +yarn add @sequelize/core +``` diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000000..cf94787be7bc --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,170 @@ +{ + "name": "@sequelize/core", + "description": "Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift, Snowflake’s Data Cloud, Db2, and IBM i. It features solid transaction support, relations, eager and lazy loading, read replication and more.", + "version": "7.0.0-alpha.47", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "repository": { + "type": "git", + "url": "git@github.com:sequelize/sequelize.git" + }, + "bugs": { + "url": "https://github.com/sequelize/sequelize/issues" + }, + "homepage": "https://sequelize.org/", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "type": "commonjs", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + }, + "./decorators-legacy": { + "import": "./lib/decorators/legacy/index.mjs", + "require": { + "types": "./lib/decorators/legacy/index.d.ts", + "default": "./lib/decorators/legacy/index.js" + } + }, + "./_non-semver-use-at-your-own-risk_/*": "./lib/*", + "./package.json": "./package.json" + }, + "engines": { + "node": ">=18.20.8" + }, + "files": [ + "lib" + ], + "license": "MIT", + "dependencies": { + "@sequelize/utils": "workspace:*", + "@types/debug": "^4.1.12", + "@types/validator": "^13.15.10", + "ansis": "^3.17.0", + "bnf-parser": "^3.1.6", + "dayjs": "^1.11.19", + "debug": "^4.4.3", + "dottie": "^2.0.6", + "fast-glob": "^3.3.3", + "inflection": "^3.0.2", + "lodash": "^4.17.21", + "retry-as-promised": "^7.1.1", + "semver": "^7.7.3", + "sequelize-pool": "^8.0.1", + "toposort-class": "^1.0.1", + "type-fest": "^4.41.0", + "uuid": "^11.1.0", + "validator": "^13.15.26" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/chai-as-promised": "7.1.8", + "@types/chai-datetime": "1.0.0", + "@types/lodash": "4.17.21", + "@types/mocha": "10.0.10", + "@types/semver": "7.7.1", + "@types/sinon": "17.0.4", + "@types/sinon-chai": "3.2.12", + "chai": "4.5.0", + "chai-as-promised": "7.1.2", + "chai-datetime": "1.8.1", + "delay": "5.0.0", + "expect-type": "0.13.0", + "fs-jetpack": "5.1.0", + "lcov-result-merger": "5.0.1", + "mocha": "11.7.5", + "moment": "2.30.1", + "nyc": "17.1.0", + "p-map": "4.0.0", + "p-props": "4.0.0", + "p-settle": "4.1.1", + "p-timeout": "4.1.0", + "rimraf": "5.0.10", + "sinon": "18.0.1", + "sinon-chai": "3.7.0" + }, + "keywords": [ + "mysql", + "mariadb", + "sqlite", + "sqlite3", + "postgresql", + "postgres", + "pg", + "mssql", + "db2", + "ibm_db", + "sql", + "sqlserver", + "snowflake", + "orm", + "nodejs", + "object relational mapper", + "database", + "db" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "----------------------------------------- static analysis -----------------------------------------": "", + "test-typings": "tsc --noEmit && tsc -b test/tsconfig.json", + "----------------------------------------- tests ---------------------------------------------------": "", + "mocha": "mocha -r ../../test/register-esbuild.js", + "_test-unit": "yarn mocha \"test/unit/**/*.test.[tj]s\"", + "test-integration": "yarn mocha \"test/integration/**/*.test.[tj]s\"", + "test-smoke": "yarn mocha \"test/smoke/**/*.test.[tj]s\" --timeout 600000", + "teaser": "ts-node test/teaser.ts", + "test": "yarn build && yarn test-typings && yarn teaser && yarn _test-unit && yarn test-integration", + "----------------------------------------- coverage ------------------------------------------------": "", + "cover": "rimraf coverage && yarn teaser && yarn cover-integration && yarn cover-unit && yarn merge-coverage", + "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly yarn mocha \"test/integration/**/*.test.[tj]s\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", + "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly yarn mocha \"test/unit/**/*.test.[tj]s\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", + "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", + "----------------------------------------- unit tests ---------------------------------------------": "", + "test-unit-mariadb": "cross-env DIALECT=mariadb yarn _test-unit", + "test-unit-mysql": "cross-env DIALECT=mysql yarn _test-unit", + "test-unit-postgres": "cross-env DIALECT=postgres yarn _test-unit", + "test-unit-sqlite3": "cross-env DIALECT=sqlite3 yarn _test-unit", + "test-unit-mssql": "cross-env DIALECT=mssql yarn _test-unit", + "test-unit-db2": "cross-env DIALECT=db2 yarn _test-unit", + "test-unit-ibmi": "cross-env DIALECT=ibmi yarn _test-unit", + "test-unit-snowflake": "cross-env DIALECT=snowflake yarn _test-unit", + "test-unit-all": "yarn test-unit-mariadb && yarn test-unit-mysql && yarn test-unit-postgres && yarn test-unit-mssql && yarn test-unit-sqlite3 && yarn test-unit-snowflake && yarn test-unit-db2 && yarn test-unit-ibmi", + "test-unit": "yarn test-unit-all", + "----------------------------------------- integration tests ---------------------------------------------": "", + "test-integration-mariadb": "cross-env DIALECT=mariadb yarn test-integration", + "test-integration-mysql": "cross-env DIALECT=mysql yarn test-integration", + "test-integration-postgres": "cross-env DIALECT=postgres yarn test-integration", + "test-integration-postgres-native": "cross-env DIALECT=postgres-native yarn test-integration", + "test-integration-sqlite3": "cross-env DIALECT=sqlite3 yarn test-integration", + "test-integration-mssql": "cross-env DIALECT=mssql yarn test-integration", + "test-integration-db2": "cross-env DIALECT=db2 yarn test-integration", + "test-integration-ibmi": "cross-env DIALECT=ibmi yarn test-integration", + "test-integration-snowflake": "cross-env DIALECT=snowflake yarn test-integration", + "test-integration-all": "yarn test-integration-mariadb && yarn test-integration-mysql && yarn test-integration-postgres && yarn test-integration-postgres-native && yarn test-integration-sqlite3 && yarn test-integration-mssql && yarn test-integration-db2 && yarn test-integration-ibmi && yarn test-integration-snowflake", + "----------------------------------------- all tests ---------------------------------------------": "", + "test-mariadb": "cross-env DIALECT=mariadb yarn test", + "test-mysql": "cross-env DIALECT=mysql yarn test", + "test-sqlite3": "cross-env DIALECT=sqlite3 yarn test", + "test-postgres": "cross-env DIALECT=postgres yarn test", + "test-postgres-native": "cross-env DIALECT=postgres-native yarn test", + "test-mssql": "cross-env DIALECT=mssql yarn test", + "test-db2": "cross-env DIALECT=db2 yarn test", + "test-ibmi": "cross-env DIALECT=ibmi yarn test", + "----------------------------------------- development ---------------------------------------------": "", + "build": "node ../../build-packages.mjs core" + }, + "support": true +} diff --git a/packages/core/src/_model-internals/get-belongs-to-associations-with-target.ts b/packages/core/src/_model-internals/get-belongs-to-associations-with-target.ts new file mode 100644 index 000000000000..2441452e5792 --- /dev/null +++ b/packages/core/src/_model-internals/get-belongs-to-associations-with-target.ts @@ -0,0 +1,22 @@ +import { BelongsToAssociation } from '../associations/index.js'; +import type { ModelStatic } from '../model.js'; + +/** + * Returns all BelongsTo associations in the entire Sequelize instance that target the given model. + * + * @param target + */ +export function getBelongsToAssociationsWithTarget(target: ModelStatic): BelongsToAssociation[] { + const sequelize = target.sequelize; + + const associations: BelongsToAssociation[] = []; + for (const model of sequelize.models) { + for (const association of Object.values(model.associations)) { + if (association instanceof BelongsToAssociation && association.target === target) { + associations.push(association); + } + } + } + + return associations; +} diff --git a/packages/core/src/abstract-dialect/connection-manager.ts b/packages/core/src/abstract-dialect/connection-manager.ts new file mode 100644 index 000000000000..af50bd46064e --- /dev/null +++ b/packages/core/src/abstract-dialect/connection-manager.ts @@ -0,0 +1,74 @@ +import type { AbstractDialect, ConnectionOptions } from './dialect.js'; + +export interface GetConnectionOptions { + /** + * Set which replica to use. Available options are `read` and `write` + */ + type: 'read' | 'write'; + + /** + * Force master or write replica to get connection from + */ + useMaster?: boolean; +} + +export interface AbstractConnection { + /** The UUID of the transaction that is using this connection */ + // TODO: replace with the transaction object itself. + uuid?: string | undefined; +} + +declare const ConnectionType: unique symbol; +export type Connection< + DialectOrConnectionManager extends AbstractDialect | AbstractConnectionManager, +> = DialectOrConnectionManager extends AbstractDialect + ? Connection + : DialectOrConnectionManager extends AbstractConnectionManager + ? DialectOrConnectionManager[typeof ConnectionType] + : never; + +/** + * Abstract Connection Manager + * + * Connection manager which handles pooling & replication. + * Uses sequelize-pool for pooling + * + * @param connection + */ +export class AbstractConnectionManager< + Dialect extends AbstractDialect = AbstractDialect, + TConnection extends AbstractConnection = AbstractConnection, +> { + declare [ConnectionType]: TConnection; + + protected readonly dialect: Dialect; + + constructor(dialect: Dialect) { + this.dialect = dialect; + } + + protected get sequelize() { + return this.dialect.sequelize; + } + + get pool(): never { + throw new Error('The "pool" property has been moved to the Sequelize instance.'); + } + + /** + * Determine if a connection is still valid or not + * + * @param _connection + */ + validate(_connection: TConnection): boolean { + throw new Error(`validate not implemented in ${this.constructor.name}`); + } + + async connect(_config: ConnectionOptions): Promise { + throw new Error(`connect not implemented in ${this.constructor.name}`); + } + + async disconnect(_connection: TConnection): Promise { + throw new Error(`disconnect not implemented in ${this.constructor.name}`); + } +} diff --git a/packages/core/src/abstract-dialect/data-types-utils.ts b/packages/core/src/abstract-dialect/data-types-utils.ts new file mode 100644 index 000000000000..c522f0c88064 --- /dev/null +++ b/packages/core/src/abstract-dialect/data-types-utils.ts @@ -0,0 +1,123 @@ +import NodeUtils from 'node:util'; +import { BaseError, ValidationErrorItem } from '../errors/index.js'; +import type { Model } from '../model.js'; +import type { + DataType, + DataTypeClass, + DataTypeClassOrInstance, + DataTypeInstance, +} from './data-types.js'; +import { AbstractDataType } from './data-types.js'; +import type { AbstractDialect } from './dialect.js'; + +export function isDataType(value: any): value is DataType { + return isDataTypeClass(value) || value instanceof AbstractDataType; +} + +export function isDataTypeClass(value: any): value is DataTypeClass { + return typeof value === 'function' && value.prototype instanceof AbstractDataType; +} + +export function cloneDataType(value: DataTypeInstance | string): DataTypeInstance | string { + if (typeof value === 'string') { + return value; + } + + return value.clone(); +} + +export function normalizeDataType( + Type: DataTypeClassOrInstance, + dialect: AbstractDialect, +): AbstractDataType; +export function normalizeDataType(Type: string, dialect: AbstractDialect): string; +export function normalizeDataType( + Type: DataTypeClassOrInstance | string, + dialect: AbstractDialect, +): AbstractDataType | string; +export function normalizeDataType( + Type: DataTypeClassOrInstance | string, + dialect: AbstractDialect, +): AbstractDataType | string { + if (typeof Type === 'string') { + return Type; + } + + if (typeof Type !== 'function' && !(Type instanceof AbstractDataType)) { + throw new TypeError( + `Expected type to be a string, a DataType class, or a DataType instance, but got ${NodeUtils.inspect(Type)}.`, + ); + } + + const type = dataTypeClassOrInstanceToInstance(Type); + + if (!type.belongsToDialect(dialect)) { + return type.toDialectDataType(dialect); + } + + return type; +} + +export function dataTypeClassOrInstanceToInstance(Type: DataTypeClassOrInstance): DataTypeInstance { + return typeof Type === 'function' ? new Type() : Type; +} + +export function validateDataType( + value: unknown, + type: AbstractDataType, + attributeName: string = '[unnamed]', + modelInstance: Model | null = null, +): ValidationErrorItem | null { + try { + type.validate(value); + + return null; + } catch (error) { + if (!(error instanceof ValidationErrorItem)) { + throw new BaseError( + `Validation encountered an unexpected error while validating attribute ${attributeName}. (Note: If this error is intended, ${type.constructor.name}#validate must throw an instance of ValidationErrorItem instead)`, + { + cause: error, + }, + ); + } + + error.path = attributeName; + error.value = value; + error.instance = modelInstance; + // @ts-expect-error -- untyped constructor + error.validatorKey = `${type.constructor.getDataTypeId()} validator`; + + return error; + } +} + +export function attributeTypeToSql(type: AbstractDataType | string): string { + if (typeof type === 'string') { + return type; + } + + if (type instanceof AbstractDataType) { + return type.toSql(); + } + + throw new Error( + 'attributeTypeToSql received a type that is neither a string or an instance of AbstractDataType', + ); +} + +export function getDataTypeParser( + dialect: AbstractDialect, + dataType: DataTypeClassOrInstance, +): (value: unknown) => unknown { + const type = normalizeDataType(dataType, dialect); + + return (value: unknown) => { + return type.parseDatabaseValue(value); + }; +} + +export function throwUnsupportedDataType(dialect: AbstractDialect, typeName: string): never { + throw new Error(`${dialect.name} does not support the ${typeName} data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); +} diff --git a/packages/core/src/abstract-dialect/data-types.ts b/packages/core/src/abstract-dialect/data-types.ts new file mode 100644 index 000000000000..26975feafa37 --- /dev/null +++ b/packages/core/src/abstract-dialect/data-types.ts @@ -0,0 +1,2867 @@ +import { + EMPTY_ARRAY, + isPlainObject, + isString, + parseBigInt, + parseSafeInteger, +} from '@sequelize/utils'; +import dayjs from 'dayjs'; +import isEqual from 'lodash/isEqual'; +import isObject from 'lodash/isObject'; +import { Blob } from 'node:buffer'; +import util from 'node:util'; +import type { Class } from 'type-fest'; +import { ValidationErrorItem } from '../errors'; +import type { GeoJson, GeoJsonType } from '../geo-json.js'; +import { assertIsGeoJson } from '../geo-json.js'; +import type { ModelStatic, Rangable, RangePart } from '../model.js'; +import type { Sequelize } from '../sequelize.js'; +import { makeBufferFromTypedArray } from '../utils/buffer.js'; +import { isValidTimeZone } from '../utils/dayjs.js'; +import { doNotUseRealDataType } from '../utils/deprecations.js'; +import { joinSQLFragments } from '../utils/join-sql-fragments'; +import { validator as Validator } from '../utils/validator-extras'; +import { + attributeTypeToSql, + dataTypeClassOrInstanceToInstance, + isDataType, + isDataTypeClass, + throwUnsupportedDataType, +} from './data-types-utils.js'; +import type { AbstractDialect } from './dialect.js'; +import type { TableNameWithSchema } from './query-interface.js'; + +// TODO: try merging "validate" & "sanitize" by making sanitize coerces the type, and if it cannot, throw a ValidationError. +// right now, they share a lot of the same logic. + +// legacy support +let Moment: any; +try { + Moment = require('moment'); +} catch { + /* ignore */ +} + +function isMoment(value: any): boolean { + return Moment?.isMoment(value) ?? false; +} + +// If T is a constructor, returns the type of what `new T()` would return, +// otherwise, returns T +export type Constructed = T extends abstract new () => infer Instance ? Instance : T; + +export type AcceptableTypeOf = + Constructed extends AbstractDataType ? Acceptable : never; + +export type DataTypeInstance = AbstractDataType; +export type DataTypeClass = Class>; + +export type DataTypeClassOrInstance = DataTypeInstance | DataTypeClass; + +export type DataType = string | DataTypeClassOrInstance; + +export type NormalizedDataType = string | DataTypeInstance; + +export interface BindParamOptions { + bindParam(value: unknown): string; +} + +export type DataTypeUseContext = + | { model: ModelStatic; attributeName: string; sequelize: Sequelize } + | { tableName: TableNameWithSchema; columnName: string; sequelize: Sequelize }; + +/** + * A symbol that can be used as the key for a static property on a DataType class to uniquely identify it. + */ +export const DataTypeIdentifier = Symbol('DataTypeIdentifier'); + +/** + * @category DataTypes + */ +export abstract class AbstractDataType< + /** The type of value we'll accept - ie for a column of this type, we'll accept this value as user input. */ + AcceptedType, +> { + /** + * This property is designed to uniquely identify the DataType. + * Do not change this value in implementation-specific dialects, or they will not be mapped to their parent DataType properly! + * + * @hidden + */ + declare static readonly [DataTypeIdentifier]: string; + + static getDataTypeId(): string { + return this[DataTypeIdentifier]; + } + + getDataTypeId(): string { + // @ts-expect-error -- untyped constructor + return this.constructor.getDataTypeId(); + } + + /** + * Where this DataType is being used. + */ + usageContext: DataTypeUseContext | undefined; + #dialect: AbstractDialect | undefined; + + protected _getDialect(): AbstractDialect { + if (!this.#dialect) { + throw new Error('toDialectDataType has not yet been called on this DataType'); + } + + return this.#dialect; + } + + // TODO: Remove in v8 + /** + * @hidden + */ + static get escape() { + throw new Error( + 'The "escape" static property has been removed. Each DataType is responsible for escaping its value correctly.', + ); + } + + // TODO: Remove in v8 + /** + * @hidden + */ + static get types() { + throw new Error('The "types" static property has been removed. Use getDataTypeDialectMeta.'); + } + + // TODO: Remove in v8 + /** + * @hidden + */ + static get key() { + throw new Error('The "key" static property has been removed.'); + } + + // TODO: Remove in v8 + /** + * @hidden + */ + get types() { + throw new Error('The "types" instance property has been removed.'); + } + + // TODO: Remove in v8 + /** + * @hidden + */ + get key() { + throw new Error('The "key" instance property has been removed.'); + } + + // TODO: move to utils? + protected _construct AbstractDataType>( + ...args: ConstructorParameters + ): this { + const constructor = this.constructor as new ( + ..._args: ConstructorParameters + ) => this; + + return new constructor(...args); + } + + areValuesEqual(value: AcceptedType, originalValue: AcceptedType): boolean { + return isEqual(value, originalValue); + } + + /** + * Whether this DataType wishes to handle NULL values itself. + * This is almost exclusively used by {@link JSON} and {@link JSONB} which serialize `null` as the JSON string `'null'`. + */ + acceptsNull(): boolean { + return false; + } + + /** + * Called when a value is retrieved from the Database, and its DataType is specified. + * Used to normalize values from the database. + * + * Note: It is also possible to do an initial parsing of a Database value using {@link AbstractDialect#registerDataTypeParser}. + * That normalization uses the type ID from the database instead of a Sequelize Data Type to determine which parser to use, + * and is called before this method. + * + * @param value The value to parse. + */ + parseDatabaseValue(value: unknown): unknown { + return value as AcceptedType; + } + + /** + * Used to normalize a value when {@link Model#set} is called. + * That is, when a user sets a value on a Model instance. + * + * @param value + */ + sanitize(value: unknown): unknown { + return value; + } + + /** + * Checks whether the JS value is compatible with (or can be converted to) the SQL data type. + * Throws if that is not the case. + * + * @param value + */ + validate(value: any): asserts value is AcceptedType {} + + /** + * Escapes a value for the purposes of inlining it in a SQL query. + * The resulting value will be inlined as-is with no further escaping. + * + * @param value The value to escape. + */ + escape(value: AcceptedType): string { + const asBindValue = this.toBindableValue(value); + + if (!isString(asBindValue)) { + throw new Error( + `${this.constructor.name}#stringify has been overridden to return a non-string value, so ${this.constructor.name}#escape must be implemented to handle that value correctly.`, + ); + } + + return this._getDialect().escapeString(asBindValue); + } + + /** + * This method is called when {@link AbstractQueryGenerator} needs to add a bind parameter to a query it is building. + * This method allows for customizing both the SQL to add to the query, and convert the bind parameter value to a DB-compatible value. + * + * If you only need to prepare the bind param value, implement {@link toBindableValue} instead. + * + * This method must return the SQL to add to the query. You can obtain a bind parameter ID by calling {@link BindParamOptions#bindParam} + * with the value associated to that bind parameter. + * + * An example of a data type that requires customizing the SQL is the {@link GEOMETRY} data type. + * + * @param value The value to bind. + * @param options Options. + */ + getBindParamSql(value: AcceptedType, options: BindParamOptions): string { + // TODO: rename "options.bindParam" to "options.collectBindParam" + return options.bindParam(this.toBindableValue(value)); + } + + /** + * Converts a JS value to a value compatible with the connector library for this Data Type. + * Unlike {@link escape}, this value does not need to be escaped. It is passed separately to the database, which + * will handle escaping. + * + * @param value The value to convert. + */ + toBindableValue(value: AcceptedType): unknown { + return String(value); + } + + toString(): string { + try { + return this.toSql(); + } catch { + // best effort introspection (dialect may not be available) + return this.constructor.toString(); + } + } + + static toString() { + return this.name; + } + + /** + * Returns a SQL declaration of this data type. + * e.g. 'VARCHAR(255)', 'TEXT', etc… + */ + abstract toSql(): string; + + /** + * Override this method to emit an error or a warning if the Data Type, as it is configured, is not compatible + * with the current dialect. + * + * @param dialect The dialect using this data type. + */ + protected _checkOptionSupport(dialect: AbstractDialect) { + // use "dialect.supports" to determine base support for this DataType. + assertDataTypeSupported(dialect, this); + } + + belongsToDialect(dialect: AbstractDialect): boolean { + return this.#dialect === dialect; + } + + /** + * Returns this DataType, using its dialect-specific subclass. + * + * @param dialect + */ + toDialectDataType(dialect: AbstractDialect): this { + // This DataType has already been converted to a dialect-specific DataType. + if (this.#dialect === dialect) { + return this; + } + + const DataTypeClass = this.constructor as Class>; + // get dialect-specific implementation + const subClass = dialect.getDataTypeForDialect(DataTypeClass); + + const replacement: this = + !subClass || subClass === DataTypeClass + ? // optimisation: re-use instance if it doesn't belong to any dialect yet. + this.#dialect == null + ? this + : this.clone() + : // there is a convention that all DataTypes must accept a single "options" parameter as one of their signatures, but it's impossible to enforce in typing + // @ts-expect-error -- see ^ + (new subClass(this.options) as this); + + replacement.#dialect = dialect; + replacement._checkOptionSupport(dialect); + if (this.usageContext) { + replacement.attachUsageContext(this.usageContext); + } + + return replacement; + } + + /** + * Returns a copy of this DataType, without usage context. + * Designed to re-use a DataType on another Model. + */ + clone(): this { + // there is a convention that all DataTypes must accept a single "options" parameter as one of their signatures, but it's impossible to enforce in typing + // @ts-expect-error -- see ^ + return this._construct(this.options); + } + + withUsageContext(usageContext: DataTypeUseContext): this { + const out = this.clone().attachUsageContext(usageContext); + + if (this.#dialect) { + out.#dialect = this.#dialect; + } + + return out; + } + + /** + * @param usageContext + * @private + */ + attachUsageContext(usageContext: DataTypeUseContext): this { + if (this.usageContext && !isEqual(this.usageContext, usageContext)) { + throw new Error( + `This DataType is already attached to ${printContext(this.usageContext)}, and therefore cannot be attached to ${printContext(usageContext)}.`, + ); + } + + this.usageContext = Object.freeze(usageContext); + + return this; + } +} + +function printContext(usageContext: DataTypeUseContext): string { + if ('model' in usageContext) { + return `attribute ${usageContext.model.name}#${usageContext.attributeName}`; + } + + return `column "${usageContext.tableName}"."${usageContext.columnName}"`; +} + +export interface StringTypeOptions { + /** + * @default 255 + */ + length?: number | undefined; + + /** + * @default false + */ + binary?: boolean; +} + +/** + * Represents a variable length string type. + * + * __Fallback policy:__ + * - If the 'length' option is not supported by the dialect, a CHECK constraint will be added to ensure + * the value remains within the specified length. + * - If the 'binary' option is not supported by the dialect, a suitable binary type will be used instead. + * If none is available, an error will be raised instead. + * + * @example + * ```ts + * DataTypes.STRING(255) + * ``` + * + * @category DataTypes + */ +export class STRING extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'STRING'; + readonly options: StringTypeOptions; + + constructor(length: number, binary?: boolean); + constructor(options?: StringTypeOptions); + // we have to define the constructor overloads using tuples due to a TypeScript limitation + // https://github.com/microsoft/TypeScript/issues/29732, to play nice with classToInvokable. + /** @hidden */ + constructor( + ...args: + | [] + | [length: number] + | [length: number, binary: boolean] + | [options: StringTypeOptions] + ); + + constructor(lengthOrOptions?: number | StringTypeOptions, binary?: boolean) { + super(); + + if (isObject(lengthOrOptions)) { + this.options = { + length: lengthOrOptions.length, + binary: lengthOrOptions.binary ?? false, + }; + } else { + this.options = { + length: lengthOrOptions, + binary: binary ?? false, + }; + } + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.COLLATE_BINARY && this.options.binary) { + throwUnsupportedDataType(dialect, 'STRING.BINARY'); + } + } + + toSql(): string { + // TODO: STRING should use an unlimited length type by default - https://github.com/sequelize/sequelize/issues/14259 + return joinSQLFragments([ + `VARCHAR(${this.options.length ?? 255})`, + this.options.binary ? 'BINARY' : '', + ]); + } + + validate(value: any): asserts value is string | Buffer { + if (typeof value === 'string') { + return; + } + + if (!this.options.binary) { + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid string. Only the string type is accepted for non-binary strings.`, + ); + } + + rejectBlobs(value); + + if (Buffer.isBuffer(value)) { + return; + } + + if (value instanceof Uint8Array || value instanceof ArrayBuffer) { + return; + } + + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid binary value: Only strings, Buffer, Uint8Array and ArrayBuffer are supported.`, + ); + } + + get BINARY() { + return this._construct({ + ...this.options, + binary: true, + }); + } + + static get BINARY() { + return new this({ binary: true }); + } + + escape(value: string | Buffer): string { + if (Buffer.isBuffer(value)) { + return this._getDialect().escapeBuffer(value); + } + + return this._getDialect().escapeString(value); + } + + toBindableValue(value: string | Buffer): unknown { + return this.sanitize(value); + } +} + +/** + * Represents a fixed length string type. + * + * __Fallback policy:__ + * - If this DataType is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.CHAR(1000) + * ``` + * + * @category DataTypes + */ +export class CHAR extends STRING { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'CHAR'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.CHAR) { + throwUnsupportedDataType(dialect, 'CHAR'); + } + + if (!dialect.supports.dataTypes.COLLATE_BINARY && this.options.binary) { + throwUnsupportedDataType(dialect, 'CHAR.BINARY'); + } + } + + toSql() { + return joinSQLFragments([ + `CHAR(${this.options.length ?? 255})`, + this.options.binary ? 'BINARY' : '', + ]); + } +} + +const validTextLengths = ['tiny', 'medium', 'long']; +export type TextLength = 'tiny' | 'medium' | 'long'; + +export interface TextOptions { + length?: TextLength | undefined; +} + +/** + * Represents an unlimited length string type. + * + * @example + * ```ts + * DataTypes.TEXT('tiny') // TINYTEXT + * ``` + * + * @category DataTypes + */ +export class TEXT extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'TEXT'; + readonly options: TextOptions; + + /** + * @param lengthOrOptions could be tiny, medium, long. + */ + constructor(lengthOrOptions?: TextLength | TextOptions) { + super(); + + const length = ( + typeof lengthOrOptions === 'object' ? lengthOrOptions.length : lengthOrOptions + )?.toLowerCase(); + + if (length != null && !validTextLengths.includes(length)) { + throw new TypeError( + `If specified, the "length" option must be one of: ${validTextLengths.join(', ')}`, + ); + } + + this.options = { + length: length as TextLength, + }; + } + + toSql(): string { + switch (this.options.length) { + case 'tiny': + return 'TINYTEXT'; + case 'medium': + return 'MEDIUMTEXT'; + case 'long': + return 'LONGTEXT'; + default: + return 'TEXT'; + } + } + + validate(value: any): asserts value is string { + if (typeof value !== 'string') { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%s is not a valid string', value), + ); + } + } +} + +/** + * An unlimited length case-insensitive text column. + * Original case is preserved but acts case-insensitive when comparing values (such as when finding or unique constraints). + * Only available in Postgres and SQLite. + * + * __Fallback policy:__ + * - If this DataType is not supported, and no case-insensitive text alternative exists, an error will be raised. + * + * @example + * ```ts + * DataTypes.CITEXT + * ``` + * + * @category DataTypes + */ +export class CITEXT extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'CITEXT'; + + toSql(): string { + return 'CITEXT'; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.CITEXT) { + throwUnsupportedDataType(dialect, 'case-insensitive text (CITEXT)'); + } + } + + validate(value: any): asserts value is string { + if (typeof value !== 'string') { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%s is not a valid string', value), + ); + } + } +} + +export interface NumberOptions { + /** + * Pad the value with zeros to the specified length. + * + * Currently useless for types that are returned as JS BigInts or JS Numbers. + */ + // TODO: When a number is 0-filled, return it as a string instead of number or bigint + zerofill?: boolean | undefined; + + /** + * Is unsigned? + */ + unsigned?: boolean | undefined; +} + +export interface IntegerOptions extends NumberOptions { + /** + * In MariaDB: When specified, and {@link zerofill} is set, the returned value will be padded with zeros to the specified length. + * In MySQL: This option is ignored. + * This option is supported in no other dialect. + * Currently useless for types that are returned as JS BigInts or JS Numbers. + */ + length?: number; +} + +export interface DecimalNumberOptions extends NumberOptions { + /** + * Total number of digits. + * + * {@link DecimalNumberOptions#scale} must be specified if precision is specified. + */ + precision?: number | undefined; + + /** + * Count of decimal digits in the fractional part. + * + * {@link DecimalNumberOptions#precision} must be specified if scale is specified. + */ + scale?: number | undefined; +} + +type AcceptedNumber = number | bigint | boolean | string | null; + +/** + * Base number type which is used to build other types + */ +export class BaseNumberDataType< + Options extends NumberOptions = NumberOptions, +> extends AbstractDataType { + readonly options: Options; + + constructor(options?: Options) { + super(); + + // @ts-expect-error -- "options" is always optional, but we can't tell TypeScript that all properties of the object must be optional + this.options = { ...options }; + } + + protected getNumberSqlTypeName(): string { + throw new Error(`getNumberSqlTypeName has not been implemented in ${this.constructor.name}`); + } + + toSql(): string { + let result: string = this.getNumberSqlTypeName(); + + if (this.options.unsigned && this._supportsNativeUnsigned(this._getDialect())) { + result += ' UNSIGNED'; + } + + if (this.options.zerofill) { + result += ' ZEROFILL'; + } + + return result; + } + + protected _supportsNativeUnsigned(_dialect: AbstractDialect) { + return false; + } + + validate(value: any): asserts value is number { + if (typeof value === 'number' && Number.isInteger(value) && !Number.isSafeInteger(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format( + `${this.constructor.name} received an integer % that is not a safely represented using the JavaScript number type. Use a JavaScript bigint or a string instead.`, + value, + ), + ); + } + + if (!Validator.isFloat(String(value))) { + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid ${this.toString().toLowerCase()}`, + ); + } + } + + escape(value: AcceptedNumber): string { + return String(this.toBindableValue(value)); + } + + toBindableValue(num: AcceptedNumber): string | number { + // This should be unnecessary but since this directly returns the passed string its worth the added validation. + this.validate(num); + + if (Number.isNaN(num)) { + return 'NaN'; + } + + if (num === Number.NEGATIVE_INFINITY || num === Number.POSITIVE_INFINITY) { + const sign = num < 0 ? '-' : ''; + + return `${sign}Infinity`; + } + + return num; + } + + getBindParamSql(value: AcceptedNumber, options: BindParamOptions): string { + return options.bindParam(value); + } + + get UNSIGNED(): this { + return this._construct({ ...this.options, unsigned: true }); + } + + get ZEROFILL(): this { + return this._construct({ ...this.options, zerofill: true }); + } + + static get UNSIGNED() { + return new this({ unsigned: true }); + } + + static get ZEROFILL() { + return new this({ zerofill: true }); + } +} + +export class BaseIntegerDataType extends BaseNumberDataType { + constructor(optionsOrLength?: number | Readonly) { + if (typeof optionsOrLength === 'number') { + super({ length: optionsOrLength }); + } else { + super(optionsOrLength ?? {}); + } + } + + validate(value: unknown) { + super.validate(value); + + if (typeof value === 'number' && !Number.isInteger(value)) { + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid ${this.toString().toLowerCase()}`, + ); + } + + if (!Validator.isInt(String(value))) { + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid ${this.toString().toLowerCase()}`, + ); + } + } + + sanitize(value: unknown): unknown { + if (typeof value === 'string' || typeof value === 'bigint') { + const out = parseSafeInteger(value); + + // let validate sort this validation instead + if (out === null) { + return value; + } + + return out; + } + + return value; + } + + parseDatabaseValue(value: unknown): unknown { + return this.sanitize(value); + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.zerofill && !dialect.supports.dataTypes.INTS.zerofill) { + throwUnsupportedDataType(dialect, `${this.getDataTypeId()}.ZEROFILL`); + } + } + + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + return _dialect.supports.dataTypes.INTS.unsigned; + } + + toSql(): string { + let result: string = this.getNumberSqlTypeName(); + if (this.options.length != null) { + result += `(${this.options.length})`; + } + + if (this.options.unsigned && this._supportsNativeUnsigned(this._getDialect())) { + result += ' UNSIGNED'; + } + + if (this.options.zerofill) { + result += ' ZEROFILL'; + } + + return result; + } +} + +/** + * An 8-bit integer. + * + * __Fallback policy:__ + * - If this type or its unsigned option is unsupported by the dialect, it will be replaced by a SMALLINT or greater, + * with a CHECK constraint to ensure the value is withing the bounds of an 8-bit integer. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the length option is unsupported by the dialect, it will be discarded. + * + * @example + * ```ts + * DataTypes.TINYINT + * ``` + * + * @category DataTypes + */ +export class TINYINT extends BaseIntegerDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'TINYINT'; + + protected getNumberSqlTypeName(): string { + return 'TINYINT'; + } +} + +/** + * A 16-bit integer. + * + * __Fallback policy:__ + * - If this type or its unsigned option is unsupported by the dialect, it will be replaced by a MEDIUMINT or greater, + * with a CHECK constraint to ensure the value is withing the bounds of an 16-bit integer. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the length option is unsupported by the dialect, it will be discarded. + * + * @example + * ```ts + * DataTypes.SMALLINT + * ``` + * + * @category DataTypes + */ +export class SMALLINT extends BaseIntegerDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'SMALLINT'; + + protected getNumberSqlTypeName(): string { + return 'SMALLINT'; + } +} + +/** + * A 24-bit integer. + * + * __Fallback policy:__ + * - If this type or its unsigned option is unsupported by the dialect, it will be replaced by a INTEGER (32 bits) or greater, + * with a CHECK constraint to ensure the value is withing the bounds of an 32-bit integer. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the length option is unsupported by the dialect, it will be discarded. + * + * @example + * ```ts + * DataTypes.MEDIUMINT + * ``` + * + * @category DataTypes + */ +export class MEDIUMINT extends BaseIntegerDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'MEDIUMINT'; + + protected getNumberSqlTypeName(): string { + return 'MEDIUMINT'; + } +} + +/** + * A 32-bit integer. + * + * __Fallback policy:__ + * - When this type or its unsigned option is unsupported by the dialect, it will be replaced by a BIGINT, + * with a CHECK constraint to ensure the value is withing the bounds of an 32-bit integer. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the length option is unsupported by the dialect, it will be discarded. + * + * @example + * ```ts + * DataTypes.INTEGER + * ``` + * + * @category DataTypes + */ +export class INTEGER extends BaseIntegerDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'INTEGER'; + + protected getNumberSqlTypeName(): string { + return 'INTEGER'; + } +} + +/** + * A 64-bit integer. + * + * __Fallback policy:__ + * - If this type or its unsigned option is unsupported by the dialect, an error will be raised. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the length option is unsupported by the dialect, it will be discarded. + * + * @example + * ```ts + * DataTypes.BIGINT + * ``` + * + * @category DataTypes + */ +export class BIGINT extends BaseIntegerDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'BIGINT'; + + protected getNumberSqlTypeName(): string { + return 'BIGINT'; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (!dialect.supports.dataTypes.BIGINT) { + throwUnsupportedDataType(dialect, 'BIGINT'); + } + + if (this.options.unsigned && !this._supportsNativeUnsigned(dialect)) { + throwUnsupportedDataType(dialect, `${this.getDataTypeId()}.UNSIGNED`); + } + } + + sanitize(value: AcceptedNumber): AcceptedNumber { + if (typeof value === 'bigint') { + return value; + } + + if (typeof value !== 'string' && typeof value !== 'number') { + // let validate() handle this instead + return value; + } + + // TODO: Breaking Change: Return a BigInt by default - https://github.com/sequelize/sequelize/issues/14296 + return String(parseBigInt(value)); + } +} + +export class BaseDecimalNumberDataType extends BaseNumberDataType { + constructor(options?: DecimalNumberOptions); + /** + * @param precision defines precision + * @param scale defines scale + */ + constructor(precision: number, scale: number); + + // we have to define the constructor overloads using tuples due to a TypeScript limitation + // https://github.com/microsoft/TypeScript/issues/29732, to play nice with classToInvokable. + /** @hidden */ + constructor( + ...args: + | [] + | [precision: number] + | [precision: number, scale: number] + | [options: DecimalNumberOptions] + ); + + constructor(precisionOrOptions?: number | DecimalNumberOptions, scale?: number) { + if (isObject(precisionOrOptions)) { + super(precisionOrOptions); + } else { + super({}); + + this.options.precision = precisionOrOptions; + this.options.scale = scale; + } + + if (this.options.scale != null && this.options.precision == null) { + throw new Error( + `The ${this.getDataTypeId()} DataType requires that the "precision" option be specified if the "scale" option is specified.`, + ); + } + + if (this.options.scale == null && this.options.precision != null) { + throw new Error( + `The ${this.getDataTypeId()} DataType requires that the "scale" option be specified if the "precision" option is specified.`, + ); + } + } + + validate(value: any): asserts value is AcceptedNumber { + if (Number.isNaN(value)) { + const typeId = this.getDataTypeId(); + const dialect = this._getDialect(); + + // @ts-expect-error -- 'typeId' is string, but only some dataTypes are objects + if (dialect.supports.dataTypes[typeId]?.NaN) { + return; + } + + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid ${this.toString().toLowerCase()}`, + ); + } + + if (value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) { + const typeId = this.getDataTypeId(); + const dialect = this._getDialect(); + + // @ts-expect-error -- 'typeId' is string, but only some dataTypes are objects + if (dialect.supports.dataTypes[typeId]?.infinity) { + return; + } + + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid ${this.toString().toLowerCase()}`, + ); + } + + super.validate(value); + } + + isUnconstrained() { + return this.options.scale == null && this.options.precision == null; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + const typeId = this.getDataTypeId(); + if (typeId !== 'FLOAT' && typeId !== 'DOUBLE' && typeId !== 'DECIMAL' && typeId !== 'REAL') { + return; + } + + const supportTable = dialect.supports.dataTypes[typeId]; + if (!supportTable) { + throwUnsupportedDataType(dialect, this.getDataTypeId()); + } + + if (!supportTable.zerofill && this.options.zerofill) { + throwUnsupportedDataType(dialect, `${this.getDataTypeId()}.ZEROFILL`); + } + + if (typeId === 'DECIMAL') { + return; + } + + const supportTable2 = dialect.supports.dataTypes[typeId]; + if ( + !supportTable2.scaleAndPrecision && + (this.options.scale != null || this.options.precision != null) + ) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support ${this.getDataTypeId()} with scale or precision specified. These options are ignored.`, + ); + + delete this.options.scale; + delete this.options.precision; + } + } + + toSql(): string { + let sql = this.getNumberSqlTypeName(); + if (!this.isUnconstrained()) { + sql += `(${this.options.precision}, ${this.options.scale})`; + } + + if (this.options.unsigned && this._supportsNativeUnsigned(this._getDialect())) { + sql += ' UNSIGNED'; + } + + if (this.options.zerofill) { + sql += ' ZEROFILL'; + } + + return sql; + } +} + +/** + * A single-floating point number with a 4-byte precision. + * If single-precision floating-point format is not supported, a double-precision floating-point number may be used instead. + * + * __Fallback policy:__ + * - If the precision or scale options are unsupported by the dialect, they will be discarded. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the unsigned option is unsupported, it will be replaced by a CHECK > 0 constraint. + * + * @example + * ```ts + * DataTypes.FLOAT + * ``` + * + * @category DataTypes + */ +export class FLOAT extends BaseDecimalNumberDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'FLOAT'; + + protected getNumberSqlTypeName(): string { + throw new Error(`getNumberSqlTypeName is not implemented by default in the FLOAT DataType because 'float' has very different meanings in different dialects. +In Sequelize, DataTypes.FLOAT must be a single-precision floating point, and DataTypes.DOUBLE must be a double-precision floating point. +Please override this method in your dialect, and provide the best available type for single-precision floating points. +If single-precision floating points are not available in your dialect, you may return a double-precision floating point type instead, as long as you print a warning. +If neither single precision nor double precision IEEE 754 floating point numbers are available in your dialect, you must throw an error in the _checkOptionSupport method.`); + } + + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + return _dialect.supports.dataTypes.FLOAT.unsigned; + } +} + +/** + * @deprecated Use {@link FLOAT} instead. + */ +// TODO (v8): remove this +export class REAL extends BaseDecimalNumberDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'REAL'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + doNotUseRealDataType(); + } + + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + return _dialect.supports.dataTypes.REAL.unsigned; + } + + protected getNumberSqlTypeName(): string { + return 'REAL'; + } +} + +/** + * Floating point number (8-byte precision). + * Throws an error when unsupported, instead of silently falling back to a lower precision. + * + * __Fallback policy:__ + * - If the precision or scale options are unsupported by the dialect, they will be discarded. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the unsigned option is unsupported, it will be replaced by a CHECK > 0 constraint. + * + * @example + * ```ts + * DataTypes.DOUBLE + * ``` + * + * @category DataTypes + */ +export class DOUBLE extends BaseDecimalNumberDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'DOUBLE'; + + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + return _dialect.supports.dataTypes.DOUBLE.unsigned; + } + + protected getNumberSqlTypeName(): string { + return 'DOUBLE PRECISION'; + } +} + +/** + * Arbitrary/exact precision decimal number. + * + * __Fallback policy:__ + * - If the precision or scale options are unsupported by the dialect, they will be ignored. + * - If the precision or scale options are not specified, and the dialect does not support unconstrained decimals, an error will be raised. + * - If the zerofill option is unsupported by the dialect, an error will be raised. + * - If the unsigned option is unsupported, it will be replaced by a CHECK > 0 constraint. + * + * @example + * ```ts + * DataTypes.DECIMAL + * ``` + * + * @category DataTypes + */ +export class DECIMAL extends BaseDecimalNumberDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'DECIMAL'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + const decimalSupport = dialect.supports.dataTypes.DECIMAL; + + if (!decimalSupport) { + throwUnsupportedDataType(dialect, 'DECIMAL'); + } + + if (this.isUnconstrained() && !decimalSupport.unconstrained) { + throw new Error( + `${dialect.name} does not support unconstrained DECIMAL types. Please specify the "precision" and "scale" options.`, + ); + } + + if (!this.isUnconstrained() && !decimalSupport.constrained) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support constrained DECIMAL types. The "precision" and "scale" options will be ignored.`, + ); + this.options.scale = undefined; + this.options.precision = undefined; + } + } + + sanitize(value: AcceptedNumber): AcceptedNumber { + if (typeof value === 'number') { + // Some dialects support NaN + if (Number.isNaN(value)) { + return value; + } + + // catch loss of precision issues + if (Number.isInteger(value) && !Number.isSafeInteger(value)) { + throw new Error( + `${this.getDataTypeId()} received an integer ${util.inspect(value)} that is not a safely represented using the JavaScript number type. Use a JavaScript bigint or a string instead.`, + ); + } + } + + // Decimal is arbitrary precision, and *must* be represented as strings, as the JS number type does not support arbitrary precision. + return String(value); + } + + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + const decimalSupport = _dialect.supports.dataTypes.DECIMAL; + + return decimalSupport && decimalSupport.unsigned; + } + + protected getNumberSqlTypeName(): string { + return 'DECIMAL'; + } +} + +/** + * A boolean / tinyint column, depending on dialect + * + * __Fallback policy:__ + * - If a native boolean type is not available, a dialect-specific numeric replacement (bit, tinyint) will be used instead. + * + * @example + * ```ts + * DataTypes.BOOLEAN + * ``` + * + * @category DataTypes + */ +export class BOOLEAN extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'BOOLEAN'; + + toSql() { + // Note: This may vary depending on the dialect. + return 'BOOLEAN'; + } + + validate(value: any): asserts value is boolean { + if (typeof value !== 'boolean') { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid boolean', value), + ); + } + } + + parseDatabaseValue(value: unknown): boolean { + if (typeof value === 'boolean') { + return value; + } + + // Some dialects do not have a dedicated boolean type. We receive integers instead. + if (value === 1) { + return true; + } + + if (value === 0) { + return false; + } + + // Some dialects also use BIT for booleans, which produces a Buffer. + if (Buffer.isBuffer(value) && value.length === 1) { + if (value[0] === 1) { + return true; + } + + if (value[0] === 0) { + return false; + } + } + + throw new Error(`Received invalid boolean value from DB: ${util.inspect(value)}`); + } + + escape(value: boolean | unknown): string { + return value ? 'true' : 'false'; + } + + toBindableValue(value: boolean | unknown): unknown { + return Boolean(value); + } +} + +export interface TimeOptions { + /** + * The precision of the date. + */ + precision?: number | undefined; +} + +/** + * A time column. + * + * __Fallback policy:__ + * If the dialect does not support this type natively, it will be replaced by a string type, + * and a CHECK constraint to enforce a valid ISO 8601 time format. + * + * @example + * ```ts + * DataTypes.TIME(3) + * ``` + * + * @category DataTypes + */ +export class TIME extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'TIME'; + readonly options: TimeOptions; + + /** + * @param precisionOrOptions precision to allow storing milliseconds + */ + constructor(precisionOrOptions?: number | TimeOptions) { + super(); + + this.options = { + precision: + typeof precisionOrOptions === 'object' ? precisionOrOptions.precision : precisionOrOptions, + }; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.precision != null && !dialect.supports.dataTypes.TIME.precision) { + throwUnsupportedDataType(dialect, 'TIME(precision)'); + } + } + + toSql() { + if (this.options.precision != null) { + return `TIME(${this.options.precision})`; + } + + return 'TIME'; + } +} + +export interface DateOptions { + /** + * The precision of the date. + */ + precision?: number | undefined; +} + +type RawDate = Date | string | number; +export type AcceptedDate = RawDate | dayjs.Dayjs | number; + +/** + * A date and time. + * + * __Fallback policy:__ + * If the dialect does not support this type natively, it will be replaced by a string type, + * and a CHECK constraint to enforce a valid ISO 8601 date-only format. + * + * @example + * ```ts + * DataTypes.DATE(3) + * ``` + * + * @category DataTypes + */ +export class DATE extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'DATE'; + readonly options: DateOptions; + + /** + * @param precisionOrOptions precision to allow storing milliseconds + */ + constructor(precisionOrOptions?: number | DateOptions) { + super(); + + this.options = { + precision: + typeof precisionOrOptions === 'object' ? precisionOrOptions.precision : precisionOrOptions, + }; + + if ( + this.options.precision != null && + (this.options.precision < 0 || !Number.isInteger(this.options.precision)) + ) { + throw new TypeError('Option "precision" must be a positive integer'); + } + } + + toSql() { + // TODO [>=8]: Consider making precision default to 3 instead of being dialect-dependent. + if (this.options.precision != null) { + return `DATETIME(${this.options.precision})`; + } + + return 'DATETIME'; + } + + validate(value: any) { + if (!Validator.isDate(String(value))) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid date', value), + ); + } + } + + sanitize(value: unknown): unknown { + if (value instanceof Date || dayjs.isDayjs(value) || isMoment(value)) { + return value; + } + + if (typeof value === 'string' || typeof value === 'number') { + return new Date(value); + } + + throw new TypeError( + `${util.inspect(value)} cannot be converted to a Date object, and is not a DayJS nor Moment object`, + ); + } + + parseDatabaseValue(value: unknown): unknown { + return this.sanitize(value); + } + + areValuesEqual(value: AcceptedDate, originalValue: AcceptedDate): boolean { + if ( + originalValue && + Boolean(value) && + (value === originalValue || + (value instanceof Date && + originalValue instanceof Date && + value.getTime() === originalValue.getTime())) + ) { + return true; + } + + // not changed when set to same empty value + if (!originalValue && !value && originalValue === value) { + return true; + } + + return false; + } + + protected _applyTimezone(date: AcceptedDate) { + const timezone = this._getDialect().sequelize.options.timezone; + + if (timezone) { + if (isValidTimeZone(timezone)) { + return dayjs(date).tz(timezone); + } + + return dayjs(date).utcOffset(timezone); + } + + return dayjs(date); + } + + toBindableValue(date: AcceptedDate) { + // Z here means current timezone, _not_ UTC + return this._applyTimezone(date).format('YYYY-MM-DD HH:mm:ss.SSS Z'); + } +} + +/** + * A date only column (no timestamp) + * + * __Fallback policy:__ + * If the dialect does not support this type natively, it will be replaced by a string type, + * and a CHECK constraint to enforce a valid ISO 8601 datetime format. + * + * @example + * ```ts + * DataTypes.DATEONLY + * ``` + * + * @category DataTypes + */ +export class DATEONLY extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'DATEONLY'; + + toSql() { + return 'DATE'; + } + + toBindableValue(date: AcceptedDate) { + return dayjs.utc(date).format('YYYY-MM-DD'); + } + + sanitize(value: unknown): unknown { + if (typeof value !== 'string' && typeof value !== 'number' && !(value instanceof Date)) { + throw new TypeError(`${value} cannot be normalized into a DateOnly string.`); + } + + if (value) { + return dayjs.utc(value).format('YYYY-MM-DD'); + } + + return value; + } + + areValuesEqual(value: AcceptedDate, originalValue: AcceptedDate): boolean { + if (originalValue && Boolean(value) && originalValue === value) { + return true; + } + + // not changed when set to same empty value + if (!originalValue && !value && originalValue === value) { + return true; + } + + return false; + } +} + +export type HstoreRecord = Record; + +/** + * A key / value store column. Only available in Postgres. + * + * __Fallback policy:__ + * If the dialect does not support this type natively, an error will be raised. + * + * @example + * ```ts + * DataTypes.HSTORE + * ``` + * + * @category DataTypes + */ +export class HSTORE extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'HSTORE'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + if (!dialect.supports.dataTypes.HSTORE) { + throwUnsupportedDataType(dialect, 'HSTORE'); + } + } + + validate(value: any) { + if (!isPlainObject(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid hstore, it must be a plain object', value), + ); + } + + const hstore = value as Record; + + for (const key of Object.keys(hstore)) { + if (!isString(hstore[key])) { + ValidationErrorItem.throwDataTypeValidationError( + util.format( + `%O is not a valid hstore, its values must be strings but ${key} is %O`, + hstore, + hstore[key], + ), + ); + } + } + } + + toSql(): string { + return 'HSTORE'; + } +} + +/** + * A JSON string column. + * + * __Fallback policy:__ + * If the dialect does not support this type natively, but supports verifying a string as is valid JSON through CHECK constraints, + * that will be used instead. + * If neither are available, an error will be raised. + * + * @example + * ```ts + * DataTypes.JSON + * ``` + * + * @category DataTypes + */ +export class JSON extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'JSON'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + if (!dialect.supports.dataTypes.JSON) { + throwUnsupportedDataType(dialect, 'JSON'); + } + } + + /** + * We stringify null too. + */ + acceptsNull(): boolean { + const sequelize = this._getDialect().sequelize; + + return sequelize.options.nullJsonStringification !== 'sql'; + } + + toBindableValue(value: any): string { + if (value === null) { + const sequelize = this._getDialect().sequelize; + + const isExplicit = sequelize.options.nullJsonStringification === 'explicit'; + if (isExplicit) { + throw new Error( + `Attempted to insert the JavaScript null into a JSON column, but the "nullJsonStringification" option is set to "explicit", so Sequelize cannot decide whether to use the SQL NULL or the JSON 'null'. Use the SQL_NULL or JSON_NULL variable instead, or set the option to a different value. See https://sequelize.org/docs/v7/querying/json/ for details.`, + ); + } + } + + return globalThis.JSON.stringify(value); + } + + toSql(): string { + return 'JSON'; + } +} + +/** + * A binary storage JSON column. Only available in Postgres. + * + * __Fallback policy:__ + * If the dialect does not support this type natively, an error will be raised. + * + * @example + * ```ts + * DataTypes.JSONB + * ``` + * + * @category DataTypes + */ +export class JSONB extends JSON { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'JSONB'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + if (!dialect.supports.dataTypes.JSONB) { + throwUnsupportedDataType(dialect, 'JSONB'); + } + } + + toSql(): string { + return 'JSONB'; + } +} + +/** + * A default value of the current timestamp. Not a valid type. + * + * @example + * ```ts + * const User = sequelize.define('User', { + * registeredAt: { + * type: DataTypes.DATE, + * defaultValue: DataTypes.NOW, + * }, + * }); + * ``` + * + * @category DataTypes + */ +// TODO: this should not be a DataType. Replace with a new version of `fn` that is dialect-aware, so we don't need to hardcode it in toDefaultValue(). +export class NOW extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'NOW'; + + toSql(): string { + return 'NOW'; + } +} + +export type AcceptedBlob = Buffer | string; + +export type BlobLength = 'tiny' | 'medium' | 'long'; + +export interface BlobOptions { + // TODO: must also allow BLOB(255), BLOB(16M) in db2/ibmi + length?: BlobLength | undefined; +} + +/** + * Binary storage. BLOB is the "TEXT" of binary data: it allows data of arbitrary size. + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * const User = sequelize.define('User', { + * profilePicture: { + * type: DataTypes.BLOB, + * }, + * }); + * ``` + * + * @category DataTypes + */ +// TODO: add FIXED_BINARY & VAR_BINARY data types. They are not the same as CHAR BINARY / VARCHAR BINARY. +export class BLOB extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'BLOB'; + readonly options: BlobOptions; + + /** + * @param lengthOrOptions could be tiny, medium, long. + */ + constructor(lengthOrOptions?: BlobLength | BlobOptions) { + super(); + + // TODO: valide input (tiny, medium, long, number, 16M, 2G, etc) + + this.options = { + length: typeof lengthOrOptions === 'object' ? lengthOrOptions.length : lengthOrOptions, + }; + } + + toSql(): string { + switch (this.options.length) { + case 'tiny': + return 'TINYBLOB'; + case 'medium': + return 'MEDIUMBLOB'; + case 'long': + return 'LONGBLOB'; + default: + return 'BLOB'; + } + } + + validate(value: any) { + if ( + Buffer.isBuffer(value) || + typeof value === 'string' || + value instanceof Uint8Array || + value instanceof ArrayBuffer + ) { + return; + } + + rejectBlobs(value); + + ValidationErrorItem.throwDataTypeValidationError( + `${util.inspect(value)} is not a valid binary value: Only strings, Buffer, Uint8Array and ArrayBuffer are supported.`, + ); + } + + sanitize(value: unknown): unknown { + if (value instanceof Uint8Array || value instanceof ArrayBuffer) { + return makeBufferFromTypedArray(value); + } + + if (typeof value === 'string') { + return Buffer.from(value); + } + + return value; + } + + escape(value: string | Buffer) { + const buf = typeof value === 'string' ? Buffer.from(value, 'binary') : value; + + return this._getDialect().escapeBuffer(buf); + } + + getBindParamSql(value: AcceptedBlob, options: BindParamOptions) { + return options.bindParam(value); + } +} + +export interface RangeOptions { + subtype?: DataTypeClassOrInstance; +} + +/** + * Range types are data types representing a range of values of some element type (called the range's subtype). + * Only available in Postgres. See [the Postgres documentation](http://www.postgresql.org/docs/9.4/static/rangetypes.html) for more details + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * // A range of integers + * DataTypes.RANGE(DataTypes.INTEGER) + * // A range of bigints + * DataTypes.RANGE(DataTypes.BIGINT) + * // A range of decimals + * DataTypes.RANGE(DataTypes.DECIMAL) + * // A range of timestamps + * DataTypes.RANGE(DataTypes.DATE) + * // A range of dates + * DataTypes.RANGE(DataTypes.DATEONLY) + * ``` + * + * @category DataTypes + */ +export class RANGE< + T extends BaseNumberDataType | DATE | DATEONLY = INTEGER, +> extends AbstractDataType> | AcceptableTypeOf> { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'RANGE'; + readonly options: { + subtype: AbstractDataType; + }; + + /** + * @param subtypeOrOptions A subtype for range, like RANGE(DATE) + */ + constructor(subtypeOrOptions: DataTypeClassOrInstance | RangeOptions) { + super(); + + const subtypeRaw = + (isDataType(subtypeOrOptions) ? subtypeOrOptions : subtypeOrOptions?.subtype) ?? + new INTEGER(); + + const subtype: DataTypeInstance = isDataTypeClass(subtypeRaw) ? new subtypeRaw() : subtypeRaw; + + this.options = { + subtype, + }; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (!dialect.supports.dataTypes.RANGE) { + throwUnsupportedDataType(dialect, 'RANGE'); + } + } + + toDialectDataType(dialect: AbstractDialect): this { + let replacement = super.toDialectDataType(dialect); + + if (replacement === this) { + replacement = replacement.clone(); + } + + replacement.options.subtype = replacement.options.subtype.toDialectDataType(dialect); + + return replacement; + } + + parseDatabaseValue(_value: unknown): unknown { + throw new Error(`DataTypes.RANGE is not supported in ${this._getDialect().name}.`); + } + + sanitize(value: unknown): unknown { + if (!Array.isArray(value)) { + return value; + } + + // this is the "empty" range, which is not the same value as "(,)" (represented by [null, null]) + if (value.length === 0) { + return value; + } + + let [low, high] = value; + if (!isPlainObject(low)) { + low = { value: low ?? null, inclusive: true }; + } + + if (!isPlainObject(high)) { + high = { value: high ?? null, inclusive: false }; + } + + return [this.#sanitizeSide(low), this.#sanitizeSide(high)]; + } + + #sanitizeSide(rangePart: RangePart) { + if (rangePart.value == null) { + return rangePart; + } + + return { ...rangePart, value: this.options.subtype.sanitize(rangePart.value) }; + } + + validate(value: any) { + if (!Array.isArray(value) || (value.length !== 2 && value.length !== 0)) { + ValidationErrorItem.throwDataTypeValidationError( + `A range must either be an array with two elements, or an empty array for the empty range. Got ${util.inspect(value)}.`, + ); + } + } + + toSql(): string { + throw new Error('RANGE has not been implemented in this dialect.'); + } +} + +export interface UuidOptions { + version: 1 | 4 | 'all'; +} + +/** + * A column storing a unique universal identifier. + * Use with `sql.uuidV1` or `sql.uuidV4` for default values. + * + * __Fallback policy:__ + * If this type is not supported, it will be replaced by a string type with a CHECK constraint to enforce a GUID format. + * + * @example + * ```ts + * const User = sequelize.define('User', { + * id: { + * type: DataTypes.UUID.V4, + * defaultValue: sql.uuidV4, + * }, + * }); + * ``` + * + * @category DataTypes + */ +export class UUID extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'UUID'; + + readonly options: UuidOptions; + + constructor(options?: Partial) { + super(); + + this.options = { + version: options?.version ?? 'all', + }; + } + + get V4() { + return this._construct({ + ...this.options, + version: 4, + }); + } + + static get V4() { + return new this({ version: 4 }); + } + + get V1() { + return this._construct({ + ...this.options, + version: 1, + }); + } + + static get V1() { + return new this({ version: 1 }); + } + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isUUID(value, this.options.version)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format(`%O is not a valid uuid (version: ${this.options.version})`, value), + ); + } + } + + toSql(): string { + return 'UUID'; + } +} + +/** + * A default unique universal identifier generated following the UUID v1 standard. + * Cannot be used as a type, must be used as a default value instead. + * + * @category DataTypes + * @deprecated use `DataTypes.UUID.V1` (data type) & `sql.uuidV1` (default value) instead + */ +export class UUIDV1 extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'UUIDV1'; + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isUUID(value, 1)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid uuidv1', value), + ); + } + } + + toSql(): string { + throw new Error('toSQL should not be called on DataTypes.UUIDV1'); + } +} + +/** + * A default unique universal identifier generated following the UUID v4 standard. + * Cannot be used as a type, must be used as a default value instead. + * + * @category DataTypes + * @deprecated use `DataTypes.UUID.V4` (data type) & `sql.uuidV4` (default value) instead + */ +export class UUIDV4 extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'UUIDV4'; + + validate(value: unknown) { + if (typeof value !== 'string' || !Validator.isUUID(value, 4)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid uuidv4', value), + ); + } + } + + toSql(): string { + throw new Error('toSQL should not be called on DataTypes.UUIDV4'); + } +} + +export interface VirtualOptions { + returnType?: DataTypeClassOrInstance | undefined; + attributeDependencies?: string[] | undefined; +} + +export interface NormalizedVirtualOptions { + returnType: DataTypeClassOrInstance | undefined; + attributeDependencies: string[]; +} + +/** + * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB. + * + * You could also use it to validate a value before permuting and storing it. VIRTUAL also takes a return type and dependency fields as arguments + * If a virtual attribute is present in `attributes` it will automatically pull in the extra fields as well. + * Return type is mostly useful for setups that rely on types like GraphQL. + * + * @example Checking password length before hashing it + * ```ts + * sequelize.define('user', { + * password_hash: DataTypes.STRING, + * password: { + * type: DataTypes.VIRTUAL, + * set: function (val) { + * // Remember to set the data value, otherwise it won't be validated + * this.setDataValue('password', val); + * this.setDataValue('password_hash', this.salt + val); + * }, + * validate: { + * isLongEnough: function (val) { + * if (val.length < 7) { + * throw new Error("Please choose a longer password") + * } + * } + * } + * } + * }) + * ``` + * + * In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB. + * + * @example Virtual with dependency fields + * ```ts + * { + * active: { + * type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']), + * get: function() { + * return this.get('createdAt') > Date.now() - (7 * 24 * 60 * 60 * 1000) + * } + * } + * } + * ``` + * + * @category DataTypes + */ +export class VIRTUAL extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'VIRTUAL'; + + options: NormalizedVirtualOptions; + + constructor(returnType?: DataTypeClassOrInstance, attributeDependencies?: string[]); + constructor(options?: VirtualOptions); + + // we have to define the constructor overloads using tuples due to a TypeScript limitation + // https://github.com/microsoft/TypeScript/issues/29732, to play nice with classToInvokable. + /** @hidden */ + constructor( + ...args: + | [returnType?: DataTypeClassOrInstance, attributeDependencies?: string[]] + | [options?: VirtualOptions] + ); + + /** + * @param [returnTypeOrOptions] return type for virtual type, or an option bag + * @param [attributeDependencies] array of attributes this virtual type is dependent on + */ + constructor( + returnTypeOrOptions?: DataTypeClassOrInstance | VirtualOptions, + attributeDependencies?: string[], + ) { + super(); + + const returnType = + returnTypeOrOptions == null + ? undefined + : isDataType(returnTypeOrOptions) + ? returnTypeOrOptions + : returnTypeOrOptions.returnType; + + this.options = { + returnType: returnType ? dataTypeClassOrInstanceToInstance(returnType) : undefined, + attributeDependencies: + (isDataType(returnTypeOrOptions) + ? attributeDependencies + : returnTypeOrOptions?.attributeDependencies) ?? [], + }; + } + + toSql(): string { + throw new Error('toSQL should not be called on DataTypes.VIRTUAL'); + } + + get returnType() { + return this.options.returnType; + } + + get attributeDependencies() { + return this.options.attributeDependencies; + } +} + +/** + * If an array, each element in the array is a possible value for the ENUM. + * + * If a record (plain object, typescript enum), + * it will use the keys as the list of possible values for the ENUM, in the order specified by the Object. + * This is designed to be used with TypeScript enums, but it can be used with plain objects as well. + * Because we don't handle any mapping between the enum keys and values, we require that they be the same. + */ +type EnumValues = readonly Member[] | Record; + +export interface EnumOptions { + values: EnumValues; +} + +export interface NormalizedEnumOptions { + values: readonly Member[]; +} + +/** + * An enumeration, Postgres Only + * + * __Fallback policy:__ + * If this type is not supported, it will be replaced by a string type with a CHECK constraint to enforce a list of values. + * + * @example + * ```ts + * DataTypes.ENUM('value', 'another value') + * DataTypes.ENUM(['value', 'another value']) + * DataTypes.ENUM({ + * values: ['value', 'another value'], + * }); + * ``` + * + * @category DataTypes + */ +export class ENUM extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'ENUM'; + readonly options: NormalizedEnumOptions; + + /** + * @param options either array of values or options object with values array. It also supports variadic values. + */ + constructor(options: EnumOptions); + constructor(members: EnumValues); + constructor(...members: Member[]); + // we have to define the constructor overloads using tuples due to a TypeScript limitation + // https://github.com/microsoft/TypeScript/issues/29732, to play nice with classToInvokable. + /** @hidden */ + constructor( + ...args: [options: EnumOptions] | [members: EnumValues] | [...members: Member[]] + ); + constructor(...args: [EnumValues | Member | EnumOptions, ...Member[]]) { + super(); + + const values: readonly Member[] = this.#getEnumValues(args); + + if (values.length === 0) { + throw new TypeError( + ` +DataTypes.ENUM cannot be used without specifying its possible enum values. + +Note that the "values" property has been removed from column definitions. The following is no longer supported: + +sequelize.define('MyModel', { + roles: { + type: DataTypes.ENUM, + values: ['admin', 'user'], + }, +}); + +Instead, define enum values like this: + +sequelize.define('MyModel', { + roles: { + type: DataTypes.ENUM(['admin', 'user']), + }, +}); +`.trim(), + ); + } + + for (const value of values) { + if (typeof value !== 'string') { + throw new TypeError( + util.format( + `One of the possible values passed to DataTypes.ENUM (%O) is not a string. Only strings can be used as enum values.`, + value, + ), + ); + } + } + + this.options = { + values, + }; + } + + #getEnumValues( + args: [EnumValues | Member | EnumOptions, ...Member[]], + ): readonly Member[] { + if (args.length === 0) { + return EMPTY_ARRAY; + } + + const [first, ...rest] = args; + + if (isString(first)) { + return [first, ...rest]; + } + + if (rest.length > 0) { + throw new TypeError( + 'DataTypes.ENUM has been constructed incorrectly: Its first parameter is the option bag or the array of values, but more than one parameter has been provided.', + ); + } + + let enumOrArray: EnumValues; + if (!Array.isArray(first) && 'values' in first && typeof first.values !== 'string') { + // This is the option bag + // @ts-expect-error -- Array.isArray does not narrow correctly when the array is readonly + enumOrArray = first.values; + } else { + // @ts-expect-error -- Array.isArray does not narrow correctly when the array is readonly + enumOrArray = first; + } + + if (Array.isArray(enumOrArray)) { + return [...enumOrArray]; + } + + // @ts-expect-error -- Array.isArray does not narrow correctly when the array is readonly + const theEnum: Record = enumOrArray; + const enumKeys = Object.keys(theEnum) as Member[]; + for (const enumKey of enumKeys) { + if (theEnum[enumKey] !== enumKey) { + throw new TypeError( + `DataTypes.ENUM has been constructed incorrectly: When specifying values as a TypeScript enum or an object of key-values, the values of the object must be equal to their keys.`, + ); + } + } + + return enumKeys; + } + + validate(value: any): asserts value is Member { + if (!this.options.values.includes(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid choice for enum %O', value, this.options.values), + ); + } + } + + toSql(): string { + throw new Error(`ENUM has not been implemented in the ${this._getDialect().name} dialect.`); + } +} + +export interface ArrayOptions { + type: DataTypeClassOrInstance; +} + +interface NormalizedArrayOptions { + type: NormalizedDataType; +} + +/** + * An array of `type`. Only available in Postgres. + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.ARRAY(DataTypes.DECIMAL) + * ``` + * + * @category DataTypes + */ +export class ARRAY> extends AbstractDataType< + Array> +> { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'ARRAY'; + readonly options: NormalizedArrayOptions; + + /** + * @param typeOrOptions type of array values + */ + constructor(typeOrOptions: DataType | ArrayOptions) { + super(); + + const rawType = isDataType(typeOrOptions) ? typeOrOptions : typeOrOptions?.type; + + if (!rawType) { + throw new TypeError('DataTypes.ARRAY is missing type definition for its values.'); + } + + this.options = { + type: isString(rawType) ? rawType : dataTypeClassOrInstanceToInstance(rawType), + }; + } + + toSql(): string { + return `${attributeTypeToSql(this.options.type)}[]`; + } + + validate(value: any) { + if (!Array.isArray(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid array', value), + ); + } + + if (isString(this.options.type)) { + return; + } + + const subType: AbstractDataType = this.options.type; + + for (const item of value) { + subType.validate(item); + } + } + + sanitize(value: unknown): unknown { + if (!Array.isArray(value)) { + return value; + } + + if (isString(this.options.type)) { + return; + } + + const subType: AbstractDataType = this.options.type; + + return value.map(item => subType.sanitize(item)); + } + + parseDatabaseValue(value: unknown[]): unknown { + if (!Array.isArray(value)) { + throw new Error( + `DataTypes.ARRAY Received a non-array value from database: ${util.inspect(value)}`, + ); + } + + if (isString(this.options.type)) { + return value; + } + + const subType: AbstractDataType = this.options.type; + + return value.map(item => subType.parseDatabaseValue(item)); + } + + toBindableValue(value: Array>): unknown { + if (isString(this.options.type)) { + return value; + } + + const subType: AbstractDataType = this.options.type; + + return value.map(val => subType.toBindableValue(val)); + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (!dialect.supports.dataTypes.ARRAY) { + throwUnsupportedDataType(dialect, 'ARRAY'); + } + } + + toDialectDataType(dialect: AbstractDialect): this { + let replacement = super.toDialectDataType(dialect); + + if (replacement === this) { + replacement = replacement.clone(); + } + + if (!isString(replacement.options.type)) { + replacement.options.type = replacement.options.type.toDialectDataType(dialect); + } + + return replacement; + } + + attachUsageContext(usageContext: DataTypeUseContext): this { + if (!isString(this.options.type)) { + this.options.type.attachUsageContext(usageContext); + } + + return super.attachUsageContext(usageContext); + } + + static is>(obj: unknown, type: new () => T): obj is ARRAY { + return obj instanceof ARRAY && obj.options.type instanceof type; + } +} + +export interface GeometryOptions { + type?: GeoJsonType | undefined; + srid?: number | undefined; +} + +/** + * A column storing Geometry information. + * It is only available in PostgreSQL (with PostGIS), MariaDB or MySQL. + * + * GeoJSON is accepted as input and returned as output. + * + * In PostGIS, the GeoJSON is parsed using the PostGIS function `STGeomFromGeoJSON`. + * In MySQL it is parsed using the function `STGeomFromText`. + * + * Therefore, one can just follow the [GeoJSON spec](https://tools.ietf.org/html/rfc7946) for handling geometry objects. See the following examples: + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example Defining a Geometry type attribute + * ```ts + * DataTypes.GEOMETRY + * DataTypes.GEOMETRY('POINT') + * DataTypes.GEOMETRY('POINT', 4326) + * ``` + * + * @example Create a new point + * ```ts + * const point = { type: 'Point', coordinates: [-76.984722, 39.807222]}; // GeoJson format: [lng, lat] + * + * User.create({username: 'username', geometry: point }); + * ``` + * + * @example Create a new linestring + * ```ts + * const line = { type: 'LineString', 'coordinates': [ [100.0, 0.0], [101.0, 1.0] ] }; + * + * User.create({username: 'username', geometry: line }); + * ``` + * + * @example Create a new polygon + * ```ts + * const polygon = { type: 'Polygon', coordinates: [ + * [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + * [100.0, 1.0], [100.0, 0.0] ] + * ]}; + * + * User.create({username: 'username', geometry: polygon }); + * ``` + * + * @example Create a new point with a custom SRID + * ```ts + * const point = { + * type: 'Point', + * coordinates: [-76.984722, 39.807222], // GeoJson format: [lng, lat] + * crs: { type: 'name', properties: { name: 'EPSG:4326'} } + * }; + * + * User.create({username: 'username', geometry: point }) + * ``` + * + * @category DataTypes + */ +export class GEOMETRY extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'GEOMETRY'; + readonly options: GeometryOptions; + + /** + * @param {string} [type] Type of geometry data + * @param {string} [srid] SRID of type + */ + constructor(type: GeoJsonType, srid?: number); + constructor(options: GeometryOptions); + + // we have to define the constructor overloads using tuples due to a TypeScript limitation + // https://github.com/microsoft/TypeScript/issues/29732, to play nice with classToInvokable. + /** @hidden */ + constructor(...args: [type: GeoJsonType, srid?: number] | [options: GeometryOptions]); + + constructor(typeOrOptions: GeoJsonType | GeometryOptions, srid?: number) { + super(); + + this.options = isObject(typeOrOptions) ? { ...typeOrOptions } : { type: typeOrOptions, srid }; + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (!dialect.supports.dataTypes.GEOMETRY) { + throwUnsupportedDataType(dialect, 'GEOMETRY'); + } + } + + validate(value: unknown): asserts value is GeoJson { + try { + assertIsGeoJson(value); + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + + // TODO: add 'cause' + ValidationErrorItem.throwDataTypeValidationError(error.message); + } + + return super.validate(value); + } + + toSql(): string { + return 'GEOMETRY'; + } +} + +/** + * A geography datatype represents two dimensional spacial objects in an elliptic coord system. + * + * **The difference from geometry and geography type:** + * + * PostGIS 1.5 introduced a new spatial type called geography, which uses geodetic measurement instead of Cartesian measurement. + * Coordinate points in the geography type are always represented in WGS 84 lon lat degrees (SRID 4326), + * but measurement functions and relationships STDistance, STDWithin, STLength, and STArea always return answers in meters or assume inputs in meters. + * + * **What is best to use? It depends:** + * + * When choosing between the geometry and geography type for data storage, you should consider what you’ll be using it for. + * If all you do are simple measurements and relationship checks on your data, and your data covers a fairly large area, then most likely you’ll be better off storing your data using the new geography type. + * Although the new geography data type can cover the globe, the geometry type is far from obsolete. + * The geometry type has a much richer set of functions than geography, relationship checks are generally faster, and it has wider support currently across desktop and web-mapping tools + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example Defining a Geography type attribute + * ```ts + * DataTypes.GEOGRAPHY + * DataTypes.GEOGRAPHY('POINT') + * DataTypes.GEOGRAPHY('POINT', 4326) + * ``` + * + * @category DataTypes + */ +export class GEOGRAPHY extends GEOMETRY { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'GEOGRAPHY'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.GEOGRAPHY) { + throwUnsupportedDataType(dialect, 'GEOGRAPHY'); + } + } + + toSql(): string { + return 'GEOGRAPHY'; + } +} + +/** + * The cidr type holds an IPv4 or IPv6 network specification. Takes 7 or 19 bytes. + * + * Only available for Postgres + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.CIDR + * ``` + * + * @category DataTypes + */ +export class CIDR extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'CIDR'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.CIDR) { + throwUnsupportedDataType(dialect, 'CIDR'); + } + } + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isIPRange(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid CIDR', value), + ); + } + } + + toSql(): string { + return 'CIDR'; + } +} + +/** + * The INET type holds an IPv4 or IPv6 host address, and optionally its subnet. Takes 7 or 19 bytes + * + * Only available for Postgres + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.INET + * ``` + * + * @category DataTypes + */ +export class INET extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'INET'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.INET) { + throwUnsupportedDataType(dialect, 'INET'); + } + } + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isIP(value)) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid INET', value), + ); + } + } + + toSql(): string { + return 'INET'; + } +} + +/** + * The MACADDR type stores MAC addresses. Takes 6 bytes + * + * Only available for Postgres + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.MACADDR + * ``` + * + * @category DataTypes + */ +export class MACADDR extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'MACADDR'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.MACADDR) { + throwUnsupportedDataType(dialect, 'MACADDR'); + } + } + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isMACAddress(value, { eui: '48' })) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid MACADDR', value), + ); + } + } + + toSql(): string { + return 'MACADDR'; + } +} + +/** + * The MACADDR type stores MAC addresses. Takes 8 bytes + * + * Only available for Postgres + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.MACADDR8 + * ``` + * + * @category DataTypes + */ +export class MACADDR8 extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'MACADDR8'; + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.MACADDR8) { + throwUnsupportedDataType(dialect, 'MACADDR8'); + } + } + + validate(value: any) { + if (typeof value !== 'string' || !Validator.isMACAddress(value, { eui: '64' })) { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid MACADDR8', value), + ); + } + } + + toSql(): string { + return 'MACADDR8'; + } +} + +/** + * The TSVECTOR type stores text search vectors. + * + * Only available for Postgres + * + * __Fallback policy:__ + * If this type is not supported, an error will be raised. + * + * @example + * ```ts + * DataTypes.TSVECTOR + * ``` + * + * @category DataTypes + */ +export class TSVECTOR extends AbstractDataType { + /** @hidden */ + static readonly [DataTypeIdentifier]: string = 'TSVECTOR'; + + validate(value: any) { + if (typeof value !== 'string') { + ValidationErrorItem.throwDataTypeValidationError( + util.format('%O is not a valid string', value), + ); + } + } + + protected _checkOptionSupport(dialect: AbstractDialect) { + if (!dialect.supports.dataTypes.TSVECTOR) { + throwUnsupportedDataType(dialect, 'TSVECTOR'); + } + } + + toSql(): string { + return 'TSVECTOR'; + } +} + +function rejectBlobs(value: unknown) { + // We have a DataType called BLOB. People might try to use the built-in Blob type with it, which they cannot. + // To clarify why it doesn't work, we have a dedicated message for it. + if (Blob && value instanceof Blob) { + ValidationErrorItem.throwDataTypeValidationError( + 'Blob instances are not supported values, because reading their data is an async operation. Call blob.arrayBuffer() to get a buffer, and pass that to Sequelize instead.', + ); + } +} + +function assertDataTypeSupported(dialect: AbstractDialect, dataType: AbstractDataType) { + const typeId = dataType.getDataTypeId(); + + if ( + typeId in dialect.supports.dataTypes && + // @ts-expect-error -- it's possible that typeId isn't listed in the support table, but that's checked above + !dialect.supports.dataTypes[typeId] + ) { + throwUnsupportedDataType(dialect, typeId); + } +} diff --git a/packages/core/src/abstract-dialect/dialect.ts b/packages/core/src/abstract-dialect/dialect.ts new file mode 100644 index 000000000000..9d17dd6ee1a1 --- /dev/null +++ b/packages/core/src/abstract-dialect/dialect.ts @@ -0,0 +1,752 @@ +import { EMPTY_OBJECT, freezeDeep, getImmutablePojo, isFunction, isString } from '@sequelize/utils'; +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; +import type { Class } from 'type-fest'; +import type { DialectName, Sequelize } from '../sequelize.js'; +import { logger } from '../utils/logger.js'; +import type { DeepPartial } from '../utils/types.js'; +import type { AbstractConnectionManager } from './connection-manager.js'; +import type { AbstractDataType } from './data-types.js'; +import * as BaseDataTypes from './data-types.js'; +import type { AbstractQueryGenerator } from './query-generator.js'; +import type { AbstractQueryInterface } from './query-interface.js'; +import type { AbstractQuery } from './query.js'; + +export interface SupportableNumericOptions { + zerofill: boolean; + /** Whether this dialect supports the unsigned option natively */ + unsigned: boolean; +} + +export interface SupportableDecimalNumberOptions extends SupportableNumericOptions { + /** Whether NaN can be inserted in a column that uses this DataType. */ + NaN: boolean; + /** Whether Infinity/-Infinity can be inserted in a column that uses this DataType. */ + infinity: boolean; +} + +export interface SupportableFloatOptions extends SupportableDecimalNumberOptions { + /** Whether scale & precision can be specified as parameters */ + scaleAndPrecision: boolean; +} + +export interface SupportableExactDecimalOptions extends SupportableDecimalNumberOptions { + /** + * Whether this dialect supports unconstrained numeric/decimal columns. i.e. columns where numeric values of any length can be stored. + * The SQL standard requires that "NUMERIC" with no option be equal to "NUMERIC(0,0)", but some dialects (postgres) + * interpret it as an unconstrained numeric. + */ + unconstrained: boolean; + + /** + * Whether this dialect supports constrained numeric/decimal columns. i.e. columns where numeric values of any length can be stored. + */ + constrained: boolean; +} + +export type DialectSupports = { + DEFAULT: boolean; + 'DEFAULT VALUES': boolean; + 'VALUES ()': boolean; + // TODO: rename to `update.limit` + 'LIMIT ON UPDATE': boolean; + 'ON DUPLICATE KEY': boolean; + 'ORDER NULLS': boolean; + UNION: boolean; + 'UNION ALL': boolean; + 'RIGHT JOIN': boolean; + EXCEPTION: boolean; + + forShare?: 'LOCK IN SHARE MODE' | 'FOR SHARE' | undefined; + lock: boolean; + lockOf: boolean; + lockKey: boolean; + lockOuterJoinFailure: boolean; + skipLocked: boolean; + finalTable: boolean; + + /* does the dialect support returning values for inserted/updated fields */ + returnValues: false | 'output' | 'returning'; + + /* features specific to autoIncrement values */ + autoIncrement: { + /* does the dialect require modification of insert queries when inserting auto increment fields */ + identityInsert: boolean; + + /* does the dialect support inserting default/null values for autoincrement fields */ + defaultValue: boolean; + + /* does the dialect support updating autoincrement fields */ + update: boolean; + }; + /* Do we need to say DEFAULT for bulk insert */ + bulkDefault: boolean; + /** + * Whether this dialect has native support for schemas. + * For the purposes of Sequelize, a Schema is considered to be a grouping of tables. + * For instance, in MySQL, "CREATE DATABASE" creates what we consider to be a schema. + */ + schemas: boolean; + /** + * Whether this dialect has native support for having multiple databases per instance (in the postgres or mssql sense). + * For the purposes of Sequelize, a database is considered to be a grouping of schemas. + * For instance, in MySQL, "CREATE DATABASE" creates what we consider to be a schema, + * so we do not consider that MySQL supports this option. + */ + multiDatabases: boolean; + transactions: boolean; + savepoints: boolean; + isolationLevels: boolean; + connectionTransactionMethods: boolean; + settingIsolationLevelDuringTransaction: boolean; + startTransaction: { + useBegin: boolean; + readOnly: boolean; + transactionType: boolean; + }; + migrations: boolean; + upserts: boolean; + inserts: { + ignoreDuplicates: string /* dialect specific words for INSERT IGNORE or DO NOTHING */; + updateOnDuplicate: boolean | string /* whether dialect supports ON DUPLICATE KEY UPDATE */; + onConflictDoNothing: string /* dialect specific words for ON CONFLICT DO NOTHING */; + onConflictWhere: boolean /* whether dialect supports ON CONFLICT WHERE */; + conflictFields: boolean /* whether the dialect supports specifying conflict fields or not */; + }; + constraints: { + restrict: boolean; + /** + * This dialect supports marking a column's constraints as deferrable. + * e.g. 'DEFERRABLE' and 'INITIALLY DEFERRED' + */ + deferrable: boolean; + unique: boolean; + default: boolean; + check: boolean; + foreignKey: boolean; + /** Whether this dialect supports disabling foreign key checks for the current session */ + foreignKeyChecksDisableable: boolean; + primaryKey: boolean; + onUpdate: boolean; + add: boolean; + remove: boolean; + removeOptions: { + cascade: boolean; + ifExists: boolean; + }; + }; + index: { + collate: boolean; + length: boolean; + parser: boolean; + concurrently: boolean; + type: boolean; + using: boolean | number; + functionBased: boolean; + operator: boolean; + where: boolean; + include: boolean; + }; + groupedLimit: boolean; + indexViaAlter: boolean; + alterColumn: { + /** + * Can "ALTER TABLE x ALTER COLUMN y" add UNIQUE to the column in this dialect? + */ + unique: boolean; + }; + dataTypes: { + CHAR: boolean; + /** + * Whether this dialect provides a binary collation on text, varchar & char columns. + */ + COLLATE_BINARY: boolean; + /** This dialect supports case-insensitive text */ + CITEXT: boolean; + /** Options supportable by all int types (from tinyint to bigint) */ + INTS: SupportableNumericOptions; + /** @deprecated */ + REAL: SupportableFloatOptions; + /** This dialect supports 4 byte long floating point numbers */ + FLOAT: SupportableFloatOptions; + /** This dialect supports 8 byte long floating point numbers */ + DOUBLE: SupportableFloatOptions; + /** This dialect supports arbitrary precision numbers */ + DECIMAL: false | SupportableExactDecimalOptions; + /** This dialect supports big integers */ + BIGINT: boolean; + /** + * The dialect is considered to support JSON if it provides either: + * - A JSON data type. + * - An SQL function that can be used as a CHECK constraint on a text column, to ensure its contents are valid JSON. + */ + JSON: boolean; + JSONB: boolean; + ARRAY: boolean; + RANGE: boolean; + GEOMETRY: boolean; + GEOGRAPHY: boolean; + HSTORE: boolean; + TSVECTOR: boolean; + CIDR: boolean; + INET: boolean; + MACADDR: boolean; + MACADDR8: boolean; + DATETIME: { + /** Whether "infinity" is a valid value in this dialect's DATETIME data type */ + infinity: boolean; + }; + DATEONLY: { + /** Whether "infinity" is a valid value in this dialect's DATEONLY data type */ + infinity: boolean; + }; + TIME: { + /** Whether the dialect supports TIME(precision) */ + precision: boolean; + }; + }; + REGEXP: boolean; + /** + * Case-insensitive regexp operator support ('~*' in postgres). + */ + IREGEXP: boolean; + /** Whether this dialect supports SQL JSON functions */ + jsonOperations: boolean; + /** Whether this dialect supports returning quoted & unquoted JSON strings */ + jsonExtraction: { + unquoted: boolean; + quoted: boolean; + }; + tmpTableTrigger: boolean; + indexHints: boolean; + tableHints: boolean; + searchPath: boolean; + /** + * This dialect supports E-prefixed strings, e.g. "E'foo'", which + * enables the ability to use backslash escapes inside the string. + */ + escapeStringConstants: boolean; + + /** Whether this dialect supports changing the global timezone option */ + globalTimeZoneConfig: boolean; + /** Whether this dialect provides a native way to generate UUID v1 values */ + uuidV1Generation: boolean; + /** Whether this dialect provides a native way to generate UUID v4 values */ + uuidV4Generation: boolean; + dropTable: { + cascade: boolean; + }; + maxExecutionTimeHint: { + select: boolean; + }; + truncate: { + cascade: boolean; + restartIdentity: boolean; + }; + removeColumn: { + cascade: boolean; + ifExists: boolean; + }; + renameTable: { + changeSchema: boolean; + changeSchemaAndTable: boolean; + }; + createSchema: { + authorization: boolean; + charset: boolean; + collate: boolean; + comment: boolean; + ifNotExists: boolean; + replace: boolean; + }; + dropSchema: { + cascade: boolean; + ifExists: boolean; + }; + delete: { + limit: boolean; + }; +}; + +type TypeParser = (...params: any[]) => unknown; + +declare const OptionType: unique symbol; +declare const ConnectionOptionType: unique symbol; + +export type DialectOptions = Dialect[typeof OptionType]; +export type ConnectionOptions = + Dialect[typeof ConnectionOptionType]; + +export type AbstractDialectParams = { + dataTypeOverrides: Record>>; + dataTypesDocumentationUrl: string; + /** + * The character used to delimit identifiers in SQL queries. + * + * This can be a string, in which case the character will be used for both the start & end of the identifier, + * or an object with `start` and `end` properties. + */ + identifierDelimiter: string | { start: string; end: string }; + minimumDatabaseVersion: string; + name: DialectName; + options: Options | undefined; + sequelize: Sequelize; +}; + +export abstract class AbstractDialect< + Options extends object = object, + TConnectionOptions extends object = object, +> { + declare [OptionType]: Options; + declare [ConnectionOptionType]: TConnectionOptions; + + /** + * List of features this dialect supports. + * + * Important: Dialect implementations inherit these values. + * When changing a default, ensure the implementations still properly declare which feature they support. + */ + static readonly supports: DialectSupports = freezeDeep({ + DEFAULT: true, + 'DEFAULT VALUES': false, + 'VALUES ()': false, + 'LIMIT ON UPDATE': false, + 'ON DUPLICATE KEY': true, + 'ORDER NULLS': false, + UNION: true, + 'UNION ALL': true, + 'RIGHT JOIN': true, + EXCEPTION: false, + lock: false, + lockOf: false, + lockKey: false, + lockOuterJoinFailure: false, + skipLocked: false, + finalTable: false, + returnValues: false, + autoIncrement: { + identityInsert: false, + defaultValue: true, + update: true, + }, + bulkDefault: false, + schemas: false, + multiDatabases: false, + transactions: true, + savepoints: true, + isolationLevels: true, + connectionTransactionMethods: false, + settingIsolationLevelDuringTransaction: true, + startTransaction: { + useBegin: false, + readOnly: false, + transactionType: false, + }, + migrations: true, + upserts: true, + inserts: { + ignoreDuplicates: '', + updateOnDuplicate: false, + onConflictDoNothing: '', + onConflictWhere: false, + conflictFields: false, + }, + constraints: { + restrict: true, + deferrable: false, + unique: true, + default: false, + check: true, + foreignKey: true, + foreignKeyChecksDisableable: false, + primaryKey: true, + onUpdate: true, + add: true, + remove: true, + removeOptions: { + cascade: false, + ifExists: false, + }, + }, + index: { + collate: true, + length: false, + parser: false, + concurrently: false, + type: false, + using: true, + functionBased: false, + operator: false, + where: false, + include: false, + }, + groupedLimit: true, + indexViaAlter: false, + alterColumn: { + unique: true, + }, + dataTypes: { + CHAR: true, + COLLATE_BINARY: false, + CITEXT: false, + INTS: { zerofill: false, unsigned: false }, + FLOAT: { + NaN: false, + infinity: false, + zerofill: false, + unsigned: false, + scaleAndPrecision: false, + }, + REAL: { + NaN: false, + infinity: false, + zerofill: false, + unsigned: false, + scaleAndPrecision: false, + }, + DOUBLE: { + NaN: false, + infinity: false, + zerofill: false, + unsigned: false, + scaleAndPrecision: false, + }, + DECIMAL: { + constrained: true, + unconstrained: false, + NaN: false, + infinity: false, + zerofill: false, + unsigned: false, + }, + BIGINT: true, + CIDR: false, + MACADDR: false, + MACADDR8: false, + INET: false, + JSON: false, + JSONB: false, + ARRAY: false, + RANGE: false, + GEOMETRY: false, + GEOGRAPHY: false, + HSTORE: false, + TSVECTOR: false, + DATETIME: { + infinity: false, + }, + DATEONLY: { + infinity: false, + }, + TIME: { + precision: true, + }, + }, + jsonOperations: false, + jsonExtraction: { + unquoted: false, + quoted: false, + }, + REGEXP: false, + IREGEXP: false, + tmpTableTrigger: false, + indexHints: false, + tableHints: false, + searchPath: false, + escapeStringConstants: false, + globalTimeZoneConfig: false, + uuidV1Generation: false, + uuidV4Generation: false, + dropTable: { + cascade: false, + }, + maxExecutionTimeHint: { + select: false, + }, + truncate: { + cascade: false, + restartIdentity: false, + }, + removeColumn: { + cascade: false, + ifExists: false, + }, + renameTable: { + changeSchema: true, + changeSchemaAndTable: true, + }, + createSchema: { + authorization: false, + charset: false, + collate: false, + comment: false, + ifNotExists: false, + replace: false, + }, + dropSchema: { + cascade: false, + ifExists: false, + }, + delete: { + limit: true, + }, + }); + + protected static extendSupport(supportsOverwrite: DeepPartial): DialectSupports { + return merge(cloneDeep(this.supports) ?? {}, supportsOverwrite); + } + + readonly sequelize: Sequelize; + + abstract readonly Query: typeof AbstractQuery; + abstract readonly queryGenerator: AbstractQueryGenerator; + abstract readonly queryInterface: AbstractQueryInterface; + abstract readonly connectionManager: AbstractConnectionManager; + + /** + * @deprecated use {@link minimumDatabaseVersion} + */ + get defaultVersion(): string { + return this.minimumDatabaseVersion; + } + + /** + * @deprecated use {@link identifierDelimiter}.start + */ + get TICK_CHAR_LEFT(): string { + return this.identifierDelimiter.start; + } + + /** + * @deprecated use {@link identifierDelimiter}.end + */ + get TICK_CHAR_RIGHT(): string { + return this.identifierDelimiter.end; + } + + readonly identifierDelimiter: { readonly start: string; readonly end: string }; + readonly minimumDatabaseVersion: string; + readonly dataTypesDocumentationUrl: string; + readonly options: Options; + readonly name: DialectName; + + /** dialect-specific implementation of shared data types */ + readonly #dataTypeOverrides: Map>>; + /** base implementations of shared data types */ + readonly #baseDataTypes: Map>>; + readonly #dataTypeParsers = new Map(); + + get supports(): DialectSupports { + const Dialect = this.constructor as typeof AbstractDialect; + + return Dialect.supports; + } + + constructor(params: AbstractDialectParams) { + this.sequelize = params.sequelize as Sequelize; + this.name = params.name; + this.dataTypesDocumentationUrl = params.dataTypesDocumentationUrl; + this.options = params.options ? getImmutablePojo(params.options) : EMPTY_OBJECT; + + this.identifierDelimiter = isString(params.identifierDelimiter) + ? Object.freeze({ + start: params.identifierDelimiter, + end: params.identifierDelimiter, + }) + : getImmutablePojo(params.identifierDelimiter); + + this.minimumDatabaseVersion = params.minimumDatabaseVersion; + + const baseDataTypes = new Map>>(); + for (const dataType of Object.values(BaseDataTypes) as Array>>) { + // Some exports are not Data Types + if (!isFunction(dataType)) { + continue; + } + + const dataTypeId: string = (dataType as unknown as typeof AbstractDataType).getDataTypeId(); + + // intermediary data type + if (!dataTypeId) { + continue; + } + + if (baseDataTypes.has(dataTypeId)) { + throw new Error( + `Internal Error: Sequelize declares more than one base implementation for DataType ID ${dataTypeId}.`, + ); + } + + baseDataTypes.set(dataTypeId, dataType); + } + + const dataTypeOverrides = new Map>>(); + for (const dataType of Object.values(params.dataTypeOverrides)) { + const replacedDataTypeId: string = ( + dataType as unknown as typeof AbstractDataType + ).getDataTypeId(); + + if (dataTypeOverrides.has(replacedDataTypeId)) { + throw new Error( + `Dialect ${this.name} declares more than one implementation for DataType ID ${replacedDataTypeId}.`, + ); + } + + dataTypeOverrides.set(replacedDataTypeId, dataType); + } + + this.#dataTypeOverrides = dataTypeOverrides; + this.#baseDataTypes = baseDataTypes; + } + + /** + * Returns the dialect-specific implementation of a shared data type, or null if no such implementation exists + * (in which case you need to use the base implementation). + * + * @param dataType The shared data type. + */ + getDataTypeForDialect( + dataType: Class>, + ): Class> | null { + const typeId = (dataType as unknown as typeof AbstractDataType).getDataTypeId(); + const baseType = this.#baseDataTypes.get(typeId); + + // this is not one of our types. May be a custom type by a user. We don't replace it. + if (baseType != null && baseType !== dataType) { + return null; + } + + return this.#dataTypeOverrides.get(typeId) ?? null; + } + + readonly #printedWarnings = new Set(); + warnDataTypeIssue(text: string): void { + // TODO: log this to sequelize's log option instead (requires a logger with multiple log levels first) + if (this.#printedWarnings.has(text)) { + return; + } + + this.#printedWarnings.add(text); + logger.warn(`${text} \n>> Check: ${this.dataTypesDocumentationUrl}`); + } + + abstract createBindCollector(): BindCollector; + + /** + * Produces a safe representation of a Buffer for this dialect, that can be inlined in a SQL string. + * Used mainly by DataTypes. + * + * @param buffer The buffer to escape + * @returns The string, escaped for SQL. + */ + escapeBuffer(buffer: Buffer): string { + const hex = buffer.toString('hex'); + + return `X'${hex}'`; + } + + /** + * Produces a safe representation of a string for this dialect, that can be inlined in a SQL string. + * Used mainly by DataTypes. + * + * @param value The string to escape + * @returns The string, escaped for SQL. + */ + escapeString(value: string): string { + // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS + // http://stackoverflow.com/q/603572/130598 + value = value.replaceAll("'", "''"); + + return `'${value}'`; + } + + // Keep the logic of this class synchronized with the logic in the JSON DataType. + escapeJson(value: unknown): string { + return this.escapeString(JSON.stringify(value)); + } + + /** + * Whether this dialect can use \ in strings to escape string delimiters. + * + * @returns + */ + canBackslashEscape(): boolean { + return false; + } + + /** + * Used to register a base parser for a Database type. + * Parsers are based on the Database Type, not the JS type. + * Only one parser can be assigned as the parser for a Database Type. + * For this reason, prefer neutral implementations. + * + * For instance, when implementing "parse" for a Date type, + * prefer returning a String rather than a Date object. + * + * The {@link DataTypes.ABSTRACT#parseDatabaseValue} method will then be called on the DataType instance defined by the user, + * which can decide on a more specific JS type (e.g. parse the date string & return a Date instance or a Temporal instance). + * + * You typically do not need to implement this method. This is used to provide default parsers when no DataType + * is provided (e.g. raw queries that don't specify a model). Sequelize already provides a default parser for most types. + * For a custom Data Type, implementing {@link DataTypes.ABSTRACT#parseDatabaseValue} is typically what you want. + * + * @param databaseDataTypes Dialect-specific DB data type identifiers that will use this parser. + * @param parser The parser function to call when parsing the data type. Parameters are dialect-specific. + */ + registerDataTypeParser(databaseDataTypes: unknown[], parser: TypeParser) { + for (const databaseDataType of databaseDataTypes) { + if (this.#dataTypeParsers.has(databaseDataType)) { + throw new Error( + `Sequelize DataType for DB DataType ${databaseDataType} already registered for dialect ${this.name}`, + ); + } + + this.#dataTypeParsers.set(databaseDataType, parser); + } + } + + getParserForDatabaseDataType(databaseDataType: unknown): TypeParser | undefined { + return this.#dataTypeParsers.get(databaseDataType); + } + + abstract getDefaultSchema(): string; + + abstract parseConnectionUrl(url: string): TConnectionOptions; + + static getSupportedOptions(): readonly string[] { + throw new Error( + `Dialect ${this.name} does not implement the static method getSupportedOptions. +It must return the list of option names that can be passed to the dialect constructor.`, + ); + } + + static getSupportedConnectionOptions(): readonly string[] { + throw new Error( + `Dialect ${this.name} does not implement the static method getSupportedConnectionOptions. +It must return the list of connection option names that will be passed to its ConnectionManager's getConnection.`, + ); + } + + getSupportedOptions(): readonly string[] { + return (this.constructor as typeof AbstractDialect).getSupportedOptions(); + } + + getSupportedConnectionOptions(): readonly string[] { + return (this.constructor as typeof AbstractDialect).getSupportedConnectionOptions(); + } +} + +export type BindCollector = { + /** + * + * + * @param bindParameterName The name of the bind parameter + */ + collect(bindParameterName: string): string; + + /** + * Returns either an array of orders if the bind parameters are mapped to numeric parameters (e.g. '?', $1, @1), + * or null if no mapping was necessary because the dialect supports named parameters. + */ + getBindParameterOrder(): string[] | null; +}; diff --git a/packages/core/src/abstract-dialect/query-generator-internal.ts b/packages/core/src/abstract-dialect/query-generator-internal.ts new file mode 100644 index 000000000000..59ae9250d275 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator-internal.ts @@ -0,0 +1,354 @@ +import { EMPTY_ARRAY } from '@sequelize/utils'; +import { Deferrable } from '../deferrable.js'; +import type { AssociationPath } from '../expression-builders/association-path.js'; +import type { Attribute } from '../expression-builders/attribute.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; +import type { Cast } from '../expression-builders/cast.js'; +import type { Col } from '../expression-builders/col.js'; +import type { DialectAwareFn } from '../expression-builders/dialect-aware-fn.js'; +import type { Fn } from '../expression-builders/fn.js'; +import type { JsonPath } from '../expression-builders/json-path.js'; +import type { Literal } from '../expression-builders/literal.js'; +import type { Sequelize } from '../sequelize.js'; +import { extractModelDefinition } from '../utils/model-utils.js'; +import { injectReplacements } from '../utils/sql.js'; +import { attributeTypeToSql } from './data-types-utils.js'; +import type { AbstractDialect } from './dialect.js'; +import type { EscapeOptions } from './query-generator-typescript.js'; +import type { AddLimitOffsetOptions } from './query-generator.internal-types.js'; +import type { GetConstraintSnippetQueryOptions, TableOrModel } from './query-generator.types.js'; +import { WhereSqlBuilder, wrapAmbiguousWhere } from './where-sql-builder.js'; + +export class AbstractQueryGeneratorInternal { + readonly dialect: Dialect; + readonly whereSqlBuilder: WhereSqlBuilder; + + get sequelize(): Sequelize { + return this.dialect.sequelize; + } + + get queryGenerator(): Dialect['queryGenerator'] { + return this.dialect.queryGenerator; + } + + constructor(dialect: Dialect) { + this.dialect = dialect; + + this.whereSqlBuilder = new WhereSqlBuilder(dialect); + } + + getTechnicalDatabaseNames(): readonly string[] { + return EMPTY_ARRAY; + } + + getTechnicalSchemaNames(): readonly string[] { + return EMPTY_ARRAY; + } + + getConstraintSnippet(tableName: TableOrModel, options: GetConstraintSnippetQueryOptions) { + const quotedFields = options.fields.map(field => { + if (typeof field === 'string') { + return this.queryGenerator.quoteIdentifier(field); + } + + if (field instanceof BaseSqlExpression) { + return this.queryGenerator.formatSqlExpression(field); + } + + if (field.attribute) { + throw new Error( + 'The field.attribute property has been removed. Use the field.name property instead', + ); + } + + if (!field.name) { + throw new Error(`The following index field has no name: ${field}`); + } + + return this.queryGenerator.quoteIdentifier(field.name); + }); + + const constraintNameParts = options.name + ? null + : options.fields.map(field => { + if (typeof field === 'string') { + return field; + } + + if (field instanceof BaseSqlExpression) { + throw new TypeError( + `The constraint name must be provided explicitly if one of Sequelize's method (literal(), col(), etc…) is used in the constraint's fields`, + ); + } + + return field.name; + }); + + let constraintSnippet; + const table = this.queryGenerator.extractTableDetails(tableName); + const fieldsSqlQuotedString = quotedFields.join(', '); + const fieldsSqlString = constraintNameParts?.join('_'); + + switch (options.type.toUpperCase()) { + case 'CHECK': { + if (!this.dialect.supports.constraints.check) { + throw new Error(`Check constraints are not supported by ${this.dialect.name} dialect`); + } + + const constraintName = this.queryGenerator.quoteIdentifier( + options.name || `${table.tableName}_${fieldsSqlString}_ck`, + ); + constraintSnippet = `CONSTRAINT ${constraintName} CHECK (${this.queryGenerator.whereItemsQuery(options.where)})`; + break; + } + + case 'UNIQUE': { + if (!this.dialect.supports.constraints.unique) { + throw new Error(`Unique constraints are not supported by ${this.dialect.name} dialect`); + } + + const constraintName = this.queryGenerator.quoteIdentifier( + options.name || `${table.tableName}_${fieldsSqlString}_uk`, + ); + constraintSnippet = `CONSTRAINT ${constraintName} UNIQUE (${fieldsSqlQuotedString})`; + if (options.deferrable) { + constraintSnippet += ` ${this.getDeferrableConstraintSnippet(options.deferrable)}`; + } + + break; + } + + case 'DEFAULT': { + if (!this.dialect.supports.constraints.default) { + throw new Error(`Default constraints are not supported by ${this.dialect.name} dialect`); + } + + if (options.defaultValue === undefined) { + throw new Error('Default value must be specified for DEFAULT CONSTRAINT'); + } + + const constraintName = this.queryGenerator.quoteIdentifier( + options.name || `${table.tableName}_${fieldsSqlString}_df`, + ); + constraintSnippet = `CONSTRAINT ${constraintName} DEFAULT (${this.queryGenerator.escape(options.defaultValue, options)}) FOR ${quotedFields[0]}`; + break; + } + + case 'PRIMARY KEY': { + if (!this.dialect.supports.constraints.primaryKey) { + throw new Error( + `Primary key constraints are not supported by ${this.dialect.name} dialect`, + ); + } + + const constraintName = this.queryGenerator.quoteIdentifier( + options.name || `${table.tableName}_${fieldsSqlString}_pk`, + ); + constraintSnippet = `CONSTRAINT ${constraintName} PRIMARY KEY (${fieldsSqlQuotedString})`; + if (options.deferrable) { + constraintSnippet += ` ${this.getDeferrableConstraintSnippet(options.deferrable)}`; + } + + break; + } + + case 'FOREIGN KEY': { + if (!this.dialect.supports.constraints.foreignKey) { + throw new Error( + `Foreign key constraints are not supported by ${this.dialect.name} dialect`, + ); + } + + const references = options.references; + if (!references || !references.table || !(references.field || references.fields)) { + throw new Error( + 'Invalid foreign key constraint options. `references` object with `table` and `field` must be specified', + ); + } + + const referencedTable = this.queryGenerator.extractTableDetails(references.table); + const constraintName = this.queryGenerator.quoteIdentifier( + options.name || `${table.tableName}_${fieldsSqlString}_${referencedTable.tableName}_fk`, + ); + const quotedReferences = + references.field !== undefined + ? this.queryGenerator.quoteIdentifier(references.field) + : references.fields!.map(f => this.queryGenerator.quoteIdentifier(f)).join(', '); + const referencesSnippet = `${this.queryGenerator.quoteTable(referencedTable)} (${quotedReferences})`; + constraintSnippet = `CONSTRAINT ${constraintName} `; + constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`; + if (options.onUpdate) { + if (!this.dialect.supports.constraints.onUpdate) { + throw new Error( + `Foreign key constraint with onUpdate is not supported by ${this.dialect.name} dialect`, + ); + } + + constraintSnippet += ` ON UPDATE ${options.onUpdate.toUpperCase()}`; + } + + if (options.onDelete) { + constraintSnippet += ` ON DELETE ${options.onDelete.toUpperCase()}`; + } + + if (options.deferrable) { + constraintSnippet += ` ${this.getDeferrableConstraintSnippet(options.deferrable)}`; + } + + break; + } + + default: { + throw new Error( + `Constraint type ${options.type} is not supported by ${this.dialect.name} dialect`, + ); + } + } + + return constraintSnippet; + } + + getDeferrableConstraintSnippet(deferrable: Deferrable) { + if (!this.dialect.supports.constraints.deferrable) { + throw new Error(`Deferrable constraints are not supported by ${this.dialect.name} dialect`); + } + + switch (deferrable) { + case Deferrable.INITIALLY_DEFERRED: { + return 'DEFERRABLE INITIALLY DEFERRED'; + } + + case Deferrable.INITIALLY_IMMEDIATE: { + return 'DEFERRABLE INITIALLY IMMEDIATE'; + } + + case Deferrable.NOT: { + return 'NOT DEFERRABLE'; + } + + default: { + throw new Error(`Unknown constraint checking behavior ${deferrable}`); + } + } + } + + formatAssociationPath(associationPath: AssociationPath): string { + return `${this.queryGenerator.quoteIdentifier(associationPath.associationPath.join('->'))}.${this.queryGenerator.quoteIdentifier(associationPath.attributeName)}`; + } + + formatJsonPath(jsonPathVal: JsonPath, options?: EscapeOptions): string { + const value = this.queryGenerator.escape(jsonPathVal.expression, options); + + if (jsonPathVal.path.length === 0) { + return value; + } + + return this.queryGenerator.jsonPathExtractionQuery(value, jsonPathVal.path, false); + } + + formatLiteral(piece: Literal, options?: EscapeOptions): string { + const sql = piece.val + .map(part => { + if (part instanceof BaseSqlExpression) { + return this.queryGenerator.formatSqlExpression(part, options); + } + + return part; + }) + .join(''); + + if (options?.replacements) { + return injectReplacements(sql, this.dialect, options.replacements, { + onPositionalReplacement: () => { + throw new TypeError(`The following literal includes positional replacements (?). +Only named replacements (:name) are allowed in literal() because we cannot guarantee the order in which they will be evaluated: +➜ literal(${JSON.stringify(sql)})`); + }, + }); + } + + return sql; + } + + formatAttribute(piece: Attribute, options?: EscapeOptions): string { + const modelDefinition = options?.model ? extractModelDefinition(options.model) : null; + + // This handles special attribute syntaxes like $association.references$, json.paths, and attribute::casting + const columnName = + modelDefinition?.getColumnNameLoose(piece.attributeName) ?? piece.attributeName; + + if (options?.mainAlias) { + return `${this.queryGenerator.quoteIdentifier(options.mainAlias)}.${this.queryGenerator.quoteIdentifier(columnName)}`; + } + + return this.queryGenerator.quoteIdentifier(columnName); + } + + formatFn(piece: Fn, options?: EscapeOptions): string { + // arguments of a function can be anything, it's not necessarily the type of the attribute, + // so we need to remove the type from their escape options + const argEscapeOptions = + piece.args.length > 0 && options?.type ? { ...options, type: undefined } : options; + const args = piece.args + .map(arg => { + return this.queryGenerator.escape(arg, argEscapeOptions); + }) + .join(', '); + + return `${piece.fn}(${args})`; + } + + formatDialectAwareFn(piece: DialectAwareFn, options?: EscapeOptions): string { + // arguments of a function can be anything, it's not necessarily the type of the attribute, + // so we need to remove the type from their escape options + const argEscapeOptions = + piece.args.length > 0 && options?.type ? { ...options, type: undefined } : options; + + if (!piece.supportsDialect(this.dialect)) { + throw new Error( + `Function ${piece.constructor.name} is not supported by ${this.dialect.name}.`, + ); + } + + return piece.applyForDialect(this.dialect, argEscapeOptions); + } + + formatCast(cast: Cast, options?: EscapeOptions) { + const type = this.sequelize.normalizeDataType(cast.type); + + const castSql = wrapAmbiguousWhere( + cast.expression, + this.queryGenerator.escape(cast.expression, { ...options, type }), + ); + const targetSql = attributeTypeToSql(type).toUpperCase(); + + // TODO: if we're casting to the same SQL DataType, we could skip the SQL cast (but keep the JS cast) + // This is useful because sometimes you want to cast the Sequelize DataType to another Sequelize DataType, + // but they are both the same SQL type, so a SQL cast would be redundant. + + return `CAST(${castSql} AS ${targetSql})`; + } + + formatCol(piece: Col, options?: EscapeOptions) { + // TODO: can this be removed? + if (piece.identifiers.length === 1 && piece.identifiers[0].startsWith('*')) { + return '*'; + } + + // Weird legacy behavior + const identifiers = piece.identifiers.length === 1 ? piece.identifiers[0] : piece.identifiers; + + // TODO: use quoteIdentifiers? + // @ts-expect-error -- quote is declared on child class + return this.queryGenerator.quote(identifiers, options?.model, undefined, options); + } + + /** + * Returns an SQL fragment for adding result constraints. + * + * @param _options + */ + addLimitAndOffset(_options: AddLimitOffsetOptions): string { + throw new Error(`addLimitAndOffset has not been implemented in ${this.dialect.name}.`); + } +} diff --git a/packages/core/src/abstract-dialect/query-generator-typescript.ts b/packages/core/src/abstract-dialect/query-generator-typescript.ts new file mode 100644 index 000000000000..0744d4b41d7c --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator-typescript.ts @@ -0,0 +1,974 @@ +import type { RequiredBy } from '@sequelize/utils'; +import { EMPTY_OBJECT, isPlainObject, isString, join, map } from '@sequelize/utils'; +import isObject from 'lodash/isObject'; +import { randomUUID } from 'node:crypto'; +import NodeUtil from 'node:util'; +import type { Class } from 'type-fest'; +import { ConstraintChecking } from '../deferrable.js'; +import type { ParameterStyle } from '../enums.js'; +import { IndexHints, TableHints } from '../enums.js'; +import { AssociationPath } from '../expression-builders/association-path.js'; +import { Attribute } from '../expression-builders/attribute.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; +import { Cast } from '../expression-builders/cast.js'; +import { Col } from '../expression-builders/col.js'; +import { DialectAwareFn } from '../expression-builders/dialect-aware-fn.js'; +import { Fn } from '../expression-builders/fn.js'; +import { Identifier } from '../expression-builders/identifier.js'; +import { JsonPath } from '../expression-builders/json-path.js'; +import { List } from '../expression-builders/list.js'; +import { Literal } from '../expression-builders/literal.js'; +import { Value } from '../expression-builders/value.js'; +import { Where } from '../expression-builders/where.js'; +import type { ModelDefinition } from '../model-definition.js'; +import type { Attributes, Model, ModelStatic } from '../model.js'; +import { Op } from '../operators.js'; +import type { BindOrReplacements, Expression, Sequelize } from '../sequelize.js'; +import type { NormalizedOptions } from '../sequelize.types.js'; +import { bestGuessDataTypeOfVal } from '../sql-string.js'; +import type { IsolationLevel } from '../transaction.js'; +import { rejectInvalidOptions } from '../utils/check.js'; +import { noOpCol } from '../utils/deprecations.js'; +import { quoteIdentifier } from '../utils/dialect.js'; +import { joinSQLFragments } from '../utils/join-sql-fragments.js'; +import { + extractModelDefinition, + extractTableIdentifier, + isModelStatic, +} from '../utils/model-utils.js'; +import type { BindParamOptions, DataType } from './data-types.js'; +import { AbstractDataType } from './data-types.js'; +import type { AbstractDialect } from './dialect.js'; +import { AbstractQueryGeneratorInternal } from './query-generator-internal.js'; +import type { + AddConstraintQueryOptions, + BulkDeleteQueryOptions, + CreateDatabaseQueryOptions, + CreateSchemaQueryOptions, + DropSchemaQueryOptions, + DropTableQueryOptions, + ListDatabasesQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + QuoteTableOptions, + RemoveColumnQueryOptions, + RemoveConstraintQueryOptions, + RemoveIndexQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + StartTransactionQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from './query-generator.types.js'; +import type { TableNameWithSchema } from './query-interface.js'; +import type { WhereOptions } from './where-sql-builder-types.js'; +import type { WhereSqlBuilder } from './where-sql-builder.js'; +import { PojoWhere } from './where-sql-builder.js'; + +export const CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'charset', + 'collate', + 'ctype', + 'encoding', + 'template', +]); +export const CREATE_SCHEMA_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'authorization', + 'charset', + 'collate', + 'comment', + 'ifNotExists', + 'replace', +]); +export const DROP_SCHEMA_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'cascade', + 'ifExists', +]); +export const DROP_TABLE_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'cascade', +]); +export const LIST_DATABASES_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'skip', +]); +export const LIST_TABLES_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'schema', +]); +export const QUOTE_TABLE_SUPPORTABLE_OPTIONS = new Set([ + 'indexHints', + 'tableHints', +]); +export const REMOVE_COLUMN_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'ifExists', + 'cascade', +]); +export const REMOVE_CONSTRAINT_QUERY_SUPPORTABLE_OPTIONS = new Set< + keyof RemoveConstraintQueryOptions +>(['ifExists', 'cascade']); +export const REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'concurrently', + 'ifExists', + 'cascade', +]); +export const RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'changeSchema', +]); +export const SHOW_CONSTRAINTS_QUERY_SUPPORTABLE_OPTIONS = new Set< + keyof ShowConstraintsQueryOptions +>(['columnName', 'constraintName', 'constraintType']); +export const START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS = new Set< + keyof StartTransactionQueryOptions +>(['readOnly', 'transactionType']); +export const TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'cascade', + 'restartIdentity', +]); + +/** + * Options accepted by {@link AbstractQueryGeneratorTypeScript#escape} + */ +export interface EscapeOptions extends FormatWhereOptions { + readonly type?: DataType | undefined; +} + +export interface FormatWhereOptions extends Partial, ParameterOptions { + /** + * The model of the main alias. Used to determine the type & column name of attributes referenced in the where clause. + */ + readonly model?: ModelStatic | ModelDefinition | null | undefined; + + /** + * The alias of the main table corresponding to {@link FormatWhereOptions.model}. + * Used as the prefix for attributes that do not reference an association, e.g. + * + * ```ts + * const where = { name: 'foo' }; + * ``` + * + * will produce + * + * ```sql + * WHERE ""."name" = 'foo' + * ``` + */ + readonly mainAlias?: string | undefined; +} + +export interface ParameterOptions { + /** + * The style of parameter to use. + */ + readonly parameterStyle?: ParameterStyle | `${ParameterStyle}` | undefined; + /** + * These are used to inline replacements into the query, when one is found inside of a {@link sql.literal}. + */ + readonly replacements?: BindOrReplacements | undefined; +} + +// DO NOT MAKE THIS CLASS PUBLIC! +/** + * This is a temporary class used to progressively migrate the AbstractQueryGenerator class to TypeScript by slowly moving its functions here. + * Always use {@link AbstractQueryGenerator} instead. + */ +export class AbstractQueryGeneratorTypeScript { + readonly dialect: Dialect; + readonly #internals: AbstractQueryGeneratorInternal; + + constructor( + dialect: Dialect, + internals: AbstractQueryGeneratorInternal = new AbstractQueryGeneratorInternal(dialect), + ) { + this.dialect = dialect; + this.#internals = internals; + } + + get #whereGenerator(): WhereSqlBuilder { + return this.#internals.whereSqlBuilder; + } + + protected get sequelize(): Sequelize { + return this.dialect.sequelize; + } + + protected get options(): NormalizedOptions { + return this.sequelize.options; + } + + createDatabaseQuery(_database: string, _options?: CreateDatabaseQueryOptions): string { + if (this.dialect.supports.multiDatabases) { + throw new Error( + `${this.dialect.name} declares supporting databases but createDatabaseQuery is not implemented.`, + ); + } + + throw new Error(`Databases are not supported in ${this.dialect.name}.`); + } + + dropDatabaseQuery(database: string): string { + if (this.dialect.supports.multiDatabases) { + return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(database)}`; + } + + throw new Error(`Databases are not supported in ${this.dialect.name}.`); + } + + listDatabasesQuery(_options?: ListDatabasesQueryOptions): string { + if (this.dialect.supports.multiDatabases) { + throw new Error( + `${this.dialect.name} declares supporting databases but listDatabasesQuery is not implemented.`, + ); + } + + throw new Error(`Databases are not supported in ${this.dialect.name}.`); + } + + createSchemaQuery(schemaName: string, options?: CreateSchemaQueryOptions): string { + if (!this.dialect.supports.schemas) { + throw new Error(`Schemas are not supported in ${this.dialect.name}.`); + } + + if (options) { + rejectInvalidOptions( + 'createSchemaQuery', + this.dialect, + CREATE_SCHEMA_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.createSchema, + options, + ); + } + + return joinSQLFragments([ + 'CREATE', + options?.replace ? 'OR REPLACE' : '', + 'SCHEMA', + options?.ifNotExists ? 'IF NOT EXISTS' : '', + this.quoteIdentifier(schemaName), + options?.authorization + ? `AUTHORIZATION ${options.authorization instanceof Literal ? this.#internals.formatLiteral(options.authorization) : this.quoteIdentifier(options.authorization)}` + : '', + options?.charset ? `DEFAULT CHARACTER SET ${this.escape(options.charset)}` : '', + options?.collate ? `DEFAULT COLLATE ${this.escape(options.collate)}` : '', + options?.comment ? `COMMENT ${this.escape(options.comment)}` : '', + ]); + } + + dropSchemaQuery(schemaName: string, options?: DropSchemaQueryOptions): string { + if (!this.dialect.supports.schemas) { + throw new Error(`Schemas are not supported in ${this.dialect.name}.`); + } + + if (options) { + rejectInvalidOptions( + 'dropSchemaQuery', + this.dialect, + DROP_SCHEMA_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.dropSchema, + options, + ); + } + + return joinSQLFragments([ + 'DROP SCHEMA', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(schemaName), + options?.cascade ? 'CASCADE' : '', + ]); + } + + listSchemasQuery(_options?: ListSchemasQueryOptions): string { + if (this.dialect.supports.schemas) { + throw new Error( + `${this.dialect.name} declares supporting schema but listSchemasQuery is not implemented.`, + ); + } + + throw new Error(`Schemas are not supported in ${this.dialect.name}.`); + } + + describeTableQuery(tableName: TableOrModel) { + return `DESCRIBE ${this.quoteTable(tableName)};`; + } + + dropTableQuery(tableName: TableOrModel, options?: DropTableQueryOptions): string { + if (options) { + rejectInvalidOptions( + 'dropTableQuery', + this.dialect, + DROP_TABLE_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.dropTable, + options, + ); + } + + return joinSQLFragments([ + 'DROP TABLE IF EXISTS', + this.quoteTable(tableName), + options?.cascade ? 'CASCADE' : '', + ]); + } + + listTablesQuery(_options?: ListTablesQueryOptions): string { + throw new Error(`listTablesQuery has not been implemented in ${this.dialect.name}.`); + } + + renameTableQuery( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableQueryOptions, + ): string { + const beforeTable = this.extractTableDetails(beforeTableName); + const afterTable = this.extractTableDetails(afterTableName); + + if (beforeTable.schema !== afterTable.schema && !options?.changeSchema) { + throw new Error( + 'To move a table between schemas, you must set `options.changeSchema` to true.', + ); + } + + return `ALTER TABLE ${this.quoteTable(beforeTableName)} RENAME TO ${this.quoteTable(afterTableName)}`; + } + + truncateTableQuery( + _tableName: TableOrModel, + _options?: TruncateTableQueryOptions, + ): string | string[] { + throw new Error(`truncateTableQuery has not been implemented in ${this.dialect.name}.`); + } + + removeColumnQuery( + tableName: TableOrModel, + columnName: string, + options?: RemoveColumnQueryOptions, + ): string { + if (options) { + rejectInvalidOptions( + 'removeColumnQuery', + this.dialect, + REMOVE_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.removeColumn, + options, + ); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP COLUMN', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(columnName), + options?.cascade ? 'CASCADE' : '', + ]); + } + + addConstraintQuery(tableName: TableOrModel, options: AddConstraintQueryOptions): string { + if (!this.dialect.supports.constraints.add) { + throw new Error(`Add constraint queries are not supported by ${this.dialect.name} dialect`); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'ADD', + this.#internals.getConstraintSnippet(tableName, options), + ]); + } + + removeConstraintQuery( + tableName: TableOrModel, + constraintName: string, + options?: RemoveConstraintQueryOptions, + ) { + if (!this.dialect.supports.constraints.remove) { + throw new Error( + `Remove constraint queries are not supported by ${this.dialect.name} dialect`, + ); + } + + if (options) { + rejectInvalidOptions( + 'removeConstraintQuery', + this.dialect, + REMOVE_CONSTRAINT_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.constraints.removeOptions, + options, + ); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP CONSTRAINT', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(constraintName), + options?.cascade ? 'CASCADE' : '', + ]); + } + + setConstraintCheckingQuery(type: ConstraintChecking): string; + setConstraintCheckingQuery( + type: Class, + constraints?: readonly string[], + ): string; + setConstraintCheckingQuery( + type: ConstraintChecking | Class, + constraints?: readonly string[], + ) { + if (!this.dialect.supports.constraints.deferrable) { + throw new Error(`Deferrable constraints are not supported by ${this.dialect.name} dialect`); + } + + let constraintFragment = 'ALL'; + if (type instanceof ConstraintChecking) { + if (type.constraints?.length) { + constraintFragment = type.constraints + .map(constraint => this.quoteIdentifier(constraint)) + .join(', '); + } + + return `SET CONSTRAINTS ${constraintFragment} ${type.toString()}`; + } + + if (constraints?.length) { + constraintFragment = constraints + .map(constraint => this.quoteIdentifier(constraint)) + .join(', '); + } + + return `SET CONSTRAINTS ${constraintFragment} ${type.toString()}`; + } + + showConstraintsQuery(_tableName: TableOrModel, _options?: ShowConstraintsQueryOptions): string { + throw new Error(`showConstraintsQuery has not been implemented in ${this.dialect.name}.`); + } + + showIndexesQuery(_tableName: TableOrModel): string { + throw new Error(`showIndexesQuery has not been implemented in ${this.dialect.name}.`); + } + + removeIndexQuery( + _tableName: TableOrModel, + _indexNameOrAttributes: string | string[], + _options?: RemoveIndexQueryOptions, + ): string { + throw new Error(`removeIndexQuery has not been implemented in ${this.dialect.name}.`); + } + + /** + * Generates an SQL query that returns all foreign keys of a table or the foreign key constraint of a given column. + * + * @deprecated Use {@link showConstraintsQuery} instead. + * @param _tableName The table or associated model. + * @param _columnName The name of the column. Not supported by SQLite. + * @returns The generated SQL query. + */ + getForeignKeyQuery(_tableName: TableOrModel, _columnName?: string): Error { + throw new Error(`getForeignKeyQuery has been deprecated. Use showConstraintsQuery instead.`); + } + + /** + * Generates an SQL query that drops a foreign key constraint. + * + * @deprecated Use {@link removeConstraintQuery} instead. + * @param _tableName The table or associated model. + * @param _foreignKey The name of the foreign key constraint. + */ + dropForeignKeyQuery(_tableName: TableOrModel, _foreignKey: string): Error { + throw new Error(`dropForeignKeyQuery has been deprecated. Use removeConstraintQuery instead.`); + } + + /** + * Returns a query that commits a transaction. + */ + commitTransactionQuery(): string { + if (this.dialect.supports.connectionTransactionMethods) { + throw new Error( + `commitTransactionQuery is not supported by the ${this.dialect.name} dialect.`, + ); + } + + return 'COMMIT'; + } + + /** + * Returns a query that creates a savepoint. + * + * @param savepointName + */ + createSavepointQuery(savepointName: string): string { + if (!this.dialect.supports.savepoints) { + throw new Error(`Savepoints are not supported by ${this.dialect.name}.`); + } + + return `SAVEPOINT ${this.quoteIdentifier(savepointName)}`; + } + + /** + * Returns a query that rollbacks a savepoint. + * + * @param savepointName + */ + rollbackSavepointQuery(savepointName: string): string { + if (!this.dialect.supports.savepoints) { + throw new Error(`Savepoints are not supported by ${this.dialect.name}.`); + } + + return `ROLLBACK TO SAVEPOINT ${this.quoteIdentifier(savepointName)}`; + } + + /** + * Returns a query that rollbacks a transaction. + */ + rollbackTransactionQuery(): string { + if (this.dialect.supports.connectionTransactionMethods) { + throw new Error( + `rollbackTransactionQuery is not supported by the ${this.dialect.name} dialect.`, + ); + } + + return 'ROLLBACK'; + } + + /** + * Returns a query that sets the transaction isolation level. + * + * @param isolationLevel + */ + setIsolationLevelQuery(isolationLevel: IsolationLevel): string { + if (!this.dialect.supports.isolationLevels) { + throw new Error(`Isolation levels are not supported by ${this.dialect.name}.`); + } + + if (!this.dialect.supports.connectionTransactionMethods) { + return `SET TRANSACTION ISOLATION LEVEL ${isolationLevel}`; + } + + throw new Error(`setIsolationLevelQuery is not supported by the ${this.dialect.name} dialect.`); + } + + /** + * Returns a query that starts a transaction. + * + * @param options + */ + startTransactionQuery(options?: StartTransactionQueryOptions): string { + if (this.dialect.supports.connectionTransactionMethods) { + throw new Error( + `startTransactionQuery is not supported by the ${this.dialect.name} dialect.`, + ); + } + + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.startTransaction, + options, + ); + } + + return joinSQLFragments([ + this.dialect.supports.startTransaction.useBegin ? 'BEGIN' : 'START', + 'TRANSACTION', + options?.readOnly ? 'READ ONLY' : '', + ]); + } + + /** + * Generates a unique identifier for the current transaction. + */ + generateTransactionId(): string { + return randomUUID(); + } + + // TODO: rename to "normalizeTable" & move to sequelize class + extractTableDetails( + tableOrModel: TableOrModel, + options?: { schema?: string; delimiter?: string }, + ): RequiredBy { + const tableIdentifier = extractTableIdentifier(tableOrModel); + + if (!isPlainObject(tableIdentifier)) { + throw new Error( + `Invalid input received, got ${NodeUtil.inspect(tableOrModel)}, expected a Model Class, a TableNameWithSchema object, or a table name string`, + ); + } + + return { + ...tableIdentifier, + schema: + options?.schema || + tableIdentifier.schema || + this.options.schema || + this.dialect.getDefaultSchema(), + delimiter: options?.delimiter || tableIdentifier.delimiter || '.', + }; + } + + /** + * Quote table name with optional alias and schema attribution + * + * @param param table string or object + * @param options options + */ + quoteTable(param: TableOrModel, options?: QuoteTableOptions): string { + if (options) { + rejectInvalidOptions( + 'quoteTable', + this.dialect, + QUOTE_TABLE_SUPPORTABLE_OPTIONS, + { + indexHints: this.dialect.supports.indexHints, + tableHints: this.dialect.supports.tableHints, + }, + options, + ); + } + + if (isModelStatic(param)) { + param = param.table; + } + + const tableName = this.extractTableDetails(param); + + if (isObject(param) && ('as' in param || 'name' in param)) { + throw new Error( + 'parameters "as" and "name" are not allowed in the first parameter of quoteTable, pass them as the second parameter.', + ); + } + + let sql = ''; + + if (this.dialect.supports.schemas) { + // Some users sync the same set of tables in different schemas for various reasons + // They then set `searchPath` when running a query to use different schemas. + // See https://github.com/sequelize/sequelize/pull/15274#discussion_r1020770364 + // For this reason, we treat the default schema as equivalent to "no schema specified" + if (tableName.schema && tableName.schema !== this.dialect.getDefaultSchema()) { + sql += `${this.quoteIdentifier(tableName.schema)}.`; + } + + sql += this.quoteIdentifier(tableName.tableName); + } else { + const fakeSchemaPrefix = + tableName.schema && tableName.schema !== this.dialect.getDefaultSchema() + ? tableName.schema + (tableName.delimiter || '.') + : ''; + + sql += this.quoteIdentifier(fakeSchemaPrefix + tableName.tableName); + } + + if (options?.alias) { + sql += ` AS ${this.quoteIdentifier(options.alias === true ? tableName.tableName : options.alias)}`; + } + + if (options?.indexHints) { + for (const hint of options.indexHints) { + if (IndexHints[hint.type]) { + sql += ` ${IndexHints[hint.type]} INDEX (${hint.values.map(indexName => this.quoteIdentifier(indexName)).join(',')})`; + } else { + throw new Error( + `The index hint type "${hint.type}" is invalid or not supported by dialect "${this.dialect.name}".`, + ); + } + } + } + + if (options?.tableHints) { + const hints: TableHints[] = []; + for (const hint of options.tableHints) { + if (TableHints[hint]) { + hints.push(TableHints[hint]); + } else { + throw new Error( + `The table hint "${hint}" is invalid or not supported by dialect "${this.dialect.name}".`, + ); + } + } + + if (hints.length) { + sql += ` WITH (${hints.join(', ')})`; + } + } + + return sql; + } + + /** + * Adds quotes to identifier + * + * @param identifier + * @param _force + */ + // TODO: memoize last result + quoteIdentifier(identifier: string, _force?: boolean) { + return quoteIdentifier(identifier, this.dialect.TICK_CHAR_LEFT, this.dialect.TICK_CHAR_RIGHT); + } + + isSameTable(tableA: TableOrModel, tableB: TableOrModel) { + if (tableA === tableB) { + return true; + } + + tableA = this.extractTableDetails(tableA); + tableB = this.extractTableDetails(tableB); + + return tableA.tableName === tableB.tableName && tableA.schema === tableB.schema; + } + + whereQuery(where: WhereOptions>, options?: FormatWhereOptions) { + const query = this.whereItemsQuery(where, options); + if (query && query.length > 0) { + return `WHERE ${query}`; + } + + return ''; + } + + whereItemsQuery( + where: WhereOptions> | undefined, + options?: FormatWhereOptions, + ) { + return this.#whereGenerator.formatWhereOptions(where, options); + } + + formatSqlExpression(piece: BaseSqlExpression, options?: EscapeOptions): string { + if (piece instanceof Literal) { + return this.#internals.formatLiteral(piece, options); + } + + if (piece instanceof Fn) { + return this.#internals.formatFn(piece, options); + } + + if (piece instanceof List) { + return this.escapeList(piece.values, options); + } + + if (piece instanceof Value) { + return this.escape(piece.value, options); + } + + if (piece instanceof Identifier) { + return piece.values + .map(value => { + if (isString(value)) { + return this.quoteIdentifier(value); + } + + return this.quoteTable(value); + }) + .join('.'); + } + + if (piece instanceof Cast) { + return this.#internals.formatCast(piece, options); + } + + if (piece instanceof Col) { + return this.#internals.formatCol(piece, options); + } + + if (piece instanceof Attribute) { + return this.#internals.formatAttribute(piece, options); + } + + if (piece instanceof Where) { + if (piece.where instanceof PojoWhere) { + return this.#whereGenerator.formatPojoWhere(piece.where, options); + } + + return this.#whereGenerator.formatWhereOptions(piece.where, options); + } + + if (piece instanceof JsonPath) { + return this.#internals.formatJsonPath(piece, options); + } + + if (piece instanceof AssociationPath) { + return this.#internals.formatAssociationPath(piece); + } + + if (piece instanceof DialectAwareFn) { + return this.#internals.formatDialectAwareFn(piece, options); + } + + throw new Error(`Unknown sequelize method ${piece.constructor.name}`); + } + + /** + * The goal of this method is to execute the equivalent of json_unquote for the current dialect. + * + * @param _arg + * @param _options + */ + formatUnquoteJson(_arg: Expression, _options: EscapeOptions | undefined): string { + if (!this.dialect.supports.jsonOperations) { + throw new Error(`Unquoting JSON is not supported by ${this.dialect.name} dialect.`); + } + + throw new Error(`formatUnquoteJson has not been implemented in ${this.dialect.name}.`); + } + + /** + * @param _sqlExpression ⚠️ This is not an identifier, it's a raw SQL expression. It will be inlined in the query. + * @param _path The JSON path, where each item is one level of the path + * @param _unquote Whether the result should be unquoted (depending on dialect: ->> and #>> operators, json_unquote function). Defaults to `false`. + */ + jsonPathExtractionQuery( + _sqlExpression: string, + _path: ReadonlyArray, + _unquote: boolean, + ): string { + if (!this.dialect.supports.jsonOperations) { + throw new Error(`JSON Paths are not supported in ${this.dialect.name}.`); + } + + throw new Error(`jsonPathExtractionQuery has not been implemented in ${this.dialect.name}.`); + } + + /** + * Escapes a value (e.g. a string, number or date) as an SQL value (as opposed to an identifier). + * + * @param value The value to escape + * @param options The options to use when escaping the value + */ + escape(value: unknown, options: EscapeOptions = EMPTY_OBJECT): string { + if (isPlainObject(value) && Op.col in value) { + noOpCol(); + value = new Col(value[Op.col] as string); + } + + if (value instanceof BaseSqlExpression) { + return this.formatSqlExpression(value, options); + } + + if (value === undefined) { + throw new TypeError('"undefined" cannot be escaped'); + } + + let { type } = options; + if (type != null) { + type = this.sequelize.normalizeDataType(type); + } + + if ( + value === null && + // we handle null values ourselves by default, unless the data type explicitly accepts null + (!(type instanceof AbstractDataType) || !type.acceptsNull()) + ) { + if (options.bindParam) { + return options.bindParam(null); + } + + return 'NULL'; + } + + if (type == null || typeof type === 'string') { + type = bestGuessDataTypeOfVal(value, this.dialect); + } else { + type = this.sequelize.normalizeDataType(type); + } + + this.sequelize.validateValue(value, type); + + if (options.bindParam) { + return type.getBindParamSql(value, options as BindParamOptions); + } + + return type.escape(value); + } + + /** + * Escapes an array of values (e.g. strings, numbers or dates) as an SQL List of values. + * + * @param values The list of values to escape + * @param options + * + * @example + * ```ts + * const values = [1, 2, 3]; + * queryGenerator.escapeList([1, 2, 3]); // '(1, 2, 3)' + */ + escapeList(values: unknown[], options?: EscapeOptions): string { + return `(${values.map(value => this.escape(value, options)).join(', ')})`; + } + + getUuidV1FunctionCall(): string { + if (!this.dialect.supports.uuidV1Generation) { + throw new Error(`UUID V1 generation is not supported by ${this.dialect.name} dialect.`); + } + + throw new Error(`getUuidV1FunctionCall has not been implemented in ${this.dialect.name}.`); + } + + getUuidV4FunctionCall(): string { + if (!this.dialect.supports.uuidV4Generation) { + throw new Error(`UUID V4 generation is not supported by ${this.dialect.name} dialect.`); + } + + throw new Error(`getUuidV4FunctionCall has not been implemented in ${this.dialect.name}.`); + } + + getToggleForeignKeyChecksQuery(_enable: boolean): string { + throw new Error(`${this.dialect.name} does not support toggling foreign key checks`); + } + + versionQuery(): string { + throw new Error(`${this.dialect.name} did not implement versionQuery`); + } + + tableExistsQuery(tableName: TableOrModel): string { + const table = this.extractTableDetails(tableName); + + return `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = ${this.escape(table.tableName)} AND TABLE_SCHEMA = ${this.escape(table.schema)}`; + } + + bulkDeleteQuery(tableOrModel: TableOrModel, options: BulkDeleteQueryOptions): string { + const table = this.quoteTable(tableOrModel); + const modelDefinition = extractModelDefinition(tableOrModel); + const whereOptions = { ...options, model: modelDefinition }; + const whereFragment = whereOptions.where + ? this.whereQuery(whereOptions.where, whereOptions) + : ''; + + if (whereOptions.limit && !this.dialect.supports.delete.limit) { + if (!modelDefinition) { + throw new Error( + 'Using LIMIT in bulkDeleteQuery requires specifying a model or model definition.', + ); + } + + const pks = join( + map(modelDefinition.primaryKeysAttributeNames.values(), attrName => + this.quoteIdentifier(modelDefinition.getColumnName(attrName)), + ), + ', ', + ); + + const primaryKeys = modelDefinition.primaryKeysAttributeNames.size > 1 ? `(${pks})` : pks; + + return joinSQLFragments([ + `DELETE FROM ${table} WHERE ${primaryKeys} IN (`, + `SELECT ${pks} FROM ${table}`, + whereFragment, + `ORDER BY ${pks}`, + this.#internals.addLimitAndOffset(whereOptions), + ')', + ]); + } + + return joinSQLFragments([ + `DELETE FROM ${this.quoteTable(tableOrModel)}`, + whereFragment, + this.#internals.addLimitAndOffset(whereOptions), + ]); + } + + __TEST__getInternals() { + if (process.env.npm_lifecycle_event !== 'mocha') { + throw new Error('You can only access the internals of the query generator in test mode.'); + } + + return this.#internals; + } +} diff --git a/packages/core/src/abstract-dialect/query-generator.d.ts b/packages/core/src/abstract-dialect/query-generator.d.ts new file mode 100644 index 000000000000..b5b4e93e5b4c --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator.d.ts @@ -0,0 +1,135 @@ +// TODO: complete me - this file is a stub that will be completed when query-generator.ts is migrated to TS + +import type { Col } from '../expression-builders/col.js'; +import type { Literal } from '../expression-builders/literal.js'; +import type { + AttributeOptions, + FindOptions, + Model, + ModelStatic, + NormalizedAttributeOptions, + SearchPathable, +} from '../model.js'; +import type { DataType } from './data-types.js'; +import type { AbstractDialect } from './dialect.js'; +import type { + AbstractQueryGeneratorTypeScript, + ParameterOptions, +} from './query-generator-typescript.js'; +import type { AttributeToSqlOptions } from './query-generator.internal-types.js'; +import type { BoundQuery, TableOrModel } from './query-generator.types.js'; +import type { TableName } from './query-interface.js'; +import type { ColumnsDescription } from './query-interface.types.js'; +import type { WhereOptions } from './where-sql-builder-types.js'; + +type SelectOptions = FindOptions & { + model: ModelStatic; +}; + +type InsertOptions = ParameterOptions & + SearchPathable & { + exception?: boolean; + updateOnDuplicate?: string[]; + ignoreDuplicates?: boolean; + upsertKeys?: string[]; + returning?: boolean | Array; + }; + +type BulkInsertOptions = ParameterOptions & { + hasTrigger?: boolean; + updateOnDuplicate?: string[]; + ignoreDuplicates?: boolean; + upsertKeys?: string[]; + returning?: boolean | Array; +}; + +type UpdateOptions = ParameterOptions; + +type ArithmeticQueryOptions = ParameterOptions & { + returning?: boolean | Array; +}; + +// keep CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface CreateTableQueryOptions { + collate?: string; + charset?: string; + engine?: string; + rowFormat?: string; + comment?: string; + initialAutoIncrement?: number; + /** + * Used for compound unique keys. + */ + uniqueKeys?: Array<{ fields: string[] }> | { [indexName: string]: { fields: string[] } }; +} + +// keep ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface AddColumnQueryOptions { + ifNotExists?: boolean; +} + +/** + * The base class for all query generators, used to generate all SQL queries. + * + * The implementation varies between SQL dialects, and is overridden by subclasses. You can access your dialect's version + * through {@link Sequelize#queryGenerator}. + */ +export class AbstractQueryGenerator< + Dialect extends AbstractDialect = AbstractDialect, +> extends AbstractQueryGeneratorTypeScript { + quoteIdentifiers(identifiers: string): string; + + selectQuery( + tableName: TableName, + options?: SelectOptions, + model?: ModelStatic, + ): string; + insertQuery( + table: TableName, + valueHash: object, + columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions }, + options?: InsertOptions, + ): BoundQuery; + bulkInsertQuery( + tableName: TableName, + newEntries: object[], + options?: BulkInsertOptions, + columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions }, + ): string; + + addColumnQuery( + table: TableName, + columnName: string, + columnDefinition: AttributeOptions | DataType, + options?: AddColumnQueryOptions, + ): string; + + updateQuery( + tableName: TableName, + attrValueHash: object, + where: WhereOptions, + options?: UpdateOptions, + columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions }, + ): BoundQuery; + + arithmeticQuery( + operator: string, + tableName: TableName, + where: WhereOptions, + incrementAmountsByField: { [key: string]: number | Literal }, + extraAttributesToBeUpdated: { [key: string]: unknown }, + options?: ArithmeticQueryOptions, + ): string; + + createTableQuery( + tableName: TableOrModel, + // TODO: rename attributes to columns and accept a map of attributes in the implementation when migrating to TS, see https://github.com/sequelize/sequelize/pull/15526/files#r1143840411 + columns: { [columnName: string]: string }, + options?: CreateTableQueryOptions, + ): string; + + attributesToSQL( + attributes: ColumnsDescription, + options?: AttributeToSqlOptions, + ): Record; +} diff --git a/packages/core/src/abstract-dialect/query-generator.internal-types.ts b/packages/core/src/abstract-dialect/query-generator.internal-types.ts new file mode 100644 index 000000000000..4b7566964742 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator.internal-types.ts @@ -0,0 +1,16 @@ +import type { Nullish } from '@sequelize/utils'; +import type { Literal } from '../expression-builders/literal.js'; +import type { BindOrReplacements } from '../sequelize.js'; + +export interface AddLimitOffsetOptions { + limit?: number | Literal | Nullish; + offset?: number | Literal | Nullish; + replacements?: BindOrReplacements | undefined; +} + +export interface AttributeToSqlOptions { + context: 'addColumn' | 'changeColumn' | 'createTable'; + schema?: string; + table: string; + withoutForeignKeyConstraints?: boolean; +} diff --git a/packages/core/src/abstract-dialect/query-generator.js b/packages/core/src/abstract-dialect/query-generator.js new file mode 100644 index 000000000000..17353c98b250 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator.js @@ -0,0 +1,2693 @@ +'use strict'; + +import compact from 'lodash/compact'; +import defaults from 'lodash/defaults'; +import each from 'lodash/each'; +import forOwn from 'lodash/forOwn'; +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import isObject from 'lodash/isObject'; +import isPlainObject from 'lodash/isPlainObject'; +import pick from 'lodash/pick'; +import reduce from 'lodash/reduce'; +import uniq from 'lodash/uniq'; +import NodeUtil from 'node:util'; +import { Association } from '../associations/base'; +import { BelongsToAssociation } from '../associations/belongs-to'; +import { BelongsToManyAssociation } from '../associations/belongs-to-many'; +import { HasManyAssociation } from '../associations/has-many'; +import { ParameterStyle } from '../enums.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; +import { Col } from '../expression-builders/col.js'; +import { Literal } from '../expression-builders/literal.js'; +import { conformIndex } from '../model-internals'; +import { and } from '../sequelize'; +import { mapFinderOptions, removeNullishValuesFromHash } from '../utils/format'; +import { joinSQLFragments } from '../utils/join-sql-fragments'; +import { isModelStatic } from '../utils/model-utils'; +import { createBindParamGenerator } from '../utils/sql.js'; +import { nameIndex, spliceStr } from '../utils/string'; +import { attributeTypeToSql } from './data-types-utils'; +import { AbstractQueryGeneratorInternal } from './query-generator-internal.js'; +import { AbstractQueryGeneratorTypeScript } from './query-generator-typescript'; +import { joinWithLogicalOperator } from './where-sql-builder'; + +const util = require('node:util'); +const crypto = require('node:crypto'); + +const DataTypes = require('../data-types'); +const { Op } = require('../operators'); +const sequelizeError = require('../errors'); +const { _validateIncludedElements } = require('../model-internals'); + +export const CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS = new Set([ + 'collate', + 'charset', + 'engine', + 'rowFormat', + 'comment', + 'initialAutoIncrement', + 'uniqueKeys', +]); +export const ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS = new Set(['ifNotExists']); + +const VALID_ORDER_OPTIONS = [ + 'ASC', + 'DESC', + 'ASC NULLS LAST', + 'DESC NULLS LAST', + 'ASC NULLS FIRST', + 'DESC NULLS FIRST', + 'NULLS FIRST', + 'NULLS LAST', +]; + +function isOrderDirection(value) { + return typeof value === 'string' && VALID_ORDER_OPTIONS.includes(value.toUpperCase()); +} + +function isModelAttribute(value) { + return value?._modelAttribute === true; +} + +/** + * Abstract Query Generator + * + * @private + */ +export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript { + #internals; + + constructor(dialect, internals = new AbstractQueryGeneratorInternal(dialect)) { + super(dialect, internals); + this.#internals = internals; + } + + /** + * Returns an insert into command + * + * @param {string} table + * @param {object} valueHash attribute value pairs + * @param {object} modelAttributes + * @param {object} [options] + * + * @private + */ + insertQuery(table, valueHash, modelAttributes, options) { + options ||= {}; + defaults(options, this.options); + if ('bindParam' in options) { + throw new Error('The bindParam option has been removed. Use parameterStyle instead.'); + } + + const modelAttributeMap = {}; + const fields = []; + const returningModelAttributes = []; + const values = Object.create(null); + const quotedTable = this.quoteTable(table); + let bind; + let bindParam; + let parameterStyle = options?.parameterStyle ?? ParameterStyle.BIND; + let query; + let valueQuery = ''; + let emptyQuery = ''; + let outputFragment = ''; + let returningFragment = ''; + let identityWrapperRequired = false; + let tmpTable = ''; // tmpTable declaration for trigger + + if (modelAttributes) { + each(modelAttributes, (attribute, key) => { + modelAttributeMap[key] = attribute; + if (attribute.field) { + modelAttributeMap[attribute.field] = attribute; + } + }); + } + + if (this.dialect.supports['DEFAULT VALUES']) { + emptyQuery += ' DEFAULT VALUES'; + } else if (this.dialect.supports['VALUES ()']) { + emptyQuery += ' VALUES ()'; + } + + if (this.dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(modelAttributes, options); + + returningModelAttributes.push(...returnValues.returnFields); + returningFragment = returnValues.returningFragment; + tmpTable = returnValues.tmpTable || ''; + outputFragment = returnValues.outputFragment || ''; + } + + if (get(this, ['sequelize', 'options', 'prependSearchPath']) || options.searchPath) { + // Not currently supported with search path (requires output of multiple queries) + parameterStyle = ParameterStyle.REPLACEMENT; + } + + if (this.dialect.supports.EXCEPTION && options.exception) { + // Not currently supported with bind parameters (requires output of multiple queries) + parameterStyle = ParameterStyle.REPLACEMENT; + } + + if (parameterStyle === ParameterStyle.BIND) { + bind = Object.create(null); + bindParam = createBindParamGenerator(bind); + } + + valueHash = removeNullishValuesFromHash(valueHash, this.options.omitNull); + for (const key in valueHash) { + if (Object.hasOwn(valueHash, key)) { + // if value is undefined, we replace it with null + const value = valueHash[key] ?? null; + fields.push(this.quoteIdentifier(key)); + + // SERIALS' can't be NULL in postgresql, use DEFAULT where supported + if ( + modelAttributeMap[key] && + modelAttributeMap[key].autoIncrement === true && + value == null + ) { + if (!this.dialect.supports.autoIncrement.defaultValue) { + fields.splice(-1, 1); + } else if (this.dialect.supports.DEFAULT) { + values[key] = 'DEFAULT'; + } else { + values[key] = this.escape(null); + } + } else { + if (modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true) { + identityWrapperRequired = true; + } + + values[key] = this.escape(value, { + model: options.model, + type: modelAttributeMap[key]?.type, + replacements: options.replacements, + bindParam, + }); + } + } + } + + let onDuplicateKeyUpdate = ''; + + if (!isEmpty(options.conflictWhere) && !this.dialect.supports.inserts.onConflictWhere) { + throw new Error('missing dialect support for conflictWhere option'); + } + + // `options.updateOnDuplicate` is the list of field names to update if a duplicate key is hit during the insert. It + // contains just the field names. This option is _usually_ explicitly set by the corresponding query-interface + // upsert function. + 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)}`, + ); + + const fragments = ['ON CONFLICT', '(', conflictKeys.join(','), ')']; + + if (!isEmpty(options.conflictWhere)) { + fragments.push(this.whereQuery(options.conflictWhere, options)); + } + + // if update keys are provided, then apply them here. if there are no updateKeys provided, then do not try to + // do an update. Instead, fall back to DO NOTHING. + if (isEmpty(updateKeys)) { + fragments.push('DO NOTHING'); + } else { + fragments.push('DO UPDATE SET', updateKeys.join(',')); + } + + onDuplicateKeyUpdate = ` ${joinSQLFragments(fragments)}`; + } else { + const valueKeys = options.updateOnDuplicate.map( + attr => `${this.quoteIdentifier(attr)}=${values[attr]}`, + ); + // the rough equivalent to ON CONFLICT DO NOTHING in mysql, etc is ON DUPLICATE KEY UPDATE id = id + // So, if no update values were provided, fall back to the identifier columns provided in the upsertKeys array. + // This will be the primary key in most cases, but it could be some other constraint. + if (isEmpty(valueKeys) && options.upsertKeys) { + valueKeys.push( + ...options.upsertKeys.map( + attr => `${this.quoteIdentifier(attr)}=${this.quoteIdentifier(attr)}`, + ), + ); + } + + // edge case... but if for some reason there were no valueKeys, and there were also no upsertKeys... then we + // can no longer build the requested query without a syntax error. Let's throw something more graceful here + // so the devs know what the problem is. + if (isEmpty(valueKeys)) { + throw new Error( + 'No update values found for ON DUPLICATE KEY UPDATE clause, and no identifier fields could be found to use instead.', + ); + } + + 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 + : '', + attributes: fields.join(','), + output: outputFragment, + values: Object.values(values).join(','), + tmpTable, + }; + + valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`; + emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`; + + // Mostly for internal use, so we expect the user to know what he's doing! + // pg_temp functions are private per connection, so we never risk this function interfering with another one. + if (this.dialect.supports.EXCEPTION && options.exception) { + const dropFunction = 'DROP FUNCTION IF EXISTS pg_temp.testfunc()'; + + if (returningModelAttributes.length === 0) { + returningModelAttributes.push('*'); + } + + const delimiter = `$func_${crypto.randomUUID().replaceAll('-', '')}$`; + const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; + + options.exception = + 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; + valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter} BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; + } else { + valueQuery += returningFragment; + emptyQuery += returningFragment; + } + + query = `${`${replacements.attributes.length > 0 ? valueQuery : emptyQuery}`.trim()};`; + if (this.dialect.supports.finalTable) { + query = `SELECT * FROM FINAL TABLE (${replacements.attributes.length > 0 ? valueQuery : emptyQuery});`; + } + + if (identityWrapperRequired && this.dialect.supports.autoIncrement.identityInsert) { + query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`; + } + + // Used by Postgres upsertQuery and calls to here with options.exception set to true + const result = { query }; + if (parameterStyle === ParameterStyle.BIND) { + result.bind = bind; + } + + return result; + } + + /** + * Returns an insert into command for multiple values. + * + * @param {string} tableName + * @param {object} fieldValueHashes + * @param {object} options + * @param {object} fieldMappedAttributes + * + * @private + */ + bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { + options ||= {}; + fieldMappedAttributes ||= {}; + + const tuples = []; + const serials = {}; + const allAttributes = []; + let onDuplicateKeyUpdate = ''; + + for (const fieldValueHash of fieldValueHashes) { + forOwn(fieldValueHash, (value, key) => { + if (!allAttributes.includes(key)) { + allAttributes.push(key); + } + + if (fieldMappedAttributes[key] && fieldMappedAttributes[key].autoIncrement === true) { + serials[key] = true; + } + }); + } + + for (const fieldValueHash of fieldValueHashes) { + const values = allAttributes.map(key => { + if (this.dialect.supports.bulkDefault && serials[key] === true) { + // fieldValueHashes[key] ?? 'DEFAULT' + return fieldValueHash[key] != null ? fieldValueHash[key] : 'DEFAULT'; + } + + return this.escape(fieldValueHash[key] ?? null, { + // model // TODO: make bulkInsertQuery accept model instead of fieldValueHashes + // bindParam // TODO: support bind params + type: fieldMappedAttributes[key]?.type, + replacements: options.replacements, + }); + }); + + tuples.push(`(${values.join(',')})`); + } + + // `options.updateOnDuplicate` is the list of field names to update if a duplicate key is hit during the insert. It + // contains just the field names. This option is _usually_ explicitly set by the corresponding query-interface + // upsert function. + 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)}`, + ); + + let whereClause = false; + if (options.conflictWhere) { + if (!this.dialect.supports.inserts.onConflictWhere) { + throw new Error(`conflictWhere not supported for dialect ${this.dialect.name}`); + } + + whereClause = this.whereQuery(options.conflictWhere, options); + } + + // The Utils.joinSQLFragments later on will join this as it handles nested arrays. + onDuplicateKeyUpdate = [ + 'ON CONFLICT', + '(', + conflictKeys.join(','), + ')', + whereClause, + 'DO UPDATE SET', + updateKeys.join(','), + ]; + } else { + // mysql / maria + if (options.conflictWhere) { + throw new Error(`conflictWhere not supported for dialect ${this.dialect.name}`); + } + + const valueKeys = options.updateOnDuplicate.map( + attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`, + ); + onDuplicateKeyUpdate = `${this.dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`; + } + } + + const ignoreDuplicates = options.ignoreDuplicates + ? this.dialect.supports.inserts.ignoreDuplicates + : ''; + const attributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(','); + const onConflictDoNothing = options.ignoreDuplicates + ? this.dialect.supports.inserts.onConflictDoNothing + : ''; + let returning = ''; + + if (this.dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(fieldMappedAttributes, options); + + returning += returnValues.returningFragment; + } + + return 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} columnDefinitions + * + * @private + */ + updateQuery(tableName, attrValueHash, where, options, columnDefinitions) { + options ||= {}; + defaults(options, this.options); + if ('bindParam' in options) { + throw new Error('The bindParam option has been removed. Use parameterStyle instead.'); + } + + attrValueHash = removeNullishValuesFromHash(attrValueHash, options.omitNull, options); + + const values = []; + const modelAttributeMap = {}; + let bind; + let bindParam; + let parameterStyle = options?.parameterStyle ?? ParameterStyle.BIND; + let outputFragment = ''; + let tmpTable = ''; // tmpTable declaration for trigger + let suffix = ''; + + if (get(this, ['sequelize', 'options', 'prependSearchPath']) || options.searchPath) { + // Not currently supported with search path (requires output of multiple queries) + parameterStyle = ParameterStyle.REPLACEMENT; + } + + if (parameterStyle === ParameterStyle.BIND) { + bind = Object.create(null); + bindParam = createBindParamGenerator(bind); + } + + if ( + this.dialect.supports['LIMIT ON UPDATE'] && + options.limit && + this.dialect.name !== 'mssql' && + this.dialect.name !== 'db2' + ) { + // TODO: use bind parameter + suffix = ` LIMIT ${this.escape(options.limit, options)} `; + } + + if (this.dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(columnDefinitions, options); + + suffix += returnValues.returningFragment; + tmpTable = returnValues.tmpTable || ''; + outputFragment = returnValues.outputFragment || ''; + + // ensure that the return output is properly mapped to model fields. + if (this.dialect.supports.returnValues !== 'output' && options.returning) { + options.mapToModel = true; + } + } + + if (columnDefinitions) { + each(columnDefinitions, (attribute, key) => { + modelAttributeMap[key] = attribute; + if (attribute.field) { + modelAttributeMap[attribute.field] = attribute; + } + }); + } + + for (const key in attrValueHash) { + if ( + modelAttributeMap && + modelAttributeMap[key] && + modelAttributeMap[key].autoIncrement === true && + !this.dialect.supports.autoIncrement.update + ) { + // not allowed to update identity column + continue; + } + + const value = attrValueHash[key] ?? null; + + values.push( + `${this.quoteIdentifier(key)}=${this.escape(value, { + // model // TODO: receive modelDefinition instead of columnDefinitions + type: modelAttributeMap?.[key]?.type, + replacements: options.replacements, + bindParam, + })}`, + ); + } + + const whereOptions = { ...options, bindParam }; + + if (values.length === 0) { + return { query: '' }; + } + + const query = + `${tmpTable}UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where, whereOptions)}${suffix}`.trim(); + + // Used by Postgres upsertQuery and calls to here with options.exception set to true + const result = { query }; + if (parameterStyle === ParameterStyle.BIND) { + result.bind = bind; + } + + return result; + } + + /** + * 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} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} incrementAmountsByAttribute A plain-object with attribute-value-pairs + * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs + * @param {object} options + * + * @private + */ + arithmeticQuery( + operator, + tableName, + where, + incrementAmountsByAttribute, + extraAttributesToBeUpdated, + options, + ) { + // TODO: this method should delegate to `updateQuery` + + options ||= {}; + defaults(options, { returning: true }); + const { model } = options; + + // TODO: add attribute DataType + // TODO: add model + const escapeOptions = pick(options, ['replacements', 'model']); + + extraAttributesToBeUpdated = removeNullishValuesFromHash( + extraAttributesToBeUpdated, + this.options.omitNull, + ); + + let outputFragment = ''; + let returningFragment = ''; + + if (this.dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(null, options); + + outputFragment = returnValues.outputFragment; + returningFragment = returnValues.returningFragment; + } + + const updateSetSqlFragments = []; + for (const attributeName in incrementAmountsByAttribute) { + const columnName = model + ? model.modelDefinition.getColumnNameLoose(attributeName) + : attributeName; + const incrementAmount = incrementAmountsByAttribute[columnName]; + const quotedField = this.quoteIdentifier(columnName); + const escapedAmount = this.escape(incrementAmount, escapeOptions); + updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`); + } + + for (const attributeName in extraAttributesToBeUpdated) { + const columnName = model + ? model.modelDefinition.getColumnNameLoose(attributeName) + : attributeName; + + const newValue = extraAttributesToBeUpdated[columnName]; + const quotedField = this.quoteIdentifier(columnName); + const escapedValue = this.escape(newValue, escapeOptions); + updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); + } + + return joinSQLFragments([ + 'UPDATE', + this.quoteTable(tableName), + 'SET', + updateSetSqlFragments.join(','), + outputFragment, + this.whereQuery(where, escapeOptions), + returningFragment, + ]); + } + + /* + Returns an add index query. + Parameters: + - tableName -> Name of an existing table, possibly with schema. + - options: + - type: UNIQUE|FULLTEXT|SPATIAL + - name: The name of the index. Default is
UNIQUECHECKDefault - MSSQL onlyPrimary KeyForeign KeyComposite Foreign Keyhow to create scopesTo invoke scope functions you can doSimple search using AND and =Using greater than, less than etc.Queries using ORincrement number by 1increment number and count by 2increment answer by 42, and decrement tries by 1decrement number by 1decrement number and count by 2decrement answer by 42, and decrement tries by -1Convert a user's username to upper caseA 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:Postgres also supports specific locks while eager loading by using OF:You can also skip locked rows:
__ + - fields: An array of attributes as string or as hash. + If the attribute is a hash, it must have the following content: + - name: The name of the attribute/column + - length: An integer. Optional + - order: 'ASC' or 'DESC'. Optional + - parser + - using + - operator + - concurrently: Pass CONCURRENT so other operations run while the index is created + - include + - rawTablename, the name of the table, without schema. Used to create the name of the index + @private + */ + addIndexQuery(tableName, attributes, options, rawTablename) { + options ||= {}; + + if (!Array.isArray(attributes)) { + options = attributes; + attributes = undefined; + } else { + options.fields = attributes; + } + + options.prefix = options.prefix || rawTablename || tableName; + if (options.prefix && typeof options.prefix === 'string') { + options.prefix = options.prefix.replaceAll('.', '_'); + } + + const fieldsSql = options.fields.map(field => { + if (field instanceof BaseSqlExpression) { + return this.formatSqlExpression(field); + } + + if (typeof field === 'string') { + field = { + name: field, + }; + } + + let result = ''; + + if (field.attribute) { + field.name = field.attribute; + } + + if (!field.name) { + throw new Error(`The following index field has no name: ${util.inspect(field)}`); + } + + result += this.quoteIdentifier(field.name); + + if (this.dialect.supports.index.collate && field.collate) { + 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 > 0 && field.length > 0) { + result += `(${field.length})`; + } + + if (field.order) { + result += ` ${field.order}`; + } + + return result; + }); + + let includeSql; + if (options.include) { + if (!this.dialect.supports.index.include) { + throw new Error( + `The include attribute for indexes is not supported by ${this.dialect.name} dialect`, + ); + } + + if (options.include instanceof Literal) { + includeSql = `INCLUDE ${options.include.val}`; + } else if (Array.isArray(options.include)) { + includeSql = `INCLUDE (${options.include.map(field => (field instanceof Literal ? field.val : this.quoteIdentifier(field))).join(', ')})`; + } else { + throw new TypeError('The include attribute for indexes must be an array or a literal.'); + } + } + + if (!options.name) { + // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) + // All calls that go through sequelize should already have a name + options = nameIndex(options, options.prefix); + } + + options = conformIndex(options); + + if (!this.dialect.supports.index.type) { + delete options.type; + } + + if (options.where) { + options.where = this.whereQuery(options.where); + } + + const escapedTableName = this.quoteTable(tableName); + + const concurrently = + this.dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined; + let ind; + if (this.dialect.supports.indexViaAlter) { + ind = ['ALTER TABLE', escapedTableName, concurrently, 'ADD']; + } else { + ind = ['CREATE']; + } + + // DB2 incorrectly scopes the index if we don't specify the schema name, + // which will cause it to error if another schema contains a table that uses an index with an identical name + const escapedIndexName = + tableName.schema && this.dialect.name === 'db2' + ? // 'quoteTable' isn't the best name: it quotes any identifier. + // in this case, the goal is to produce '"schema_name"."index_name"' to scope the index in this schema + this.quoteTable({ + schema: tableName.schema, + tableName: options.name, + }) + : this.quoteIdentifiers(options.name); + + ind = ind.concat( + options.unique ? 'UNIQUE' : '', + options.type, + 'INDEX', + !this.dialect.supports.indexViaAlter ? concurrently : undefined, + escapedIndexName, + this.dialect.supports.index.using === 1 && options.using ? `USING ${options.using}` : '', + !this.dialect.supports.indexViaAlter ? `ON ${escapedTableName}` : undefined, + this.dialect.supports.index.using === 2 && options.using ? `USING ${options.using}` : '', + `(${fieldsSql.join(', ')})`, + this.dialect.supports.index.parser && options.parser + ? `WITH PARSER ${options.parser}` + : undefined, + this.dialect.supports.index.include && options.include ? includeSql : undefined, + this.dialect.supports.index.where && options.where ? options.where : undefined, + ); + + return compact(ind).join(' '); + } + + /* + Quote an object based on its type. This is a more general version of quoteIdentifiers + Strings: should proxy to quoteIdentifiers + Arrays: + * Expects array in the form: [ (optional), (optional),... String, String (optional)] + Each can be a model, or an object {model: Model, as: String}, matching include, or an + association object, or the name of an association. + * Zero or more models can be included in the array and are used to trace a path through the tree of + included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL + and quotes it. + * If a single string is appended to end of array, it is quoted. + If two strings appended, the 1st string is quoted, the 2nd string unquoted. + Objects: + * If raw is set, that value should be returned verbatim, without quoting + * If fn is set, the string should start with the value of fn, starting paren, followed by + the values of cols (which is assumed to be an array), quoted and joined with ', ', + unless they are themselves objects + * If direction is set, should be prepended + + Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could + potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values) + @private + */ + quote(collection, parent, connector = '.', options) { + // init + const validOrderOptions = VALID_ORDER_OPTIONS; + + // just quote as identifiers if string + if (typeof collection === 'string') { + return this.quoteIdentifiers(collection); + } + + if (Array.isArray(collection)) { + // iterate through the collection and mutate objects into associations + collection.forEach((item, index) => { + const previous = collection[index - 1]; + let previousAssociation; + let previousModel; + + // set the previous as the parent when previous is undefined or the target of the association + if (!previous && parent !== undefined) { + previousModel = parent; + } else if (previous && previous instanceof Association) { + previousAssociation = previous; + previousModel = previous.target; + } + + // if the previous item is a model, then attempt getting an association + if (isModelStatic(previousModel)) { + let model; + let as; + + if (isModelStatic(item)) { + // set + model = item; + } else if (isPlainObject(item) && item.model && isModelStatic(item.model)) { + // set + model = item.model; + as = item.as; + } + + if (model) { + // set the as to either the through name or the model name + if ( + !as && + previousAssociation && + previousAssociation instanceof Association && + previousAssociation.through?.model === model + ) { + // we get here for cases like + // [manyToManyAssociation, throughModel] + // "throughModel" must be replaced by the association from the many to many to the through model + item = previousAssociation.fromSourceToThroughOne; + } else { + // get association from previous model + item = previousModel.getAssociationWithModel(model, as); + } + + // make sure we have an association + if (!(item instanceof Association)) { + throw new TypeError( + `Unable to find a valid association between models "${previousModel.name}" and "${model.name}"`, + ); + } + } + } + + if (typeof item === 'string') { + // get order index + const orderIndex = validOrderOptions.indexOf(item.toUpperCase()); + + // see if this is an order + if (index > 0 && orderIndex !== -1) { + item = new Literal(` ${validOrderOptions[orderIndex]}`); + } else if (isModelStatic(previousModel)) { + const { modelDefinition: previousModelDefinition } = previousModel; + + // only go down this path if we have previous model and check only once + if (previousModel.associations?.[item]) { + // convert the item to an association + item = previousModel.associations[item]; + } else if (previousModelDefinition.attributes.has(item)) { + // convert the item attribute from its alias + item = previousModelDefinition.attributes.get(item).columnName; + } else if (item.includes('.')) { + const itemSplit = item.split('.'); + + const jsonAttribute = previousModelDefinition.attributes.get(itemSplit[0]); + if (jsonAttribute.type instanceof DataTypes.JSON) { + // just quote identifiers for now + const identifier = this.quoteIdentifiers( + `${previousModel.name}.${jsonAttribute.columnName}`, + ); + + // get path + const path = itemSplit.slice(1); + + // extract path + item = this.jsonPathExtractionQuery(identifier, path); + + // literal because we don't want to append the model name when string + item = new Literal(item); + } + } + } + } + + collection[index] = item; + }); + + // loop through array, adding table names of models to quoted + const collectionLength = collection.length; + const tableNames = []; + let item; + let i = 0; + + for (i = 0; i < collectionLength - 1; i++) { + item = collection[i]; + if (typeof item === 'string' || item._modelAttribute || item instanceof BaseSqlExpression) { + break; + } else if (item instanceof Association) { + const previousAssociation = collection[i - 1]; + + // BelongsToManyAssociation.throughModel are a special case. We want + // through model to be loaded under the model's name instead of the association name, + // because we want them to be available under the model's name in the entity's data. + if ( + previousAssociation instanceof BelongsToManyAssociation && + item === previousAssociation.fromSourceToThroughOne + ) { + tableNames[i] = previousAssociation.throughModel.name; + } else { + tableNames[i] = item.as; + } + } + } + + // start building sql + let sql = ''; + + if (i > 0) { + sql += `${this.quoteIdentifier(tableNames.join(connector))}.`; + } else if (typeof collection[0] === 'string' && parent) { + sql += `${this.quoteIdentifier(parent.name)}.`; + } + + // loop through everything past i and append to the sql + for (const collectionItem of collection.slice(i)) { + sql += this.quote(collectionItem, parent, connector, options); + } + + return sql; + } + + if (collection._modelAttribute) { + return `${this.quoteTable(collection.Model.name)}.${this.quoteIdentifier(collection.fieldName)}`; + } + + if (collection instanceof BaseSqlExpression) { + return this.formatSqlExpression(collection, options); + } + + if (isPlainObject(collection) && collection.raw) { + // simple objects with raw is no longer supported + throw new Error( + 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.', + ); + } + + throw new Error(`Unknown structure passed to order / group: ${util.inspect(collection)}`); + } + + /** + * Split a list of identifiers by "." and quote each part. + * + * ⚠️ You almost certainly want to use `quoteIdentifier` instead! + * This method splits the identifier by "." into multiple identifiers, and has special meaning for "*". + * This behavior should never be the default and should be explicitly opted into by using {@link sql.col}. + * + * @param {string} identifiers + * + * @returns {string} + */ + quoteIdentifiers(identifiers) { + if (identifiers.includes('.')) { + identifiers = identifiers.split('.'); + + const head = identifiers.slice(0, -1).join('->'); + const tail = identifiers.at(-1); + + return `${this.quoteIdentifier(head)}.${tail === '*' ? '*' : this.quoteIdentifier(tail)}`; + } + + if (identifiers === '*') { + return '*'; + } + + return this.quoteIdentifier(identifiers); + } + + /* + Returns a query for selecting elements in the table . + Options: + - attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: * + - where -> A hash with conditions (e.g. {name: 'foo'}) + OR an ID as integer + - order -> e.g. 'id DESC' + - group + - limit -> The maximum count you want to get. + - offset -> An offset value to start from. Only useable with limit! + @private + */ + selectQuery(tableName, options, model) { + options ||= {}; + const limit = options.limit; + const mainQueryItems = []; + const subQueryItems = []; + const subQuery = + options.subQuery === undefined ? limit && options.hasMultiAssociation : options.subQuery; + const attributes = { + main: options.attributes && [...options.attributes], + subQuery: null, + }; + const mainTable = { + name: tableName, + quotedName: null, + as: null, + quotedAs: null, + model, + }; + const topLevelInfo = { + names: mainTable, + options, + subQuery, + }; + let mainJoinQueries = []; + let subJoinQueries = []; + let query; + + // Aliases can be passed through subqueries and we don't want to reset them + if (options.minifyAliases && !options.aliasesMapping) { + options.aliasesMapping = new Map(); + options.aliasesByTable = {}; + options.includeAliases = new Map(); + } + + // resolve table name options + if (options.tableAs) { + mainTable.as = options.tableAs; + } else if (!Array.isArray(mainTable.name) && mainTable.model) { + mainTable.as = mainTable.model.name; + } + + mainTable.quotedAs = mainTable.as && this.quoteIdentifier(mainTable.as); + + mainTable.quotedName = !Array.isArray(mainTable.name) + ? this.quoteTable(mainTable.name, { ...options, alias: mainTable.as ?? false }) + : tableName + .map(t => { + return Array.isArray(t) + ? this.quoteTable(t[0], { ...options, alias: t[1] }) + : this.quoteTable(t, { ...options, alias: true }); + }) + .join(', '); + + const mainModelDefinition = mainTable.model?.modelDefinition; + const mainModelAttributes = mainModelDefinition?.attributes; + + if (subQuery && attributes.main) { + for (const pkAttrName of mainModelDefinition.primaryKeysAttributeNames) { + // Check if mainAttributes contain the primary key of the model either as a field or an aliased field + if ( + !attributes.main.some( + attr => pkAttrName === attr || pkAttrName === attr[0] || pkAttrName === attr[1], + ) + ) { + const attribute = mainModelAttributes.get(pkAttrName); + attributes.main.push( + attribute.columnName !== pkAttrName ? [pkAttrName, attribute.columnName] : pkAttrName, + ); + } + } + } + + attributes.main = this.escapeAttributes(attributes.main, options, mainTable.as); + attributes.main = attributes.main || (options.include ? [`${mainTable.quotedAs}.*`] : ['*']); + + // If subquery, we add the mainAttributes to the subQuery and set the mainAttributes to select * from subquery + if (subQuery || options.groupedLimit) { + // We need primary keys + attributes.subQuery = attributes.main; + attributes.main = [`${mainTable.quotedAs || mainTable.quotedName}.*`]; + } + + if (options.include) { + for (const include of options.include) { + if (include.separate) { + continue; + } + + const joinQueries = this.generateInclude( + include, + { externalAs: mainTable.as, internalAs: mainTable.as }, + topLevelInfo, + { replacements: options.replacements, minifyAliases: options.minifyAliases }, + ); + + subJoinQueries = subJoinQueries.concat(joinQueries.subQuery); + mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); + + if (joinQueries.attributes.main.length > 0) { + attributes.main = uniq(attributes.main.concat(joinQueries.attributes.main)); + } + + if (joinQueries.attributes.subQuery.length > 0) { + attributes.subQuery = uniq(attributes.subQuery.concat(joinQueries.attributes.subQuery)); + } + } + } + + if (subQuery) { + subQueryItems.push( + this.selectFromTableFragment( + options, + mainTable.model, + attributes.subQuery, + mainTable.quotedName, + mainTable.quotedAs, + ), + subJoinQueries.join(''), + ); + } else { + if (options.groupedLimit) { + if (!mainTable.quotedAs) { + mainTable.quotedAs = mainTable.quotedName; + } + + if (!mainTable.as) { + mainTable.as = mainTable.name; + } + + let where = { ...options.where }; + let groupedLimitOrder; + let whereKey; + let include; + let groupedTableName = mainTable.as; + + if (typeof options.groupedLimit.on === 'string') { + whereKey = options.groupedLimit.on; + } else if (options.groupedLimit.on instanceof HasManyAssociation) { + whereKey = options.groupedLimit.on.identifierField; + } + + // TODO: do not use a placeholder! + const placeholder = '"$PLACEHOLDER$" = true'; + + if (options.groupedLimit.on instanceof BelongsToManyAssociation) { + // BTM includes needs to join the through table on to check ID + groupedTableName = options.groupedLimit.on.throughModel.name; + + const groupedLimitOptions = _validateIncludedElements({ + include: [ + { + as: options.groupedLimit.on.throughModel.name, + association: options.groupedLimit.on.fromSourceToThrough, + duplicating: false, // The UNION'ed query may contain duplicates, but each sub-query cannot + required: true, + where: and(new Literal(placeholder), options.groupedLimit.through?.where), + }, + ], + model, + }); + + // Make sure attributes from the join table are mapped back to models + options.hasJoin = true; + options.hasMultiAssociation = true; + options.includeMap = Object.assign(groupedLimitOptions.includeMap, options.includeMap); + options.includeNames = groupedLimitOptions.includeNames.concat( + options.includeNames || [], + ); + include = groupedLimitOptions.include; + + if (Array.isArray(options.order)) { + // We need to make sure the order by attributes are available to the parent query + options.order.forEach((order, i) => { + if (Array.isArray(order)) { + order = order[0]; + } + + let alias = `subquery_order_${i}`; + options.attributes.push([order, alias]); + + // We don't want to prepend model name when we alias the attributes, so quote them here + alias = new Literal(this.quote(alias, undefined, undefined, options)); + + if (Array.isArray(options.order[i])) { + options.order[i][0] = alias; + } else { + options.order[i] = alias; + } + }); + groupedLimitOrder = options.order; + } + } else { + // Ordering is handled by the subqueries, so ordering the UNION'ed result is not needed + groupedLimitOrder = options.order; + delete options.order; + where = and(new Literal(placeholder), where); + } + + // Caching the base query and splicing the where part into it is consistently > twice + // as fast than generating from scratch each time for values.length >= 5 + const baseQuery = `SELECT * FROM (${this.selectQuery( + tableName, + { + attributes: options.attributes, + offset: options.offset, + limit: options.groupedLimit.limit, + order: groupedLimitOrder, + minifyAliases: options.minifyAliases, + aliasesMapping: options.aliasesMapping, + aliasesByTable: options.aliasesByTable, + where, + include, + model, + }, + model, + ).replace(/;$/, '')}) AS sub`; // Every derived table must have its own alias + const splicePos = baseQuery.indexOf(placeholder); + + mainQueryItems.push( + this.selectFromTableFragment( + options, + mainTable.model, + attributes.main, + `(${options.groupedLimit.values + .map(value => { + let groupWhere; + if (whereKey) { + groupWhere = { + [whereKey]: value, + }; + } + + if (include) { + groupWhere = { + [options.groupedLimit.on.foreignIdentifierField]: value, + }; + } + + return spliceStr( + baseQuery, + splicePos, + placeholder.length, + this.whereItemsQuery(groupWhere, { ...options, mainAlias: groupedTableName }), + ); + }) + .join(this.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')})`, + mainTable.quotedAs, + ), + ); + } else { + mainQueryItems.push( + this.selectFromTableFragment( + options, + mainTable.model, + attributes.main, + mainTable.quotedName, + mainTable.quotedAs, + ), + ); + } + + mainQueryItems.push(mainJoinQueries.join('')); + } + + // Add WHERE to sub or main query + if (Object.hasOwn(options, 'where') && !options.groupedLimit) { + options.where = this.whereItemsQuery(options.where, { + ...options, + model, + mainAlias: mainTable.as || tableName, + }); + + if (options.where) { + if (subQuery) { + subQueryItems.push(` WHERE ${options.where}`); + } else { + mainQueryItems.push(` WHERE ${options.where}`); + // Walk the main query to update all selects + for (const [key, value] of mainQueryItems.entries()) { + if (value.startsWith('SELECT')) { + mainQueryItems[key] = this.selectFromTableFragment( + options, + model, + attributes.main, + mainTable.quotedName, + mainTable.quotedAs, + options.where, + ); + } + } + } + } + } + + // Add GROUP BY to sub or main query + 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 && options.group) { + subQueryItems.push(` GROUP BY ${options.group}`); + } else if (options.group) { + mainQueryItems.push(` GROUP BY ${options.group}`); + } + } + + // Add HAVING to sub or main query + if (Object.hasOwn(options, 'having')) { + options.having = this.whereItemsQuery(options.having, { + ...options, + model, + mainAlias: mainTable.as || tableName, + }); + + if (options.having) { + if (subQuery) { + subQueryItems.push(` HAVING ${options.having}`); + } else { + mainQueryItems.push(` HAVING ${options.having}`); + } + } + } + + // Add ORDER to sub or main query + if (options.order) { + const orders = this.getQueryOrders(options, model, subQuery); + if (orders.mainQueryOrder.length > 0) { + mainQueryItems.push(` ORDER BY ${orders.mainQueryOrder.join(', ')}`); + } else if (!subQuery && (options.limit != null || options.offset)) { + if (!isModelStatic(model)) { + throw new Error('Cannot use offset or limit without a model or order being set'); + } + + // Always order by primary key if order is not specified and limit/offset is not null + const pks = []; + for (const pkAttrName of mainModelDefinition.primaryKeysAttributeNames) { + const attribute = mainModelAttributes.get(pkAttrName); + pks.push(attribute.columnName !== pkAttrName ? attribute.columnName : pkAttrName); + } + + mainQueryItems.push( + ` ORDER BY ${pks.map(pk => `${mainTable.quotedAs}.${this.quoteIdentifier(pk)}`).join(', ')}`, + ); + } + + if (orders.subQueryOrder.length > 0) { + subQueryItems.push(` ORDER BY ${orders.subQueryOrder.join(', ')}`); + } else if (subQuery && (options.limit != null || options.offset)) { + if (!isModelStatic(model)) { + throw new Error('Cannot use offset or limit without a model or order being set'); + } + + // Always order by primary key if order is not specified and limit/offset is not null + const pks = []; + for (const pkAttrName of mainModelDefinition.primaryKeysAttributeNames) { + const attribute = mainModelAttributes.get(pkAttrName); + pks.push(attribute.columnName !== pkAttrName ? attribute.columnName : pkAttrName); + } + + subQueryItems.push( + ` ORDER BY ${pks.map(pk => `${mainTable.quotedAs}.${this.quoteIdentifier(pk)}`).join(', ')}`, + ); + } + } else if (options.limit != null || options.offset) { + if (!isModelStatic(model)) { + throw new Error('Cannot use offset or limit without a model or order being set'); + } + + // Always order by primary key if order is not specified and limit/offset is not null + const pks = []; + for (const pkAttrName of mainModelDefinition.primaryKeysAttributeNames) { + const attribute = mainModelAttributes.get(pkAttrName); + pks.push(attribute.columnName !== pkAttrName ? attribute.columnName : pkAttrName); + } + + if (subQuery) { + subQueryItems.push( + ` ORDER BY ${pks.map(pk => `${mainTable.quotedAs}.${this.quoteIdentifier(pk)}`).join(', ')}`, + ); + } else { + mainQueryItems.push( + ` ORDER BY ${pks.map(pk => `${mainTable.quotedAs}.${this.quoteIdentifier(pk)}`).join(', ')}`, + ); + } + } + + // Add LIMIT, OFFSET to sub or main query + const limitOrder = this.#internals.addLimitAndOffset(options); + if (limitOrder && !options.groupedLimit) { + if (subQuery) { + subQueryItems.push(limitOrder); + } else { + mainQueryItems.push(limitOrder); + } + } + + if (subQuery) { + this._throwOnEmptyAttributes(attributes.main, { + modelName: model && model.name, + as: mainTable.quotedAs, + }); + query = `SELECT ${attributes.main.join(', ')} FROM (${subQueryItems.join('')}) AS ${mainTable.quotedAs}${mainJoinQueries.join('')}${mainQueryItems.join('')}`; + } else { + query = mainQueryItems.join(''); + } + + if (options.lock && this.dialect.supports.lock) { + let lock = options.lock; + if (typeof options.lock === 'object') { + lock = options.lock.level; + } + + if (this.dialect.supports.lockKey && ['KEY SHARE', 'NO KEY UPDATE'].includes(lock)) { + query += ` FOR ${lock}`; + } else if (lock === 'SHARE') { + query += ` ${this.dialect.supports.forShare}`; + } else { + query += ' FOR UPDATE'; + } + + if (this.dialect.supports.lockOf && options.lock.of && isModelStatic(options.lock.of)) { + query += ` OF ${this.quoteTable(options.lock.of.name)}`; + } + + if (this.dialect.supports.skipLocked && options.skipLocked) { + query += ' SKIP LOCKED'; + } + } + + return `${query};`; + } + + aliasGrouping(field, model, tableName, options) { + const src = Array.isArray(field) ? field[0] : field; + + return this.quote( + this._getAliasForField(tableName, src, options) || src, + model, + undefined, + options, + ); + } + + escapeAttributes(attributes, options, mainTableAs) { + const quotedMainTableAs = mainTableAs && this.quoteIdentifier(mainTableAs); + + return ( + attributes && + attributes.map(attr => { + let addTable = true; + + if (attr instanceof BaseSqlExpression) { + return this.formatSqlExpression(attr, options); + } + + if (Array.isArray(attr)) { + if (attr.length !== 2) { + throw new Error( + `${JSON.stringify(attr)} is not a valid attribute definition. Please use the following format: ['attribute definition', 'alias']`, + ); + } + + attr = [...attr]; + + if (attr[0] instanceof BaseSqlExpression) { + attr[0] = this.formatSqlExpression(attr[0], options); + addTable = false; + } else { + attr[0] = this.quoteIdentifier(attr[0]); + } + + let alias = attr[1]; + + if (options.minifyAliases) { + alias = this._getMinifiedAlias(alias, mainTableAs, options); + } + + attr = [attr[0], this.quoteIdentifier(alias)].join(' AS '); + } else { + attr = this.quoteIdentifier(attr, options.model); + } + + if (!isEmpty(options.include) && (!attr.includes('.') || options.dotNotation) && addTable) { + attr = `${quotedMainTableAs}.${attr}`; + } + + return attr; + }) + ); + } + + generateInclude(include, parentTableName, topLevelInfo, options) { + const joinQueries = { + mainQuery: [], + subQuery: [], + }; + const mainChildIncludes = []; + const subChildIncludes = []; + let requiredMismatch = false; + const includeAs = { + internalAs: include.as, + externalAs: include.as, + }; + const attributes = { + main: [], + subQuery: [], + }; + + topLevelInfo.options.keysEscaped = true; + + // Index hints should not be passed down to any include subqueries + if (topLevelInfo.options && topLevelInfo.options.indexHints) { + delete topLevelInfo.options.indexHints; + } + + if ( + topLevelInfo.names.name !== parentTableName.externalAs && + topLevelInfo.names.as !== parentTableName.externalAs + ) { + includeAs.internalAs = `${parentTableName.internalAs}->${include.as}`; + includeAs.externalAs = `${parentTableName.externalAs}.${include.as}`; + } + + // includeIgnoreAttributes is used by aggregate functions + if (topLevelInfo.options.includeIgnoreAttributes !== false) { + include.model._expandAttributes(include); + mapFinderOptions(include, include.model); + + const includeAttributes = include.attributes.map(attr => { + let attrAs = attr; + let verbatim = false; + + if (Array.isArray(attr) && attr.length === 2) { + if (attr[0] instanceof BaseSqlExpression) { + verbatim = true; + } + + attr = attr.map(attrPart => { + return attrPart instanceof BaseSqlExpression + ? this.formatSqlExpression(attrPart, options) + : attrPart; + }); + + attrAs = attr[1]; + attr = attr[0]; + } + + if (attr instanceof Literal) { + // We trust the user to rename the field correctly + return this.#internals.formatLiteral(attr, options); + } + + if (attr instanceof BaseSqlExpression) { + throw new TypeError( + `Tried to select attributes using ${attr.constructor.name} without specifying an alias for the result, during eager loading. This means the attribute will not be added to the returned instance`, + ); + } + + let prefix; + if (verbatim === true) { + prefix = attr; + } else if (/#>>|->>/.test(attr)) { + prefix = `(${this.quoteIdentifier(includeAs.internalAs)}.${attr.replaceAll(/\(|\)/g, '')})`; + } else if (/json_extract\(/.test(attr)) { + prefix = attr.replace( + /json_extract\(/i, + `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.`, + ); + } else { + prefix = `${this.quoteIdentifier(includeAs.internalAs)}.${this.quoteIdentifier(attr)}`; + } + + let alias = `${includeAs.externalAs}.${attrAs}`; + + if (options.minifyAliases) { + alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options); + } + + return joinSQLFragments([prefix, 'AS', this.quoteIdentifier(alias, true)]); + }); + if (include.subQuery && topLevelInfo.subQuery) { + for (const attr of includeAttributes) { + attributes.subQuery.push(attr); + } + } else { + for (const attr of includeAttributes) { + attributes.main.push(attr); + } + } + } + + let joinQuery; + if (include.through) { + joinQuery = this.generateThroughJoin( + include, + includeAs, + parentTableName.internalAs, + topLevelInfo, + { minifyAliases: options.minifyAliases }, + ); + } else { + this._generateSubQueryFilter(include, includeAs, topLevelInfo); + joinQuery = this.generateJoin(include, topLevelInfo, options); + } + + // handle possible new attributes created in join + if (joinQuery.attributes.main.length > 0) { + attributes.main = attributes.main.concat(joinQuery.attributes.main); + } + + if (joinQuery.attributes.subQuery.length > 0) { + attributes.subQuery = attributes.subQuery.concat(joinQuery.attributes.subQuery); + } + + if (include.include) { + for (const childInclude of include.include) { + if (childInclude.separate || childInclude._pseudo) { + continue; + } + + const childOriginalSubQuery = childInclude.subQuery; + + if (childInclude.subQuery && (!include.subQuery || !topLevelInfo.subQuery)) { + childInclude.subQuery = false; + } + + const childJoinQueries = this.generateInclude( + childInclude, + includeAs, + topLevelInfo, + options, + ); + + childInclude.subQuery = childOriginalSubQuery; + + if (include.required === false && childInclude.required === true) { + requiredMismatch = true; + } + + // if the child is a sub query we just give it to the + if (childOriginalSubQuery && include.subQuery && topLevelInfo.subQuery) { + subChildIncludes.push(childJoinQueries.subQuery); + } + + if (childJoinQueries.mainQuery) { + mainChildIncludes.push(childJoinQueries.mainQuery); + } + + if (childJoinQueries.attributes.main.length > 0) { + attributes.main = attributes.main.concat(childJoinQueries.attributes.main); + } + + if (childJoinQueries.attributes.subQuery.length > 0) { + attributes.subQuery = attributes.subQuery.concat(childJoinQueries.attributes.subQuery); + } + } + } + + if (include.subQuery && topLevelInfo.subQuery) { + if (requiredMismatch && subChildIncludes.length > 0) { + joinQueries.subQuery.push( + ` ${joinQuery.join} ( ${joinQuery.body}${subChildIncludes.join('')} ) ON ${joinQuery.condition}`, + ); + } else { + joinQueries.subQuery.push(` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`); + if (subChildIncludes.length > 0) { + joinQueries.subQuery.push(subChildIncludes.join('')); + } + } + + joinQueries.mainQuery.push(mainChildIncludes.join('')); + } else { + if (requiredMismatch && mainChildIncludes.length > 0) { + joinQueries.mainQuery.push( + ` ${joinQuery.join} ( ${joinQuery.body}${mainChildIncludes.join('')} ) ON ${joinQuery.condition}`, + ); + } else { + joinQueries.mainQuery.push( + ` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`, + ); + if (mainChildIncludes.length > 0) { + joinQueries.mainQuery.push(mainChildIncludes.join('')); + } + } + + if (subChildIncludes.length > 0) { + if (topLevelInfo.subQuery) { + const subFragments = []; + + if (requiredMismatch) { + subFragments.push( + ` ${joinQuery.join} ( ${joinQuery.body}${subChildIncludes.join('')} ) ON ${joinQuery.condition}`, + ); + } else { + subFragments.push( + ` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`, + subChildIncludes.join(''), + ); + } + + joinQueries.subQuery.push(...subFragments); + } else { + joinQueries.subQuery.push(subChildIncludes.join('')); + } + } + } + + return { + mainQuery: joinQueries.mainQuery.join(''), + subQuery: joinQueries.subQuery.join(''), + attributes, + }; + } + + _getMinifiedAlias(alias, tableName, options) { + // We do not want to re-alias in case of a subquery + if (options.aliasesByTable[`${tableName}${alias}`]) { + return options.aliasesByTable[`${tableName}${alias}`]; + } + + // Do not alias custom suquery_orders + if (/subquery_order_\d/.test(alias)) { + return alias; + } + + const minifiedAlias = `_${options.aliasesMapping.size}`; + + options.aliasesMapping.set(minifiedAlias, alias); + options.aliasesByTable[`${tableName}${alias}`] = minifiedAlias; + + return minifiedAlias; + } + + _getAliasForField(tableName, field, options) { + if (!options.minifyAliases || !options.aliasesByTable) { + return null; + } + + const candidates = new Set(); + const tableNameString = typeof tableName === 'string' ? tableName : undefined; + const tableVariants = []; + + if (tableNameString) { + tableVariants.push(tableNameString); + + const normalizedTable = tableNameString.replaceAll('->', '.'); + if (normalizedTable !== tableNameString) { + tableVariants.push(normalizedTable); + } + } + + const fieldVariants = new Set(); + + if (typeof field === 'string') { + fieldVariants.add(field); + + const dotVariant = field.replaceAll('->', '.'); + const arrowVariant = field.replaceAll('.', '->'); + + fieldVariants.add(dotVariant); + fieldVariants.add(arrowVariant); + + if (field.includes('.')) { + fieldVariants.add(field.slice(field.lastIndexOf('.') + 1)); + } + } else if (field != null) { + fieldVariants.add(field); + } + + for (const variant of fieldVariants) { + candidates.add(variant); + } + + if (tableVariants.length > 0) { + for (const tableVariant of tableVariants) { + for (const fieldVariant of fieldVariants) { + if (typeof fieldVariant === 'string' && fieldVariant.length > 0) { + candidates.add(`${tableVariant}.${fieldVariant}`); + } + } + } + } + + for (const candidate of candidates) { + const alias = options.aliasesByTable[`${tableName}${candidate}`]; + + if (alias) { + return alias; + } + } + + return null; + } + + _getAliasForFieldFromQueryOptions(field, options) { + return (options.attributes || []).find( + attr => Array.isArray(attr) && attr[1] && (attr[0] === field || attr[1] === field), + ); + } + + /** + * Analyzes an ORDER BY clause to determine the attribute reference path. + * + * This handles order arrays like [Model, 'field', 'ASC'] or ['assoc1', 'assoc2', 'field', 'DESC'] + * by traversing the association chain to find the correct table alias path and attribute. + * + * This does not handle raw SQL or literal orders, only structured association orders. + * + * The returned information is used to: + * - Resolve the correct table alias (internal path with '->' or external path with '.') + * - Find the corresponding include to access minified aliases + * - Replace the association parts to their table.column reference. + * + * @param {Array} order - The order clause array (e.g., [User, Task, 'name', 'DESC']) + * @param {Model} model - The root model for the query + * @param {object} options - Query options including include tree + * @returns {object|null} Info about the association path and attribute, or null if not an association order or malformed + */ + _getAssociationOrderInfo(order, model, options) { + if (!Array.isArray(order) || order.length < 2 || !isModelStatic(model)) { + return null; + } + + const isDirectional = + typeof order.at(-1) === 'string' && VALID_ORDER_OPTIONS.includes(order.at(-1).toUpperCase()); + const attributeIndex = order.length - 1 - (isDirectional ? 1 : 0); + + // If attributeIndex is 0, there are no associations in the order array + if (attributeIndex < 1) { + return null; + } + + const attribute = order[attributeIndex]; + + // Sanity check that the attribute is a property name and not malformed. + if (typeof attribute !== 'string' || attribute.length === 0) { + return null; + } + + // Everything before the attribute should be associations + const associationParts = order.slice(0, attributeIndex); + if (associationParts.length === 0) { + return null; + } + + let currentModel = model; + const associations = []; + let previousAssociation = null; + + // Start association walk through the order clause + for (const part of associationParts) { + let association; + const previousBelongsToMany = + previousAssociation instanceof BelongsToManyAssociation ? previousAssociation : null; + const matchesPreviousThroughModel = + previousBelongsToMany && + (part === previousBelongsToMany.throughModel || + (isPlainObject(part) && + part.model === previousBelongsToMany.throughModel && + (part.as == null || part.as === previousBelongsToMany.fromTargetToThrough.as))); + + if (matchesPreviousThroughModel) { + association = previousBelongsToMany.fromTargetToThrough; + } else if (part instanceof Association) { + association = part; + } else if (typeof part === 'string' && currentModel?.associations?.[part]) { + association = currentModel.associations[part]; + } else if (isModelStatic(part)) { + association = currentModel.getAssociationWithModel(part); + } else if (isPlainObject(part) && part.model && isModelStatic(part.model)) { + association = currentModel.getAssociationWithModel(part.model, part.as); + } else { + throw new Error( + `Invalid Order: association "${part}" is not found in ${currentModel.name}'s associations. Check your order definition.`, + ); + } + + associations.push(association); + currentModel = association.target; + previousAssociation = association; + } + + if (associations.length === 0) { + return null; + } + + const associationAliases = associations.map(association => association.as); + if (associationAliases.some(alias => !alias)) { + return null; + } + + // Find the corresponding include in the query options to access its alias information + const include = this._findIncludeForAliasPath(options?.include, associationAliases); + + return { + attribute, + replaceCount: associationParts.length + 1, // Number of elements to replace in the order array + associationAliases, + include, + externalPath: associationAliases.join('.'), // e.g., 'user.tasks' for nested associations + internalPath: associationAliases.join('->'), // e.g., 'user->tasks' used for internal aliasing + }; + } + + /** + * Finds the nested include object that matches a given alias path. + * + * Starting from the provided root `includes` array, this method walks the tree by + * following each segment in `aliasPath` and matching it against the `as` of each include. + * If any segment is not found at a given level, it returns `null`. When all segments are + * matched, it returns the deepest include corresponding to the full path. + * + * This is used (e.g. by association order resolution) to locate the include that corresponds + * to an ORDER BY association chain so that the correct table aliasing/minified alias can be applied. + * + * @param {Array} includes - The root include array to traverse (e.g. `options.include`). + * @param {string[]} aliasPath - Ordered list of association alias names (e.g. ['user', 'tasks']). + * @returns {object|null} The include that matches the full alias path, or `null` if no match is found. + */ + _findIncludeForAliasPath(includes, aliasPath) { + if (!Array.isArray(includes) || aliasPath.length === 0) { + return null; + } + + let currentIncludes = includes; + let match; + + for (const segment of aliasPath) { + match = currentIncludes.find(include => include.as === segment); + if (!match) { + return null; + } + + currentIncludes = match.include || []; + } + + return match; + } + + /** + * Splits a raw ORDER clause entry into normalized sub-entries. + * + * This helper peels off any leading model/attribute reference segments and keeps + * an optional trailing direction (ASC/DESC). For example: + * - [User, 'tasks', 'name', 'DESC'] -> [[User], ['tasks', 'name', 'DESC']] + * - ['createdAt', 'ASC'] -> [['createdAt', 'ASC']] + * - ['DESC'] -> [['DESC']] + * - [] -> [[]] + * + * The output is consumed by later normalization steps that resolve association paths + * and build the final ORDER BY fragment. + * + * @param {Array} rawOrder - The raw order entry array to split. + * @returns {Array>} A list of order sub-entries, where leading model/attribute + * references are emitted as single-item arrays, and the remaining path plus optional + * direction are grouped as the last entry. Returns `[[]]` for an empty input. + * @private + */ + _splitOrderEntry(rawOrder) { + const orderParts = [...rawOrder]; + const results = []; + + let direction; + if (isOrderDirection(orderParts.at(-1))) { + direction = orderParts.pop(); + } + + while (orderParts.length > 1 && isModelAttribute(orderParts[0])) { + results.push([orderParts.shift()]); + } + + if (orderParts.length > 0) { + const entry = [...orderParts]; + if (direction !== undefined) { + entry.push(direction); + direction = undefined; + } + + results.push(entry); + } + + if (direction !== undefined) { + if (results.length === 0) { + results.push([direction]); + } else { + results.at(-1).push(direction); + } + } + + return results.length > 0 ? results : [[]]; + } + + generateJoin(include, topLevelInfo, options) { + const association = include.association; + const parent = include.parent; + const parentIsTop = + Boolean(parent) && + !include.parent.association && + include.parent.model.name === topLevelInfo.options.model.name; + let $parent; + let joinWhere; + /* Attributes for the left side */ + const left = association.source; + const leftAttributes = left.modelDefinition.attributes; + + const attrNameLeft = + association instanceof BelongsToAssociation + ? association.foreignKey + : association.sourceKeyAttribute; + const columnNameLeft = + association instanceof BelongsToAssociation + ? association.identifierField + : leftAttributes.get(association.sourceKeyAttribute).columnName; + let asLeft; + /* Attributes for the right side */ + const right = include.model; + const rightAttributes = right.modelDefinition.attributes; + const tableRight = right.table; + const fieldRight = + association instanceof BelongsToAssociation + ? rightAttributes.get(association.targetKey).columnName + : association.identifierField; + let asRight = include.as; + + while (($parent = ($parent && $parent.parent) || include.parent) && $parent.association) { + if (asLeft) { + asLeft = `${$parent.as}->${asLeft}`; + } else { + asLeft = $parent.as; + } + } + + if (!asLeft) { + asLeft = parent.as || parent.model.name; + } else { + asRight = `${asLeft}->${asRight}`; + } + + // TODO: use whereItemsQuery to generate the entire "ON" condition. + let joinOn = `${this.quoteTable(asLeft)}.${this.quoteIdentifier(columnNameLeft)}`; + const subqueryAttributes = []; + + if ( + (topLevelInfo.options.groupedLimit && parentIsTop) || + (topLevelInfo.subQuery && include.parent.subQuery && !include.subQuery) + ) { + if (parentIsTop) { + // The main model attributes is not aliased to a prefix + const tableName = parent.as || parent.model.name; + const quotedTableName = this.quoteTable(tableName); + + // Check for potential aliased JOIN condition + joinOn = + this._getAliasForField(tableName, attrNameLeft, topLevelInfo.options) || + `${quotedTableName}.${this.quoteIdentifier(attrNameLeft)}`; + + if (topLevelInfo.subQuery) { + const dbIdentifier = `${quotedTableName}.${this.quoteIdentifier(columnNameLeft)}`; + subqueryAttributes.push( + dbIdentifier !== joinOn + ? `${dbIdentifier} AS ${this.quoteIdentifier(attrNameLeft)}` + : dbIdentifier, + ); + } + } else { + const joinSource = `${asLeft.replaceAll('->', '.')}.${attrNameLeft}`; + + // Check for potential aliased JOIN condition + joinOn = + this._getAliasForField(asLeft, joinSource, topLevelInfo.options) || + this.quoteIdentifier(joinSource); + } + } + + joinOn += ` = ${this.quoteIdentifier(asRight)}.${this.quoteIdentifier(fieldRight)}`; + + if (include.on) { + joinOn = this.whereItemsQuery(include.on, { + mainAlias: asRight, + model: include.model, + replacements: options?.replacements, + }); + } + + if (include.where) { + joinWhere = this.whereItemsQuery(include.where, { + mainAlias: asRight, + model: include.model, + replacements: options?.replacements, + }); + if (joinWhere) { + joinOn = joinWithLogicalOperator([joinOn, joinWhere], include.or ? Op.or : Op.and); + } + } + + if (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, { ...topLevelInfo.options, ...include, alias: asRight }), + condition: joinOn, + attributes: { + main: [], + subQuery: subqueryAttributes, + }, + }; + } + + /** + * Returns the SQL fragments to handle returning the attributes from an insert/update query. + * + * @param {object} modelAttributes An object with the model attributes. + * @param {object} options An object with options. + * + * @private + */ + generateReturnValues(modelAttributes, options) { + const returnFields = []; + const returnTypes = []; + let outputFragment = ''; + let returningFragment = ''; + let tmpTable = ''; + + const returnValuesType = this.dialect.supports.returnValues; + + if (Array.isArray(options.returning)) { + returnFields.push( + ...options.returning.map(field => { + if (typeof field === 'string') { + return this.quoteIdentifier(field); + } else if (field instanceof Literal) { + // Due to how the mssql query is built, using a literal would never result in a properly formed query. + // It's better to warn early. + if (returnValuesType === 'output') { + throw new Error( + `literal() cannot be used in the "returning" option array in ${this.dialect.name}. Use col(), or a string instead.`, + ); + } + + return this.formatSqlExpression(field); + } else if (field instanceof Col) { + return this.formatSqlExpression(field); + } + + throw new Error( + `Unsupported value in "returning" option: ${NodeUtil.inspect(field)}. This option only accepts true, false, or an array of strings, col() or literal().`, + ); + }), + ); + } else if (modelAttributes) { + each(modelAttributes, attribute => { + if (!(attribute.type instanceof DataTypes.VIRTUAL)) { + returnFields.push(this.quoteIdentifier(attribute.field)); + returnTypes.push(attribute.type); + } + }); + } + + if (isEmpty(returnFields)) { + returnFields.push(`*`); + } + + if (returnValuesType === 'returning') { + returningFragment = ` RETURNING ${returnFields.join(', ')}`; + } else if (returnValuesType === 'output') { + outputFragment = ` OUTPUT ${returnFields.map(field => `INSERTED.${field}`).join(', ')}`; + + // To capture output rows when there is a trigger on MSSQL DB + if (options.hasTrigger && this.dialect.supports.tmpTableTrigger) { + const tmpColumns = returnFields.map((field, i) => { + return `${field} ${attributeTypeToSql(returnTypes[i], { dialect: this.dialect })}`; + }); + + tmpTable = `DECLARE @tmp TABLE (${tmpColumns.join(',')}); `; + outputFragment += ' INTO @tmp'; + returningFragment = '; SELECT * FROM @tmp'; + } + } + + return { outputFragment, returnFields, returningFragment, tmpTable }; + } + + generateThroughJoin(include, includeAs, parentTableName, topLevelInfo, options) { + const isRootParent = + !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name; + const isMinified = topLevelInfo.options.minifyAliases; + + const through = include.through; + const throughTable = through.model.table; + const throughAs = `${includeAs.internalAs}->${through.as}`; + const externalThroughAs = `${includeAs.externalAs}.${through.as}`; + + const throughAttributes = through.attributes.map(attr => { + let alias = `${externalThroughAs}.${Array.isArray(attr) ? attr[1] : attr}`; + + if (options.minifyAliases) { + alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options); + } + + return joinSQLFragments([ + `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)}`, + 'AS', + this.quoteIdentifier(alias), + ]); + }); + + const association = include.association; + const tableSource = parentTableName; + const identSource = association.identifierField; + const tableTarget = includeAs.internalAs; + const identTarget = association.foreignIdentifierField; + const attrTarget = association.targetKeyField; + + const joinType = include.required + ? 'INNER JOIN' + : include.right && this.dialect.supports['RIGHT JOIN'] + ? 'RIGHT OUTER JOIN' + : 'LEFT OUTER JOIN'; + let joinBody; + let joinCondition; + const attributes = { + main: [], + subQuery: [], + }; + let attrSource = association.sourceKey; + let sourceJoinOn; + let targetJoinOn; + let throughWhere; + let targetWhere; + + if (options.minifyAliases && throughAs.length > 63) { + topLevelInfo.options.includeAliases.set( + `%${topLevelInfo.options.includeAliases.size}`, + throughAs, + ); + if (includeAs.internalAs.length > 63) { + topLevelInfo.options.includeAliases.set( + `%${topLevelInfo.options.includeAliases.size}`, + includeAs.internalAs, + ); + } + } + + if (topLevelInfo.options.includeIgnoreAttributes !== false) { + // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery) + for (const attr of throughAttributes) { + attributes.main.push(attr); + } + } + + // Figure out if we need to use field or attribute + if (!topLevelInfo.subQuery) { + attrSource = association.sourceKeyField; + } + + if ( + topLevelInfo.subQuery && + !include.subQuery && + !include.parent.subQuery && + include.parent.model !== topLevelInfo.options.mainModel + ) { + attrSource = association.sourceKeyField; + } + + // Build source side of the JOIN predicate for the through association. + // This condition is reused by the actual JOIN and any subquery WHERE filters. + + if (topLevelInfo.subQuery && !include.subQuery && include.parent.subQuery && isMinified) { + // When the parent include is also part of a subquery (especially with minified aliases), + // the source key may only be available under a projected alias. Thus, we resolve + // and reference the aliased attribute (or project it if missing) instead of the raw column + // name to avoid referencing a missing column. + const aliasCandidates = new Set(); + + const dottedTableSource = tableSource.replaceAll('->', '.'); + + if (attrSource) { + aliasCandidates.add(attrSource); + aliasCandidates.add(`${tableSource}.${attrSource}`); + aliasCandidates.add(`${dottedTableSource}.${attrSource}`); + } + + if (association.sourceKeyField && association.sourceKeyField !== attrSource) { + aliasCandidates.add(association.sourceKeyField); + aliasCandidates.add(`${tableSource}.${association.sourceKeyField}`); + aliasCandidates.add(`${dottedTableSource}.${association.sourceKeyField}`); + } + + let aliasedSource = null; + + for (const candidate of aliasCandidates) { + aliasedSource = this._getAliasForField(tableSource, candidate, topLevelInfo.options); + + if (aliasedSource) { + break; + } + } + + if (!aliasedSource) { + const joinColumn = association.sourceKeyField || attrSource || identSource; + + if (isRootParent) { + sourceJoinOn = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(joinColumn)} = `; + } else { + const aliasBase = `${dottedTableSource}.${joinColumn}`; + + aliasedSource = this._getMinifiedAlias(aliasBase, tableSource, topLevelInfo.options); + + const projection = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(joinColumn)} AS ${this.quoteIdentifier(aliasedSource)}`; + + if (!attributes.subQuery.includes(projection)) { + attributes.subQuery.push(projection); + } + } + } + + if (!sourceJoinOn) { + sourceJoinOn = `${this.quoteIdentifier(aliasedSource)} = `; + } + } else if ( + topLevelInfo.subQuery && + !include.subQuery && + include.parent.subQuery && + !isRootParent + ) { + // Subquery + non-root parent: when alias minification is enabled, + // the parent path's source key may have been projected under a generated alias. + // Resolve and use the projected alias for the source side of the JOIN; + // If no alias is found, fall back to the table-qualified column, prefixed + // by the main subquery alias when available. + const aliasedSource = this._getAliasForField( + tableSource, + `${tableSource}.${attrSource}`, + topLevelInfo.options, + ); + + if (aliasedSource) { + sourceJoinOn = `${this.quoteIdentifier(aliasedSource)} = `; + } else { + const mainAlias = topLevelInfo.names.quotedAs || topLevelInfo.names.quotedName; + + if (mainAlias) { + sourceJoinOn = `${mainAlias}.${this.quoteIdentifier(`${tableSource}.${attrSource}`)} = `; + } else { + sourceJoinOn = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(attrSource)} = `; + } + } + } else { + sourceJoinOn = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(attrSource)} = `; + } + + sourceJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identSource)}`; + + // Filter statement for right side of through + // Used by both join and subquery where + targetJoinOn = `${this.quoteIdentifier(tableTarget)}.${this.quoteIdentifier(attrTarget)} = `; + targetJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identTarget)}`; + + if (through.where) { + throughWhere = this.whereItemsQuery(through.where, { + ...topLevelInfo.options, + model: through.model, + mainAlias: throughAs, + }); + } + + // Generate a wrapped join so that the through table join can be dependent on the target join + joinBody = `( ${this.quoteTable(throughTable, { ...topLevelInfo.options, ...include, alias: throughAs })} INNER JOIN ${this.quoteTable(include.model.table, { ...topLevelInfo.options, ...include, alias: includeAs.internalAs })} ON ${targetJoinOn}`; + if (throughWhere) { + joinBody += ` AND ${throughWhere}`; + } + + joinBody += ')'; + joinCondition = sourceJoinOn; + + if ((include.where || include.through.where) && include.where) { + targetWhere = this.whereItemsQuery(include.where, { + ...topLevelInfo.options, + model: include.model, + mainAlias: includeAs.internalAs, + }); + if (targetWhere) { + joinCondition += ` AND ${targetWhere}`; + } + } + + this._generateSubQueryFilter(include, includeAs, topLevelInfo); + + return { + join: joinType, + body: joinBody, + condition: joinCondition, + attributes, + }; + } + + /* + * Generates subQueryFilter - a select nested in the where clause of the subQuery. + * For a given include a query is generated that contains all the way from the subQuery + * table to the include table plus everything that's in required transitive closure of the + * given include. + */ + _generateSubQueryFilter(include, includeAs, topLevelInfo) { + if (!topLevelInfo.subQuery || !include.subQueryFilter) { + return; + } + + if (!topLevelInfo.options.where) { + topLevelInfo.options.where = {}; + } + + let parent = include; + let child = include; + let nestedIncludes = this._getRequiredClosure(include).include; + let query; + + while ((parent = parent.parent)) { + if (parent.parent && !parent.required) { + return; // only generate subQueryFilter if all the parents of this include are required + } + + if (parent.subQueryFilter) { + // the include is already handled as this parent has the include on its required closure + // skip to prevent duplicate subQueryFilter + return; + } + + nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }]; + child = parent; + } + + const topInclude = nestedIncludes[0]; + const topParent = topInclude.parent; + const topAssociation = topInclude.association; + topInclude.association = undefined; + + if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) { + query = this.selectQuery( + topInclude.through.model.table, + { + attributes: [topInclude.through.model.primaryKeyField], + include: _validateIncludedElements({ + model: topInclude.through.model, + include: [ + { + association: topAssociation.fromThroughToTarget, + required: true, + where: topInclude.where, + include: topInclude.include, + }, + ], + }).include, + model: topInclude.through.model, + where: { + [Op.and]: [ + new Literal( + [ + `${this.quoteTable(topParent.model.name)}.${this.quoteIdentifier(topParent.model.primaryKeyField)}`, + `${this.quoteIdentifier(topInclude.through.model.name)}.${this.quoteIdentifier(topAssociation.identifierField)}`, + ].join(' = '), + ), + topInclude.through.where, + ], + }, + includeIgnoreAttributes: false, + }, + topInclude.through.model, + ); + } else { + const isBelongsTo = topAssociation.associationType === 'BelongsTo'; + const sourceField = isBelongsTo + ? topAssociation.identifierField + : topAssociation.sourceKeyField || topParent.model.primaryKeyField; + const targetField = isBelongsTo + ? topAssociation.sourceKeyField || topInclude.model.primaryKeyField + : topAssociation.identifierField; + + const join = [ + `${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(targetField)}`, + `${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(sourceField)}`, + ].join(' = '); + + query = this.selectQuery( + topInclude.model.table, + { + attributes: [targetField], + include: _validateIncludedElements(topInclude).include, + model: topInclude.model, + where: { + [Op.and]: [topInclude.where, new Literal(join)], + }, + tableAs: topInclude.as, + includeIgnoreAttributes: false, + }, + topInclude.model, + ); + } + + topLevelInfo.options.where = and( + topLevelInfo.options.where, + new Literal(['EXISTS (', query.replace(/;$/, ''), ')'].join(' ')), + ); + } + + /* + * For a given include hierarchy creates a copy of it where only the required includes + * are preserved. + */ + _getRequiredClosure(include) { + const copy = { ...include, attributes: [], include: [] }; + + if (Array.isArray(include.include)) { + copy.include = include.include + .filter(i => i.required) + .map(inc => this._getRequiredClosure(inc)); + } + + return copy; + } + + getQueryOrders(options, model, subQuery) { + const mainQueryOrder = []; + const subQueryOrder = []; + + if (Array.isArray(options.order)) { + for (const rawOrder of options.order) { + const normalizedOrder = Array.isArray(rawOrder) ? rawOrder : [rawOrder]; + const orderEntries = this._splitOrderEntry(normalizedOrder); + + for (let order of orderEntries) { + if (!Array.isArray(order)) { + order = [order]; + } + + const associationOrderInfo = this._getAssociationOrderInfo(order, model, options); + + if (subQuery && associationOrderInfo?.include?.subQuery) { + const subOrder = [...order]; + subQueryOrder.push(this.quote(subOrder, model, '->', options)); + } + + if ( + subQuery && + Array.isArray(order) && + order[0] && + !Array.isArray(order[0]) && + !(order[0] instanceof Association) && + !isModelStatic(order[0]) && + !isModelStatic(order[0].model) && + !( + typeof order[0] === 'string' && + model && + model.associations !== undefined && + model.associations[order[0]] + ) + ) { + // TODO - refactor this.quote() to not change the first argument + const columnName = model.modelDefinition.getColumnNameLoose(order[0]); + const subQueryAlias = this._getAliasForField(model.name, columnName, options); + + let parent = null; + let orderToQuote = []; + + // we need to ensure that the parent is null if we use the subquery alias, else we'll get an exception since + // "model_name"."alias" doesn't exist - only "alias" does. we also need to ensure that we preserve order direction + // by pushing order[1] to the subQueryOrder as well - in case it doesn't exist, we want to push "ASC" + if (subQueryAlias === null) { + orderToQuote = order; + parent = model; + } else { + orderToQuote = [subQueryAlias, order.length > 1 ? order[1] : 'ASC']; + parent = null; + } + + subQueryOrder.push(this.quote(orderToQuote, parent, '->', options)); + } + + if (subQuery && associationOrderInfo?.include?.subQuery) { + const aliasField = `${associationOrderInfo.externalPath}.${associationOrderInfo.attribute}`; + const alias = + this._getAliasForField(associationOrderInfo.internalPath, aliasField, options) || + aliasField; + const aliasLiteral = new Literal(this.quoteIdentifier(alias)); + + order.splice(0, associationOrderInfo.replaceCount, aliasLiteral); + } + + // Handle case where renamed attributes are used to order by, + // see https://github.com/sequelize/sequelize/issues/8739 + // need to check if either of the attribute options match the order + if (options.attributes && model) { + const aliasedAttribute = this._getAliasForFieldFromQueryOptions(order[0], options); + + if (aliasedAttribute) { + const alias = this._getAliasForField(model.name, aliasedAttribute[1], options); + + order[0] = new Col(alias || aliasedAttribute[1]); + } + } + + mainQueryOrder.push(this.quote(order, model, '->', options)); + } + } + } else if (options.order instanceof BaseSqlExpression) { + const sql = this.quote(options.order, model, '->', options); + if (subQuery) { + subQueryOrder.push(sql); + } + + mainQueryOrder.push(sql); + } else { + throw new TypeError('Order must be type of array or instance of a valid sequelize method.'); + } + + 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.replaceAll(/ +/g, ' ')); + } + + _validateSelectOptions(options) { + if ( + options.maxExecutionTimeHintMs != null && + !this.dialect.supports.maxExecutionTimeHint.select + ) { + throw new Error(`The maxExecutionTimeMs option is not supported by ${this.dialect.name}`); + } + } + + _getBeforeSelectAttributesFragment(_options) { + return ''; + } + + selectFromTableFragment(options, model, attributes, tables, mainTableAs) { + this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); + + this._validateSelectOptions(options); + + let fragment = 'SELECT'; + fragment += this._getBeforeSelectAttributesFragment(options); + fragment += ` ${attributes.join(', ')} FROM ${tables}`; + + if (options.groupedLimit) { + fragment += ` AS ${mainTableAs}`; + } + + return fragment; + } + + // A recursive parser for nested where conditions + parseConditionObject(conditions, path) { + path ||= []; + + return reduce( + conditions, + (result, value, key) => { + if (isObject(value)) { + return result.concat(this.parseConditionObject(value, path.concat(key))); // Recursively parse objects + } + + result.push({ path: path.concat(key), value }); + + return result; + }, + [], + ); + } +} diff --git a/packages/core/src/abstract-dialect/query-generator.types.ts b/packages/core/src/abstract-dialect/query-generator.types.ts new file mode 100644 index 000000000000..105d474469cc --- /dev/null +++ b/packages/core/src/abstract-dialect/query-generator.types.ts @@ -0,0 +1,198 @@ +import type { Deferrable } from '../deferrable'; +import type { TableHints } from '../enums'; +import type { BaseSqlExpression } from '../expression-builders/base-sql-expression'; +import type { Literal } from '../expression-builders/literal'; +import type { Filterable, IndexHintable, ModelStatic, ReferentialAction } from '../model'; +import type { ModelDefinition } from '../model-definition.js'; +import type { TransactionType } from '../transaction'; +import type { AddLimitOffsetOptions } from './query-generator.internal-types.js'; +import type { TableName } from './query-interface.js'; +import type { ConstraintType } from './query-interface.types'; +import type { WhereOptions } from './where-sql-builder-types'; + +export type TableOrModel = TableName | ModelStatic | ModelDefinition; + +export interface BoundQuery { + query: string; + bind?: Record | undefined; +} + +// keep CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface CreateDatabaseQueryOptions { + charset?: string; + collate?: string; + ctype?: string; + encoding?: string; + template?: string; +} + +// keep LIST_DATABASES_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface ListDatabasesQueryOptions { + skip?: string[]; +} + +// keep CREATE_SCHEMA_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface CreateSchemaQueryOptions { + authorization?: string | Literal; + charset?: string; + collate?: string; + comment?: string; + ifNotExists?: boolean; + replace?: boolean; +} + +// keep DROP_SCHEMA_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface DropSchemaQueryOptions { + cascade?: boolean; + ifExists?: boolean; +} + +export interface ListSchemasQueryOptions { + /** List of schemas to exclude from output */ + skip?: string[]; +} + +// keep DROP_TABLE_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface DropTableQueryOptions { + cascade?: boolean; +} + +// Keeep LIST_TABLES_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface ListTablesQueryOptions { + schema?: string; +} + +// keep RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface RenameTableQueryOptions { + changeSchema?: boolean; +} + +// Keep TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface TruncateTableQueryOptions { + cascade?: boolean; + restartIdentity?: boolean; +} + +// keep REMOVE_COLUMN_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface RemoveColumnQueryOptions { + cascade?: boolean; + ifExists?: boolean; +} + +export interface BaseConstraintQueryOptions { + name?: string; + type: ConstraintType; + fields: Array; +} + +export interface AddCheckConstraintQueryOptions extends BaseConstraintQueryOptions { + type: 'CHECK'; + where?: WhereOptions; +} +export interface AddDefaultConstraintQueryOptions extends BaseConstraintQueryOptions { + type: 'DEFAULT'; + defaultValue?: unknown; +} + +export interface AddUniqueConstraintQueryOptions extends BaseConstraintQueryOptions { + type: 'UNIQUE'; + deferrable?: Deferrable; +} + +export interface AddPrimaryKeyConstraintQueryOptions extends BaseConstraintQueryOptions { + type: 'PRIMARY KEY'; + deferrable?: Deferrable; +} + +export interface AddForeignKeyConstraintQueryOptions extends BaseConstraintQueryOptions { + type: 'FOREIGN KEY'; + references: + | { + table: TableOrModel; + field?: string; + fields: string[]; + } + | { + table: TableOrModel; + field: string; + fields?: string[]; + }; + onDelete?: ReferentialAction; + onUpdate?: ReferentialAction; + deferrable?: Deferrable; +} + +export type AddConstraintQueryOptions = + | AddCheckConstraintQueryOptions + | AddUniqueConstraintQueryOptions + | AddDefaultConstraintQueryOptions + | AddPrimaryKeyConstraintQueryOptions + | AddForeignKeyConstraintQueryOptions; + +export interface GetConstraintSnippetQueryOptions { + name?: string; + type: ConstraintType; + fields: Array< + | string + | BaseSqlExpression + | { + /** + * @deprecated use `name` instead + */ + attribute?: string; + name: string; + } + >; + where?: WhereOptions; + defaultValue?: unknown; + references?: + | { + table: TableOrModel; + field?: string; + fields: string[]; + } + | { + table: TableOrModel; + field: string; + fields?: string[]; + }; + onDelete?: ReferentialAction; + onUpdate?: ReferentialAction; + deferrable?: Deferrable; +} + +// keep REMOVE_CONSTRAINT_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface RemoveConstraintQueryOptions { + ifExists?: boolean; + cascade?: boolean; +} + +// keep SHOW_CONSTRAINTS_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface ShowConstraintsQueryOptions { + columnName?: string; + constraintName?: string; + constraintType?: ConstraintType; +} + +// keep START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface StartTransactionQueryOptions { + readOnly?: boolean; + transactionName?: string; + transactionType?: TransactionType | undefined; +} + +export interface QuoteTableOptions extends IndexHintable { + alias: boolean | string; + tableHints?: TableHints[] | undefined; +} + +export interface BulkDeleteQueryOptions + extends AddLimitOffsetOptions, + Filterable {} + +// keep REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS updated when modifying this +export interface RemoveIndexQueryOptions { + concurrently?: boolean; + ifExists?: boolean; + cascade?: boolean; +} diff --git a/packages/core/src/abstract-dialect/query-interface-internal.ts b/packages/core/src/abstract-dialect/query-interface-internal.ts new file mode 100644 index 000000000000..230cdffc0f09 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-interface-internal.ts @@ -0,0 +1,50 @@ +import assert from 'node:assert'; +import { QueryTypes } from '../enums.js'; +import type { QueryRawOptions, Sequelize } from '../sequelize.js'; +import type { AbstractDialect } from './dialect.js'; +import type { AbstractQueryGenerator } from './query-generator.js'; +import type { FetchDatabaseVersionOptions } from './query-interface.types.js'; + +/** + * The methods in this class are not part of the public API. + */ +export class AbstractQueryInterfaceInternal { + readonly #dialect: AbstractDialect; + + get #sequelize(): Sequelize { + return this.#dialect.sequelize; + } + + get #queryGenerator(): AbstractQueryGenerator { + return this.#dialect.queryGenerator; + } + + constructor(dialect: AbstractDialect) { + this.#dialect = dialect; + } + + async fetchDatabaseVersionRaw( + options?: FetchDatabaseVersionOptions, + ): Promise { + const out = await this.#sequelize.queryRaw(this.#queryGenerator.versionQuery(), { + ...options, + type: QueryTypes.SELECT, + plain: true, + }); + + assert(out != null); + + return out; + } + + async executeQueriesSequentially(queries: string[], options?: QueryRawOptions): Promise { + const results = []; + for (const query of queries) { + // eslint-disable-next-line no-await-in-loop + const result = await this.#sequelize.queryRaw(query, { ...options }); + results.push(result); + } + + return results; + } +} diff --git a/packages/core/src/abstract-dialect/query-interface-typescript.ts b/packages/core/src/abstract-dialect/query-interface-typescript.ts new file mode 100644 index 000000000000..a7168e152ac3 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-interface-typescript.ts @@ -0,0 +1,955 @@ +import { isNotNullish } from '@sequelize/utils'; +import isEmpty from 'lodash/isEmpty'; +import assert from 'node:assert'; +import type { ConstraintChecking } from '../deferrable'; +import { Deferrable } from '../deferrable'; +import { QueryTypes } from '../enums'; +import { BaseError } from '../errors'; +import { setTransactionFromCls } from '../model-internals.js'; +import type { QueryRawOptions, QueryRawOptionsWithType, Sequelize } from '../sequelize'; +import { COMPLETES_TRANSACTION, Transaction } from '../transaction'; +import { isErrorWithStringCode } from '../utils/check.js'; +import { + noSchemaDelimiterParameter, + noSchemaParameter, + showAllToListSchemas, + showAllToListTables, +} from '../utils/deprecations'; +import type { AbstractConnection } from './connection-manager.js'; +import type { AbstractDialect } from './dialect.js'; +import type { TableOrModel } from './query-generator.types.js'; +import { AbstractQueryInterfaceInternal } from './query-interface-internal.js'; +import type { TableNameWithSchema } from './query-interface.js'; +import type { + AddConstraintOptions, + ColumnsDescription, + CommitTransactionOptions, + ConstraintDescription, + CreateDatabaseOptions, + CreateSavepointOptions, + CreateSchemaOptions, + DatabaseDescription, + DeferConstraintsOptions, + DescribeTableOptions, + DropSchemaOptions, + FetchDatabaseVersionOptions, + ListDatabasesOptions, + QiBulkDeleteOptions, + QiDropAllSchemasOptions, + QiDropAllTablesOptions, + QiDropTableOptions, + QiListSchemasOptions, + QiListTablesOptions, + QiTruncateTableOptions, + RemoveColumnOptions, + RemoveConstraintOptions, + RenameTableOptions, + RollbackSavepointOptions, + RollbackTransactionOptions, + SetIsolationLevelOptions, + ShowConstraintsOptions, + StartTransactionOptions, +} from './query-interface.types'; + +export type WithoutForeignKeyChecksCallback = (connection: AbstractConnection) => Promise; + +// DO NOT MAKE THIS CLASS PUBLIC! +/** + * This is a temporary class used to progressively migrate the AbstractQueryInterface class to TypeScript by slowly moving its functions here. + * Always use {@link AbstractQueryInterface} instead. + */ +export class AbstractQueryInterfaceTypeScript { + readonly dialect: Dialect; + readonly #internalQueryInterface: AbstractQueryInterfaceInternal; + + /** + * @param dialect The dialect instance. + * @param internalQueryInterface The internal query interface to use. + * Defaults to a new instance of {@link AbstractQueryInterfaceInternal}. + * Your dialect may replace this with a custom implementation. + */ + constructor(dialect: Dialect, internalQueryInterface?: AbstractQueryInterfaceInternal) { + this.dialect = dialect; + this.#internalQueryInterface = + internalQueryInterface ?? new AbstractQueryInterfaceInternal(dialect); + } + + get sequelize(): Sequelize { + return this.dialect.sequelize; + } + + get queryGenerator(): Dialect['queryGenerator'] { + return this.dialect.queryGenerator; + } + + /** + * Create a database + * + * @param database + * @param options + */ + async createDatabase(database: string, options?: CreateDatabaseOptions): Promise { + const sql = this.queryGenerator.createDatabaseQuery(database, options); + + await this.sequelize.queryRaw(sql, options); + } + + /** + * Drop a database + * + * @param database + * @param options + */ + async dropDatabase(database: string, options?: QueryRawOptions): Promise { + const sql = this.queryGenerator.dropDatabaseQuery(database); + + await this.sequelize.queryRaw(sql, options); + } + + /** + * Lists all available databases + * + * @param options + */ + async listDatabases(options?: ListDatabasesOptions): Promise { + const sql = this.queryGenerator.listDatabasesQuery(options); + + return this.sequelize.queryRaw(sql, { + ...options, + type: QueryTypes.SELECT, + }); + } + + /** + * Returns the database version. + * + * @param options Query Options + */ + async fetchDatabaseVersion(options?: FetchDatabaseVersionOptions): Promise { + const payload = await this.#internalQueryInterface.fetchDatabaseVersionRaw<{ version: string }>( + options, + ); + + assert( + payload.version != null, + 'Expected the version query to produce an object that includes a `version` property.', + ); + + return payload.version; + } + + /** + * Create a new database schema. + * + * **Note:** We define schemas as a namespace that can contain tables. + * In mysql and mariadb, this command will create what they call a database. + * + * @param schema Name of the schema + * @param options + */ + async createSchema(schema: string, options?: CreateSchemaOptions): Promise { + const sql = this.queryGenerator.createSchemaQuery(schema, options); + await this.sequelize.queryRaw(sql, options); + } + + /** + * Drop a single schema + * + * **Note:** We define schemas as a namespace that can contain tables. + * In mysql and mariadb, this command will create what they call a database. + * + * @param schema Name of the schema + * @param options + */ + async dropSchema(schema: string, options?: DropSchemaOptions): Promise { + const sql = this.queryGenerator.dropSchemaQuery(schema, options); + await this.sequelize.queryRaw(sql, options); + } + + /** + * Drops all schemas + * + * @param options + */ + async dropAllSchemas(options?: QiDropAllSchemasOptions): Promise { + const skip = options?.skip || []; + const allSchemas = await this.listSchemas(options); + const schemaNames = allSchemas.filter(schemaName => !skip.includes(schemaName)); + + const dropOptions = { ...options }; + // enable "cascade" by default for dialects that support it + if (dropOptions.cascade === undefined) { + if (this.sequelize.dialect.supports.dropSchema.cascade) { + dropOptions.cascade = true; + } else { + // if the dialect does not support "cascade", then drop all tables first in a loop to avoid deadlocks and timeouts + for (const schema of schemaNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropAllTables({ ...dropOptions, schema }); + } + } + } + + // Drop all the schemas in a loop to avoid deadlocks and timeouts + for (const schema of schemaNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropSchema(schema, dropOptions); + } + } + + /** + * List defined schemas + * + * **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 mariadb, this will show all databases. + * + * @param options + * + * @returns list of schemas + */ + async listSchemas(options?: QiListSchemasOptions): Promise { + const showSchemasSql = this.queryGenerator.listSchemasQuery(options); + const schemaNames = await this.sequelize.queryRaw<{ schema: string }>(showSchemasSql, { + ...options, + raw: true, + type: QueryTypes.SELECT, + }); + + return schemaNames.map(schemaName => schemaName.schema); + } + + /** + * Show all defined schemas + * + * @deprecated Use {@link listSchemas} instead. + * @param options + */ + async showAllSchemas(options?: QiListSchemasOptions): Promise { + showAllToListSchemas(); + + return this.listSchemas(options); + } + + /** + * Drop a table from database + * + * @param tableName Table name to drop + * @param options Query options + */ + async dropTable(tableName: TableOrModel, options?: QiDropTableOptions): Promise { + const sql = this.queryGenerator.dropTableQuery(tableName, options); + + await this.sequelize.queryRaw(sql, options); + } + + /** + * Drop all tables + * + * @param options + */ + async dropAllTables(options?: QiDropAllTablesOptions): Promise { + const skip = options?.skip || []; + const allTables = await this.listTables(options); + const tableNames = allTables.filter(tableName => !skip.includes(tableName.tableName)); + + const dropOptions = { ...options }; + // enable "cascade" by default if supported by this dialect + if (this.sequelize.dialect.supports.dropTable.cascade && dropOptions.cascade === undefined) { + dropOptions.cascade = true; + } + + // Remove all the foreign keys first in a loop to avoid deadlocks and timeouts + for (const tableName of tableNames) { + // eslint-disable-next-line no-await-in-loop + const foreignKeys = await this.showConstraints(tableName, { + ...options, + constraintType: 'FOREIGN KEY', + }); + // eslint-disable-next-line no-await-in-loop + await Promise.all( + foreignKeys.map(async fk => this.removeConstraint(tableName, fk.constraintName, options)), + ); + } + + // Drop all the tables loop to avoid deadlocks and timeouts + for (const tableName of tableNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropTable(tableName, dropOptions); + } + } + + /** + * List tables + * + * @param options + */ + async listTables(options?: QiListTablesOptions): Promise { + const sql = this.queryGenerator.listTablesQuery(options); + + return this.sequelize.queryRaw(sql, { + ...options, + raw: true, + type: QueryTypes.SELECT, + }); + } + + /** + * Show all tables + * + * @deprecated Use {@link listTables} instead. + * @param options + */ + async showAllTables(options?: QiListTablesOptions): Promise { + showAllToListTables(); + + return this.listTables(options); + } + + /** + * Rename a table + * + * @param beforeTableName + * @param afterTableName + * @param options + */ + async renameTable( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableOptions, + ): Promise { + const sql = this.queryGenerator.renameTableQuery(beforeTableName, afterTableName, options); + + await this.sequelize.queryRaw(sql, options); + } + + /** + * Returns a promise that will resolve to true if the table or model exists in the database, false otherwise. + * + * @param tableName - The name of the table or model + * @param options - Query options + */ + async tableExists(tableName: TableOrModel, options?: QueryRawOptions): Promise { + const sql = this.queryGenerator.tableExistsQuery(tableName); + const out = await this.sequelize.query(sql, { ...options, type: QueryTypes.SELECT }); + + return out.length === 1; + } + + /** + * 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 tableName + * @param options Query options + */ + async describeTable( + tableName: TableOrModel, + options?: DescribeTableOptions, + ): Promise { + const table = this.queryGenerator.extractTableDetails(tableName); + + if (typeof options === 'string') { + noSchemaParameter(); + table.schema = options; + } + + if (typeof options === 'object' && options !== null) { + if (options.schema) { + noSchemaParameter(); + table.schema = options.schema; + } + + if (options.schemaDelimiter) { + noSchemaDelimiterParameter(); + table.delimiter = options.schemaDelimiter; + } + } + + const sql = this.queryGenerator.describeTableQuery(table); + const queryOptions: QueryRawOptionsWithType = { + ...options, + type: QueryTypes.DESCRIBE, + }; + + try { + const data = await this.sequelize.queryRaw(sql, queryOptions); + /* + * 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 table ${table.tableName}${table.schema ? ` in schema ${table.schema}` : ''}. Check the table name and schema; remember, they _are_ case sensitive.`, + ); + } + + return data; + } catch (error: unknown) { + if ( + error instanceof BaseError && + isErrorWithStringCode(error.cause) && + error.cause.code === 'ER_NO_SUCH_TABLE' + ) { + throw new Error( + `No description found for table ${table.tableName}${table.schema ? ` in schema ${table.schema}` : ''}. Check the table name and schema; remember, they _are_ case sensitive.`, + ); + } + + throw error; + } + } + + /** + * Truncates a table + * + * @param tableName + * @param options + */ + async truncate(tableName: TableOrModel, options?: QiTruncateTableOptions): Promise { + const sql = this.queryGenerator.truncateTableQuery(tableName, options); + const queryOptions = { ...options, raw: true, type: QueryTypes.RAW }; + if (Array.isArray(sql)) { + await this.#internalQueryInterface.executeQueriesSequentially(sql, queryOptions); + } else { + await this.sequelize.queryRaw(sql, queryOptions); + } + } + + /** + * Removes a column from a table + * + * @param tableName + * @param columnName + * @param options + */ + async removeColumn( + tableName: TableOrModel, + columnName: string, + options?: RemoveColumnOptions, + ): Promise { + const queryOptions = { ...options, raw: true }; + const sql = this.queryGenerator.removeColumnQuery(tableName, columnName, queryOptions); + + await this.sequelize.queryRaw(sql, queryOptions); + } + + /** + * Add a constraint to a table + * + * Available constraints: + * - UNIQUE + * - DEFAULT (MSSQL only) + * - CHECK (Not supported by MySQL) + * - FOREIGN KEY + * - PRIMARY KEY + * + * @example UNIQUE + * ```ts + * queryInterface.addConstraint('Users', { + * fields: ['email'], + * type: 'UNIQUE', + * name: 'custom_unique_constraint_name' + * }); + * ``` + * + * @example CHECK + * ```ts + * queryInterface.addConstraint('Users', { + * fields: ['roles'], + * type: 'CHECK', + * where: { + * roles: ['user', 'admin', 'moderator', 'guest'] + * } + * }); + * ``` + * + * @example Default - MSSQL only + * ```ts + * queryInterface.addConstraint('Users', { + * fields: ['roles'], + * type: 'DEFAULT', + * defaultValue: 'guest' + * }); + * ``` + * + * @example Primary Key + * ```ts + * queryInterface.addConstraint('Users', { + * fields: ['username'], + * type: 'PRIMARY KEY', + * name: 'custom_primary_constraint_name' + * }); + * ``` + * + * @example Composite Primary Key + * ```ts + * queryInterface.addConstraint('Users', { + * fields: ['first_name', 'last_name'], + * type: 'PRIMARY KEY', + * name: 'custom_primary_constraint_name' + * }); + * ``` + * + * @example Foreign Key + * ```ts + * 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 Composite Foreign Key + * ```ts + * 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 tableName - Table name where you want to add a constraint + * @param options - An object to define the constraint name, type etc + */ + async addConstraint(tableName: TableOrModel, options: AddConstraintOptions): Promise { + 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'); + } + + const sql = this.queryGenerator.addConstraintQuery(tableName, options); + + await this.sequelize.queryRaw(sql, { ...options, raw: true, type: QueryTypes.RAW }); + } + + async deferConstraints( + constraintChecking: ConstraintChecking, + options?: DeferConstraintsOptions, + ): Promise { + setTransactionFromCls(options ?? {}, this.sequelize); + if (!options?.transaction) { + throw new Error('Missing transaction in deferConstraints option.'); + } + + const sql = this.queryGenerator.setConstraintCheckingQuery(constraintChecking); + + await this.sequelize.queryRaw(sql, { ...options, raw: true, type: QueryTypes.RAW }); + } + + /** + * Remove a constraint from a table + * + * @param tableName -Table name to drop constraint from + * @param constraintName -Constraint name + * @param options -Query options + */ + async removeConstraint( + tableName: TableOrModel, + constraintName: string, + options?: RemoveConstraintOptions, + ): Promise { + const sql = this.queryGenerator.removeConstraintQuery(tableName, constraintName, options); + + await this.sequelize.queryRaw(sql, { ...options, raw: true, type: QueryTypes.RAW }); + } + + async showConstraints( + tableName: TableOrModel, + options?: ShowConstraintsOptions, + ): Promise { + const sql = this.queryGenerator.showConstraintsQuery(tableName, options); + const rawConstraints = await this.sequelize.queryRaw(sql, { + ...options, + raw: true, + type: QueryTypes.SHOWCONSTRAINTS, + }); + const constraintMap = new Map(); + for (const { + columnNames, + definition, + deleteAction, + initiallyDeferred, + isDeferrable, + referencedColumnNames, + referencedTableName, + referencedTableSchema, + updateAction, + ...rawConstraint + } of rawConstraints) { + const constraint = constraintMap.get(rawConstraint.constraintName)!; + if (constraint) { + if (columnNames) { + constraint.columnNames = constraint.columnNames + ? [...new Set([...constraint.columnNames, columnNames])] + : [columnNames]; + } + + if (referencedColumnNames) { + constraint.referencedColumnNames = constraint.referencedColumnNames + ? [...new Set([...constraint.referencedColumnNames, referencedColumnNames])] + : [referencedColumnNames]; + } + } else { + const constraintData: ConstraintDescription = { ...rawConstraint }; + if (columnNames) { + constraintData.columnNames = [columnNames]; + } + + if (referencedTableSchema) { + constraintData.referencedTableSchema = referencedTableSchema; + } + + if (referencedTableName) { + constraintData.referencedTableName = referencedTableName; + } + + if (referencedColumnNames) { + constraintData.referencedColumnNames = [referencedColumnNames]; + } + + if (deleteAction) { + constraintData.deleteAction = deleteAction.replaceAll('_', ' '); + } + + if (updateAction) { + constraintData.updateAction = updateAction.replaceAll('_', ' '); + } + + if (definition) { + constraintData.definition = definition; + } + + if (this.sequelize.dialect.supports.constraints.deferrable) { + constraintData.deferrable = isDeferrable + ? initiallyDeferred === 'YES' + ? Deferrable.INITIALLY_DEFERRED + : Deferrable.INITIALLY_IMMEDIATE + : Deferrable.NOT; + } + + constraintMap.set(rawConstraint.constraintName, constraintData); + } + } + + return [...constraintMap.values()]; + } + + /** + * Returns all foreign key constraints of requested tables + * + * @deprecated Use {@link showConstraints} instead. + * @param _tableNames + * @param _options + */ + getForeignKeysForTables(_tableNames: TableOrModel[], _options?: QueryRawOptions): Error { + throw new Error(`getForeignKeysForTables has been deprecated. Use showConstraints instead.`); + } + + /** + * Get foreign key references details for the table + * + * @deprecated Use {@link showConstraints} instead. + * @param _tableName + * @param _options + */ + getForeignKeyReferencesForTable(_tableName: TableOrModel, _options?: QueryRawOptions): Error { + throw new Error( + `getForeignKeyReferencesForTable has been deprecated. Use showConstraints instead.`, + ); + } + + /** + * Disables foreign key checks for the duration of the callback. + * The foreign key checks are only disabled for the current connection. + * To specify the connection, you can either use the "connection" or the "transaction" option. + * If you do not specify a connection, this method will reserve a connection for the duration of the callback, + * and release it afterwards. You will receive the connection or transaction as the first argument of the callback. + * You must use this connection to execute queries + * + * @example + * ```ts + * await this.queryInterface.withoutForeignKeyChecks(options, async connection => { + * const truncateOptions = { ...options, connection }; + * + * for (const model of models) { + * await model.truncate(truncateOptions); + * } + * }); + * ``` + * + * @param cb + */ + async withoutForeignKeyChecks(cb: WithoutForeignKeyChecksCallback): Promise; + async withoutForeignKeyChecks( + options: QueryRawOptions, + cb: WithoutForeignKeyChecksCallback, + ): Promise; + async withoutForeignKeyChecks( + optionsOrCallback: QueryRawOptions | WithoutForeignKeyChecksCallback, + maybeCallback?: WithoutForeignKeyChecksCallback, + ): Promise { + let options: QueryRawOptions; + let callback: WithoutForeignKeyChecksCallback; + + if (typeof optionsOrCallback === 'function') { + options = {}; + callback = optionsOrCallback; + } else { + options = { ...optionsOrCallback }; + callback = maybeCallback!; + } + + setTransactionFromCls(options, this.sequelize); + + if (options.connection) { + return this.#withoutForeignKeyChecks(options, callback); + } + + return this.sequelize.withConnection(async connection => { + return this.#withoutForeignKeyChecks({ ...options, connection }, callback); + }); + } + + async #withoutForeignKeyChecks( + options: QueryRawOptions, + cb: WithoutForeignKeyChecksCallback, + ): Promise { + try { + await this.unsafeToggleForeignKeyChecks(false, options); + + isNotNullish.assert(options.connection, 'options.connection must be provided'); + + return await cb(options.connection); + } finally { + await this.unsafeToggleForeignKeyChecks(true, options); + } + } + + /** + * Toggles foreign key checks. + * Don't forget to turn them back on, use {@link withoutForeignKeyChecks} to do this automatically. + * + * @param enable + * @param options + */ + async unsafeToggleForeignKeyChecks(enable: boolean, options?: QueryRawOptions): Promise { + await this.sequelize.queryRaw( + this.queryGenerator.getToggleForeignKeyChecksQuery(enable), + options, + ); + } + + /** + * Commit an already started transaction. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _commitTransaction( + transaction: Transaction, + options: CommitTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to commit a transaction without the transaction object.'); + } + + const sql = this.queryGenerator.commitTransactionQuery(); + await this.sequelize.queryRaw(sql, { + ...options, + transaction, + supportsSearchPath: false, + [COMPLETES_TRANSACTION]: true, + }); + } + + /** + * Create a new savepoint. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _createSavepoint(transaction: Transaction, options: CreateSavepointOptions): Promise { + if (!this.queryGenerator.dialect.supports.savepoints) { + throw new Error(`Savepoints are not supported by ${this.sequelize.dialect.name}.`); + } + + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to create a savepoint without the transaction object.'); + } + + const sql = this.queryGenerator.createSavepointQuery(options.savepointName); + await this.sequelize.queryRaw(sql, { ...options, transaction, supportsSearchPath: false }); + } + + /** + * Rollback to a savepoint. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _rollbackSavepoint( + transaction: Transaction, + options: RollbackSavepointOptions, + ): Promise { + if (!this.queryGenerator.dialect.supports.savepoints) { + throw new Error(`Savepoints are not supported by ${this.sequelize.dialect.name}.`); + } + + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a savepoint without the transaction object.'); + } + + const sql = this.queryGenerator.rollbackSavepointQuery(options.savepointName); + await this.sequelize.queryRaw(sql, { + ...options, + transaction, + supportsSearchPath: false, + [COMPLETES_TRANSACTION]: true, + }); + } + + /** + * Rollback (revert) a transaction that hasn't been committed. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _rollbackTransaction( + transaction: Transaction, + options: RollbackTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a transaction without the transaction object.'); + } + + const sql = this.queryGenerator.rollbackTransactionQuery(); + await this.sequelize.queryRaw(sql, { + ...options, + transaction, + supportsSearchPath: false, + [COMPLETES_TRANSACTION]: true, + }); + } + + /** + * Set the isolation level of a transaction. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _setIsolationLevel( + transaction: Transaction, + options: SetIsolationLevelOptions, + ): Promise { + if (!this.queryGenerator.dialect.supports.settingIsolationLevelDuringTransaction) { + throw new Error( + `Changing the isolation level during the transaction is not supported by ${this.sequelize.dialect.name}.`, + ); + } + + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error( + 'Unable to set the isolation level for a transaction without the transaction object.', + ); + } + + const sql = this.queryGenerator.setIsolationLevelQuery(options.isolationLevel); + await this.sequelize.queryRaw(sql, { ...options, transaction, supportsSearchPath: false }); + } + + /** + * Begin a new transaction. + * + * This is an internal method used by `sequelize.transaction()` use at your own risk. + * + * @param transaction + * @param options + */ + async _startTransaction( + transaction: Transaction, + options: StartTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to start a transaction without the transaction object.'); + } + + const queryOptions = { ...options, transaction, supportsSearchPath: false }; + if ( + queryOptions.isolationLevel && + !this.queryGenerator.dialect.supports.settingIsolationLevelDuringTransaction + ) { + const sql = this.queryGenerator.setIsolationLevelQuery(queryOptions.isolationLevel); + await this.sequelize.queryRaw(sql, queryOptions); + } + + const sql = this.queryGenerator.startTransactionQuery(options); + await this.sequelize.queryRaw(sql, queryOptions); + if ( + queryOptions.isolationLevel && + this.sequelize.dialect.supports.settingIsolationLevelDuringTransaction + ) { + await transaction.setIsolationLevel(queryOptions.isolationLevel); + } + } + + /** + * Deletes records from a table + * + * @param tableOrModel + * @param options + */ + async bulkDelete(tableOrModel: TableOrModel, options?: QiBulkDeleteOptions): Promise { + const bulkDeleteOptions = { ...options }; + const sql = this.queryGenerator.bulkDeleteQuery(tableOrModel, bulkDeleteOptions); + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete bulkDeleteOptions.replacements; + + return this.sequelize.queryRaw(sql, { + ...bulkDeleteOptions, + raw: true, + type: QueryTypes.DELETE, + }); + } +} diff --git a/packages/core/src/abstract-dialect/query-interface.d.ts b/packages/core/src/abstract-dialect/query-interface.d.ts new file mode 100644 index 000000000000..c81c6e420119 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-interface.d.ts @@ -0,0 +1,505 @@ +import type { SetRequired } from 'type-fest'; +import type { Col } from '../expression-builders/col.js'; +import type { Fn } from '../expression-builders/fn.js'; +import type { Literal } from '../expression-builders/literal.js'; +import type { + AttributeOptions, + Attributes, + CreationAttributes, + Filterable, + Model, + ModelStatic, + NormalizedAttributeOptions, +} from '../model'; +import type { QueryRawOptions, QueryRawOptionsWithModel } from '../sequelize'; +import type { AllowLowercase } from '../utils/types.js'; +import type { DataType } from './data-types.js'; +import type { AbstractDialect } from './dialect.js'; +import type { AddLimitOffsetOptions } from './query-generator.internal-types.js'; +import type { AddColumnQueryOptions } from './query-generator.js'; +import type { RemoveIndexQueryOptions, TableOrModel } from './query-generator.types.js'; +import { AbstractQueryInterfaceTypeScript } from './query-interface-typescript'; +import type { ColumnsDescription } from './query-interface.types.js'; +import type { WhereOptions } from './where-sql-builder-types.js'; + +interface Replaceable { + /** + * Only named replacements are allowed in query interface methods. + */ + replacements?: Record; +} + +interface QiOptionsWithReplacements extends QueryRawOptions, Replaceable {} + +export interface QiInsertOptions extends QueryRawOptions, Replaceable { + returning?: boolean | Array; +} + +export interface QiSelectOptions extends QueryRawOptions, Filterable, AddLimitOffsetOptions { + minifyAliases?: boolean; +} + +export interface QiUpdateOptions extends QueryRawOptions, Replaceable { + returning?: boolean | Array; +} + +export interface QiArithmeticOptions extends QueryRawOptions, Replaceable { + returning?: boolean | Array; +} + +export interface QiUpsertOptions + extends QueryRawOptionsWithModel, + Replaceable {} + +export interface CreateFunctionOptions extends QueryRawOptions { + force?: boolean; +} + +export interface CollateCharsetOptions { + collate?: string; + charset?: string; +} + +export interface QueryInterfaceCreateTableOptions extends QueryRawOptions, CollateCharsetOptions { + engine?: string; + /** + * Used for compound unique keys. + */ + uniqueKeys?: { [indexName: string]: { fields: string[] } }; +} + +export interface TableNameWithSchema { + tableName: string; + schema?: string; + delimiter?: string; +} + +export type TableName = string | TableNameWithSchema; + +export type IndexType = AllowLowercase<'UNIQUE' | 'FULLTEXT' | 'SPATIAL'>; +export type IndexMethod = 'BTREE' | 'HASH' | 'GIST' | 'SPGIST' | 'GIN' | 'BRIN' | string; + +export interface IndexField { + /** + * The name of the column + */ + name: string; + + /** + * Create a prefix index of length chars + */ + length?: number; + + /** + * The direction the column should be sorted in + */ + order?: 'ASC' | 'DESC'; + + /** + * The collation (sort order) for the column + */ + collate?: string; + + /** + * Index operator type. Postgres only + */ + operator?: string; +} + +export interface IndexOptions { + /** + * The name of the index. Defaults to model name + _ + fields concatenated + */ + name?: string; + + /** For FULLTEXT columns set your parser */ + parser?: string | null; + + /** + * Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL` + */ + type?: IndexType | undefined; + + /** + * Should the index be unique? Can also be triggered by setting type to `UNIQUE` + * + * @default false + */ + unique?: boolean; + + /** + * The message to display if the unique constraint is violated. + */ + msg?: string; + + /** + * PostgreSQL will build the index without taking any write locks. Postgres only. + * + * @default false + */ + concurrently?: boolean; + + /** + * The fields to index. + */ + // TODO: rename to "columns" + fields?: Array; + + /** + * The method to create the index by (`USING` statement in SQL). + * BTREE and HASH are supported by mysql and postgres. + * Postgres additionally supports GIST, SPGIST, BRIN and GIN. + */ + using?: IndexMethod; + + /** + * Index operator type. Postgres only + */ + operator?: string; + + /** + * Optional where parameter for index. Can be used to limit the index to certain rows. + */ + where?: WhereOptions; + + /** + * Prefix to append to the index name. + */ + prefix?: string; + + /** + * Non-key columns to be added to the lead level of the nonclustered index. + */ + include?: Literal | Array; +} + +export interface QueryInterfaceIndexOptions + extends IndexOptions, + Omit {} + +export interface QueryInterfaceRemoveIndexOptions + extends QueryInterfaceIndexOptions, + RemoveIndexQueryOptions {} + +export interface FunctionParam { + type: string; + name?: string; + direction?: string; +} + +export interface IndexFieldDescription { + attribute: string; + length: number | undefined; + order: 'DESC' | 'ASC'; + collate: string | undefined; +} + +export interface IndexDescription { + primary: boolean; + fields: IndexFieldDescription[]; + includes: string[] | undefined; + name: string; + tableName: string | undefined; + unique: boolean; + type: string | undefined; +} + +export interface AddColumnOptions extends AddColumnQueryOptions, QueryRawOptions, Replaceable {} + +export interface CreateTableAttributeOptions extends AttributeOptions { + /** + * Apply unique constraint on a column + */ + unique?: boolean; +} + +/** + * Interface for Attributes provided for all columns in a model + */ +export type CreateTableAttributes = { + /** + * The description of a database column + */ + [name in keyof TAttributes]: DataType | CreateTableAttributeOptions; +}; + +/** + * This interface exposes low-level APIs to interact with the database. + * Typically useful in contexts where models are not available, such as migrations. + * + * This interface is available through {@link Sequelize#queryInterface}. + */ +export class AbstractQueryInterface< + Dialect extends AbstractDialect = AbstractDialect, +> extends AbstractQueryInterfaceTypeScript { + /** + * Creates a table with specified attributes. + * + * @param tableName Name of table to create + * @param attributes Hash of attributes, key is attribute name, value is data type + * @param options Table options. + */ + createTable( + tableName: TableName, + attributes: CreateTableAttributes>, + options?: QueryInterfaceCreateTableOptions, + ): Promise; + + /** + * Drops all defined enums + * + * @param options + */ + dropAllEnums(options?: QueryRawOptions): Promise; + + /** + * Adds a new column to a table + */ + addColumn( + table: TableName, + key: string, + attribute: AttributeOptions | DataType, + options?: AddColumnOptions, + ): Promise; + + /** + * Changes a column + */ + changeColumn( + tableName: TableName, + attributeName: string, + dataTypeOrOptions?: DataType | AttributeOptions, + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Renames a column + */ + renameColumn( + tableName: TableName, + attrNameBefore: string, + attrNameAfter: string, + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Adds a new index to a table + */ + addIndex( + tableName: TableOrModel, + attributes: string[], + options?: QueryInterfaceIndexOptions, + rawTablename?: string, + ): Promise; + addIndex( + tableName: TableOrModel, + options: SetRequired, + rawTablename?: string, + ): Promise; + + /** + * Removes an index of a table + */ + removeIndex( + tableName: TableName, + indexName: string, + options?: QueryInterfaceRemoveIndexOptions, + ): Promise; + removeIndex( + tableName: TableName, + attributes: string[], + options?: QueryInterfaceRemoveIndexOptions, + ): Promise; + + /** + * Shows the index of a table + */ + showIndex(tableName: TableOrModel, options?: QueryRawOptions): Promise; + + /** + * Put a name to an index + */ + nameIndexes(indexes: string[], rawTablename: string): Promise; + + /** + * Inserts a new record + */ + insert( + instance: Model | null, + tableName: TableName, + values: object, + options?: QiInsertOptions, + ): Promise; + + /** + * Inserts or Updates a record in the database + */ + upsert( + tableName: TableName, + insertValues: object, + updateValues: object, + where: object, + options?: QiUpsertOptions, + ): Promise; + + /** + * Inserts multiple records at once + */ + bulkInsert( + tableName: TableName, + records: object[], + options?: QiOptionsWithReplacements, + attributes?: Record, + ): Promise; + + /** + * Updates a row + */ + update( + instance: M, + tableName: TableName, + values: object, + where: WhereOptions>, + options?: QiUpdateOptions, + ): Promise; + + /** + * Updates multiple rows at once + */ + bulkUpdate( + tableName: TableName, + values: object, + where: WhereOptions, + options?: QiOptionsWithReplacements, + columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions }, + ): Promise; + + /** + * Returns selected rows + */ + select( + model: ModelStatic | null, + tableName: TableName, + options?: QiSelectOptions, + ): Promise; + + /** + * Increments a row value + */ + increment( + model: ModelStatic, + tableName: TableName, + where: WhereOptions>, + incrementAmountsByField: object, + extraAttributesToBeUpdated: object, + options?: QiArithmeticOptions, + ): Promise; + + /** + * Decrements a row value + */ + decrement( + model: ModelStatic, + tableName: TableName, + where: WhereOptions>, + decrementAmountsByField: object, + extraAttributesToBeUpdated: object, + options?: QiArithmeticOptions, + ): Promise; + + /** + * Selects raw without parsing the string into an object + */ + rawSelect( + tableName: TableName, + options: QiSelectOptions, + attributeSelector: string, + model?: ModelStatic, + ): Promise; + + /** + * Postgres only. Creates a trigger on specified table to call the specified function with supplied + * parameters. + */ + createTrigger( + tableName: TableName, + triggerName: string, + timingType: string, + fireOnArray: Array<{ + [key: string]: unknown; + }>, + functionName: string, + functionParams: FunctionParam[], + optionsArray: string[], + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Postgres only. Drops the specified trigger. + */ + dropTrigger( + tableName: TableName, + triggerName: string, + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Postgres only. Renames a trigger + */ + renameTrigger( + tableName: TableName, + oldTriggerName: string, + newTriggerName: string, + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Postgres only. Create a function + */ + createFunction( + functionName: string, + params: FunctionParam[], + returnType: string, + language: string, + body: string, + optionsArray?: string[], + options?: CreateFunctionOptions, + ): Promise; + + /** + * Postgres only. Drops a function + */ + dropFunction( + functionName: string, + params: FunctionParam[], + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Postgres only. Rename a function + */ + renameFunction( + oldFunctionName: string, + params: FunctionParam[], + newFunctionName: string, + options?: QiOptionsWithReplacements, + ): Promise; + + /** + * Escape an identifier (e.g. a table or attribute name). If force is true, the identifier will be quoted + * even if the `quoteIdentifiers` option is false. + */ + quoteIdentifier(identifier: string, force?: boolean): string; + + /** + * Split an identifier into .-separated tokens and quote each part. + */ + quoteIdentifiers(identifiers: string): string; + + // TODO: rename to "describeColumn" + assertTableHasColumn( + tableName: TableOrModel, + columnName: string, + options?: QueryRawOptions, + ): Promise; +} diff --git a/packages/core/src/abstract-dialect/query-interface.js b/packages/core/src/abstract-dialect/query-interface.js new file mode 100644 index 000000000000..2110a9030f0e --- /dev/null +++ b/packages/core/src/abstract-dialect/query-interface.js @@ -0,0 +1,880 @@ +'use strict'; + +import { map } from '@sequelize/utils'; +import defaults from 'lodash/defaults'; +import find from 'lodash/find'; +import intersection from 'lodash/intersection'; +import isObject from 'lodash/isObject'; +import mapValues from 'lodash/mapValues'; +import uniq from 'lodash/uniq'; +import * as DataTypes from '../data-types'; +import { QueryTypes } from '../enums'; +import { cloneDeep, getObjectFromMap } from '../utils/object'; +import { assertNoReservedBind, combineBinds } from '../utils/sql'; +import { AbstractDataType } from './data-types'; +import { AbstractQueryInterfaceTypeScript } from './query-interface-typescript'; + +/** + * The interface that Sequelize uses to talk to all databases + */ +export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript { + /** + * Create a table with given set of attributes + * + * ```js + * queryInterface.createTable( + * 'nameOfTheNewTable', + * { + * id: { + * type: DataTypes.INTEGER, + * primaryKey: true, + * autoIncrement: true + * }, + * createdAt: { + * type: DataTypes.DATE + * }, + * updatedAt: { + * type: DataTypes.DATE + * }, + * attr1: DataTypes.STRING, + * attr2: DataTypes.INTEGER, + * attr3: { + * type: DataTypes.BOOLEAN, + * defaultValue: false, + * allowNull: false + * }, + * //foreign key usage + * attr4: { + * type: DataTypes.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} + */ + // TODO: remove "schema" option from the option bag, it must be passed as part of "tableName" instead + async createTable(tableName, attributes, options, model) { + options = { ...options }; + + // TODO: the sqlite implementation of createTableQuery should be improved so it also generates a CREATE UNIQUE INDEX query + if (model && this.sequelize.dialect.name !== 'sqlite3') { + 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); + + // Snowflake requires special SQL commands for SEQUENCES + await this.ensureSequences(tableName, attributes, options); + + const modelTable = model?.table; + + if (!tableName.schema && (options.schema || modelTable?.schema)) { + tableName = this.queryGenerator.extractTableDetails(tableName); + tableName.schema = modelTable?.schema || options.schema; + } + + attributes = this.queryGenerator.attributesToSQL(attributes, { + table: tableName, + context: 'createTable', + withoutForeignKeyConstraints: options.withoutForeignKeyConstraints, + // schema override for multi-tenancy + schema: options.schema, + }); + + const sql = this.queryGenerator.createTableQuery(tableName, attributes, options); + + return await this.sequelize.queryRaw(sql, options); + } + + /** + * Add a new column to a table + * + * ```js + * queryInterface.addColumn('tableA', 'columnC', DataTypes.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)', + ); + } + + attribute = this.sequelize.normalizeAttribute(attribute); + + if ( + attribute.type instanceof AbstractDataType && + // we don't give a context if it already has one, because it could come from a Model. + !attribute.type.usageContext + ) { + attribute.type.attachUsageContext({ + tableName: table, + columnName: key, + sequelize: this.sequelize, + }); + } + + const { ifNotExists, ...rawQueryOptions } = options; + const addColumnQueryOptions = ifNotExists ? { ifNotExists } : undefined; + + return await this.sequelize.queryRaw( + this.queryGenerator.addColumnQuery(table, key, attribute, addColumnQueryOptions), + rawQueryOptions, + ); + } + + /** + * 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 + */ + + normalizeAttribute(dataTypeOrOptions) { + let attribute; + if (Object.values(DataTypes).includes(dataTypeOrOptions)) { + attribute = { type: dataTypeOrOptions, allowNull: true }; + } else { + attribute = dataTypeOrOptions; + } + + return this.sequelize.normalizeAttribute(attribute); + } + + /** + * Split a list of identifiers by "." and quote each part + * + * @param {string} identifier + * @param {boolean} force + * + * @returns {string} + */ + quoteIdentifier(identifier, force) { + return this.queryGenerator.quoteIdentifier(identifier, force); + } + + /** + * Split a list of identifiers by "." and quote each part. + * + * @param {string} identifiers + * + * @returns {string} + */ + quoteIdentifiers(identifiers) { + return this.queryGenerator.quoteIdentifiers(identifiers); + } + + /** + * 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 ||= {}; + + const query = this.queryGenerator.attributesToSQL( + { + [attributeName]: this.normalizeAttribute(dataTypeOrOptions), + }, + { + context: 'changeColumn', + table: tableName, + }, + ); + const sql = this.queryGenerator.changeColumnQuery(tableName, query); + + return this.sequelize.queryRaw(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 + */ + // TODO: rename to "describeColumn" + 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 ||= {}; + 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.queryRaw(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 = cloneDeep(options) ?? {}; + options.fields = attributes; + const sql = this.queryGenerator.addIndexQuery(tableName, options, rawTablename); + + return await this.sequelize.queryRaw(sql, { ...options, supportsSearchPath: false }); + } + + /** + * Show indexes on a table + * + * @param {TableOrModel} tableName + * @param {object} [options] Query options + * + * @returns {Promise} + * @private + */ + async showIndex(tableName, options) { + const sql = this.queryGenerator.showIndexesQuery(tableName, options); + + return await this.sequelize.queryRaw(sql, { ...options, type: QueryTypes.SHOWINDEXES }); + } + + /** + * 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 + * @param {boolean} [options.concurrently] Pass CONCURRENTLY so other operations run while the index is created + * + * @returns {Promise} + */ + async removeIndex(tableName, indexNameOrAttributes, options) { + options ||= {}; + const sql = this.queryGenerator.removeIndexQuery(tableName, indexNameOrAttributes, options); + + return await this.sequelize.queryRaw(sql, options); + } + + async insert(instance, tableName, values, options) { + if (options?.bind) { + assertNoReservedBind(options.bind); + } + + options = cloneDeep(options) ?? {}; + const modelDefinition = instance?.modelDefinition; + + options.hasTrigger = modelDefinition?.options.hasTrigger; + const { bind, query } = this.queryGenerator.insertQuery( + tableName, + values, + modelDefinition && getObjectFromMap(modelDefinition.attributes), + options, + ); + + options.type = QueryTypes.INSERT; + options.instance = instance; + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + options.bind = combineBinds(options.bind, bind); + + const results = await this.sequelize.queryRaw(query, 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 + */ + // Note: "where" is only used by DB2 and MSSQL. This is because these dialects do not propose any "ON CONFLICT UPDATE" mechanisms + // The UPSERT pattern in SQL server requires providing a WHERE clause + // TODO: the user should be able to configure the WHERE clause for upsert instead of the current default which + // is using the primary keys. + async upsert(tableName, insertValues, updateValues, where, options) { + if (!this.dialect.supports.upserts) { + throw new Error(`Upserts are not supported by the ${this.dialect.name} dialect.`); + } + + if (options?.bind) { + assertNoReservedBind(options.bind); + } + + options = { ...options }; + + const model = options.model; + const modelDefinition = model.modelDefinition; + + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + options.upsertKeys = options.conflictFields || []; + + if (options.upsertKeys.length === 0) { + const primaryKeys = Array.from( + map( + modelDefinition.primaryKeysAttributeNames, + pkAttrName => modelDefinition.attributes.get(pkAttrName).columnName, + ), + ); + + const uniqueColumnNames = Object.values(model.getIndexes()) + .filter(c => c.unique && c.fields.length > 0) + .map(c => c.fields); + // 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 indexKey = uniqueColumnNames.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 > 0 + ) { + options.upsertKeys = primaryKeys; + } + + options.upsertKeys = uniq(options.upsertKeys); + } + + const { bind, query } = this.queryGenerator.insertQuery( + tableName, + insertValues, + getObjectFromMap(modelDefinition.attributes), + options, + ); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacement; + options.bind = combineBinds(options.bind, bind); + + return await this.sequelize.queryRaw(query, 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, type: QueryTypes.INSERT }; + + const sql = this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + + const results = await this.sequelize.queryRaw(sql, options); + + return results[0]; + } + + async update(instance, tableName, values, where, options) { + if (options?.bind) { + assertNoReservedBind(options.bind); + } + + const modelDefinition = instance?.modelDefinition; + + options = { ...options, model: instance?.constructor }; + options.hasTrigger = modelDefinition?.options.hasTrigger; + + const { bind, query } = this.queryGenerator.updateQuery( + tableName, + values, + where, + options, + modelDefinition && getObjectFromMap(modelDefinition.attributes), + ); + + options.type = QueryTypes.UPDATE; + options.instance = instance; + + delete options.replacements; + + options.bind = combineBinds(options.bind, bind); + + return await this.sequelize.queryRaw(query, 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} where 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} [columnDefinitions] Attributes on return objects if supported by SQL dialect + * + * @returns {Promise} + */ + async bulkUpdate(tableName, values, where, options, columnDefinitions) { + if (options?.bind) { + assertNoReservedBind(options.bind); + } + + options = cloneDeep(options) ?? {}; + if (typeof where === 'object') { + where = cloneDeep(where) ?? {}; + } + + const { bind, query } = this.queryGenerator.updateQuery( + tableName, + values, + where, + options, + columnDefinitions, + ); + const table = isObject(tableName) ? tableName : { tableName }; + const model = options.model + ? options.model + : find(this.sequelize.models, { tableName: table.tableName }); + + options.type = QueryTypes.BULKUPDATE; + options.model = model; + options.bind = combineBinds(options.bind, bind); + + return await this.sequelize.queryRaw(query, options); + } + + async select(model, tableName, optionsArg) { + const minifyAliases = optionsArg.minifyAliases ?? this.sequelize.options.minifyAliases; + const options = { ...optionsArg, type: QueryTypes.SELECT, model, minifyAliases }; + + const sql = this.queryGenerator.selectQuery(tableName, options, model); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + + return await this.sequelize.queryRaw(sql, options); + } + + async increment( + model, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ) { + return this.#arithmeticQuery( + '+', + model, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ); + } + + async decrement( + model, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ) { + return this.#arithmeticQuery( + '-', + model, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ); + } + + async #arithmeticQuery( + operator, + model, + tableName, + where, + incrementAmountsByAttribute, + extraAttributesToBeUpdated, + options, + ) { + options = cloneDeep(options) ?? {}; + options.model = model; + + const sql = this.queryGenerator.arithmeticQuery( + operator, + tableName, + where, + incrementAmountsByAttribute, + extraAttributesToBeUpdated, + options, + ); + + options.type = QueryTypes.UPDATE; + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + + return await this.sequelize.queryRaw(sql, options); + } + + async rawSelect(tableName, options, attributeSelector, Model) { + options = 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!'); + } + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + + const data = await this.sequelize.queryRaw(sql, options); + if (!options.plain) { + return data; + } + + const result = data ? data[attributeSelector] : null; + + if (!options || !options.dataType) { + return result; + } + + const dataType = options.dataType; + + // TODO: DECIMAL is not safely representable as a float! + // Use the DataType's parse method instead. + if ( + (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) && + result !== null + ) { + return Number.parseFloat(result); + } + + // TODO: BIGINT is not safely representable as an int! + // Use the DataType's parse method instead. + if ( + (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) && + result !== null + ) { + return Number.parseInt(result, 10); + } + + if (dataType instanceof DataTypes.DATE && 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 ||= {}; + if (sql) { + return await this.sequelize.queryRaw(sql, options); + } + } + + async dropTrigger(tableName, triggerName, options) { + const sql = this.queryGenerator.dropTrigger(tableName, triggerName); + options ||= {}; + + if (sql) { + return await this.sequelize.queryRaw(sql, options); + } + } + + async renameTrigger(tableName, oldTriggerName, newTriggerName, options) { + const sql = this.queryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); + options ||= {}; + + if (sql) { + return await this.sequelize.queryRaw(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 ||= {}; + + if (sql) { + return await this.sequelize.queryRaw(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 ||= {}; + + if (sql) { + return await this.sequelize.queryRaw(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 ||= {}; + + if (sql) { + return await this.sequelize.queryRaw(sql, options); + } + } + + // Helper methods useful for querying + + /** + * @private + */ + ensureEnums() { + // noop by default + } + + /** + * @private + */ + ensureSequences() { + // noop by default + } + + /** + * @private + */ + getNextPrimaryKeyValue() { + // noop by default + } +} diff --git a/packages/core/src/abstract-dialect/query-interface.types.ts b/packages/core/src/abstract-dialect/query-interface.types.ts new file mode 100644 index 000000000000..5c6171ce06d0 --- /dev/null +++ b/packages/core/src/abstract-dialect/query-interface.types.ts @@ -0,0 +1,181 @@ +import type { Deferrable } from '../deferrable'; +import type { QueryRawOptions } from '../sequelize'; +import type { IsolationLevel } from '../transaction'; +import type { + AddConstraintQueryOptions, + BulkDeleteQueryOptions, + CreateDatabaseQueryOptions, + CreateSchemaQueryOptions, + DropSchemaQueryOptions, + DropTableQueryOptions, + ListDatabasesQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveColumnQueryOptions, + RemoveConstraintQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + StartTransactionQueryOptions, + TruncateTableQueryOptions, +} from './query-generator.types'; + +export interface DatabaseDescription { + name: string; +} + +export interface ColumnDescription { + type: string; + allowNull: boolean; + defaultValue: string; + primaryKey: boolean; + autoIncrement: boolean; + comment: string | null; +} + +export type ColumnsDescription = Record; + +export type ConstraintType = 'CHECK' | 'DEFAULT' | 'FOREIGN KEY' | 'PRIMARY KEY' | 'UNIQUE'; + +export interface RawConstraintDescription { + constraintCatalog?: string; + constraintSchema: string; + constraintName: string; + constraintType: ConstraintType; + tableCatalog?: string; + tableSchema: string; + tableName: string; + columnNames?: string; + referencedTableSchema?: string; + referencedTableName?: string; + referencedColumnNames?: string; + deleteAction?: string; + updateAction?: string; + definition?: string; + isDeferrable?: string; + initiallyDeferred?: string; +} + +export interface ConstraintDescription { + constraintCatalog?: string; + constraintSchema: string; + constraintName: string; + constraintType: ConstraintType; + tableCatalog?: string; + tableSchema: string; + tableName: string; + columnNames?: string[]; + referencedTableSchema?: string; + referencedTableName?: string; + referencedColumnNames?: string[]; + deleteAction?: string; + updateAction?: string; + definition?: string; + deferrable?: Deferrable; +} + +/** Options accepted by {@link AbstractQueryInterface#createDatabase} */ +export interface CreateDatabaseOptions extends CreateDatabaseQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#listDatabases} */ +export interface ListDatabasesOptions extends ListDatabasesQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#createSchema} */ +export interface CreateSchemaOptions extends CreateSchemaQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#dropSchema} */ +export interface DropSchemaOptions extends DropSchemaQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#listSchemas} */ +export interface QiListSchemasOptions extends ListSchemasQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#dropAllSchemas} */ +export interface QiDropAllSchemasOptions extends DropSchemaOptions, QueryRawOptions { + /** + * List of schemas to skip dropping (i.e., list of schemas to keep) + */ + skip?: string[]; +} + +/** Options accepted by {@link AbstractQueryInterface#listTables} */ +export interface QiListTablesOptions extends ListTablesQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#describeTable} */ +export interface DescribeTableOptions extends QueryRawOptions { + /** + * @deprecated Use a TableNameWithSchema object to specify the schema or set the schema globally in the options. + */ + schema?: string; + /** + * @deprecated Use a TableNameWithSchema object to specify the schemaDelimiter. + */ + schemaDelimiter?: string; +} + +/** Options accepted by {@link AbstractQueryInterface#dropTable} */ +export interface QiDropTableOptions extends DropTableQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#dropAllTables} */ +export interface QiDropAllTablesOptions extends ListTablesQueryOptions, QiDropTableOptions { + skip?: string[]; +} + +/** Options accepted by {@link AbstractQueryInterface#renameTable} */ +export interface RenameTableOptions extends RenameTableQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#truncate} */ +export interface QiTruncateTableOptions extends TruncateTableQueryOptions, QueryRawOptions {} + +export interface FetchDatabaseVersionOptions extends Omit {} + +/** Options accepted by {@link AbstractQueryInterface#removeColumn} */ +export interface RemoveColumnOptions extends RemoveColumnQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#addConstraint} */ +export type AddConstraintOptions = AddConstraintQueryOptions & QueryRawOptions; + +/** Options accepted by {@link AbstractQueryInterface#deferConstraints} */ +export interface DeferConstraintsOptions extends QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#removeConstraint} */ +export interface RemoveConstraintOptions extends RemoveConstraintQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#showConstraints} */ +export interface ShowConstraintsOptions extends ShowConstraintsQueryOptions, QueryRawOptions {} + +/** Options accepted by {@link AbstractQueryInterface#_commitTransaction} */ +export interface CommitTransactionOptions + extends Omit {} + +/** Options accepted by {@link AbstractQueryInterface#_createSavepoint} */ +export interface CreateSavepointOptions + extends Omit { + savepointName: string; +} + +/** Options accepted by {@link AbstractQueryInterface#_rollbackSavepoint} */ +export interface RollbackSavepointOptions + extends Omit { + savepointName: string; +} + +/** Options accepted by {@link AbstractQueryInterface#_rollbackTransaction} */ +export interface RollbackTransactionOptions + extends Omit {} + +/** Options accepted by {@link AbstractQueryInterface#_setIsolationLevel} */ +export interface SetIsolationLevelOptions + extends Omit { + isolationLevel: IsolationLevel; +} + +/** Options accepted by {@link AbstractQueryInterface#_startTransaction} */ +export interface StartTransactionOptions + extends StartTransactionQueryOptions, + Omit { + isolationLevel?: IsolationLevel | null | undefined; +} + +/** Options accepted by {@link AbstractQueryInterface#bulkDelete} */ +export interface QiBulkDeleteOptions + extends BulkDeleteQueryOptions, + Omit {} diff --git a/packages/core/src/abstract-dialect/query.d.ts b/packages/core/src/abstract-dialect/query.d.ts new file mode 100644 index 000000000000..4c39774f57b9 --- /dev/null +++ b/packages/core/src/abstract-dialect/query.d.ts @@ -0,0 +1,248 @@ +import type { QueryTypes } from '../enums'; +import type { IncludeOptions, Model, ModelStatic } from '../model'; +import type { Sequelize } from '../sequelize'; +import type { AbstractConnection } from './connection-manager'; + +export interface AbstractQueryGroupJoinDataOptions { + checkExisting: boolean; +} + +export interface AbstractQueryOptions { + instance?: Model; + model?: ModelStatic; + 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); + queryLabel?: string; + + 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; +} + +/** + * 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 { + /** + * The SQL being executed by this Query. + */ + sql: string; + + /** + * Returns a unique identifier assigned to a query internally by Sequelize. + */ + uuid: unknown; + + /** + * A Sequelize connection instance. + */ + connection: AbstractConnection; + + /** + * If provided, returns the model instance. + */ + instance: Model; + + /** + * Model type definition. + */ + model: ModelStatic; + + /** + * Returns the current sequelize instance. + */ + sequelize: Sequelize; + + options: AbstractQueryOptions; + + constructor(connection: AbstractConnection, sequelize: Sequelize, options?: AbstractQueryOptions); + + /** + * Execute the passed sql query. + * + * @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 the field name associated with the unique constraint. + * + * @returns The unique constraint error message. + * @private + */ + private getUniqueConstraintErrorMessage(field: string): string; + + /** + * Checks if the query type is RAW + */ + isRawQuery(): boolean; + + /** + * Checks if the query type is UPSERT + */ + isUpsertQuery(): boolean; + + /** + * Checks if the query type is INSERT + */ + isInsertQuery(results?: unknown[], metaData?: unknown): boolean; + + /** + * Sets auto increment field values (if applicable). + */ + handleInsertQuery(results?: unknown[], metaData?: unknown): void; + + /** + * Checks if the query type is SHOWINDEXES + */ + isShowIndexesQuery(): boolean; + + /** + * Checks if the query type is SHOWCONSTRAINTS + */ + isShowConstraintsQuery(): boolean; + + /** + * Checks if the query type is DESCRIBE + */ + isDescribeQuery(): boolean; + + /** + * Checks if the query type is SELECT + */ + isSelectQuery(): boolean; + + /** + * Checks if the query type is BULKUPDATE + */ + isBulkUpdateQuery(): boolean; + + /** + * Checks if the query type is DELETE + */ + isDeleteQuery(): boolean; + + /** + * Checks if the query type is UPDATE + */ + isUpdateQuery(): boolean; + + /** + * Maps raw fields to attribute names (if applicable). + * + * @param results from a select query. + * @returns the first model instance within the select. + */ + handleSelectQuery(results: Model[]): Model; + + /** + * Checks if the query starts with 'show' or 'describe' + */ + isShowOrDescribeQuery(): boolean; + + /** + * Checks if the query starts with 'call' + */ + isCallQuery(): boolean; + + /** + * @protected + * @returns A function to call after the query was completed. + */ + protected _logQuery( + sql: string, + debugContext: (msg: string) => any, + parameters: unknown[] | Record, + ): () => void; + + /** + * The function takes the result of the query execution and groups + * the associated data by the callee. + * + * @example + * ```ts + * 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 } + * } + * ]); + * ``` + * + * Results in: + * + * ```ts + * [ + * { + * some: 'data', + * id: 1, + * association: [ + * { foo: 'bar', id: 1 }, + * { foo: 'bar', id: 2 }, + * { foo: 'bar', id: 3 } + * ] + * } + * ] + * ``` + * + * @private + */ + static _groupJoinData( + rows: unknown[], + includeOptions: IncludeOptions, + options: AbstractQueryGroupJoinDataOptions, + ): unknown[]; +} diff --git a/packages/core/src/abstract-dialect/query.js b/packages/core/src/abstract-dialect/query.js new file mode 100644 index 000000000000..acaba307cfd7 --- /dev/null +++ b/packages/core/src/abstract-dialect/query.js @@ -0,0 +1,786 @@ +'use strict'; + +import NodeUtil from 'node:util'; +import { QueryTypes } from '../enums'; +import { AbstractDataType } from './data-types'; + +import chain from 'lodash/chain'; +import findKey from 'lodash/findKey'; +import isEmpty from 'lodash/isEmpty'; +import reduce from 'lodash/reduce'; + +const Dot = require('dottie'); +const deprecations = require('../utils/deprecations'); +const crypto = require('node:crypto'); + +export class AbstractQuery { + constructor(connection, sequelize, options) { + this.uuid = crypto.randomUUID(); + this.connection = connection; + this.instance = options.instance; + this.model = options.model; + this.sequelize = sequelize; + this.options = { + plain: false, + raw: false, + logging: console.debug, + ...options, + }; + this.checkLoggingOption(); + + if (options.rawErrors) { + // The default implementation in AbstractQuery just returns the same + // error object. By overidding this.formatError, this saves every dialect + // having to check for options.rawErrors in their own formatError + // implementations. + this.formatError = AbstractQuery.prototype.formatError; + } + } + + async logWarnings(results) { + const warningResults = await this.run('SHOW WARNINGS'); + const warningMessage = `${this.sequelize.dialect.name} 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.hasOwn(_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); + + return results; + } + + /** + * Formats a raw database error from the database library into a common Sequelize exception. + * + * @param {Error} error The exception object. + * @param {object} errStack The stack trace that started the database query. + * @returns {BaseError} the new formatted error object. + */ + formatError(error, errStack) { + // Default implementation, no formatting. + // Each dialect overrides this method to parse errors from their respective the database engines. + error.stack = errStack; + + return error; + } + + /** + * Execute the passed sql query. + * + * Examples: + * + * query.run('SELECT 1') + * + * @private + */ + run() { + throw new Error("The run method wasn't overwritten!"); + } + + /** + * Check the logging option of the instance and print deprecation warnings. + * + * @private + */ + checkLoggingOption() { + if (this.options.logging === true) { + deprecations.noTrueLogging(); + this.options.logging = console.debug; + } + } + + /** + * Get the attributes of an insert query, which contains the just inserted id. + * + * @returns {string} The field name. + * @private + */ + getInsertIdField() { + return 'insertId'; + } + + getUniqueConstraintErrorMessage(field) { + if (!field) { + return 'Must be unique'; + } + + const message = `${field} must be unique`; + + if (!this.model) { + return message; + } + + for (const index of this.model.getIndexes()) { + if (!index.unique) { + continue; + } + + if (index.fields.includes(field.replaceAll('"', '')) && index.msg) { + return index.msg; + } + } + + return message; + } + + isRawQuery() { + return this.options.type === QueryTypes.RAW; + } + + isUpsertQuery() { + return this.options.type === QueryTypes.UPSERT; + } + + isInsertQuery(results, metaData) { + let result = true; + + if (this.options.type === QueryTypes.INSERT) { + return true; + } + + // is insert query if sql contains insert into + result &&= this.sql.toLowerCase().startsWith('insert into'); + + // is insert query if no results are passed or if the result has the inserted id + result &&= !results || Object.hasOwn(results, this.getInsertIdField()); + + // is insert query if no metadata are passed or if the metadata has the inserted id + result &&= !metaData || Object.hasOwn(metaData, this.getInsertIdField()); + + return result; + } + + handleInsertQuery(results, metaData) { + if (!this.instance) { + return; + } + + const autoIncrementAttribute = this.model.modelDefinition.autoIncrementAttributeName; + const id = results?.[this.getInsertIdField()] ?? metaData?.[this.getInsertIdField()] ?? null; + + this.instance[autoIncrementAttribute] = id; + } + + isShowIndexesQuery() { + return this.options.type === QueryTypes.SHOWINDEXES; + } + + isShowConstraintsQuery() { + return this.options.type === QueryTypes.SHOWCONSTRAINTS; + } + + isDescribeQuery() { + return this.options.type === QueryTypes.DESCRIBE; + } + + isSelectQuery() { + return this.options.type === QueryTypes.SELECT; + } + + isBulkUpdateQuery() { + return this.options.type === QueryTypes.BULKUPDATE; + } + + isDeleteQuery() { + return this.options.type === QueryTypes.DELETE; + } + + isUpdateQuery() { + return this.options.type === QueryTypes.UPDATE; + } + + handleSelectQuery(results) { + let result = null; + + // Map raw fields to names if a mapping is provided + if (this.options.fieldMap) { + const fieldMap = this.options.fieldMap; + results = results.map(result => + reduce( + fieldMap, + (result, name, field) => { + if (result[field] !== undefined && name !== field) { + result[name] = result[field]; + delete result[field]; + } + + return result; + }, + result, + ), + ); + } + + // Raw queries + if (this.options.raw) { + result = results.map(result => { + let o = {}; + + for (const key in result) { + if (Object.hasOwn(result, key)) { + o[key] = result[key]; + } + } + + if (this.options.nest) { + o = Dot.transform(o); + } + + return o; + }); + // Queries with include + } else if (this.options.hasJoin === true) { + results = AbstractQuery._groupJoinData( + results, + { + model: this.model, + includeMap: this.options.includeMap, + includeNames: this.options.includeNames, + }, + { + checkExisting: this.options.hasMultiAssociation, + }, + ); + + result = this.model.bulkBuild( + this._parseDataArrayByType(results, this.model, this.options.includeMap), + { + isNewRecord: false, + include: this.options.include, + includeNames: this.options.includeNames, + includeMap: this.options.includeMap, + includeValidated: true, + attributes: this.options.originalAttributes || this.options.attributes, + raw: true, + comesFromDatabase: true, + }, + ); + // Regular queries + } else { + result = this.model.bulkBuild( + this._parseDataArrayByType(results, this.model, this.options.includeMap), + { + isNewRecord: false, + raw: true, + comesFromDatabase: true, + attributes: this.options.originalAttributes || this.options.attributes, + }, + ); + } + + // return the first real model instance if options.plain is set (e.g. Model.find) + if (this.options.plain) { + result = result.length === 0 ? null : result[0]; + } + + return result; + } + + /** + * Calls {@link DataTypes.ABSTRACT#parseDatabaseValue} on all attributes returned by the database, if a model is specified. + * + * This method mutates valueArrays. + * + * @param {Array} valueArrays The values to parse + * @param {Model} model The model these values belong to + * @param {object} includeMap The list of included associations + */ + _parseDataArrayByType(valueArrays, model, includeMap) { + for (const values of valueArrays) { + this._parseDataByType(values, model, includeMap); + } + + return valueArrays; + } + + _parseDataByType(values, model, includeMap) { + for (const key of Object.keys(values)) { + // parse association values + // hasOwnProperty is very important here. An include could be called "toString" + if (includeMap && Object.hasOwn(includeMap, key)) { + if (Array.isArray(values[key])) { + values[key] = this._parseDataArrayByType( + values[key], + includeMap[key].model, + includeMap[key].includeMap, + ); + } else { + values[key] = this._parseDataByType( + values[key], + includeMap[key].model, + includeMap[key].includeMap, + ); + } + + continue; + } + + const attribute = model?.modelDefinition.attributes.get(key); + values[key] = this._parseDatabaseValue(values[key], attribute?.type); + } + + return values; + } + + _parseDatabaseValue(value, attributeType) { + if (value == null) { + return value; + } + + if (!attributeType || !(attributeType instanceof AbstractDataType)) { + return value; + } + + return attributeType.parseDatabaseValue(value); + } + + isShowOrDescribeQuery() { + let result = false; + + result ||= this.sql.toLowerCase().startsWith('show'); + result ||= this.sql.toLowerCase().startsWith('describe'); + + return result; + } + + isCallQuery() { + return this.sql.toLowerCase().startsWith('call'); + } + + /** + * @param {string} sql + * @param {Function} debugContext + * @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 logQueryParameters = + this.sequelize.options.logQueryParameters || options.logQueryParameters; + const startTime = Date.now(); + let logParameter = ''; + + if (logQueryParameters && parameters) { + const delimiter = sql.endsWith(';') ? '' : ';'; + + logParameter = `${delimiter} with parameters ${NodeUtil.inspect(parameters)}`; + } + + const fmt = `(${connection.uuid || 'default'}): ${sql}${logParameter}`; + const queryLabel = options.queryLabel ? `${options.queryLabel}\n` : ''; + const msg = `${queryLabel}Executing ${fmt}`; + debugContext(msg); + if (!benchmark) { + this.sequelize.log(`${queryLabel}Executing ${fmt}`, options); + } + + return () => { + const afterMsg = `${queryLabel}Executed ${fmt}`; + debugContext(afterMsg); + if (benchmark) { + this.sequelize.log(afterMsg, Date.now() - startTime, options); + } + }; + } + + /** + * 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, includeOptions, options) { + /* + * Assumptions + * ID is not necessarily the first field + * All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible) + * Parent keys will be seen before any include/child keys + * Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other) + */ + + /* + * Author (MH) comment: This code is an unreadable mess, but it's performant. + * groupJoinData is a performance critical function so we prioritize perf over readability. + */ + if (rows.length === 0) { + return []; + } + + // Generic looping + let i; + let length; + let $i; + let $length; + // Row specific looping + let rowsI; + let row; + const rowsLength = rows.length; + // Key specific looping + let keys; + let key; + let keyI; + let keyLength; + let prevKey; + let values; + let topValues; + let topExists; + const checkExisting = options.checkExisting; + // If we don't have to deduplicate we can pre-allocate the resulting array + let itemHash; + let parentHash; + let topHash; + const results = checkExisting ? [] : Array.from({ length: rowsLength }); + const resultMap = {}; + const includeMap = {}; + // Result variables for the respective functions + let $keyPrefix; + let $prevKeyPrefix; + let $lastKeyPrefix; + let $current; + let $parent; + // Map each key to an include option + let previousPiece; + const buildIncludeMap = piece => { + if (Object.hasOwn($current.includeMap, piece)) { + includeMap[key] = $current = $current.includeMap[piece]; + if (previousPiece) { + previousPiece = `${previousPiece}.${piece}`; + } else { + previousPiece = piece; + } + + includeMap[previousPiece] = $current; + } + }; + + // Calculate the string prefix of a key ('User.Results' for 'User.Results.id') + const keyPrefixStringMemo = {}; + const keyPrefixString = (key, memo) => { + if (!Object.hasOwn(memo, key)) { + memo[key] = key.slice(0, Math.max(0, key.lastIndexOf('.'))); + } + + return memo[key]; + }; + + // Removes the prefix from a key ('id' for 'User.Results.id') + const removeKeyPrefixMemo = {}; + const removeKeyPrefix = key => { + if (!Object.hasOwn(removeKeyPrefixMemo, key)) { + const index = key.lastIndexOf('.'); + removeKeyPrefixMemo[key] = key.slice(index === -1 ? 0 : index + 1); + } + + return removeKeyPrefixMemo[key]; + }; + + // Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id') + const keyPrefixMemo = {}; + const keyPrefix = key => { + // We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values + if (!Object.hasOwn(keyPrefixMemo, key)) { + const prefixString = keyPrefixString(key, keyPrefixStringMemo); + if (!Object.hasOwn(keyPrefixMemo, prefixString)) { + keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : []; + } + + keyPrefixMemo[key] = keyPrefixMemo[prefixString]; + } + + return keyPrefixMemo[key]; + }; + + // Calcuate the last item in the array prefix ('Results' for 'User.Results.id') + const lastKeyPrefixMemo = {}; + const lastKeyPrefix = key => { + if (!Object.hasOwn(lastKeyPrefixMemo, key)) { + const prefix = keyPrefix(key); + const length = prefix.length; + + lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1]; + } + + return lastKeyPrefixMemo[key]; + }; + + // sort the array by the level of their depth calculated by dot. + const sortByDepth = keys => keys.sort((a, b) => a.split('.').length - b.split('.').length); + + const getUniqueKeyAttributes = model => { + let uniqueKeyAttributes = chain(model.uniqueKeys); + uniqueKeyAttributes = uniqueKeyAttributes + .result(`${uniqueKeyAttributes.findKey()}.fields`) + .map(field => findKey(model.attributes, chr => chr.field === field)) + .value(); + + return uniqueKeyAttributes; + }; + + const stringify = obj => (obj instanceof Buffer ? obj.toString('hex') : obj); + let primaryKeyAttributes; + let uniqueKeyAttributes; + let prefix; + + for (rowsI = 0; rowsI < rowsLength; rowsI++) { + row = rows[rowsI]; + + // Keys are the same for all rows, so only need to compute them on the first row + if (rowsI === 0) { + keys = sortByDepth(Object.keys(row)); + keyLength = keys.length; + } + + if (checkExisting) { + topExists = false; + + // Compute top level hash key (this is usually just the primary key values) + $length = includeOptions.model.primaryKeyAttributes.length; + topHash = ''; + if ($length === 1) { + topHash = stringify(row[includeOptions.model.primaryKeyAttributes[0]]); + } else if ($length > 1) { + for ($i = 0; $i < $length; $i++) { + topHash += stringify(row[includeOptions.model.primaryKeyAttributes[$i]]); + } + } else if (!isEmpty(includeOptions.model.uniqueKeys)) { + uniqueKeyAttributes = getUniqueKeyAttributes(includeOptions.model); + for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { + topHash += row[uniqueKeyAttributes[$i]]; + } + } + } + + topValues = values = {}; + $prevKeyPrefix = undefined; + for (keyI = 0; keyI < keyLength; keyI++) { + key = keys[keyI]; + + // The string prefix isn't actualy needed + // We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix + // TODO: Find a better way? + $keyPrefix = keyPrefix(key); + + // On the first row we compute the includeMap + if (rowsI === 0 && !Object.hasOwn(includeMap, key)) { + if ($keyPrefix.length === 0) { + includeMap[key] = includeMap[''] = includeOptions; + } else { + $current = includeOptions; + previousPiece = undefined; + $keyPrefix.forEach(buildIncludeMap); + } + } + + // End of key set + if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) { + if (checkExisting) { + // Compute hash key for this set instance + // TODO: Optimize + length = $prevKeyPrefix.length; + $parent = null; + parentHash = null; + + if (length) { + for (i = 0; i < length; i++) { + prefix = $parent ? `${$parent}.${$prevKeyPrefix[i]}` : $prevKeyPrefix[i]; + primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; + $length = primaryKeyAttributes.length; + itemHash = prefix; + if ($length === 1) { + itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[0]}`]); + } else if ($length > 1) { + for ($i = 0; $i < $length; $i++) { + itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[$i]}`]); + } + } else if (!isEmpty(includeMap[prefix].model.uniqueKeys)) { + uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); + for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { + itemHash += row[`${prefix}.${uniqueKeyAttributes[$i]}`]; + } + } + + if (!parentHash) { + parentHash = topHash; + } + + itemHash = parentHash + itemHash; + $parent = prefix; + if (i < length - 1) { + parentHash = itemHash; + } + } + } else { + itemHash = topHash; + } + + if (itemHash === topHash) { + if (!resultMap[itemHash]) { + resultMap[itemHash] = values; + } else { + topExists = true; + } + } else if (!resultMap[itemHash]) { + $parent = resultMap[parentHash]; + $lastKeyPrefix = lastKeyPrefix(prevKey); + + if (includeMap[prevKey].association.isSingleAssociation) { + if ($parent) { + $parent[$lastKeyPrefix] = resultMap[itemHash] = values; + } + } else { + if (!$parent[$lastKeyPrefix]) { + $parent[$lastKeyPrefix] = []; + } + + $parent[$lastKeyPrefix].push((resultMap[itemHash] = values)); + } + } + + // Reset values + values = {}; + } else { + // If checkExisting is false it's because there's only 1:1 associations in this query + // However we still need to map onto the appropriate parent + // For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop + $current = topValues; + length = $keyPrefix.length; + if (length) { + for (i = 0; i < length; i++) { + if (i === length - 1) { + values = $current[$keyPrefix[i]] = {}; + } + + $current = $current[$keyPrefix[i]] || {}; + } + } + } + } + + // End of iteration, set value and set prev values (for next iteration) + values[removeKeyPrefix(key)] = row[key]; + prevKey = key; + $prevKeyPrefix = $keyPrefix; + } + + if (checkExisting) { + length = $prevKeyPrefix.length; + $parent = null; + parentHash = null; + + if (length) { + for (i = 0; i < length; i++) { + prefix = $parent ? `${$parent}.${$prevKeyPrefix[i]}` : $prevKeyPrefix[i]; + primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; + $length = primaryKeyAttributes.length; + itemHash = prefix; + if ($length === 1) { + itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[0]}`]); + } else if ($length > 0) { + for ($i = 0; $i < $length; $i++) { + itemHash += stringify(row[`${prefix}.${primaryKeyAttributes[$i]}`]); + } + } else if (!isEmpty(includeMap[prefix].model.uniqueKeys)) { + uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); + for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { + itemHash += row[`${prefix}.${uniqueKeyAttributes[$i]}`]; + } + } + + if (!parentHash) { + parentHash = topHash; + } + + itemHash = parentHash + itemHash; + $parent = prefix; + if (i < length - 1) { + parentHash = itemHash; + } + } + } else { + itemHash = topHash; + } + + if (itemHash === topHash) { + if (!resultMap[itemHash]) { + resultMap[itemHash] = values; + } else { + topExists = true; + } + } else if (!resultMap[itemHash]) { + $parent = resultMap[parentHash]; + $lastKeyPrefix = lastKeyPrefix(prevKey); + + if (includeMap[prevKey].association.isSingleAssociation) { + if ($parent) { + $parent[$lastKeyPrefix] = resultMap[itemHash] = values; + } + } else { + if (!$parent[$lastKeyPrefix]) { + $parent[$lastKeyPrefix] = []; + } + + $parent[$lastKeyPrefix].push((resultMap[itemHash] = values)); + } + } + + if (!topExists) { + results.push(topValues); + } + } else { + results[rowsI] = topValues; + } + } + + return results; + } +} diff --git a/packages/core/src/abstract-dialect/replication-pool.ts b/packages/core/src/abstract-dialect/replication-pool.ts new file mode 100644 index 000000000000..478804cf94c5 --- /dev/null +++ b/packages/core/src/abstract-dialect/replication-pool.ts @@ -0,0 +1,239 @@ +import { pojo, shallowClonePojo } from '@sequelize/utils'; +import { Pool, TimeoutError } from 'sequelize-pool'; +import type { Class } from 'type-fest'; +import { logger } from '../utils/logger.js'; + +const debug = logger.debugContext('pool'); + +export type ConnectionType = 'read' | 'write'; + +export interface ReplicationPoolOptions { + /** + * Maximum number of connections in pool. Default is 5 + */ + max: number; + + /** + * Minimum number of connections in pool. Default is 0 + */ + min: number; + + /** + * The maximum time, in milliseconds, that a connection can be idle before being released + */ + idle: number; + + /** + * The maximum time, in milliseconds, that pool will try to get connection before throwing error + */ + acquire: number; + + /** + * The time interval, in milliseconds, after which sequelize-pool will remove idle connections. + */ + evict: number; + + /** + * The number of times to use a connection before closing and replacing it. Default is Infinity + */ + maxUses: number; +} + +export interface AcquireConnectionOptions { + /** + * Set which replica to use. Available options are `read` and `write` + */ + type?: 'read' | 'write'; + + /** + * Force master or write replica to get connection from + */ + useMaster?: boolean; +} + +interface ReplicationPoolConfig { + readConfig: readonly ConnectionOptions[] | null; + writeConfig: ConnectionOptions; + pool: ReplicationPoolOptions; + + // TODO: move this option to sequelize-pool so it applies to sub-pools as well + timeoutErrorClass?: Class; + + connect(options: ConnectionOptions): Promise; + + disconnect(connection: Connection): Promise; + + validate(connection: Connection): boolean; + + beforeAcquire?(options: AcquireConnectionOptions): Promise; + afterAcquire?(connection: Connection, options: AcquireConnectionOptions): Promise; +} + +const owningPools = new WeakMap(); + +export class ReplicationPool { + /** + * Replication read pool. Will only be used if the 'read' replication option has been provided, + * otherwise the {@link write} will be used instead. + */ + readonly read: Pool | null; + readonly write: Pool; + + readonly #timeoutErrorClass: Class | undefined; + readonly #beforeAcquire: ((options: AcquireConnectionOptions) => Promise) | undefined; + readonly #afterAcquire: + | ((connection: Connection, options: AcquireConnectionOptions) => Promise) + | undefined; + + constructor(config: ReplicationPoolConfig) { + const { + connect, + disconnect, + validate, + beforeAcquire, + afterAcquire, + timeoutErrorClass, + readConfig, + writeConfig, + } = config; + + this.#beforeAcquire = beforeAcquire; + this.#afterAcquire = afterAcquire; + this.#timeoutErrorClass = timeoutErrorClass; + + if (!readConfig || readConfig.length === 0) { + // no replication, the write pool will always be used instead + this.read = null; + } else { + let reads = 0; + + this.read = new Pool({ + name: 'sequelize:read', + create: async () => { + // round robin config + const nextRead = reads++ % readConfig.length; + const connection = await connect(readConfig[nextRead]); + + owningPools.set(connection, 'read'); + + return connection; + }, + destroy: disconnect, + validate, + max: config.pool.max, + min: config.pool.min, + acquireTimeoutMillis: config.pool.acquire, + idleTimeoutMillis: config.pool.idle, + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses, + }); + } + + this.write = new Pool({ + name: 'sequelize:write', + create: async () => { + const connection = await connect(writeConfig); + + owningPools.set(connection, 'write'); + + return connection; + }, + destroy: disconnect, + validate, + max: config.pool.max, + min: config.pool.min, + acquireTimeoutMillis: config.pool.acquire, + idleTimeoutMillis: config.pool.idle, + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses, + }); + + if (!this.read) { + debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, no replication`); + } else { + debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, with replication`); + } + } + + async acquire(options?: AcquireConnectionOptions | undefined) { + options = options ? shallowClonePojo(options) : pojo(); + await this.#beforeAcquire?.(options); + Object.freeze(options); + + const { useMaster = false, type = 'write' } = options; + + if (type !== 'read' && type !== 'write') { + throw new Error(`Expected queryType to be either read or write. Received ${type}`); + } + + const pool = this.read != null && type === 'read' && !useMaster ? this.read : this.write; + + let connection; + try { + connection = await pool.acquire(); + } catch (error) { + if (this.#timeoutErrorClass && error instanceof TimeoutError) { + throw new this.#timeoutErrorClass(error.message, { cause: error }); + } + + throw error; + } + + await this.#afterAcquire?.(connection, options); + + return connection; + } + + release(client: Connection): void { + const connectionType = owningPools.get(client); + if (!connectionType) { + throw new Error('Unable to determine to which pool the connection belongs'); + } + + this.getPool(connectionType).release(client); + } + + async destroy(client: Connection): Promise { + const connectionType = owningPools.get(client); + if (!connectionType) { + throw new Error('Unable to determine to which pool the connection belongs'); + } + + await this.getPool(connectionType).destroy(client); + debug('connection destroy'); + } + + async destroyAllNow() { + await Promise.all([this.read?.destroyAllNow(), this.write.destroyAllNow()]); + + debug('all connections destroyed'); + } + + async drain() { + await Promise.all([this.write.drain(), this.read?.drain()]); + } + + getPool(poolType: ConnectionType): Pool { + if (poolType === 'read' && this.read != null) { + return this.read; + } + + return this.write; + } + + get size(): number { + return (this.read?.size ?? 0) + this.write.size; + } + + get available(): number { + return (this.read?.available ?? 0) + this.write.available; + } + + get using(): number { + return (this.read?.using ?? 0) + this.write.using; + } + + get waiting(): number { + return (this.read?.waiting ?? 0) + this.write.waiting; + } +} diff --git a/packages/core/src/abstract-dialect/where-sql-builder-types.ts b/packages/core/src/abstract-dialect/where-sql-builder-types.ts new file mode 100644 index 000000000000..6272340fc19f --- /dev/null +++ b/packages/core/src/abstract-dialect/where-sql-builder-types.ts @@ -0,0 +1,108 @@ +import type { AllowArray } from '@sequelize/utils'; +import type { DynamicSqlExpression } from '../expression-builders/base-sql-expression.js'; +import type { WhereOperators } from '../model.js'; +import type { Op } from '../operators.js'; + +/** + * This type allows using `Op.or`, `Op.and`, and `Op.not` recursively around another type. + * It also supports using a plain Array as an alias for `Op.and`. (unlike {@link AllowNotOrAndRecursive}). + * + * Example of plain-array treated as `Op.and`: + * ```ts + * User.findAll({ where: [{ id: 1 }, { id: 2 }] }); + * ``` + * + * Meant to be used by {@link WhereOptions}. + */ +export type AllowNotOrAndWithImplicitAndArrayRecursive = AllowArray< + // this is the equivalent of Op.and + | T + | { [Op.or]: AllowArray> } + | { [Op.and]: AllowArray> } + | { [Op.not]: AllowNotOrAndWithImplicitAndArrayRecursive } +>; + +/** + * The type accepted by every `where` option + */ +export type WhereOptions = + // "where" is typically optional. If the user sets it to undefined, we treat is as if the option was not set. + | undefined + | AllowNotOrAndWithImplicitAndArrayRecursive< + WhereAttributeHash | DynamicSqlExpression + >; + +/** + * This type allows using `Op.or`, `Op.and`, and `Op.not` recursively around another type. + * Unlike {@link AllowNotOrAndWithImplicitAndArrayRecursive}, it does not allow the 'implicit AND Array'. + * + * Example of plain-array NOT treated as Op.and: + * ```ts + * User.findAll({ where: { id: [1, 2] } }); + * ``` + * + * Meant to be used by {@link WhereAttributeHashValue}. + */ +type AllowNotOrAndRecursive = + | T + | { [Op.or]: AllowArray> } + | { [Op.and]: AllowArray> } + | { [Op.not]: AllowNotOrAndRecursive }; + +/** + * Types that can be compared to an attribute in a WHERE context. + */ +export type WhereAttributeHashValue = + | AllowNotOrAndRecursive< + // if the right-hand side is an array, it will be equal to Op.in + // otherwise it will be equal to Op.eq + // Exception: array attribtues always use Op.eq, never Op.in. + AttributeType extends any[] + ? WhereOperators[typeof Op.eq] | WhereOperators + : + | WhereOperators[typeof Op.in] + | WhereOperators[typeof Op.eq] + | WhereOperators + > + // TODO: this needs a simplified version just for JSON columns + | WhereAttributeHash; // for JSON columns + +/** + * A hash of attributes to describe your search. + * + * Possible key values: + * + * - An attribute name: `{ id: 1 }` + * - A nested attribute: `{ '$projects.id$': 1 }` + * - A JSON key: `{ 'object.key': 1 }` + * - A cast: `{ 'id::integer': 1 }` + * + * - A combination of the above: `{ '$join.attribute$.json.path::integer': 1 }` + */ +export type WhereAttributeHash = { + // support 'attribute' & '$attribute$' + [AttributeName in keyof TAttributes as AttributeName extends string + ? AttributeName | `$${AttributeName}$` + : never]?: WhereAttributeHashValue; +} & { + [AttributeName in keyof TAttributes as AttributeName extends string + ? // support 'json.path', '$json$.path', json[index]', '$json$[index]' + | `${AttributeName}.${string}` + | `$${AttributeName}$.${string}` + | `${AttributeName}[${string}` + | `$${AttributeName}$[${string}` + // support 'attribute::cast', '$attribute$::cast', 'json.path::cast' & '$json$.path::cast' + | `${AttributeName | `$${AttributeName}$` | `${AttributeName}.${string}` | `$${AttributeName}$.${string}`}:${string}` + : never]?: WhereAttributeHashValue; +} & { + // support '$nested.attribute$', '$nested.attribute$::cast', '$nested.attribute$.json.path', & '$nested.attribute$.json.path::cast', '$nested.attribute$[index]', & '$nested.attribute$[index]::cast' + [ + attribute: + | `$${string}.${string}$` + | `$${string}.${string}$::${string}` + | `$${string}.${string}$.${string}` + | `$${string}.${string}$.${string}:${string}` + | `$${string}.${string}$[${string}` + | `$${string}.${string}$[${string}:${string}` + ]: WhereAttributeHashValue; +}; diff --git a/packages/core/src/abstract-dialect/where-sql-builder.ts b/packages/core/src/abstract-dialect/where-sql-builder.ts new file mode 100644 index 000000000000..d55f992e41c0 --- /dev/null +++ b/packages/core/src/abstract-dialect/where-sql-builder.ts @@ -0,0 +1,1027 @@ +import type { Nullish } from '@sequelize/utils'; +import { EMPTY_ARRAY, EMPTY_OBJECT, isPlainObject, isString } from '@sequelize/utils'; +import NodeUtil from 'node:util'; +import { BaseError } from '../errors/base-error.js'; +import { AssociationPath } from '../expression-builders/association-path.js'; +import { Attribute } from '../expression-builders/attribute.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; +import { Cast } from '../expression-builders/cast.js'; +import { Col } from '../expression-builders/col.js'; +import { JsonPath } from '../expression-builders/json-path.js'; +import { SQL_NULL } from '../expression-builders/json-sql-null.js'; +import { Literal } from '../expression-builders/literal.js'; +import { Value } from '../expression-builders/value.js'; +import { Where } from '../expression-builders/where.js'; +import type { AbstractDialect, Expression, ModelDefinition, WhereOptions } from '../index.js'; +import { Op } from '../operators'; +import type { ParsedJsonPropertyKey } from '../utils/attribute-syntax.js'; +import { parseAttributeSyntax, parseNestedJsonKeySyntax } from '../utils/attribute-syntax.js'; +import { noOpCol } from '../utils/deprecations.js'; +import { extractModelDefinition } from '../utils/model-utils.js'; +import { getComplexKeys, getOperators } from '../utils/where.js'; +import type { NormalizedDataType } from './data-types.js'; +import * as DataTypes from './data-types.js'; +import type { FormatWhereOptions } from './query-generator-typescript.js'; +import type { AbstractQueryGenerator } from './query-generator.js'; +import type { WhereAttributeHashValue } from './where-sql-builder-types.js'; + +export class PojoWhere { + declare leftOperand: Expression; + declare whereValue: WhereAttributeHashValue; + + static create( + leftOperand: Expression, + whereAttributeHashValue: WhereAttributeHashValue, + ): PojoWhere { + const pojoWhere = new PojoWhere(); + pojoWhere.leftOperand = leftOperand; + pojoWhere.whereValue = whereAttributeHashValue; + + return pojoWhere; + } +} + +class ObjectPool { + #freeItems: T[]; + readonly #factory: () => T; + #lastOccupiedIndex: number; + constructor(factory: () => T, initialSize: number) { + this.#freeItems = Array.from({ length: initialSize }).map(factory); + this.#lastOccupiedIndex = initialSize - 1; + this.#factory = factory; + } + + getObject(): T { + if (this.#lastOccupiedIndex < 0) { + return this.#factory(); + } + + return this.#freeItems[this.#lastOccupiedIndex--]; + } + + free(val: T): void { + if (this.#lastOccupiedIndex >= this.#freeItems.length - 1) { + this.#freeItems.push(val); + + return; + } + + this.#freeItems[++this.#lastOccupiedIndex] = val; + } +} + +const pojoWherePool = new ObjectPool(() => new PojoWhere(), 20); + +export class WhereSqlBuilder { + readonly #dialect: AbstractDialect; + + #operatorMap: Record = { + [Op.eq]: '=', + [Op.ne]: '!=', + [Op.gte]: '>=', + [Op.gt]: '>', + [Op.lte]: '<=', + [Op.lt]: '<', + [Op.is]: 'IS', + [Op.isNot]: 'IS NOT', + [Op.in]: 'IN', + [Op.notIn]: 'NOT IN', + [Op.like]: 'LIKE', + [Op.notLike]: 'NOT LIKE', + [Op.iLike]: 'ILIKE', + [Op.notILike]: 'NOT ILIKE', + [Op.regexp]: '~', + [Op.notRegexp]: '!~', + [Op.iRegexp]: '~*', + [Op.notIRegexp]: '!~*', + [Op.between]: 'BETWEEN', + [Op.notBetween]: 'NOT BETWEEN', + [Op.overlap]: '&&', + [Op.contains]: '@>', + [Op.contained]: '<@', + [Op.adjacent]: '-|-', + [Op.strictLeft]: '<<', + [Op.strictRight]: '>>', + [Op.noExtendRight]: '&<', + [Op.noExtendLeft]: '&>', + [Op.any]: 'ANY', + [Op.all]: 'ALL', + [Op.match]: '@@', + [Op.anyKeyExists]: '?|', + [Op.allKeysExist]: '?&', + }; + + readonly #jsonType: NormalizedDataType | undefined; + readonly #arrayOfTextType: NormalizedDataType | undefined; + + constructor(dialect: AbstractDialect) { + this.#dialect = dialect; + + this.#jsonType = dialect.supports.dataTypes.JSON + ? new DataTypes.JSON().toDialectDataType(dialect) + : undefined; + + this.#arrayOfTextType = dialect.supports.dataTypes.ARRAY + ? new DataTypes.ARRAY(new DataTypes.TEXT()).toDialectDataType(dialect) + : undefined; + } + + get #queryGenerator(): AbstractQueryGenerator { + return this.#dialect.queryGenerator; + } + + setOperatorKeyword(op: symbol, keyword: string): void { + this.#operatorMap[op] = keyword; + } + + /** + * Transforms any value accepted by {@link WhereOptions} into a SQL string. + * + * @param where + * @param options + */ + formatWhereOptions(where: WhereOptions, options: FormatWhereOptions = EMPTY_OBJECT): string { + if (typeof where === 'string') { + throw new TypeError( + "Support for `{ where: 'raw query' }` has been removed. Use `{ where: literal('raw query') }` instead", + ); + } + + if (where === undefined) { + return ''; + } + + try { + return this.#handleRecursiveNotOrAndWithImplicitAndArray( + where, + (piece: PojoWhere | BaseSqlExpression) => { + if (piece instanceof BaseSqlExpression) { + return this.#queryGenerator.formatSqlExpression(piece, options); + } + + return this.formatPojoWhere(piece, options); + }, + ); + } catch (error) { + throw new BaseError( + `Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts.\nValue: ${NodeUtil.inspect(where)}`, + { + cause: error, + }, + ); + } + } + + /** + * This is the recursive "and", "or" and "not" handler of the first level of {@link WhereOptions} (the level *before* encountering an attribute name). + * Unlike handleRecursiveNotOrAndNestedPathRecursive, this method accepts arrays at the top level, which are implicitly converted to "and" groups. + * and does not handle nested JSON paths. + * + * @param input + * @param handlePart + * @param logicalOperator AND / OR + */ + #handleRecursiveNotOrAndWithImplicitAndArray( + input: WhereOptions, + handlePart: (part: BaseSqlExpression | PojoWhere) => string, + logicalOperator: typeof Op.and | typeof Op.or = Op.and, + ): string { + // Arrays in this method are treated as an implicit "AND" operator + if (Array.isArray(input)) { + return joinWithLogicalOperator( + input.map(part => { + if (part === undefined) { + return ''; + } + + return this.#handleRecursiveNotOrAndWithImplicitAndArray(part, handlePart); + }), + logicalOperator, + ); + } + + // if the input is not a plan object, then it can't include Operators. + if (!isPlainObject(input)) { + // @ts-expect-error -- This catches a scenario where the user did not respect the typing + if (!(input instanceof BaseSqlExpression)) { + throw new TypeError( + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got ${NodeUtil.inspect(input)} `, + ); + } + + return handlePart(input); + } + + const keys = getComplexKeys(input); + + const sqlArray = keys.map(operatorOrAttribute => { + if (operatorOrAttribute === Op.not) { + const generatedResult = this.#handleRecursiveNotOrAndWithImplicitAndArray( + // @ts-expect-error -- This is a recursive type, which TS does not handle well + input[Op.not], + handlePart, + ); + + return wrapWithNot(generatedResult); + } + + if (operatorOrAttribute === Op.and || operatorOrAttribute === Op.or) { + return this.#handleRecursiveNotOrAndWithImplicitAndArray( + // @ts-expect-error -- This is a recursive type, which TS does not handle well + input[operatorOrAttribute], + handlePart, + operatorOrAttribute as typeof Op.and | typeof Op.or, + ); + } + + // it *has* to be an attribute now + if (typeof operatorOrAttribute === 'symbol') { + throw new TypeError( + `Invalid Query: ${NodeUtil.inspect(input)} includes the Symbol Operator Op.${operatorOrAttribute.description} but only attributes, Op.and, Op.or, and Op.not are allowed.`, + ); + } + + let pojoWhereObject; + try { + pojoWhereObject = pojoWherePool.getObject(); + + pojoWhereObject.leftOperand = parseAttributeSyntax(operatorOrAttribute); + + // @ts-expect-error -- The type of "operatorOrAttribute" is too dynamic for TS + pojoWhereObject.whereValue = input[operatorOrAttribute]; + + return handlePart(pojoWhereObject); + } finally { + if (pojoWhereObject) { + pojoWherePool.free(pojoWhereObject); + } + } + }); + + return joinWithLogicalOperator(sqlArray, logicalOperator); + } + + /** + * This method is responsible for transforming a group "left operand" + "operators, right operands" (multiple) into a SQL string. + * + * @param pojoWhere The representation of the group. + * @param options Option bag. + */ + formatPojoWhere(pojoWhere: PojoWhere, options: FormatWhereOptions = EMPTY_OBJECT): string { + const modelDefinition = options?.model ? extractModelDefinition(options.model) : null; + + // we need to parse the left operand early to determine the data type of the right operand + let leftDataType = this.#getOperandType(pojoWhere.leftOperand, modelDefinition); + const operandIsJsonColumn = leftDataType == null || leftDataType instanceof DataTypes.JSON; + + return this.#handleRecursiveNotOrAndNestedPathRecursive( + pojoWhere.leftOperand, + pojoWhere.whereValue, + operandIsJsonColumn, + (left: Expression, operator: symbol | undefined, right: Expression) => { + // "left" could have been wrapped in a JSON path. If we still don't know its data type, it's very likely a JSON column + // if the user used a JSON path in the where clause. + if (leftDataType == null && left instanceof JsonPath) { + leftDataType = this.#jsonType; + } else if (left !== pojoWhere.leftOperand) { + // if "left" was wrapped in a JSON path, we need to get its data type again as it might have been cast + leftDataType = this.#getOperandType(left, modelDefinition); + } + + if (operator === Op.col) { + noOpCol(); + + right = new Col(right as string); + operator = Op.eq; + } + + // This happens when the user does something like `where: { id: { [Op.any]: { id: 1 } } }` + if (operator === Op.any || operator === Op.all) { + right = { [operator]: right }; + operator = Op.eq; + } + + if (operator == null) { + if (right === null && leftDataType instanceof DataTypes.JSON) { + throw new Error( + `When comparing against a JSON column, the JavaScript null value can be represented using either the JSON 'null', or the SQL NULL. You must be explicit about which one you mean by using Op.is or SQL_NULL for the SQL NULL; or Op.eq or JSON_NULL for the JSON 'null'. Learn more at https://sequelize.org/docs/v7/querying/json/`, + ); + } + + operator = + Array.isArray(right) && !(leftDataType instanceof DataTypes.ARRAY) + ? Op.in + : right === null || right === SQL_NULL + ? Op.is + : Op.eq; + } + + // backwards compatibility + if (right === null && !(leftDataType instanceof DataTypes.JSON)) { + if (operator === Op.eq) { + operator = Op.is; + } + + if (operator === Op.ne) { + operator = Op.isNot; + } + } + + const rightDataType = this.#getOperandType(right, modelDefinition); + + if (operator in this) { + // @ts-expect-error -- TS does not know that this is a method + return this[operator](left, leftDataType, operator, right, rightDataType, options); + } + + return this.formatBinaryOperation( + left, + leftDataType, + operator, + right, + rightDataType, + options, + ); + }, + ); + } + + protected [Op.notIn](...args: Parameters): string { + return this[Op.in](...args); + } + + protected [Op.in]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + const rightEscapeOptions = { ...options, type: rightDataType ?? leftDataType }; + const leftEscapeOptions = { ...options, type: leftDataType ?? rightDataType }; + + let rightSql: string; + if (right instanceof Literal) { + rightSql = this.#queryGenerator.escape(right, rightEscapeOptions); + } else if (Array.isArray(right)) { + if (right.length === 0) { + // NOT IN () does not exist in SQL, so we need to return a condition that is: + // - always false if the operator is IN + // - always true if the operator is NOT IN + if (operator === Op.notIn) { + return ''; + } + + rightSql = '(NULL)'; + } else { + rightSql = this.#queryGenerator.escapeList(right, rightEscapeOptions); + } + } else { + throw new TypeError( + 'Operators Op.in and Op.notIn must be called with an array of values, or a literal', + ); + } + + const leftSql = this.#queryGenerator.escape(left, leftEscapeOptions); + + return `${leftSql} ${this.#operatorMap[operator]} ${rightSql}`; + } + + protected [Op.isNot](...args: Parameters): string { + return this[Op.is](...args); + } + + protected [Op.is]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + if (right !== null && typeof right !== 'boolean' && !(right instanceof Literal)) { + throw new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ); + } + + // "IS" operator does not accept bind parameters, only literals + if (options.bindParam) { + delete options.bindParam; + } + + return this.formatBinaryOperation(left, undefined, operator, right, undefined, options); + } + + protected [Op.notBetween](...args: Parameters): string { + return this[Op.between](...args); + } + + protected [Op.between]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + const rightEscapeOptions = { ...options, type: rightDataType ?? leftDataType }; + const leftEscapeOptions = { ...options, type: leftDataType ?? rightDataType }; + + const leftSql = this.#queryGenerator.escape(left, leftEscapeOptions); + + let rightSql: string; + if (right instanceof BaseSqlExpression) { + rightSql = this.#queryGenerator.escape(right, rightEscapeOptions); + } else if (Array.isArray(right) && right.length === 2) { + rightSql = `${this.#queryGenerator.escape(right[0], rightEscapeOptions)} AND ${this.#queryGenerator.escape(right[1], rightEscapeOptions)}`; + } else { + throw new Error( + 'Operators Op.between and Op.notBetween must be used with an array of two values, or a literal.', + ); + } + + return `${leftSql} ${this.#operatorMap[operator]} ${rightSql}`; + } + + protected [Op.contains]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + // In postgres, Op.contains has multiple signatures: + // - RANGE Op.contains RANGE (both represented by fixed-size arrays in JS) + // - RANGE Op.contains VALUE + // - ARRAY Op.contains ARRAY + // When the left operand is a range RANGE, we must be able to serialize the right operand as either a RANGE or a VALUE. + if (!rightDataType && leftDataType instanceof DataTypes.RANGE && !Array.isArray(right)) { + // This serializes the right operand as a VALUE + return this.formatBinaryOperation( + left, + leftDataType, + operator, + right, + leftDataType.options.subtype, + options, + ); + } + + // This serializes the right operand as a RANGE (or an array for ARRAY contains ARRAY) + return this.formatBinaryOperation(left, leftDataType, operator, right, rightDataType, options); + } + + protected [Op.contained]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + // This function has the opposite semantics of Op.contains. It has the following signatures: + // - RANGE Op.contained RANGE (both represented by fixed-size arrays in JS) + // - VALUE Op.contained RANGE + // - ARRAY Op.contained ARRAY + + // This serializes VALUE contained RANGE + if ( + leftDataType instanceof DataTypes.AbstractDataType && + !(leftDataType instanceof DataTypes.RANGE) && + !(leftDataType instanceof DataTypes.ARRAY) && + Array.isArray(right) + ) { + return this.formatBinaryOperation( + left, + leftDataType, + operator, + right, + new DataTypes.RANGE(leftDataType).toDialectDataType(this.#dialect), + options, + ); + } + + // This serializes: + // RANGE contained RANGE + // ARRAY contained ARRAY + return this.formatBinaryOperation(left, leftDataType, operator, right, rightDataType, options); + } + + protected [Op.startsWith]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.like, + right, + rightDataType, + options, + false, + true, + ); + } + + protected [Op.notStartsWith]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.notLike, + right, + rightDataType, + options, + false, + true, + ); + } + + protected [Op.endsWith]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.like, + right, + rightDataType, + options, + true, + false, + ); + } + + protected [Op.notEndsWith]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.notLike, + right, + rightDataType, + options, + true, + false, + ); + } + + protected [Op.substring]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.like, + right, + rightDataType, + options, + true, + true, + ); + } + + protected [Op.notSubstring]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + return this.formatSubstring( + left, + leftDataType, + Op.notLike, + right, + rightDataType, + options, + true, + true, + ); + } + + protected formatSubstring( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + start: boolean, + end: boolean, + ) { + if (typeof right === 'string') { + const startToken = start ? '%' : ''; + const endToken = end ? '%' : ''; + + return this.formatBinaryOperation( + left, + leftDataType, + operator, + startToken + right + endToken, + rightDataType, + options, + ); + } + + const escapedPercent = this.#dialect.escapeString('%'); + const literalBuilder: Array = [`CONCAT(`]; + if (start) { + literalBuilder.push(escapedPercent, ', '); + } + + literalBuilder.push(new Value(right)); + + if (end) { + literalBuilder.push(', ', escapedPercent); + } + + literalBuilder.push(')'); + + return this.formatBinaryOperation( + left, + leftDataType, + operator, + new Literal(literalBuilder), + rightDataType, + options, + ); + } + + [Op.anyKeyExists]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + if (!this.#arrayOfTextType) { + throw new Error('This dialect does not support Op.anyKeyExists'); + } + + return this.formatBinaryOperation( + left, + leftDataType, + operator, + right, + this.#arrayOfTextType, + options, + ); + } + + [Op.allKeysExist]( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ): string { + if (!this.#arrayOfTextType) { + throw new Error('This dialect does not support Op.allKeysExist'); + } + + return this.formatBinaryOperation( + left, + leftDataType, + operator, + right, + this.#arrayOfTextType, + options, + ); + } + + protected formatBinaryOperation( + left: Expression, + leftDataType: NormalizedDataType | undefined, + operator: symbol, + right: Expression, + rightDataType: NormalizedDataType | undefined, + options: FormatWhereOptions, + ) { + const operatorSql = this.#operatorMap[operator]; + if (!operatorSql) { + throw new TypeError( + `Operator Op.${operator.description} does not exist or is not supported by this dialect.`, + ); + } + + const leftSql = this.#queryGenerator.escape(left, { + ...options, + type: leftDataType ?? rightDataType, + }); + const rightSql = + this.#formatOpAnyAll(right, rightDataType ?? leftDataType) || + this.#queryGenerator.escape(right, { ...options, type: rightDataType ?? leftDataType }); + + return `${wrapAmbiguousWhere(left, leftSql)} ${this.#operatorMap[operator]} ${wrapAmbiguousWhere(right, rightSql)}`; + } + + #formatOpAnyAll(value: unknown, type: NormalizedDataType | undefined): string { + if (!isPlainObject(value)) { + return ''; + } + + if (Op.any in value) { + return `ANY (${this.#formatOpValues(value[Op.any], type)})`; + } + + if (Op.all in value) { + return `ALL (${this.#formatOpValues(value[Op.all], type)})`; + } + + return ''; + } + + #formatOpValues(value: unknown, type: NormalizedDataType | undefined): string { + if (isPlainObject(value) && Op.values in value) { + const options = { type }; + + const operand: unknown[] = Array.isArray(value[Op.values]) + ? (value[Op.values] as unknown[]) + : [value[Op.values]]; + + const valueSql = operand.map(v => `(${this.#queryGenerator.escape(v, options)})`).join(', '); + + return `VALUES ${valueSql}`; + } + + return this.#queryGenerator.escape(value, { type: type && new DataTypes.ARRAY(type) }); + } + + /** + * This is the recursive "and", "or" and "not" handler of {@link WhereAttributeHashValue} (the level *after* encountering an attribute name). + * Unlike handleRecursiveNotOrAndWithImplicitAndArray, arrays at the top level have an implicit "IN" operator, instead of an implicit "AND" operator, + * and this method handles nested JSON paths. + * + * @param leftOperand + * @param whereValue + * @param allowJsonPath + * @param handlePart + * @param operator + * @param parentJsonPath + */ + #handleRecursiveNotOrAndNestedPathRecursive( + leftOperand: Expression, + whereValue: WhereAttributeHashValue, + allowJsonPath: boolean, + handlePart: (left: Expression, operator: symbol | undefined, right: Expression) => string, + operator: typeof Op.and | typeof Op.or = Op.and, + parentJsonPath: ReadonlyArray = EMPTY_ARRAY, + ): string { + if (!isPlainObject(whereValue)) { + return handlePart( + this.#wrapSimpleJsonPath(leftOperand, parentJsonPath), + undefined, + whereValue, + ); + } + + const stringKeys = Object.keys(whereValue); + if (!allowJsonPath && stringKeys.length > 0) { + return handlePart( + this.#wrapSimpleJsonPath(leftOperand, parentJsonPath), + undefined, + whereValue as Expression, + ); + } + + const keys = [...stringKeys, ...getOperators(whereValue)]; + + const parts: string[] = keys.map(key => { + const value = whereValue[key]; + + // nested JSON path + if (typeof key === 'string') { + // parse path segments & cast syntax + const parsedKey = parseNestedJsonKeySyntax(key); + + // optimization for common simple scenario (to skip replacing leftOperand on every iteration) + if (parsedKey.castsAndModifiers.length === 0) { + return this.#handleRecursiveNotOrAndNestedPathRecursive( + leftOperand, + value, + allowJsonPath, + handlePart, + operator, + [...parentJsonPath, ...parsedKey.pathSegments], + ); + } + + // less optimized scenario: happens when we leave the JSON path (cast to another type or unquote), + // we need to replace leftOperand with the casted value or the unquote operation + const newOperand = this.#wrapComplexJsonPath(leftOperand, parentJsonPath, parsedKey); + + return this.#handleRecursiveNotOrAndNestedPathRecursive( + newOperand, + value, + // TODO: allow JSON if last cast is JSON? + // needs a mechanism to get JS DataType from SQL DataType first. To get last cast: + // newOperand instanceof Cast && isString(newOperand.type) && newOperand.type.toLowerCase(); + false, + handlePart, + operator, + // reset json path + EMPTY_ARRAY, + ); + } + + if (key === Op.not) { + return wrapWithNot( + this.#handleRecursiveNotOrAndNestedPathRecursive( + leftOperand, + value, + allowJsonPath, + handlePart, + Op.and, + ), + ); + } + + if (key === Op.and || key === Op.or) { + if (Array.isArray(value)) { + const sqlParts = value.map(v => + this.#handleRecursiveNotOrAndNestedPathRecursive( + leftOperand, + v, + allowJsonPath, + handlePart, + Op.and, + ), + ); + + return joinWithLogicalOperator(sqlParts, key as typeof Op.and | typeof Op.or); + } + + return this.#handleRecursiveNotOrAndNestedPathRecursive( + leftOperand, + value, + allowJsonPath, + handlePart, + key as typeof Op.and | typeof Op.or, + ); + } + + return handlePart(this.#wrapSimpleJsonPath(leftOperand, parentJsonPath), key, value); + }); + + return joinWithLogicalOperator(parts, operator); + } + + #wrapSimpleJsonPath( + operand: Expression, + pathSegments: ReadonlyArray, + ): Expression { + if (pathSegments.length === 0) { + return operand; + } + + // merge JSON paths + if (operand instanceof JsonPath) { + return new JsonPath(operand.expression, [...operand.path, ...pathSegments]); + } + + return new JsonPath(operand, pathSegments); + } + + #wrapComplexJsonPath( + operand: Expression, + parentJsonPath: ReadonlyArray, + parsedPath: ParsedJsonPropertyKey, + ): Expression { + const finalPathSegments = + parentJsonPath.length > 0 + ? [...parentJsonPath, ...parsedPath.pathSegments] + : parsedPath.pathSegments; + + operand = this.#wrapSimpleJsonPath(operand, finalPathSegments); + + for (const castOrModifier of parsedPath.castsAndModifiers) { + if (isString(castOrModifier)) { + // casts are always strings + operand = new Cast(operand, castOrModifier); + } else { + // modifiers are always classes + operand = new castOrModifier(operand); + } + } + + return operand; + } + + #getOperandType( + operand: Expression, + modelDefinition: ModelDefinition | Nullish, + ): NormalizedDataType | undefined { + if (operand instanceof Cast) { + // TODO: if operand.type is a string (= SQL Type), look up a per-dialect mapping of SQL types to Sequelize types? + return this.#dialect.sequelize.normalizeDataType(operand.type); + } + + if (operand instanceof JsonPath) { + // JsonPath can wrap Attributes + return this.#jsonType; + } + + if (!modelDefinition) { + return undefined; + } + + if (operand instanceof AssociationPath) { + const association = modelDefinition.getAssociation(operand.associationPath); + + if (!association) { + return undefined; + } + + return this.#getOperandType(operand.attributeName, association.target.modelDefinition); + } + + if (operand instanceof Attribute) { + return modelDefinition.attributes.get(operand.attributeName)?.type; + } + + return undefined; + } +} + +export function joinWithLogicalOperator( + sqlArray: string[], + operator: typeof Op.and | typeof Op.or, +): string { + const operatorSql = operator === Op.and ? ' AND ' : ' OR '; + + sqlArray = sqlArray.filter(val => Boolean(val)); + + if (sqlArray.length === 0) { + return ''; + } + + if (sqlArray.length === 1) { + return sqlArray[0]; + } + + return sqlArray + .map(sql => { + if (/ AND | OR /i.test(sql)) { + return `(${sql})`; + } + + return sql; + }) + .join(operatorSql); +} + +function wrapWithNot(sql: string): string { + if (!sql) { + return ''; + } + + return `NOT (${sql})`; +} + +export function wrapAmbiguousWhere(operand: Expression, sql: string): string { + // where() can produce ambiguous SQL when used as an operand: + // + // { booleanAttr: where(fn('lower', col('name')), Op.is, null) } + // produces the ambiguous SQL: + // [booleanAttr] = lower([name]) IS NULL + // which is better written as: + // [booleanAttr] = (lower([name]) IS NULL) + if (operand instanceof Where && sql.includes(' ')) { + return `(${sql})`; + } + + return sql; +} diff --git a/packages/core/src/associations/base.ts b/packages/core/src/associations/base.ts new file mode 100644 index 000000000000..a6cbf972df61 --- /dev/null +++ b/packages/core/src/associations/base.ts @@ -0,0 +1,344 @@ +import type { AllowIterable, Nullish, PartialBy } from '@sequelize/utils'; +import { isIterable } from '@sequelize/utils'; +import isObject from 'lodash/isObject.js'; +import type { AttributeNames, AttributeOptions, Hookable, Model, ModelStatic } from '../model'; +import { cloneDeep } from '../utils/object.js'; +import type { NormalizeBaseAssociationOptions } from './helpers'; +import { AssociationSecret } from './helpers'; + +/** + * Creating associations in sequelize is done by calling one of the belongsTo / hasOne / hasMany / belongsToMany functions on a model (the source), and providing another model as the first argument to the function (the target). + * + * * hasOne - adds a foreign key to the target and singular association mixins to the source. + * * belongsTo - add a foreign key and singular association mixins to the source. + * * hasMany - adds a foreign key to target and plural association mixins to the source. + * * belongsToMany - creates an N:M association with a join table and adds plural association mixins to the source. The junction table is created with sourceId and targetId. + * + * Creating an association will add a foreign key constraint to the attributes. All associations use `CASCADE` on update and `SET NULL` on delete, except for n:m, which also uses `CASCADE` on delete. + * + * When creating associations, you can provide an alias, via the `as` option. This is useful if the same model is associated twice, or you want your association to be called something other than the name of the target model. + * + * As an example, consider the case where users have many pictures, one of which is their profile picture. All pictures have a `userId`, but in addition the user model also has a `profilePictureId`, to be able to easily load the user's profile picture. + * + * ```js + * User.hasMany(Picture) + * User.belongsTo(Picture, { as: 'ProfilePicture', foreignKeyConstraints: false }) + * + * user.getPictures() // gets you all pictures + * user.getProfilePicture() // gets you only the profile picture + * + * User.findAll({ + * where: ..., + * include: [ + * { model: Picture }, // load all pictures + * { model: Picture, as: 'ProfilePicture' }, // load the profile picture. + * // Notice that the spelling must be the exact same as the one in the association + * ] + * }) + * ``` + * To get full control over the foreign key column added by sequelize, you can use the `foreignKey` option. It can either be a string, that specifies the name, or and object type definition, + * equivalent to those passed to `sequelize.define`. + * + * ```js + * User.hasMany(Picture, { foreignKey: 'uid' }) + * ``` + * + * The foreign key column in Picture will now be called `uid` instead of the default `userId`. + * + * ```js + * User.hasMany(Picture, { + * foreignKey: { + * name: 'uid', + * allowNull: false + * } + * }) + * ``` + * + * This specifies that the `uid` column cannot be null. In most cases this will already be covered by the foreign key constraints, which sequelize creates automatically, but can be useful in case where the foreign keys are disabled, e.g. due to circular references (see `constraints: false` below). + * + * When fetching associated models, you can limit your query to only load some models. These queries are written in the same way as queries to `find`/`findAll`. To only get pictures in JPG, you can do: + * + * ```js + * user.getPictures({ + * where: { + * format: 'jpg' + * } + * }) + * ``` + * + * There are several ways to update and add new associations. Continuing with our example of users and pictures: + * ```js + * user.addPicture(p) // Add a single picture + * user.setPictures([p1, p2]) // Associate user with ONLY these two picture, all other associations will be deleted + * user.addPictures([p1, p2]) // Associate user with these two pictures, but don't touch any current associations + * ``` + * + * You don't have to pass in a complete object to the association functions, if your associated model has a single primary key: + * + * ```js + * user.addPicture(req.query.pid) // Here pid is just an integer, representing the primary key of the picture + * ``` + * + * In the example above we have specified that a user belongs to his profile picture. Conceptually, this might not make sense, but since we want to add the foreign key to the user model this is the way to do it. + * + * Note how we also specified `foreignKeyConstraints: false` for profile picture. This is because we add a foreign key from user to picture (profilePictureId), and from picture to user (userId). If we were to add foreign keys to both, it would create a cyclic dependency, and sequelize would not know which table to create first, since user depends on picture, and picture depends on user. These kinds of problems are detected by sequelize before the models are synced to the database, and you will get an error along the lines of `Error: Cyclic dependency found. 'users' is dependent of itself`. If you encounter this, you should either disable some constraints, or rethink your associations completely. + */ +export abstract class Association< + S extends Model = Model, + T extends Model = Model, + ForeignKey extends string = string, + Opts extends NormalizedAssociationOptions = NormalizedAssociationOptions, +> { + source: ModelStatic; + target: ModelStatic; + isSelfAssociation: boolean; + isAliased: boolean; + + readonly options: Opts; + + abstract accessors: Record< + /* methodName in association */ string, + /* method name in model */ string + >; + + abstract foreignKey: ForeignKey; + + /** + * A reference to the association that created this one. + */ + readonly parentAssociation: Association | null; + + /** + * Creating an associations can automatically create other associations. + * This returns the initial association that caused the creation of the descendant associations. + */ + // eslint-disable-next-line @typescript-eslint/prefer-return-this-type -- false positive + get rootAssociation(): Association { + if (this.parentAssociation) { + return this.parentAssociation.rootAssociation; + } + + return this; + } + + /** + * The type of the association. One of `HasMany`, `BelongsTo`, `HasOne`, `BelongsToMany` + * + * @type {string} + */ + get associationType(): string { + return this.constructor.name; + } + + get isMultiAssociation(): boolean { + return (this.constructor as typeof Association).isMultiAssociation; + } + + /** + * @deprecated negate {@link isMultiAssociation} instead + */ + get isSingleAssociation(): boolean { + return !this.isMultiAssociation; + } + + static get isMultiAssociation(): boolean { + return false; + } + + constructor( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: Opts, + parent?: Association, + ) { + if (secret !== AssociationSecret) { + throw new Error( + `Class ${this.constructor.name} cannot be instantiated directly due to it mutating the source model. Use one of the static methods on Model instead.`, + ); + } + + this.source = source; + this.target = target; + this.parentAssociation = parent ?? null; + + this.isSelfAssociation = + // @ts-expect-error -- TypeScript thinks ModelStatic & ModelStatic have no overlap. + this.source === this.target; + + this.isAliased = Boolean(options?.as); + + this.options = cloneDeep(options) ?? {}; + + source.modelDefinition.hooks.runSync('beforeDefinitionRefresh'); + source.associations[this.as] = this; + source.modelDefinition.hooks.runSync('afterDefinitionRefresh'); + } + + /** + * The identifier of the relation on the source model. + */ + get as(): string { + return this.options.as; + } + + get name(): { singular: string; plural: string } { + return this.options.name; + } + + get scope(): AssociationScope | undefined { + return this.options.scope; + } + + [Symbol.for('nodejs.util.inspect.custom')]() { + return this.as; + } +} + +/** + * @private + */ +export abstract class MultiAssociation< + S extends Model = Model, + T extends Model = Model, + ForeignKey extends string = string, + TargetKey extends AttributeNames = any, + Opts extends NormalizedAssociationOptions = NormalizedAssociationOptions, +> extends Association { + static get isMultiAssociation() { + return true; + } + + protected toInstanceOrPkArray( + input: AllowIterable> | Nullish, + ): Array> { + if (input == null) { + return []; + } + + if (!isIterable(input) || !isObject(input)) { + return [input]; + } + + return [...input]; + } + + /** + * Normalize input + * + * @param input it may be array or single obj, instance or primary key + * + * @private + * @returns built objects + */ + protected toInstanceArray(input: AllowIterable> | null): T[] { + const normalizedInput = this.toInstanceOrPkArray(input); + + // TODO: remove eslint-disable once we drop support for < 5.2 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- TS 5.2 works, but < 5.2 does not + // @ts-ignore + return normalizedInput.map(element => { + if (element instanceof this.target) { + return element; + } + + const tmpInstance = Object.create(null); + // @ts-expect-error -- TODO: what if the target has no primary key? + tmpInstance[this.target.primaryKeyAttribute] = element; + + return this.target.build(tmpInstance, { isNewRecord: false }); + }); + } +} + +export type SingleAssociationAccessors = { + get: string; + set: string; + create: string; +}; + +export type MultiAssociationAccessors = { + get: string; + set: string; + addMultiple: string; + add: string; + create: string; + remove: string; + removeMultiple: string; + hasSingle: string; + hasAll: string; + count: string; +}; + +/** Foreign Key Options */ +export interface ForeignKeyOptions + extends PartialBy { + /** + * The name of the foreign key attribute. + * + * Not to be confused with {@link AttributeOptions#columnName} which controls the name of the foreign key Column. + */ + name?: ForeignKey; + + /** + * Alias of {@link ForeignKeyOptions#name}. + * + * @deprecated + */ + fieldName?: string; +} + +export type NormalizedAssociationOptions = + NormalizeBaseAssociationOptions>; + +/** + * Options provided when associating models + */ +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 + * 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 + */ + as?: string | { singular: string; plural: string }; + + /** + * The configuration of the foreign key Attribute. See {@link Sequelize#define} + * or {@link Model.init} for more information about the syntax. + * + * Using a string is equivalent to passing a {@link ForeignKeyOptions} object + * with the {@link ForeignKeyOptions.name} option set. + */ + foreignKey?: ForeignKey | ForeignKeyOptions; + + /** + * Should ON UPDATE, ON DELETE, and REFERENCES constraints be enabled on the foreign key. + */ + foreignKeyConstraints?: boolean; + + scope?: AssociationScope; +} + +/** + * Options for Association Scope + */ +export interface AssociationScope { + /** + * The name of the column that will be used for the associated scope and it's value + */ + [scopeName: string]: unknown; +} + +/** + * Options provided for many-to-many relationships + */ +export interface MultiAssociationOptions + extends AssociationOptions { + /** + * A key/value set that will be used for association create and find defaults on the target. + * (sqlite not supported for N:M) + */ + scope?: AssociationScope; +} diff --git a/packages/core/src/associations/belongs-to-many.ts b/packages/core/src/associations/belongs-to-many.ts new file mode 100644 index 000000000000..1cc70306ce2e --- /dev/null +++ b/packages/core/src/associations/belongs-to-many.ts @@ -0,0 +1,1485 @@ +import type { AllowIterable, RequiredBy } from '@sequelize/utils'; +import { EMPTY_ARRAY, EMPTY_OBJECT } from '@sequelize/utils'; +import each from 'lodash/each'; +import isEqual from 'lodash/isEqual'; +import omit from 'lodash/omit'; +import upperFirst from 'lodash/upperFirst'; +import type { WhereOptions } from '../abstract-dialect/where-sql-builder-types.js'; +import { AssociationError } from '../errors'; +import { col } from '../expression-builders/col.js'; +import { fn } from '../expression-builders/fn.js'; +import type { + AttributeNames, + Attributes, + BulkCreateOptions, + CreateOptions, + CreationAttributes, + Filterable, + FindAttributeOptions, + FindOptions, + Includeable, + InstanceDestroyOptions, + InstanceUpdateOptions, + Model, + ModelAttributes, + ModelOptions, + ModelStatic, + Transactionable, + UpdateOptions, +} from '../model'; +import { Op } from '../operators'; +import type { Sequelize } from '../sequelize'; +import { isModelStatic, isSameInitialModel } from '../utils/model-utils.js'; +import { removeUndefined } from '../utils/object.js'; +import { camelize, singularize } from '../utils/string.js'; +import type { + Association, + AssociationOptions, + AssociationScope, + ForeignKeyOptions, + MultiAssociationAccessors, + MultiAssociationOptions, + NormalizedAssociationOptions, +} from './base'; +import { MultiAssociation } from './base'; +import type { BelongsToAssociation } from './belongs-to.js'; +import { HasManyAssociation } from './has-many.js'; +import { HasOneAssociation } from './has-one.js'; +import type { AssociationStatic, MaybeForwardedModelStatic } from './helpers'; +import { + AssociationSecret, + defineAssociation, + isThroughOptions, + mixinMethods, + normalizeBaseAssociationOptions, + normalizeForeignKeyOptions, + normalizeInverseAssociation, +} from './helpers'; + +function addInclude(findOptions: FindOptions, include: Includeable) { + if (Array.isArray(findOptions.include)) { + findOptions.include.push(include); + } else if (!findOptions.include) { + findOptions.include = [include]; + } else { + findOptions.include = [findOptions.include, include]; + } +} + +/** + * Many-to-many association with a join/through table. + * See {@link Model.belongsToMany} + * + * When the join table has additional attributes, these can be passed in the options object: + * + * ```js + * UserProject = sequelize.define('user_project', { + * role: DataTypes.STRING + * }); + * User.belongsToMany(Project, { through: UserProject }); + * Project.belongsToMany(User, { through: UserProject }); + * // through is required! + * + * user.addProject(project, { through: { role: 'manager' }}); + * ``` + * + * All methods allow you to pass either a persisted instance, its primary key, or a mixture: + * + * ```js + * 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: + * + * ```js + * p1.UserProjects = { + * started: true + * } + * user.setProjects([p1, p2], { through: { started: false }}) // The default value is false, but p1 overrides that. + * ``` + * + * 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 + * 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()`. + */ +// Note: this class is named BelongsToManyAssociation instead of BelongsToMany to prevent naming conflicts with the BelongsToMany decorator +export class BelongsToManyAssociation< + SourceModel extends Model = Model, + TargetModel extends Model = Model, + ThroughModel extends Model = Model, + SourceKey extends AttributeNames = any, + TargetKey extends AttributeNames = any, +> extends MultiAssociation< + SourceModel, + TargetModel, + /* ForeignKey */ string, + TargetKey, + NormalizedBelongsToManyOptions +> { + readonly accessors: MultiAssociationAccessors; + + get foreignKey(): string { + return this.fromSourceToThrough.foreignKey; + } + + /** + * The name of the Foreign Key attribute, located on the through table, that points to the Target model. + * + * Not to be confused with {@link BelongsToManyAssociation#foreignKey}, which points to the Source model instead. + */ + get otherKey(): string { + return this.pairedWith.foreignKey; + } + + /** + * @deprecated use {@link BelongsToManyAssociation#foreignKey} + */ + get identifier() { + return this.foreignKey; + } + + /** + * The corresponding column name of {@link BelongsToManyAssociation#foreignKey} + */ + get identifierField(): string { + return this.fromThroughToSource.identifierField; + } + + /** + * The corresponding column name of {@link BelongsToManyAssociation#otherKey} + */ + get foreignIdentifierField() { + return this.pairedWith.identifierField; + } + + /** + * The name of the Attribute that the {@link foreignKey} fk (located on the Through Model) will reference on the Source model. + */ + get sourceKey(): SourceKey { + return this.fromThroughToSource.targetKey; + } + + /** + * The name of the Column that the {@link foreignKey} fk (located on the Through Table) will reference on the Source model. + */ + get sourceKeyField(): string { + return this.fromThroughToSource.targetKeyField; + } + + /** + * The name of the Attribute that the {@link otherKey} fk (located on the Through Model) will reference on the Target model. + */ + get targetKey(): TargetKey { + return this.pairedWith.sourceKey; + } + + /** + * The name of the Column that the {@link otherKey} fk (located on the Through Table) will reference on the Target model. + */ + get targetKeyField(): string { + return this.pairedWith.sourceKeyField; + } + + /** + * The corresponding association this entity is paired with. + */ + pairedWith: BelongsToManyAssociation< + TargetModel, + SourceModel, + ThroughModel, + TargetKey, + SourceKey + >; + + // intermediary associations + // these create the actual associations on the model. Remove them would be a breaking change. + readonly fromSourceToThrough: HasManyAssociation; + readonly fromSourceToThroughOne: HasOneAssociation; + get fromThroughToSource(): BelongsToAssociation { + return this.fromSourceToThrough.inverse; + } + + get fromTargetToThrough(): HasManyAssociation { + return this.pairedWith.fromSourceToThrough; + } + + get fromTargetToThroughOne(): HasOneAssociation { + return this.pairedWith.fromSourceToThroughOne; + } + + get fromThroughToTarget(): BelongsToAssociation { + return this.pairedWith.fromThroughToSource; + } + + get through(): NormalizedThroughOptions { + return this.options.through; + } + + get throughModel(): ModelStatic { + return this.through.model; + } + + constructor( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: NormalizedBelongsToManyOptions, + pair?: BelongsToManyAssociation, + parent?: Association, + ) { + super(secret, source, target, options, parent); + + try { + this.pairedWith = + pair ?? + BelongsToManyAssociation.associate< + TargetModel, + SourceModel, + ThroughModel, + TargetKey, + SourceKey + >( + secret, + target, + source, + removeUndefined({ + ...options, + // note: we can't just use '...options.inverse' because we need to set to undefined if the option wasn't set + as: options.inverse?.as, + scope: options.inverse?.scope, + foreignKeyConstraints: options.inverse?.foreignKeyConstraints, + inverse: removeUndefined({ + as: options.as, + scope: options.scope, + foreignKeyConstraints: options.foreignKeyConstraints, + }), + sourceKey: options.targetKey, + targetKey: options.sourceKey, + foreignKey: options.otherKey, + otherKey: options.foreignKey, + throughAssociations: { + toSource: options.throughAssociations.toTarget, + fromSource: options.throughAssociations.fromTarget, + toTarget: options.throughAssociations.toSource, + fromTarget: options.throughAssociations.fromSource, + }, + through: removeUndefined({ + ...options.through, + scope: undefined, + }), + }), + this, + this, + ); + } catch (error) { + throw new AssociationError( + `BelongsToMany associations automatically create the corresponding association on the target model, + but this association failed to create its paired association (BelongsToMany from ${target.name} to ${source.name}). + + This may happen if you try to define the same BelongsToMany association on both sides of the association. + If that is the case, instead of doing this: + A.belongsToMany(B, { as: 'b', through: 'AB' }); + B.belongsToMany(A, { as: 'a', through: 'AB' }); + + Do this: + A.belongsToMany(B, { as: 'b', through: 'AB', inverse: { as: 'a' } }); + `, + { cause: error }, + ); + } + + // we'll need to access their foreign key (through .otherKey) in this constructor. + // this makes sure it's created + this.pairedWith.pairedWith = this; + + const sourceKey = options?.sourceKey || (source.primaryKeyAttribute as TargetKey); + + this.fromSourceToThrough = HasManyAssociation.associate( + AssociationSecret, + this.source, + this.throughModel, + removeUndefined({ + as: + options.throughAssociations.fromSource || + `${this.name.plural}${upperFirst(this.pairedWith.name.plural)}`, + scope: this.through.scope, + foreignKey: { + ...this.options.foreignKey, + allowNull: this.options.foreignKey.allowNull ?? false, + name: + this.options.foreignKey.name || + (this.isSelfAssociation + ? camelize(`${this.pairedWith.name.singular}_${sourceKey}`) + : camelize(`${this.source.options.name.singular}_${sourceKey}`)), + }, + sourceKey: this.options.sourceKey, + foreignKeyConstraints: this.options.foreignKeyConstraints, + hooks: this.options.hooks, + inverse: { + as: options.throughAssociations.toSource || this.pairedWith.name.singular, + }, + }), + this, + ); + + this.fromSourceToThroughOne = HasOneAssociation.associate( + AssociationSecret, + this.source, + this.throughModel, + removeUndefined({ + as: options.throughAssociations.fromSource + ? singularize(options.throughAssociations.fromSource) + : `${this.name.singular}${upperFirst(this.pairedWith.name.singular)}`, + scope: this.through.scope, + // foreignKey: this.options.foreignKey, + foreignKey: { + ...this.options.foreignKey, + allowNull: this.options.foreignKey.allowNull ?? false, + name: + this.options.foreignKey.name || + (this.isSelfAssociation + ? camelize(`${this.pairedWith.name.singular}_${sourceKey}`) + : camelize(`${this.source.options.name.singular}_${sourceKey}`)), + }, + sourceKey: this.options.sourceKey, + foreignKeyConstraints: this.options.foreignKeyConstraints, + hooks: this.options.hooks, + inverse: { + as: options.throughAssociations.toSource + ? singularize(options.throughAssociations.toSource) + : this.pairedWith.name.singular, + }, + }), + this, + ); + + // Get singular and plural names, trying to uppercase the first letter, unless the model forbids it + const plural = upperFirst(this.options.name.plural); + const singular = upperFirst(this.options.name.singular); + + this.accessors = { + get: `get${plural}`, + set: `set${plural}`, + addMultiple: `add${plural}`, + add: `add${singular}`, + create: `create${singular}`, + remove: `remove${singular}`, + removeMultiple: `remove${plural}`, + hasSingle: `has${singular}`, + hasAll: `has${plural}`, + count: `count${plural}`, + }; + + this.#mixin(source.prototype); + + // we are the 'parent' of the belongs-to-many pair + if (pair == null) { + this.#makeFkPairUnique(); + } + } + + #makeFkPairUnique() { + let hasPrimaryKey = false; + + const throughModelDefinition = this.throughModel.modelDefinition; + + // remove any PKs previously defined by sequelize + // but ignore any keys that are part of this association (#5865) + const { rawAttributes: throughRawAttributes } = throughModelDefinition; + + each(throughRawAttributes, (attribute, attributeName) => { + if (!attribute.primaryKey) { + return; + } + + if ([this.foreignKey, this.otherKey].includes(attributeName)) { + return; + } + + if (attribute._autoGenerated) { + delete throughRawAttributes[attributeName]; + + return; + } + + hasPrimaryKey = true; + }); + + if (!hasPrimaryKey) { + if (typeof this.through.unique === 'string') { + throw new TypeError(`BelongsToMany: Option "through.unique" can only be used if the through model's foreign keys are not also the primary keys. +Add your own primary key to the through model, on different attributes than the foreign keys, to be able to use this option.`); + } + + throughRawAttributes[this.foreignKey].primaryKey = true; + throughRawAttributes[this.otherKey].primaryKey = true; + } else if (this.through.unique !== false) { + let uniqueKey; + if (typeof this.through.unique === 'string' && this.through.unique !== '') { + uniqueKey = this.through.unique; + } else { + const keys = [this.foreignKey, this.otherKey].sort(); + uniqueKey = [this.through.model.table.tableName, ...keys, 'unique'].join('_'); + } + + throughRawAttributes[this.foreignKey].unique = [{ name: uniqueKey }]; + throughRawAttributes[this.otherKey].unique = [{ name: uniqueKey }]; + } + + throughModelDefinition.refreshAttributes(); + } + + static associate< + S extends Model, + T extends Model, + ThroughModel extends Model, + SourceKey extends AttributeNames, + TargetKey extends AttributeNames, + >( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: BelongsToManyOptions, + pair?: BelongsToManyAssociation, + parent?: Association, + ): BelongsToManyAssociation { + return defineAssociation< + BelongsToManyAssociation, + BelongsToManyOptions, + NormalizedBelongsToManyOptions + >( + BelongsToManyAssociation, + source, + target, + options, + parent, + normalizeBelongsToManyOptions, + newOptions => { + // self-associations must always set their 'as' parameter + if ( + isSameInitialModel(source, target) && + // use 'options' because this will always be set in 'newOptions' + (!options.as || !newOptions.inverse?.as || options.as === newOptions.inverse.as) + ) { + throw new AssociationError( + 'Both options "as" and "inverse.as" must be defined for belongsToMany self-associations, and their value must be different.', + ); + } + + return new BelongsToManyAssociation(secret, source, target, newOptions, pair, parent); + }, + ); + } + + #mixin(modelPrototype: Model) { + mixinMethods( + this, + modelPrototype, + [ + 'get', + 'count', + 'hasSingle', + 'hasAll', + 'set', + 'add', + 'addMultiple', + 'remove', + 'removeMultiple', + 'create', + ], + { + hasSingle: 'has', + hasAll: 'has', + addMultiple: 'add', + removeMultiple: 'remove', + }, + ); + } + + /** + * Get everything currently associated with this, using an optional where clause. + * + * See {@link Model} for a full explanation of options + * + * @param instance instance + * @param options find options + */ + async get( + instance: SourceModel, + options?: BelongsToManyGetAssociationsMixinOptions, + ): Promise { + const through = this.through; + + const findOptions: FindOptions> = { + ...options, + // @ts-expect-error -- TODO: current WhereOptions typings do not allow having 'WhereOptions' inside another 'WhereOptions' + where: { + [Op.and]: [options?.where, this.scope], + }, + }; + + let throughWhere = { + [this.foreignKey]: instance.get(this.sourceKey), + }; + + if (through.scope) { + Object.assign(throughWhere, through.scope); + } + + // If a user pass a where on the options through options, make an "and" with the current throughWhere + if (options?.through?.where) { + throughWhere = { + [Op.and]: [throughWhere, options.through.where], + }; + } + + addInclude( + findOptions, + removeUndefined({ + association: this.fromTargetToThroughOne, + attributes: options?.joinTableAttributes, + required: true, + paranoid: options?.through?.paranoid ?? true, + where: throughWhere, + }), + ); + + let model = this.target; + if (options?.scope != null) { + if (!options.scope) { + model = model.withoutScope(); + } else if (options.scope !== true) { + // 'true' means default scope. Which is the same as not doing anything. + model = model.withScope(options.scope); + } + } + + if (options?.schema) { + model = model.withSchema({ + schema: options.schema, + schemaDelimiter: options.schemaDelimiter, + }); + } + + return model.findAll(findOptions); + } + + /** + * Count everything currently associated with this, using an optional where clause. + * + * @param instance instance + * @param options find options + */ + async count( + instance: SourceModel, + options?: BelongsToManyCountAssociationsMixinOptions, + ): Promise { + const getOptions: BelongsToManyGetAssociationsMixinOptions = { + ...options, + attributes: [[fn('COUNT', col([this.target.name, this.targetKeyField].join('.'))), 'count']], + joinTableAttributes: [], + raw: true, + plain: true, + }; + + const result = await this.get(instance, getOptions); + + // @ts-expect-error -- this.get() isn't designed to expect returning a raw output. + return Number.parseInt(result.count, 10); + } + + /** + * Check if one or more instance(s) are associated with this. If a list of instances is passed, the function returns true if _all_ instances are associated + * + * @param sourceInstance source instance to check for an association with + * @param targetInstancesOrPks Can be an array of instances or their primary keys + * @param options Options passed to getAssociations + */ + async has( + sourceInstance: SourceModel, + targetInstancesOrPks: AllowIterable>, + options?: BelongsToManyHasAssociationMixinOptions, + ): Promise { + const targets = this.toInstanceOrPkArray(targetInstancesOrPks); + + const targetPrimaryKeys: Array = targets.map(instance => { + if (instance instanceof this.target) { + // TODO: remove eslint-disable once we drop support for < 5.2 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- TS 5.2 works, but < 5.2 does not + // @ts-ignore + return instance.get(this.targetKey); + } + + return instance as TargetModel[TargetKey]; + }); + + const associatedObjects: TargetModel[] = await this.get(sourceInstance, { + ...options, + raw: true, + scope: false, + attributes: [this.targetKey], + joinTableAttributes: [], + // @ts-expect-error -- TODO: current WhereOptions typings do not allow having 'WhereOptions' inside another 'WhereOptions' + where: { + [Op.and]: [{ [this.targetKey]: { [Op.in]: targetPrimaryKeys } }, options?.where], + }, + }); + + return targetPrimaryKeys.every(pk => { + return associatedObjects.some(instance => { + // instance[x] instead of instance.get() because the query output is 'raw' + // isEqual is used here because the PK can be a non-primitive value, such as a Buffer + return isEqual(instance[this.targetKey], pk); + }); + }); + } + + /** + * Set the associated models by passing an array of instances or their primary keys. + * Everything that it not in the passed array will be un-associated. + * + * @param sourceInstance source instance to associate new instances with + * @param newInstancesOrPrimaryKeys A single instance or primary key, or a mixed array of persisted instances or primary keys + * @param options Options passed to `through.findAll`, `bulkCreate`, `update` and `destroy` + */ + async set( + sourceInstance: SourceModel, + newInstancesOrPrimaryKeys: AllowIterable>, + options: BelongsToManySetAssociationsMixinOptions = {}, + ): Promise { + const sourceKey = this.sourceKey; + const targetKey = this.targetKey; + const foreignKey = this.foreignKey; + const otherKey = this.otherKey; + + const newInstances = this.toInstanceArray(newInstancesOrPrimaryKeys); + + const where: WhereOptions = { + [foreignKey]: sourceInstance.get(sourceKey), + ...this.through.scope, + }; + + // @ts-expect-error -- the findAll call is raw, no model here + const currentThroughRows: ThroughModel[] = await this.through.model.findAll({ + ...options, + where, + raw: true, + // force this option to be false, in case the user enabled + rejectOnEmpty: false, + include: this.scope + ? [ + { + association: this.fromThroughToTarget, + where: this.scope, + required: true, + }, + ] + : EMPTY_ARRAY, + }); + + const obsoleteTargets: Array> = []; + + // find all obsolete targets + for (const currentRow of currentThroughRows) { + const newTarget = newInstances.find(obj => { + // @ts-expect-error -- the findAll call is raw, no model here + return currentRow[otherKey] === obj.get(targetKey); + }); + + if (!newTarget) { + // @ts-expect-error -- the findAll call is raw, no model here + obsoleteTargets.push(currentRow[this.otherKey]); + } + } + + const promises: Array> = []; + if (obsoleteTargets.length > 0) { + promises.push(this.remove(sourceInstance, obsoleteTargets, options)); + } + + if (newInstances.length > 0) { + promises.push( + this.#updateAssociations(sourceInstance, currentThroughRows, newInstances, options), + ); + } + + await Promise.all(promises); + } + + /** + * Associate one or several rows with source instance. It will not un-associate any already associated instance + * that may be missing from `newInstances`. + * + * @param sourceInstance source instance to associate new instances with + * @param newInstancesOrPrimaryKeys A single instance or primary key, or a mixed array of persisted instances or primary keys + * @param options Options passed to `through.findAll`, `bulkCreate` and `update` + */ + async add( + sourceInstance: SourceModel, + newInstancesOrPrimaryKeys: AllowIterable>, + options?: BelongsToManyAddAssociationsMixinOptions, + ): Promise { + const newInstances = this.toInstanceArray(newInstancesOrPrimaryKeys); + if (newInstances.length === 0) { + return; + } + + const where: WhereOptions = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + [this.otherKey]: newInstances.map(newInstance => newInstance.get(this.targetKey)), + ...this.through.scope, + }; + + let currentRows: readonly any[] = EMPTY_ARRAY; + if (this.through?.unique ?? true) { + // @ts-expect-error -- the findAll call is raw, no model here + currentRows = await this.through.model.findAll({ + ...options, + raw: true, + where, + // force this option to be false, in case the user enabled + rejectOnEmpty: false, + }); + } + + await this.#updateAssociations(sourceInstance, currentRows, newInstances, options); + } + + /** + * Adds new target instances that were not already present in the through table. + * Updates the through table row of the instances that already were present. + * + * @param sourceInstance + * @param currentThroughRows + * @param newTargets + * @param options + * @private + */ + async #updateAssociations( + sourceInstance: SourceModel, + currentThroughRows: readonly ThroughModel[], + newTargets: readonly TargetModel[], + options?: { through?: JoinTableAttributes } & BulkCreateOptions> & + Omit>, 'where'>, + ) { + const sourceKey = this.sourceKey; + const targetKey = this.targetKey; + const foreignKey = this.foreignKey; + const otherKey = this.otherKey; + + const defaultAttributes = options?.through || EMPTY_OBJECT; + + const promises: Array> = []; + const unassociatedTargets: TargetModel[] = []; + // the 'through' table of these targets has changed + const changedTargets: TargetModel[] = []; + for (const newInstance of newTargets) { + const existingThroughRow = currentThroughRows.find(throughRow => { + // @ts-expect-error -- throughRow[] instead of .get because throughRows are loaded using 'raw' + return throughRow[otherKey] === newInstance.get(targetKey); + }); + + if (!existingThroughRow) { + unassociatedTargets.push(newInstance); + + continue; + } + + // @ts-expect-error -- gets the content of the "through" table for this association that is set on the model + const throughAttributes = newInstance[this.through.model.name]; + const attributes = { ...defaultAttributes, ...throughAttributes }; + + if ( + Object.keys(attributes).some(attribute => { + // @ts-expect-error -- existingThroughRow is raw + return attributes[attribute] !== existingThroughRow[attribute]; + }) + ) { + changedTargets.push(newInstance); + } + } + + if (unassociatedTargets.length > 0) { + const bulk = unassociatedTargets.map(unassociatedTarget => { + // @ts-expect-error -- gets the content of the "through" table for this association that is set on the model + const throughAttributes = unassociatedTarget[this.through.model.name]; + const attributes = { ...defaultAttributes, ...throughAttributes }; + + attributes[foreignKey] = sourceInstance.get(sourceKey); + attributes[otherKey] = unassociatedTarget.get(targetKey); + + Object.assign(attributes, this.through.scope); + + return attributes; + }); + + promises.push(this.through.model.bulkCreate(bulk, { validate: true, ...options })); + } + + for (const changedTarget of changedTargets) { + // @ts-expect-error -- gets the content of the "through" table for this association that is set on the model + let throughAttributes = changedTarget[this.through.model.name]; + const attributes = { ...defaultAttributes, ...throughAttributes }; + // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) + if (throughAttributes instanceof this.through.model) { + throughAttributes = {}; + } + + const where: WhereOptions = { + [foreignKey]: sourceInstance.get(sourceKey), + [otherKey]: changedTarget.get(targetKey), + }; + + promises.push( + this.through.model.update(attributes, { + ...options, + where, + }), + ); + } + + await Promise.all(promises); + } + + /** + * Un-associate one or more instance(s). + * + * @param sourceInstance instance to un associate instances with + * @param targetInstanceOrPks Can be an Instance or its primary key, or a mixed array of instances and primary keys + * @param options Options passed to `through.destroy` + */ + async remove( + sourceInstance: SourceModel, + targetInstanceOrPks: AllowIterable>, + options?: BelongsToManyRemoveAssociationMixinOptions, + ): Promise { + const targetInstance = this.toInstanceArray(targetInstanceOrPks); + if (targetInstance.length === 0) { + return; + } + + const where: WhereOptions = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + [this.otherKey]: targetInstance.map(newInstance => newInstance.get(this.targetKey)), + ...this.through.scope, + }; + + await this.through.model.destroy({ ...options, where }); + } + + /** + * Create a new instance of the associated model and associate it with this. + * + * @param sourceInstance source instance + * @param values values for target model + * @param options Options passed to create and add + */ + async create( + sourceInstance: SourceModel, + // @ts-expect-error -- {} is not always assignable to 'values', but Target.create will enforce this, not us. + values: CreationAttributes = {}, + options: + | BelongsToManyCreateAssociationMixinOptions + | BelongsToManyCreateAssociationMixinOptions['fields'] = {}, + ): Promise { + if (Array.isArray(options)) { + options = { + fields: options, + }; + } + + if (this.scope) { + Object.assign(values, this.scope); + if (options.fields) { + options.fields = [...options.fields, ...Object.keys(this.scope)]; + } + } + + // Create the related model instance + const newAssociatedObject = await this.target.create(values, options); + + await this.add(sourceInstance, newAssociatedObject, omit(options, ['fields'])); + + return newAssociatedObject; + } +} + +// workaround https://github.com/evanw/esbuild/issues/1260 +Object.defineProperty(BelongsToManyAssociation, 'name', { + value: 'BelongsToMany', +}); + +function normalizeThroughOptions( + source: ModelStatic, + target: ModelStatic, + through: ThroughOptions, + sequelize: Sequelize, +): NormalizedThroughOptions { + const timestamps = through.timestamps ?? sequelize.options.define?.timestamps; + + let model: ModelStatic; + + if (!through || (typeof through.model !== 'string' && typeof through.model !== 'function')) { + throw new AssociationError( + `${source.name}.belongsToMany(${target.name}) requires a through model, set the "through", or "through.model" options to either a string or a model`, + ); + } + + if (isModelStatic(through.model)) { + // model class provided directly + model = through.model; + } else if (typeof through.model === 'function') { + // model class provided as a forward reference + model = through.model(sequelize); + } else if (sequelize.models.hasByName(through.model)) { + // model name provided: get if exists, create if not + model = sequelize.models.getOrThrow(through.model); + } else { + const sourceTable = source.table; + + model = sequelize.define( + through.model, + {} as ModelAttributes, + removeUndefined({ + tableName: through.model, + indexes: [], // we don't want indexes here (as referenced in #2416) + paranoid: through.paranoid || false, // Default to non-paranoid join (referenced in #11991) + validate: {}, // Don't propagate model-level validations + timestamps: through.timestamps, + schema: sourceTable.schema, + schemaDelimiter: sourceTable.delimiter, + }), + ); + } + + return removeUndefined({ + ...through, + timestamps, + model, + }); +} + +function normalizeBelongsToManyOptions< + SourceKey extends string, + TargetKey extends string, + ThroughModel extends Model, +>( + type: AssociationStatic, + options: BelongsToManyOptions, + source: ModelStatic, + target: ModelStatic, +): NormalizedBelongsToManyOptions { + if ('timestamps' in options) { + throw new TypeError( + 'The "timestamps" option in belongsToMany has been renamed to through.timestamps', + ); + } + + if ('uniqueKey' in options) { + throw new TypeError( + 'The "uniqueKey" option in belongsToMany has been renamed to through.unique', + ); + } + + const sequelize = target.sequelize; + + return normalizeBaseAssociationOptions( + type, + { + ...options, + inverse: normalizeInverseAssociation(options.inverse), + otherKey: normalizeForeignKeyOptions(options.otherKey), + through: removeUndefined( + isThroughOptions(options.through) + ? normalizeThroughOptions(source, target, options.through, sequelize) + : normalizeThroughOptions(source, target, { model: options.through }, sequelize), + ), + throughAssociations: options?.throughAssociations + ? removeUndefined(options.throughAssociations) + : EMPTY_OBJECT, + }, + source, + target, + ); +} + +/** + * Used for the through table in n:m associations. + * + * Used in {@link BelongsToManyOptions.through} + */ +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: MaybeForwardedModelStatic | string; + + /** + * See {@link ModelOptions.timestamps} + */ + timestamps?: ModelOptions['timestamps']; + + /** + * See {@link ModelOptions.paranoid} + */ + paranoid?: ModelOptions['paranoid']; + + /** + * 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) + */ + scope?: AssociationScope; + + /** + * If true a unique constraint will be added on the foreign key pair. + * If set to a string, the generated unique key will use the string as its name. + * If set to false, no unique constraint will be added. + * Useful if you want to turn this off and create your own unique constraint when using scopes. + * + * This option only works if the model already has a Primary Key, + * as the unique constraint will not be added if the foreign keys are already part of the composite primary key. + * + * @default true + */ + unique?: boolean | string; +} + +/** + * Attributes for the join table + */ +export interface JoinTableAttributes { + [attribute: string]: unknown; +} + +type NormalizedBelongsToManyOptions< + SourceKey extends string, + TargetKey extends string, + ThroughModel extends Model, +> = Omit< + RequiredBy, 'throughAssociations'>, + 'through' | 'as' | 'hooks' | 'foreignKey' | 'inverse' +> & { + through: NormalizedThroughOptions; + inverse?: Exclude['inverse'], string>; +} & Pick, 'as' | 'name' | 'hooks' | 'foreignKey'>; + +type NormalizedThroughOptions = Omit< + ThroughOptions, + 'model' +> & { + model: ModelStatic; +}; + +/** + * Options provided when associating models with belongsToMany relationship. + * + * Used by {@link Model.belongsToMany}. + */ +export interface BelongsToManyOptions< + SourceKey extends string = string, + TargetKey extends string = string, + ThroughModel extends Model = Model, +> extends MultiAssociationOptions> { + /** + * The name of the inverse association, or an object for further association setup. + */ + inverse?: + | string + | undefined + | { + as?: AssociationOptions['as']; + scope?: MultiAssociationOptions['scope']; + foreignKeyConstraints?: AssociationOptions['foreignKeyConstraints']; + }; + + // this is also present in AssociationOptions, but they have different JSDoc, keep both! + /** + * Should "ON UPDATE", "ON DELETE" and "REFERENCES" constraints be enabled on the foreign key? + * + * This only affects the foreign key that points to the source model. + * to control the one that points to the target model, set the "foreignKeyConstraints" option in {@link BelongsToManyOptions.inverse}. + */ + foreignKeyConstraints?: boolean; + + /** + * 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: MaybeForwardedModelStatic | string | ThroughOptions; + + /** + * Configures the name of the associations that will be defined between the source model and the through model, + * as well as between the target model and the through model. + */ + throughAssociations?: { + /** + * The name of the HasMany association going from the Source model to the Through model. + * + * By default, the association will be the name of the BelongsToMany association + * + the name of the inverse BelongsToMany association. + */ + fromSource?: string | undefined; + + /** + * The name of the BelongsTo association going from the Through model to the Source model. + * + * By default, the association name will be the name of the inverse BelongsToMany association, singularized. + */ + toSource?: string | undefined; + + /** + * The name of the HasMany association going from the Target model to the Through model. + * + * By default, the association will be the name of the Inverse BelongsToMany association + * + the name of the BelongsToMany association. + */ + fromTarget?: string | undefined; + + /** + * The name of the BelongsTo association going from the Through model to the Target model. + * + * By default, the association name will be the name of the parent BelongsToMany association, singularized. + */ + toTarget?: string | undefined; + }; + + /** + * The name of the foreign key attribute in the through model (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 colum. Defaults to the name of target + primary key of + * target + */ + otherKey?: AttributeNames | ForeignKeyOptions>; + + /** + * The name of the attribute to use as the key for the association in the source table. + * Defaults to the primary key attribute of the source model + */ + sourceKey?: SourceKey; + + /** + * The name of the attribute to use as the key for the association in the target table. + * Defaults to the primary key attribute of the target model + */ + targetKey?: TargetKey; +} + +/** + * The options for the getAssociations mixin of the belongsToMany association. + * + * @see BelongsToManyGetAssociationsMixin + */ +export interface BelongsToManyGetAssociationsMixinOptions + extends FindOptions> { + /** + * A list of the attributes from the join table that you want to select. + */ + joinTableAttributes?: FindAttributeOptions>; + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | boolean; + + /** + * Apply a schema on the related model + */ + schema?: string; + schemaDelimiter?: string; + + through?: { + where?: WhereOptions; + paranoid?: boolean; + }; +} + +/** + * The getAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare getRoles: BelongsToManyGetAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyGetAssociationsMixin = ( + options?: BelongsToManyGetAssociationsMixinOptions, +) => Promise; + +/** + * The options for the setAssociations mixin of the belongsToMany association. + * + * @see BelongsToManySetAssociationsMixin + */ +export interface BelongsToManySetAssociationsMixinOptions + extends FindOptions>, + BulkCreateOptions>, + InstanceUpdateOptions>, + InstanceDestroyOptions { + /** + * Additional attributes for the join table. + */ + through?: JoinTableAttributes; +} + +/** + * The setAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare setRoles: BelongsToManySetAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManySetAssociationsMixin = ( + newAssociations?: Iterable | null, + options?: BelongsToManySetAssociationsMixinOptions, +) => Promise; + +/** + * The options for the addAssociations mixin of the belongsToMany association. + * + * @see BelongsToManyAddAssociationsMixin + */ +export interface BelongsToManyAddAssociationsMixinOptions + extends FindOptions>, + BulkCreateOptions>, + InstanceUpdateOptions>, + InstanceDestroyOptions { + through?: JoinTableAttributes; +} + +/** + * The addAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare addRoles: BelongsToManyAddAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyAddAssociationsMixin = ( + newAssociations?: Iterable, + options?: BelongsToManyAddAssociationsMixinOptions, +) => Promise; + +/** + * The options for the addAssociation mixin of the belongsToMany association. + * + * @see BelongsToManyAddAssociationMixin + */ +export interface BelongsToManyAddAssociationMixinOptions + extends FindOptions>, + BulkCreateOptions>, + InstanceUpdateOptions>, + InstanceDestroyOptions { + through?: JoinTableAttributes; +} + +/** + * The addAssociation mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare addRole: BelongsToManyAddAssociationMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyAddAssociationMixin = ( + newAssociation?: T | TModelPrimaryKey, + options?: BelongsToManyAddAssociationMixinOptions, +) => Promise; + +/** + * The options for the createAssociation mixin of the belongsToMany association. + * + * @see BelongsToManyCreateAssociationMixin + */ +export interface BelongsToManyCreateAssociationMixinOptions + extends CreateOptions> { + through?: JoinTableAttributes; +} +/** + * The createAssociation mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare createRole: BelongsToManyCreateAssociationMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyCreateAssociationMixin = ( + values?: CreationAttributes, + options?: BelongsToManyCreateAssociationMixinOptions, +) => Promise; + +/** + * The options for the removeAssociation mixin of the belongsToMany association. + * + * @see BelongsToManyRemoveAssociationMixin + */ +export interface BelongsToManyRemoveAssociationMixinOptions extends InstanceDestroyOptions {} + +/** + * The removeAssociation mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare removeRole: BelongsToManyRemoveAssociationMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyRemoveAssociationMixin = ( + oldAssociated?: TModel | TModelPrimaryKey, + options?: BelongsToManyRemoveAssociationMixinOptions, +) => Promise; + +/** + * The options for the removeAssociations mixin of the belongsToMany association. + * + * @see BelongsToManyRemoveAssociationsMixin + */ +export interface BelongsToManyRemoveAssociationsMixinOptions + extends InstanceDestroyOptions, + InstanceDestroyOptions {} + +/** + * The removeAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare removeRoles: BelongsToManyRemoveAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyRemoveAssociationsMixin = ( + associationsToRemove?: Iterable, + options?: BelongsToManyRemoveAssociationsMixinOptions, +) => Promise; + +/** + * The options for the hasAssociation mixin of the belongsToMany association. + * + * @see BelongsToManyHasAssociationMixin + */ +export interface BelongsToManyHasAssociationMixinOptions + extends BelongsToManyGetAssociationsMixinOptions {} + +/** + * The hasAssociation mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare hasRole: BelongsToManyHasAssociationMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyHasAssociationMixin = ( + target: TModel | TModelPrimaryKey, + options?: BelongsToManyHasAssociationMixinOptions, +) => Promise; + +/** + * The options for the hasAssociations mixin of the belongsToMany association. + * + * @see BelongsToManyHasAssociationsMixin + */ +export interface BelongsToManyHasAssociationsMixinOptions + extends BelongsToManyGetAssociationsMixinOptions {} + +/** + * The removeAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare hasRoles: BelongsToManyHasAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyHasAssociationsMixin = ( + targets: Iterable, + options?: BelongsToManyHasAssociationsMixinOptions, +) => Promise; + +/** + * The options for the countAssociations mixin of the belongsToMany association. + * + * @see BelongsToManyCountAssociationsMixin + */ +export interface BelongsToManyCountAssociationsMixinOptions + extends Transactionable, + Filterable> { + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | boolean; +} + +/** + * The countAssociations mixin applied to models with belongsToMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare countRoles: Sequelize.BelongsToManyCountAssociationsMixin; + * } + * + * User.belongsToMany(Role, { through: UserRole }); + * ``` + * + * @see Model.belongsToMany + */ +export type BelongsToManyCountAssociationsMixin = ( + options?: BelongsToManyCountAssociationsMixinOptions, +) => Promise; diff --git a/packages/core/src/associations/belongs-to.ts b/packages/core/src/associations/belongs-to.ts new file mode 100644 index 000000000000..53fff6f75e60 --- /dev/null +++ b/packages/core/src/associations/belongs-to.ts @@ -0,0 +1,570 @@ +import isEqual from 'lodash/isEqual'; +import isObject from 'lodash/isObject.js'; +import upperFirst from 'lodash/upperFirst'; +import assert from 'node:assert'; +import { cloneDataType } from '../abstract-dialect/data-types-utils.js'; +import { AssociationError } from '../errors/index.js'; +import type { + AttributeNames, + AttributeReferencesOptions, + Attributes, + CreateOptions, + CreationAttributes, + FindOptions, + Model, + ModelStatic, + SaveOptions, +} from '../model'; +import { normalizeReference } from '../model-definition.js'; +import { Op } from '../operators'; +import { getColumnName } from '../utils/format.js'; +import { isSameInitialModel } from '../utils/model-utils.js'; +import { cloneDeep, removeUndefined } from '../utils/object.js'; +import { camelize } from '../utils/string.js'; +import type { AssociationOptions, SingleAssociationAccessors } from './base'; +import { Association } from './base'; +import { HasManyAssociation } from './has-many.js'; +import { HasOneAssociation } from './has-one.js'; +import type { NormalizeBaseAssociationOptions } from './helpers'; +import { defineAssociation, mixinMethods, normalizeBaseAssociationOptions } from './helpers'; + +/** + * One-to-one association + * See {@link Model.belongsTo} + * + * This is almost the same as {@link HasOneAssociation}, but the foreign key will be defined on the source model. + * + * In the API reference below, add the name of the association to the method, e.g. for `User.belongsTo(Project)` the getter will be `user.getProject()`. + * + * @typeParam S The model on which {@link Model.belongsTo} has been called, on which the association methods, as well as the foreign key attribute, will be added. + * @typeParam T The model passed to {@link Model.belongsTo}. + * @typeParam SourceKey The name of the Foreign Key attribute on the Source model. + * @typeParam TargetKey The name of the attribute that the foreign key in the source model will reference, typically the Primary Key. + */ +// Note: this class is named BelongsToAssociation instead of BelongsTo to prevent naming conflicts with the BelongsTo decorator +export class BelongsToAssociation< + S extends Model = Model, + T extends Model = Model, + SourceKey extends AttributeNames = any, + TargetKey extends AttributeNames = any, +> extends Association> { + readonly accessors: SingleAssociationAccessors; + + /** + * The attribute name of the identifier + * + * @deprecated use {@link foreignKey} instead + */ + get identifier(): string { + return this.foreignKey; + } + + foreignKey: SourceKey; + + /** + * The column name of the foreign key + */ + // TODO: rename to foreignKeyColumnName + identifierField: string; + + /** + * The name of the attribute the foreign key points to. + * In belongsTo, this key is on the Target Model, instead of the Source Model (unlike {@link HasOneAssociation.sourceKey}). + * The {@link Association.foreignKey} is on the Source Model. + */ + targetKey: TargetKey; + + /** + * The column name of the target key + */ + // TODO: rename to targetKeyColumnName + readonly targetKeyField: string; + + readonly targetKeyIsPrimary: boolean; + + /** + * @deprecated use {@link BelongsToAssociation.targetKey} + */ + get targetIdentifier(): string { + return this.targetKey; + } + + inverse: Association | undefined; + + constructor( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: NormalizedBelongsToOptions, + parent?: Association, + ) { + // TODO: throw is source model has a composite primary key. + const targetKey = options?.targetKey || (target.primaryKeyAttribute as TargetKey); + + const targetAttributes = target.modelDefinition.attributes; + + if (!targetAttributes.has(targetKey)) { + throw new Error( + `Unknown attribute "${options.targetKey}" passed as targetKey, define this attribute on model "${target.name}" first`, + ); + } + + if ('keyType' in options) { + throw new TypeError( + 'Option "keyType" has been removed from the BelongsTo\'s options. Set "foreignKey.type" instead.', + ); + } + + super(secret, source, target, options, parent); + + this.targetKey = targetKey; + + // For Db2 server, a reference column of a FOREIGN KEY must be unique + // else, server throws SQL0573N error. Hence, setting it here explicitly + // for non primary columns. + if ( + target.sequelize.dialect.name === 'db2' && + targetAttributes.get(this.targetKey)!.primaryKey !== true + ) { + // TODO: throw instead + this.target.modelDefinition.rawAttributes[this.targetKey].unique = true; + } + + let foreignKey: string | undefined; + let foreignKeyAttributeOptions; + if (isObject(this.options?.foreignKey)) { + // lodash has poor typings + assert(typeof this.options?.foreignKey === 'object'); + + foreignKeyAttributeOptions = this.options.foreignKey; + foreignKey = this.options.foreignKey.name || this.options.foreignKey.fieldName; + } else if (this.options?.foreignKey) { + foreignKey = this.options.foreignKey; + } + + if (!foreignKey) { + foreignKey = this.inferForeignKey(); + } + + this.foreignKey = foreignKey as SourceKey; + + this.targetKeyField = getColumnName(targetAttributes.getOrThrow(this.targetKey)); + this.targetKeyIsPrimary = this.targetKey === this.target.primaryKeyAttribute; + + const targetAttribute = targetAttributes.get(this.targetKey)!; + + const existingForeignKey = source.modelDefinition.rawAttributes[this.foreignKey]; + const newForeignKeyAttribute = removeUndefined({ + type: cloneDataType(targetAttribute.type), + ...foreignKeyAttributeOptions, + allowNull: existingForeignKey?.allowNull ?? foreignKeyAttributeOptions?.allowNull, + }); + + // FK constraints are opt-in: users must either set `foreignKeyConstraints` + // on the association, or request an `onDelete` or `onUpdate` behavior + if (options.foreignKeyConstraints !== false) { + const existingReference = existingForeignKey?.references + ? ((normalizeReference(existingForeignKey.references) ?? + existingForeignKey.references) as AttributeReferencesOptions) + : undefined; + + const queryGenerator = this.source.sequelize.queryGenerator; + + const existingReferencedTable = existingReference?.table + ? queryGenerator.extractTableDetails(existingReference.table) + : undefined; + + const newReferencedTable = queryGenerator.extractTableDetails(this.target); + + const newReference: AttributeReferencesOptions = {}; + if (existingReferencedTable) { + if (!isEqual(existingReferencedTable, newReferencedTable)) { + throw new Error( + `Foreign key ${this.foreignKey} on ${this.source.name} already references ${queryGenerator.quoteTable(existingReferencedTable)}, but this association needs to make it reference ${queryGenerator.quoteTable(newReferencedTable)} instead.`, + ); + } + } else { + newReference.table = newReferencedTable; + } + + if (existingReference?.key && existingReference.key !== this.targetKeyField) { + throw new Error( + `Foreign key ${this.foreignKey} on ${this.source.name} already references column ${existingReference.key}, but this association needs to make it reference ${this.targetKeyField} instead.`, + ); + } + + newReference.key = this.targetKeyField; + + newForeignKeyAttribute.references = newReference; + newForeignKeyAttribute.onDelete ??= + newForeignKeyAttribute.allowNull !== false ? 'SET NULL' : 'CASCADE'; + newForeignKeyAttribute.onUpdate ??= newForeignKeyAttribute.onUpdate ?? 'CASCADE'; + } + + this.source.mergeAttributesDefault({ + [this.foreignKey]: newForeignKeyAttribute, + }); + + this.identifierField = getColumnName(this.source.getAttributes()[this.foreignKey]); + + // Get singular name, trying to uppercase the first letter, unless the model forbids it + const singular = upperFirst(this.options.name.singular); + + this.accessors = { + get: `get${singular}`, + set: `set${singular}`, + create: `create${singular}`, + }; + + this.#mixin(source.prototype); + + if (options.inverse) { + const passDown = removeUndefined({ + ...options, + as: options.inverse.as, + scope: options.inverse?.scope, + sourceKey: options.targetKey, + inverse: undefined, + }); + + delete passDown.targetKey; + + switch (options.inverse.type) { + case 'hasMany': + HasManyAssociation.associate(secret, target, source, passDown, this, this); + break; + + case 'hasOne': + HasOneAssociation.associate(secret, target, source, passDown, this, this); + break; + + default: + throw new Error( + `Invalid option received for "inverse.type": ${options.inverse.type} is not recognised. Expected "hasMany" or "hasOne"`, + ); + } + } + } + + static associate< + S extends Model, + T extends Model, + SourceKey extends AttributeNames, + TargetKey extends AttributeNames, + >( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: BelongsToOptions = {}, + parent?: Association, + ): BelongsToAssociation { + return defineAssociation< + BelongsToAssociation, + BelongsToOptions, + NormalizedBelongsToOptions + >( + BelongsToAssociation, + source, + target, + options, + parent, + normalizeBaseAssociationOptions, + normalizedOptions => { + // self-associations must always set their 'as' parameter + if ( + isSameInitialModel(source, target) && + options.inverse && + // use 'options' because this will always be set in 'newOptions' + (!options.as || !options.inverse.as || options.as === options.inverse.as) + ) { + throw new AssociationError( + `Both options "as" and "inverse.as" must be defined for belongsTo self-associations, and their value must be different, if you specify the 'inverse' option.`, + ); + } + + return new BelongsToAssociation(secret, source, target, normalizedOptions, parent); + }, + ); + } + + #mixin(modelPrototype: Model): void { + mixinMethods(this, modelPrototype, ['get', 'set', 'create']); + } + + protected inferForeignKey(): string { + const associationName = this.options.name.singular; + if (!associationName) { + throw new Error('Sanity check: Could not guess the name of the association'); + } + + return camelize(`${associationName}_${this.targetKey}`); + } + + /** + * Get the associated instance. + * + * See {@link BelongsToGetAssociationMixinOptions} for a full explanation of options. + * This method is mixed-in the source model prototype. See {@link BelongsToGetAssociationMixin}. + * + * @param instances source instances + * @param options find options + */ + async get(instances: S, options?: BelongsToGetAssociationMixinOptions): Promise; + async get( + instances: S[], + options?: BelongsToGetAssociationMixinOptions, + ): Promise>; + async get( + instances: S | S[], + options?: BelongsToGetAssociationMixinOptions, + ): Promise | T | null> { + options = cloneDeep(options) ?? {}; + + let Target = this.target; + if (options.scope != null) { + if (!options.scope) { + Target = Target.withoutScope(); + } else if (options.scope !== true) { + // 'true' means default scope. Which is the same as not doing anything. + Target = Target.withScope(options.scope); + } + } + + if (options.schema != null) { + Target = Target.withSchema({ + schema: options.schema, + schemaDelimiter: options.schemaDelimiter, + }); + } + + let isManyMode = true; + if (!Array.isArray(instances)) { + isManyMode = false; + instances = [instances]; + } + + // TODO: the scope is ignored + const where = Object.create(null); + + if (instances.length > 1) { + where[this.targetKey] = { + [Op.in]: instances + .map(instance => instance.get(this.foreignKey)) + // only fetch entities that actually have a foreign key set + .filter(foreignKey => foreignKey != null), + }; + } else { + const foreignKeyValue = instances[0].get(this.foreignKey); + + if (foreignKeyValue == null) { + return null; + } + + where[this.targetKey] = foreignKeyValue; + options.limit = null; + } + + options.where = options.where ? { [Op.and]: [where, options.where] } : where; + + if (isManyMode) { + const results = await Target.findAll(options); + const result = new Map(); + + for (const instance of results) { + result.set(instance.get(this.targetKey, { raw: true }), instance); + } + + return result; + } + + return Target.findOne(options); + } + + /** + * Set the associated model. + * + * @param sourceInstance the source instance + * @param associatedInstance An persisted instance or the primary key of an instance to associate with this. Pass `null` to remove the association. + * @param options options passed to `this.save` + */ + async set( + sourceInstance: S, + associatedInstance: T | T[TargetKey] | null, + options: BelongsToSetAssociationMixinOptions = {}, + ): Promise { + let value = associatedInstance; + + if (associatedInstance != null && associatedInstance instanceof this.target) { + value = associatedInstance[this.targetKey]; + } + + sourceInstance.set(this.foreignKey, value); + + if (options.save === false) { + return; + } + + // passes the changed field to save, so only that field get updated. + await sourceInstance.save({ + fields: [this.foreignKey], + association: true, + ...options, + }); + } + + /** + * Create a new instance of the associated model and associate it with this. + * + * @param sourceInstance the source instance + * @param values values to create associated model instance with + * @param options Options passed to `target.create` and setAssociation. + * + * @returns The created target model + */ + async create( + sourceInstance: S, + // @ts-expect-error -- {} is not always assignable to 'values', but Target.create will enforce this, not us. + values: CreationAttributes = {}, + options: BelongsToCreateAssociationMixinOptions = {}, + ): Promise { + values = values || {}; + options = options || {}; + + const newAssociatedObject = await this.target.create(values, options); + await this.set(sourceInstance, newAssociatedObject, options); + + return newAssociatedObject; + } +} + +// workaround https://github.com/evanw/esbuild/issues/1260 +Object.defineProperty(BelongsToAssociation, 'name', { + value: 'BelongsTo', +}); + +export type NormalizedBelongsToOptions< + SourceKey extends string, + TargetKey extends string, +> = NormalizeBaseAssociationOptions>; + +/** + * Options provided when associating models with belongsTo relationship + * + * @see Association class belongsTo method + */ +export interface BelongsToOptions + extends AssociationOptions { + /** + * The name of the field to use as the key for the association in the target table. Defaults to the primary + * key of the target table + */ + targetKey?: TargetKey; + + inverse?: { + type: 'hasMany' | 'hasOne'; + as?: string; + scope?: AssociationOptions['scope']; + }; +} + +/** + * The options for the getAssociation mixin of the belongsTo association. + * + * @see BelongsToGetAssociationMixin + */ +export interface BelongsToGetAssociationMixinOptions + extends FindOptions> { + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | string[] | boolean; + + /** + * Apply a schema on the related model + */ + schema?: string; + schemaDelimiter?: string; +} + +/** + * The getAssociation mixin applied to models with belongsTo. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare getRole: BelongsToGetAssociationMixin; + * } + * + * User.belongsTo(Role); + * ``` + * + * @see Model.belongsTo + */ +// TODO: in the future, type the return value based on whether the foreign key is nullable or not on the source model. +// if nullable, return TModel | null +// https://github.com/sequelize/meetings/issues/14 +export type BelongsToGetAssociationMixin = ( + options?: BelongsToGetAssociationMixinOptions, +) => Promise; + +/** + * The options for the setAssociation mixin of the belongsTo association. + * + * @see BelongsToSetAssociationMixin + */ +export interface BelongsToSetAssociationMixinOptions + extends SaveOptions> { + /** + * Skip saving this after setting the foreign key if false. + */ + save?: boolean; +} + +/** + * The setAssociation mixin applied to models with belongsTo. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare setRole: BelongsToSetAssociationMixin; + * } + * + * User.belongsTo(Role); + * ``` + * + * @see Model.belongsTo + * + * @typeParam TargetKeyType The type of the attribute that the foreign key references. + */ +export type BelongsToSetAssociationMixin = ( + newAssociation?: T | TargetKeyType, + options?: BelongsToSetAssociationMixinOptions, +) => Promise; + +/** + * The options for the createAssociation mixin of the belongsTo association. + * + * @see BelongsToCreateAssociationMixin + */ +export interface BelongsToCreateAssociationMixinOptions + extends CreateOptions>, + BelongsToSetAssociationMixinOptions {} + +/** + * The createAssociation mixin applied to models with belongsTo. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare createRole: BelongsToCreateAssociationMixin; + * } + * + * User.belongsTo(Role); + * ``` + * + * @see Model.belongsTo + */ +export type BelongsToCreateAssociationMixin = ( + values?: CreationAttributes, + options?: BelongsToCreateAssociationMixinOptions, +) => Promise; diff --git a/packages/core/src/associations/has-many.ts b/packages/core/src/associations/has-many.ts new file mode 100644 index 000000000000..2f98c7c3161a --- /dev/null +++ b/packages/core/src/associations/has-many.ts @@ -0,0 +1,976 @@ +import type { AllowIterable } from '@sequelize/utils'; +import { isPlainObject } from '@sequelize/utils'; +import isObject from 'lodash/isObject'; +import upperFirst from 'lodash/upperFirst'; +import type { WhereOptions } from '../abstract-dialect/where-sql-builder-types.js'; +import { AssociationError } from '../errors/index.js'; +import { col } from '../expression-builders/col.js'; +import { fn } from '../expression-builders/fn.js'; +import type { + AttributeNames, + Attributes, + CreateOptions, + CreationAttributes, + DestroyOptions, + Filterable, + FindOptions, + InstanceUpdateOptions, + Model, + ModelStatic, + Transactionable, + UpdateValues, +} from '../model'; +import { Op } from '../operators'; +import { isSameInitialModel } from '../utils/model-utils.js'; +import { removeUndefined } from '../utils/object.js'; +import type { + Association, + AssociationOptions, + MultiAssociationAccessors, + MultiAssociationOptions, +} from './base'; +import { MultiAssociation } from './base'; +import { BelongsToAssociation } from './belongs-to.js'; +import type { AssociationStatic, NormalizeBaseAssociationOptions } from './helpers'; +import { + defineAssociation, + mixinMethods, + normalizeBaseAssociationOptions, + normalizeInverseAssociation, +} from './helpers'; + +/** + * One-to-many association. + * See {@link Model.hasMany} + * + * Like with {@link HasOneAssociation}, the foreign key will be defined on the target model. + * + * In the API reference below, add the name of the association to the method, e.g. for `User.hasMany(Project)` the getter will be `user.getProjects()`. + * If the association is aliased, use the alias instead, e.g. `User.hasMany(Project, { as: 'jobs' })` will be `user.getJobs()`. + * + * @typeParam S The model on which {@link Model.hasMany} has been called, on which the association methods will be added. + * @typeParam T The model passed to {@link Model.hasMany}. This model will receive the Foreign Key attribute. + * @typeParam SourceKey The name of the attribute that the foreign key in the target model will reference. + * @typeParam TargetKey The name of the Foreign Key attribute on the Target model. + * @typeParam TargetPrimaryKey The name of the Primary Key attribute of the Target model. Used by {@link HasManySetAssociationsMixin} & others. + */ +// Note: this class is named HasManyAssociation instead of HasMany to prevent naming conflicts with the HasMany decorator +export class HasManyAssociation< + S extends Model = Model, + T extends Model = Model, + SourceKey extends AttributeNames = any, + TargetKey extends AttributeNames = any, + TargetPrimaryKey extends AttributeNames = any, +> extends MultiAssociation< + S, + T, + TargetKey, + TargetPrimaryKey, + NormalizedHasManyOptions +> { + accessors: MultiAssociationAccessors; + + get foreignKey(): TargetKey { + return this.inverse.foreignKey; + } + + /** + * The column name of the foreign key (on the target model) + */ + get identifierField(): string { + return this.inverse.identifierField; + } + + /** + * The name of the attribute the foreign key points to. + * + * This key is on the Source Model. + * The {@link Association.foreignKey} is on the Target Model. + */ + get sourceKey(): SourceKey { + return this.inverse.targetKey; + } + + /** + * @deprecated use {@link sourceKey} + */ + get sourceKeyAttribute(): SourceKey { + return this.sourceKey; + } + + get sourceKeyField(): string { + return this.inverse.targetKeyField; + } + + readonly inverse: BelongsToAssociation; + + constructor( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: NormalizedHasManyOptions, + parent?: Association, + inverse?: BelongsToAssociation, + ) { + if (options.sourceKey && !source.getAttributes()[options.sourceKey]) { + throw new Error( + `Unknown attribute "${options.sourceKey}" passed as sourceKey, define this attribute on model "${source.name}" first`, + ); + } + + if ('keyType' in options) { + throw new TypeError( + 'Option "keyType" has been removed from the BelongsTo\'s options. Set "foreignKey.type" instead.', + ); + } + + if ('through' in options) { + throw new Error( + 'The "through" option is not available in hasMany. N:M associations are defined using belongsToMany instead.', + ); + } + + super(secret, source, target, options, parent); + + this.inverse = + inverse ?? + BelongsToAssociation.associate( + secret, + target, + source, + removeUndefined({ + as: options.inverse?.as, + scope: options.inverse?.scope, + foreignKey: options.foreignKey, + targetKey: options.sourceKey, + foreignKeyConstraints: options.foreignKeyConstraints, + hooks: options.hooks, + }), + this, + ); + + // Get singular and plural names + // try to uppercase the first letter, unless the model forbids it + const plural = upperFirst(this.options.name.plural); + const singular = upperFirst(this.options.name.singular); + + this.accessors = { + get: `get${plural}`, + set: `set${plural}`, + addMultiple: `add${plural}`, + add: `add${singular}`, + create: `create${singular}`, + remove: `remove${singular}`, + removeMultiple: `remove${plural}`, + hasSingle: `has${singular}`, + hasAll: `has${plural}`, + count: `count${plural}`, + }; + + this.#mixin(source.prototype); + } + + static associate< + S extends Model, + T extends Model, + SourceKey extends AttributeNames, + TargetKey extends AttributeNames, + >( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: HasManyOptions = {}, + parent?: Association, + inverse?: BelongsToAssociation, + ): HasManyAssociation { + return defineAssociation< + HasManyAssociation, + HasManyOptions, + NormalizedHasManyOptions + >( + HasManyAssociation, + source, + target, + options, + parent, + normalizeHasManyOptions, + normalizedOptions => { + // self-associations must always set their 'as' parameter + if ( + isSameInitialModel(source, target) && + // use 'options' because this will always be set in 'normalizedOptions' + (!options.as || + !normalizedOptions.inverse?.as || + options.as === normalizedOptions.inverse.as) + ) { + throw new AssociationError( + 'Both options "as" and "inverse.as" must be defined for hasMany self-associations, and their value must be different.', + ); + } + + return new HasManyAssociation(secret, source, target, normalizedOptions, parent, inverse); + }, + ); + } + + #mixin(mixinTargetPrototype: Model) { + mixinMethods( + this, + mixinTargetPrototype, + [ + 'get', + 'count', + 'hasSingle', + 'hasAll', + 'set', + 'add', + 'addMultiple', + 'remove', + 'removeMultiple', + 'create', + ], + { + hasSingle: 'has', + hasAll: 'has', + addMultiple: 'add', + removeMultiple: 'remove', + }, + ); + } + + /** + * Get everything currently associated with this, using an optional where clause. + * + * @param instances source instances + * @param options find options + */ + async get(instances: S, options?: HasManyGetAssociationsMixinOptions): Promise; + async get( + instances: S[], + options?: HasManyGetAssociationsMixinOptions, + ): Promise>; + async get( + instances: S | S[], + options: HasManyGetAssociationsMixinOptions = {}, + ): Promise> { + let isManyMode = true; + if (!Array.isArray(instances)) { + isManyMode = false; + instances = [instances]; + } + + const findOptions: FindOptions = { ...options }; + + const where = Object.create(null); + + // TODO: scopes should be combined using AND instance of overwriting. + if (this.scope) { + Object.assign(where, this.scope); + } + + let values; + if (instances.length > 1) { + values = instances.map(instance => instance.get(this.sourceKey, { raw: true })); + + if (findOptions.limit && instances.length > 1) { + findOptions.groupedLimit = { + limit: findOptions.limit, + on: this, // association + values, + }; + + delete findOptions.limit; + } else { + where[this.foreignKey] = { + [Op.in]: values, + }; + delete findOptions.groupedLimit; + } + } else { + where[this.foreignKey] = instances[0].get(this.sourceKey, { raw: true }); + } + + findOptions.where = findOptions.where ? { [Op.and]: [where, findOptions.where] } : where; + + let Model = this.target; + if (options.scope != null) { + if (!options.scope) { + Model = Model.withoutScope(); + } else if (options.scope !== true) { + // 'true' means default scope. Which is the same as not doing anything. + Model = Model.withScope(options.scope); + } + } + + if (options.schema != null) { + Model = Model.withSchema({ + schema: options.schema, + schemaDelimiter: options.schemaDelimiter, + }); + } + + const results = await Model.findAll(findOptions); + if (!isManyMode) { + return results; + } + + const result = new Map(); + for (const instance of instances) { + result.set(instance.get(this.sourceKey, { raw: true }), []); + } + + for (const instance of results) { + const value = instance.get(this.foreignKey, { raw: true }); + result.get(value)!.push(instance); + } + + return result; + } + + /** + * Count everything currently associated with this, using an optional where clause. + * + * @param instance the source instance + * @param options find & count options + */ + async count(instance: S, options?: HasManyCountAssociationsMixinOptions): Promise { + const findOptions: HasManyGetAssociationsMixinOptions = { + ...options, + raw: true, + plain: true, + attributes: [ + [fn('COUNT', col(`${this.target.name}.${this.target.primaryKeyField}`)), 'count'], + ], + }; + + const result = await this.get(instance, findOptions); + + return Number.parseInt( + // @ts-expect-error -- this.get() isn't designed to expect returning a raw output. + result.count, + 10, + ); + } + + /** + * Check if one or more rows are associated with `this`. + * + * @param sourceInstance the source instance + * @param targets A list of instances or their primary keys + * @param options Options passed to getAssociations + */ + async has( + sourceInstance: S, + targets: AllowIterable>, + options?: HasManyHasAssociationsMixinOptions, + ): Promise { + const normalizedTargets = this.toInstanceOrPkArray(targets); + + const where = { + [Op.or]: normalizedTargets.map(instance => { + if (instance instanceof this.target) { + // TODO: remove eslint-disable once we drop support for < 5.2 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- TS 5.2 works, but < 5.2 does not + // @ts-ignore + return instance.where(); + } + + return { + // TODO: support composite foreign keys + // @ts-expect-error -- TODO: what if the target has no primary key? + [this.target.primaryKeyAttribute]: instance, + }; + }), + }; + + const findOptions: HasManyGetAssociationsMixinOptions = { + ...options, + scope: false, + // TODO: support composite foreign keys + // @ts-expect-error -- TODO: what if the target has no primary key? + attributes: [this.target.primaryKeyAttribute], + raw: true, + // @ts-expect-error -- TODO: current WhereOptions typings do not allow having 'WhereOptions' inside another 'WhereOptions' + where: { + [Op.and]: [where, options?.where], + }, + }; + + const associatedObjects = await this.get(sourceInstance, findOptions); + + return associatedObjects.length === normalizedTargets.length; + } + + /** + * Set the associated models by passing an array of persisted instances or their primary keys. Everything that is not in the passed array will be un-associated + * + * @param sourceInstance source instance to associate new instances with + * @param targets An array of persisted instances or primary key of instances to associate with this. Pass `null` to remove all associations. + * @param options Options passed to `target.findAll` and `update`. + */ + async set( + sourceInstance: S, + targets: AllowIterable> | null, + options?: HasManySetAssociationsMixinOptions, + ): Promise { + const normalizedTargets = this.toInstanceArray(targets); + + const oldAssociations = await this.get(sourceInstance, { ...options, scope: false, raw: true }); + const promises: Array> = []; + const obsoleteAssociations = oldAssociations.filter(old => { + return !normalizedTargets.some(obj => { + // @ts-expect-error -- old is a raw result + return obj.get(this.target.primaryKeyAttribute) === old[this.target.primaryKeyAttribute]; + }); + }); + + const unassociatedObjects = normalizedTargets.filter(obj => { + return !oldAssociations.some(old => { + // @ts-expect-error -- old is a raw result + return obj.get(this.target.primaryKeyAttribute) === old[this.target.primaryKeyAttribute]; + }); + }); + + if (obsoleteAssociations.length > 0) { + promises.push( + this.remove(sourceInstance, obsoleteAssociations, { + ...options, + destroy: options?.destroyPrevious, + }), + ); + } + + if (unassociatedObjects.length > 0) { + const update = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + ...this.scope, + } as UpdateValues; + + const updateWhere = { + // @ts-expect-error -- TODO: what if the target has no primary key? + [this.target.primaryKeyAttribute]: unassociatedObjects.map(unassociatedObject => { + // @ts-expect-error -- TODO: what if the target has no primary key? + return unassociatedObject.get(this.target.primaryKeyAttribute); + }), + }; + + promises.push( + this.target.withoutScope().update(update, { + ...options, + where: updateWhere, + }), + ); + } + + await Promise.all(promises); + } + + /** + * Associate one or more target rows with `this`. This method accepts a Model / string / number to associate a single row, + * or a mixed array of Model / string / numbers to associate multiple rows. + * + * @param sourceInstance the source instance + * @param [rawTargetInstances] A single instance or primary key, or a mixed array of persisted instances or primary keys + * @param [options] Options passed to `target.update`. + */ + async add( + sourceInstance: S, + rawTargetInstances: AllowIterable>, + options: HasManyAddAssociationsMixinOptions = {}, + ): Promise { + const targetInstances = this.toInstanceArray(rawTargetInstances); + + if (targetInstances.length === 0) { + return; + } + + const update = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + ...this.scope, + } as UpdateValues; + + const where = { + // @ts-expect-error -- TODO: what if the target has no primary key? + [this.target.primaryKeyAttribute]: targetInstances.map(unassociatedObject => { + // @ts-expect-error -- TODO: what if the target has no primary key? + return unassociatedObject.get(this.target.primaryKeyAttribute); + }), + }; + + await this.target.withoutScope().update(update, { ...options, where }); + } + + /** + * Un-associate one or several target rows. + * + * @param sourceInstance instance to un associate instances with + * @param targets Can be an Instance or its primary key, or a mixed array of instances and primary keys + * @param options Options passed to `target.update` + */ + async remove( + sourceInstance: S, + targets: AllowIterable>, + options: HasManyRemoveAssociationsMixinOptions = {}, + ): Promise { + if (targets == null) { + return; + } + + const normalizedTargets = this.toInstanceOrPkArray(targets); + if (normalizedTargets.length === 0) { + return; + } + + const where: WhereOptions = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + // @ts-expect-error -- TODO: what if the target has no primary key? + [this.target.primaryKeyAttribute]: normalizedTargets.map(targetInstance => { + if (targetInstance instanceof this.target) { + // @ts-expect-error -- TODO: what if the target has no primary key? + return targetInstance.get(this.target.primaryKeyAttribute); + } + + // raw entity + // @ts-expect-error -- TODO: what if the target has no primary key? + if (isPlainObject(targetInstance) && this.target.primaryKeyAttribute in targetInstance) { + // @ts-expect-error -- implicit any, can't be fixed + return targetInstance[this.target.primaryKeyAttribute]; + } + + // primary key + return targetInstance; + }), + }; + + const foreignKeyIsNullable = + this.target.modelDefinition.attributes.get(this.foreignKey)?.allowNull ?? true; + + if (options.destroy || !foreignKeyIsNullable) { + await this.target.withoutScope().destroy({ + ...(isObject(options.destroy) ? options.destroy : undefined), + logging: options.logging, + benchmark: options.benchmark, + transaction: options.transaction, + where, + }); + } else { + const update = { + [this.foreignKey]: null, + } as UpdateValues; + + await this.target.withoutScope().update(update, { ...options, where }); + } + } + + /** + * Create a new instance of the associated model and associate it with this. + * + * @param sourceInstance source instance + * @param values values for target model instance + * @param options Options passed to `target.create` + */ + async create( + sourceInstance: S, + // @ts-expect-error -- {} is not always assignable to 'values', but Target.create will enforce this, not us. + values: CreationAttributes = {}, + options: + | HasManyCreateAssociationMixinOptions + | HasManyCreateAssociationMixinOptions['fields'] = {}, + ): Promise { + if (Array.isArray(options)) { + options = { + fields: options, + }; + } + + if (this.scope) { + for (const attribute of Object.keys(this.scope)) { + // @ts-expect-error -- TODO: fix the typing of {@link AssociationScope} + values[attribute] = this.scope[attribute]; + if (options.fields) { + options.fields.push(attribute); + } + } + } + + if (options.fields) { + options.fields.push(this.foreignKey); + } + + return this.target.create( + { + ...values, + [this.foreignKey]: sourceInstance.get(this.sourceKey), + }, + options, + ); + } +} + +// workaround https://github.com/evanw/esbuild/issues/1260 +Object.defineProperty(HasManyAssociation, 'name', { + value: 'HasMany', +}); + +export type NormalizedHasManyOptions< + SourceKey extends string, + TargetKey extends string, +> = NormalizeBaseAssociationOptions, 'inverse'>> & { + inverse?: Exclude['inverse'], string>; +}; + +/** + * Options provided when associating models with hasMany relationship + */ +export interface HasManyOptions + extends MultiAssociationOptions { + /** + * 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 + */ + sourceKey?: SourceKey; + + /** + * The name of the inverse association, or an object for further association setup. + */ + inverse?: + | string + | undefined + | { + as?: AssociationOptions['as']; + scope?: AssociationOptions['scope']; + }; +} + +function normalizeHasManyOptions( + type: AssociationStatic, + options: HasManyOptions, + source: ModelStatic, + target: ModelStatic, +): NormalizedHasManyOptions { + return normalizeBaseAssociationOptions( + type, + { + ...options, + inverse: normalizeInverseAssociation(options.inverse), + }, + source, + target, + ); +} + +/** + * The options for the getAssociations mixin of the hasMany association. + * + * Can provide an optional where clause to limit the associated models through {@link HasManyGetAssociationsMixinOptions.where}. + * + * @see HasManyGetAssociationsMixin + */ +export interface HasManyGetAssociationsMixinOptions + extends FindOptions> { + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | string[] | boolean; + + /** + * Apply a schema on the related model + */ + schema?: string; + schemaDelimiter?: string; +} + +/** + * The getAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare getRoles: HasManyGetAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyGetAssociationsMixin = ( + options?: HasManyGetAssociationsMixinOptions, +) => Promise; + +/** + * The options for the setAssociations mixin of the hasMany association. + * + * @see HasManySetAssociationsMixin + */ +export interface HasManySetAssociationsMixinOptions + extends FindOptions>, + InstanceUpdateOptions> { + /** + * Delete the previous associated model. Default to false. + * + * Only applies if the foreign key is nullable. If the foreign key is not nullable, + * the previous associated model is always deleted. + */ + destroyPrevious?: + | boolean + | Omit>, 'where' | 'transaction' | 'logging' | 'benchmark'> + | undefined; +} + +/** + * The setAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare setRoles: HasManySetAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManySetAssociationsMixin = ( + newAssociations?: Iterable | null, + options?: HasManySetAssociationsMixinOptions, +) => Promise; + +/** + * The options for the addAssociations mixin of the hasMany association. + * + * @see HasManyAddAssociationsMixin + */ +export interface HasManyAddAssociationsMixinOptions + extends InstanceUpdateOptions> {} + +/** + * The addAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare addRoles: HasManyAddAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyAddAssociationsMixin = ( + newAssociations?: Iterable, + options?: HasManyAddAssociationsMixinOptions, +) => Promise; + +/** + * The options for the addAssociation mixin of the hasMany association. + * + * @see HasManyAddAssociationMixin + */ +export interface HasManyAddAssociationMixinOptions + extends HasManyAddAssociationsMixinOptions {} + +/** + * The addAssociation mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare addRole: HasManyAddAssociationMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyAddAssociationMixin = ( + newAssociation?: T | TModelPrimaryKey, + options?: HasManyAddAssociationMixinOptions, +) => Promise; + +/** + * The options for the createAssociation mixin of the hasMany association. + * + * @see HasManyCreateAssociationMixin + */ +export interface HasManyCreateAssociationMixinOptions + extends CreateOptions> {} + +/** + * The createAssociation mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare createRole: HasManyCreateAssociationMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyCreateAssociationMixin< + Target extends Model, + ExcludedAttributes extends keyof CreationAttributes = never, +> = ( + values?: Omit, ExcludedAttributes>, + options?: HasManyCreateAssociationMixinOptions, +) => Promise; + +/** + * The options for the removeAssociation mixin of the hasMany association. + * + * @see HasManyRemoveAssociationMixin + */ +export interface HasManyRemoveAssociationMixinOptions + extends HasManyRemoveAssociationsMixinOptions {} + +/** + * The removeAssociation mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare removeRole: HasManyRemoveAssociationMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyRemoveAssociationMixin = ( + oldAssociated?: T | TModelPrimaryKey, + options?: HasManyRemoveAssociationMixinOptions, +) => Promise; + +/** + * The options for the removeAssociations mixin of the hasMany association. + * + * @see HasManyRemoveAssociationsMixin + */ +export interface HasManyRemoveAssociationsMixinOptions + extends Omit>, 'where'> { + /** + * Delete the associated model. Default to false. + * + * Only applies if the foreign key is nullable. If the foreign key is not nullable, + * the associated model is always deleted. + */ + destroy?: + | boolean + | Omit>, 'where' | 'transaction' | 'logging' | 'benchmark'> + | undefined; +} + +/** + * The removeAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare removeRoles: HasManyRemoveAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyRemoveAssociationsMixin = ( + oldAssociateds?: Iterable, + options?: HasManyRemoveAssociationsMixinOptions, +) => Promise; + +/** + * The options for the hasAssociation mixin of the hasMany association. + * + * @see HasManyHasAssociationMixin + */ +export interface HasManyHasAssociationMixinOptions + extends HasManyGetAssociationsMixinOptions {} + +/** + * The hasAssociation mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare hasRole: HasManyHasAssociationMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyHasAssociationMixin = ( + target: TModel | TModelPrimaryKey, + options?: HasManyHasAssociationMixinOptions, +) => Promise; + +/** + * The options for the hasAssociations mixin of the hasMany association. + * + * @see HasManyHasAssociationsMixin + */ +export interface HasManyHasAssociationsMixinOptions + extends HasManyGetAssociationsMixinOptions {} + +/** + * The removeAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare hasRoles: HasManyHasAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +// TODO: this should be renamed to "HasManyHasAllAssociationsMixin", +// we should also add a "HasManyHasAnyAssociationsMixin" +// and "HasManyHasAssociationsMixin" should instead return a Map of id -> boolean or WeakMap of instance -> boolean +export type HasManyHasAssociationsMixin = ( + targets: Iterable, + options?: HasManyHasAssociationsMixinOptions, +) => Promise; + +/** + * The options for the countAssociations mixin of the hasMany association. + * + * @see HasManyCountAssociationsMixin + */ +export interface HasManyCountAssociationsMixinOptions + extends Transactionable, + Filterable> { + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | boolean; +} + +/** + * The countAssociations mixin applied to models with hasMany. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare countRoles: HasManyCountAssociationsMixin; + * } + * + * User.hasMany(Role); + * ``` + * + * @see Model.hasMany + */ +export type HasManyCountAssociationsMixin = ( + options?: HasManyCountAssociationsMixinOptions, +) => Promise; diff --git a/packages/core/src/associations/has-one.ts b/packages/core/src/associations/has-one.ts new file mode 100644 index 000000000000..7814ccf11f2c --- /dev/null +++ b/packages/core/src/associations/has-one.ts @@ -0,0 +1,556 @@ +import isObject from 'lodash/isObject'; +import upperFirst from 'lodash/upperFirst'; +import { AssociationError } from '../errors/index.js'; +import type { + AttributeNames, + Attributes, + CreateOptions, + CreationAttributes, + FindOptions, + InstanceDestroyOptions, + InstanceUpdateOptions, + ModelStatic, +} from '../model'; +import { Model } from '../model'; +import { Op } from '../operators'; +import { isSameInitialModel } from '../utils/model-utils.js'; +import { cloneDeep, removeUndefined } from '../utils/object.js'; +import type { AssociationOptions, SingleAssociationAccessors } from './base'; +import { Association } from './base'; +import { BelongsToAssociation } from './belongs-to.js'; +import type { AssociationStatic, NormalizeBaseAssociationOptions } from './helpers'; +import { + defineAssociation, + mixinMethods, + normalizeBaseAssociationOptions, + normalizeInverseAssociation, +} from './helpers'; + +/** + * One-to-one association. + * See {@link Model.hasOne} + * + * This is almost the same as {@link BelongsToAssociation}, but the foreign key will be defined on the target model. + * + * In the API reference below, add the name of the association to the method, e.g. for `User.hasOne(Project)` the getter will be `user.getProject()`. + * + * @typeParam S The model on which {@link Model.hasOne} has been called, on which the association methods will be added. + * @typeParam T The model passed to {@link Model.hasOne}. This model will receive the Foreign Key attribute. + * @typeParam SourceKey The name of the attribute that the foreign key in the target model will reference. + * @typeParam TargetKey The name of the Foreign Key attribute on the Target model. + * @typeParam TargetPrimaryKey The name of the Primary Key attribute of the Target model. Used by {@link HasOneSetAssociationMixin}. + */ +// Note: this class is named HasOneAssociation instead of HasOne to prevent naming conflicts with the HasOne decorator +export class HasOneAssociation< + S extends Model = Model, + T extends Model = Model, + SourceKey extends AttributeNames = any, + TargetKey extends AttributeNames = any, + TargetPrimaryKey extends AttributeNames = any, +> extends Association> { + get foreignKey(): TargetKey { + return this.inverse.foreignKey; + } + + /** + * The column name of the foreign key (on the target model) + */ + get identifierField(): string { + return this.inverse.identifierField; + } + + /** + * The name of the attribute the foreign key points to. + * In HasOne, it is on the Source Model, instead of the Target Model (unlike {@link BelongsToAssociation.targetKey}). + * The {@link Association.foreignKey} is on the Target Model. + */ + get sourceKey(): SourceKey { + return this.inverse.targetKey; + } + + /** + * The Column Name of the source key. + */ + get sourceKeyField(): string { + return this.inverse.targetKeyField; + } + + /** + * @deprecated use {@link sourceKey} + */ + get sourceKeyAttribute(): SourceKey { + return this.sourceKey; + } + + readonly inverse: BelongsToAssociation; + + readonly accessors: SingleAssociationAccessors; + + constructor( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: NormalizedHasOneOptions, + parent?: Association, + inverse?: BelongsToAssociation, + ) { + if (options?.sourceKey && !source.getAttributes()[options.sourceKey]) { + throw new Error( + `Unknown attribute "${options.sourceKey}" passed as sourceKey, define this attribute on model "${source.name}" first`, + ); + } + + if ('keyType' in options) { + throw new TypeError( + `Option "keyType" has been removed from the BelongsTo's options. Set "foreignKey.type" instead.`, + ); + } + + super(secret, source, target, options, parent); + + this.inverse = + inverse ?? + BelongsToAssociation.associate( + secret, + target, + source, + removeUndefined({ + as: options.inverse?.as, + scope: options.inverse?.scope, + foreignKey: options.foreignKey, + targetKey: options.sourceKey, + foreignKeyConstraints: options.foreignKeyConstraints, + hooks: options.hooks, + }), + this, + ); + + // Get singular name, trying to uppercase the first letter, unless the model forbids it + const singular = upperFirst(this.options.name.singular); + + this.accessors = { + get: `get${singular}`, + set: `set${singular}`, + create: `create${singular}`, + }; + + this.#mixin(source.prototype); + } + + #mixin(mixinTargetPrototype: Model) { + mixinMethods(this, mixinTargetPrototype, ['get', 'set', 'create']); + } + + static associate< + S extends Model, + T extends Model, + SourceKey extends AttributeNames, + TargetKey extends AttributeNames, + >( + secret: symbol, + source: ModelStatic, + target: ModelStatic, + options: HasOneOptions = {}, + parent?: Association, + inverse?: BelongsToAssociation, + ): HasOneAssociation { + return defineAssociation< + HasOneAssociation, + HasOneOptions, + NormalizedHasOneOptions + >( + HasOneAssociation, + source, + target, + options, + parent, + normalizeHasOneOptions, + normalizedOptions => { + // self-associations must always set their 'as' parameter + if ( + isSameInitialModel(source, target) && + // use 'options' because this will always be set in 'normalizedOptions' + (!options.as || + !normalizedOptions.inverse?.as || + options.as === normalizedOptions.inverse.as) + ) { + throw new AssociationError(`Both options "as" and "inverse.as" must be defined for hasOne self-associations, and their value must be different. +This is because hasOne associations automatically create the corresponding belongsTo association, but they cannot share the same name. + +If having two associations does not make sense (for instance a "spouse" association from user to user), consider using belongsTo instead of hasOne.`); + } + + return new HasOneAssociation(secret, source, target, normalizedOptions, parent, inverse); + }, + ); + } + + /** + * Get the associated instance. + * + * See {@link HasOneGetAssociationMixinOptions} for a full explanation of options. + * This method is mixed-in the source model prototype. See {@link HasOneGetAssociationMixin}. + * + * @param instances source instances + * @param options find options + */ + async get(instances: S, options?: HasOneGetAssociationMixinOptions): Promise; + async get( + instances: S[], + options?: HasOneGetAssociationMixinOptions, + ): Promise>; + async get( + instances: S | S[], + options?: HasOneGetAssociationMixinOptions, + ): Promise | T | null> { + options = options ? cloneDeep(options) : {}; + + let Target = this.target; + if (options.scope != null) { + if (!options.scope) { + Target = Target.withoutScope(); + } else if (options.scope !== true) { + // 'true' means default scope. Which is the same as not doing anything. + Target = Target.withScope(options.scope); + } + } + + if (options.schema != null) { + Target = Target.withSchema({ + schema: options.schema, + schemaDelimiter: options.schemaDelimiter, + }); + } + + let isManyMode = true; + if (!Array.isArray(instances)) { + isManyMode = false; + instances = [instances]; + } + + const where = Object.create(null); + + if (instances.length > 1) { + where[this.foreignKey] = { + [Op.in]: instances.map(instance => instance.get(this.sourceKey)), + }; + } else { + where[this.foreignKey] = instances[0].get(this.sourceKey); + } + + if (this.scope) { + Object.assign(where, this.scope); + } + + options.where = options.where ? { [Op.and]: [where, options.where] } : where; + + if (isManyMode) { + const results = await Target.findAll(options); + const result = new Map(); + + for (const targetInstance of results) { + result.set(targetInstance.get(this.foreignKey, { raw: true }), targetInstance); + } + + return result; + } + + return Target.findOne(options); + } + + /** + * Set the associated model. + * + * @param sourceInstance the source instance + * @param associatedInstanceOrPk An persisted instance or the primary key of an instance to associate with this. Pass `null` to remove the association. + * @param options Options passed to getAssociation and `target.save` + * + * @returns The associated instance, or null if disassociated. + */ + async set( + sourceInstance: S, + associatedInstanceOrPk: T | T[TargetPrimaryKey], + options?: HasOneSetAssociationMixinOptions, + ): Promise; + async set( + sourceInstance: S, + associatedInstanceOrPk: null, + options?: HasOneSetAssociationMixinOptions, + ): Promise; + async set( + sourceInstance: S, + associatedInstanceOrPk: T | T[TargetPrimaryKey] | null, + options?: HasOneSetAssociationMixinOptions, + ): Promise { + options = { ...options, scope: false }; + + // @ts-expect-error -- .save isn't listed in the options because it's not supported, but we'll still warn users if they use it. + if (options.save === false) { + throw new Error(`The "save: false" option cannot be honoured in ${this.source.name}#${this.accessors.set} +because, as this is a hasOne association, the foreign key we need to update is located on the model ${this.target.name}. + +This option is only available in BelongsTo associations.`); + } + + // calls the 'get' mixin + const oldInstance: T | null = await this.get(sourceInstance, options); + + const alreadyAssociated = + !oldInstance || !associatedInstanceOrPk + ? false + : associatedInstanceOrPk instanceof Model + ? associatedInstanceOrPk.equals(oldInstance) + : // @ts-expect-error -- TODO: what if the target has no primary key? + oldInstance.get(this.target.primaryKeyAttribute) === associatedInstanceOrPk; + + if (alreadyAssociated) { + if (associatedInstanceOrPk instanceof Model) { + return associatedInstanceOrPk; + } + + return oldInstance; + } + + if (oldInstance) { + const foreignKeyIsNullable = + this.target.modelDefinition.attributes.get(this.foreignKey)?.allowNull ?? true; + + if (options.destroyPrevious || !foreignKeyIsNullable) { + await oldInstance.destroy({ + ...(isObject(options.destroyPrevious) ? options.destroyPrevious : undefined), + logging: options.logging, + benchmark: options.benchmark, + transaction: options.transaction, + }); + } else { + await oldInstance.update( + { + [this.foreignKey]: null, + }, + { + ...options, + association: true, + }, + ); + } + } + + if (associatedInstanceOrPk) { + let associatedInstance: T; + if (associatedInstanceOrPk instanceof this.target) { + associatedInstance = associatedInstanceOrPk; + } else { + const tmpInstance = Object.create(null); + // @ts-expect-error -- TODO: what if the target has no primary key? + tmpInstance[this.target.primaryKeyAttribute] = associatedInstanceOrPk; + associatedInstance = this.target.build(tmpInstance, { + isNewRecord: false, + }); + } + + Object.assign(associatedInstance, this.scope); + associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); + + return associatedInstance.save(options); + } + + // disassociated + return null; + } + + /** + * Create a new instance of the associated model and associate it with this. + * + * See {@link Model.create} for a full explanation of options. + * + * @param sourceInstance - the source instance + * @param values - values to create associated model instance with + * @param options - Options passed to `target.create` and setAssociation. + * + * @returns The created target model + */ + async create( + sourceInstance: S, + // @ts-expect-error -- {} is not always assignable to 'values', but Target.create will enforce this, not us. + values: CreationAttributes = {}, + options: HasOneCreateAssociationMixinOptions = {}, + ): Promise { + if (this.scope) { + for (const attribute of Object.keys(this.scope)) { + // @ts-expect-error -- TODO: fix the typing of {@link AssociationScope} + values[attribute] = this.scope[attribute]; + if (options.fields) { + options.fields.push(attribute); + } + } + } + + // @ts-expect-error -- implicit any, can't fix + values[this.foreignKey] = sourceInstance.get(this.sourceKeyAttribute); + if (options.fields) { + options.fields.push(this.foreignKey); + } + + return this.target.create(values, options); + } +} + +// workaround https://github.com/evanw/esbuild/issues/1260 +Object.defineProperty(HasOneAssociation, 'name', { + value: 'HasOne', +}); + +export type NormalizedHasOneOptions< + SourceKey extends string, + TargetKey extends string, +> = NormalizeBaseAssociationOptions, 'inverse'>> & { + inverse?: Exclude['inverse'], string>; +}; + +/** + * Options provided when associating models with hasOne relationship + */ +export interface HasOneOptions + extends AssociationOptions { + /** + * 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. + * + * This is the attribute the foreign key will target. Not to be confused with {@link AssociationOptions.foreignKey}. + */ + sourceKey?: SourceKey; + + /** + * The name of the inverse association, or an object for further association setup. + */ + inverse?: + | string + | undefined + | { + as?: AssociationOptions['as']; + scope?: AssociationOptions['scope']; + }; +} + +function normalizeHasOneOptions( + type: AssociationStatic, + options: HasOneOptions, + source: ModelStatic, + target: ModelStatic, +): NormalizedHasOneOptions { + return normalizeBaseAssociationOptions( + type, + { + ...options, + inverse: normalizeInverseAssociation(options.inverse), + }, + source, + target, + ); +} + +/** + * The options for the getAssociation mixin of the hasOne association. + * + * @see HasOneGetAssociationMixin + */ +export interface HasOneGetAssociationMixinOptions + extends FindOptions> { + /** + * Apply a scope on the related model, or remove its default scope by passing false. + */ + scope?: string | string[] | boolean; + + /** + * Apply a schema on the related model + */ + schema?: string; + schemaDelimiter?: string; +} + +/** + * The getAssociation mixin applied to models with hasOne. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare getRole: HasOneGetAssociationMixin; + * } + * + * User.hasOne(Role); + * ``` + * + * @returns The associated model, or null if no model is associated. HasOne associations are always nullable because the foreign key is on the target model. + * + * @see Model.hasOne + */ +export type HasOneGetAssociationMixin = ( + options?: HasOneGetAssociationMixinOptions, +) => Promise; + +/** + * The options for the setAssociation mixin of the hasOne association. + * + * @see HasOneSetAssociationMixin + */ +export interface HasOneSetAssociationMixinOptions + extends HasOneGetAssociationMixinOptions, + InstanceUpdateOptions> { + /** + * Delete the previous associated model. Default to false. + * + * Only applies if the foreign key is nullable. If the foreign key is not nullable, + * the previous associated model is always deleted. + */ + destroyPrevious?: boolean | Omit; +} + +/** + * The setAssociation mixin applied to models with hasOne. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare setRole: HasOneSetAssociationMixin; + * } + * + * User.hasOne(Role); + * ``` + * + * @see Model.hasOne + */ +export type HasOneSetAssociationMixin = { + (newAssociation: null, options?: HasOneSetAssociationMixinOptions): Promise; + (newAssociation: T | TModelPrimaryKey, options?: HasOneSetAssociationMixinOptions): Promise; +}; + +/** + * The options for the createAssociation mixin of the hasOne association. + * + * @see HasOneCreateAssociationMixin + */ +export interface HasOneCreateAssociationMixinOptions + extends Omit, 'fields'>, + CreateOptions> {} + +/** + * The createAssociation mixin applied to models with hasOne. + * An example of usage is as follows: + * + * ```typescript + * class User extends Model, InferCreationAttributes> { + * declare createRole: HasOneCreateAssociationMixin; + * } + * + * User.hasOne(Role); + * ``` + * + * @see Model.hasOne + */ +export type HasOneCreateAssociationMixin< + Target extends Model, + ExcludedAttributes extends keyof CreationAttributes = never, +> = ( + values?: Omit, ExcludedAttributes>, + options?: HasOneCreateAssociationMixinOptions, +) => Promise; diff --git a/packages/core/src/associations/helpers.ts b/packages/core/src/associations/helpers.ts new file mode 100644 index 000000000000..834a66c5f1d3 --- /dev/null +++ b/packages/core/src/associations/helpers.ts @@ -0,0 +1,349 @@ +import isEqual from 'lodash/isEqual'; +import isPlainObject from 'lodash/isPlainObject.js'; +import lowerFirst from 'lodash/lowerFirst'; +import omit from 'lodash/omit'; +import assert from 'node:assert'; +import NodeUtils from 'node:util'; +import type { Class } from 'type-fest'; +import { AssociationError } from '../errors/index.js'; +import type { Model, ModelStatic } from '../model'; +import type { Sequelize } from '../sequelize'; +import * as deprecations from '../utils/deprecations.js'; +import { isModelStatic, isSameInitialModel } from '../utils/model-utils.js'; +import { removeUndefined } from '../utils/object.js'; +import { pluralize, singularize } from '../utils/string.js'; +import type { OmitConstructors } from '../utils/types.js'; +import type { + Association, + AssociationOptions, + ForeignKeyOptions, + NormalizedAssociationOptions, +} from './base'; +import type { ThroughOptions } from './belongs-to-many.js'; + +export function checkNamingCollision(source: ModelStatic, associationName: string): void { + if (Object.hasOwn(source.getAttributes(), associationName)) { + throw new Error( + `Naming collision between attribute '${associationName}'` + + ` and association '${associationName}' on model ${source.name}` + + '. To remedy this, change the "as" options in your association definition', + ); + } +} + +/** + * Mixin (inject) association methods to model prototype + * + * @private + * + * @param association instance + * @param mixinTargetPrototype Model prototype + * @param methods Method names to inject + * @param aliases Mapping between model and association method names + */ +export function mixinMethods>( + association: A, + mixinTargetPrototype: Model, + methods: Array, + aliases?: Aliases, +): void { + for (const method of methods) { + // @ts-expect-error -- implicit any, no way around it + const targetMethodName = association.accessors[method]; + + // don't override custom methods + if (Object.hasOwn(mixinTargetPrototype, targetMethodName)) { + continue; + } + + // @ts-expect-error -- implicit any, no way around it + const realMethod = aliases?.[method] || method; + + Object.defineProperty(mixinTargetPrototype, targetMethodName, { + enumerable: false, + value(...params: any[]) { + // @ts-expect-error -- implicit any, no way around it + return association[realMethod](this, ...params); + }, + }); + } +} + +/** + * Used to prevent users from instantiating Associations themselves. + * Instantiating associations is not safe as it mutates the Model object. + * + * @private do not expose outside sequelize + */ +export const AssociationSecret = Symbol('AssociationConstructorPrivateKey'); + +export function assertAssociationUnique( + type: Class, + source: ModelStatic, + target: ModelStatic, + options: NormalizedAssociationOptions, + parent: Association | undefined, +) { + const as = options.as; + + const existingAssociation = source.associations[as]; + if (!existingAssociation) { + return; + } + + const incompatibilityStatus = getAssociationsIncompatibilityStatus( + existingAssociation, + type, + target, + options, + ); + if ((parent || existingAssociation.parentAssociation) && incompatibilityStatus == null) { + return; + } + + const existingRoot = existingAssociation.rootAssociation; + + if (!parent && existingRoot === existingAssociation) { + throw new AssociationError( + `You have defined two associations with the same name "${as}" on the model "${source.name}". Use another alias using the "as" parameter.`, + ); + } + + throw new AssociationError( + ` +${parent ? `The association "${parent.as}" needs to define` : `You are trying to define`} the ${type.name} association "${options.as}" from ${source.name} to ${target.name}, +but that child association has already been defined as ${existingAssociation.associationType}, to ${target.name} by this call: + +${existingRoot.source.name}.${lowerFirst(existingRoot.associationType)}(${existingRoot.target.name}, ${NodeUtils.inspect(existingRoot.options)}) + +That association would be re-used if compatible, but it is incompatible because ${ + incompatibilityStatus === IncompatibilityStatus.DIFFERENT_TYPES + ? `their types are different (${type.name} vs ${existingAssociation.associationType})` + : incompatibilityStatus === IncompatibilityStatus.DIFFERENT_TARGETS + ? `they target different models (${target.name} vs ${existingAssociation.target.name})` + : `their options are not reconcilable: + +Options of the association to create: +${NodeUtils.inspect(omit(options, 'inverse'), { sorted: true })} + +Options of the existing association: +${NodeUtils.inspect(omit(existingAssociation.options as any, 'inverse'), { sorted: true })} +` + }`.trim(), + ); +} + +/** + * @private + */ +enum IncompatibilityStatus { + DIFFERENT_TYPES = 0, + DIFFERENT_TARGETS = 1, + DIFFERENT_OPTIONS = 2, +} + +function getAssociationsIncompatibilityStatus( + existingAssociation: Association, + newAssociationType: Class, + newTarget: ModelStatic, + newOptions: NormalizeBaseAssociationOptions, +): IncompatibilityStatus | null { + if (existingAssociation.associationType !== newAssociationType.name) { + return IncompatibilityStatus.DIFFERENT_TYPES; + } + + if (!isSameInitialModel(existingAssociation.target, newTarget)) { + return IncompatibilityStatus.DIFFERENT_TARGETS; + } + + const opts1 = omit(existingAssociation.options as any, 'inverse'); + const opts2 = omit(newOptions, 'inverse'); + if (!isEqual(opts1, opts2)) { + return IncompatibilityStatus.DIFFERENT_OPTIONS; + } + + return null; +} + +export function assertAssociationModelIsDefined(model: ModelStatic): void { + if (!model.sequelize) { + throw new Error( + `Model ${model.name} must be defined (through Model.init or Sequelize#define) before calling one of its association declaration methods.`, + ); + } +} + +export type AssociationStatic = { + new (...arguments_: any[]): T; +} & OmitConstructors; + +export function defineAssociation< + T extends Association, + RawOptions extends AssociationOptions, + CleanOptions extends NormalizedAssociationOptions, +>( + type: AssociationStatic, + source: ModelStatic, + target: ModelStatic, + options: RawOptions, + parent: Association | undefined, + normalizeOptions: ( + type: AssociationStatic, + options: RawOptions, + source: ModelStatic, + target: ModelStatic, + ) => CleanOptions, + construct: (opts: CleanOptions) => T, +): T { + if (!isModelStatic(target)) { + throw new Error( + `${source.name}.${lowerFirst(type.name)} was called with ${NodeUtils.inspect(target)} as the target model, but it is not a subclass of Sequelize's Model class`, + ); + } + + assertAssociationModelIsDefined(source); + assertAssociationModelIsDefined(target); + + const normalizedOptions = normalizeOptions(type, options, source, target); + + checkNamingCollision(source, normalizedOptions.as); + assertAssociationUnique(type, source, target, normalizedOptions, parent); + + const sequelize = source.sequelize; + Object.defineProperty(normalizedOptions, 'sequelize', { + configurable: true, + get() { + deprecations.movedSequelizeParam(); + + return sequelize; + }, + }); + + if (normalizedOptions.hooks) { + source.hooks.runSync('beforeAssociate', { source, target, type, sequelize }, normalizedOptions); + } + + let association; + try { + association = (source.associations[normalizedOptions.as] as T) ?? construct(normalizedOptions); + } catch (error) { + throw new AssociationError( + parent + ? `Association "${parent.as}" needs to create the ${type.name} association "${normalizedOptions.as}" from ${source.name} to ${target.name}, but it failed` + : `Defining ${type.name} association "${normalizedOptions.as}" from ${source.name} to ${target.name} failed`, + { cause: error as Error }, + ); + } + + if (normalizedOptions.hooks) { + source.hooks.runSync( + 'afterAssociate', + { source, target, type, association, sequelize }, + normalizedOptions, + ); + } + + checkNamingCollision(source, normalizedOptions.as); + + return association; +} + +export type NormalizeBaseAssociationOptions = Omit & { + as: string; + name: { singular: string; plural: string }; + hooks: boolean; + foreignKey: ForeignKeyOptions; +}; + +export function normalizeInverseAssociation( + inverse: T | string | undefined, +): T | undefined { + if (typeof inverse === 'string') { + return { as: inverse } as T; + } + + return inverse; +} + +export function normalizeBaseAssociationOptions>( + associationType: AssociationStatic, + options: T, + source: ModelStatic, + target: ModelStatic, +): NormalizeBaseAssociationOptions { + if ('onDelete' in options || 'onUpdate' in options) { + throw new Error( + 'Options "onDelete" and "onUpdate" have been moved to "foreignKey.onDelete" and "foreignKey.onUpdate" (also available as "otherKey" in belongsToMany)', + ); + } + + if ('constraints' in options) { + throw new Error('Option "constraints" has been renamed to "foreignKeyConstraints"'); + } + + if ('foreignKeyConstraint' in options) { + throw new Error( + 'Option "foreignKeyConstraint" has been renamed to "foreignKeyConstraints" (with a "s" at the end)', + ); + } + + const isMultiAssociation = associationType.isMultiAssociation; + + let name: { singular: string; plural: string }; + let as: string; + if (options?.as) { + if (isPlainObject(options.as)) { + assert(typeof options.as === 'object'); + name = options.as; + as = isMultiAssociation ? options.as.plural : options.as.singular; + } else { + assert(typeof options.as === 'string'); + as = options.as; + name = { + plural: isMultiAssociation ? options.as : pluralize(options.as), + singular: isMultiAssociation ? singularize(options.as) : options.as, + }; + } + } else { + as = lowerFirst(isMultiAssociation ? target.options.name.plural : target.options.name.singular); + name = { + plural: lowerFirst(target.options.name.plural), + singular: lowerFirst(target.options.name.singular), + }; + } + + return removeUndefined({ + ...options, + foreignKey: normalizeForeignKeyOptions(options.foreignKey), + hooks: options.hooks ?? false, + as, + name, + }); +} + +export function normalizeForeignKeyOptions( + foreignKey: AssociationOptions['foreignKey'], +): ForeignKeyOptions { + return typeof foreignKey === 'string' + ? { name: foreignKey } + : removeUndefined({ + ...foreignKey, + name: foreignKey?.name ?? foreignKey?.fieldName, + fieldName: undefined, + }); +} + +export type MaybeForwardedModelStatic = + | ModelStatic + | ((sequelize: Sequelize) => ModelStatic); + +export function getForwardedModel( + model: MaybeForwardedModelStatic, + sequelize: Sequelize, +): ModelStatic { + return typeof model === 'function' && !isModelStatic(model) ? model(sequelize) : model; +} + +export function isThroughOptions(val: any): val is ThroughOptions { + return isPlainObject(val) && 'model' in val; +} diff --git a/packages/core/src/associations/index.ts b/packages/core/src/associations/index.ts new file mode 100644 index 000000000000..09b1f93707b3 --- /dev/null +++ b/packages/core/src/associations/index.ts @@ -0,0 +1,21 @@ +import type { Class } from 'type-fest'; +import type { Model, ModelStatic } from '../model'; +import type { Sequelize } from '../sequelize'; +import type { Association } from './base'; + +export * from './base'; +export * from './belongs-to'; +export * from './belongs-to-many'; +export * from './has-many'; +export * from './has-one'; + +export interface BeforeAssociateEventData { + source: ModelStatic; + target: ModelStatic; + sequelize: Sequelize; + type: Class; +} + +export interface AfterAssociateEventData extends BeforeAssociateEventData { + association: Association; +} diff --git a/packages/core/src/data-types.ts b/packages/core/src/data-types.ts new file mode 100644 index 000000000000..89b9f22f4754 --- /dev/null +++ b/packages/core/src/data-types.ts @@ -0,0 +1,90 @@ +/** + * The classes declared in this files are the DataTypes available on the `DataTypes` namespace. + * You can access them as follows: + * + * ```ts + * import { DataTypes } from '@sequelize/core'; + * + * DataTypes.STRING; + * ``` + * + * @module DataTypes + */ + +import * as DataTypes from './abstract-dialect/data-types.js'; +import { classToInvokable } from './utils/class-to-invokable.js'; + +export { AbstractDataType as ABSTRACT } from './abstract-dialect/data-types.js'; + +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const STRING = classToInvokable(DataTypes.STRING); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const CHAR = classToInvokable(DataTypes.CHAR); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const TEXT = classToInvokable(DataTypes.TEXT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const TINYINT = classToInvokable(DataTypes.TINYINT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const SMALLINT = classToInvokable(DataTypes.SMALLINT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const MEDIUMINT = classToInvokable(DataTypes.MEDIUMINT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const INTEGER = classToInvokable(DataTypes.INTEGER); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const BIGINT = classToInvokable(DataTypes.BIGINT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const FLOAT = classToInvokable(DataTypes.FLOAT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const TIME = classToInvokable(DataTypes.TIME); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const DATE = classToInvokable(DataTypes.DATE); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const DATEONLY = classToInvokable(DataTypes.DATEONLY); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const BOOLEAN = classToInvokable(DataTypes.BOOLEAN); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const NOW = classToInvokable(DataTypes.NOW); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const BLOB = classToInvokable(DataTypes.BLOB); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const DECIMAL = classToInvokable(DataTypes.DECIMAL); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const UUID = classToInvokable(DataTypes.UUID); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const UUIDV1 = classToInvokable(DataTypes.UUIDV1); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const UUIDV4 = classToInvokable(DataTypes.UUIDV4); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const HSTORE = classToInvokable(DataTypes.HSTORE); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const JSON = classToInvokable(DataTypes.JSON); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const JSONB = classToInvokable(DataTypes.JSONB); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const VIRTUAL = classToInvokable(DataTypes.VIRTUAL); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const ARRAY = classToInvokable(DataTypes.ARRAY); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const ENUM = classToInvokable(DataTypes.ENUM); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const RANGE = classToInvokable(DataTypes.RANGE); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const REAL = classToInvokable(DataTypes.REAL); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const DOUBLE = classToInvokable(DataTypes.DOUBLE); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const GEOMETRY = classToInvokable(DataTypes.GEOMETRY); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const GEOGRAPHY = classToInvokable(DataTypes.GEOGRAPHY); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const CIDR = classToInvokable(DataTypes.CIDR); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const INET = classToInvokable(DataTypes.INET); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const MACADDR = classToInvokable(DataTypes.MACADDR); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const MACADDR8 = classToInvokable(DataTypes.MACADDR8); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const CITEXT = classToInvokable(DataTypes.CITEXT); +/** This is a simple wrapper to make the DataType constructable without `new`. See the return type for all available options. */ +export const TSVECTOR = classToInvokable(DataTypes.TSVECTOR); diff --git a/packages/core/src/decorators/legacy/README.md b/packages/core/src/decorators/legacy/README.md new file mode 100644 index 000000000000..a7ab90e4c2d8 --- /dev/null +++ b/packages/core/src/decorators/legacy/README.md @@ -0,0 +1,3 @@ +# decorators-legacy + +This directory regroups the decorators that are built using the legacy decorator proposal. diff --git a/packages/core/src/decorators/legacy/associations.ts b/packages/core/src/decorators/legacy/associations.ts new file mode 100644 index 000000000000..74c61c79f12a --- /dev/null +++ b/packages/core/src/decorators/legacy/associations.ts @@ -0,0 +1,210 @@ +import { EMPTY_ARRAY, isString } from '@sequelize/utils'; +import { inspect } from 'node:util'; +import type { MaybeForwardedModelStatic } from '../../associations/helpers.js'; +import { AssociationSecret, getForwardedModel } from '../../associations/helpers.js'; +import type { + AssociationOptions, + BelongsToManyOptions, + BelongsToOptions, + HasManyOptions, + HasOneOptions, +} from '../../associations/index.js'; +import { + BelongsToAssociation, + BelongsToManyAssociation, + HasManyAssociation, + HasOneAssociation, +} from '../../associations/index.js'; +import type { AttributeNames, Model, ModelStatic } from '../../model.js'; +import type { Sequelize } from '../../sequelize.js'; +import { isModelStatic } from '../../utils/model-utils.js'; +import { throwMustBeInstanceProperty, throwMustBeModel } from './decorator-utils.js'; + +export type AssociationType = 'BelongsTo' | 'HasOne' | 'HasMany' | 'BelongsToMany'; + +interface RegisteredAssociation { + type: AssociationType; + associationName: string; + source: ModelStatic; + target: MaybeForwardedModelStatic; + options: AssociationOptions; +} + +const registeredAssociations = new WeakMap(); + +function decorateAssociation( + type: AssociationType, + source: Object, + target: MaybeForwardedModelStatic, + associationName: string | symbol, + options: AssociationOptions, +): void { + if (typeof source === 'function') { + throwMustBeInstanceProperty(type, source, associationName); + } + + const sourceClass = source.constructor; + if (!isModelStatic(sourceClass)) { + throwMustBeModel(type, source, associationName); + } + + if (typeof associationName === 'symbol') { + throw new TypeError( + 'Symbol associations are not currently supported. We welcome a PR that implements this feature.', + ); + } + + if (options.as) { + throw new Error( + 'The "as" option is not allowed when using association decorators. The name of the decorated field is used as the association name.', + ); + } + + const associations = registeredAssociations.get(sourceClass) ?? []; + registeredAssociations.set(sourceClass, associations); + + associations.push({ source: sourceClass, target, options, associationName, type }); +} + +export function HasOne( + target: MaybeForwardedModelStatic, + optionsOrForeignKey: + | Omit>, 'as'> + | AttributeNames, +) { + return (source: Model, associationName: string | symbol) => { + const options = isString(optionsOrForeignKey) + ? { foreignKey: optionsOrForeignKey } + : optionsOrForeignKey; + + decorateAssociation('HasOne', source, target, associationName, options); + }; +} + +export function HasMany( + target: MaybeForwardedModelStatic, + optionsOrForeignKey: + | Omit>, 'as'> + | AttributeNames, +) { + return (source: Model, associationName: string | symbol) => { + const options = isString(optionsOrForeignKey) + ? { foreignKey: optionsOrForeignKey } + : optionsOrForeignKey; + + decorateAssociation('HasMany', source, target, associationName, options); + }; +} + +export function BelongsTo( + target: MaybeForwardedModelStatic, + optionsOrForeignKey: Omit>, 'as'> | SourceKey, +) { + return ( + // Ideally we'd type this in a way that enforces that the sourceKey is an attribute of the source model, + // but that does not work when the model itself receives its attributes as a generic parameter. + // We'll revisit this when we have a better solution. + // source: Model<{ [key in SourceKey]: any }>, + source: Model, + associationName: string, + ) => { + const options = isString(optionsOrForeignKey) + ? { foreignKey: optionsOrForeignKey } + : optionsOrForeignKey; + + decorateAssociation('BelongsTo', source, target, associationName, options); + }; +} + +export function BelongsToMany( + target: MaybeForwardedModelStatic, + options: Omit, +): PropertyDecorator { + return (source: Object, associationName: string | symbol) => { + decorateAssociation('BelongsToMany', source, target, associationName, options); + }; +} + +export function initDecoratedAssociations(source: ModelStatic, sequelize: Sequelize): void { + const associations = getDeclaredAssociations(source); + + if (!associations.length) { + return; + } + + for (const association of associations) { + const { type, target: targetGetter, associationName } = association; + const options: AssociationOptions = { ...association.options, as: associationName }; + + const target = getForwardedModel(targetGetter, sequelize); + + switch (type) { + case 'BelongsTo': + BelongsToAssociation.associate( + AssociationSecret, + source, + target, + options as BelongsToOptions, + ); + break; + case 'HasOne': + HasOneAssociation.associate( + AssociationSecret, + source, + target, + options as HasOneOptions, + ); + break; + case 'HasMany': + HasManyAssociation.associate( + AssociationSecret, + source, + target, + options as HasManyOptions, + ); + break; + case 'BelongsToMany': + BelongsToManyAssociation.associate( + AssociationSecret, + source, + target, + options as BelongsToManyOptions, + ); + break; + default: + throw new Error(`Unknown association type: ${type}`); + } + } +} + +function getDeclaredAssociations(model: ModelStatic): readonly RegisteredAssociation[] { + const associations: readonly RegisteredAssociation[] = + registeredAssociations.get(model) ?? EMPTY_ARRAY; + + const parentModel = Object.getPrototypeOf(model); + if (isModelStatic(parentModel)) { + const parentAssociations = getDeclaredAssociations(parentModel); + + for (const parentAssociation of parentAssociations) { + if (parentAssociation.type !== 'BelongsTo') { + throw new Error( + `Models that use @HasOne, @HasMany, or @BelongsToMany associations cannot be inherited from, as they would add conflicting foreign keys on the target model. +Only @BelongsTo associations can be inherited, as it will add the foreign key on the source model. +Remove the ${parentAssociation.type} association ${inspect(parentAssociation.associationName)} from model ${inspect(parentModel.name)} to fix this error.`, + ); + } + + if ('inverse' in parentAssociation.options) { + throw new Error( + `Models that use @BelongsTo associations with the "inverse" option cannot be inherited from, as they would add conflicting associations on the target model. +Only @BelongsTo associations without the "inverse" option can be inherited, as they do not declare an association on the target model. +Remove the "inverse" option from association ${inspect(parentAssociation.associationName)} on model ${inspect(parentModel.name)} to fix this error.`, + ); + } + } + + return [...parentAssociations, ...associations]; + } + + return associations; +} diff --git a/packages/core/src/decorators/legacy/attribute-utils.ts b/packages/core/src/decorators/legacy/attribute-utils.ts new file mode 100644 index 000000000000..48ae9121ae0c --- /dev/null +++ b/packages/core/src/decorators/legacy/attribute-utils.ts @@ -0,0 +1,94 @@ +import type { AttributeOptions, ModelStatic } from '../../model.js'; +import { Model } from '../../model.js'; +import { registerModelAttributeOptions } from '../shared/model.js'; +import type { + OptionalParameterizedPropertyDecorator, + RequiredParameterizedPropertyDecorator, +} from './decorator-utils.js'; +import { + DECORATOR_NO_DEFAULT, + createOptionallyParameterizedPropertyDecorator, + throwMustBeAttribute, + throwMustBeInstanceProperty, + throwMustBeModel, +} from './decorator-utils.js'; + +/** + * Creates a decorator that registers Attribute Options. Parameters are mandatory. + * + * @param decoratorName The name of the decorator (must be equal to its export key) + * @param callback The callback that will return the Attribute Options. + */ +export function createRequiredAttributeOptionsDecorator( + decoratorName: string, + callback: ( + option: T, + target: Object, + propertyName: string | symbol, + propertyDescriptor: PropertyDescriptor | undefined, + ) => Partial, +): RequiredParameterizedPropertyDecorator { + return createOptionalAttributeOptionsDecorator(decoratorName, DECORATOR_NO_DEFAULT, callback); +} + +/** + * Creates a decorator that registers Attribute Options. Parameters are optional. + * + * @param decoratorName The name of the decorator (must be equal to its export key) + * @param defaultValue The default value, if no parameter was provided. + * @param callback The callback that will return the Attribute Options. + */ +export function createOptionalAttributeOptionsDecorator( + decoratorName: string, + defaultValue: T | typeof DECORATOR_NO_DEFAULT, + callback: ( + option: T, + target: Object, + propertyName: string, + propertyDescriptor: PropertyDescriptor | undefined, + ) => Partial, +): OptionalParameterizedPropertyDecorator { + return createOptionallyParameterizedPropertyDecorator( + decoratorName, + defaultValue, + (decoratorOption, target, propertyName, propertyDescriptor) => { + if (typeof propertyName === 'symbol') { + throwMustBeAttribute(decoratorName, target, propertyName); + } + + const attributeOptions = callback(decoratorOption, target, propertyName, propertyDescriptor); + + annotate(decoratorName, target, propertyName, propertyDescriptor, attributeOptions); + }, + ); +} + +function annotate( + decoratorName: string, + target: Object, + propertyName: string, + propertyDescriptor: PropertyDescriptor | undefined, + options: Partial, +): void { + if (typeof target === 'function') { + throwMustBeInstanceProperty(decoratorName, target, propertyName); + } + + if (!(target instanceof Model)) { + throwMustBeModel(decoratorName, target, propertyName); + } + + options = { ...options }; + + if (propertyDescriptor) { + if (propertyDescriptor.get) { + options.get = propertyDescriptor.get; + } + + if (propertyDescriptor.set) { + options.set = propertyDescriptor.set; + } + } + + registerModelAttributeOptions(target.constructor as ModelStatic, propertyName, options); +} diff --git a/packages/core/src/decorators/legacy/attribute.ts b/packages/core/src/decorators/legacy/attribute.ts new file mode 100644 index 000000000000..88e09581af15 --- /dev/null +++ b/packages/core/src/decorators/legacy/attribute.ts @@ -0,0 +1,275 @@ +import type { NonUndefined } from '@sequelize/utils'; +import { isDataType } from '../../abstract-dialect/data-types-utils.js'; +import type { DataType } from '../../abstract-dialect/data-types.js'; +import type { AttributeIndexOptions, AttributeOptions } from '../../model.js'; +import { columnToAttribute } from '../../utils/deprecations.js'; +import { underscore } from '../../utils/string.js'; +import { + createOptionalAttributeOptionsDecorator, + createRequiredAttributeOptionsDecorator, +} from './attribute-utils.js'; +import type { PropertyOrGetterDescriptor } from './decorator-utils.js'; + +export type InheritedAttributeOptions = Partial & { + /** + * If true, the attribute will be inserted before the descendant's attributes. + */ + insertBefore?: boolean; + /** + * If true, the attribute will be inserted after the descendant's attributes. + * This is the default behavior. + */ + insertAfter?: boolean; +}; + +type AttributeDecoratorOption = DataType | InheritedAttributeOptions; + +/** + * The `@Attribute` decorator is used to add an attribute to a model. It is used on an instance property. + * + * @example + * The simplest way to use it is to pass a data type as the parameter: + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * declare firstName: string | null; + * } + * ``` + * + * @example + * `@Attribute` also accepts an option bag, {@link index~AttributeOptions}, which allows you to configure all available attribute definition options. + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute({ + * type: DataTypes.STRING, + * allowNull: false, + * }) + * declare firstName: string; + * } + * ``` + */ +export const Attribute = createRequiredAttributeOptionsDecorator( + 'Attribute', + attrOptionOrDataType => { + if (isDataType(attrOptionOrDataType)) { + return { + type: attrOptionOrDataType, + }; + } + + if (attrOptionOrDataType.insertBefore && attrOptionOrDataType.insertAfter) { + throw new Error( + `Cannot set both 'insertBefore' and 'insertAfter' to true on the same attribute`, + ); + } + + return attrOptionOrDataType; + }, +); + +/** + * @param optionsOrDataType + * @deprecated use {@link Attribute} instead. + */ +export function Column(optionsOrDataType: DataType | AttributeOptions): PropertyOrGetterDescriptor { + columnToAttribute(); + + return Attribute(optionsOrDataType); +} + +type UniqueOptions = NonNullable; + +/** + * The `@Unique` decorator is used to make an attribute unique, it is a shortcut for setting the `unique` option of the {@link Attribute} decorator. + * Learn more about unique constraints in our documentation. + * + * @example + * This makes "firstName" unique + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @Unique + * declare firstName: string; + * } + * ``` + * + * @example + * This creates a composite unique on columns "firstName" and "lastName" + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @Unique('firstName-lastName') + * declare firstName: string; + * + * @Attribute(DataTypes.STRING) + * @Unique('firstName-lastName') + * declare lastName: string; + * } + * ``` + */ +export const Unique = createOptionalAttributeOptionsDecorator( + 'Unique', + true, + (unique: UniqueOptions) => ({ unique }), +); + +/** + * Makes the attribute accept null values. Opposite of {@link NotNull}. + * It is a shortcut for setting the `allowNull` option of the {@link Attribute} decorator to true. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @AllowNull + * declare firstName: string | null; + * } + * ``` + */ +export const AllowNull = createOptionalAttributeOptionsDecorator( + 'AllowNull', + true, + (allowNull: boolean) => ({ allowNull }), +); + +/** + * Makes the attribute reject null values. Opposite of {@link AllowNull}. + * It is a shortcut for setting the `allowNull` option of the {@link Attribute} decorator to false. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @NotNull + * declare firstName: string; + * } + * ``` + */ +export const NotNull = createOptionalAttributeOptionsDecorator( + 'NotNull', + true, + (notNull: boolean) => ({ allowNull: !notNull }), +); + +/** + * The `@PrimaryKey` decorator is used to make an attribute a primary key, + * it is a shortcut for setting the `primaryKey` option of the {@link Attribute} decorator to true. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.INTEGER) + * @PrimaryKey + * declare id: number; + * } + * ``` + */ +export const PrimaryKey = createOptionalAttributeOptionsDecorator( + 'PrimaryKey', + true, + (primaryKey: boolean) => ({ primaryKey }), +); + +/** + * The `@AutoIncrement` decorator is used to make an attribute auto-increment, + * it is a shortcut for setting the `autoIncrement` option of the {@link Attribute} decorator to true. + * + * Some dialects require the field to be a primary key. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.INTEGER) + * @PrimaryKey + * @AutoIncrement + * declare id: number; + * } + * ``` + */ +export const AutoIncrement = createOptionalAttributeOptionsDecorator( + 'AutoIncrement', + true, + (autoIncrement: boolean) => ({ autoIncrement }), +); + +/** + * The `@Comment` decorator is used to set the comment on a column, it is a shortcut for setting the `comment` option of the {@link Attribute} decorator. + * + * This is only useful if you use {@link index~Sequelize#sync} to create your tables. + */ +export const Comment = createRequiredAttributeOptionsDecorator( + 'Comment', + (comment: string) => ({ comment }), +); + +/** + * The `@Default` decorator is used to set a default value for an attribute, it is a shortcut for setting the `defaultValue` option of the {@link Attribute} decorator. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @Default('John Doe') + * declare firstName: string; + * } + * ``` + */ +export const Default = createRequiredAttributeOptionsDecorator( + 'Default', + (defaultValue: unknown) => ({ defaultValue }), +); + +/** + * Sets the name of the column (in the database) this attribute maps to. + * It is a shortcut for setting the `columnName` option of the {@link Attribute} decorator. + * + * With a good naming strategy configured, you rarely need to use this decorator. + * Learn about naming strategies in our documentation. + * + * @example + * ```ts + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.STRING) + * @ColumnName('first_name') + * declare firstName: string; + * } + * ``` + */ +export const ColumnName = createRequiredAttributeOptionsDecorator( + 'ColumnName', + (columnName: string) => ({ columnName }), +); + +type IndexAttributeOption = NonUndefined; + +export function createIndexDecorator( + decoratorName: string, + options: Omit = {}, +) { + return createOptionalAttributeOptionsDecorator( + decoratorName, + {}, + (indexField: IndexAttributeOption): Partial => { + const index: AttributeIndexOptions = { + ...options, + // TODO: default index name should be generated using https://github.com/sequelize/sequelize/issues/15312 + name: options.name || underscore(decoratorName), + attribute: indexField, + }; + + return { index }; + }, + ); +} + +type IndexDecoratorOptions = NonUndefined; + +export const Index = createOptionalAttributeOptionsDecorator( + 'Index', + {}, + (indexField: IndexDecoratorOptions): Partial => { + return { + index: indexField, + }; + }, +); diff --git a/packages/core/src/decorators/legacy/built-in-attributes.ts b/packages/core/src/decorators/legacy/built-in-attributes.ts new file mode 100644 index 000000000000..066428e39b49 --- /dev/null +++ b/packages/core/src/decorators/legacy/built-in-attributes.ts @@ -0,0 +1,75 @@ +import type { ModelStatic } from '../../model.js'; +import { isModelStatic } from '../../utils/model-utils.js'; +import { registerModelOptions } from '../shared/model.js'; +import type { OptionalParameterizedPropertyDecorator } from './decorator-utils.js'; +import { + createOptionallyParameterizedPropertyDecorator, + throwMustBeAttribute, + throwMustBeInstanceProperty, + throwMustBeModel, +} from './decorator-utils.js'; + +function createBuiltInAttributeDecorator( + decoratorName: string, + callback: (target: ModelStatic, propertyName: string) => void, +): OptionalParameterizedPropertyDecorator { + return createOptionallyParameterizedPropertyDecorator( + decoratorName, + undefined, + (decoratorOption, target, propertyName) => { + if (typeof target === 'function') { + throwMustBeInstanceProperty(decoratorName, target, propertyName); + } + + if (!isModelStatic(target.constructor)) { + throwMustBeModel(decoratorName, target, propertyName); + } + + if (typeof propertyName === 'symbol') { + throwMustBeAttribute(decoratorName, target, propertyName); + } + + callback(target.constructor, propertyName); + }, + ); +} + +export const CreatedAt = createBuiltInAttributeDecorator( + 'CreatedAt', + (target: ModelStatic, propertyName: string) => { + registerModelOptions(target, { + createdAt: propertyName, + timestamps: true, + }); + }, +); + +export const UpdatedAt = createBuiltInAttributeDecorator( + 'UpdatedAt', + (target: ModelStatic, propertyName: string) => { + registerModelOptions(target, { + updatedAt: propertyName, + timestamps: true, + }); + }, +); + +export const DeletedAt = createBuiltInAttributeDecorator( + 'DeletedAt', + (target: ModelStatic, propertyName: string) => { + registerModelOptions(target, { + deletedAt: propertyName, + timestamps: true, + paranoid: true, + }); + }, +); + +export const Version = createBuiltInAttributeDecorator( + 'Version', + (target: ModelStatic, propertyName: string) => { + registerModelOptions(target, { + version: propertyName, + }); + }, +); diff --git a/packages/core/src/decorators/legacy/decorator-utils.ts b/packages/core/src/decorators/legacy/decorator-utils.ts new file mode 100644 index 000000000000..aac56f08db16 --- /dev/null +++ b/packages/core/src/decorators/legacy/decorator-utils.ts @@ -0,0 +1,164 @@ +export type PropertyOrGetterDescriptor = ( + target: Object, + propertyName: string | symbol, + propertyDescriptor?: PropertyDescriptor, +) => void; + +export interface OptionalParameterizedPropertyDecorator { + // @Decorator() + (): PropertyOrGetterDescriptor; + // @Decorator(value) + (options: T): PropertyOrGetterDescriptor; + // @Decorator + (target: Object, propertyName: string | symbol, propertyDescriptor?: PropertyDescriptor): void; +} + +export interface RequiredParameterizedPropertyDecorator { + // @Decorator(value) + (options: T): PropertyOrGetterDescriptor; +} + +export const DECORATOR_NO_DEFAULT = Symbol('DECORATOR_NO_DEFAULT'); + +/** + * Creates a decorator that MUST receive a parameter + * + * @param name + * @param callback The callback that will be executed once the decorator is applied. + */ +export function createParameterizedPropertyDecorator( + name: string, + callback: ( + option: T, + target: Object, + propertyName: string | symbol, + propertyDescriptor: PropertyDescriptor | undefined, + ) => void, +): RequiredParameterizedPropertyDecorator { + return createOptionallyParameterizedPropertyDecorator(name, DECORATOR_NO_DEFAULT, callback); +} + +/** + * Creates a decorator that can optionally receive a parameter + * + * @param name + * @param defaultValue The value to use if no parameter is provided. + * @param callback The callback that will be executed once the decorator is applied. + */ +export function createOptionallyParameterizedPropertyDecorator( + name: string, + defaultValue: T | typeof DECORATOR_NO_DEFAULT, + callback: ( + option: T, + target: Object, + propertyName: string | symbol, + propertyDescriptor: PropertyDescriptor | undefined, + ) => void, +): OptionalParameterizedPropertyDecorator { + return function decorator(...args: [] | [options: T] | Parameters) { + // note: cannot use <= 1, because TypeScript uses this to infer the type of "args". + if (args.length === 0 || args.length === 1) { + return function parameterizedDecorator( + target: Object, + propertyName: string | symbol, + propertyDescriptor?: PropertyDescriptor | undefined, + ) { + const value = args[0] ?? defaultValue; + if (value === DECORATOR_NO_DEFAULT) { + throw new Error( + `Decorator @${name} requires an argument (used on ${getPropertyName(target, propertyName)})`, + ); + } + + callback( + value, + target, + propertyName, + propertyDescriptor ?? Object.getOwnPropertyDescriptor(target, propertyName), + ); + }; + } + + if (defaultValue === DECORATOR_NO_DEFAULT) { + throw new Error( + `Decorator @${name} requires an argument (used on ${getPropertyName(args[0], args[1])})`, + ); + } + + callback( + defaultValue, + args[0], + args[1], + args[2] ?? Object.getOwnPropertyDescriptor(args[0], args[1]), + ); + + // this method only returns something if args.length === 1, but typescript doesn't understand it + return undefined as unknown as PropertyOrGetterDescriptor; + }; +} + +export function throwMustBeStaticProperty( + decoratorName: string, + target: Object, + propertyName: string | symbol, +): never { + throw new TypeError( + `Decorator @${decoratorName} has been used on ${getPropertyName(target, propertyName)}, which is an instance property. This decorator can only be used on static properties, setters and getters.`, + ); +} + +export function throwMustBeModel( + decoratorName: string, + target: Object, + propertyName: string | symbol, +): never { + throw new TypeError( + `Decorator @${decoratorName} has been used on ${getPropertyName(target, propertyName)}, but class "${getClassName(target)}" does not extend Model. This decorator can only be used on models.`, + ); +} + +export function throwMustBeInstanceProperty( + decoratorName: string, + target: Object, + propertyName: string | symbol, +): never { + throw new TypeError( + `Decorator @${decoratorName} has been used on ${getPropertyName(target, propertyName)}, which is static. This decorator can only be used on instance properties, setters and getters.`, + ); +} + +export function throwMustBeMethod( + decoratorName: string, + target: Object, + propertyName: string | symbol, +): never { + throw new TypeError( + `Decorator @${decoratorName} has been used on ${getPropertyName(target, propertyName)}, which is not a method. This decorator can only be used on methods.`, + ); +} + +export function throwMustBeAttribute( + decoratorName: string, + target: Object, + propertyName: string | symbol, +): never { + throw new TypeError( + `Decorator @${decoratorName} has been used on ${getPropertyName(target, propertyName)}, which is a symbol field. Symbol Model Attributes are not currently supported. We welcome a PR that implements this feature.`, + ); +} + +export function getPropertyName(obj: object, property: string | symbol): string { + if (typeof obj === 'function') { + return `${obj.name}.${String(property)}`; + } + + return `${obj.constructor.name}#${String(property)}`; +} + +export function getClassName(obj: object): string { + if (typeof obj === 'function') { + return obj.name; + } + + return obj.constructor.name; +} diff --git a/packages/core/src/decorators/legacy/index.mjs b/packages/core/src/decorators/legacy/index.mjs new file mode 100644 index 000000000000..6f7a3cd71da0 --- /dev/null +++ b/packages/core/src/decorators/legacy/index.mjs @@ -0,0 +1,79 @@ +import Pkg from './index.js'; + +// Model Decorators + +export const Table = Pkg.Table; + +// Attribute Decorators + +export const Column = Pkg.Column; +export const Attribute = Pkg.Attribute; +export const AllowNull = Pkg.AllowNull; +export const AutoIncrement = Pkg.AutoIncrement; +export const ColumnName = Pkg.ColumnName; +export const Comment = Pkg.Comment; +export const Default = Pkg.Default; +export const NotNull = Pkg.NotNull; +export const PrimaryKey = Pkg.PrimaryKey; +export const Unique = Pkg.Unique; +export const Index = Pkg.Index; +export const createIndexDecorator = Pkg.createIndexDecorator; + +// Built-in Attribute Decorators + +export const CreatedAt = Pkg.CreatedAt; +export const UpdatedAt = Pkg.UpdatedAt; +export const DeletedAt = Pkg.DeletedAt; +export const Version = Pkg.Version; + +// Association Decorators + +export const BelongsTo = Pkg.BelongsTo; +export const BelongsToMany = Pkg.BelongsToMany; +export const HasMany = Pkg.HasMany; +export const HasOne = Pkg.HasOne; + +// Validation Decorators + +export const ValidateAttribute = Pkg.ValidateAttribute; +export const ModelValidator = Pkg.ModelValidator; +// Other validation decorators are provided by other packages, such as @sequelize/validator.js + +// Model Hook Decorators + +export const AfterAssociate = Pkg.AfterAssociate; +export const AfterBulkCreate = Pkg.AfterBulkCreate; +export const AfterBulkDestroy = Pkg.AfterBulkDestroy; +export const AfterBulkRestore = Pkg.AfterBulkRestore; +export const AfterBulkUpdate = Pkg.AfterBulkUpdate; +export const AfterCreate = Pkg.AfterCreate; +export const AfterDestroy = Pkg.AfterDestroy; +export const AfterDestroyMany = Pkg.AfterDestroyMany; +export const AfterFind = Pkg.AfterFind; +export const AfterRestore = Pkg.AfterRestore; +export const AfterSave = Pkg.AfterSave; +export const AfterSync = Pkg.AfterSync; +export const AfterUpdate = Pkg.AfterUpdate; +export const AfterUpsert = Pkg.AfterUpsert; +export const AfterValidate = Pkg.AfterValidate; +export const BeforeAssociate = Pkg.BeforeAssociate; +export const BeforeBulkCreate = Pkg.BeforeBulkCreate; +export const BeforeBulkDestroy = Pkg.BeforeBulkDestroy; +export const BeforeBulkRestore = Pkg.BeforeBulkRestore; +export const BeforeBulkUpdate = Pkg.BeforeBulkUpdate; +export const BeforeCount = Pkg.BeforeCount; +export const BeforeCreate = Pkg.BeforeCreate; +export const BeforeDestroy = Pkg.BeforeDestroy; +export const BeforeDestroyMany = Pkg.BeforeDestroyMany; +export const BeforeFind = Pkg.BeforeFind; +export const BeforeFindAfterExpandIncludeAll = Pkg.BeforeFindAfterExpandIncludeAll; +export const BeforeFindAfterOptions = Pkg.BeforeFindAfterOptions; +export const BeforeRestore = Pkg.BeforeRestore; +export const BeforeSave = Pkg.BeforeSave; +export const BeforeSync = Pkg.BeforeSync; +export const BeforeUpdate = Pkg.BeforeUpdate; +export const BeforeUpsert = Pkg.BeforeUpsert; +export const BeforeValidate = Pkg.BeforeValidate; +export const ValidationFailed = Pkg.ValidationFailed; +export const BeforeDefinitionRefresh = Pkg.BeforeDefinitionRefresh; +export const AfterDefinitionRefresh = Pkg.AfterDefinitionRefresh; diff --git a/packages/core/src/decorators/legacy/index.ts b/packages/core/src/decorators/legacy/index.ts new file mode 100644 index 000000000000..dda4dc811d7a --- /dev/null +++ b/packages/core/src/decorators/legacy/index.ts @@ -0,0 +1,18 @@ +/** + * This package exports all decorators that are available in Sequelize, built using the legacy stage-3 decorators proposal. + * + * All available decorators can be imported as follows: + * + * ```js + * import { HasOne, Attribute } from '@sequelize/core/decorators-legacy'; + * ``` + * + * @module decorators-legacy + */ + +export { BelongsTo, BelongsToMany, HasMany, HasOne } from './associations.js'; +export * from './attribute.js'; +export * from './built-in-attributes.js'; +export * from './model-hooks.js'; +export * from './table.js'; +export * from './validation.js'; diff --git a/packages/core/src/decorators/legacy/model-hooks.ts b/packages/core/src/decorators/legacy/model-hooks.ts new file mode 100644 index 000000000000..295dc9ee258a --- /dev/null +++ b/packages/core/src/decorators/legacy/model-hooks.ts @@ -0,0 +1,114 @@ +import upperFirst from 'lodash/upperFirst.js'; +import type { ModelHooks } from '../../model-hooks.js'; +import { Model } from '../../model.js'; +import { isModelStatic } from '../../utils/model-utils.js'; +import { registerModelOptions } from '../shared/model.js'; +import { + createOptionallyParameterizedPropertyDecorator, + throwMustBeMethod, + throwMustBeModel, + throwMustBeStaticProperty, +} from './decorator-utils.js'; + +export interface HookOptions { + name?: string; +} + +/** + * Implementation for hook decorator functions. These are polymorphic. When + * called with a single argument (IHookOptions) they return a decorator + * factory function. When called with multiple arguments, they add the hook + * to the model’s metadata. + * + * @param hookType The type of hook + */ +function createHookDecorator(hookType: keyof ModelHooks) { + return createOptionallyParameterizedPropertyDecorator( + upperFirst(hookType), + undefined, + (options: HookOptions | undefined, targetModel, methodName) => { + if (typeof targetModel !== 'function') { + throwMustBeStaticProperty(upperFirst(hookType), targetModel, methodName); + } + + if (!isModelStatic(targetModel)) { + throwMustBeModel(upperFirst(hookType), targetModel, methodName); + } + + // @ts-expect-error -- implicit any, no way around it + const targetMethod: unknown = targetModel[methodName]; + if (typeof targetMethod !== 'function') { + throwMustBeMethod(upperFirst(hookType), targetModel, methodName); + } + + if (methodName in Model) { + throw new Error( + `Decorator @${upperFirst(hookType)} has been used on "${targetModel.name}.${String(methodName)}", but method ${JSON.stringify(methodName)} already exists on the base Model class and replacing it can lead to issues.`, + ); + } + + const callback = targetMethod.bind(targetModel); + + registerModelOptions(targetModel, { + hooks: { + [hookType]: options?.name ? { name: options?.name, callback } : callback, + }, + }); + }, + ); +} + +export const BeforeBulkCreate = createHookDecorator('beforeBulkCreate'); +export const AfterBulkCreate = createHookDecorator('afterBulkCreate'); + +export const BeforeBulkDestroy = createHookDecorator('beforeBulkDestroy'); +export const AfterBulkDestroy = createHookDecorator('afterBulkDestroy'); + +export const BeforeBulkRestore = createHookDecorator('beforeBulkRestore'); +export const AfterBulkRestore = createHookDecorator('afterBulkRestore'); + +export const BeforeBulkUpdate = createHookDecorator('beforeBulkUpdate'); +export const AfterBulkUpdate = createHookDecorator('afterBulkUpdate'); + +export const BeforeAssociate = createHookDecorator('beforeAssociate'); +export const AfterAssociate = createHookDecorator('afterAssociate'); + +export const BeforeCount = createHookDecorator('beforeCount'); + +export const BeforeCreate = createHookDecorator('beforeCreate'); +export const AfterCreate = createHookDecorator('afterCreate'); + +export const BeforeDestroy = createHookDecorator('beforeDestroy'); +export const AfterDestroy = createHookDecorator('afterDestroy'); + +export const BeforeDestroyMany = createHookDecorator('beforeDestroyMany'); +export const AfterDestroyMany = createHookDecorator('afterDestroyMany'); + +export const BeforeFind = createHookDecorator('beforeFind'); +export const BeforeFindAfterExpandIncludeAll = createHookDecorator( + 'beforeFindAfterExpandIncludeAll', +); +export const BeforeFindAfterOptions = createHookDecorator('beforeFindAfterOptions'); +export const AfterFind = createHookDecorator('afterFind'); + +export const BeforeRestore = createHookDecorator('beforeRestore'); +export const AfterRestore = createHookDecorator('afterRestore'); + +export const BeforeSave = createHookDecorator('beforeSave'); +export const AfterSave = createHookDecorator('afterSave'); + +export const BeforeSync = createHookDecorator('beforeSync'); +export const AfterSync = createHookDecorator('afterSync'); + +export const BeforeUpdate = createHookDecorator('beforeUpdate'); +export const AfterUpdate = createHookDecorator('afterUpdate'); + +export const BeforeUpsert = createHookDecorator('beforeUpsert'); +export const AfterUpsert = createHookDecorator('afterUpsert'); + +export const BeforeValidate = createHookDecorator('beforeValidate'); +export const AfterValidate = createHookDecorator('afterValidate'); +export const ValidationFailed = createHookDecorator('validationFailed'); + +export const BeforeDefinitionRefresh = createHookDecorator('beforeDefinitionRefresh'); +export const AfterDefinitionRefresh = createHookDecorator('afterDefinitionRefresh'); diff --git a/packages/core/src/decorators/legacy/table.ts b/packages/core/src/decorators/legacy/table.ts new file mode 100644 index 000000000000..9f5d12f2b0a1 --- /dev/null +++ b/packages/core/src/decorators/legacy/table.ts @@ -0,0 +1,65 @@ +import type { Model, ModelOptions, ModelStatic } from '../../model.js'; +import type { RegisteredModelOptions } from '../shared/model.js'; +import { registerModelOptions } from '../shared/model.js'; + +/** + * The `@Table` decorator is used to configure a model. It is used on a model class, and takes an object as parameter.
+ * Using this decorator is completely optional, you only need to use it if you want to configure one of the options of your model. + * + * @example + * ```ts + * @Table({ + * tableName: 'users', + * timestamps: false, + * }) + * class User extends Model, InferCreationAttributes> {} + * ``` + * + * @param options + */ +export function Table(options: ModelOptions): ClassDecorator; +export function Table(target: ModelStatic): void; +export function Table(arg: any): undefined | ClassDecorator { + if (typeof arg === 'function') { + annotate(arg); + + return undefined; + } + + const options: ModelOptions = { ...arg }; + + // @ts-expect-error -- making sure the option is not provided. + if (options.abstract) { + throw new Error( + '`abstract` is not a valid option for @Table. Did you mean to use @Table.Abstract?', + ); + } + + return (target: any) => annotate(target, options); +} + +function AbstractTable( + options: Omit, 'tableName' | 'name'>, +): ClassDecorator; +function AbstractTable(target: ModelStatic): void; +function AbstractTable(arg: any): undefined | ClassDecorator { + if (typeof arg === 'function') { + annotate(arg, { abstract: true }); + + return undefined; + } + + const options: ModelOptions = { ...arg, abstract: true }; + + if (options.tableName || options.name) { + throw new Error('Options "tableName" and "name" cannot be set on abstract models.'); + } + + return (target: any) => annotate(target, options); +} + +Table.Abstract = AbstractTable; + +function annotate(target: ModelStatic, options: RegisteredModelOptions = {}): void { + registerModelOptions(target, options); +} diff --git a/packages/core/src/decorators/legacy/validation.ts b/packages/core/src/decorators/legacy/validation.ts new file mode 100644 index 000000000000..e15b1a06ba07 --- /dev/null +++ b/packages/core/src/decorators/legacy/validation.ts @@ -0,0 +1,94 @@ +import type { ColumnValidateOptions, ModelOptions } from '../../model.js'; +import { isModelStatic } from '../../utils/model-utils.js'; +import { registerModelOptions } from '../shared/model.js'; +import { createRequiredAttributeOptionsDecorator } from './attribute-utils.js'; +import { + createOptionallyParameterizedPropertyDecorator, + throwMustBeMethod, + throwMustBeModel, +} from './decorator-utils.js'; + +/** + * Used to register a function that will be called when an attribute is being validated. + * + * @example + * ```ts + * class User extends Model { + * @Attribute(DataTypes.STRING) + * @ValidateAttribute({ + * myCustomValidator: () => { + * // this function will run when this attribute is validated. + * }, + * }) + * declare name: string; + * } + * ``` + * + * See also {@link ModelValidator}. + */ +export const ValidateAttribute = createRequiredAttributeOptionsDecorator( + 'ValidateAttribute', + (decoratorOption: ColumnValidateOptions) => { + return { validate: decoratorOption }; + }, +); + +/** + * Used to register a model method that will be called when an instance is being validated. + * Available as both an instance and static method (static method receives the model as a parameter). + * + * @example + * ```ts + * class User extends Model { + * @ModelValidator + * onValidate() { + * if (this.name !== VALID_NAME) { + * throw new Error(ERROR_MESSAGE); + * } + * } + * + * @ModelValidator + * static onValidate(instance) { + * if (instance.name !== VALID_NAME) { + * throw new Error(ERROR_MESSAGE); + * } + * } + * } + * ``` + * + * See also {@link ValidateAttribute}. + */ +export const ModelValidator = createOptionallyParameterizedPropertyDecorator( + 'ModelValidator', + undefined, + (decoratorOption: ModelOptions['validate'], target: Object, propertyName: string | symbol) => { + const isStatic = typeof target === 'function'; + const targetClass = isStatic ? target : target.constructor; + + if (!isModelStatic(targetClass)) { + throwMustBeModel('ModelValidator', target, propertyName); + } + + // @ts-expect-error -- it's normal to get any here + const property = target[propertyName]; + if (typeof property !== 'function') { + throwMustBeMethod('ModelValidator', target, propertyName); + } + + const validator = isStatic + ? function validate() { + // When registered as a static method, the model is passed as the first parameter, and the context ("this") must be the class + /* eslint-disable @typescript-eslint/no-invalid-this */ + // @ts-expect-error -- description above ^ + property.call(target, this); + /* eslint-enable @typescript-eslint/no-invalid-this */ + } + : property; + + registerModelOptions(targetClass, { + validate: { + [propertyName]: validator, + }, + }); + }, +); diff --git a/packages/core/src/decorators/shared/model.ts b/packages/core/src/decorators/shared/model.ts new file mode 100644 index 000000000000..c2e90680f406 --- /dev/null +++ b/packages/core/src/decorators/shared/model.ts @@ -0,0 +1,262 @@ +import { EMPTY_OBJECT } from '@sequelize/utils'; +import { cloneDataType } from '../../abstract-dialect/data-types-utils.js'; +import { BaseError } from '../../errors/base-error.js'; +import { mergeModelOptions } from '../../model-definition.js'; +import { initModel } from '../../model-typescript.js'; +import type { AttributeOptions, ModelAttributes, ModelOptions, ModelStatic } from '../../model.js'; +import type { Sequelize } from '../../sequelize.js'; +import { isModelStatic } from '../../utils/model-utils.js'; +import { cloneDeep, getAllOwnEntries } from '../../utils/object.js'; +import type { InheritedAttributeOptions } from '../legacy/attribute.js'; + +export interface RegisteredModelOptions extends ModelOptions { + /** + * Abstract models cannot be used directly, or registered. + * They exist only to be extended by other models. + */ + abstract?: boolean; +} + +export interface RegisteredAttributeOptions { + [key: string]: InheritedAttributeOptions; +} + +interface RegisteredOptions { + model: RegisteredModelOptions; + attributes: RegisteredAttributeOptions; +} + +const registeredOptions = new WeakMap(); + +/** + * Registers model options for future registering of the Model using Model.init + * Subsequent calls for the same model & attributeName will be merged, with the newer call taking precedence. + * 'sequelize' option is not accepted here. Pass it through `Model.init` when registering the model. + * + * @param model + * @param options + */ +export function registerModelOptions(model: ModelStatic, options: RegisteredModelOptions): void { + if (!registeredOptions.has(model)) { + registeredOptions.set(model, { model: options, attributes: {} }); + + return; + } + + // merge-able: scopes, indexes + const existingModelOptions = registeredOptions.get(model)!.model; + + try { + mergeModelOptions(existingModelOptions, options, false); + } catch (error) { + throw new BaseError( + `Multiple decorators are trying to register conflicting options on model ${model.name}`, + { cause: error }, + ); + } +} + +/** + * Registers attribute options for future registering of the Model using Model.init + * Subsequent calls for the same model & attributeName will be merged, with the newer call taking precedence. + * + * @param model + * @param attributeName + * @param options + */ +export function registerModelAttributeOptions( + model: ModelStatic, + attributeName: string, + options: Partial, +): void { + if (!registeredOptions.has(model)) { + registeredOptions.set(model, { + model: {}, + attributes: { + [attributeName]: options, + }, + }); + + return; + } + + const existingAttributesOptions = registeredOptions.get(model)!.attributes; + if (!(attributeName in existingAttributesOptions)) { + existingAttributesOptions[attributeName] = options; + + return; + } + + const existingOptions = existingAttributesOptions[attributeName]; + + mergeAttributeOptions(attributeName, model, existingOptions, options, false); +} + +export function mergeAttributeOptions( + attributeName: string, + model: ModelStatic, + existingOptions: Partial, + options: Partial, + overrideOnConflict: boolean, +): Partial { + for (const [optionName, optionValue] of Object.entries(options) as Array< + [keyof AttributeOptions, any] + >) { + if (existingOptions[optionName] === undefined) { + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- This error only occurs on TS 5.3+ + // @ts-ignore -- this function is very fuzzy in terms of typing due to how generic it is. + existingOptions[optionName] = optionValue; + continue; + } + + // These are objects. We merge their properties, unless the same key is used in both values. + if (optionName === 'validate') { + for (const [subOptionName, subOptionValue] of getAllOwnEntries(optionValue)) { + if (subOptionName in existingOptions[optionName] && !overrideOnConflict) { + throw new Error( + `Multiple decorators are attempting to register option ${optionName}[${JSON.stringify(subOptionName)}] of attribute ${attributeName} on model ${model.name}.`, + ); + } + + // @ts-expect-error -- runtime type checking is enforced by model + existingOptions[optionName][subOptionName] = subOptionValue; + } + + continue; + } + + if (optionName === 'index' || optionName === 'unique') { + if (!existingOptions[optionName]) { + existingOptions[optionName] = []; + } else if (!Array.isArray(existingOptions[optionName])) { + // @ts-expect-error -- runtime type checking is enforced by model + existingOptions[optionName] = [existingOptions[optionName]]; + } + + if (Array.isArray(optionValue)) { + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- became valid in TS 5.8 + // @ts-ignore -- runtime type checking is enforced by model + existingOptions[optionName] = [...existingOptions[optionName], ...optionValue]; + } else { + existingOptions[optionName] = [...existingOptions[optionName], optionValue]; + } + + continue; + } + + if (optionValue === existingOptions[optionName] || overrideOnConflict) { + continue; + } + + throw new Error( + `Multiple decorators are attempting to set different values for the option ${optionName} of attribute ${attributeName} on model ${model.name}.`, + ); + } + + return existingOptions; +} + +export function initDecoratedModel(model: ModelStatic, sequelize: Sequelize): boolean { + const isAbstract = registeredOptions.get(model)?.model.abstract; + + if (isAbstract) { + return false; + } + + const modelOptions = getRegisteredModelOptions(model); + const attributeOptions = getRegisteredAttributeOptions(model); + + initModel(model, attributeOptions as ModelAttributes, { + ...modelOptions, + sequelize, + }); + + return true; +} + +const NON_INHERITABLE_MODEL_OPTIONS = ['modelName', 'name', 'tableName'] as const; + +function getRegisteredModelOptions(model: ModelStatic): ModelOptions { + const modelOptions = registeredOptions.get(model)?.model ?? (EMPTY_OBJECT as ModelOptions); + + const parentModel = Object.getPrototypeOf(model); + if (isModelStatic(parentModel)) { + const parentModelOptions: ModelOptions = { ...getRegisteredModelOptions(parentModel) }; + + for (const nonInheritableOption of NON_INHERITABLE_MODEL_OPTIONS) { + delete parentModelOptions[nonInheritableOption]; + } + + // options that must be cloned + parentModelOptions.indexes = cloneDeep(parentModelOptions.indexes); + parentModelOptions.defaultScope = cloneDeep(parentModelOptions.defaultScope); + parentModelOptions.scopes = cloneDeep(parentModelOptions.scopes); + parentModelOptions.validate = cloneDeep(parentModelOptions.validate); + parentModelOptions.hooks = cloneDeep(parentModelOptions.hooks); + + return mergeModelOptions(parentModelOptions, modelOptions, true); + } + + return modelOptions; +} + +function getRegisteredAttributeOptions(model: ModelStatic): RegisteredAttributeOptions { + const descendantAttributes: RegisteredAttributeOptions = { + ...(registeredOptions.get(model)?.attributes ?? EMPTY_OBJECT), + }; + const insertAfterAttributes: RegisteredAttributeOptions = {}; + const insertBeforeAttributes: RegisteredAttributeOptions = {}; + + const parentModel = Object.getPrototypeOf(model); + if (isModelStatic(parentModel)) { + const parentAttributes: RegisteredAttributeOptions = getRegisteredAttributeOptions(parentModel); + + for (const attributeName of Object.keys(parentAttributes)) { + const parentAttribute = { ...parentAttributes[attributeName] }; + if (parentAttribute.insertBefore && parentAttribute.insertAfter) { + throw new Error( + `Attribute ${attributeName} on model ${model.name} cannot have both 'insertBefore' and 'insertAfter' set to true.`, + ); + } + + if (parentAttribute.type) { + if (typeof parentAttribute.type === 'function') { + parentAttribute.type = new parentAttribute.type(); + } else { + parentAttribute.type = cloneDataType(parentAttribute.type); + } + } + + // options that must be cloned + parentAttribute.unique = cloneDeep(parentAttribute.unique); + parentAttribute.index = cloneDeep(parentAttribute.index); + parentAttribute.references = cloneDeep(parentAttribute.references); + parentAttribute.validate = cloneDeep(parentAttribute.validate); + + const descendantAttribute = descendantAttributes[attributeName]; + if (descendantAttribute) { + descendantAttributes[attributeName] = mergeAttributeOptions( + attributeName, + model, + parentAttribute, + descendantAttribute, + true, + ); + } else if (parentAttribute.insertBefore) { + insertBeforeAttributes[attributeName] = parentAttribute; + } else { + insertAfterAttributes[attributeName] = parentAttribute; + } + } + } + + return { + ...insertBeforeAttributes, + ...descendantAttributes, + ...insertAfterAttributes, + }; +} + +export function isDecoratedModel(model: ModelStatic): boolean { + return registeredOptions.has(model); +} diff --git a/packages/core/src/deferrable.ts b/packages/core/src/deferrable.ts new file mode 100644 index 000000000000..bcccbdb6127d --- /dev/null +++ b/packages/core/src/deferrable.ts @@ -0,0 +1,114 @@ +import { EMPTY_ARRAY } from '@sequelize/utils'; +import isEqual from 'lodash/isEqual'; +import { classToInvokable } from './utils/class-to-invokable.js'; + +/** + * Can be used to make foreign key constraints deferrable. + * This is only supported in PostgreSQL. + * + * The foreign keys can be configured like this. It will create a foreign key + * that will check the constraints immediately when the data was inserted. + * + * ```js + * class MyModel extends Model {} + * MyModel.init({ + * foreign_id: { + * type: DataTypes.INTEGER, + * references: { + * model: OtherModel, + * key: 'id', + * deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE + * } + * } + * }, { sequelize }); + * ``` + */ +export enum Deferrable { + INITIALLY_DEFERRED = 'INITIALLY_DEFERRED', + INITIALLY_IMMEDIATE = 'INITIALLY_IMMEDIATE', + NOT = 'NOT', +} + +/** + * Can be used to set constraints deferrable within a transaction. + * This is only supported in PostgreSQL. + * + * The constraints can be configured to be deferrable in a transaction like this. + * It will trigger a query once the transaction has been started and set the constraints + * to be checked at the very end of the transaction. + * + * ```js + * sequelize.transaction({ + * constraintChecking: Sequelize.ConstraintChecking.DEFERRED + * }); + * ``` + */ +export class ConstraintChecking { + toString() { + return this.constructor.name; + } + + isEqual(_other: unknown): boolean { + throw new Error('isEqual implementation missing'); + } + + static toString() { + return this.name; + } + + get constraints(): readonly string[] { + throw new Error('constraints getter implementation missing'); + } + + /** + * Will trigger an additional query at the beginning of a + * transaction which sets the constraints to deferred. + */ + static readonly DEFERRED = classToInvokable( + class DEFERRED extends ConstraintChecking { + readonly #constraints: readonly string[]; + + /** + * @param constraints An array of constraint names. Will defer all constraints by default. + */ + constructor(constraints: readonly string[] = EMPTY_ARRAY) { + super(); + this.#constraints = Object.freeze([...constraints]); + } + + isEqual(other: unknown): boolean { + return other instanceof DEFERRED && isEqual(this.#constraints, other.#constraints); + } + + get constraints(): readonly string[] { + return this.#constraints; + } + }, + ); + + /** + * Will trigger an additional query at the beginning of a + * transaction which sets the constraints to immediately. + */ + static readonly IMMEDIATE = classToInvokable( + class IMMEDIATE extends ConstraintChecking { + readonly #constraints: readonly string[]; + + /** + * @param constraints An array of constraint names. Will defer all constraints by default. + */ + constructor(constraints: readonly string[] = EMPTY_ARRAY) { + super(); + this.#constraints = Object.freeze([...constraints]); + } + + isEqual(other: unknown): boolean { + return other instanceof IMMEDIATE && isEqual(this.#constraints, other.#constraints); + } + + get constraints(): readonly string[] { + return this.#constraints; + } + }, + ); +} diff --git a/packages/core/src/enums.ts b/packages/core/src/enums.ts new file mode 100644 index 000000000000..b7da162a6389 --- /dev/null +++ b/packages/core/src/enums.ts @@ -0,0 +1,82 @@ +/** + * An enum of index hints to be used in mysql for querying with index hints + * + * @property USE + * @property FORCE + * @property IGNORE + */ +export enum IndexHints { + USE = 'USE', + FORCE = 'FORCE', + IGNORE = 'IGNORE', +} + +/** + * An enum of operators to be used in {@link Sequelize#queryGenerator} methods. + * + * @property BIND + * @property REPLACEMENT + */ +export enum ParameterStyle { + /** + * The parameter will be added to the query as a bind parameter. + */ + BIND = 'BIND', + /** + * The parameter will be replaced directly into the query. + */ + REPLACEMENT = 'REPLACEMENT', +} + +/** + * An enum of query types used by {@link Sequelize#query}. + */ +export enum QueryTypes { + SELECT = 'SELECT', + INSERT = 'INSERT', + UPDATE = 'UPDATE', + BULKUPDATE = 'BULKUPDATE', + DELETE = 'DELETE', + UPSERT = 'UPSERT', + SHOWINDEXES = 'SHOWINDEXES', + DESCRIBE = 'DESCRIBE', + RAW = 'RAW', + SHOWCONSTRAINTS = 'SHOWCONSTRAINTS', +} + +/** + * An enum of table hints to be used in mssql for querying with table hints + * + * @property NOLOCK + * @property READUNCOMMITTED + * @property UPDLOCK + * @property REPEATABLEREAD + * @property SERIALIZABLE + * @property READCOMMITTED + * @property TABLOCK + * @property TABLOCKX + * @property PAGLOCK + * @property ROWLOCK + * @property NOWAIT + * @property READPAST + * @property XLOCK + * @property SNAPSHOT + * @property NOEXPAND + */ +export enum TableHints { + NOLOCK = 'NOLOCK', + READUNCOMMITTED = 'READUNCOMMITTED', + UPDLOCK = 'UPDLOCK', + REPEATABLEREAD = 'REPEATABLEREAD', + SERIALIZABLE = 'SERIALIZABLE', + READCOMMITTED = 'READCOMMITTED', + TABLOCK = 'TABLOCK', + TABLOCKX = 'TABLOCKX', + PAGLOCK = 'PAGLOCK', + ROWLOCK = 'ROWLOCK', + NOWAIT = 'NOWAIT', + READPAST = 'READPAST', + XLOCK = 'XLOCK', + SNAPSHOT = 'SNAPSHOT', + NOEXPAND = 'NOEXPAND', +} diff --git a/packages/core/src/errors/aggregate-error.ts b/packages/core/src/errors/aggregate-error.ts new file mode 100644 index 000000000000..60dfbc68d330 --- /dev/null +++ b/packages/core/src/errors/aggregate-error.ts @@ -0,0 +1,31 @@ +import { BaseError } from './base-error'; + +/** + * A wrapper for multiple Errors + * + * @param errors The aggregated errors that occurred + */ +export class AggregateError extends BaseError { + /** the aggregated errors that occurred */ + readonly errors: Array; + + constructor(errors: Array) { + super(); + this.errors = errors; + this.name = 'AggregateError'; + } + + toString(): string { + const message = `AggregateError of:\n${this.errors + .map((error: Error | AggregateError) => { + return error === this + ? '[Circular AggregateError]' + : error instanceof AggregateError + ? String(error).replace(/\n$/, '').replaceAll(/^/gm, ' ') + : String(error).replaceAll(/^/gm, ' ').slice(2); + }) + .join('\n')}\n`; + + return message; + } +} diff --git a/packages/core/src/errors/association-error.ts b/packages/core/src/errors/association-error.ts new file mode 100644 index 000000000000..b7aabcdbd53a --- /dev/null +++ b/packages/core/src/errors/association-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Thrown when an association is improperly constructed (see message for details) + */ +export class AssociationError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeAssociationError'; + } +} diff --git a/packages/core/src/errors/base-error.ts b/packages/core/src/errors/base-error.ts new file mode 100644 index 000000000000..5917dfc12a91 --- /dev/null +++ b/packages/core/src/errors/base-error.ts @@ -0,0 +1,38 @@ +import { useErrorCause } from '../utils/deprecations.js'; + +export interface CommonErrorProperties { + /** The SQL that triggered the error */ + readonly sql: string; +} + +/** + * The Base Error all Sequelize Errors inherit from. + * + * Sequelize provides a host of custom error classes, to allow you to do easier debugging. + * All of these errors are exported by the `@sequelize/core` package. + * All sequelize errors inherit from the base JS error object. + */ +export class BaseError extends Error { + /** + * @deprecated use {@link cause}. + */ + get parent(): this['cause'] { + useErrorCause(); + + return this.cause; + } + + /** + * @deprecated use {@link cause}. + */ + get original(): this['cause'] { + useErrorCause(); + + return this.cause; + } + + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeBaseError'; + } +} diff --git a/packages/core/src/errors/bulk-record-error.ts b/packages/core/src/errors/bulk-record-error.ts new file mode 100644 index 000000000000..692f8e62f8eb --- /dev/null +++ b/packages/core/src/errors/bulk-record-error.ts @@ -0,0 +1,21 @@ +import type { Model } from '..'; +import { BaseError } from './base-error'; + +/** + * Thrown when bulk operation fails, it represents per record level error. + * Used with AggregateError + * + * @param error Error for a given record/instance + * @param record DAO instance that error belongs to + */ +export class BulkRecordError extends BaseError { + errors: Error; + record: Model; + + constructor(error: Error, record: Model, options?: ErrorOptions) { + super(error.message, options); + this.name = 'SequelizeBulkRecordError'; + this.errors = error; + this.record = record; + } +} diff --git a/packages/core/src/errors/connection-error.ts b/packages/core/src/errors/connection-error.ts new file mode 100644 index 000000000000..987dc2d58e59 --- /dev/null +++ b/packages/core/src/errors/connection-error.ts @@ -0,0 +1,13 @@ +import { BaseError } from './base-error'; + +/** + * A base class for all connection-related errors. + * + * The connection-specific error which triggered this one is available as {@link BaseError#cause} + */ +export class ConnectionError extends BaseError { + constructor(parent?: Error) { + super(parent ? parent.message : '', { cause: parent }); + this.name = 'SequelizeConnectionError'; + } +} diff --git a/packages/core/src/errors/connection/access-denied-error.ts b/packages/core/src/errors/connection/access-denied-error.ts new file mode 100644 index 000000000000..eb9e579d7627 --- /dev/null +++ b/packages/core/src/errors/connection/access-denied-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database is refused due to insufficient privileges + */ +export class AccessDeniedError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeAccessDeniedError'; + } +} diff --git a/packages/core/src/errors/connection/connection-acquire-timeout-error.ts b/packages/core/src/errors/connection/connection-acquire-timeout-error.ts new file mode 100644 index 000000000000..ed8dc7d357c6 --- /dev/null +++ b/packages/core/src/errors/connection/connection-acquire-timeout-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when acquiring a connection from the Sequelize Pool times out. + */ +export class ConnectionAcquireTimeoutError extends ConnectionError { + constructor(_message: string, cause: Error) { + super(cause); + this.name = 'SequelizeConnectionAcquireTimeoutError'; + } +} diff --git a/packages/core/src/errors/connection/connection-refused-error.ts b/packages/core/src/errors/connection/connection-refused-error.ts new file mode 100644 index 000000000000..d59dd49b89dc --- /dev/null +++ b/packages/core/src/errors/connection/connection-refused-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database is refused + */ +export class ConnectionRefusedError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeConnectionRefusedError'; + } +} diff --git a/packages/core/src/errors/connection/connection-timed-out-error.ts b/packages/core/src/errors/connection/connection-timed-out-error.ts new file mode 100644 index 000000000000..4c25931ed44f --- /dev/null +++ b/packages/core/src/errors/connection/connection-timed-out-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database times out + */ +export class ConnectionTimedOutError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeConnectionTimedOutError'; + } +} diff --git a/packages/core/src/errors/connection/host-not-found-error.ts b/packages/core/src/errors/connection/host-not-found-error.ts new file mode 100644 index 000000000000..c2db9369e456 --- /dev/null +++ b/packages/core/src/errors/connection/host-not-found-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database has a hostname that was not found + */ +export class HostNotFoundError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeHostNotFoundError'; + } +} diff --git a/packages/core/src/errors/connection/host-not-reachable-error.ts b/packages/core/src/errors/connection/host-not-reachable-error.ts new file mode 100644 index 000000000000..733219448cc3 --- /dev/null +++ b/packages/core/src/errors/connection/host-not-reachable-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database has a hostname that was not reachable + */ +export class HostNotReachableError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeHostNotReachableError'; + } +} diff --git a/packages/core/src/errors/connection/invalid-connection-error.ts b/packages/core/src/errors/connection/invalid-connection-error.ts new file mode 100644 index 000000000000..fce79fff76d0 --- /dev/null +++ b/packages/core/src/errors/connection/invalid-connection-error.ts @@ -0,0 +1,11 @@ +import { ConnectionError } from '../connection-error'; + +/** + * Thrown when a connection to a database has invalid values for any of the connection parameters + */ +export class InvalidConnectionError extends ConnectionError { + constructor(cause: Error) { + super(cause); + this.name = 'SequelizeInvalidConnectionError'; + } +} diff --git a/packages/core/src/errors/database-error.ts b/packages/core/src/errors/database-error.ts new file mode 100644 index 000000000000..398b355452e4 --- /dev/null +++ b/packages/core/src/errors/database-error.ts @@ -0,0 +1,38 @@ +import type { CommonErrorProperties } from './base-error'; +import { BaseError } from './base-error'; + +export interface DatabaseErrorParent extends Error, Pick { + /** The parameters for the sql that triggered the error */ + readonly parameters?: object; +} + +export interface DatabaseErrorSubclassOptions { + cause?: DatabaseErrorParent; + + /** + * @deprecated use {@link DatabaseErrorSubclassOptions.cause} + */ + parent?: DatabaseErrorParent; + message?: string; +} + +/** + * A base class for all database-related errors. + */ +export class DatabaseError extends BaseError implements DatabaseErrorParent, CommonErrorProperties { + sql: string; + parameters: object; + + declare cause: DatabaseErrorParent; + + /** + * @param parent The database-specific error which triggered this one + */ + constructor(parent: DatabaseErrorParent) { + super(parent.message, { cause: parent }); + this.name = 'SequelizeDatabaseError'; + + this.sql = parent.sql; + this.parameters = parent.parameters ?? {}; + } +} diff --git a/packages/core/src/errors/database/exclusion-constraint-error.ts b/packages/core/src/errors/database/exclusion-constraint-error.ts new file mode 100644 index 000000000000..952af8e9583a --- /dev/null +++ b/packages/core/src/errors/database/exclusion-constraint-error.ts @@ -0,0 +1,33 @@ +import { useErrorCause } from '../../utils/deprecations.js'; +import type { DatabaseErrorSubclassOptions } from '../database-error'; +import { DatabaseError } from '../database-error'; + +interface ExclusionConstraintErrorOptions { + constraint?: string; + fields?: Record; + table?: string; +} + +/** + * Thrown when an exclusion constraint is violated in the database + */ +export class ExclusionConstraintError extends DatabaseError { + constraint: string | undefined; + fields: Record | undefined; + table: string | undefined; + + constructor(options: DatabaseErrorSubclassOptions & ExclusionConstraintErrorOptions = {}) { + if ('parent' in options) { + useErrorCause(); + } + + const parent = options.cause ?? options.parent ?? { sql: '', name: '', message: '' }; + + super(parent); + this.message = options.message || parent.message; + this.name = 'SequelizeExclusionConstraintError'; + this.constraint = options.constraint; + this.fields = options.fields; + this.table = options.table; + } +} diff --git a/packages/core/src/errors/database/foreign-key-constraint-error.ts b/packages/core/src/errors/database/foreign-key-constraint-error.ts new file mode 100644 index 000000000000..2718d3d40ceb --- /dev/null +++ b/packages/core/src/errors/database/foreign-key-constraint-error.ts @@ -0,0 +1,43 @@ +import { useErrorCause } from '../../utils/deprecations.js'; +import type { DatabaseErrorSubclassOptions } from '../database-error'; +import { DatabaseError } from '../database-error'; + +export enum RelationshipType { + parent = 'parent', + child = 'child', +} + +interface ForeignKeyConstraintErrorOptions { + table?: string; + fields?: { [field: string]: string }; + value?: unknown; + index?: string; + reltype?: RelationshipType; +} + +/** + * Thrown when a foreign key constraint is violated in the database + */ +export class ForeignKeyConstraintError extends DatabaseError { + table: string | undefined; + fields: { [field: string]: string } | undefined; + value: unknown; + index: string | undefined; + reltype: RelationshipType | undefined; + + constructor(options: ForeignKeyConstraintErrorOptions & DatabaseErrorSubclassOptions = {}) { + if ('parent' in options) { + useErrorCause(); + } + + const parent = options.cause ?? options.parent ?? { sql: '', name: '', message: '' }; + + super(parent); + this.name = 'SequelizeForeignKeyConstraintError'; + this.fields = options.fields; + this.table = options.table; + this.value = options.value; + this.index = options.index; + this.reltype = options.reltype; + } +} diff --git a/packages/core/src/errors/database/timeout-error.ts b/packages/core/src/errors/database/timeout-error.ts new file mode 100644 index 000000000000..20dec2691394 --- /dev/null +++ b/packages/core/src/errors/database/timeout-error.ts @@ -0,0 +1,12 @@ +import type { DatabaseErrorParent } from '../database-error'; +import { DatabaseError } from '../database-error'; + +/** + * Thrown when a database query times out because of a deadlock + */ +export class TimeoutError extends DatabaseError { + constructor(parent: DatabaseErrorParent) { + super(parent); + this.name = 'SequelizeTimeoutError'; + } +} diff --git a/packages/core/src/errors/database/unknown-constraint-error.ts b/packages/core/src/errors/database/unknown-constraint-error.ts new file mode 100644 index 000000000000..f7ab5bdef9e5 --- /dev/null +++ b/packages/core/src/errors/database/unknown-constraint-error.ts @@ -0,0 +1,32 @@ +import { useErrorCause } from '../../utils/deprecations.js'; +import type { DatabaseErrorSubclassOptions } from '../database-error'; +import { DatabaseError } from '../database-error'; + +interface UnknownConstraintErrorOptions { + constraint?: string; + fields?: Record; + table?: string; +} + +/** + * Thrown when constraint name is not found in the database + */ +export class UnknownConstraintError extends DatabaseError { + constraint: string | undefined; + fields: Record | undefined; + table: string | undefined; + + constructor(options: UnknownConstraintErrorOptions & DatabaseErrorSubclassOptions = {}) { + if ('parent' in options) { + useErrorCause(); + } + + const parent = options.cause ?? options.parent ?? { sql: '', name: '', message: '' }; + + super(parent); + this.name = 'SequelizeUnknownConstraintError'; + this.constraint = options.constraint; + this.fields = options.fields; + this.table = options.table; + } +} diff --git a/packages/core/src/errors/eager-loading-error.ts b/packages/core/src/errors/eager-loading-error.ts new file mode 100644 index 000000000000..7cf6a7b79891 --- /dev/null +++ b/packages/core/src/errors/eager-loading-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Thrown when an include statement is improperly constructed (see message for details) + */ +export class EagerLoadingError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeEagerLoadingError'; + } +} diff --git a/packages/core/src/errors/empty-result-error.ts b/packages/core/src/errors/empty-result-error.ts new file mode 100644 index 000000000000..1cd82c251f4e --- /dev/null +++ b/packages/core/src/errors/empty-result-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Thrown when a record was not found, Usually used with rejectOnEmpty mode (see message for details) + */ +export class EmptyResultError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeEmptyResultError'; + } +} diff --git a/packages/core/src/errors/index.ts b/packages/core/src/errors/index.ts new file mode 100644 index 000000000000..734764811647 --- /dev/null +++ b/packages/core/src/errors/index.ts @@ -0,0 +1,30 @@ +export { AggregateError } from './aggregate-error'; +export { AssociationError } from './association-error'; +export { BaseError } from './base-error'; +export { BulkRecordError } from './bulk-record-error'; +export { ConnectionError } from './connection-error'; +export { AccessDeniedError } from './connection/access-denied-error'; +export { ConnectionAcquireTimeoutError } from './connection/connection-acquire-timeout-error'; +export { ConnectionRefusedError } from './connection/connection-refused-error'; +export { ConnectionTimedOutError } from './connection/connection-timed-out-error'; +export { HostNotFoundError } from './connection/host-not-found-error'; +export { HostNotReachableError } from './connection/host-not-reachable-error'; +export { InvalidConnectionError } from './connection/invalid-connection-error'; +export { DatabaseError } from './database-error'; +export { ExclusionConstraintError } from './database/exclusion-constraint-error'; +export { ForeignKeyConstraintError } from './database/foreign-key-constraint-error'; +export { TimeoutError } from './database/timeout-error'; +export { UnknownConstraintError } from './database/unknown-constraint-error'; +export { EagerLoadingError } from './eager-loading-error'; +export { EmptyResultError } from './empty-result-error'; +export { InstanceError } from './instance-error'; +export { OptimisticLockError } from './optimistic-lock-error'; +export { QueryError } from './query-error'; +export { SequelizeScopeError } from './sequelize-scope-error'; +export { + ValidationError, + ValidationErrorItem, + ValidationErrorItemOrigin, + ValidationErrorItemType, +} from './validation-error'; +export { UniqueConstraintError } from './validation/unique-constraint-error'; diff --git a/packages/core/src/errors/instance-error.ts b/packages/core/src/errors/instance-error.ts new file mode 100644 index 000000000000..89185a17cc05 --- /dev/null +++ b/packages/core/src/errors/instance-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Thrown when a some problem occurred with Instance methods (see message for details) + */ +export class InstanceError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeInstanceError'; + } +} diff --git a/packages/core/src/errors/optimistic-lock-error.ts b/packages/core/src/errors/optimistic-lock-error.ts new file mode 100644 index 000000000000..e10d9d3b3dc7 --- /dev/null +++ b/packages/core/src/errors/optimistic-lock-error.ts @@ -0,0 +1,32 @@ +import { BaseError } from './base-error'; + +interface OptimisticLockErrorOptions { + message?: string; + + /** The name of the model on which the update was attempted */ + modelName?: string; + + /** The values of the attempted update */ + values?: Record; + where?: Record; +} + +/** + * Thrown when attempting to update a stale model instance + */ +export class OptimisticLockError extends BaseError { + modelName: string | undefined; + values: Record | undefined; + where: Record | undefined; + + constructor(options?: OptimisticLockErrorOptions, errorOptions?: ErrorOptions) { + const message = + options?.message || `Attempting to update a stale model instance: ${options?.modelName}`; + + super(message, errorOptions); + this.name = 'SequelizeOptimisticLockError'; + this.modelName = options?.modelName; + this.values = options?.values; + this.where = options?.where; + } +} diff --git a/packages/core/src/errors/query-error.ts b/packages/core/src/errors/query-error.ts new file mode 100644 index 000000000000..04917a5adf3b --- /dev/null +++ b/packages/core/src/errors/query-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Thrown when a query is passed invalid options (see message for details) + */ +export class QueryError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeQueryError'; + } +} diff --git a/packages/core/src/errors/sequelize-scope-error.ts b/packages/core/src/errors/sequelize-scope-error.ts new file mode 100644 index 000000000000..e5a88ac56d16 --- /dev/null +++ b/packages/core/src/errors/sequelize-scope-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from './base-error'; + +/** + * Scope Error. Thrown when Sequelize cannot query the specified scope. + */ +export class SequelizeScopeError extends BaseError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'SequelizeScopeError'; + } +} diff --git a/packages/core/src/errors/validation-error.ts b/packages/core/src/errors/validation-error.ts new file mode 100644 index 000000000000..4dbda943c3ed --- /dev/null +++ b/packages/core/src/errors/validation-error.ts @@ -0,0 +1,243 @@ +import type { Model } from '..'; +import { BaseError } from './base-error'; + +/** + * An enum that is used internally by the `ValidationErrorItem` class + * that maps current `type` strings (as given to ValidationErrorItem.constructor()) to + * our new `origin` values. + */ +export enum ValidationErrorItemType { + 'notNull violation' = 'CORE', + 'unique violation' = 'DB', + 'Validation error' = 'FUNCTION', +} + +/** + * An enum that defines valid ValidationErrorItem `origin` values + */ +export enum ValidationErrorItemOrigin { + /** + * specifies errors that originate from the sequelize "core" + */ + CORE = 'CORE', + + /** + * specifies validation errors that originate from the storage engine + */ + DB = 'DB', + + /** + * specifies validation errors that originate from validator functions (both built-in and custom) defined for a given attribute + */ + FUNCTION = 'FUNCTION', + + /** + * specifies validation errors that originate from {@link DataTypes.ABSTRACT#validate} constraint validation. + */ + DATATYPE = 'DATATYPE', +} + +/** + * Validation Error Item + * Instances of this class are included in the `ValidationError.errors` property. + */ +export class ValidationErrorItem extends Error { + /** + * @deprecated Will be removed in v7 + */ + static TypeStringMap = ValidationErrorItemType; + + /** + * @deprecated Will be removed in v7 + */ + static Origins = ValidationErrorItemOrigin; + + /** + * The type/origin of the validation error + */ + readonly type: keyof typeof ValidationErrorItemType | null; + + /** + * The field that triggered the validation error + */ + path: string | null; + + /** + * The value that generated the error + */ + value: unknown; + + readonly origin: keyof typeof ValidationErrorItemOrigin | null; + + /** + * The DAO instance that caused the validation error + */ + instance: Model | null; + + /** + * A validation "key", used for identification + */ + validatorKey: string | null; + + /** + * Property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable + */ + validatorName: string | null; + + /** + * Parameters used with the BUILT-IN validator function, if applicable + */ + readonly validatorArgs: unknown[]; + + static throwDataTypeValidationError(message: string): never { + throw new ValidationErrorItem(message, 'Validation error', ValidationErrorItemOrigin.DATATYPE); + } + + /** + * 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/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: keyof typeof ValidationErrorItemType | keyof typeof ValidationErrorItemOrigin, + path?: string, + value?: string, + instance?: Model, + validatorKey?: string, + fnName?: string, + fnArgs?: unknown[], + ) { + super(message); + + this.type = null; + this.path = path || null; + + this.value = value ?? null; + + this.origin = null; + + this.instance = instance || null; + + this.validatorKey = validatorKey || null; + + this.validatorName = fnName || null; + + this.validatorArgs = fnArgs || []; + + if (type) { + if (this.isValidationErrorItemOrigin(type)) { + this.origin = type; + } else { + const realType = ValidationErrorItemType[type]; + + if (realType && ValidationErrorItemOrigin[realType]) { + this.origin = realType; + this.type = type; + } + } + } + + // This doesn't need captureStackTrace because it's not a subclass of Error + } + + private isValidationErrorItemOrigin( + origin: keyof typeof ValidationErrorItemOrigin | keyof typeof ValidationErrorItemType, + ): origin is keyof typeof ValidationErrorItemOrigin { + return ( + ValidationErrorItemOrigin[origin as keyof typeof ValidationErrorItemOrigin] !== undefined + ); + } + + /** + * return a lowercase, trimmed string "key" that identifies the validator. + * + * Note: the string will be empty if the instance has neither a valid `validatorKey` property nor a valid `validatorName` property + * + * @param useTypeAsNS controls whether the returned value is "namespace", + * this parameter is ignored if the validator's `type` is not one of ValidationErrorItem.Origins + * @throws {Error} thrown if NSSeparator is found to be invalid. + */ + getValidatorKey(useTypeAsNS: false): string; + + /** + * @param useTypeAsNS controls whether the returned value is "namespace", + * this parameter is ignored if the validator's `type` is not one of ValidationErrorItem.Origins + * @param NSSeparator a separator string for concatenating the namespace, must be not be empty, + * defaults to "." (fullstop). only used and validated if useTypeAsNS is TRUE. + */ + getValidatorKey(useTypeAsNS?: true, NSSeparator?: string): string; + getValidatorKey(useTypeAsNS: boolean = true, NSSeparator: string = '.'): string { + const useTANS = useTypeAsNS === undefined || Boolean(useTypeAsNS); + + const type = this.origin; + const key = this.validatorKey || this.validatorName; + const useNS = useTANS && type && ValidationErrorItemOrigin[type]; + + if (useNS && (typeof NSSeparator !== 'string' || NSSeparator.length === 0)) { + throw new Error('Invalid namespace separator given, must be a non-empty string'); + } + + if (!(typeof key === 'string' && key.length > 0)) { + return ''; + } + + return (useNS ? [this.origin, key].join(NSSeparator) : key).toLowerCase().trim(); + } +} + +/** + * Validation Error. Thrown when the sequelize validation has failed. The error contains an `errors` property, + * which is an array with 1 or more ValidationErrorItems, one for each validation that failed. + * + * @param message Error message + * @param errors Array of ValidationErrorItem objects describing the validation errors + */ +export class ValidationError extends BaseError { + /** Array of ValidationErrorItem objects describing the validation errors */ + readonly errors: ValidationErrorItem[]; + + constructor(message: string, errors: ValidationErrorItem[] = [], options: ErrorOptions = {}) { + super(message, options); + + this.name = 'SequelizeValidationError'; + this.errors = errors; + + // Use provided error message if available... + if (message) { + this.message = message; + + // ... otherwise create a concatenated message out of existing errors. + } else if (this.errors.length > 0 && this.errors[0].message) { + this.message = this.errors + .map((err: ValidationErrorItem) => `${err.type || err.origin}: ${err.message}`) + .join(',\n'); + } + } + + /** + * Gets all validation error items for the path / field specified. + * + * @param path The path to be checked for error items + * + * @returns Validation error items for the specified path + */ + get(path: string): ValidationErrorItem[] { + const out: ValidationErrorItem[] = []; + + for (const error of this.errors) { + if (error.path === path) { + out.push(error); + } + } + + return out; + } +} diff --git a/packages/core/src/errors/validation/unique-constraint-error.ts b/packages/core/src/errors/validation/unique-constraint-error.ts new file mode 100644 index 000000000000..fb6076458cf3 --- /dev/null +++ b/packages/core/src/errors/validation/unique-constraint-error.ts @@ -0,0 +1,44 @@ +import { useErrorCause } from '../../utils/deprecations.js'; +import type { CommonErrorProperties } from '../base-error'; +import type { ValidationErrorItem } from '../validation-error'; +import { ValidationError } from '../validation-error'; + +interface UniqueConstraintErrorParent extends Error, Pick {} + +export interface UniqueConstraintErrorOptions { + cause?: UniqueConstraintErrorParent; + + /** + * @deprecated use {@link UniqueConstraintErrorOptions.cause} + */ + parent?: UniqueConstraintErrorParent; + errors?: ValidationErrorItem[]; + fields?: Record; + message?: string; +} + +/** + * Thrown when a unique constraint is violated in the database + */ +export class UniqueConstraintError extends ValidationError { + /** The database-specific error which triggered this one */ + declare cause?: UniqueConstraintErrorParent; + + readonly fields: Record; + readonly sql: string; + + constructor(options: UniqueConstraintErrorOptions = {}) { + if ('parent' in options) { + useErrorCause(); + } + + const parent = options.cause ?? options.parent ?? { sql: '', name: '', message: '' }; + const message = options.message || parent.message || 'Validation Error'; + const errors = options.errors ?? []; + super(message, errors, { cause: parent }); + + this.name = 'SequelizeUniqueConstraintError'; + this.fields = options.fields ?? {}; + this.sql = parent.sql; + } +} diff --git a/packages/core/src/expression-builders/association-path.ts b/packages/core/src/expression-builders/association-path.ts new file mode 100644 index 000000000000..da0ff82c04be --- /dev/null +++ b/packages/core/src/expression-builders/association-path.ts @@ -0,0 +1,12 @@ +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +export class AssociationPath extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'associationPath'; + + constructor( + readonly associationPath: readonly string[], + readonly attributeName: string, + ) { + super(); + } +} diff --git a/packages/core/src/expression-builders/attribute.ts b/packages/core/src/expression-builders/attribute.ts new file mode 100644 index 000000000000..25615473a0fe --- /dev/null +++ b/packages/core/src/expression-builders/attribute.ts @@ -0,0 +1,62 @@ +import { parseAttributeSyntax } from '../utils/attribute-syntax.js'; +import type { AssociationPath } from './association-path.js'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; +import type { Cast } from './cast.js'; +import type { DialectAwareFn } from './dialect-aware-fn.js'; +import type { JsonPath } from './json-path.js'; + +/** + * Use {@link sql.attribute} instead. + */ +export class Attribute extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'attribute'; + + constructor(readonly attributeName: string) { + super(); + } +} + +/** + * Used to represent the attribute of a model. You should use the attribute name, which will be mapped to the correct column name. + * This attribute name follows the same rules as the attribute names in POJO where options. + * As such, you can use dot notation to access nested JSON properties, and you can reference included associations. + * + * If you want to use a database name, without mapping, you can use {@link Identifier}. + * + * @example + * Let's say the class User has an attribute `firstName`, which maps to the column `first_name`. + * + * ```ts + * User.findAll({ + * where: sql`${attribute('firstName')} = 'John'` + * }); + * ``` + * + * Will generate: + * + * ```sql + * SELECT * FROM users WHERE first_name = 'John' + * ``` + * + * @example + * Let's say the class User has an attribute `data`, which is a JSON column. + * + * ```ts + * User.findAll({ + * where: sql`${attribute('data.registered')} = 'true'` + * }); + * ``` + * + * Will generate (assuming the dialect supports JSON operators): + * + * ```sql + * SELECT * FROM users WHERE data->'registered' = 'true' + * ``` + * + * @param attributeName + */ +export function attribute( + attributeName: string, +): Cast | JsonPath | AssociationPath | Attribute | DialectAwareFn { + return parseAttributeSyntax(attributeName); +} diff --git a/packages/core/src/expression-builders/base-sql-expression.ts b/packages/core/src/expression-builders/base-sql-expression.ts new file mode 100644 index 000000000000..078b4a385ad0 --- /dev/null +++ b/packages/core/src/expression-builders/base-sql-expression.ts @@ -0,0 +1,41 @@ +import type { AssociationPath } from './association-path.js'; +import type { Attribute } from './attribute.js'; +import type { Cast } from './cast.js'; +import type { Col } from './col.js'; +import type { DialectAwareFn } from './dialect-aware-fn.js'; +import type { Fn } from './fn.js'; +import type { Identifier } from './identifier.js'; +import type { JsonPath } from './json-path.js'; +import type { List } from './list.js'; +import type { Literal } from './literal.js'; +import type { Value } from './value.js'; +import type { Where } from './where.js'; + +/** + * A symbol that can be used as the key for a static property on a BaseSqlExpression class to uniquely identify it. + */ +export declare const SQL_IDENTIFIER: unique symbol; + +/** + * 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 + */ +export class BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: string; +} + +export type DynamicSqlExpression = + | List + | Value + | Identifier + | Attribute + | Fn + | DialectAwareFn + | Col + | Cast + | Literal + | Where + | JsonPath + | AssociationPath; diff --git a/packages/core/src/expression-builders/cast.ts b/packages/core/src/expression-builders/cast.ts new file mode 100644 index 000000000000..44b2a02e944e --- /dev/null +++ b/packages/core/src/expression-builders/cast.ts @@ -0,0 +1,36 @@ +import { isPlainObject } from '@sequelize/utils'; +import type { DataType } from '../abstract-dialect/data-types.js'; +import { Op } from '../operators.js'; +import type { Expression } from '../sequelize.js'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; +import { where } from './where.js'; + +/** + * Do not use me directly. Use {@link sql.cast} + */ +export class Cast extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'cast'; + + constructor( + readonly expression: Expression, + readonly type: DataType, + ) { + super(); + } +} + +/** + * Creates an object representing a call to the cast function. + * + * @param val The value to cast + * @param type The type to cast it to + */ +export function cast(val: unknown, type: DataType): Cast { + if (isPlainObject(val) && !(Op.col in val)) { + // Users should wrap this parameter with `where` themselves, but we do it to ensure backwards compatibility + // with https://github.com/sequelize/sequelize/issues/6666 + val = where(val); + } + + return new Cast(val, type); +} diff --git a/packages/core/src/expression-builders/col.ts b/packages/core/src/expression-builders/col.ts new file mode 100644 index 000000000000..42400f36f93a --- /dev/null +++ b/packages/core/src/expression-builders/col.ts @@ -0,0 +1,31 @@ +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Do not use me directly. Use {@link sql.col} + */ +export class Col extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'col'; + + readonly identifiers: string[]; + + constructor(...identifiers: string[]) { + super(); + + // TODO: verify whether the "more than one identifier" case is still needed + this.identifiers = identifiers; + } +} + +/** + * Creates an object which represents a column in the DB, this allows referencing another column in your query. + * This is often useful in conjunction with {@link sql.fn}, {@link sql.where} and {@link sql} which interpret strings as values and not column names. + * + * Col works similarly to {@link sql.identifier}, but "*" has special meaning, for backwards compatibility. + * + * ⚠️ We recommend using {@link sql.identifier}, or {@link sql.attribute} instead. + * + * @param identifiers The name of the column + */ +export function col(...identifiers: string[]): Col { + return new Col(...identifiers); +} diff --git a/packages/core/src/expression-builders/dialect-aware-fn.ts b/packages/core/src/expression-builders/dialect-aware-fn.ts new file mode 100644 index 000000000000..c8939a4dfcf5 --- /dev/null +++ b/packages/core/src/expression-builders/dialect-aware-fn.ts @@ -0,0 +1,113 @@ +import type { Class } from 'type-fest'; +import type { AbstractDialect } from '../abstract-dialect/dialect.js'; +import type { EscapeOptions } from '../abstract-dialect/query-generator-typescript.js'; +import type { Expression } from '../sequelize.js'; +import { BaseSqlExpression } from './base-sql-expression.js'; +import { JsonPath } from './json-path.js'; + +/** + * Unlike {@link sql.fn}, this class does not accept a function name. + * It must instead be extended by a class that implements the {@link applyForDialect} method, in which + * the function name is provided. + * + * The goal of this class is to allow dialect-specific functions to be used in a cross-dialect way. + * For instance, an extension of this class could be used to represent the `LOWER` function in a cross-dialect way, + * by generating the correct SQL function name based on which dialect is used. + */ +export abstract class DialectAwareFn extends BaseSqlExpression { + readonly args: readonly Expression[]; + + constructor(...args: DialectAwareFn['args']) { + super(); + this.args = args; + + if (this.args.length > this.maxArgCount) { + throw new Error( + `Too many arguments provided to ${this.constructor.name} function. Expected ${this.maxArgCount} or less, but got ${this.args.length}.`, + ); + } + + if (this.args.length < this.minArgCount) { + throw new Error( + `Too few arguments provided to ${this.constructor.name} function. Expected ${this.minArgCount} or more, but got ${this.args.length}.`, + ); + } + } + + get maxArgCount() { + return Number.POSITIVE_INFINITY; + } + + get minArgCount() { + return 0; + } + + abstract supportsDialect(dialect: AbstractDialect): boolean; + + abstract applyForDialect(dialect: AbstractDialect, options?: EscapeOptions): string; + + supportsJavaScript(): boolean { + return false; + } + + applyForJavaScript(): unknown { + throw new Error(`JavaScript is not supported by the ${this.constructor.name} function.`); + } + + /** + * This getter is designed to be used as an attribute's default value. + * This is useful when the SQL version must be bypassed due to a limitation of the dialect that Sequelize cannot detect, + * such as a missing extension. + * + * ```ts + * const User = sequelize.define('User', { + * uuid: { + * type: DataTypes.UUID, + * defaultValue: sql.uuidV4.asJavaScript, + * }, + * }); + * ``` + */ + get asJavaScript(): () => unknown { + if (!this.supportsJavaScript()) { + throw new Error(`JavaScript is not supported by the ${this.constructor.name} function.`); + } + + return () => this.applyForJavaScript(); + } + + static build(this: Class, ...args: DialectAwareFn['args']): M { + return new this(...args); + } +} + +/** + * Unquotes JSON values. + */ +export class Unquote extends DialectAwareFn { + get maxArgCount() { + return 1; + } + + get minArgCount() { + return 1; + } + + supportsDialect(dialect: AbstractDialect): boolean { + return dialect.supports.jsonOperations; + } + + applyForDialect(dialect: AbstractDialect, options?: EscapeOptions): string { + const arg = this.args[0]; + + if (arg instanceof JsonPath) { + return dialect.queryGenerator.jsonPathExtractionQuery( + dialect.queryGenerator.escape(arg.expression), + arg.path, + true, + ); + } + + return dialect.queryGenerator.formatUnquoteJson(arg, options); + } +} diff --git a/packages/core/src/expression-builders/fn.ts b/packages/core/src/expression-builders/fn.ts new file mode 100644 index 000000000000..32e6bb475a08 --- /dev/null +++ b/packages/core/src/expression-builders/fn.ts @@ -0,0 +1,52 @@ +import { isPlainObject } from '@sequelize/utils'; +import { Op } from '../operators.js'; +import type { Expression } from '../sequelize.js'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; +import { where } from './where.js'; + +/** + * Do not use me directly. Use {@link sql.fn} + */ +export class Fn extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'fn'; + + readonly fn: string; + readonly args: readonly Expression[]; + + constructor(fnName: string, args: Fn['args']) { + super(); + this.fn = fnName; + this.args = args; + } +} + +/** + * Creates an object representing a database function. This can be used in search queries, both in where and order parts, and as default values in column definitions. + * If you want to refer to columns in your function, you should use {@link sql.attribute} (recommended), {@link sql.identifier}, or {@link sql.col} (discouraged) + * otherwise the value will be interpreted as a string. + * + * ℹ️ This method is usually verbose and we recommend using the {@link sql} template string tag instead. + * + * @param fnName The SQL function you want to call + * @param args All further arguments will be passed as arguments to the function + * + * @example Convert a user's username to upper case + * ```ts + * instance.update({ + * username: fn('upper', col('username')) + * }); + * ``` + */ +export function fn(fnName: string, ...args: Fn['args']): Fn { + for (let i = 0; i < args.length; i++) { + // Users should wrap this parameter with `where` themselves, but we do it to ensure backwards compatibility + // with https://github.com/sequelize/sequelize/issues/6666 + // @ts-expect-error -- backwards compatibility hack + if (isPlainObject(args[i]) && !(Op.col in args[i])) { + // @ts-expect-error -- backwards compatibility hack + args[i] = where(args[i]); + } + } + + return new Fn(fnName, args); +} diff --git a/packages/core/src/expression-builders/identifier.ts b/packages/core/src/expression-builders/identifier.ts new file mode 100644 index 000000000000..08387b583f48 --- /dev/null +++ b/packages/core/src/expression-builders/identifier.ts @@ -0,0 +1,36 @@ +import type { TableOrModel } from '../abstract-dialect/query-generator.types'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Use {@link sql.identifier} instead. + */ +export class Identifier extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'identifier'; + + constructor(readonly values: Array) { + super(); + } +} + +/** + * Used to represent a value that will either be escaped to a literal, or a bind parameter. + * Unlike {@link sql.attribute} and {@link sql.col}, this identifier will be escaped as-is, + * without mapping to a column name or any other transformation. + * + * This method supports strings, table structures, model classes (in which case the identifiers will be the model schema & table name), and model definitions (same behavior as model classes) + * + * @param values The identifiers to escape. Automatically joins them with a period (`.`). + * @example + * ```ts + * sequelize.query(sql`SELECT * FROM users WHERE ${identifier('firstName')} = 'John'`); + * ``` + * + * Will generate (identifier quoting depending on the dialect): + * + * ```sql + * SELECT * FROM users WHERE "firstName" = 'John' + * ``` + */ +export function identifier(...values: Array): Identifier { + return new Identifier(values); +} diff --git a/packages/core/src/expression-builders/json-path.ts b/packages/core/src/expression-builders/json-path.ts new file mode 100644 index 000000000000..e27cdf724cfc --- /dev/null +++ b/packages/core/src/expression-builders/json-path.ts @@ -0,0 +1,71 @@ +import type { Expression } from '../sequelize.js'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Do not use me directly. Use {@link sql.jsonPath}. + */ +export class JsonPath extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'jsonPath'; + + constructor( + readonly expression: Expression, + readonly path: ReadonlyArray, + ) { + super(); + } +} + +/** + * Use this to access nested properties in a JSON column. + * You can also use the dot notation with {@link sql.attribute}, but this works with any values, not just attributes. + * + * @param expression The expression to access the property on. + * @param path The path to the property. If a number is used, it will be treated as an array index, otherwise as a key. + * + * @example + * ```ts + * sql`${jsonPath('data', ['name'])} = '"John"'` + * ``` + * + * will produce + * + * ```sql + * -- postgres + * "data"->'name' = '"John"' + * -- sqlite, mysql, mariadb + * JSON_EXTRACT("data", '$.name') = '"John"' + * ``` + * + * @example + * ```ts + * // notice here that 0 is a number, not a string. It will be treated as an array index. + * sql`${jsonPath('array', [0])}` + * ``` + * + * will produce + * + * ```sql + * -- postgres + * "array"->0 + * -- sqlite, mysql, mariadb + * JSON_EXTRACT(`array`, '$[0]') + * ``` + * + * @example + * ```ts + * // notice here that 0 is a string, not a number. It will be treated as an object key. + * sql`${jsonPath('object', ['0'])}` + * ``` + * + * will produce + * + * ```sql + * -- postgres + * "object"->'0' + * -- sqlite, mysql, mariadb + * JSON_EXTRACT(`object`, '$.0') + * ``` + */ +export function jsonPath(expression: Expression, path: ReadonlyArray): JsonPath { + return new JsonPath(expression, path); +} diff --git a/packages/core/src/expression-builders/json-sql-null.ts b/packages/core/src/expression-builders/json-sql-null.ts new file mode 100644 index 000000000000..844379d0e6cc --- /dev/null +++ b/packages/core/src/expression-builders/json-sql-null.ts @@ -0,0 +1,31 @@ +import type { AbstractDialect } from '../abstract-dialect/dialect.js'; +import { DialectAwareFn } from './dialect-aware-fn.js'; +import { literal } from './literal.js'; + +class JsonNullClass extends DialectAwareFn { + get maxArgCount() { + return 0; + } + + get minArgCount() { + return 0; + } + + supportsDialect(): boolean { + return true; + } + + applyForDialect(dialect: AbstractDialect): string { + return dialect.escapeJson(null); + } +} + +/** + * null as a JSON value + */ +export const JSON_NULL = JsonNullClass.build(); + +/** + * null as an SQL value + */ +export const SQL_NULL = literal('NULL'); diff --git a/packages/core/src/expression-builders/json.ts b/packages/core/src/expression-builders/json.ts new file mode 100644 index 000000000000..2bb9460c40ff --- /dev/null +++ b/packages/core/src/expression-builders/json.ts @@ -0,0 +1,44 @@ +import { noSqlJson } from '../utils/deprecations.js'; +import type { AssociationPath } from './association-path.js'; +import type { Attribute } from './attribute.js'; +import { attribute } from './attribute.js'; +import type { Cast } from './cast.js'; +import type { DialectAwareFn } from './dialect-aware-fn.js'; +import type { JsonPath } from './json-path.js'; +import type { Where } from './where.js'; +import { where } from './where.js'; + +/** + * Creates an object representing nested where conditions for postgres/sqlite/mysql json data-type. + * + * @param 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 value An optional value to compare against. Produces a string of the form " = ''". + * + * @deprecated use {@link sql.where}, {@link sql.attribute}, and/or {@link sql.jsonPath} instead. + */ +export function json( + conditionsOrPath: { [key: string]: any } | string, + value?: string | number | boolean | null, +): Cast | JsonPath | AssociationPath | Attribute | DialectAwareFn | Where { + noSqlJson(); + + if (typeof conditionsOrPath === 'string') { + const attr = attribute(conditionsOrPath); + + // json('profile.id') is identical to attribute('profile.id') + if (value === undefined) { + return attr; + } + + // json('profile.id', value) is identical to where(attribute('profile.id'), value) + return where(attr, value); + } + + if (value === undefined && typeof conditionsOrPath === 'string') { + return attribute(conditionsOrPath); + } + + // json({ key: value }) is identical to where({ key: value }) + + return where(conditionsOrPath); +} diff --git a/packages/core/src/expression-builders/list.ts b/packages/core/src/expression-builders/list.ts new file mode 100644 index 000000000000..62e88ecb661f --- /dev/null +++ b/packages/core/src/expression-builders/list.ts @@ -0,0 +1,33 @@ +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Use {@link sql.list} instead. + */ +export class List extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'list'; + + constructor(readonly values: unknown[]) { + super(); + } +} + +/** + * Used to represent an SQL list of values, e.g. `WHERE id IN (1, 2, 3)`. This ensure that the array is interpreted + * as an SQL list, and not as an SQL Array. + * + * @example + * ```ts + * sequelize.query(sql`SELECT * FROM users WHERE id IN ${list([1, 2, 3])}`); + * ``` + * + * Will generate: + * + * ```sql + * SELECT * FROM users WHERE id IN (1, 2, 3) + * ``` + * + * @param values The members of the list. + */ +export function list(values: unknown[]): List { + return new List(values); +} diff --git a/packages/core/src/expression-builders/literal.ts b/packages/core/src/expression-builders/literal.ts new file mode 100644 index 000000000000..8aa7d98ea50e --- /dev/null +++ b/packages/core/src/expression-builders/literal.ts @@ -0,0 +1,26 @@ +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Do not use me directly. Use {@link sql.literal} + */ +export class Literal extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'literal'; + + readonly val: ReadonlyArray; + + constructor(val: string | Array) { + super(); + + this.val = Array.isArray(val) ? val : [val]; + } +} + +/** + * Creates an object representing a literal, i.e. something that will not be escaped. + * We recommend using {@link sql} for a better DX. + * + * @param val literal value + */ +export function literal(val: string | Array): Literal { + return new Literal(val); +} diff --git a/packages/core/src/expression-builders/sql.ts b/packages/core/src/expression-builders/sql.ts new file mode 100644 index 000000000000..e0fc3f1fb640 --- /dev/null +++ b/packages/core/src/expression-builders/sql.ts @@ -0,0 +1,76 @@ +import { intersperse } from '@sequelize/utils'; +import { attribute } from './attribute.js'; +import { BaseSqlExpression } from './base-sql-expression.js'; +import { cast } from './cast.js'; +import { col } from './col.js'; +import { Unquote } from './dialect-aware-fn.js'; +import { fn } from './fn.js'; +import { identifier } from './identifier.js'; +import { jsonPath } from './json-path.js'; +import { list } from './list.js'; +import { Literal, literal } from './literal.js'; +import { SqlUuidV1, SqlUuidV4 } from './uuid.js'; +import { Value } from './value.js'; +import { where } from './where.js'; + +/** + * The template tag function used to easily create {@link sql.literal}. + * + * @param rawSql + * @param values + * @example + * ```ts + * sql`SELECT * FROM ${sql.identifier(table)} WHERE ${sql.identifier(column)} = ${value}` + * ``` + */ +export function sql(rawSql: TemplateStringsArray, ...values: unknown[]): Literal { + const arg: Array = []; + + for (const [i, element] of rawSql.entries()) { + arg.push(element); + + if (i < values.length) { + const value = values[i]; + + arg.push(wrapValue(value)); + } + } + + return new Literal(arg); +} + +function wrapValue(value: unknown): BaseSqlExpression { + return value instanceof BaseSqlExpression ? value : new Value(value); +} + +/** + * A version of {@link Array#join}, but for SQL expressions. + * Using {@link Array#join} directly would not work, because the end result would be a string, not a SQL expression. + * + * @param parts The parts to join. Each part can be a SQL expression, or a value to escape. + * @param separator A raw SQL string, or a SQL expression to separate each pair of adjacent elements of the array. + * @returns A SQL expression representing the concatenation of all parts, interspersed with the separator. + */ +function joinSql(parts: unknown[], separator: string | BaseSqlExpression) { + const escapedParts = parts.map(wrapValue); + + return new Literal(separator ? intersperse(escapedParts, separator) : escapedParts); +} + +// The following builders are not listed here for the following reasons: +// - json(): deprecated & redundant with other builders +// - value(): internal detail of the `sql` template tag function +// - associationPath(): internal detail of attribute() +sql.attribute = attribute; +sql.cast = cast; +sql.col = col; +sql.fn = fn; +sql.identifier = identifier; +sql.jsonPath = jsonPath; +sql.list = list; +sql.literal = literal; +sql.where = where; +sql.uuidV4 = SqlUuidV4.build(); +sql.uuidV1 = SqlUuidV1.build(); +sql.unquote = Unquote.build.bind(Unquote); +sql.join = joinSql; diff --git a/packages/core/src/expression-builders/uuid.ts b/packages/core/src/expression-builders/uuid.ts new file mode 100644 index 000000000000..598d0797a619 --- /dev/null +++ b/packages/core/src/expression-builders/uuid.ts @@ -0,0 +1,56 @@ +import crypto from 'node:crypto'; +import { v1 as generateUuidV1 } from 'uuid'; +import type { AbstractDialect } from '../abstract-dialect/dialect.js'; +import { DialectAwareFn } from './dialect-aware-fn.js'; + +export class SqlUuidV4 extends DialectAwareFn { + get maxArgCount() { + return 0; + } + + get minArgCount() { + return 0; + } + + supportsJavaScript(): boolean { + return true; + } + + applyForJavaScript(): unknown { + return crypto.randomUUID(); + } + + supportsDialect(dialect: AbstractDialect): boolean { + return dialect.supports.uuidV4Generation; + } + + applyForDialect(dialect: AbstractDialect): string { + return dialect.queryGenerator.getUuidV4FunctionCall(); + } +} + +export class SqlUuidV1 extends DialectAwareFn { + get maxArgCount() { + return 0; + } + + get minArgCount() { + return 0; + } + + supportsJavaScript(): boolean { + return true; + } + + applyForJavaScript(): unknown { + return generateUuidV1(); + } + + supportsDialect(dialect: AbstractDialect): boolean { + return dialect.supports.uuidV1Generation; + } + + applyForDialect(dialect: AbstractDialect): string { + return dialect.queryGenerator.getUuidV1FunctionCall(); + } +} diff --git a/packages/core/src/expression-builders/value.ts b/packages/core/src/expression-builders/value.ts new file mode 100644 index 000000000000..38a2143021fd --- /dev/null +++ b/packages/core/src/expression-builders/value.ts @@ -0,0 +1,14 @@ +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Used to represent a value that will either be escaped to a literal, or a bind parameter. + * You do not need to use this function directly, it will be used automatically when you interpolate parameters + * in a template string tagged with {@link sql}. + */ +export class Value extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'value'; + + constructor(readonly value: unknown) { + super(); + } +} diff --git a/packages/core/src/expression-builders/where.ts b/packages/core/src/expression-builders/where.ts new file mode 100644 index 000000000000..5af05fd07637 --- /dev/null +++ b/packages/core/src/expression-builders/where.ts @@ -0,0 +1,162 @@ +import type { + WhereAttributeHashValue, + WhereOptions, +} from '../abstract-dialect/where-sql-builder-types.js'; +import { PojoWhere } from '../abstract-dialect/where-sql-builder.js'; +import type { WhereOperators } from '../model.js'; +import type { Op } from '../operators.js'; +import type { Expression } from '../sequelize.js'; +import { BaseSqlExpression, SQL_IDENTIFIER } from './base-sql-expression.js'; + +/** + * Do not use me directly. Use {@link sql.where} + */ +export class Where extends BaseSqlExpression { + declare protected readonly [SQL_IDENTIFIER]: 'where'; + + readonly where: PojoWhere | WhereOptions; + + /** + * @example + * ```ts + * where({ id: 1 }) + * ``` + * + * @param whereOptions + */ + constructor(whereOptions: WhereOptions); + + /** + * @example + * ```ts + * where(col('id'), { [Op.eq]: 1 }) + * ``` + * + * @param leftOperand + * @param whereAttributeHashValue + */ + constructor(leftOperand: Expression, whereAttributeHashValue: WhereAttributeHashValue); + + /** + * @example + * ```ts + * where(col('id'), Op.eq, 1) + * ``` + * + * @param leftOperand + * @param operator + * @param rightOperand + */ + constructor(leftOperand: Expression, operator: Operator, rightOperand: WhereOperators[Operator]); + + constructor( + ...args: + | [whereOptions: WhereOptions] + | [leftOperand: Expression, whereAttributeHashValue: WhereAttributeHashValue] + | [leftOperand: Expression, operator: Operator, rightOperand: WhereOperators[Operator]] + ) { + super(); + + if (args.length === 1) { + this.where = args[0]; + } else if (args.length === 2) { + this.where = PojoWhere.create(args[0], args[1]); + } else { + if (typeof args[1] === 'string') { + throw new TypeError(`where(left, operator, right) does not accept a string as the operator. Use one of the operators available in the Op object. +If you wish to use custom operators not provided by Sequelize, you can use the "sql" template literal tag. Refer to the documentation on custom operators on https://sequelize.org/docs/v7/querying/operators/#custom-operators for more details.`); + } + + // normalize where(col, op, val) + // to where(col, { [op]: val }) + this.where = PojoWhere.create(args[0], { [args[1]]: args[2] }); + } + } +} + +/** + * A way of writing an SQL binary operator, or more complex where conditions. + * + * This solution is slightly more verbose than the POJO syntax, but allows any value on the left hand side of the operator (unlike the POJO syntax which only accepts attribute names). + * For instance, either the left or right hand side of the operator can be {@link sql.fn}, {@link sql.col}, {@link sql.literal} etc. + * + * If your left operand is an attribute name, using the regular POJO syntax (`{ where: { attrName: value }}`) syntax is usually more convenient. + * + * ⚠️ Unlike the POJO syntax, if the left operand is a string, it will be treated as a _value_, not an attribute name. If you wish to refer to an attribute, use {@link Attribute} instead. + * + * @example + * ```ts + * where(attribute('id'), { [Op.eq]: 1 }); + * where(attribute('id'), { + * [Op.or]: { + * [Op.eq]: 1, + * [Op.gt]: 10, + * }, + * }); + * ``` + * + * @param leftOperand The left operand + * @param whereAttributeHashValue The POJO containing the operators and the right operands + */ +export function where( + leftOperand: Expression, + whereAttributeHashValue: WhereAttributeHashValue, +): Where; +/** + * This version of `where` is used to opt back into the POJO syntax. Useful in combination with {@link sql}. + * + * @example + * ```ts + * sequelize.query(sql` + * SELECT * FROM users WHERE ${where({ id: 1 })}; + * `) + * ``` + * + * produces + * + * ```sql + * SELECT * FROM users WHERE "id" = 1; + * ``` + * + * @param whereOptions + */ +export function where(whereOptions: WhereOptions): Where; +/** + * @example + * ```ts + * where(col('id'), Op.eq, 1) + * ``` + * + * @example + * // Using a column name as the left operand. + * // Equal to: WHERE first_name = 'Lily' + * where(col('first_name'), Op.eq, 'Lily'); + * + * @example + * // Using a SQL function on the left operand. + * // Equal to: WHERE LOWER(first_name) = 'lily' + * where(fn('LOWER', col('first_name')), Op.eq, 'lily'); + * + * @example + * // Using raw SQL as the left operand. + * // Equal to: WHERE 'Lily' = 'Lily' + * where(literal(`'Lily'`), Op.eq, 'Lily'); + * + * @param leftOperand The left operand + * @param operator The operator to use (one of the different values available in the {@link Op} object) + * @param rightOperand The right operand + */ +export function where( + leftOperand: Expression, + operator: keyof WhereOperators, + rightOperand: Expression, +): Where; +export function where( + ...args: + | [whereOptions: WhereOptions] + | [leftOperand: Expression, whereAttributeHashValue: WhereAttributeHashValue] + | [leftOperand: Expression, operator: keyof WhereOperators, rightOperand: Expression] +): Where { + // @ts-expect-error -- they are the same type but this overload is internal + return new Where(...args); +} diff --git a/packages/core/src/generic/sql-fragment.ts b/packages/core/src/generic/sql-fragment.ts new file mode 100644 index 000000000000..f203670f4c43 --- /dev/null +++ b/packages/core/src/generic/sql-fragment.ts @@ -0,0 +1,4 @@ +import type { Nullish } from '@sequelize/utils'; + +export type SQLFragment = string | Nullish | SQLFragment[]; +export type TruthySQLFragment = string | SQLFragment[]; diff --git a/packages/core/src/geo-json.ts b/packages/core/src/geo-json.ts new file mode 100644 index 000000000000..0f2e5cd4ef23 --- /dev/null +++ b/packages/core/src/geo-json.ts @@ -0,0 +1,309 @@ +import { isPlainObject } from '@sequelize/utils'; +import util from 'node:util'; +import { validator as Validator } from './utils/validator-extras.js'; + +export enum GeoJsonType { + Point = 'Point', + LineString = 'LineString', + Polygon = 'Polygon', + MultiPoint = 'MultiPoint', + MultiLineString = 'MultiLineString', + MultiPolygon = 'MultiPolygon', + GeometryCollection = 'GeometryCollection', +} + +interface BaseGeoJson { + type: Type; + properties?: Record; + crs?: { + type: 'name'; + properties: { + name: string; + }; + }; +} + +export type PositionPosition = [x: number, y: number, elevation?: number]; + +export interface GeoJsonPoint extends BaseGeoJson<'Point'> { + coordinates: PositionPosition | []; +} + +export interface GeoJsonLineString extends BaseGeoJson<'LineString'> { + coordinates: PositionPosition[]; +} + +export interface GeoJsonPolygon extends BaseGeoJson<'Polygon'> { + coordinates: PositionPosition[][]; +} + +export interface GeoJsonMultiPoint extends BaseGeoJson<'MultiPoint'> { + coordinates: PositionPosition[]; +} + +export interface GeoJsonMultiLineString extends BaseGeoJson<'MultiLineString'> { + coordinates: PositionPosition[][]; +} + +export interface GeoJsonMultiPolygon extends BaseGeoJson<'MultiPolygon'> { + coordinates: PositionPosition[][][]; +} + +export interface GeoJsonGeometryCollection extends BaseGeoJson<'GeometryCollection'> { + geometries: GeoJson[]; +} + +export type GeoJson = + | GeoJsonPoint + | GeoJsonLineString + | GeoJsonPolygon + | GeoJsonMultiPoint + | GeoJsonMultiLineString + | GeoJsonMultiPolygon + | GeoJsonGeometryCollection; + +const geoJsonTypeArray = Object.keys(GeoJsonType); + +export function assertIsGeoJson(value: unknown): asserts value is GeoJson { + assertIsBaseGeoJson(value); + + switch (value.type) { + case GeoJsonType.Point: + assertIsGeoJsonPoint(value); + break; + + case GeoJsonType.LineString: + assertIsGeoJsonLineString(value); + break; + + case GeoJsonType.Polygon: + assertIsGeoJsonPolygon(value); + break; + + case GeoJsonType.MultiPoint: + assertIsGeoJsonMultiPoint(value); + break; + + case GeoJsonType.MultiLineString: + assertIsGeoJsonMultiLineString(value); + break; + + case GeoJsonType.MultiPolygon: + assertIsGeoJsonMultiPolygon(value); + break; + + case GeoJsonType.GeometryCollection: + assertIsGeoJsonGeometryCollection(value); + break; + + default: + throw new Error( + `GeoJSON object ${util.inspect(value)} has an invalid or missing "type" property. Expected one of ${geoJsonTypeArray.join(', ')}`, + ); + } +} + +function validatePosition(tuple: unknown, source: GeoJson): void { + if (!Array.isArray(tuple)) { + throw new Error( + `GeoJSON ${source.type} object ${util.inspect(source)} specifies an invalid position: ${util.inspect(tuple)}. Expected an array of numeric values.`, + ); + } + + // Prevent a SQL injection attack, as coordinates are inlined in the query without escaping. + for (const coordinate of tuple) { + if (!Validator.isNumeric(String(coordinate))) { + throw new Error( + `GeoJSON ${source.type} object ${util.inspect(source)} specifies an invalid point: ${util.inspect(tuple)}. ${util.inspect(coordinate)} is not a numeric value.`, + ); + } + } +} + +function assertIsBaseGeoJson(value: unknown): asserts value is GeoJson { + if (!isPlainObject(value)) { + throw new Error( + `${util.inspect(value)} is not a valid GeoJSON object: it must be a plain object.`, + ); + } +} + +export function assertIsGeoJsonPoint(value: unknown): asserts value is GeoJsonPoint { + assertIsBaseGeoJson(value); + + if (value.type !== 'Point') { + throw new Error( + `GeoJSON Point object ${util.inspect(value)} has an invalid or missing "type" property. Expected "Point".`, + ); + } + + const coordinates = value.coordinates; + // Some Point implementations accepts empty coordinates. + if (Array.isArray(coordinates) && coordinates.length === 0) { + return; + } + + validatePosition(coordinates, value); +} + +export function assertIsGeoJsonLineString(value: unknown): asserts value is GeoJsonLineString { + assertIsBaseGeoJson(value); + + if (value.type !== 'LineString') { + throw new Error( + `GeoJSON LineString object ${util.inspect(value)} has an invalid or missing "type" property. Expected "LineString".`, + ); + } + + const coordinates = value.coordinates; + if (!Array.isArray(coordinates)) { + throw new Error( + `GeoJSON LineString object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of positions (array of numeric values).`, + ); + } + + for (const position of coordinates) { + validatePosition(position, value); + } +} + +export function assertIsGeoJsonPolygon(value: unknown): asserts value is GeoJsonPolygon { + assertIsBaseGeoJson(value); + + if (value.type !== 'Polygon') { + throw new Error( + `GeoJSON Polygon object ${util.inspect(value)} has an invalid or missing "type" property. Expected "Polygon".`, + ); + } + + const coordinates = value.coordinates; + if (!Array.isArray(coordinates)) { + throw new Error( + `GeoJSON Polygon object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of linear ring coordinate arrays. Refer to the GeoJSON specification for more information.`, + ); + } + + for (const ring of coordinates) { + if (!Array.isArray(ring)) { + throw new Error( + `GeoJSON Polygon object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of linear ring coordinate arrays. Refer to the GeoJSON specification for more information.`, + ); + } + + for (const position of ring) { + validatePosition(position, value); + } + } +} + +export function assertIsGeoJsonMultiPoint(value: unknown): asserts value is GeoJsonMultiPoint { + assertIsBaseGeoJson(value); + + if (value.type !== 'MultiPoint') { + throw new Error( + `GeoJSON MultiPoint object ${util.inspect(value)} has an invalid or missing "type" property. Expected "MultiPoint".`, + ); + } + + const coordinates = value.coordinates; + if (!Array.isArray(coordinates)) { + throw new Error( + `GeoJSON MultiPoint object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of point coordinates.`, + ); + } + + for (const position of coordinates) { + validatePosition(position, value); + } +} + +export function assertIsGeoJsonMultiLineString( + value: unknown, +): asserts value is GeoJsonMultiLineString { + assertIsBaseGeoJson(value); + + if (value.type !== 'MultiLineString') { + throw new Error( + `GeoJSON MultiLineString object ${util.inspect(value)} has an invalid or missing "type" property. Expected "MultiLineString".`, + ); + } + + const coordinates = value.coordinates; + if (!Array.isArray(coordinates)) { + throw new Error( + `GeoJSON MultiLineString object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of line string coordinates.`, + ); + } + + for (const lineString of coordinates) { + if (!Array.isArray(lineString)) { + throw new Error( + `GeoJSON MultiLineString object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of line string coordinates.`, + ); + } + + for (const position of lineString) { + validatePosition(position, value); + } + } +} + +export function assertIsGeoJsonMultiPolygon(value: unknown): asserts value is GeoJsonMultiPolygon { + assertIsBaseGeoJson(value); + + if (value.type !== 'MultiPolygon') { + throw new Error( + `GeoJSON MultiPolygon object ${util.inspect(value)} has an invalid or missing "type" property. Expected "MultiPolygon".`, + ); + } + + const coordinates = value.coordinates; + if (!Array.isArray(coordinates)) { + throw new Error( + `GeoJSON MultiPolygon object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of polygon coordinates.`, + ); + } + + for (const polygon of coordinates) { + if (!Array.isArray(polygon)) { + throw new Error( + `GeoJSON MultiPolygon object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of polygon coordinates.`, + ); + } + + for (const ring of polygon) { + if (!Array.isArray(ring)) { + throw new Error( + `GeoJSON MultiPolygon object ${util.inspect(value)} has an invalid or missing "coordinates" property. Expected an array of polygon coordinates.`, + ); + } + + for (const position of ring) { + validatePosition(position, value); + } + } + } +} + +export function assertIsGeoJsonGeometryCollection( + value: unknown, +): asserts value is GeoJsonGeometryCollection { + assertIsBaseGeoJson(value); + + if (value.type !== 'GeometryCollection') { + throw new Error( + `GeoJSON GeometryCollection object ${util.inspect(value)} has an invalid or missing "type" property. Expected "GeometryCollection".`, + ); + } + + const geometries = value.geometries; + if (!Array.isArray(geometries)) { + throw new Error( + `GeoJSON GeometryCollection object ${util.inspect(value)} has an invalid or missing "geometries" property. Expected an array of GeoJSON geometry objects.`, + ); + } + + for (const geometry of geometries) { + assertIsGeoJson(geometry); + } +} diff --git a/packages/core/src/hooks-legacy.ts b/packages/core/src/hooks-legacy.ts new file mode 100644 index 000000000000..dc82fdc0c436 --- /dev/null +++ b/packages/core/src/hooks-legacy.ts @@ -0,0 +1,150 @@ +import type { HookHandler, HookHandlerBuilder } from './hooks.js'; +import { hooksReworked } from './utils/deprecations.js'; + +// TODO: delete this in Sequelize v8 + +export interface LegacyRunHookFunction { + ( + hookName: HookName, + ...args: HookConfig[HookName] extends (...args2: any) => any + ? Parameters + : never + ): Return; +} + +export function legacyBuildRunHook( + // added for typing purposes + _hookHandlerBuilder: HookHandlerBuilder, +): LegacyRunHookFunction { + return async function runHooks( + this: { hooks: HookHandler }, + hookName: HookName, + ...args: HookConfig[HookName] extends (...args2: any) => any + ? Parameters + : never + ): Promise { + hooksReworked(); + + return this.hooks.runAsync(hookName, ...args); + }; +} + +export interface LegacyAddAnyHookFunction { + /** + * Adds a hook listener + */ + ( + this: This, + hookName: HookName, + hook: HookConfig[HookName], + ): This; + + /** + * Adds a hook listener + * + * @param listenerName Provide a name for the hook function. It can be used to remove the hook later. + */ + ( + this: This, + hookName: HookName, + listenerName: string, + hook: HookConfig[HookName], + ): This; +} + +export function legacyBuildAddAnyHook( + // added for typing purposes + _hookHandlerBuilder: HookHandlerBuilder, +): LegacyAddAnyHookFunction { + return function addHook< + This extends { hooks: HookHandler }, + HookName extends keyof HookConfig, + >( + this: This, + hookName: HookName, + listenerNameOrHook: HookConfig[HookName] | string, + hook?: HookConfig[HookName], + ): This { + hooksReworked(); + + if (hook) { + // TODO: remove this eslint-disable once we drop support for TypeScript 5.1 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- In TypeScript 5.1, this is valid. In all other versions, this is not. + this.hooks.addListener(hookName, hook, listenerNameOrHook); + } else { + // @ts-expect-error -- TypeScript struggles with the multiple possible signatures of addListener + this.hooks.addListener(hookName, listenerNameOrHook); + } + + return this; + }; +} + +export interface LegacyAddHookFunction { + /** + * Adds a hook listener + */ + (this: This, hook: Fn): This; + + /** + * Adds a hook listener + * + * @param listenerName Provide a name for the hook function. It can be used to remove the hook later. + */ + (this: This, listenerName: string, hook: Fn): This; +} + +export function legacyBuildAddHook( + hookHandlerBuilder: HookHandlerBuilder, + hookName: HookName, +): LegacyAddHookFunction { + return function addHook }>( + this: This, + listenerNameOrHook: HookConfig[HookName] | string, + hook?: HookConfig[HookName], + ): This { + hooksReworked(); + + if (hook) { + // TODO: remove this eslint-disable once we drop support for TypeScript 5.1 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- In TypeScript 5.1, this is valid. In all other versions, this is not. + this.hooks.addListener(hookName, hook, listenerNameOrHook); + } else { + // @ts-expect-error -- TypeScript struggles with the multiple possible signatures of addListener + this.hooks.addListener(hookName, listenerNameOrHook); + } + + return this; + }; +} + +export function legacyBuildHasHook( + // added for typing purposes + _hookHandlerBuilder: HookHandlerBuilder, +) { + return function hasHook( + this: { hooks: HookHandler }, + hookName: HookName, + ): boolean { + hooksReworked(); + + return this.hooks.hasListeners(hookName); + }; +} + +export function legacyBuildRemoveHook( + // added for typing purposes + _hookHandlerBuilder: HookHandlerBuilder, +) { + return function removeHook( + this: { hooks: HookHandler }, + hookName: HookName, + listenerNameOrListener: HookConfig[HookName] | string, + ): void { + hooksReworked(); + + return this.hooks.removeListener(hookName, listenerNameOrListener); + }; +} diff --git a/packages/core/src/hooks.ts b/packages/core/src/hooks.ts new file mode 100644 index 000000000000..33855edbccab --- /dev/null +++ b/packages/core/src/hooks.ts @@ -0,0 +1,258 @@ +import type { AllowArray, Nullish } from '@sequelize/utils'; +import { MultiMap } from '@sequelize/utils'; + +export type AsyncHookReturn = Promise | void; + +type HookParameters = Hook extends (...args2: any) => any ? Parameters : never; + +type OnRunHook = ( + eventTarget: object, + isAsync: boolean, + hookName: HookName, + args: HookParameters, +) => AsyncHookReturn; + +/** + * @private + */ +export class HookHandler { + readonly #validHookNames: Array; + readonly #eventTarget: object; + readonly #listeners = new MultiMap< + PropertyKey, + { listenerName: string | Nullish; callback: HookConfig[keyof HookConfig] } + >(); + + readonly #onRunHook: OnRunHook | undefined; + + constructor( + eventTarget: object, + validHookNames: Array, + onRunHook?: OnRunHook, + ) { + this.#eventTarget = eventTarget; + this.#validHookNames = validHookNames; + this.#onRunHook = onRunHook; + } + + removeListener( + hookName: HookName, + listenerOrListenerName: string | HookConfig[HookName], + ): void { + this.#assertValidHookName(hookName); + + if (typeof listenerOrListenerName === 'string') { + const listener = this.#getNamedListener(hookName, listenerOrListenerName); + if (listener) { + this.#listeners.deleteValue(hookName, listener); + } + } else { + const listeners = this.#listeners.get(hookName); + for (const listener of listeners) { + if (listener.callback === listenerOrListenerName) { + this.#listeners.deleteValue(hookName, listener); + } + } + } + } + + removeAllListeners() { + this.#listeners.clear(); + } + + #getNamedListener( + hookName: HookName, + listenerName: string, + ): { listenerName: string | Nullish; callback: HookConfig[keyof HookConfig] } | null { + const listeners = this.#listeners.get(hookName); + for (const listener of listeners) { + if (listener.listenerName === listenerName) { + return listener; + } + } + + return null; + } + + hasListeners(hookName: keyof HookConfig): boolean { + this.#assertValidHookName(hookName); + + return this.#listeners.count(hookName) > 0; + } + + getListenerCount(hookName: keyof HookConfig): number { + this.#assertValidHookName(hookName); + + return this.#listeners.count(hookName); + } + + runSync( + hookName: HookName, + ...args: HookConfig[HookName] extends (...args2: any) => any + ? Parameters + : never + ): void { + this.#assertValidHookName(hookName); + + const listeners = this.#listeners.get(hookName); + for (const listener of listeners) { + // @ts-expect-error -- callback can by any hook type (due to coming from the map), args is the args of a specific hook. Too hard to type properly. + const out = listener.callback(...args); + + if (out && 'then' in out) { + throw new Error( + `${listener.listenerName ? `Listener ${listener.listenerName}` : `An unnamed listener`} of hook ${String(hookName)} on ${getName(this.#eventTarget)} returned a Promise, but the hook is synchronous.`, + ); + } + } + + if (this.#onRunHook) { + void this.#onRunHook(this.#eventTarget, false, hookName, args); + } + } + + async runAsync( + hookName: HookName, + ...args: HookConfig[HookName] extends (...args2: any) => any + ? Parameters + : never + ): Promise { + this.#assertValidHookName(hookName); + + const listeners = this.#listeners.get(hookName); + for (const listener of listeners) { + /* eslint-disable no-await-in-loop */ + // @ts-expect-error -- callback can by any hook type (due to coming from the map), args is the args of a specific hook. Too hard to type properly. + await listener.callback(...args); + /* eslint-enable no-await-in-loop */ + } + + if (this.#onRunHook) { + await this.#onRunHook(this.#eventTarget, true, hookName, args); + } + } + + /** + * Registers a listener for a hook. + * + * Returns a function that can be called to deregister the listener. + * + * @param hookName + * @param listener + * @param listenerName + */ + addListener( + hookName: HookName, + listener: HookConfig[HookName], + listenerName?: string, + ): () => void { + this.#assertValidHookName(hookName); + + if (listenerName) { + const existingListener = this.#getNamedListener(hookName, listenerName); + + if (existingListener) { + throw new Error( + `Named listener ${listenerName} already exists for hook ${String(hookName)} on ${getName(this.#eventTarget)}.`, + ); + } + } + + this.#listeners.append(hookName, { callback: listener, listenerName }); + + return () => { + this.removeListener(hookName, listenerName || listener); + }; + } + + addListeners(listeners: { + [Key in keyof HookConfig]?: AllowArray< + HookConfig[Key] | { name: string | symbol; callback: HookConfig[Key] } + >; + }) { + for (const hookName of this.#validHookNames) { + const hookListeners = listeners[hookName]; + if (!hookListeners) { + continue; + } + + const hookListenersArray = Array.isArray(hookListeners) ? hookListeners : [hookListeners]; + for (const listener of hookListenersArray) { + if (typeof listener === 'function') { + this.addListener(hookName, listener); + } else { + this.addListener(hookName, listener.callback, listener.name); + } + } + } + } + + #assertValidHookName(hookName: any) { + if (!this.#validHookNames.includes(hookName)) { + throw new Error( + `Target ${getName(this.#eventTarget)} does not support a hook named "${String(hookName)}".`, + ); + } + } +} + +export class HookHandlerBuilder { + readonly #validHookNames: Array; + readonly #hookHandlers = new WeakMap>(); + readonly #onRunHook: OnRunHook | undefined; + + constructor(validHookNames: Array, onRunHook?: OnRunHook) { + this.#validHookNames = validHookNames; + this.#onRunHook = onRunHook; + } + + getFor(target: object): HookHandler { + let hookHandler = this.#hookHandlers.get(target); + if (!hookHandler) { + hookHandler = new HookHandler(target, this.#validHookNames, this.#onRunHook); + this.#hookHandlers.set(target, hookHandler); + } + + return hookHandler; + } +} + +function getName(obj: object) { + if (typeof obj === 'function') { + return `[class ${obj.name}]`; + } + + return `[instance ${obj.constructor.name}]`; +} + +export interface NewHookable { + /** + * Controls which hooks should be run. + * + * Possible values: + * - false: All hooks will be run. (default) + * - true: No hooks will be run. + * - An array of strings: The hooks listed in the array will not be run. + * - An object with the "except" property: Only the hooks listed in the array will be run. + */ + noHooks?: boolean | undefined | readonly HookNames[] | { except: readonly HookNames[] }; +} + +export function mayRunHook( + hookName: HookName, + noHooksConfig: NewHookable['noHooks'], +): boolean { + if (!noHooksConfig) { + return true; + } + + if (noHooksConfig === true) { + return false; + } + + if ('except' in noHooksConfig) { + return noHooksConfig.except.includes(hookName); + } + + return !noHooksConfig.includes(hookName); +} diff --git a/packages/core/src/import-models.ts b/packages/core/src/import-models.ts new file mode 100644 index 000000000000..bbcdacf3214b --- /dev/null +++ b/packages/core/src/import-models.ts @@ -0,0 +1,62 @@ +import { isPlainObject } from '@sequelize/utils'; +import glob from 'fast-glob'; +import uniq from 'lodash/uniq'; +import { pathToFileURL } from 'node:url'; +import type { ModelStatic } from './model.js'; +import { isModelStatic } from './utils/model-utils.js'; + +type ModelMatch = (path: string, exportName: string, exportValue: ModelStatic) => boolean; + +/** + * Imports all model classes exported in the file matching the specified globs. + * Useful when setting the "models" option in the Sequelize constructor. + * + * @param globPaths + * @param modelMatch + */ +export async function importModels( + globPaths: string | string[], + modelMatch?: ModelMatch, +): Promise { + if (Array.isArray(globPaths)) { + const promises: Array> = []; + + for (const globPath of globPaths) { + promises.push(importModels(globPath, modelMatch)); + } + + return uniq((await Promise.all(promises)).flat(1)); + } + + const promises: Array> = []; + for (const path of await glob(globPaths)) { + const url = pathToFileURL(path).href; + promises.push(importModelNoGlob(url, modelMatch)); + } + + return uniq((await Promise.all(promises)).flat(1)); +} + +async function importModelNoGlob(url: string, modelMatch?: ModelMatch): Promise { + let module = await import(url); + // When importing a CJS file, sometimes only the default export is available, + // as named exports depend on the file's exports being statically analyzable by node. + // The default export contains the contents of the file's `module.exports` + if (module.default && isPlainObject(module.default)) { + module = { ...module.default, ...module }; + } + + return Object.keys(module) + .filter(exportName => { + if (!isModelStatic(module[exportName])) { + return false; + } + + if (modelMatch) { + return modelMatch(url, exportName, module[exportName]); + } + + return true; + }) + .map(exportName => module[exportName]); +} diff --git a/packages/core/src/index.d.ts b/packages/core/src/index.d.ts new file mode 100644 index 000000000000..c8c4904ba839 --- /dev/null +++ b/packages/core/src/index.d.ts @@ -0,0 +1,102 @@ +/** + * This package contains the core functionality of Sequelize. + * Values can be imported as follows: + * + * ```js + * import { Model, DataTypes } from '@sequelize/core'; + * ``` + * + * The documentation is available at https://sequelize.org/docs/v7/ + * + * @module + */ + +export * from './abstract-dialect/connection-manager.js'; +export type { + ArrayOptions, + BindParamOptions, + BlobOptions, + DataType, + DataTypeClass, + DataTypeClassOrInstance, + DataTypeInstance, + DateOptions, + DecimalNumberOptions, + EnumOptions, + GeometryOptions, + IntegerOptions, + NumberOptions, + RangeOptions, + TextOptions, + TimeOptions, + VirtualOptions, +} from './abstract-dialect/data-types.js'; +export { + AbstractDialect, + type ConnectionOptions, + type DialectOptions, +} from './abstract-dialect/dialect.js'; +export { AbstractQueryGenerator } from './abstract-dialect/query-generator.js'; +export * from './abstract-dialect/query-generator.types.js'; +export * from './abstract-dialect/query-interface.js'; +export * from './abstract-dialect/query-interface.types.js'; +export * from './abstract-dialect/query.js'; +export type { AcquireConnectionOptions } from './abstract-dialect/replication-pool.js'; +export type { WhereOptions } from './abstract-dialect/where-sql-builder-types.js'; +export * from './associations/index.js'; +export * as DataTypes from './data-types.js'; +export { ConstraintChecking, Deferrable } from './deferrable.js'; +export * from './enums.js'; +export * from './errors/index.js'; +export { AssociationPath } from './expression-builders/association-path.js'; +export { Attribute } from './expression-builders/attribute.js'; +export { BaseSqlExpression } from './expression-builders/base-sql-expression.js'; +export { Identifier } from './expression-builders/identifier.js'; +export { JsonPath } from './expression-builders/json-path.js'; +export { JSON_NULL, SQL_NULL } from './expression-builders/json-sql-null.js'; +export { List } from './expression-builders/list.js'; +export { sql } from './expression-builders/sql.js'; +export { Value } from './expression-builders/value.js'; +export { GeoJsonType } from './geo-json.js'; +export type { + GeoJson, + GeoJsonGeometryCollection, + GeoJsonLineString, + GeoJsonMultiLineString, + GeoJsonMultiPoint, + GeoJsonMultiPolygon, + GeoJsonPoint, + GeoJsonPolygon, + PositionPosition, +} from './geo-json.js'; +export { importModels } from './import-models.js'; +export { ModelDefinition } from './model-definition.js'; +export { ModelRepository } from './model-repository.js'; +export * from './model-repository.types.js'; +export * from './model.js'; +export { Op, type OpTypes } from './operators.js'; +export * from './sequelize.js'; +export { + IsolationLevel, + Lock, + Transaction, + TransactionNestMode, + TransactionType, + type ManagedTransactionOptions, + type NormalizedTransactionOptions, + type TransactionOptions, +} from './transaction.js'; +// eslint-disable-next-line import/no-default-export -- legacy, will be removed in the future | TODO [>=8]: remove this alias +export { Sequelize as default } from './sequelize.js'; +export type { NormalizedOptions, Options, PoolOptions } from './sequelize.types.js'; +export { isModelStatic, isSameInitialModel } from './utils/model-utils.js'; +export { useInflection } from './utils/string.js'; +export type { Validator } from './utils/validator-extras.js'; + +// All functions are available on sql.x, but these are exported for backwards compatibility +export { Cast, cast } from './expression-builders/cast.js'; +export { Col, col } from './expression-builders/col.js'; +export { Fn, fn } from './expression-builders/fn.js'; +export { json } from './expression-builders/json.js'; +export { Literal, literal } from './expression-builders/literal.js'; +export { Where, where } from './expression-builders/where.js'; diff --git a/packages/core/src/index.js b/packages/core/src/index.js new file mode 100644 index 000000000000..4abd9bb4c480 --- /dev/null +++ b/packages/core/src/index.js @@ -0,0 +1,18 @@ +'use strict'; + +/** + * A Sequelize module that contains the sequelize entry point. + * + * @module sequelize + */ + +/** Exports the sequelize entry point. */ +const { Sequelize } = require('./sequelize'); + +// for backward compatibility, Sequelize is importable with both of these styles: +// const Sequelize = require('@sequelize/core') +// const { Sequelize } = require('@sequelize/core') +// TODO [>7]: Make sequelize only importable using the second form, so we can remove properties from the constructor +module.exports = Sequelize; +module.exports.Sequelize = Sequelize; +module.exports.default = Sequelize; diff --git a/packages/core/src/index.mjs b/packages/core/src/index.mjs new file mode 100644 index 000000000000..cd02003f491f --- /dev/null +++ b/packages/core/src/index.mjs @@ -0,0 +1,109 @@ +import Pkg from './index.js'; + +// export * from './lib/sequelize'; +export const Sequelize = Pkg.Sequelize; +export const fn = Pkg.fn; +export const Fn = Pkg.Fn; +export const col = Pkg.col; +export const Col = Pkg.Col; +export const cast = Pkg.cast; +export const Cast = Pkg.Cast; +export const literal = Pkg.literal; +export const Literal = Pkg.Literal; +export const json = Pkg.json; +export const where = Pkg.where; +export const Where = Pkg.Where; +export const List = Pkg.List; +export const Identifier = Pkg.Identifier; +export const JsonPath = Pkg.JsonPath; +export const AssociationPath = Pkg.AssociationPath; +export const Attribute = Pkg.Attribute; +export const Value = Pkg.Value; +export const sql = Pkg.sql; +export const and = Pkg.and; +export const or = Pkg.or; +export const SQL_NULL = Pkg.SQL_NULL; +export const JSON_NULL = Pkg.JSON_NULL; + +export const AbstractQueryInterface = Pkg.AbstractQueryInterface; +export const AbstractConnectionManager = Pkg.AbstractConnectionManager; +export const AbstractQueryGenerator = Pkg.AbstractQueryGenerator; +export const AbstractQuery = Pkg.AbstractQuery; +export const AbstractDialect = Pkg.AbstractDialect; + +// export * from './lib/model'; +export const Model = Pkg.Model; + +// export * from './lib/transaction'; +export const Transaction = Pkg.Transaction; +export const TransactionNestMode = Pkg.TransactionNestMode; +export const TransactionType = Pkg.TransactionType; +export const Lock = Pkg.Lock; +export const IsolationLevel = Pkg.IsolationLevel; + +// export * from './lib/associations/index'; +export const Association = Pkg.Association; +export const BelongsToAssociation = Pkg.BelongsToAssociation; +export const HasOneAssociation = Pkg.HasOneAssociation; +export const HasManyAssociation = Pkg.HasManyAssociation; +export const BelongsToManyAssociation = Pkg.BelongsToManyAssociation; + +// export * from './lib/errors'; +export const BaseError = Pkg.BaseError; + +export const AggregateError = Pkg.AggregateError; +export const AssociationError = Pkg.AssociationError; +export const BulkRecordError = Pkg.BulkRecordError; +export const ConnectionError = Pkg.ConnectionError; +export const DatabaseError = Pkg.DatabaseError; +export const EagerLoadingError = Pkg.EagerLoadingError; +export const EmptyResultError = Pkg.EmptyResultError; +export const InstanceError = Pkg.InstanceError; +export const OptimisticLockError = Pkg.OptimisticLockError; +export const QueryError = Pkg.QueryError; +export const SequelizeScopeError = Pkg.SequelizeScopeError; +export const ValidationError = Pkg.ValidationError; +export const ValidationErrorItem = Pkg.ValidationErrorItem; + +export const AccessDeniedError = Pkg.AccessDeniedError; +export const ConnectionAcquireTimeoutError = Pkg.ConnectionAcquireTimeoutError; +export const ConnectionRefusedError = Pkg.ConnectionRefusedError; +export const ConnectionTimedOutError = Pkg.ConnectionTimedOutError; +export const HostNotFoundError = Pkg.HostNotFoundError; +export const HostNotReachableError = Pkg.HostNotReachableError; +export const InvalidConnectionError = Pkg.InvalidConnectionError; + +export const ExclusionConstraintError = Pkg.ExclusionConstraintError; +export const ForeignKeyConstraintError = Pkg.ForeignKeyConstraintError; +export const TimeoutError = Pkg.TimeoutError; +export const UnknownConstraintError = Pkg.UnknownConstraintError; + +export const UniqueConstraintError = Pkg.UniqueConstraintError; + +// export { useInflection } from './lib/utils'; +export const useInflection = Pkg.useInflection; + +// export { QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable, ConstraintChecking, ParameterStyle } +export const QueryTypes = Pkg.QueryTypes; +export const Op = Pkg.Op; +export const TableHints = Pkg.TableHints; +export const IndexHints = Pkg.IndexHints; +export const DataTypes = Pkg.DataTypes; +export const GeoJsonType = Pkg.GeoJsonType; +export const Deferrable = Pkg.Deferrable; +export const ConstraintChecking = Pkg.ConstraintChecking; +export const ParameterStyle = Pkg.ParameterStyle; + +// export { Validator as validator } from './lib/utils/validator-extras'; +export const Validator = Pkg.Validator; + +export const ValidationErrorItemOrigin = Pkg.ValidationErrorItemOrigin; +export const ValidationErrorItemType = Pkg.ValidationErrorItemType; + +export const isModelStatic = Pkg.isModelStatic; +export const isSameInitialModel = Pkg.isSameInitialModel; +export const importModels = Pkg.importModels; +export const ManualOnDelete = Pkg.ManualOnDelete; + +// eslint-disable-next-line import/no-default-export -- legacy, will be removed in the future +export { default } from './index.js'; diff --git a/packages/core/src/instance-validator.d.ts b/packages/core/src/instance-validator.d.ts new file mode 100644 index 000000000000..a751372036b3 --- /dev/null +++ b/packages/core/src/instance-validator.d.ts @@ -0,0 +1,13 @@ +import type { Hookable } from './model'; + +// TODO: move this to "validate". "validate" should accept either ValidationOptions or a boolean +export interface ValidationOptions extends Hookable { + /** + * An array of strings. All properties that are in this array will not be validated + */ + skip?: string[]; + /** + * An array of strings. Only the properties that are in this array will be validated + */ + fields?: string[]; +} diff --git a/packages/core/src/instance-validator.js b/packages/core/src/instance-validator.js new file mode 100644 index 000000000000..8fda70bc9bd5 --- /dev/null +++ b/packages/core/src/instance-validator.js @@ -0,0 +1,486 @@ +'use strict'; + +import difference from 'lodash/difference'; +import forIn from 'lodash/forIn'; +import get from 'lodash/get'; +import { promisify } from 'node:util'; +import { AbstractDataType } from './abstract-dialect/data-types'; +import { validateDataType } from './abstract-dialect/data-types-utils'; +import { BelongsToAssociation } from './associations/belongs-to'; +import * as SequelizeError from './errors'; +import { BaseSqlExpression } from './expression-builders/base-sql-expression.js'; +import { getAllOwnKeys } from './utils/object'; +import { validator } from './utils/validator-extras'; + +/** + * Instance Validator. + * + * @param {Instance} modelInstance The model instance. + * @param {object} options A dictionary with options. + * + * @private + */ +export class InstanceValidator { + constructor(modelInstance, options) { + options = { + // assign defined and default options + hooks: true, + ...options, + }; + + if (options.fields && !options.skip) { + options.skip = difference( + Array.from(modelInstance.modelDefinition.attributes.keys()), + options.fields, + ); + } else { + options.skip ??= []; + } + + 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 + */ + this.validator = validator; + + /** + * All errors will be stored here from the validations. + * + * @type {Array} Will contain keys that correspond to attributes which will + * be Arrays of Errors. + * @private + */ + this.errors = []; + + /** + * @type {boolean} Indicates if validations are in progress + * @private + */ + this.inProgress = false; + } + + /** + * The main entry point for the Validation module, invoke to start the dance. + * + * @returns {Promise} + * @private + */ + async _validate() { + if (this.inProgress) { + throw new Error('Validations already in progress.'); + } + + this.inProgress = true; + + await Promise.all([this._perAttributeValidators(), this._customValidators()]); + + if (this.errors.length > 0) { + throw new SequelizeError.ValidationError(null, this.errors); + } + } + + /** + * Invoke the Validation sequence and run validation hooks if defined + * - Before Validation Model Hooks + * - Validation + * - On validation success: After Validation Model Hooks + * - On validation failure: Validation Failed Model Hooks + * + * @returns {Promise} + * @private + */ + async validate() { + return await (this.options.hooks ? this._validateAndRunHooks() : this._validate()); + } + + /** + * Invoke the Validation sequence and run hooks + * - Before Validation Model Hooks + * - Validation + * - On validation success: After Validation Model Hooks + * - On validation failure: Validation Failed Model Hooks + * + * @returns {Promise} + * @private + */ + async _validateAndRunHooks() { + await this.modelInstance.constructor.hooks.runAsync( + 'beforeValidate', + this.modelInstance, + this.options, + ); + + try { + await this._validate(); + } catch (error) { + const newError = await this.modelInstance.constructor.hooks.runAsync( + 'validationFailed', + this.modelInstance, + this.options, + error, + ); + throw newError || error; + } + + await this.modelInstance.constructor.hooks.runAsync( + 'afterValidate', + this.modelInstance, + this.options, + ); + + return this.modelInstance; + } + + /** + * Will run all the validators defined per attribute (built-in validators and custom validators) + * + * @returns {Promise} + * @private + */ + async _perAttributeValidators() { + // promisify all attribute invocations + const validators = []; + + const { attributes } = this.modelInstance.modelDefinition; + + for (const attribute of attributes.values()) { + const attrName = attribute.attributeName; + + if (this.options.skip.includes(attrName)) { + continue; + } + + const value = this.modelInstance.dataValues[attrName]; + + if (value instanceof BaseSqlExpression) { + continue; + } + + if (!attribute._autoGenerated && !attribute.autoIncrement) { + // perform validations based on schema + this._validateSchema(attribute, attrName, value); + } + + if (attribute.validate) { + validators.push(this._singleAttrValidate(value, attrName, attribute.allowNull)); + } + } + + return await Promise.all(validators); + } + + /** + * Will run all the custom validators defined in the model's options. + * + * @returns {Promise} + * @private + */ + async _customValidators() { + const validators = []; + + const validateOptions = this.modelInstance.constructor.options.validate; + + for (const validatorName of getAllOwnKeys(validateOptions)) { + if (this.options.skip.includes(validatorName)) { + continue; + } + + const validator = validateOptions[validatorName]; + + const valprom = this._invokeCustomValidator(validator, validatorName) + // errors are handled in settling, stub this + .catch(() => {}); + + validators.push(valprom); + } + + return await Promise.all(validators); + } + + /** + * Validate a single attribute with all the defined built-in validators and custom validators. + * + * @private + * + * @param {*} value Anything. + * @param {string} attributeName The attribute name. + * @param {boolean} allowNull Whether or not the schema allows null values + * + * @returns {Promise} A promise, will always resolve, auto populates error on this.error local object. + */ + async _singleAttrValidate(value, attributeName, allowNull) { + // If value is null and allowNull is false, no validators should run (see #9143) + if (value == null && !allowNull) { + // The schema validator (_validateSchema) has already generated the validation error. Nothing to do here. + return; + } + + // Promisify each validator + const validators = []; + + const attribute = this.modelInstance.modelDefinition.attributes.get(attributeName); + + forIn(attribute.validate, (test, validatorType) => { + if (['isUrl', 'isURL', 'isEmail'].includes(validatorType)) { + // Preserve backwards compat. Validator.js now expects the second param to isURL and isEmail to be an object + if (typeof test === 'object' && test !== null && test.msg) { + test = { + msg: test.msg, + }; + } else if (test === true) { + test = {}; + } + } + + // 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, attributeName), + ); + + return; + } + + // If value is null, built-in validators should not run (only custom validators have to run) (see #9134). + if (value === null || value === undefined) { + return; + } + + const validatorPromise = this._invokeBuiltinValidator( + value, + test, + validatorType, + attributeName, + ); + // errors are handled in settling, stub this + validatorPromise.catch(() => {}); + validators.push(validatorPromise); + }); + + return Promise.all( + validators.map(validator => + validator.catch(error => { + const isBuiltIn = Boolean(error.validatorName); + this._pushError( + isBuiltIn, + attributeName, + error, + value, + error.validatorName, + error.validatorArgs, + ); + }), + ), + ); + } + + /** + * Prepare and invoke a custom validator. + * + * @private + * + * @param {Function} validator The custom validator. + * @param {string} validatorType the custom validator type (name). + * @param {boolean} optAttrDefined Set to true if custom validator was defined from the attribute + * @param {*} optValue value for attribute + * @param {string} optField field for attribute + * + * @returns {Promise} A promise. + */ + async _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { + let isAsync = false; + + const validatorArity = validator.length; + // check if validator is async and requires a callback + let asyncArity = 1; + let errorKey = validatorType; + let invokeArgs; + if (optAttrDefined) { + asyncArity = 2; + invokeArgs = optValue; + errorKey = optField; + } + + if (validatorArity === asyncArity) { + isAsync = true; + } + + // TODO [>7]: validators should receive their model as the first argument, not through "this". + // Only exception to this is the @ModelValidator decorator when used on an instance method, but it is responsible for that + // behavior, not this! + if (isAsync) { + try { + if (optAttrDefined) { + return await promisify(validator.bind(this.modelInstance, invokeArgs))(); + } + + return await promisify(validator.bind(this.modelInstance))(); + } catch (error) { + return this._pushError(false, errorKey, error, optValue, validatorType); + } + } + + try { + return await validator.call(this.modelInstance, invokeArgs); + } catch (error) { + return this._pushError(false, errorKey, error, optValue, validatorType); + } + } + + /** + * Prepare and invoke a build-in validator. + * + * @private + * + * @param {*} value Anything. + * @param {*} test The test case. + * @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. + */ + 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 TypeError(`Invalid validator function: ${validatorType}`); + } + + 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, + }); + } + } + + /** + * Will extract arguments for the validator. + * + * @param {*} test The test case. + * @param {string} validatorType One of known to Sequelize validators. + * @param {string} field The field that is being validated. + * + * @private + */ + _extractValidatorArgs(test, validatorType, field) { + let validatorArgs = test.args || test; + const isLocalizedValidator = + typeof validatorArgs !== 'string' && + ['isAlpha', 'isAlphanumeric', 'isMobilePhone'].includes(validatorType); + + if (!Array.isArray(validatorArgs)) { + if (validatorType === 'isImmutable') { + validatorArgs = [validatorArgs, field, this.modelInstance]; + } else if (isLocalizedValidator || validatorType === 'isIP') { + validatorArgs = []; + } else { + validatorArgs = [validatorArgs]; + } + } else { + validatorArgs = [...validatorArgs]; + } + + return validatorArgs; + } + + /** + * Will validate a single field against its schema definition (isnull). + * + * @param {object} attribute As defined in the Schema. + * @param {string} attributeName The field name. + * @param {*} value anything. + * + * @private + */ + _validateSchema(attribute, attributeName, value) { + if (attribute.allowNull === false && value == null) { + const modelDefinition = this.modelInstance.modelDefinition; + + const association = Object.values(modelDefinition.associations).find( + association => + association instanceof BelongsToAssociation && + association.foreignKey === attribute.fieldName, + ); + + if (!association || !this.modelInstance.get(association.as)) { + const validators = modelDefinition.attributes.get(attributeName)?.validate; + const errMsg = get( + validators, + 'notNull.msg', + `${this.modelInstance.constructor.name}.${attributeName} cannot be null`, + ); + + this.errors.push( + new SequelizeError.ValidationErrorItem( + errMsg, + 'notNull violation', // sequelizeError.ValidationErrorItem.Origins.CORE, + attributeName, + value, + this.modelInstance, + 'is_null', + ), + ); + } + } + + const type = attribute.type; + if ( + value != null && + !(value instanceof BaseSqlExpression) && + type instanceof AbstractDataType + ) { + const error = validateDataType(value, type, attributeName, this.modelInstance); + if (error) { + this.errors.push(error); + } + } + } + + /** + * Signs all errors retaining the original. + * + * @param {boolean} isBuiltin - Determines if error is from builtin validator. + * @param {string} errorKey - name of invalid attribute. + * @param {Error|string} rawError - The original error. + * @param {string|number} value - The data that triggered the error. + * @param {string} fnName - Name of the validator, if any + * @param {Array} fnArgs - Arguments for the validator [function], if any + * + * @private + */ + _pushError(isBuiltin, errorKey, rawError, value, fnName, fnArgs) { + const message = rawError.message || rawError || 'Validation error'; + const error = new SequelizeError.ValidationErrorItem( + message, + 'Validation error', // sequelizeError.ValidationErrorItem.Origins.FUNCTION, + errorKey, + value, + this.modelInstance, + fnName, + isBuiltin ? fnName : undefined, + isBuiltin ? fnArgs : undefined, + ); + + error[InstanceValidator.RAW_KEY_NAME] = rawError; + + this.errors.push(error); + } +} + +/** + * The error key for arguments as passed by custom validators + * + * @type {string} + * @private + */ +InstanceValidator.RAW_KEY_NAME = 'original'; diff --git a/packages/core/src/model-definition.ts b/packages/core/src/model-definition.ts new file mode 100644 index 000000000000..94d317430030 --- /dev/null +++ b/packages/core/src/model-definition.ts @@ -0,0 +1,1040 @@ +import { MapView, SetView, cloneDeepPlainValues, pojo, some } from '@sequelize/utils'; +import isPlainObject from 'lodash/isPlainObject'; +import omit from 'lodash/omit'; +import NodeUtil from 'node:util'; +import { isDataTypeClass } from './abstract-dialect/data-types-utils.js'; +import { AbstractDataType } from './abstract-dialect/data-types.js'; +import type { IndexOptions, TableNameWithSchema } from './abstract-dialect/query-interface.js'; +import type { Association } from './associations/index.js'; +import * as DataTypes from './data-types.js'; +import { BaseError } from './errors/index.js'; +import type { HookHandler } from './hooks.js'; +import type { ModelHooks } from './model-hooks.js'; +import { staticModelHooks } from './model-hooks.js'; +import { conformIndex } from './model-internals.js'; +import type { + AttributeOptions, + BuiltModelOptions, + InitOptions, + Model, + ModelAttributes, + ModelOptions, + ModelStatic, + NormalizedAttributeOptions, + NormalizedAttributeReferencesOptions, +} from './model.js'; +import type { Sequelize } from './sequelize.js'; +import { fieldToColumn } from './utils/deprecations.js'; +import { toDefaultValue } from './utils/dialect.js'; +import { isModelStatic } from './utils/model-utils.js'; +import { getAllOwnEntries, removeUndefined } from './utils/object.js'; +import { generateIndexName, pluralize, underscoredIf } from './utils/string.js'; + +export interface TimestampAttributes { + createdAt?: string; + updatedAt?: string; + deletedAt?: string; +} + +/** + * The goal of this class is to store the definition of a model. + * + * It is part of the Repository Design Pattern. + * See https://github.com/sequelize/sequelize/issues/15389 for more details. + * + * There is only one ModelDefinition instance per model per sequelize instance. + */ +export class ModelDefinition { + readonly #sequelize: Sequelize; + readonly options: BuiltModelOptions; + readonly #table: TableNameWithSchema; + get table(): TableNameWithSchema { + return this.#table; + } + + readonly associations: { [associationName: string]: Association } = Object.create(null); + + /** + * The list of attributes that have *not* been normalized. + * This list can be mutated. Call {@link refreshAttributes} to update the normalized attributes ({@link attributes)}. + */ + readonly rawAttributes: { [attributeName: string]: AttributeOptions }; + + readonly #attributes = new Map(); + + /** + * The list of attributes that have been normalized. + * + * This map is fully frozen and cannot be modified directly. + * Modify {@link rawAttributes} then call {@link refreshAttributes} instead. + */ + readonly attributes = new MapView(this.#attributes); + + readonly #physicalAttributes = new Map(); + + /** + * The list of attributes that actually exist in the database, as opposed to {@link virtualAttributeNames}. + */ + readonly physicalAttributes = new MapView(this.#physicalAttributes); + + readonly #columns = new Map(); + readonly columns = new MapView(this.#columns); + + readonly #primaryKeyAttributeNames = new Set(); + + readonly primaryKeysAttributeNames = new SetView(this.#primaryKeyAttributeNames); + + /** + * List of attributes that cannot be modified by the user + */ + readonly #readOnlyAttributeNames = new Set(); + + /** + * List of attributes that cannot be modified by the user (read-only) + */ + readonly readOnlyAttributeNames = new SetView(this.#readOnlyAttributeNames); + + /** + * Records which attributes are the different built-in timestamp attributes + */ + readonly timestampAttributeNames: TimestampAttributes = Object.create(null); + + /** + * The name of the attribute that records the version of the model instance. + */ + readonly #versionAttributeName: string | undefined; + + get versionAttributeName(): string | undefined { + return this.#versionAttributeName; + } + + readonly #jsonAttributeNames = new Set(); + readonly jsonAttributeNames = new SetView(this.#jsonAttributeNames); + + readonly #virtualAttributeNames = new Set(); + + /** + * The list of attributes that do not really exist in the database. + */ + readonly virtualAttributeNames = new SetView(this.#virtualAttributeNames); + + readonly #attributesWithGetters = new Set(); + readonly attributesWithGetters = new SetView(this.#attributesWithGetters); + + readonly #attributesWithSetters = new Set(); + readonly attributesWithSetters = new SetView(this.#attributesWithSetters); + + /** + * @deprecated Code should not rely on this as users can create custom attributes. + */ + readonly #booleanAttributeNames = new Set(); + + /** + * @deprecated Code should not rely on this as users can create custom attributes. + */ + readonly booleanAttributeNames = new SetView(this.#booleanAttributeNames); + + /** + * @deprecated Code should not rely on this as users can create custom attributes. + */ + readonly #dateAttributeNames = new Set(); + + /** + * @deprecated Code should not rely on this as users can create custom attributes. + */ + readonly dateAttributeNames = new SetView(this.#dateAttributeNames); + + #autoIncrementAttributeName: string | null = null; + get autoIncrementAttributeName(): string | null { + return this.#autoIncrementAttributeName; + } + + readonly #defaultValues = new Map unknown>(); + readonly defaultValues = new MapView(this.#defaultValues); + + /** + * Final list of indexes, built by refreshIndexes + */ + #indexes: IndexOptions[] = []; + + // TODO: associated model can be any class, not just ModelStatic. + readonly model: ModelStatic; + + get modelName(): string { + return this.options.modelName; + } + + get underscored(): boolean { + return this.options.underscored; + } + + get sequelize(): Sequelize { + return this.#sequelize; + } + + // TODO: add generic type to ModelHooks (model, attributes) + get hooks(): HookHandler { + return staticModelHooks.getFor(this); + } + + constructor( + attributesOptions: ModelAttributes, + modelOptions: InitOptions, + model: ModelStatic, + ) { + if (!modelOptions.sequelize) { + throw new Error( + 'new ModelDefinition() expects a Sequelize instance to be passed through the option bag, which is the second parameter.', + ); + } + + if (!modelOptions.modelName) { + throw new Error( + 'new ModelDefinition() expects a modelName to be passed through the option bag, which is the second parameter.', + ); + } + + this.#sequelize = modelOptions.sequelize; + this.model = model; + + const globalOptions = this.#sequelize.options; + + // TODO: deep freeze this.options + // caution: mergeModelOptions mutates its first input + const validate = {} satisfies ModelOptions['validate']; + this.options = mergeModelOptions( + // default options + { + noPrimaryKey: false, + timestamps: true, + validate, + freezeTableName: false, + underscored: false, + paranoid: false, + schema: '', + schemaDelimiter: '', + defaultScope: {}, + scopes: {}, + name: {}, + indexes: [], + ...cloneDeepPlainValues(globalOptions.define, true), + }, + removeUndefined(modelOptions), + true, + ) as BuiltModelOptions; + + // @ts-expect-error -- guide to help users migrate to alternatives, these were deprecated in v6 + if (this.options.getterMethods || this.options.setterMethods) { + throw new Error(`Error in the definition of Model ${this.modelName}: The "getterMethods" and "setterMethods" options have been removed. + +If you need to use getters & setters that behave like attributes, use VIRTUAL attributes. +If you need regular getters & setters, define your model as a class and add getter & setters. +See https://sequelize.org/docs/v6/core-concepts/getters-setters-virtuals/#deprecated-in-sequelize-v7-gettermethods-and-settermethods for more information.`); + } + + this.options.name.plural ??= pluralize(this.options.modelName); + // Model Names must be singular! + this.options.name.singular ??= this.options.modelName; + + this.#sequelize.hooks.runSync('beforeDefine', attributesOptions, this.options); + + if (this.options.hooks) { + this.hooks.addListeners(this.options.hooks); + } + + if (!this.options.tableName) { + this.options.tableName = this.options.freezeTableName + ? this.modelName + : underscoredIf(this.options.name.plural, this.underscored); + } + + this.#table = Object.freeze( + this.sequelize.queryGenerator.extractTableDetails( + removeUndefined({ + tableName: this.options.tableName, + schema: this.options.schema, + delimiter: this.options.schemaDelimiter, + }), + ), + ); + + // error check options + for (const [validatorName, validator] of getAllOwnEntries(this.options.validate)) { + if (typeof validator !== 'function') { + throw new TypeError( + `Members of the validate option must be functions. Model: ${this.modelName}, error with validate member ${String(validatorName)}`, + ); + } + } + + // attributes that will be added at the start of this.rawAttributes (id) + const rawAttributes: { [attributeName: string]: AttributeOptions } = Object.create(null); + + for (const [attributeName, rawAttributeOrDataType] of getAllOwnEntries(attributesOptions)) { + if (typeof attributeName === 'symbol') { + throw new TypeError('Symbol attributes are not supported'); + } + + let rawAttribute: AttributeOptions; + try { + rawAttribute = this.sequelize.normalizeAttribute(rawAttributeOrDataType); + } catch (error) { + throw new BaseError( + `An error occurred for attribute ${attributeName} on model ${this.modelName}.`, + { cause: error }, + ); + } + + rawAttributes[attributeName] = rawAttribute; + + if (rawAttribute.field) { + fieldToColumn(); + } + } + + // setup names of timestamp attributes + if (this.options.timestamps) { + for (const key of ['createdAt', 'updatedAt', 'deletedAt'] as const) { + 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.timestampAttributeNames.createdAt = + typeof this.options.createdAt === 'string' ? this.options.createdAt : 'createdAt'; + + this.#readOnlyAttributeNames.add(this.timestampAttributeNames.createdAt); + } + + if (this.options.updatedAt !== false) { + this.timestampAttributeNames.updatedAt = + typeof this.options.updatedAt === 'string' ? this.options.updatedAt : 'updatedAt'; + this.#readOnlyAttributeNames.add(this.timestampAttributeNames.updatedAt); + } + + if (this.options.paranoid && this.options.deletedAt !== false) { + this.timestampAttributeNames.deletedAt = + typeof this.options.deletedAt === 'string' ? this.options.deletedAt : 'deletedAt'; + + this.#readOnlyAttributeNames.add(this.timestampAttributeNames.deletedAt); + } + } + + // setup name for version attribute + if (this.options.version) { + this.#versionAttributeName = + typeof this.options.version === 'string' ? this.options.version : 'version'; + this.#readOnlyAttributeNames.add(this.#versionAttributeName); + } + + this.rawAttributes = Object.create(null); + + // Add id if no primary key was manually added to definition + if ( + !this.options.noPrimaryKey && + !some(Object.values(rawAttributes), attr => Boolean(attr.primaryKey)) + ) { + if ('id' in rawAttributes && rawAttributes.id?.primaryKey === undefined) { + throw new Error( + `An attribute called 'id' was defined in model '${this.options.tableName}' but primaryKey is not set. This is likely to be an error, which can be fixed by setting its 'primaryKey' option to true. If this is intended, explicitly set its 'primaryKey' option to false`, + ); + } + + // add PK first for a clean attribute order + this.rawAttributes.id = { + type: DataTypes.INTEGER(), + allowNull: false, + primaryKey: true, + autoIncrement: true, + _autoGenerated: true, + }; + } + + // add all user defined attributes + + for (const [attributeName, rawAttribute] of Object.entries(rawAttributes)) { + this.rawAttributes[attributeName] = rawAttribute; + } + + // add timestamp & version last for a clean attribute order + + if (this.timestampAttributeNames.createdAt) { + this.#addTimestampAttribute(this.timestampAttributeNames.createdAt, false); + } + + if (this.timestampAttributeNames.updatedAt) { + this.#addTimestampAttribute(this.timestampAttributeNames.updatedAt, false); + } + + if (this.timestampAttributeNames.deletedAt) { + this.#addTimestampAttribute(this.timestampAttributeNames.deletedAt, true); + } + + if (this.#versionAttributeName) { + const existingAttribute: AttributeOptions | undefined = + this.rawAttributes[this.#versionAttributeName]; + + if (existingAttribute?.type && !(existingAttribute.type instanceof DataTypes.INTEGER)) { + throw new Error(`Sequelize is trying to add the version attribute ${NodeUtil.inspect(this.#versionAttributeName)} to Model ${NodeUtil.inspect(this.modelName)}, +but an attribute with the same name already exists and declares a data type. +The "version" attribute is managed automatically by Sequelize, and its type must be DataTypes.INTEGER. Please either: +- remove the "type" property from your attribute definition, +- rename either your attribute or the version attribute, +- or disable the automatic timestamp attributes.`); + } + + if (existingAttribute?.allowNull === true) { + throw new Error(`Sequelize is trying to add the timestamp attribute ${NodeUtil.inspect(this.#versionAttributeName)} to Model ${NodeUtil.inspect(this.modelName)}, +but an attribute with the same name already exists and its allowNull option (${existingAttribute.allowNull}) conflicts with the one Sequelize is trying to set (false). +The "version" attribute is managed automatically by Sequelize, and its nullability is not configurable. Please either: +- remove the "allowNull" property from your attribute definition, +- rename either your attribute or the version attribute, +- or disable the automatic version attribute.`); + } + + this.rawAttributes[this.#versionAttributeName] = { + ...existingAttribute, + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + _autoGenerated: true, + }; + } + + this.refreshAttributes(); + } + + #addTimestampAttribute(attributeName: string, allowNull: boolean) { + const existingAttribute: AttributeOptions | undefined = this.rawAttributes[attributeName]; + + if (existingAttribute?.type && !(existingAttribute.type instanceof DataTypes.DATE)) { + throw new Error(`Sequelize is trying to add the timestamp attribute ${NodeUtil.inspect(attributeName)} to Model ${NodeUtil.inspect(this.modelName)}, +but an attribute with the same name already exists and declares a data type. +Timestamp attributes are managed automatically by Sequelize, and their data type must be DataTypes.DATE (https://github.com/sequelize/sequelize/issues/2572). Please either: +- remove the "type" property from your attribute definition, +- rename either your attribute or the timestamp attribute, +- or disable the automatic timestamp attributes.`); + } + + if (existingAttribute?.allowNull != null && existingAttribute?.allowNull !== allowNull) { + throw new Error(`Sequelize is trying to add the timestamp attribute ${NodeUtil.inspect(attributeName)} to Model ${NodeUtil.inspect(this.modelName)}, +but an attribute with the same name already exists and its allowNull option (${existingAttribute.allowNull}) conflicts with the one Sequelize is trying to set (${allowNull}). +Timestamp attributes are managed automatically by Sequelize, and their nullability is not configurable. Please either: +- remove the "allowNull" property from your attribute definition, +- rename either your attribute or the timestamp attribute, +- or disable the automatic timestamp attributes.`); + } + + const { defaultTimestampPrecision } = this.#sequelize.options; + + this.rawAttributes[attributeName] = { + // @ts-expect-error -- this property is not mandatory in timestamp attributes + type: + typeof defaultTimestampPrecision === 'number' + ? DataTypes.DATE(defaultTimestampPrecision) + : DataTypes.DATE, + ...this.rawAttributes[attributeName], + allowNull, + _autoGenerated: true, + }; + } + + /** + * Normalizes all attribute definitions, using {@link rawAttributes} as the source. + */ + refreshAttributes() { + this.hooks.runSync('beforeDefinitionRefresh'); + + this.#attributes.clear(); + this.#booleanAttributeNames.clear(); + this.#dateAttributeNames.clear(); + this.#jsonAttributeNames.clear(); + this.#virtualAttributeNames.clear(); + this.#physicalAttributes.clear(); + this.#defaultValues.clear(); + this.#columns.clear(); + this.#primaryKeyAttributeNames.clear(); + this.#autoIncrementAttributeName = null; + this.#attributesWithGetters.clear(); + this.#attributesWithSetters.clear(); + + // indexes defined through attributes + const attributeIndexes: IndexOptions[] = []; + + for (const [attributeName, rawAttribute] of Object.entries(this.rawAttributes)) { + if (typeof attributeName !== 'string') { + throw new TypeError( + `Attribute names must be strings, but "${this.modelName}" declared a non-string attribute: ${NodeUtil.inspect(attributeName)}`, + ); + } + + // Checks whether the name is ambiguous with isColString + // we check whether the attribute starts *or* ends because the following query: + // { '$json.key$' } + // could be interpreted as both + // "json"."key" (accessible attribute 'key' on model 'json') + // or + // "$json" #>> {key$} (accessing key 'key$' on attribute '$json') + if (attributeName.startsWith('$') || attributeName.endsWith('$')) { + throw new Error( + `Name of attribute "${attributeName}" in model "${this.modelName}" cannot start or end with "$" as "$attribute$" is reserved syntax used to reference nested columns in queries.`, + ); + } + + if (attributeName.includes('.')) { + throw new Error( + `Name of attribute "${attributeName}" in model "${this.modelName}" cannot include the character "." as it would be ambiguous with the syntax used to reference nested columns, and nested json keys, in queries.`, + ); + } + + if (attributeName.includes('::')) { + throw new Error( + `Name of attribute "${attributeName}" in model "${this.modelName}" cannot include the character sequence "::" as it is reserved syntax used to cast attributes in queries.`, + ); + } + + if (attributeName.includes('->')) { + throw new Error( + `Name of attribute "${attributeName}" in model "${this.modelName}" cannot include the character sequence "->" as it is reserved syntax used in SQL generated by Sequelize to target nested associations.`, + ); + } + + if (!isPlainObject(rawAttribute)) { + throw new Error( + `Attribute "${this.modelName}.${attributeName}" must be specified as a plain object.`, + ); + } + + if (!rawAttribute.type) { + throw new Error( + `Attribute "${this.modelName}.${attributeName}" does not specify its DataType.`, + ); + } + + try { + const columnName = + rawAttribute.columnName ?? + rawAttribute.field ?? + underscoredIf(attributeName, this.underscored); + + const builtAttribute = pojo({ + ...omit(rawAttribute, ['unique', 'index']), + type: this.#sequelize.normalizeDataType(rawAttribute.type), + references: normalizeReference(rawAttribute.references), + + // fieldName is a legacy name, renamed to attributeName. + fieldName: attributeName, + attributeName, + + // field is a legacy name, renamed to columnName. + field: columnName, + columnName, + + // @ts-expect-error -- undocumented legacy property, to be removed. + Model: this.model, + + // undocumented legacy property, to be removed. + _modelAttribute: true, + }); + + if (builtAttribute.type instanceof AbstractDataType) { + // @ts-expect-error -- defaultValue is not readOnly yet! + builtAttribute.type = builtAttribute.type.withUsageContext({ + // TODO: Repository Pattern - replace with ModelDefinition + model: this.model, + attributeName, + sequelize: this.sequelize, + }); + } + + if (Object.hasOwn(builtAttribute, 'defaultValue')) { + if (isDataTypeClass(builtAttribute.defaultValue)) { + // @ts-expect-error -- defaultValue is not readOnly yet! + builtAttribute.defaultValue = new builtAttribute.defaultValue(); + } + + this.#defaultValues.set(attributeName, () => toDefaultValue(builtAttribute.defaultValue)); + } + + // TODO: remove "notNull" & "isNull" validators + if (rawAttribute.allowNull !== false && rawAttribute.validate?.notNull) { + throw new Error(`"notNull" validator is only allowed with "allowNull:false"`); + } + + if (builtAttribute.primaryKey === true) { + this.#primaryKeyAttributeNames.add(attributeName); + } + + if (builtAttribute.type instanceof DataTypes.BOOLEAN) { + this.#booleanAttributeNames.add(attributeName); + } else if ( + builtAttribute.type instanceof DataTypes.DATE || + rawAttribute.type instanceof DataTypes.DATEONLY + ) { + this.#dateAttributeNames.add(attributeName); + } else if (builtAttribute.type instanceof DataTypes.JSON) { + this.#jsonAttributeNames.add(attributeName); + } + + if (Object.hasOwn(rawAttribute, 'unique') && rawAttribute.unique) { + const uniqueIndexes = Array.isArray(rawAttribute.unique) + ? rawAttribute.unique + : [rawAttribute.unique]; + + for (const uniqueIndex of uniqueIndexes) { + if (uniqueIndex === true || typeof uniqueIndex === 'string') { + attributeIndexes.push({ + unique: true, + fields: [builtAttribute.columnName], + ...(typeof uniqueIndex === 'string' ? { name: uniqueIndex } : undefined), + }); + } else { + attributeIndexes.push({ + ...uniqueIndex, + unique: true, + fields: [builtAttribute.columnName], + }); + } + } + } + + if (Object.hasOwn(rawAttribute, 'index') && rawAttribute.index) { + const indexes = Array.isArray(rawAttribute.index) + ? rawAttribute.index + : [rawAttribute.index]; + + for (const index of indexes) { + const jsonbIndexDefaults = + rawAttribute.type instanceof DataTypes.JSONB ? { using: 'gin' } : undefined; + + if (!index) { + continue; + } + + if (index === true || typeof index === 'string') { + attributeIndexes.push({ + fields: [builtAttribute.columnName], + ...(typeof index === 'string' ? { name: index } : undefined), + ...jsonbIndexDefaults, + }); + } else { + // @ts-expect-error -- forbidden property + if (index.fields) { + throw new Error( + '"fields" cannot be specified for indexes defined on attributes. Use the "indexes" option on the table definition instead. You can also customize how this attribute is part of the index by specifying the "attribute" option on the index.', + ); + } + + const { attribute: indexAttributeOptions, ...indexOptions } = index; + + attributeIndexes.push({ + ...jsonbIndexDefaults, + ...indexOptions, + fields: [ + indexAttributeOptions + ? { + ...indexAttributeOptions, + name: builtAttribute.columnName, + } + : builtAttribute.columnName, + ], + }); + } + } + } + + if (builtAttribute.autoIncrement) { + if (this.#autoIncrementAttributeName) { + throw new Error( + `Only one autoIncrement attribute is allowed per model, but both ${NodeUtil.inspect(attributeName)} and ${NodeUtil.inspect(this.#autoIncrementAttributeName)} are marked as autoIncrement.`, + ); + } + + this.#autoIncrementAttributeName = attributeName; + } + + Object.freeze(builtAttribute); + + this.#attributes.set(attributeName, builtAttribute); + this.#columns.set(builtAttribute.columnName, builtAttribute); + + if (builtAttribute.type instanceof DataTypes.VIRTUAL) { + this.#virtualAttributeNames.add(attributeName); + } else { + this.#physicalAttributes.set(attributeName, builtAttribute); + } + + if (builtAttribute.get) { + this.#attributesWithGetters.add(attributeName); + } + + if (builtAttribute.set) { + this.#attributesWithSetters.add(attributeName); + } + } catch (error) { + throw new BaseError( + `An error occurred while normalizing attribute ${JSON.stringify(attributeName)} in model ${JSON.stringify(this.modelName)}.`, + { cause: error }, + ); + } + } + + this.#refreshIndexes(attributeIndexes); + + this.hooks.runSync('afterDefinitionRefresh'); + } + + #refreshIndexes(attributeIndexes: IndexOptions[]): void { + this.#indexes = []; + + for (const index of this.options.indexes) { + this.#addIndex(index); + } + + for (const index of attributeIndexes) { + this.#addIndex(index); + } + } + + #addIndex(index: IndexOptions): void { + index = this.#nameIndex(conformIndex(index)); + + if (typeof index.fields?.[0] === 'string') { + const column = this.columns.get(index.fields[0])?.attributeName; + + if (column) { + // @ts-expect-error -- TODO: remove this 'column'. It does not work with composite indexes, and is only used by db2. On top of that, it's named "column" but is actually an attribute name. + index.column = column; + } + } + + const existingIndex = this.#indexes.find(i => i.name === index.name); + if (existingIndex == null) { + this.#indexes.push(index); + + return; + } + + for (const key of Object.keys(index) as Array) { + if (index[key] === undefined) { + continue; + } + + // @ts-expect-error -- TODO: remove this 'column'. It does not work with composite indexes, and is only used by db2 which should use fields instead. + if (key === 'column') { + continue; + } + + // TODO: rename "fields" to columnNames + if (key === 'fields') { + if (existingIndex.fields == null) { + existingIndex.fields = index.fields!; + } else { + existingIndex.fields = [...existingIndex.fields, ...index.fields!]; + } + + continue; + } + + if (existingIndex[key] === undefined) { + // @ts-expect-error -- same type + existingIndex[key] = index[key]; + } + + if (existingIndex[key] !== index[key]) { + throw new Error( + `Index "${index.name}" has conflicting options: "${key}" was defined with different values ${NodeUtil.inspect(existingIndex[key])} and ${NodeUtil.inspect(index[key])}.`, + ); + } + } + } + + #nameIndex(newIndex: IndexOptions): IndexOptions { + if (Object.hasOwn(newIndex, 'name')) { + return newIndex; + } + + const newName = generateIndexName(this.table, newIndex); + + // TODO: check for collisions on *all* models, not just this one, as index names are global. + for (const index of this.getIndexes()) { + if (index.name === newName) { + throw new Error(`Sequelize tried to give the name "${newName}" to index: +${NodeUtil.inspect(newIndex)} +on model "${this.modelName}", but that name is already taken by index: +${NodeUtil.inspect(index)} + +Specify a different name for either index to resolve this issue.`); + } + } + + newIndex.name = newName; + + return newIndex; + } + + getIndexes(): readonly IndexOptions[] { + return this.#indexes; + } + + /** + * Returns the column name corresponding to the given attribute name. + * + * @param attributeName + */ + getColumnName(attributeName: string): string { + const attribute = this.#attributes.get(attributeName); + + if (attribute == null) { + throw new Error(`Attribute "${attributeName}" does not exist on model "${this.modelName}".`); + } + + return attribute.columnName; + } + + /** + * Returns the column name corresponding to the given attribute name if it exists, otherwise returns the attribute name. + * + * ⚠️ Using this method is highly discouraged. Users should specify column names & attribute names separately, to prevent any ambiguity. + * + * @param attributeName + */ + getColumnNameLoose(attributeName: string): string { + const attribute = this.#attributes.get(attributeName); + + return attribute?.columnName ?? attributeName; + } + + /** + * Follows the association path and returns the association at the end of the path. + * For instance, say we have a model User, associated to a model Profile, associated to a model Address. + * + * If we call `User.modelDefinition.getAssociation(['profile', 'address'])`, we will get the association named `address` in the model Profile. + * If we call `User.modelDefinition.getAssociation(['profile'])`, we will get the association named `profile` in the model User. + * + * @param associationPath + */ + getAssociation(associationPath: readonly string[] | string): Association | undefined { + if (typeof associationPath === 'string') { + return this.associations[associationPath]; + } + + return this.#getAssociationFromPathMut([...associationPath]); + } + + #getAssociationFromPathMut(associationPath: string[]): Association | undefined { + if (associationPath.length === 0) { + return undefined; + } + + const associationName = associationPath.shift()!; + const association = this.associations[associationName]; + + if (association == null) { + return undefined; + } + + if (associationPath.length === 0) { + return association; + } + + return association.target.modelDefinition.#getAssociationFromPathMut(associationPath); + } + + isParanoid(): boolean { + return Boolean(this.timestampAttributeNames.deletedAt); + } +} + +const modelDefinitionListeners = new Set<(model: ModelStatic) => void>(); +export function listenForModelDefinition(callback: (model: ModelStatic) => void): void { + modelDefinitionListeners.add(callback); +} + +const modelDefinitions = new WeakMap>(); + +export function registerModelDefinition( + model: ModelStatic, + modelDefinition: ModelDefinition, +): void { + if (modelDefinitions.has(model)) { + throw new Error( + `Model ${model.name} has already been initialized. Models can only belong to one Sequelize instance. Registering the same model with multiple Sequelize instances is not yet supported. Please see https://github.com/sequelize/sequelize/issues/15389`, + ); + } + + modelDefinitions.set(model, modelDefinition); + + for (const listener of modelDefinitionListeners) { + listener(model); + } +} + +export function removeModelDefinition(model: ModelStatic): void { + modelDefinitions.delete(model); +} + +export function hasModelDefinition(model: ModelStatic): boolean { + return modelDefinitions.has(model); +} + +export function getModelDefinition(model: ModelStatic): ModelDefinition { + const definition = modelDefinitions.get(model); + if (!definition) { + throw new Error(`Model ${model.name} has not been initialized yet.`); + } + + return definition; +} + +export function normalizeReference( + references: AttributeOptions['references'], +): NormalizedAttributeReferencesOptions | undefined { + if (!references) { + return undefined; + } + + if (typeof references === 'string') { + return Object.freeze( + banReferenceModel({ + table: references, + }), + ); + } + + if (isModelStatic(references)) { + return Object.freeze( + banReferenceModel({ + table: references.table, + }), + ); + } + + const { model, table, ...referencePassDown } = references; + + if (model && table) { + throw new Error('"references" cannot contain both "model" and "tableName"'); + } + + // It's possible that the model has not been defined yet but the user configured other fields, in cases where + // the reference is added by an association initializing itself. + // If that happens, we won't add the reference until the association is initialized and this method gets called again. + if (!model && !table) { + return undefined; + } + + if (model || table) { + return Object.freeze( + banReferenceModel({ + table: model ? model.table : table!, + ...referencePassDown, + }), + ); + } +} + +function banReferenceModel(reference: T): T { + Object.defineProperty(reference, 'model', { + enumerable: false, + get() { + throw new Error( + 'references.model has been renamed to references.tableName in normalized references options.', + ); + }, + }); + + return reference; +} + +/** + * This method mutates the first parameter. + * + * @param existingModelOptions + * @param options + * @param overrideOnConflict + */ +export function mergeModelOptions( + existingModelOptions: ModelOptions, + options: ModelOptions, + overrideOnConflict: boolean, +): ModelOptions { + // merge-able: scopes, indexes + for (const [optionName, optionValue] of Object.entries(options) as Array< + [keyof ModelOptions, any] + >) { + if (existingModelOptions[optionName] === undefined) { + existingModelOptions[optionName] = optionValue; + continue; + } + + // These are objects. We merge their properties, unless the same key is used in both values. + if (optionName === 'scopes' || optionName === 'validate') { + for (const [subOptionName, subOptionValue] of getAllOwnEntries(optionValue)) { + // @ts-expect-error -- dynamic type, not worth typing + if (existingModelOptions[optionName][subOptionName] === subOptionValue) { + continue; + } + + if (!overrideOnConflict && subOptionName in existingModelOptions[optionName]) { + throw new Error( + `Trying to set the option ${optionName}[${JSON.stringify(subOptionName)}], but a value already exists.`, + ); + } + + // @ts-expect-error -- runtime type checking is enforced by model + existingModelOptions[optionName][subOptionName] = subOptionValue; + } + + continue; + } + + if (optionName === 'hooks') { + const existingHooks = existingModelOptions.hooks!; + for (const hookType of Object.keys(optionValue) as Array) { + if (!existingHooks[hookType]) { + // @ts-expect-error -- type is too complex for typescript + existingHooks[hookType] = optionValue[hookType]; + continue; + } + + const existingHooksOfType = Array.isArray(existingHooks[hookType]) + ? existingHooks[hookType] + : [existingHooks[hookType]]; + + if (!Array.isArray(optionValue[hookType])) { + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- became valid in TS 5.8 + // @ts-ignore -- typescript doesn't like this merge algorithm. + existingHooks[hookType] = [...existingHooksOfType, optionValue[hookType]]; + } else { + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- This error only occurs on TS 5.3+ + // @ts-ignore -- typescript doesn't like this merge algorithm. + existingHooks[hookType] = [...existingHooksOfType, ...optionValue[hookType]]; + } + } + + continue; + } + + // This is an array. Simple array merge. + if (optionName === 'indexes') { + existingModelOptions.indexes = [...existingModelOptions.indexes!, ...optionValue]; + + continue; + } + + if (!overrideOnConflict && optionValue !== existingModelOptions[optionName]) { + throw new Error(`Trying to set the option ${optionName}, but a value already exists.`); + } + + existingModelOptions[optionName] = optionValue; + } + + return existingModelOptions; +} diff --git a/packages/core/src/model-hooks.ts b/packages/core/src/model-hooks.ts new file mode 100644 index 000000000000..09f99ffb359e --- /dev/null +++ b/packages/core/src/model-hooks.ts @@ -0,0 +1,189 @@ +import type { + AfterAssociateEventData, + AssociationOptions, + BeforeAssociateEventData, +} from './associations/index.js'; +import type { AsyncHookReturn } from './hooks.js'; +import { HookHandlerBuilder } from './hooks.js'; +import type { ValidationOptions } from './instance-validator.js'; +import type { DestroyManyOptions } from './model-repository.types.js'; +import type { + BulkCreateOptions, + CountOptions, + CreateOptions, + DestroyOptions, + FindOptions, + InstanceDestroyOptions, + InstanceRestoreOptions, + InstanceUpdateOptions, + Model, + ModelStatic, + RestoreOptions, + UpdateOptions, + UpsertOptions, +} from './model.js'; +import type { SyncOptions } from './sequelize.js'; + +export interface ModelHooks { + beforeValidate(instance: M, options: ValidationOptions): AsyncHookReturn; + afterValidate(instance: M, options: ValidationOptions): AsyncHookReturn; + validationFailed(instance: M, options: ValidationOptions, error: unknown): AsyncHookReturn; + beforeCreate(attributes: M, options: CreateOptions): AsyncHookReturn; + afterCreate(attributes: M, options: CreateOptions): AsyncHookReturn; + beforeDestroy(instance: M, options: InstanceDestroyOptions): AsyncHookReturn; + afterDestroy(instance: M, options: InstanceDestroyOptions): AsyncHookReturn; + beforeDestroyMany(instances: M[], options: DestroyManyOptions): AsyncHookReturn; + afterDestroyMany( + instances: readonly M[], + options: DestroyManyOptions, + deletedCount: number, + ): AsyncHookReturn; + beforeRestore(instance: M, options: InstanceRestoreOptions): AsyncHookReturn; + afterRestore(instance: M, options: InstanceRestoreOptions): AsyncHookReturn; + beforeUpdate(instance: M, options: InstanceUpdateOptions): AsyncHookReturn; + afterUpdate(instance: M, options: InstanceUpdateOptions): AsyncHookReturn; + beforeUpsert(attributes: M, options: UpsertOptions): AsyncHookReturn; + afterUpsert( + attributes: [M, boolean | null], + options: UpsertOptions, + ): AsyncHookReturn; + beforeSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions, + ): AsyncHookReturn; + afterSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions, + ): AsyncHookReturn; + beforeBulkCreate(instances: M[], options: BulkCreateOptions): AsyncHookReturn; + afterBulkCreate( + instances: readonly M[], + options: BulkCreateOptions, + ): AsyncHookReturn; + beforeBulkDestroy(options: DestroyOptions): AsyncHookReturn; + afterBulkDestroy(options: DestroyOptions): AsyncHookReturn; + _UNSTABLE_beforeBulkDestroy(options: DestroyOptions): AsyncHookReturn; + _UNSTABLE_afterBulkDestroy( + options: DestroyOptions, + deletedCount: number, + ): AsyncHookReturn; + beforeBulkRestore(options: RestoreOptions): AsyncHookReturn; + afterBulkRestore(options: RestoreOptions): AsyncHookReturn; + beforeBulkUpdate(options: UpdateOptions): AsyncHookReturn; + afterBulkUpdate(options: UpdateOptions): AsyncHookReturn; + + /** + * A hook that is run at the start of {@link Model.count} + */ + beforeCount(options: CountOptions): AsyncHookReturn; + + /** + * A hook that is run before a find (select) query + */ + beforeFind(options: FindOptions): AsyncHookReturn; + + /** + * A hook that is run before a find (select) query, after any `{ include: {all: ...} }` options are expanded + * + * @deprecated use `beforeFind` instead + */ + beforeFindAfterExpandIncludeAll(options: FindOptions): AsyncHookReturn; + + /** + * A hook that is run before a find (select) query, after all option have been normalized + * + * @deprecated use `beforeFind` instead + */ + beforeFindAfterOptions(options: FindOptions): AsyncHookReturn; + /** + * A hook that is run after a find (select) query + */ + afterFind( + instancesOrInstance: readonly M[] | M | null, + options: FindOptions, + ): AsyncHookReturn; + + /** + * A hook that is run at the start of {@link Model.sync} + */ + beforeSync(options: SyncOptions): AsyncHookReturn; + + /** + * A hook that is run at the end of {@link Model.sync} + */ + afterSync(options: SyncOptions): AsyncHookReturn; + beforeAssociate( + data: BeforeAssociateEventData, + options: AssociationOptions, + ): AsyncHookReturn; + afterAssociate(data: AfterAssociateEventData, options: AssociationOptions): AsyncHookReturn; + + /** + * Runs before the definition of the model changes because {@link ModelDefinition#refreshAttributes} was called. + */ + beforeDefinitionRefresh(): void; + + /** + * Runs after the definition of the model has changed because {@link ModelDefinition#refreshAttributes} was called. + */ + afterDefinitionRefresh(): void; +} + +export const validModelHooks: Array = [ + 'beforeValidate', + 'afterValidate', + 'validationFailed', + 'beforeCreate', + 'afterCreate', + 'beforeDestroy', + 'afterDestroy', + 'beforeDestroyMany', + 'afterDestroyMany', + 'beforeRestore', + 'afterRestore', + 'beforeUpdate', + 'afterUpdate', + 'beforeUpsert', + 'afterUpsert', + 'beforeSave', + 'afterSave', + 'beforeBulkCreate', + 'afterBulkCreate', + 'beforeBulkDestroy', + 'afterBulkDestroy', + '_UNSTABLE_beforeBulkDestroy', + '_UNSTABLE_afterBulkDestroy', + 'beforeBulkRestore', + 'afterBulkRestore', + 'beforeBulkUpdate', + 'afterBulkUpdate', + 'beforeCount', + 'beforeFind', + 'beforeFindAfterExpandIncludeAll', + 'beforeFindAfterOptions', + 'afterFind', + 'beforeSync', + 'afterSync', + 'beforeAssociate', + 'afterAssociate', + 'beforeDefinitionRefresh', + 'afterDefinitionRefresh', +]; + +export const staticModelHooks = new HookHandlerBuilder( + validModelHooks, + async (eventTarget, isAsync, hookName: keyof ModelHooks, args) => { + // This forwards hooks run on Models to the Sequelize instance's hooks. + const model = eventTarget as ModelStatic; + + if (!model.sequelize) { + throw new Error('Model must be initialized before running hooks on it.'); + } + + if (isAsync) { + await model.sequelize.hooks.runAsync(hookName, ...args); + } else { + model.sequelize.hooks.runSync(hookName, ...args); + } + }, +); diff --git a/packages/core/src/model-internals.ts b/packages/core/src/model-internals.ts new file mode 100644 index 000000000000..a1fe09458629 --- /dev/null +++ b/packages/core/src/model-internals.ts @@ -0,0 +1,296 @@ +import { cloneDeepPlainValues, freezeDescendants } from '@sequelize/utils'; +import NodeUtil from 'node:util'; +import type { IndexOptions } from './abstract-dialect/query-interface.js'; +import type { WhereAttributeHash } from './abstract-dialect/where-sql-builder-types.js'; +import { EagerLoadingError } from './errors'; +import type { Attributes, Filterable, Model, Transactionable } from './model'; +import type { ModelDefinition } from './model-definition.js'; +import type { Sequelize } from './sequelize'; +import { isDevEnv } from './utils/check.js'; +import { isModelStatic } from './utils/model-utils.js'; +// TODO: strictly type this file during the TS migration of model.js + +// The goal of this file is to include the different private methods that are currently present on the Model class. +// This reduces the risk of having a user implement a static method with the same name as us in their Model subclass, +// it also prevents accessing internal methods. + +export function _validateIncludedElements(options: any, tableNames: any = {}) { + if (!isModelStatic(options.model)) { + throw new TypeError('options.model must be provided, and a Model subclass.'); + } + + const ModelSubclass = options.model; + + options.includeNames = []; + options.includeMap = {}; + + /* Legacy */ + options.hasSingleAssociation = false; + options.hasMultiAssociation = false; + + if (!options.parent) { + options.topModel = options.model; + options.topLimit = options.limit; + } + + options.include = options.include.map((include: any) => { + include = ModelSubclass._conformInclude(include, options.model); + include.parent = options; + include.topLimit = options.topLimit; + + ModelSubclass._validateIncludedElement.call(options.model, include, tableNames, options); + + if (include.duplicating === undefined) { + include.duplicating = include.association.isMultiAssociation; + } + + include.hasDuplicating = include.hasDuplicating || include.duplicating; + include.hasRequired = include.hasRequired || include.required; + + options.hasDuplicating = options.hasDuplicating || include.hasDuplicating; + options.hasRequired = options.hasRequired || include.required; + + options.hasWhere = options.hasWhere || include.hasWhere || Boolean(include.where); + + return include; + }); + + for (const include of options.include) { + include.hasParentWhere = options.hasParentWhere || Boolean(options.where); + include.hasParentRequired = options.hasParentRequired || Boolean(options.required); + + if (include.subQuery !== false && options.hasDuplicating && options.topLimit) { + if (include.duplicating) { + include.subQuery = include.subQuery || false; + include.subQueryFilter = include.hasRequired; + } else { + include.subQuery = include.hasRequired; + include.subQueryFilter = false; + } + } else { + include.subQuery = include.subQuery || false; + if (include.duplicating) { + include.subQueryFilter = include.subQuery; + } else { + include.subQueryFilter = false; + include.subQuery = + include.subQuery || + (include.hasParentRequired && include.hasRequired && !include.separate); + } + } + + options.includeMap[include.as] = include; + options.includeNames.push(include.as); + + // Set top level options + if (options.topModel === options.model && options.subQuery === undefined && options.topLimit) { + if (include.subQuery) { + options.subQuery = include.subQuery; + } else if (include.hasDuplicating) { + options.subQuery = true; + } + } + + /* Legacy */ + options.hasIncludeWhere = + options.hasIncludeWhere || include.hasIncludeWhere || Boolean(include.where); + options.hasIncludeRequired = + options.hasIncludeRequired || include.hasIncludeRequired || Boolean(include.required); + + if (include.association.isMultiAssociation || include.hasMultiAssociation) { + options.hasMultiAssociation = true; + } + + if (include.association.isSingleAssociation || include.hasSingleAssociation) { + options.hasSingleAssociation = true; + } + } + + if (options.topModel === options.model && options.subQuery === undefined) { + options.subQuery = false; + } + + return options; +} + +export function combineIncludes(a: any, b: any): any { + if (a == null) { + return b; + } + + if (b == null) { + return a; + } + + if (!Array.isArray(a) || !Array.isArray(b)) { + throw new TypeError( + 'Includes should have already been normalized before calling this method, but it received something else than an array.', + ); + } + + const combinedIncludes = [...a]; + + for (const newInclude of b) { + const existingIndex = combinedIncludes.findIndex(include => { + if (!include.association || !newInclude.association) { + throw new TypeError('Include should have been normalized'); + } + + return include.association === newInclude.association; + }); + + if (existingIndex === -1) { + combinedIncludes.push(newInclude); + continue; + } + + const ModelClass = newInclude.model; + // _assignOptions *must* be called on the class of the Include's Model, + // otherwise the Include's includes won't be checked correctly. + ModelClass._assignOptions(combinedIncludes[existingIndex], newInclude); + } + + return combinedIncludes; +} + +export function throwInvalidInclude(include: any): never { + throw new EagerLoadingError(`Invalid Include received. Include has to be either a Model, an Association, the name of an association, or a plain object compatible with IncludeOptions. +Got ${NodeUtil.inspect(include)} instead`); +} + +export function setTransactionFromCls(options: Transactionable, sequelize: Sequelize): void { + if ( + options.transaction && + options.connection && + options.connection !== options.transaction.getConnection() + ) { + throw new Error( + `You are using mismatching "transaction" and "connection" options. Please pass either one of them, or make sure they're both using the same connection.`, + ); + } + + if (options.transaction === undefined && options.connection == null) { + const currentTransaction = sequelize.getCurrentClsTransaction(); + if (currentTransaction) { + options.transaction = currentTransaction; + } + } + + if (options.connection) { + const clsTransaction = sequelize.getCurrentClsTransaction(); + const transactionConnection = clsTransaction?.getConnectionIfExists(); + if (transactionConnection && transactionConnection === options.connection) { + options.transaction = clsTransaction; + } + } else { + const connection = options.transaction?.getConnectionIfExists(); + if (connection) { + options.connection = connection; + } + } +} + +export function conformIndex(index: IndexOptions): IndexOptions { + if (!index.fields) { + throw new Error('Missing "fields" property for index definition'); + } + + index = { ...index }; + + if (index.type && index.type.toLowerCase() === 'unique') { + index.unique = true; + delete index.type; + } + + return index; +} + +export function getPrimaryKeyValueOrThrow(instance: Model, attributeName: string): unknown { + const attrVal = instance.get(attributeName, { raw: true }); + if (attrVal == null) { + throw new TypeError( + `This model instance method needs to be able to identify the entity in a stable way, but this model instance is missing the value of its primary key "${attributeName}". Make sure that attribute was not excluded when retrieving the model from the database.`, + ); + } + + return attrVal; +} + +/** + * Returns a Where Object that can be used to uniquely select this instance, using the instance's primary keys. + * + * @param instance The instance for which the where options should be built. + * @param checkVersion include version attribute in where hash + * @param nullIfImpossible return null instead of throwing an error if the instance is missing its + * primary keys and therefore no Where object can be built. + */ +export function getModelPkWhere( + instance: M, + checkVersion?: boolean, + nullIfImpossible?: boolean, +): WhereAttributeHash> | null { + const modelDefinition = instance.modelDefinition; + + if (modelDefinition.primaryKeysAttributeNames.size === 0) { + if (nullIfImpossible) { + return null; + } + + assertHasPrimaryKey(modelDefinition); + } + + const where = Object.create(null); + + for (const attributeName of modelDefinition.primaryKeysAttributeNames) { + const attrVal = nullIfImpossible + ? instance.get(attributeName, { raw: true }) + : getPrimaryKeyValueOrThrow(instance, attributeName); + + // nullIfImpossible case + if (attrVal == null) { + return null; + } + + where[attributeName] = attrVal; + } + + const versionAttr = modelDefinition.versionAttributeName; + if (checkVersion && versionAttr) { + where[versionAttr] = instance.get(versionAttr, { raw: true }); + } + + return where; +} + +export function assertHasPrimaryKey(modelDefinition: ModelDefinition) { + if (modelDefinition.primaryKeysAttributeNames.size === 0) { + throw new Error( + `This model instance method needs to be able to identify the entity in a stable way, but the model does not have a primary key attribute definition. +Either add a primary key to this model, or use one of the following alternatives: + +- instance methods "save", "update", "decrement", "increment": Use the static "update" method instead. +- instance method "reload": Use the static "findOne" method instead. +- instance methods "destroy" and "restore": use the static "destroy" and "restore" methods instead. + `.trim(), + ); + } +} + +export function assertHasWhereOptions(options: Filterable | undefined): void { + if (options?.where == null) { + throw new Error( + 'As a safeguard, this method requires explicitly specifying a "where" option. If you actually mean to delete all rows in the table, set the option to a dummy condition such as sql`1 = 1`.', + ); + } +} + +export function ensureOptionsAreImmutable(options: T): T { + if (isDevEnv()) { + // Users should not mutate any mutable value inside `options`, and instead mutate the `options` object directly + // This ensures `options` remains immutable while limiting ourselves to a shallow clone in production, + // improving performance. + return freezeDescendants(cloneDeepPlainValues(options, true)); + } + + return options; +} diff --git a/packages/core/src/model-repository.ts b/packages/core/src/model-repository.ts new file mode 100644 index 000000000000..399399dc90bb --- /dev/null +++ b/packages/core/src/model-repository.ts @@ -0,0 +1,335 @@ +import { EMPTY_ARRAY, EMPTY_OBJECT, shallowClonePojo } from '@sequelize/utils'; +import assert from 'node:assert'; +import { getBelongsToAssociationsWithTarget } from './_model-internals/get-belongs-to-associations-with-target.js'; +import type { BelongsToAssociation } from './associations/index.js'; +import { mayRunHook } from './hooks.js'; +import type { ModelDefinition } from './model-definition.js'; +import { + assertHasPrimaryKey, + assertHasWhereOptions, + ensureOptionsAreImmutable, + getModelPkWhere, + getPrimaryKeyValueOrThrow, + setTransactionFromCls, +} from './model-internals.js'; +import type { + BulkDestroyOptions, + CommonDestroyOptions, + DestroyManyOptions, +} from './model-repository.types.js'; +import { ManualOnDelete } from './model-repository.types.js'; +import type { Model, Transactionable } from './model.js'; +import { Op } from './operators.js'; + +/** + * The goal of this class is to become the new home of all the static methods that are currently present on the Model class, + * as a way to enable a true Repository Mode for Sequelize. + * + * Currently, this class is not usable as a repository (due to having a dependency on ModelStatic), but as we migrate all of + * Model to this class, we will be able to remove the dependency on ModelStatic, and make this class usable as a repository. + * + * See https://github.com/sequelize/sequelize/issues/15389 for more details. + * + * Unlike {@link ModelDefinition}, it's possible to have multiple different repositories for the same model (as users can provide their own implementation). + */ +export class ModelRepository { + readonly #modelDefinition: ModelDefinition; + + constructor(modelDefinition: ModelDefinition) { + this.#modelDefinition = modelDefinition; + } + + get #sequelize() { + return this.#modelDefinition.sequelize; + } + + get #queryInterface() { + return this.#sequelize.queryInterface; + } + + async _UNSTABLE_destroy( + instanceOrInstances: readonly M[] | M, + options: DestroyManyOptions = EMPTY_OBJECT, + ): Promise { + options = shallowClonePojo(options); + options.manualOnDelete ??= ManualOnDelete.paranoid; + + assertHasPrimaryKey(this.#modelDefinition); + setTransactionFromCls(options, this.#sequelize); + + const instances: M[] = Array.isArray(instanceOrInstances) + ? [...instanceOrInstances] + : [instanceOrInstances]; + if (instances.length === 0) { + return 0; + } + + options = ensureOptionsAreImmutable(options); + + if (mayRunHook('beforeDestroyMany', options.noHooks)) { + await this.#modelDefinition.hooks.runAsync('beforeDestroyMany', instances, options); + + // in case the beforeDestroyMany hook removed all instances. + if (instances.length === 0) { + return 0; + } + } + + Object.freeze(instances); + + let result: number; + const cascadingAssociations = this.#getCascadingDeleteAssociations(options); + if (cascadingAssociations.length > 0 && !options.transaction) { + result = await this.#sequelize.transaction(async transaction => { + options.transaction = transaction; + Object.freeze(options); + + return this.#destroyInternal(instances, cascadingAssociations, options); + }); + } else { + Object.freeze(options); + result = await this.#destroyInternal(instances, cascadingAssociations, options); + } + + if (mayRunHook('afterDestroyMany', options.noHooks)) { + await this.#modelDefinition.hooks.runAsync('afterDestroyMany', instances, options, result); + } + + return result; + } + + async #destroyInternal( + instances: readonly M[], + cascadingAssociations: readonly BelongsToAssociation[], + options: DestroyManyOptions, + ): Promise { + if (cascadingAssociations.length > 0) { + await this.#manuallyCascadeDestroy(instances, cascadingAssociations, options); + } + + const isSoftDelete = !options.hardDelete && this.#modelDefinition.isParanoid(); + if (isSoftDelete) { + // TODO: implement once updateMany is implemented - https://github.com/sequelize/sequelize/issues/4501 + throw new Error('ModelRepository#_UNSTABLE_destroy does not support paranoid deletion yet.'); + } + + const primaryKeys = this.#modelDefinition.primaryKeysAttributeNames; + let where; + if (instances.length === 1) { + where = getModelPkWhere(instances[0], true)!; + } else if (primaryKeys.size === 1 && !this.#modelDefinition.versionAttributeName) { + const primaryKey: string = primaryKeys.values().next().value; + + const values = instances.map(instance => getPrimaryKeyValueOrThrow(instance, primaryKey)); + + where = { [primaryKey]: values }; + } else { + where = { + // Ideally, we'd use tuple comparison here, but that's not supported by Sequelize yet. + // It would look like this: + // WHERE (id1, id2) IN ((1, 2), (3, 4)) + [Op.or]: instances.map(instance => getModelPkWhere(instance, true)!), + }; + } + + const bulkDeleteOptions = { + ...options, + limit: null, + where, + }; + + // DestroyManyOptions-specific options. + delete bulkDeleteOptions.hardDelete; + delete bulkDeleteOptions.noHooks; + + return this.#queryInterface.bulkDelete(this.#modelDefinition, bulkDeleteOptions); + } + + async _UNSTABLE_bulkDestroy(options: BulkDestroyOptions) { + options = shallowClonePojo(options); + options.manualOnDelete ??= ManualOnDelete.paranoid; + + assertHasWhereOptions(options); + setTransactionFromCls(options, this.#sequelize); + + // TODO: support "scope" option + default scope + + const modelDefinition = this.#modelDefinition; + + if (mayRunHook('_UNSTABLE_beforeBulkDestroy', options.noHooks)) { + await modelDefinition.hooks.runAsync('_UNSTABLE_beforeBulkDestroy', options); + } + + let result: number; + const cascadingAssociations = this.#getCascadingDeleteAssociations(options); + if (cascadingAssociations.length > 0 && !options.transaction) { + result = await this.#sequelize.transaction(async transaction => { + options.transaction = transaction; + Object.freeze(options); + + return this.#bulkDestroyInternal(cascadingAssociations, options); + }); + } else { + Object.freeze(options); + result = await this.#bulkDestroyInternal(cascadingAssociations, options); + } + + if (mayRunHook('_UNSTABLE_afterBulkDestroy', options.noHooks)) { + await modelDefinition.hooks.runAsync('_UNSTABLE_afterBulkDestroy', options, result); + } + + return result; + } + + async #bulkDestroyInternal( + cascadingAssociations: readonly BelongsToAssociation[], + options: BulkDestroyOptions, + ): Promise { + const modelDefinition = this.#modelDefinition; + + if (cascadingAssociations.length > 0) { + // TODO: if we know this is the last cascade, + // we can avoid the fetch and call bulkDestroy directly instead of destroyMany. + // TODO: only fetch the attributes that are referenced by a foreign key, not all attributes. + const instances: M[] = await modelDefinition.model.findAll(options); + + await this.#manuallyCascadeDestroy(instances, cascadingAssociations, options); + } + + const deletedAtAttributeName = modelDefinition.timestampAttributeNames.deletedAt; + if (deletedAtAttributeName && !options.hardDelete) { + throw new Error( + 'ModelRepository#_UNSTABLE_bulkDestroy does not support paranoid deletion yet.', + ); + // const deletedAtAttribute = modelDefinition.attributes.getOrThrow(deletedAtAttributeName); + + // return this.#queryInterface.bulkUpdate( + // modelDefinition, + // pojo({ + // [deletedAtAttributeName]: new Date(), + // }), + // and( + // { + // [deletedAtAttributeName]: deletedAtAttribute.defaultValue ?? null, + // }, + // options.where, + // ), + // options, + // ); + } + + return this.#queryInterface.bulkDelete(this.#modelDefinition, options); + } + + #getCascadingDeleteAssociations( + options: CommonDestroyOptions & Transactionable, + ): readonly BelongsToAssociation[] { + if (options.manualOnDelete === ManualOnDelete.none) { + return EMPTY_ARRAY; + } + + if ( + options.manualOnDelete === ManualOnDelete.paranoid && + !options.hardDelete && + this.#modelDefinition.isParanoid() + ) { + return EMPTY_ARRAY; + } + + const belongsToAssociations = getBelongsToAssociationsWithTarget(this.#modelDefinition.model); + + return belongsToAssociations.filter(association => { + const source = association.source.modelDefinition; + const foreignKey = source.physicalAttributes.getOrThrow(association.foreignKey); + + return ( + foreignKey.onDelete === 'CASCADE' || + foreignKey.onDelete === 'SET NULL' || + foreignKey.onDelete === 'SET DEFAULT' + ); + }); + } + + async #manuallyCascadeDestroy( + instances: readonly M[], + cascadingAssociations: readonly BelongsToAssociation[], + options: CommonDestroyOptions & Transactionable, + ) { + assert(options.transaction, 'Handling ON DELETE in JavaScript requires a transaction.'); + + const isSoftDelete = !options.hardDelete && this.#modelDefinition.isParanoid(); + + await Promise.all( + cascadingAssociations.map(async association => { + const source = association.source.modelDefinition; + const foreignKey = source.physicalAttributes.getOrThrow(association.foreignKey); + + switch (foreignKey.onDelete) { + case 'CASCADE': { + // Because the cascade can lead to further cascades, + // we need to fetch the instances first to recursively destroy them. + // TODO: if we know this is the last cascade, + // we can avoid the fetch and call bulkDestroy directly instead of destroyMany. + // TODO: only fetch the attributes that are referenced by a foreign key, not all attributes. + const associatedInstances = await source.model.findAll({ + transaction: options.transaction, + connection: options.connection, + where: { + [association.foreignKey]: instances.map(instance => + instance.get(association.targetKey), + ), + }, + }); + + if (associatedInstances.length === 0) { + return; + } + + if (isSoftDelete && !source.isParanoid()) { + throw new Error(`Trying to soft delete model ${this.#modelDefinition.modelName}, but it is associated with a non-paranoid model, ${source.modelName}, through ${association.name} with onDelete: 'CASCADE'. +This would lead to an active record being associated with a deleted record.`); + } + + await source.model.modelRepository._UNSTABLE_destroy(associatedInstances, options); + + return; + } + + case 'SET NULL': { + // TODO: implement once bulkUpdate is implemented + throw new Error('Manual cascades do not support SET NULL yet.'); + } + + case 'SET DEFAULT': { + // TODO: implement once bulkUpdate is implemented + throw new Error('Manual cascades do not support SET DEFAULT yet.'); + } + + default: + throw new Error(`Unexpected onDelete action: ${foreignKey.onDelete}`); + } + }), + ); + } + + // async save(instances: M[] | M): Promise {} + // async updateOne(instance: M, values: object, options: unknown): Promise {} + // async updateMany(data: Array<{ instance: M, values: object }>, options: unknown): Promise {} + // async updateMany(data: Array<{ where: object, values: object }>, options: unknown): Promise {} + // async restore(instances: M[] | M, options: unknown): Promise {} + // async bulkUpdate(options: unknown): Promise {} + // async bulkRestore(options: unknown): Promise {} +} + +const modelRepositories = new WeakMap(); + +export function getModelRepository(model: ModelDefinition): ModelRepository { + let internals = modelRepositories.get(model); + if (internals) { + return internals; + } + + internals = new ModelRepository(model); + + return internals; +} diff --git a/packages/core/src/model-repository.types.ts b/packages/core/src/model-repository.types.ts new file mode 100644 index 000000000000..7067ecd382a8 --- /dev/null +++ b/packages/core/src/model-repository.types.ts @@ -0,0 +1,60 @@ +import type { StrictRequiredBy } from '@sequelize/utils'; +import type { QiBulkDeleteOptions } from './abstract-dialect/query-interface.types.js'; +import type { NewHookable } from './hooks.js'; +import type { Attributes, Model } from './model.js'; + +export enum ManualOnDelete { + /** + * Only replicates the behavior of ON DELETE in JS for soft deletions, + * otherwise is equivalent to "none". + */ + paranoid = 'paranoid', + + /** + * Lets the database delete the cascading instances, does nothing in JS. + * Most efficient, but not compatible with soft deletions. + */ + none = 'none', + + /** + * Pre-deletes every cascading model in JS before deleting the current instance. + * Useful if you need to trigger the JS hooks for cascading deletes, + * or if foreign key constraints are disabled in the database. + * + * This is the least efficient option. + */ + all = 'all', +} + +export interface CommonDestroyOptions { + /** + * If set to true, paranoid models will actually be deleted instead of soft deleted. + */ + hardDelete?: boolean | undefined; + + /** + * Manually handles the behavior of ON DELETE in JavaScript, instead of using the native database ON DELETE behavior. + * This option is useful when: + * - The deletion is a soft deletion. + * - You wish to run JS delete hooks for the cascading models. + * + * @default 'paranoid' + */ + manualOnDelete?: ManualOnDelete | undefined; +} + +/** + * Used by {@link ModelRepository#_UNSTABLE_destroy} + */ +export interface DestroyManyOptions + extends NewHookable<'beforeDestroyMany' | 'afterDestroyMany'>, + Omit, + CommonDestroyOptions {} + +/** + * Used by {@link ModelRepository#_UNSTABLE_bulkDestroy} + */ +export interface BulkDestroyOptions + extends NewHookable<'_UNSTABLE_beforeBulkDestroy' | '_UNSTABLE_afterBulkDestroy'>, + StrictRequiredBy>, 'where'>, + CommonDestroyOptions {} diff --git a/packages/core/src/model-set-view.ts b/packages/core/src/model-set-view.ts new file mode 100644 index 000000000000..86467f36774d --- /dev/null +++ b/packages/core/src/model-set-view.ts @@ -0,0 +1,127 @@ +import { map, SetView } from '@sequelize/utils'; +import { inspect } from 'node:util'; +// @ts-expect-error -- toposort-class definition will be added to sequelize/toposort later +import Toposort from 'toposort-class'; +import type { AbstractDialect } from './abstract-dialect/dialect.js'; +import type { Model, ModelStatic } from './model'; +import type { SequelizeTypeScript } from './sequelize-typescript.js'; + +export class ModelSetView extends SetView { + readonly #sequelize: SequelizeTypeScript; + + constructor(sequelize: SequelizeTypeScript, set: Set) { + super(set); + + this.#sequelize = sequelize; + } + + get(modelName: string): ModelStatic | undefined { + return this.find(model => model.modelDefinition.modelName === modelName) as + | ModelStatic + | undefined; + } + + getOrThrow(modelName: string): ModelStatic { + const model = this.get(modelName); + + if (!model) { + throw new Error(`Model ${inspect(modelName)} was not added to this Sequelize instance.`); + } + + return model; + } + + /** + * Returns the list of registered model names. + */ + getNames(): Iterable { + return map(this, model => model.modelDefinition.modelName); + } + + hasByName(modelName: string): boolean { + return this.get(modelName) !== undefined; + } + + /** + * Returns an array that lists every model, sorted in order + * of foreign key references: The first model is a model that is depended upon, + * the last model is a model that is not depended upon. + * + * If there is a cyclic dependency, this returns null. + */ + getModelsTopoSortedByForeignKey(): ModelStatic[] | null { + const models = new Map(); + const sorter = new Toposort(); + + const queryGenerator = this.#sequelize.queryGenerator; + + for (const model of this) { + let deps = []; + const tableName = queryGenerator.quoteTable(model); + + models.set(tableName, model); + + const { attributes } = model.modelDefinition; + for (const attrName of attributes.keys()) { + const attribute = attributes.get(attrName); + + if (!attribute?.references) { + continue; + } + + const dep = queryGenerator.quoteTable(attribute.references.table); + deps.push(dep); + } + + deps = deps.filter(dep => tableName !== dep); + + sorter.add(tableName, deps); + } + + let sorted; + try { + sorted = sorter.sort(); + } catch (error: unknown) { + if (error instanceof Error && !error.message.startsWith('Cyclic dependency found.')) { + throw error; + } + + return null; + } + + return sorted + .map((modelName: string) => { + return models.get(modelName); + }) + .filter(Boolean); + } + + /** + * Iterate over Models in an order suitable for e.g. creating tables. + * Will take foreign key constraints into account so that dependencies are visited before dependents. + * + * @param iterator method to execute on each model + * @param options + * @param options.reverse + * @private + * + * @deprecated + */ + forEachModel(iterator: (model: ModelStatic) => void, options?: { reverse?: boolean }) { + const sortedModels = this.getModelsTopoSortedByForeignKey(); + if (sortedModels == null) { + throw new Error('Cyclic dependency found.'); + } + + // TODO: options should be false by default + const reverse = options?.reverse ?? true; + + if (reverse) { + sortedModels.reverse(); + } + + for (const model of sortedModels) { + iterator(model); + } + } +} diff --git a/packages/core/src/model-typescript.ts b/packages/core/src/model-typescript.ts new file mode 100644 index 000000000000..22afb6675873 --- /dev/null +++ b/packages/core/src/model-typescript.ts @@ -0,0 +1,593 @@ +import type { PartialBy } from '@sequelize/utils'; +import { isPlainObject } from '@sequelize/utils'; +import { inspect } from 'node:util'; +import type { + AbstractQueryGenerator, + AbstractQueryInterface, + Association, + AttributeOptions, + Attributes, + BrandedKeysOf, + BuiltModelOptions, + FindByPkOptions, + ForeignKeyBrand, + IndexOptions, + InitOptions, + ModelAttributes, + ModelStatic, + NonNullFindByPkOptions, + NormalizedAttributeOptions, + Sequelize, + TableNameWithSchema, +} from '.'; +import { isDecoratedModel } from './decorators/shared/model.js'; +import { + legacyBuildAddAnyHook, + legacyBuildAddHook, + legacyBuildHasHook, + legacyBuildRemoveHook, + legacyBuildRunHook, +} from './hooks-legacy.js'; +import { + ModelDefinition, + getModelDefinition, + hasModelDefinition, + registerModelDefinition, +} from './model-definition.js'; +import { staticModelHooks } from './model-hooks.js'; +import type { ModelRepository } from './model-repository.js'; +import { getModelRepository } from './model-repository.js'; +import type { DestroyOptions } from './model.js'; +import { Model } from './model.js'; +import { and } from './sequelize.js'; +import { noModelTableName } from './utils/deprecations.js'; +import { getObjectFromMap } from './utils/object.js'; + +// DO NOT MAKE THIS CLASS PUBLIC! +/** + * This is a temporary class used to progressively migrate the Model class to TypeScript by slowly moving its functions here. + * Always use {@link Model} instead. + */ +export class ModelTypeScript { + static get queryInterface(): AbstractQueryInterface { + return this.sequelize.queryInterface; + } + + static get queryGenerator(): AbstractQueryGenerator { + return this.sequelize.queryGenerator; + } + + /** + * A reference to the sequelize instance. + */ + get sequelize(): Sequelize { + return (this.constructor as typeof ModelTypeScript).sequelize; + } + + /** + * A reference to the sequelize instance. + * + * Accessing this property throws if the model has not been registered with a Sequelize instance yet. + */ + static get sequelize(): Sequelize { + return this.modelDefinition.sequelize; + } + + /** + * Returns the model definition of this model. + * The model definition contains all metadata about this model. + */ + static get modelDefinition(): ModelDefinition { + // @ts-expect-error -- getModelDefinition expects ModelStatic + return getModelDefinition(this); + } + + get modelDefinition(): ModelDefinition { + return (this.constructor as ModelStatic).modelDefinition; + } + + static get modelRepository(): ModelRepository { + return getModelRepository(this.modelDefinition); + } + + get modelRepository(): ModelRepository { + return (this.constructor as ModelStatic).modelRepository; + } + + /** + * An object hash from alias to the association object + */ + static get associations(): { [associationName: string]: Association } { + return this.modelDefinition.associations; + } + + /** + * The name of the primary key attribute (on the JS side). + * + * @deprecated This property doesn't work for composed primary keys. Use {@link primaryKeyAttributes} instead. + */ + static get primaryKeyAttribute(): string | null { + return this.primaryKeyAttributes[0] ?? null; + } + + /** + * The name of the primary key attributes (on the JS side). + * + * @deprecated use {@link modelDefinition}. + */ + static get primaryKeyAttributes(): string[] { + return [...this.modelDefinition.primaryKeysAttributeNames]; + } + + /** + * The column name of the primary key. + * + * @deprecated don't use this. It doesn't work with composite PKs. It may be removed in the future to reduce duplication. + * Use the. Use {@link Model.primaryKeys} instead. + */ + static get primaryKeyField(): string | null { + const primaryKeyAttribute = this.primaryKeyAttribute; + if (!primaryKeyAttribute) { + return null; + } + + return this.modelDefinition.getColumnName(primaryKeyAttribute); + } + + /** + * Like {@link Model.rawAttributes}, but only includes attributes that are part of the Primary Key. + */ + static get primaryKeys(): { [attribute: string]: NormalizedAttributeOptions } { + const out = Object.create(null); + + const definition = this.modelDefinition; + + for (const primaryKey of definition.primaryKeysAttributeNames) { + out[primaryKey] = definition.attributes.get(primaryKey)!; + } + + return out; + } + + /** + * The options that the model was initialized with + */ + static get options(): BuiltModelOptions { + return this.modelDefinition.options; + } + + /** + * The name of the database table + * + * @deprecated use {@link modelDefinition} or {@link table}. + */ + static get tableName(): string { + noModelTableName(); + + return this.modelDefinition.table.tableName; + } + + static get table(): TableNameWithSchema { + return this.modelDefinition.table; + } + + /** + * @deprecated use {@link modelDefinition}'s {@link ModelDefinition#rawAttributes} or {@link ModelDefinition#attributes} instead. + */ + static get rawAttributes(): { [attribute: string]: AttributeOptions } { + throw new Error(`${this.name}.rawAttributes has been removed, as it has been split in two: +- If you only need to read the final attributes, use ${this.name}.modelDefinition.attributes +- If you need to modify the attributes, mutate ${this.name}.modelDefinition.rawAttributes, then call ${this.name}.modelDefinition.refreshAttributes()`); + } + + /** + * @deprecated use {@link modelDefinition}'s {@link ModelDefinition#rawAttributes} or {@link ModelDefinition#attributes} instead. + */ + get rawAttributes(): { [attribute: string]: AttributeOptions } { + return (this.constructor as typeof ModelTypeScript).rawAttributes; + } + + /** + * @deprecated use {@link modelDefinition}'s {@link ModelDefinition#columns}. + */ + static get fieldRawAttributesMap(): { [columnName: string]: NormalizedAttributeOptions } { + return getObjectFromMap(this.modelDefinition.columns); + } + + /** + * @deprecated use {@link modelDefinition}'s {@link ModelDefinition#physicalAttributes}. + */ + static get tableAttributes(): { [attribute: string]: NormalizedAttributeOptions } { + return getObjectFromMap(this.modelDefinition.physicalAttributes); + } + + /** + * A mapping of column name to attribute name + * + * @private + */ + static get fieldAttributeMap(): { [columnName: string]: string } { + const out = Object.create(null); + + const attributes = this.modelDefinition.attributes; + for (const attribute of attributes.values()) { + out[attribute.columnName] = attribute.attributeName; + } + + return out; + } + + static get hooks() { + return this.modelDefinition.hooks; + } + + static addHook = legacyBuildAddAnyHook(staticModelHooks); + static hasHook = legacyBuildHasHook(staticModelHooks); + static hasHooks = legacyBuildHasHook(staticModelHooks); + static removeHook = legacyBuildRemoveHook(staticModelHooks); + static runHooks = legacyBuildRunHook(staticModelHooks); + + static beforeValidate = legacyBuildAddHook(staticModelHooks, 'beforeValidate'); + static afterValidate = legacyBuildAddHook(staticModelHooks, 'afterValidate'); + static validationFailed = legacyBuildAddHook(staticModelHooks, 'validationFailed'); + + static beforeCreate = legacyBuildAddHook(staticModelHooks, 'beforeCreate'); + static afterCreate = legacyBuildAddHook(staticModelHooks, 'afterCreate'); + + static beforeDestroy = legacyBuildAddHook(staticModelHooks, 'beforeDestroy'); + static afterDestroy = legacyBuildAddHook(staticModelHooks, 'afterDestroy'); + + static beforeRestore = legacyBuildAddHook(staticModelHooks, 'beforeRestore'); + static afterRestore = legacyBuildAddHook(staticModelHooks, 'afterRestore'); + + static beforeUpdate = legacyBuildAddHook(staticModelHooks, 'beforeUpdate'); + static afterUpdate = legacyBuildAddHook(staticModelHooks, 'afterUpdate'); + + static beforeUpsert = legacyBuildAddHook(staticModelHooks, 'beforeUpsert'); + static afterUpsert = legacyBuildAddHook(staticModelHooks, 'afterUpsert'); + + static beforeSave = legacyBuildAddHook(staticModelHooks, 'beforeSave'); + static afterSave = legacyBuildAddHook(staticModelHooks, 'afterSave'); + + static beforeBulkCreate = legacyBuildAddHook(staticModelHooks, 'beforeBulkCreate'); + static afterBulkCreate = legacyBuildAddHook(staticModelHooks, 'afterBulkCreate'); + + static beforeBulkDestroy = legacyBuildAddHook(staticModelHooks, 'beforeBulkDestroy'); + static afterBulkDestroy = legacyBuildAddHook(staticModelHooks, 'afterBulkDestroy'); + + static beforeBulkRestore = legacyBuildAddHook(staticModelHooks, 'beforeBulkRestore'); + static afterBulkRestore = legacyBuildAddHook(staticModelHooks, 'afterBulkRestore'); + + static beforeBulkUpdate = legacyBuildAddHook(staticModelHooks, 'beforeBulkUpdate'); + static afterBulkUpdate = legacyBuildAddHook(staticModelHooks, 'afterBulkUpdate'); + + static beforeCount = legacyBuildAddHook(staticModelHooks, 'beforeCount'); + + static beforeFind = legacyBuildAddHook(staticModelHooks, 'beforeFind'); + static beforeFindAfterExpandIncludeAll = legacyBuildAddHook( + staticModelHooks, + 'beforeFindAfterExpandIncludeAll', + ); + + static beforeFindAfterOptions = legacyBuildAddHook(staticModelHooks, 'beforeFindAfterOptions'); + static afterFind = legacyBuildAddHook(staticModelHooks, 'afterFind'); + + static beforeSync = legacyBuildAddHook(staticModelHooks, 'beforeSync'); + static afterSync = legacyBuildAddHook(staticModelHooks, 'afterSync'); + + static beforeAssociate = legacyBuildAddHook(staticModelHooks, 'beforeAssociate'); + static afterAssociate = legacyBuildAddHook(staticModelHooks, 'afterAssociate'); + + /** + * Initialize a model, representing a table in the DB, with attributes and options. + * + * The table columns are defined by the hash that is given as the first argument. + * Each attribute of the hash represents a column. + * + * @example + * ```javascript + * Project.init({ + * columnA: { + * type: DataTypes.BOOLEAN, + * validate: { + * is: ['[a-z]','i'], // will only allow letters + * max: 23, // only allow values <= 23 + * isIn: { + * args: [['en', 'zh']], + * msg: "Must be English or Chinese" + * } + * }, + * field: 'column_a' + * // Other attributes here + * }, + * columnB: DataTypes.STRING, + * columnC: 'MY VERY OWN COLUMN TYPE' + * }, {sequelize}) + * ``` + * + * sequelize.models.modelName // The model will now be available in models under the class name + * + * @see https://sequelize.org/docs/v7/core-concepts/model-basics/ + * @see https://sequelize.org/docs/v7/core-concepts/validations-and-constraints/ + * + * @param 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. + * @param options These options are merged with the default define options provided to the Sequelize constructor + */ + static init>( + this: MS, + attributes: ModelAttributes< + M, + // 'foreign keys' are optional in Model.init as they are added by association declaration methods + PartialBy, BrandedKeysOf, typeof ForeignKeyBrand>> + >, + options: InitOptions, + ): MS { + if (isDecoratedModel(this)) { + throw new Error( + `Model.init cannot be used if the model uses one of Sequelize's decorators. You must pass your model to the Sequelize constructor using the "models" option instead.`, + ); + } + + if (!options.sequelize) { + throw new Error( + 'Model.init expects a Sequelize instance to be passed through the option bag, which is the second parameter.', + ); + } + + initModel(this, attributes, options); + + return this; + } + + static getIndexes(): readonly IndexOptions[] { + return this.modelDefinition.getIndexes(); + } + + /** + * Unique indexes that can be declared as part of a CREATE TABLE query. + * + * @deprecated prefer using {@link getIndexes}, this will eventually be removed. + */ + static get uniqueKeys() { + const indexes = this.getIndexes(); + const uniqueKeys = Object.create(null); + + // TODO: "column" should be removed from index definitions + const supportedOptions = ['unique', 'fields', 'column', 'name']; + + for (const index of indexes) { + if (!index.unique) { + continue; + } + + if (!index.name) { + continue; + } + + if (!index.fields) { + continue; + } + + if (!index.fields.every(field => typeof field === 'string')) { + continue; + } + + if (!Object.keys(index).every(optionName => supportedOptions.includes(optionName))) { + continue; + } + + uniqueKeys[index.name] = index; + } + + return uniqueKeys; + } + + // TODO [>7]: Remove this + private static get _indexes(): never { + throw new Error('Model._indexes has been replaced with Model.getIndexes()'); + } + + /** + * Refreshes the Model's attribute definition. + * + * @deprecated use {@link modelDefinition}. + */ + static refreshAttributes(): void { + this.modelDefinition.refreshAttributes(); + } + + static assertIsInitialized(): void { + if (!this.isInitialized()) { + throw new Error( + `Model "${this.name}" has not been initialized yet. You can check whether a model has been initialized by calling its isInitialized method.`, + ); + } + } + + static isInitialized(): boolean { + // @ts-expect-error -- getModelDefinition expects ModelStatic + return hasModelDefinition(this); + } + + /** + * Get the table name of the model, taking schema into account. The method will an object with `tableName`, `schema` and `delimiter` properties. + * + * @deprecated use {@link modelDefinition} or {@link table}. + */ + static getTableName(): TableNameWithSchema { + noModelTableName(); + + const queryGenerator = this.sequelize.queryGenerator; + + return { + ...this.table, + /** + * @deprecated This should not be relied upon! + */ + // @ts-expect-error -- This toString is a hacky property that must be removed + toString() { + return queryGenerator.quoteTable(this); + }, + }; + } + + /** + * Works like the {@link Model#destroy} instance method, but is capable of deleting multiple instances in one query. + * Unlike {@link Model.destroy}, this method takes instances, not a `where` option. + * + * @param instances The instances to delete. + * @param options Options. + */ + static async _UNSTABLE_destroyMany( + this: ModelStatic, + instances: M | M[], + options?: DestroyOptions>, + ): Promise { + return this.modelRepository._UNSTABLE_destroy(instances, options); + } + + /** + * Search for a single instance by its primary key. + * + * This applies LIMIT 1, only a single instance will be returned. + * + * Returns the model with the matching primary key. + * If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set. + */ + static findByPk>( + this: ModelStatic, + identifier: unknown, + options: FindByPkOptions & { raw: true; rejectOnEmpty?: false }, + ): Promise; + static findByPk>( + this: ModelStatic, + identifier: unknown, + options: NonNullFindByPkOptions & { raw: true }, + ): Promise; + static findByPk( + this: ModelStatic, + identifier: unknown, + options: NonNullFindByPkOptions, + ): Promise; + static findByPk( + this: ModelStatic, + identifier: unknown, + options?: FindByPkOptions, + ): Promise; + + /** + * Search for a single instance by its primary key. + * + * This applies LIMIT 1, only a single instance will be returned. + * + * Returns the model with the matching primary key. + * If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set. + * + * If the model has a composite primary key, pass an object with the primary key attributes. + * + * @param identifier The value of the desired instance's primary key. + * @param options find options + */ + static async findByPk( + this: ModelStatic, + identifier: unknown, + options?: FindByPkOptions>, + ): Promise { + if (identifier == null) { + throw new Error(`${identifier} is not a valid primary key`); + } + + const primaryKeyAttributeNames = this.modelDefinition.primaryKeysAttributeNames; + if (primaryKeyAttributeNames.size === 0) { + throw new Error( + `Model ${this.name} does not have a primary key attribute, so findByPk cannot be used`, + ); + } + + const pkWhere = Object.create(null); + if (primaryKeyAttributeNames.size === 1) { + pkWhere[primaryKeyAttributeNames.firstValue()!] = identifier; + } else { + if (!isPlainObject(identifier)) { + throw new TypeError( + `Model ${this.name} has a composite primary key. Please pass all primary keys in an object like { pk1: value1, pk2: value2 }. Received: ${inspect(identifier)}`, + ); + } + + for (const attributeName of primaryKeyAttributeNames) { + if (identifier[attributeName] === undefined) { + throw new TypeError( + `Part of the composite primary key, ${attributeName}, is missing. Please pass all primary key attributes. Received: ${inspect(identifier)}`, + ); + } + + pkWhere[attributeName] = identifier[attributeName]; + } + } + + // Bypass a possible overloaded findOne + return Model.findOne.call(this, { + ...options, + where: options?.where ? and(options?.where, pkWhere) : pkWhere, + }); + } +} + +export function initModel( + model: ModelStatic, + attributes: ModelAttributes, + options: InitOptions, +): void { + options.modelName ||= model.name; + + const modelDefinition = new ModelDefinition(attributes, options, model); + + Object.defineProperty(model, 'name', { value: modelDefinition.modelName }); + + registerModelDefinition(model, modelDefinition); + + // @ts-expect-error -- TODO: type + model._scope = model.options.defaultScope; + // @ts-expect-error -- TODO: type + model._scopeNames = ['defaultScope']; + + model.sequelize.hooks.runSync('afterDefine', model); + + addAttributeGetterAndSetters(model); + model.hooks.addListener('afterDefinitionRefresh', () => { + addAttributeGetterAndSetters(model); + }); +} + +function addAttributeGetterAndSetters(model: ModelStatic) { + const modelDefinition = model.modelDefinition; + + // TODO: temporary workaround due to cyclic import. Should not be necessary once Model is fully migrated to TypeScript. + const { Model: TmpModel } = require('./model.js'); + + // add attributes to the DAO prototype + for (const attribute of modelDefinition.attributes.values()) { + const attributeName = attribute.attributeName; + + if (attributeName in TmpModel.prototype) { + model.sequelize.log( + `Attribute ${attributeName} in model ${model.name} is shadowing a built-in property of the Model prototype. This is not recommended. Consider renaming your attribute.`, + ); + + continue; + } + + const attributeProperty: PropertyDescriptor = { + configurable: true, + get(this: Model) { + return this.get(attributeName); + }, + set(this: Model, value: unknown) { + return this.set(attributeName, value); + }, + }; + + Object.defineProperty(model.prototype, attributeName, attributeProperty); + } +} diff --git a/packages/core/src/model.d.ts b/packages/core/src/model.d.ts new file mode 100644 index 000000000000..a58fdd365e6d --- /dev/null +++ b/packages/core/src/model.d.ts @@ -0,0 +1,3439 @@ +import type { + AllowArray, + AllowReadonlyArray, + AnyFunction, + Nullish, + StrictRequiredBy, +} from '@sequelize/utils'; +import type { SetRequired } from 'type-fest'; +import type { AbstractConnection } from './abstract-dialect/connection-manager.js'; +import type { DataType, NormalizedDataType } from './abstract-dialect/data-types.js'; +import type { IndexField, IndexOptions, TableName } from './abstract-dialect/query-interface'; +import type { + Association, + BelongsToAssociation, + BelongsToManyAssociation, + BelongsToManyOptions, + BelongsToOptions, + HasManyAssociation, + HasManyOptions, + HasOneAssociation, + HasOneOptions, +} from './associations/index'; +import type { Deferrable } from './deferrable'; +import type { IndexHints } from './enums.js'; +import type { DynamicSqlExpression } from './expression-builders/base-sql-expression.js'; +import type { Cast } from './expression-builders/cast.js'; +import type { Col } from './expression-builders/col.js'; +import type { Fn } from './expression-builders/fn.js'; +import type { Literal } from './expression-builders/literal.js'; +import type { Where } from './expression-builders/where.js'; +import type { Lock, Op, TableHints, Transaction, WhereOptions } from './index'; +import type { ValidationOptions } from './instance-validator'; +import type { ModelHooks } from './model-hooks.js'; +import { ModelTypeScript } from './model-typescript.js'; +import type { QueryOptions, Sequelize, SyncOptions } from './sequelize'; +import type { COMPLETES_TRANSACTION } from './transaction'; +import type { MakeNullishOptional, OmitConstructors } from './utils/types.js'; + +export interface Logging { + /** + * A function that gets executed while running the query to log the sql. + */ + logging?: false | ((sql: string, timing?: number) => void) | undefined; + + /** + * Pass query execution time in milliseconds as second argument to logging function (options.logging). + */ + benchmark?: boolean | undefined; +} + +export interface Poolable { + /** + * Force the query to use the write pool, regardless of the query type. + * + * @default false + */ + useMaster?: boolean; +} + +export interface Transactionable { + /** + * The transaction in which this query must be run. + * Mutually exclusive with {@link Transactionable.connection}. + * + * If the Sequelize disableClsTransactions option has not been set to true, and a transaction is running in the current AsyncLocalStorage context, + * that transaction will be used, unless null or another Transaction is manually specified here. + */ + transaction?: Transaction | null | undefined; + + /** + * The connection on which this query must be run. + * Mutually exclusive with {@link Transactionable.transaction}. + * + * Can be used to ensure that a query is run on the same connection as a previous query, which is useful when + * configuring session options. + * + * Specifying this option takes precedence over CLS Transactions. If a transaction is running in the current + * AsyncLocalStorage context, it will be ignored in favor of the specified connection. + */ + connection?: AbstractConnection | null | undefined; + + /** + * Indicates if the query completes the transaction + * Internal only + * + * @private + * @hidden + */ + [COMPLETES_TRANSACTION]?: boolean | undefined; +} + +export interface SearchPathable { + /** + * An optional parameter to specify the schema search_path (Postgres only) + */ + searchPath?: string; +} + +export interface Filterable { + /** + * The `WHERE` clause. Can be many things from a hash of attributes to raw SQL. + * + * Visit {@link https://sequelize.org/docs/v7/core-concepts/model-querying-basics/} for more information. + */ + where?: WhereOptions; +} + +export interface Projectable { + /** + * If an array: a list of the attributes that you want to select. + * Attributes can also be raw SQL (`literal`), `fn`, `col`, and `cast` + * + * To rename an attribute, you can pass an array, with two elements: + * - The first is the name of the attribute (or `literal`, `fn`, `col`, `cast`), + * - and the second is the name to give to that attribute in the returned instance. + * + * If `include` is used: selects all the attributes of the model, + * plus some additional ones. Useful for aggregations. + * + * @example + * ```javascript + * { attributes: { include: [[literal('COUNT(id)'), 'total']] } + * ``` + * + * If `exclude` is used: selects all the attributes of the model, + * except the one specified in exclude. Useful for security purposes + * + * @example + * ```javascript + * { attributes: { exclude: ['password'] } } + * ``` + */ + attributes?: FindAttributeOptions; +} + +export interface Paranoid { + /** + * If true, only non-deleted records will be returned. If false, both deleted and non-deleted records will + * be returned. + * + * Only applies if {@link InitOptions.paranoid} is true for the model. + * + * @default true + */ + paranoid?: boolean; +} + +export type GroupOption = AllowArray; + +/** + * Options to pass to Model on drop + */ +export interface DropOptions extends Logging { + /** + * Also drop all objects depending on this table, such as views. Only works in postgres + */ + cascade?: boolean; +} + +/** + * Schema Options provided for applying a schema to a model + */ +export interface SchemaOptions { + schema: string; + + /** + * The character(s) that separates the schema name from the table name + */ + schemaDelimiter?: string | undefined; +} + +/** + * Scope Options for Model.scope + */ +export interface ScopeOptions { + /** + * 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. + */ + method: string | readonly [string, ...unknown[]]; +} + +type InvalidInSqlArray = ColumnReference | Fn | Cast | null | Literal; + +type AllowAnyAll = + | T + // Op.all: [x, z] results in ALL (ARRAY[x, z]) + // Some things cannot go in ARRAY. Op.values must be used to support them. + | { + [Op.all]: + | Array> + | Literal + | { [Op.values]: Array> }; + } + | { + [Op.any]: + | Array> + | Literal + | { [Op.values]: Array> }; + }; + +// number is always allowed because -Infinity & +Infinity are valid +/** + * This type represents a valid input when describing a {@link DataTypes.RANGE}. + */ +export type Rangable = + | readonly [ + lower: T | InputRangePart | number | null, + higher: T | InputRangePart | number | null, + ] + | EmptyRange; + +/** + * This type represents the output of the {@link DataTypes.RANGE} data type. + */ +// number is always allowed because -Infinity & +Infinity are valid +export type Range = + | readonly [lower: RangePart | number | null, higher: RangePart | number | null] + | EmptyRange; + +type EmptyRange = []; + +export type RangePart = { value: T | number | null; inclusive: boolean }; +export type InputRangePart = { value: T | number | null; inclusive?: boolean }; + +/** + * Internal type - prone to changes. Do not export. + * + * @private + */ +export type ColumnReference = Col | { [Op.col]: string }; + +/** + * Internal type - prone to changes. Do not export. + * + * @private + */ +type WhereSerializableValue = boolean | string | number | Buffer | Date; + +/** + * Internal type - prone to changes. Do not export. + * + * @private + */ +type OperatorValues = + | StaticValues + | DynamicValues; + +/** + * Represents acceptable Dynamic values. + * + * Dynamic values, as opposed to {@link StaticValues}. i.e. column references, functions, etc... + */ +type DynamicValues = + | Literal + | ColumnReference + | Fn + | Cast + // where() can only be used on boolean attributes + | (AcceptableValues extends boolean ? Where : never); + +/** + * Represents acceptable Static values. + * + * Static values, as opposed to {@link DynamicValues}. i.e. booleans, strings, etc... + */ +type StaticValues = + Type extends Range + ? [lower: RangeType | RangePart, higher: RangeType | RangePart] + : Type extends any[] + ? { readonly [Key in keyof Type]: StaticValues } + : Type extends null + ? Type | WhereSerializableValue | null + : Type | WhereSerializableValue; + +/** + * Operators that can be used in {@link WhereOptions} + * + * @typeParam AttributeType - The JS type of the attribute the operator is operating on. + * + * See https://sequelize.org/docs/v7/core-concepts/model-querying-basics/#operators + */ +// TODO: default to something more strict than `any` which lists serializable values +export interface WhereOperators { + /** + * @example `[Op.eq]: 6,` becomes `= 6` + * @example `[Op.eq]: [6, 7]` becomes `= ARRAY[6, 7]` + * @example `[Op.eq]: null` becomes `IS NULL` + * @example `[Op.eq]: true` becomes `= true` + * @example `[Op.eq]: literal('raw sql')` becomes `= raw sql` + * @example `[Op.eq]: col('column')` becomes `= "column"` + * @example `[Op.eq]: fn('NOW')` becomes `= NOW()` + */ + [Op.eq]?: AllowAnyAll>; + + /** + * @example `[Op.ne]: 20,` becomes `!= 20` + * @example `[Op.ne]: [20, 21]` becomes `!= ARRAY[20, 21]` + * @example `[Op.ne]: null` becomes `IS NOT NULL` + * @example `[Op.ne]: true` becomes `!= true` + * @example `[Op.ne]: literal('raw sql')` becomes `!= raw sql` + * @example `[Op.ne]: col('column')` becomes `!= "column"` + * @example `[Op.ne]: fn('NOW')` becomes `!= NOW()` + */ + [Op.ne]?: WhereOperators[typeof Op.eq]; // accepts the same types as Op.eq + + /** + * @example `[Op.is]: null` becomes `IS NULL` + * @example `[Op.is]: true` becomes `IS TRUE` + * @example `[Op.is]: literal('value')` becomes `IS value` + */ + [Op.is]?: Extract | Literal; + + /** Example: `[Op.isNot]: null,` becomes `IS NOT NULL` */ + [Op.isNot]?: WhereOperators[typeof Op.is]; // accepts the same types as Op.is + + /** @example `[Op.gte]: 6` becomes `>= 6` */ + [Op.gte]?: AllowAnyAll>>; + + /** @example `[Op.lte]: 10` becomes `<= 10` */ + [Op.lte]?: WhereOperators[typeof Op.gte]; // accepts the same types as Op.gte + + /** @example `[Op.lt]: 10` becomes `< 10` */ + [Op.lt]?: WhereOperators[typeof Op.gte]; // accepts the same types as Op.gte + + /** @example `[Op.gt]: 6` becomes `> 6` */ + [Op.gt]?: WhereOperators[typeof Op.gte]; // accepts the same types as Op.gte + + /** + * @example `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` + */ + [Op.between]?: + | [ + lowerInclusive: OperatorValues>, + higherInclusive: OperatorValues>, + ] + | Literal; + + /** @example `[Op.notBetween]: [11, 15],` becomes `NOT BETWEEN 11 AND 15` */ + [Op.notBetween]?: WhereOperators[typeof Op.between]; + + /** @example `[Op.in]: [1, 2],` becomes `IN (1, 2)` */ + [Op.in]?: ReadonlyArray>> | Literal; + + /** @example `[Op.notIn]: [1, 2],` becomes `NOT IN (1, 2)` */ + [Op.notIn]?: WhereOperators[typeof Op.in]; + + /** + * @example `[Op.like]: '%hat',` becomes `LIKE '%hat'` + * @example `[Op.like]: { [Op.any]: ['cat', 'hat'] }` becomes `LIKE ANY (ARRAY['cat', 'hat'])` + */ + [Op.like]?: AllowAnyAll>>; + + /** + * @example `[Op.notLike]: '%hat'` becomes `NOT LIKE '%hat'` + * @example `[Op.notLike]: { [Op.any]: ['cat', 'hat']}` becomes `NOT LIKE ANY (ARRAY['cat', 'hat'])` + */ + [Op.notLike]?: WhereOperators[typeof Op.like]; + + /** + * case insensitive PG only + * + * @example `[Op.iLike]: '%hat'` becomes `ILIKE '%hat'` + * @example `[Op.iLike]: { [Op.any]: ['cat', 'hat']}` becomes `ILIKE ANY (ARRAY['cat', 'hat'])` + */ + [Op.iLike]?: WhereOperators[typeof Op.like]; + + /** + * PG only + * + * @example `[Op.notILike]: '%hat'` becomes `NOT ILIKE '%hat'` + * @example `[Op.notILike]: { [Op.any]: ['cat', 'hat']}` becomes `NOT ILIKE ANY (ARRAY['cat', 'hat'])` + */ + [Op.notILike]?: WhereOperators[typeof Op.like]; + + /** + * PG array & range 'overlaps' operator + * + * @example `[Op.overlap]: [1, 2]` becomes `&& [1, 2]` + */ + // https://www.postgresql.org/docs/14/functions-range.html range && range + // https://www.postgresql.org/docs/14/functions-array.html array && array + [Op.overlap]?: AllowAnyAll< + | // RANGE && RANGE + (AttributeType extends Range + ? Rangable + : // ARRAY && ARRAY + AttributeType extends any[] + ? StaticValues> + : never) + | DynamicValues + >; + + /** + * PG array & range 'contains' operator + * + * @example `[Op.contains]: [1, 2]` becomes `@> [1, 2]` + */ + // https://www.postgresql.org/docs/14/functions-json.html jsonb @> jsonb + // https://www.postgresql.org/docs/14/functions-range.html range @> range ; range @> element + // https://www.postgresql.org/docs/14/functions-array.html array @> array + [Op.contains]?: // RANGE @> ELEMENT + AttributeType extends Range + ? OperatorValues>> + : // jsonb @> ELEMENT + AttributeType extends object + ? OperatorValues> + : + | never + // ARRAY @> ARRAY ; RANGE @> RANGE + | WhereOperators[typeof Op.overlap]; + + /** + * PG array & range 'contained by' operator + * + * @example `[Op.contained]: [1, 2]` becomes `<@ [1, 2]` + */ + [Op.contained]?: AttributeType extends any[] + ? // ARRAY <@ ARRAY ; RANGE <@ RANGE + WhereOperators[typeof Op.overlap] + : // ELEMENT <@ RANGE + AllowAnyAll>>; + + /** + * Strings starts with value. + */ + [Op.startsWith]?: OperatorValues>; + /** + * Strings not starts with value. + */ + [Op.notStartsWith]?: WhereOperators[typeof Op.startsWith]; + /** + * String ends with value. + */ + [Op.endsWith]?: WhereOperators[typeof Op.startsWith]; + /** + * String not ends with value. + */ + [Op.notEndsWith]?: WhereOperators[typeof Op.startsWith]; + /** + * String contains value. + */ + [Op.substring]?: WhereOperators[typeof Op.startsWith]; + /** + * String not contains value. + */ + [Op.notSubstring]?: WhereOperators[typeof Op.startsWith]; + + /** + * MySQL/PG only + * + * Matches regular expression, case sensitive + * + * @example `[Op.regexp]: '^[h|a|t]'` becomes `REGEXP/~ '^[h|a|t]'` + */ + [Op.regexp]?: AllowAnyAll>>; + + /** + * MySQL/PG only + * + * Does not match regular expression, case sensitive + * + * @example `[Op.notRegexp]: '^[h|a|t]'` becomes `NOT REGEXP/!~ '^[h|a|t]'` + */ + [Op.notRegexp]?: WhereOperators[typeof Op.regexp]; + + /** + * PG only + * + * Matches regular expression, case insensitive + * + * @example `[Op.iRegexp]: '^[h|a|t]'` becomes `~* '^[h|a|t]'` + */ + [Op.iRegexp]?: WhereOperators[typeof Op.regexp]; + + /** + * PG only + * + * Does not match regular expression, case insensitive + * + * @example `[Op.notIRegexp]: '^[h|a|t]'` becomes `!~* '^[h|a|t]'` + */ + [Op.notIRegexp]?: WhereOperators[typeof Op.regexp]; + + /** @example `[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` becomes `@@ to_tsquery('fat & rat')` */ + [Op.match]?: AllowAnyAll>; + + /** + * PG only + * + * Whether the range is strictly left of the other range. + * + * @example + * ```typescript + * { rangeAttribute: { [Op.strictLeft]: [1, 2] } } + * // results in + * // "rangeAttribute" << [1, 2) + * ``` + * + * https://www.postgresql.org/docs/14/functions-range.html + */ + [Op.strictLeft]?: AttributeType extends Range + ? Rangable + : never | DynamicValues; + + /** + * PG only + * + * Whether the range is strictly right of the other range. + * + * @example + * ```typescript + * { rangeAttribute: { [Op.strictRight]: [1, 2] } } + * // results in + * // "rangeAttribute" >> [1, 2) + * ``` + * + * https://www.postgresql.org/docs/14/functions-range.html + */ + [Op.strictRight]?: WhereOperators[typeof Op.strictLeft]; + + /** + * PG only + * + * Whether the range extends to the left of the other range. + * + * @example + * ```typescript + * { rangeAttribute: { [Op.noExtendLeft]: [1, 2] } } + * // results in + * // "rangeAttribute" &> [1, 2) + * ``` + * + * https://www.postgresql.org/docs/14/functions-range.html + */ + [Op.noExtendLeft]?: WhereOperators[typeof Op.strictLeft]; + + /** + * PG only + * + * Whether the range extends to the right of the other range. + * + * @example + * ```typescript + * { rangeAttribute: { [Op.noExtendRight]: [1, 2] } } + * // results in + * // "rangeAttribute" &< [1, 2) + * ``` + * + * https://www.postgresql.org/docs/14/functions-range.html + */ + [Op.noExtendRight]?: WhereOperators[typeof Op.strictLeft]; + + /** + * PG only + * + * Whether the two ranges are adjacent. + * + * @example + * ```typescript + * { rangeAttribute: { [Op.adjacent]: [1, 2] } } + * // results in + * // "rangeAttribute" -|- [1, 2) + * ``` + * + * https://www.postgresql.org/docs/14/functions-range.html + */ + [Op.adjacent]?: WhereOperators[typeof Op.strictLeft]; + + /** + * PG only + * + * Check if any of these array strings exist as top-level keys. + * + * @example + * ```typescript + * { jsonbAttribute: { [Op.anyKeyExists]: ['a','b'] } } + * // results in + * // "jsonbAttribute" ?| ARRAY['a','b'] + * ``` + * + * https://www.postgresql.org/docs/current/functions-json.html + */ + [Op.anyKeyExists]?: string[] | DynamicValues; + + /** + * PG only + * + * Check if all of these array strings exist as top-level keys. + * + * @example + * ```typescript + * { jsonbAttribute: { [Op.allKeysExist]: ['a','b'] } } + * // results in + * // "jsonbAttribute" ?& ARRAY['a','b'] + * ``` + * + * https://www.postgresql.org/docs/current/functions-json.html + */ + [Op.allKeysExist]?: WhereOperators[typeof Op.anyKeyExists]; +} + +/** + * Where Geometry Options + */ +export interface WhereGeometryOptions { + type: string; + coordinates: ReadonlyArray; +} + +/** + * Through options for Include Options + */ +export interface IncludeThroughOptions extends Filterable, Projectable { + /** + * The alias for the join model, in case you want to give it a different name than the default one. + */ + 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. + * + * @default true + */ + paranoid?: boolean; +} + +/** + * Options for eager-loading associated models. + * + * The association can be specified in different ways: + * - Using the name of the association: `{ include: 'associationName' }` *(recommended)* + * - Using a reference to the association: `{ include: MyModel.associations['associationName'] }` + * - Using the model to eager-load (an association must still be defined!): `{ include: Model1 }` + * - if the association with that model has an alias, you need to specify it too: `{ include: { model: Model1, as: 'Alias' } }` + * + * You can also eagerly load all associations using `{ include: { all: true } }` *(not recommended outside of debugging)* + */ +export type Includeable = + | ModelStatic + | Association + | IncludeOptions + | { all: true; nested?: true } + | string; + +/** + * Complex include options + */ +export interface IncludeOptions extends Filterable, Projectable, Paranoid { + /** + * Mark the include as duplicating, will prevent a subquery from being used. + */ + duplicating?: boolean; + + /** + * The model you want to eagerly load. + * + * This option only works if this model is only associated once to the parent model of this query. + * We recommend specifying {@link IncludeOptions.association} instead. + */ + model?: ModelStatic; + + /** + * The alias of the association. Used along with {@link IncludeOptions.model}. + * + * This must be specified if the association has an alias (i.e. "as" was used when defining the association). + * For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany` / `belongsToMany`, + * it should be the plural. + * + * @deprecated using "as" is the same as using {@link IncludeOptions.association} + * because "as" is always the name of the association. + */ + as?: string; + + /** + * The association you want to eagerly load. + * Either one of the values available in {@link Model.associations}, or the name of the association. + * + * This can be used instead of providing a model/as pair. + * + * This is the recommended method. + */ + association?: Association | string; + + /** + * Custom `ON` clause, overrides default. + */ + on?: WhereOptions; + + /** + * Whether to bind the ON and WHERE clause together by OR instead of AND. + * + * @default false + */ + or?: boolean; + + /** + * Where clauses to apply to the child model + * + * Note that this converts the eager load to an inner join, + * unless you explicitly set {@link IncludeOptions.required} to false + */ + where?: WhereOptions; + + /** + * 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. + */ + required?: boolean; + + /** + * If true, converts to a right join if dialect support it. + * + * Incompatible with {@link IncludeOptions.required}. + */ + right?: boolean; + + /** + * Limit include. + * + * Only available when setting {@link IncludeOptions.separate} to true. + */ + limit?: number | Literal | Nullish; + + /** + * If true, runs a separate query to fetch the associated instances. + * + * @default false + */ + separate?: boolean; + + /** + * Through Options + */ + through?: IncludeThroughOptions; + + /** + * Load further nested related models + */ + include?: AllowArray; + + /** + * Order include. Only available when setting `separate` to true. + */ + order?: Order; + + /** + * Use sub queries. This should only be used if you know for sure the query does not result in a cartesian product. + */ + subQuery?: boolean; +} + +type OrderItemAssociation = + | Association + | ModelStatic + | { model: ModelStatic; as: string } + | string; +type OrderItemColumn = string | Col | Fn | Literal; +export type OrderItem = + | string + | Fn + | Col + | Literal + | [OrderItemColumn, string] + | [OrderItemAssociation, OrderItemColumn] + | [...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 = readonly [ + expressionOrAttributeName: string | DynamicSqlExpression, + alias: string, +]; + +export type FindAttributeOptions = + | Array | ProjectionAlias | Literal> + | { + exclude: Array>; + include?: Array | ProjectionAlias>; + } + | { + exclude?: Array>; + include: Array | ProjectionAlias>; + }; + +export interface IndexHint { + type: IndexHints; + values: string[]; +} + +export interface IndexHintable { + /** + * MySQL only. + */ + indexHints?: IndexHint[]; +} + +export interface MaxExecutionTimeHintable { + /** + * This sets the max execution time for MySQL. + */ + maxExecutionTimeHintMs?: number; +} + +/** + * Options that are passed to any model creating a SELECT query + * + * A hash of options to describe the scope of the search + */ +export interface FindOptions + extends QueryOptions, + Filterable, + Projectable, + Paranoid, + IndexHintable, + SearchPathable, + MaxExecutionTimeHintable { + /** + * A list of associations to eagerly load using a left join (a single association is also supported). + * + * See {@link Includeable} to see how to specify the association, and its eager-loading options. + */ + include?: AllowArray; + + /** + * Specifies an ordering. If a string is provided, it will be escaped. + * + * Using an array, you can provide several attributes / 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. + * + * @example + * `order: [['name', 'DESC']]`. + * + * The attribute will be escaped, but the direction will not. + */ + order?: Order; + + /** + * GROUP BY in sql + */ + group?: GroupOption; + + /** + * Limits how many items will be retrieved by the operation. + * + * If `limit` and `include` are used together, Sequelize will turn the `subQuery` option on by default. + * This is done to ensure that `limit` only impacts the Model on the same level as the `limit` option. + * + * You can disable this behavior by explicitly setting `subQuery: false`, however `limit` will then + * affect the total count of returned values, including eager-loaded associations, instead of just one table. + * + * @example + * ```javascript + * // in the following query, `limit` only affects the "User" model. + * // This will return 2 users, each including all of their projects. + * User.findAll({ + * limit: 2, + * include: [User.associations.projects], + * }); + * ``` + * + * @example + * ```javascript + * // in the following query, `limit` affects the total number of returned values, eager-loaded associations included. + * // This may return 2 users, each with one project, + * // or 1 user with 2 projects. + * User.findAll({ + * limit: 2, + * include: [User.associations.projects], + * subQuery: false, + * }); + * ``` + */ + limit?: number | Literal | Nullish; + + // TODO: document this - this is an undocumented property but it exists and there are tests for it. + groupedLimit?: unknown; + + /** + * Skip the first n items of the results. + */ + offset?: number | Literal | Nullish; + + /** + * 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 {@link Lock}. + */ + lock?: Lock | { level: Lock; of: ModelStatic } | boolean; + + /** + * Skip locked rows. Only supported in Postgres. + */ + skipLocked?: boolean; + + /** + * Return raw result. See {@link Sequelize#query} for more information. + */ + raw?: boolean; + + /** + * Controls whether aliases are minified in this query. + * This overrides the global option + */ + minifyAliases?: boolean; + + /** + * Select group rows after groups and aggregates are computed. + */ + having?: WhereOptions; + + /** + * Use sub queries (internal). + * + * If unspecified, this will `true` by default if `limit` is specified, and `false` otherwise. + * See {@link FindOptions#limit} for more information. + */ + subQuery?: boolean; + + /** + * Throws an error if the query would return 0 results. + */ + rejectOnEmpty?: boolean | Error; + + /** + * Use a table hint for the query, only supported in MSSQL. + */ + tableHints?: TableHints[]; +} + +export interface NonNullFindOptions extends FindOptions { + /** + * Throw if nothing was found. + */ + rejectOnEmpty: true | Error; +} + +export interface FindByPkOptions extends FindOptions> {} + +export interface NonNullFindByPkOptions + extends NonNullFindOptions> {} + +/** + * Options for Model.count method + */ +export interface CountOptions + extends Logging, + Transactionable, + Filterable, + Projectable, + Paranoid, + Poolable, + MaxExecutionTimeHintable { + /** + * Include options. See `find` for details + */ + include?: AllowArray; + + /** + * Apply COUNT(DISTINCT(col)) + */ + distinct?: boolean; + + /** + * GROUP BY in sql + * Used in conjunction with `attributes`. + * + * @see Projectable + */ + group?: GroupOption; + + /** + * Column on which COUNT() should be applied + */ + col?: string; + + /** + * Count number of records returned by group by + * Used in conjunction with `group`. + */ + countGroupedRows?: boolean; +} + +/** + * Options for Model.count when GROUP BY is used + */ +export type CountWithOptions = SetRequired, 'group'>; + +export interface FindAndCountOptions + extends CountOptions, + FindOptions {} + +export interface GroupedCountResultItem { + [key: string]: unknown; // projected attributes + count: number; // the count for each group +} + +/** + * Options for Model.build method + */ +export interface BuildOptions { + /** + * If set to true, values will ignore field and virtual setters. + * + * @default false + */ + raw?: boolean; + + /** + * Is this record new + */ + isNewRecord?: boolean; + + /** + * An array of include options. A single option is also supported - Used to build prefetched/included model instances. See `set` + */ + include?: AllowArray; +} + +export interface Silent { + /** + * If true, the updatedAt timestamp will not be updated. + * + * @default false + */ + silent?: boolean; +} + +/** + * Options for Model.create method + */ +export interface CreateOptions + extends BuildOptions, + Logging, + Silent, + Transactionable, + Hookable, + SearchPathable { + /** + * If set, only columns matching those in fields will be saved + */ + fields?: Array; + + /** + * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE + */ + ignoreDuplicates?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | Array; + + /** + * If false, validations won't be run. + * + * @default true + */ + validate?: boolean; +} + +export interface Hookable { + /** + * If `false` the applicable hooks will not be called. + * The default value depends on the context. + * + * @default true + */ + hooks?: boolean; +} + +/** + * Options for Model.findOrCreate method + */ +export interface FindOrCreateOptions + extends FindOptions, + CreateOptions { + /** + * Default values to use if building a new instance + */ + defaults?: TCreationAttributes; +} + +/** + * Options for Model.findOrBuild method + */ +export interface FindOrBuildOptions + extends FindOptions, + BuildOptions { + /** + * Default values to use if building a new instance + */ + defaults?: TCreationAttributes; +} + +/** + * Options for Model.upsert method + */ +export interface UpsertOptions + extends Logging, + Transactionable, + SearchPathable, + Hookable { + /** + * The fields to insert / update. Defaults to all fields. + * + * If none of the specified fields are present on the provided `values` object, + * an insert will still be attempted, but duplicate key conflicts will be ignored. + */ + fields?: Array; + + /** + * Fetch back the affected rows (only for postgres) + */ + returning?: boolean | Array; + + /** + * Run validations before the row is inserted + * + * @default true + */ + validate?: boolean; + /** + * An optional parameter that specifies a where clause for the `ON CONFLICT` part of the query + * (in particular: for applying to partial unique indexes). + * Only supported in Postgres >= 9.5 and SQLite >= 3.24.0 + */ + conflictWhere?: WhereOptions; + /** + * Optional override for the conflict fields in the ON CONFLICT part of the query. + * Only supported in Postgres >= 9.5 and SQLite >= 3.24.0 + */ + conflictFields?: Array; +} + +/** + * Options for Model.bulkCreate method + */ +export interface BulkCreateOptions + extends Logging, + Transactionable, + Hookable, + SearchPathable { + /** + * Fields to insert (defaults to all fields) + */ + fields?: Array; + + /** + * Should each row be subject to validation before it is inserted. + * The whole insert will fail if one row fails validation + * + * @default false + */ + validate?: boolean; + + /** + * Run before / after create hooks for each individual Instance? + * BulkCreate hooks will still be run if {@link BulkCreateOptions.hooks} is true. + * + * @default false + */ + individualHooks?: boolean; + + /** + * Ignore duplicate values for primary keys? + * + * @default false + */ + ignoreDuplicates?: boolean; + + /** + * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, + * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). + */ + updateOnDuplicate?: Array; + + /** + * Include options. See `find` for details + */ + include?: AllowArray; + + /** + * Return all columns or only the specified columns for the affected rows (only for postgres) + */ + returning?: boolean | Array; + + /** + * An optional parameter to specify a where clause for partial unique indexes + * (note: `ON CONFLICT WHERE` not `ON CONFLICT DO UPDATE WHERE`). + * Only supported in Postgres >= 9.5 and sqlite >= 9.5 + */ + conflictWhere?: WhereOptions; + /** + * Optional override for the conflict fields in the ON CONFLICT part of the query. + * Only supported in Postgres >= 9.5 and SQLite >= 3.24.0 + */ + conflictAttributes?: Array; +} + +/** + * The options accepted by {@link Model.truncate}. + */ +export interface TruncateOptions extends Logging, Transactionable, Hookable { + /** + * Truncates all tables that have foreign-key references to the + * named table, or to any tables added to the group due to CASCADE. + * + * @default false + */ + cascade?: boolean; + + /** + * Automatically restart sequences owned by columns of the truncated table + * + * @default false + */ + restartIdentity?: boolean; +} + +/** + * Options accepted by {@link Model.destroy}. + */ +export interface DestroyOptions + extends Logging, + Transactionable, + Hookable, + Filterable { + /** + * If set to true, destroy will SELECT all records matching the where parameter and will execute before / + * after destroy hooks on each row + * + * @default false + */ + individualHooks?: boolean; + + /** + * How many rows to delete + */ + limit?: number | Literal | Nullish; + + /** + * Delete instead of setting deletedAt to current timestamp (only applicable if `paranoid` is enabled) + * + * @default false + */ + force?: boolean; +} + +/** + * Options for Model.restore + */ +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 + * bulkRestore hooks on each row + */ + individualHooks?: boolean; + + /** + * How many rows to undelete + */ + limit?: number | Literal | Nullish; +} + +/** + * Options used for Model.update + */ +export interface UpdateOptions + extends Logging, + Transactionable, + Paranoid, + Hookable { + /** + * Options to describe the scope of the search. + */ + where: WhereOptions; + + /** + * Fields to update (defaults to all fields) + */ + fields?: Array; + + /** + * Should each row be subject to validation before it is inserted. The whole insert will fail if one row + * fails validation. + * + * @default true + */ + validate?: boolean; + + /** + * Whether to update the side effects of any virtual setters. + * + * @default true + */ + sideEffects?: boolean; + + /** + * Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. + * A select is needed, because the row data needs to be passed to the hooks + * + * @default false + */ + individualHooks?: boolean; + + /** + * Return the affected rows (only for postgres) + * + * @default false + */ + returning?: boolean | Array; + + /** + * How many rows to update + * + * Only for mysql and mariadb, + * Implemented as TOP(n) for MSSQL; for sqlite it is supported only when rowid is present + */ + limit?: number | Literal | Nullish; + + /** + * If true, the updatedAt timestamp will not be updated. + */ + silent?: boolean; +} + +/** + * A pojo of values to update. + * + * Used by {@link Model.update} + */ +export type UpdateValues = { + [key in keyof Attributes]?: Attributes[key] | Fn | Col | Literal; +}; + +/** + * Options used for Model.aggregate + */ +export interface AggregateOptions + extends QueryOptions, + Filterable, + Paranoid { + /** + * The type of the result. If attribute being aggregated is a defined in the Model, + * the default will be the type of that attribute, otherwise defaults to a plain JavaScript `number`. + */ + dataType?: string | T; + + /** + * Applies DISTINCT to the field being aggregated over + */ + distinct?: boolean; +} + +// instance + +/** + * Options used for Instance.increment method + */ +export interface IncrementDecrementOptions + extends Logging, + Transactionable, + Silent, + SearchPathable, + Filterable { + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | Array; +} + +/** + * Options used for Instance.increment method + */ +export interface IncrementDecrementOptionsWithBy + extends IncrementDecrementOptions { + /** + * The number to increment by + * + * @default 1 + */ + by?: number; +} + +/** + * Options used for Instance.restore method + */ +export interface InstanceRestoreOptions extends Logging, Transactionable {} + +/** + * Options used for Instance.destroy method + */ +export interface InstanceDestroyOptions extends Logging, Transactionable, Hookable { + /** + * If set to true, paranoid models will actually be deleted + */ + force?: boolean; +} + +/** + * Options used for Instance.update method + */ +export interface InstanceUpdateOptions + extends SaveOptions, + SetOptions, + Filterable {} + +/** + * Options used for Instance.set method + */ +export interface SetOptions { + /** + * If set to true, field and virtual setters will be ignored + */ + raw?: boolean; + + /** + * Clear all previously set data values + */ + reset?: boolean; +} + +/** + * Options used for Instance.save method + */ +export interface SaveOptions + extends Logging, + Transactionable, + Silent, + Hookable, + SearchPathable { + /** + * An optional array of strings, representing database columns. If fields is provided, only those columns + * will be validated and saved. + */ + fields?: Array; + + /** + * If false, validations won't be run. + * + * @default true + */ + validate?: boolean; + + /** + * A flag that defines if null values should be passed as values or not. + * + * @default false + */ + omitNull?: boolean; + + association?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | Array; +} + +/** + * 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. + * + * The validations are implemented by validator.js. + */ +export interface ColumnValidateOptions { + /** + * - `{ is: ['^[a-z]+$','i'] }` will only allow letters + * - `{ is: /^[a-z]+$/i }` also only allows letters + */ + is?: + | string + | ReadonlyArray + | RegExp + | { msg: string; args: string | ReadonlyArray | RegExp }; + + /** + * - `{ not: ['[a-z]','i'] }` will not allow letters + */ + not?: + | string + | ReadonlyArray + | RegExp + | { msg: string; args: string | ReadonlyArray | RegExp }; + + /** + * checks for email format (foo@bar.com) + */ + isEmail?: boolean | { msg: string }; + + /** + * checks for url format (http://foo.com) + */ + isUrl?: boolean | { msg: string }; + + /** + * checks for IPv4 (129.89.23.1) or IPv6 format + */ + isIP?: boolean | { msg: string }; + + /** + * checks for IPv4 (129.89.23.1) + */ + isIPv4?: boolean | { msg: string }; + + /** + * checks for IPv6 format + */ + isIPv6?: boolean | { msg: string }; + + /** + * will only allow letters + */ + isAlpha?: boolean | { msg: string }; + + /** + * will only allow alphanumeric characters, so "_abc" will fail + */ + isAlphanumeric?: boolean | { msg: string }; + + /** + * will only allow numbers + */ + isNumeric?: boolean | { msg: string }; + + /** + * checks for valid integers + */ + isInt?: boolean | { msg: string }; + + /** + * checks for valid floating point numbers + */ + isFloat?: boolean | { msg: string }; + + /** + * checks for any numbers + */ + isDecimal?: boolean | { msg: string }; + + /** + * checks for lowercase + */ + isLowercase?: boolean | { msg: string }; + + /** + * checks for uppercase + */ + isUppercase?: boolean | { msg: string }; + + /** + * won't allow null + */ + // TODO: remove. Already checked by allowNull + notNull?: boolean | { msg: string }; + + /** + * only allows null + */ + // TODO: remove. Already checked by allowNull + isNull?: boolean | { msg: string }; + + /** + * don't allow empty strings + */ + notEmpty?: boolean | { msg: string }; + + /** + * only allow a specific value + */ + equals?: string | { msg: string }; + + /** + * force specific substrings + */ + contains?: string | { msg: string }; + + /** + * check the value is not one of these + */ + notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + + /** + * check the value is one of these + */ + isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + + /** + * don't allow specific substrings + */ + notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string }; + + /** + * only allow values with length between 2 and 10 + */ + len?: readonly [number, number] | { msg: string; args: readonly [number, number] }; + + /** + * only allow uuids + */ + isUUID?: number | { msg: string; args: number }; + + /** + * only allow date strings + */ + isDate?: boolean | { msg: string; args: boolean }; + + /** + * only allow date strings after a specific date + */ + isAfter?: string | { msg: string; args: string }; + + /** + * only allow date strings before a specific date + */ + isBefore?: string | { msg: string; args: string }; + + /** + * only allow values + */ + max?: number | { msg: string; args: readonly [number] }; + + /** + * only allow values >= 23 + */ + min?: number | { msg: string; args: readonly [number] }; + + /** + * only allow arrays + */ + isArray?: boolean | { msg: string; args: boolean }; + + /** + * check for valid credit card numbers + */ + 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 + */ + [name: string]: unknown; +} + +/** + * Interface for name property in InitOptions + */ +export interface ModelNameOptions { + /** + * Singular model name + */ + singular?: string; + + /** + * Plural model name + */ + plural?: string; +} + +/** + * Interface for Define Scope Options + */ +export interface ModelScopeOptions { + /** + * Name of the scope and it's query + */ + [scopeName: string]: + | FindOptions + | ((...args: readonly any[]) => FindOptions); +} + +/** + * References options for the column's attributes + */ +export interface AttributeReferencesOptions { + /** + * The Model to reference. + * + * Ignored if {@link table} is specified. + */ + model?: ModelStatic; + + /** + * The name of the table to reference (the sql name). + */ + table?: TableName; + + /** + * The column on the target model that this foreign key references + */ + key?: string; + + /** + * When to check for the foreign key constraint + * + * PostgreSQL only + */ + deferrable?: Deferrable; +} + +export interface NormalizedAttributeReferencesOptions + extends Omit { + /** + * The name of the table to reference (the sql name). + */ + readonly table: TableName; +} + +// TODO: when merging model.d.ts with model.js, make this an enum. +export type ReferentialAction = 'CASCADE' | 'RESTRICT' | 'SET DEFAULT' | 'SET NULL' | 'NO ACTION'; + +/** + * Column options for the model schema attributes. + * Used in {@link Model.init} and {@link Sequelize#define}, and the Attribute decorator. + */ +// TODO: Link to Attribute decorator once it's possible to have multiple entry points in the docs: https://github.com/TypeStrong/typedoc/issues/2138 +export interface AttributeOptions { + /** + * A string or a data type. + * + * @see https://sequelize.org/docs/v7/models/data-types/ + */ + type: DataType; + + /** + * If false, the column will have a NOT NULL constraint, and a not null validation will be run before an + * instance is saved. + * + * @default true + */ + allowNull?: boolean | undefined; + + /** + * @deprecated use {@link columnName} instead. + */ + field?: string | undefined; + + /** + * The name of the column. + * + * If no value is provided, Sequelize will use the name of the attribute (in snake_case if {@link InitOptions.underscored} is true) + */ + columnName?: string | undefined; + + /** + * A literal default value, a JavaScript function, or an SQL function (using {@link sql.fn}) + */ + defaultValue?: unknown | undefined; + + /** + * If true, the column will get a unique constraint. If a string is provided, the column will be part of a + * composite unique index. If multiple columns have the same string, they will be part of the same unique + * index + */ + unique?: AllowArray | undefined; + + /** + * If true, an index will be created for this column. + * If a string is provided, the column will be part of a composite index together with the other attributes that specify the same index name. + */ + index?: AllowArray | undefined; + + /** + * If true, this attribute will be marked as primary key + */ + primaryKey?: boolean | undefined; + + /** + * Is this field an auto increment field + */ + autoIncrement?: boolean | undefined; + + /** + * If this field is a Postgres auto increment field, use Postgres `GENERATED BY DEFAULT AS IDENTITY` instead of `SERIAL`. Postgres 10+ only. + */ + autoIncrementIdentity?: boolean | undefined; + + /** + * Comment to add on the column in the database. + */ + comment?: string | undefined; + + /** + * Makes this attribute a foreign key. + * You typically don't need to use this yourself, instead use associations. + * + * Setting this value to a string equivalent to setting it to `{ tableName: 'myString' }`. + */ + references?: string | ModelStatic | AttributeReferencesOptions | undefined; + + /** + * What should happen when the referenced key is updated. + * One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION + */ + onUpdate?: ReferentialAction | undefined; + + /** + * What should happen when the referenced key is deleted. + * One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION + */ + onDelete?: ReferentialAction | undefined; + + /** + * 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 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, + * it is async, the callback should be called with the error text. + */ + validate?: ColumnValidateOptions | undefined; + + /** + * Provide a custom getter for this column. + * Use {@link Model.getDataValue} to access the underlying values. + */ + get?: ((this: M) => unknown) | undefined; + + /** + * Provide a custom setter for this column. + * Use {@link Model.setDataValue} to access the underlying values. + */ + set?: ((this: M, val: any) => void) | undefined; + + /** + * This attribute is added by sequelize. Do not use! + * + * @private + */ + // TODO: use a private symbol + _autoGenerated?: boolean | undefined; +} + +export interface AttributeIndexOptions extends Omit { + /** + * Configures the options for this index attribute. + */ + attribute?: Omit; +} + +export interface NormalizedAttributeOptions + extends Readonly< + Omit< + StrictRequiredBy, 'columnName'>, + | 'type' + // index and unique are always removed from attribute options, Model.getIndexes() must be used instead. + | 'index' + | 'unique' + > + > { + /** + * @deprecated use {@link NormalizedAttributeOptions.attributeName} instead. + */ + readonly fieldName: string; + + /** + * The name of the attribute (JS side). + */ + readonly attributeName: string; + + /** + * Like {@link AttributeOptions.type}, but normalized. + */ + readonly type: NormalizedDataType; + readonly references?: NormalizedAttributeReferencesOptions; +} + +/** + * Interface for Attributes provided for all columns in a model + */ +export type ModelAttributes = { + /** + * The description of a database column + */ + [name in keyof TAttributes]: DataType | AttributeOptions; +}; + +/** + * Options for model definition. + * + * Used by {@link Sequelize.define}, {@link Model.init}, and the {@link decorators-legacy.Table} decorator. + * + * @see https://sequelize.org/docs/v7/core-concepts/model-basics/ + */ +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. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + */ + defaultScope?: FindOptions> | undefined; + + /** + * More scopes, defined in the same way as {@link ModelOptions.defaultScope} above. + * See {@link Model.scope} for more information about how scopes are defined, and what you can do with them. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + */ + scopes?: ModelScopeOptions> | undefined; + + /** + * Don't persist null values. This means that all columns with null values will not be saved. + * + * @default false + */ + omitNull?: boolean | undefined; + + /** + * Sequelize will automatically add a primary key called `id` if no + * primary key has been added manually. + * + * Set to false to disable adding that primary key. + * + * @default false + */ + noPrimaryKey?: boolean | undefined; + + /** + * Adds createdAt and updatedAt timestamps to the model. + * + * @default true + */ + timestamps?: boolean | undefined; + + /** + * If true, calling {@link Model.destroy} will not delete the model, but will instead set a `deletedAt` timestamp. + * + * This options requires {@link ModelOptions.timestamps} to be true. + * The `deletedAt` column can be customized through {@link ModelOptions.deletedAt}. + * + * @default false + */ + paranoid?: boolean | undefined; + + /** + * If true, Sequelize will snake_case the name of columns that do not have an explicit value set (using {@link AttributeOptions.field}). + * The name of the table will also be snake_cased, unless {@link ModelOptions.tableName} is set, or {@link ModelOptions.freezeTableName} is true. + * + * @default false + */ + underscored?: boolean | undefined; + + /** + * Indicates if the model's table has a trigger associated with it. + * + * @default false + */ + hasTrigger?: boolean | undefined; + + /** + * If true, sequelize will use the name of the Model as-is as the name of the SQL table. + * If false, the name of the table will be pluralised (and snake_cased if {@link ModelOptions.underscored} is true). + * + * This option has no effect if {@link ModelOptions.tableName} is set. + * + * @default false + */ + freezeTableName?: boolean | undefined; + + // TODO: merge with modelName + /** + * An object with two attributes, `singular` and `plural`, which are used when this model is associated to others. + * + * Not inherited. + */ + name?: ModelNameOptions | undefined; + + /** + * The name of the model. + * + * If not set, the name of the class will be used instead. + * You should specify this option if you are going to minify your code in a way that may mangle the class name. + * + * Not inherited. + */ + modelName?: string | undefined; + + /** + * Indexes for the provided database table + */ + indexes?: readonly IndexOptions[] | undefined; + + /** + * Override the name of the createdAt attribute if a string is provided, or disable it if false. + * {@link ModelOptions.timestamps} must be true. + * + * Not affected by underscored setting. + */ + createdAt?: string | boolean | undefined; + + /** + * Override the name of the deletedAt attribute if a string is provided, or disable it if false. + * {@link ModelOptions.timestamps} must be true. + * {@link ModelOptions.paranoid} must be true. + * + * Not affected by underscored setting. + */ + deletedAt?: string | boolean | undefined; + + /** + * Override the name of the updatedAt attribute if a string is provided, or disable it if false. + * {@link ModelOptions.timestamps} must be true. + * + * Not affected by underscored setting. + */ + updatedAt?: string | boolean | undefined; + + /** + * The name of the table in SQL. + * + * @default The {@link ModelOptions.modelName}, pluralized, + * unless freezeTableName is true, in which case it uses model name + * verbatim. + * + * Not inherited. + */ + tableName?: string | undefined; + + // TODO: merge these two together into one using SchemaOptions, or replace with tableName: TableNameWithSchema + /** + * The database schema in which this table will be located. + */ + schema?: string | undefined; + schemaDelimiter?: string | undefined; + + /** + * The name of the database storage engine to use (e.g. MyISAM, InnoDB). + * + * MySQL, MariaDB only. + */ + engine?: string | undefined; + + /** + * The charset to use for the model + */ + charset?: string | undefined; + + /** + * A comment for the table. + * + * MySQL, PG only. + */ + comment?: string | undefined; + + /** + * The collation for model's table + */ + collate?: string | undefined; + + /** + * Set the initial AUTO_INCREMENT value for the table in MySQL. + */ + initialAutoIncrement?: string | undefined; + + /** + * Add hooks to the model. + * Hooks will be called before and after certain operations. + * + * This can also be done through {@link Model.addHook}, or the individual hook methods such as {@link Model.afterBulkCreate}. + * Each property can either be a function, or an array of functions. + * + * @see https://sequelize.org/docs/v7/other-topics/hooks/ + */ + hooks?: + | { + [Key in keyof ModelHooks>]?: AllowArray< + | ModelHooks>[Key] + | { name: string | symbol; callback: ModelHooks>[Key] } + >; + } + | undefined; + + /** + * 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. + */ + validate?: + | { + /** + * Custom validation functions run on all instances of the model. + */ + [name: string]: ( + this: CreationAttributes & ExtractMethods, + callback?: (err: unknown) => void, + ) => void; + } + | undefined; + + /** + * 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. + * - If string: Uses the named attribute. + * - If boolean: Uses `version`. + * + * @default false + */ + version?: boolean | string | undefined; +} + +/** + * Options passed to {@link Model.init} + */ +export interface InitOptions extends ModelOptions { + /** + * The sequelize connection. Required ATM. + */ + sequelize: Sequelize; +} + +export type BuiltModelName = Required; +export type BuiltModelOptions = Omit< + StrictRequiredBy< + InitOptions, + 'modelName' | 'indexes' | 'underscored' | 'validate' | 'tableName' + >, + 'name' | 'sequelize' +> & { name: BuiltModelName }; + +/** + * AddScope Options for Model.addScope + */ +export interface AddScopeOptions { + /** + * If a scope of the same name already exists, should it be overwritten? + */ + override: boolean; +} + +export interface ModelGetOptions { + /** + * Only evaluated when "key === undefined". If set to true, all objects including child-objects will be copies and will not reference the original dataValues-object. + * + * @default false + */ + clone?: boolean; + + /** + * If set to true, included instances will be returned as plain objects + * + * @default false + */ + plain?: boolean; + + /** + * If set to true, field and virtual setters will be ignored. + * + * @default false + */ + raw?: boolean; +} + +/** + * A Model represents a table in the database. Instances of this class represent a database row. + */ +export abstract class Model< + TModelAttributes extends {} = any, + TCreationAttributes extends {} = TModelAttributes, +> extends ModelTypeScript { + /** + * 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; + * ``` + * + * @deprecated This property will become a Symbol in v7 to prevent collisions. + * Use Attributes instead of this property to be forward-compatible. + */ + _attributes: TModelAttributes; // TODO [>6]: make this a non-exported symbol (same as the one in hooks.d.ts) + + /** + * Object that contains underlying model data + */ + dataValues: TModelAttributes; + + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + * + * @deprecated This property will become a Symbol in v7 to prevent collisions. + * Use CreationAttributes instead of this property to be forward-compatible. + */ + _creationAttributes: TCreationAttributes; // TODO [>6]: make this a non-exported symbol (same as the one in hooks.d.ts) + + /** + * Returns the attributes of the model + */ + static getAttributes( + this: ModelStatic, + ): { + readonly [Key in keyof Attributes]: NormalizedAttributeOptions; + }; + + /** + * Checks whether an association with this name has already been registered. + * + * @param {string} alias + * @returns {boolean} + */ + // TODO: deprecate & rename to 'hasAssociation' to mirror getAssociation. + static hasAlias(alias: string): boolean; + + /** + * Returns the association that matches the name parameter. + * Throws if no such association has been defined. + * + * @param name + */ + static getAssociation(name: string): Association; + + /** + * Returns all associations that have 'target' as their target. + */ + static getAssociations( + this: ModelStatic, + target: ModelStatic, + ): Array>; + + /** + * Returns the association for which the target matches the 'target' parameter, and the alias ("as") matches the 'alias' parameter + * + * Throws if no such association were found. + */ + static getAssociationWithModel( + this: ModelStatic, + target: ModelStatic, + alias: string, + ): Association; + + /** + * Remove attribute from model definition. + * Only use if you know what you're doing. + * + * @param attribute + */ + static removeAttribute(attribute: string): void; + + /** + * Merges new attributes with the existing ones. + * Only use if you know what you're doing. + * + * Warning: Attributes are not replaced, they are merged. + * The existing configuration for an attribute takes priority over the new configuration. + * + * @param newAttributes + */ + static mergeAttributesDefault(newAttributes: { + [key: string]: AttributeOptions; + }): NormalizedAttributeOptions; + + /** + * Creates this table in the database, if it does not already exist. + * + * Works like {@link Sequelize#sync}, but only this model is synchronised. + */ + static sync(options?: SyncOptions): Promise; + + /** + * Drop the table represented by this Model + * + * @param options + */ + static drop(options?: DropOptions): Promise; + + /** + * Returns a copy of this model with the corresponding table located in the specified schema. + * + * For postgres, this will actually place the schema in front of the table name (`"schema"."tableName"`), + * while the schema will be prepended to the table name for mysql and sqlite (`'schema.tablename'`). + * + * This method is intended for use cases where the same model is needed in multiple schemas. + * In such a use case it is important to call {@link Model.sync} (or use migrations!) for each model created by this method + * to ensure the models are created in the correct schema. + * + * If a single default schema per model is needed, set the {@link ModelOptions.schema} instead. + * + * @param schema The name of the schema. Passing a string is equivalent to setting {@link SchemaOptions.schema}. + */ + static withSchema( + this: ModelStatic, + schema: string | SchemaOptions | Nullish, + ): ModelStatic; + + /** + * @deprecated this method has been renamed to {@link Model.withSchema} to emphasise the fact that this method + * does not mutate the model and instead returns a new one. + */ + static schema( + this: ModelStatic, + schema: string | Nullish, + options?: { schemaDelimiter?: string } | string, + ): ModelStatic; + + /** + * Creates a copy of this model, with one or more scopes applied. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + * + * @param scopes The scopes 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 A copy of this model, with the scopes applied. + */ + static withScope( + this: ModelStatic, + scopes?: AllowReadonlyArray | WhereOptions> | Nullish, + ): ModelStatic; + + /** + * @deprecated this method has been renamed to {@link Model.withScope} to emphasise the fact that + * this method does not mutate the model, but returns a new model. + */ + static scope( + this: ModelStatic, + scopes?: AllowReadonlyArray | WhereOptions> | Nullish, + ): ModelStatic; + + /** + * @deprecated this method has been renamed to {@link Model.withoutScope} to emphasise the fact that + * this method does not mutate the model, and is not the same as {@link Model.withInitialScope}. + */ + static unscoped(this: ModelStatic): ModelStatic; + + /** + * Returns a model without scope. The default scope is also omitted. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + * + * If you want to access the Model Class in its state before any scope was applied, use {@link Model.withInitialScope}. + */ + static withoutScope(this: ModelStatic): ModelStatic; + + /** + * Returns the base model, with its initial scope. + */ + static withInitialScope(this: ModelStatic): ModelStatic; + + /** + * Returns the initial model, the one returned by {@link Model.init} or {@link Sequelize#define}, + * before any scope or schema was applied. + */ + static getInitialModel(this: ModelStatic): ModelStatic; + + /** + * Add a new scope to the model + * + * This is especially useful for adding scopes with includes, when the model you want to + * include is not available at the time this model is defined. + * + * By default, this will throw an error if a scope with that name already exists. + * Use {@link AddScopeOptions.override} in the options object to silence this error. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + */ + static addScope( + this: ModelStatic, + name: string, + scope: FindOptions> | ((...args: readonly any[]) => FindOptions>), + options?: AddScopeOptions, + ): void; + + /** + * Search for multiple instances. + * See {@link https://sequelize.org/docs/v7/core-concepts/model-querying-basics/} for more information about querying. + * + * __Example of a simple search:__ + * ```js + * Model.findAll({ + * where: { + * attr1: 42, + * attr2: 'cake' + * } + * }) + * ``` + * + * See also: + * - {@link Model.findOne} + * - {@link Sequelize#query} + * + * @returns A promise that will resolve with the array containing the results of the SELECT query. + */ + static findAll>( + this: ModelStatic, + options: Omit>, 'raw'> & { raw: true }, + ): Promise; + static findAll( + this: ModelStatic, + options?: FindOptions>, + ): Promise; + + /** + * Search for a single instance. + * + * Returns the first instance corresponding matching the query. + * If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set. + */ + static findOne>( + this: ModelStatic, + options: FindOptions> & { raw: true; rejectOnEmpty?: false }, + ): Promise; + static findOne>( + this: ModelStatic, + options: NonNullFindOptions> & { raw: true }, + ): Promise; + static findOne( + this: ModelStatic, + options: NonNullFindOptions>, + ): Promise; + static findOne( + this: ModelStatic, + options?: FindOptions>, + ): Promise; + + /** + * Run an aggregation method on the specified field. + * + * Returns the aggregate result cast to {@link AggregateOptions.dataType}, + * unless `options.plain` is false, in which case the complete data result is returned. + * + * @param attribute The attribute to aggregate over. Can be a field name or `'*'` + * @param aggregateFunction The function to use for aggregation, e.g. sum, max etc. + */ + static aggregate( + this: ModelStatic, + attribute: keyof Attributes | '*', + aggregateFunction: string, + options?: AggregateOptions>, + ): Promise; + + /** + * Count number of records if group by is used + * + * @returns Returns count for each group and the projected attributes. + */ + static count( + this: ModelStatic, + options: CountWithOptions>, + ): Promise; + + /** + * 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. + * + * @returns Returns count for each group and the projected attributes. + */ + static count( + this: ModelStatic, + options?: Omit>, 'group'>, + ): Promise; + + /** + * Finds 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 pagination. + * + * ```js + * 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. + * + * When you add includes, only those which are required (either because they have a where clause, or + * because required` is explicitly set to true on the include) 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 + * + * This function also support grouping, when `group` is provided, the count will be an array of objects + * containing the count for each group and the projected attributes. + * ```js + * User.findAndCountAll({ + * group: 'type' + * }); + * ``` + */ + static findAndCountAll( + this: ModelStatic, + options?: Omit>, 'group' | 'countGroupedRows'>, + ): Promise<{ rows: M[]; count: number }>; + static findAndCountAll( + this: ModelStatic, + options: SetRequired>, 'group'>, + ): Promise<{ rows: M[]; count: GroupedCountResultItem[] }>; + + /** + * Finds the maximum value of field + */ + static max( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions>, + ): Promise; + + /** + * Finds the minimum value of field + */ + static min( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions>, + ): Promise; + + /** + * Retrieves the sum of field + */ + static sum( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions>, + ): Promise; + + /** + * Builds a new model instance. + * Unlike {@link Model.create}, the instance is not persisted, you need to call {@link Model#save} yourself. + * + * @param record An object of key value pairs. + * @returns The created instance. + */ + static build( + this: ModelStatic, + record?: CreationAttributes, + options?: BuildOptions, + ): M; + + /** + * Builds multiple new model instances. + * Unlike {@link Model.create}, the instances are not persisted, you need to call {@link Model#save} yourself. + * + * @param records An array of objects with key value pairs. + */ + static bulkBuild( + this: ModelStatic, + records: ReadonlyArray>, + options?: BuildOptions, + ): M[]; + + /** + * Builds a new model instance and persists it. + * Equivalent to calling {@link Model.build} then {@link Model.save}. + * + * @param record Hash of data values to create new record with + */ + static create< + M extends Model, + O extends CreateOptions> = CreateOptions>, + >( + this: ModelStatic, + record?: CreationAttributes, + options?: O, + ): Promise; + + /** + * Find an entity that matches the query, or build (but don't save) the entity if none is found. + * The successful result of the promise will be the tuple [instance, initialized]. + * + * See also {@link Model.findOrCreate} for a version that immediately saves the new entity. + */ + static findOrBuild( + this: ModelStatic, + options: FindOrBuildOptions, CreationAttributes>, + ): Promise<[entity: M, built: boolean]>; + + /** + * Find an entity that matches the query, or {@link Model.create} the entity if none is found. + * The successful result of the promise will be the tuple [instance, initialized]. + * + * If no transaction is passed in the `options` object, a new transaction will be created internally, to + * prevent the race condition where a matching row is created by another connection after the find but + * before the insert call. + * However, it is not always possible to handle this case in SQLite, specifically if one transaction inserts + * and another tries to select before the first one has committed. + * In this case, an instance of {@link TimeoutError} will be thrown instead. + * + * If a transaction is passed, a savepoint will be created instead, + * and any unique constraint violation will be handled internally. + */ + static findOrCreate( + this: ModelStatic, + options: FindOrCreateOptions, CreationAttributes>, + ): Promise<[entity: M, created: boolean]>; + + /** + * A more performant {@link Model.findOrCreate} that will not start its own transaction or savepoint (at least not in postgres) + * + * It will execute a find call, attempt to create if empty, then attempt to find again if a unique constraint fails. + * + * The successful result of the promise will be the tuple [instance, initialized]. + */ + static findCreateFind( + this: ModelStatic, + options: FindOrCreateOptions, CreationAttributes>, + ): Promise<[entity: M, created: boolean]>; + + /** + * Inserts or updates a single entity. An update will be executed if a row which matches the supplied values on + * either the primary key or a unique key is found. Note that the unique index must be defined in your + * sequelize model and not just in the table. Otherwise, you may experience a unique constraint violation, + * because sequelize fails to identify the row that should be updated. + * + * **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 + * + * **Note:** 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. + * + * @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( + this: ModelStatic, + values: CreationAttributes, + options?: UpsertOptions>, + ): Promise<[entity: M, created: boolean | null]>; + + /** + * Creates and inserts multiple instances in bulk. + * + * The promise resolves with an array of instances. + * + * Please note that, depending on your dialect, the resulting instances may not accurately + * represent the state of their rows in the database. + * This is because MySQL 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 the correct data for the newly created instance, you will need to query for them again. + * + * If validation fails, the promise is rejected with {@link AggregateError} + * + * @param records List of objects (key/value pairs) to create instances from + */ + static bulkCreate( + this: ModelStatic, + records: ReadonlyArray>, + options?: BulkCreateOptions>, + ): Promise; + + /** + * Truncates the table associated with the model. + * + * __Danger__: This will completely empty your table! + */ + static truncate(this: ModelStatic, options?: TruncateOptions): Promise; + + /** + * Deletes multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. + * + * @returns The number of destroyed rows + */ + static destroy( + this: ModelStatic, + options?: DestroyOptions>, + ): Promise; + + /** + * Restores multiple paranoid instances. + * Only usable if {@link ModelOptions.paranoid} is true. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/paranoid/} to learn more about soft deletion / paranoid models. + */ + static restore( + this: ModelStatic, + options?: RestoreOptions>, + ): Promise; + + /** + * Updates multiple instances that match the where options. + * + * The promise resolves with an array of one or two elements: + * - The first element is always the number of affected rows, + * - the second element is the list of affected entities (only supported in postgres and mssql with {@link UpdateOptions.returning} true.) + */ + static update( + this: ModelStatic, + values: UpdateValues, + options: Omit>, 'returning'> & { + returning: Exclude>['returning'], undefined | false>; + }, + ): Promise<[affectedCount: number, affectedRows: M[]]>; + static update( + this: ModelStatic, + values: UpdateValues, + options: UpdateOptions>, + ): Promise<[affectedCount: number]>; + + /** + * Runs a 'describe' query on the table. + * + * @returns a promise that resolves with a mapping of attributes and their types. + */ + static describe(schema?: string, options?: Omit): Promise; + + /** + * Increments the value of one or more attributes. + * + * The increment is done using a `SET column = column + X WHERE foo = 'bar'` query. + * + * @example increment number by 1 + * ```javascript + * Model.increment('number', { where: { foo: 'bar' }); + * ``` + * + * @example increment number and count by 2 + * ```javascript + * Model.increment(['number', 'count'], { by: 2, where: { foo: 'bar' } }); + * ``` + * + * @example increment answer by 42, and decrement tries by 1 + * ```javascript + * // `by` cannot be used, as each attribute specifies its own value + * Model.increment({ answer: 42, tries: -1}, { where: { foo: 'bar' } }); + * ``` + * + * @param 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 an object is provided, each key is incremented by the corresponding value, `by` is ignored. + * + * @returns an array of affected rows or with affected count if `options.returning` is true, whenever supported by dialect + */ + static increment( + this: ModelStatic, + fields: AllowReadonlyArray>, + options: IncrementDecrementOptionsWithBy>, + ): Promise<[affectedRows: M[], affectedCount?: number]>; + static increment( + this: ModelStatic, + fields: { [key in keyof Attributes]?: number }, + options: IncrementDecrementOptions>, + ): Promise<[affectedRows: M[], affectedCount?: number]>; + + /** + * Decrements the value of one or more attributes. + * + * Works like {@link Model.increment} + * + * @param 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 an object is provided, each key is incremented by the corresponding value, `by` is ignored. + * + * @returns an array of affected rows or with affected count if `options.returning` is true, whenever supported by dialect + * + * @since 4.36.0 + */ + static decrement( + this: ModelStatic, + fields: AllowReadonlyArray>, + options: IncrementDecrementOptionsWithBy>, + ): Promise<[affectedRows: M[], affectedCount?: number]>; + static decrement( + this: ModelStatic, + fields: { [key in keyof Attributes]?: number }, + options: IncrementDecrementOptions>, + ): Promise<[affectedRows: M[], affectedCount?: number]>; + + /** + * Creates a 1:1 association between this model (the source) and the provided target. + * The foreign key is added on the target model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * User.hasOne(Profile) + * ``` + * + * @param target The model that will be associated with hasOne relationship + * @param options hasOne association options + * @returns The newly defined association (also available in {@link Model.associations}). + */ + static hasOne< + S extends Model, + T extends Model, + SKey extends AttributeNames, + TKey extends AttributeNames, + >( + this: ModelStatic, + target: ModelStatic, + options?: HasOneOptions, + ): HasOneAssociation; + + /** + * Creates an association between this (the source) and the provided target. + * The foreign key is added on the source Model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * Profile.belongsTo(User) + * ``` + * + * @param target The model that will be associated with a belongsTo relationship + * @param options Options for the association + * @returns The newly defined association (also available in {@link Model.associations}). + */ + static belongsTo< + S extends Model, + T extends Model, + SKey extends AttributeNames, + TKey extends AttributeNames, + >( + this: ModelStatic, + target: ModelStatic, + options?: BelongsToOptions, + ): BelongsToAssociation; + + /** + * Defines a 1:n association between two models. + * The foreign key is added on the target model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * Profile.hasMany(User) + * ``` + * + * @param target The model that will be associated with a hasMany relationship + * @param options Options for the association + * @returns The newly defined association (also available in {@link Model.associations}). + */ + static hasMany< + S extends Model, + T extends Model, + SKey extends AttributeNames, + TKey extends AttributeNames, + >( + this: ModelStatic, + target: ModelStatic, + options?: HasManyOptions, + ): HasManyAssociation; + + /** + * Create an N:M association with a join table. Defining `through` is required. + * The foreign key is added on the through model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * // Automagically generated join model + * User.belongsToMany(Project, { through: 'UserProjects' }) + * + * // Join model with additional attributes + * const UserProjects = sequelize.define('UserProjects', { + * started: DataTypes.BOOLEAN + * }) + * User.belongsToMany(Project, { through: UserProjects }) + * ``` + * + * @param target Target model + * @param options belongsToMany association options + * @returns The newly defined association (also available in {@link Model.associations}). + */ + static belongsToMany< + S extends Model, + T extends Model, + ThroughModel extends Model, + SKey extends AttributeNames, + TKey extends AttributeNames, + >( + this: ModelStatic, + target: ModelStatic, + options: BelongsToManyOptions, + ): BelongsToManyAssociation; + + /** + * @private + */ + static _injectDependentVirtualAttributes(attributes: string[]): string[]; + + /** + * Returns true if this instance has not yet been persisted to the database + */ + isNewRecord: boolean; + + /** + * Builds a new model instance. + * + * @param values an object of key value pairs + * @param options instance construction options + */ + constructor(values?: MakeNullishOptional, options?: BuildOptions); + + /** + * Returns an object representing the query for this instance, use with `options.where` + * + * @param checkVersion include version attribute in where hash + */ + where(checkVersion?: boolean): WhereOptions; + + /** + * Returns the underlying data value + * + * Unlike {@link Model#get}, this method returns the value as it was retrieved, bypassing + * getters, cloning, virtual attributes. + * + * @param key The name of the attribute to return. + */ + getDataValue(key: K): TModelAttributes[K]; + + /** + * Updates the underlying data value. + * + * Unlike {@link Model#set}, this method skips any special behavior and directly replaces the raw value. + * + * @param key The name of the attribute to update. + * @param value The new value for that attribute. + */ + setDataValue(key: K, value: TModelAttributes[K]): void; + + /** + * If no key is given, returns all values of the instance, also invoking virtual getters. If an object has child objects or if options.clone===true, the object will be a copy. Otherwise, it will reference instance.dataValues. + * + * 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. + * + */ + get(options?: ModelGetOptions): TModelAttributes; + get(key: K, options?: ModelGetOptions): this[K]; + get(key: string, options?: ModelGetOptions): unknown; + + /** + * Set is used to update values on the instance (the sequelize representation of the instance that is, + * remember that nothing will be persisted before you actually call `save`). In its most basic form `set` + * will update a value stored in the underlying `dataValues` object. However, if a custom setter function + * is defined for the key, that function will be called instead. To bypass the setter, you can pass `raw: + * true` in the options object. + * + * If set is called with an object, it will loop over the object, and call set recursively for each key, + * value pair. If you set raw to true, the underlying dataValues will either be set directly to the object + * passed, or used to extend dataValues, if dataValues already contain values. + * + * When set is called, the previous value of the field is stored and sets a changed flag(see `changed`). + * + * Set can also be used to build instances for associations, if you have values for those. + * When using set with associations you need to make sure the property key matches the alias of the + * association while also making sure that the proper include options have been set (from .build() or + * .findOne()) + * + * If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the + * entire object as changed. + */ + // TODO: 'key' accepts nested paths for JSON values (json.property) + set( + key: K, + value: TModelAttributes[K], + options?: SetOptions, + ): this; + set(keys: Partial, options?: SetOptions): this; + + /** + * Alias for {@link Model.set}. + */ + setAttributes( + key: K, + value: TModelAttributes[K], + options?: SetOptions, + ): this; + 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 + * `dataValues` is different from the value in `_previousDataValues`. + * + * If changed is called without an argument, it will return an array of keys that have changed. + * + * If changed is called with two arguments, it will set the property to `dirty`. + * + * If changed is called without an argument and no keys have changed, it will return `false`. + */ + // TODO: split this method into: + // - hasChanges(): boolean; + // - getChanges(): string[]; + // - isDirty(key: string): boolean; + // - setDirty(key: string, dirty: boolean = true): void; + changed(key: K): boolean; + changed(key: K, dirty: boolean): void; + changed(): false | string[]; + + /** + * Returns the previous value for key from `_previousDataValues`. + */ + previous(): Partial; + previous(key: K): TModelAttributes[K] | undefined; + + /** + * 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 {@link ValidationError}, + * which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. + * If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. + * In other words, if some other model instance (child) was eager loaded with this instance (parent), + * and you change something in the child, calling `save()` will simply ignore the change that happened on the child. + */ + save(options?: SaveOptions): Promise; + + /** + * Refreshes the current instance in-place, i.e. update the object with current data from the DB and return + * the same object. This is different from doing a `find(Instance.id)`, because that would create and + * return a new instance. With this method, all references to the Instance are updated with the new data + * and no new objects are created. + */ + reload(options?: Omit, 'where'>): Promise; + + /** + * Runs all validators defined for this model, including non-null validators, DataTypes validators, custom attribute validators and model-level validators. + * + * If validation fails, this method will throw a {@link ValidationError}. + */ + validate(options?: ValidationOptions): Promise; + + /** + * This is the same as calling {@link Model#set} followed by calling {@link Model#save}, + * but it only saves attributes values passed to it, making it safer. + */ + update( + attributeName: K, + value: TModelAttributes[K] | Col | Fn | Literal, + options?: InstanceUpdateOptions, + ): Promise; + update( + attributes: { + [key in keyof TModelAttributes]?: TModelAttributes[key] | Fn | Col | Literal; + }, + options?: InstanceUpdateOptions, + ): Promise; + + /** + * Destroys 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. + */ + destroy(options?: InstanceDestroyOptions): Promise; + + /** + * Restores the row corresponding to this instance. + * Only available for paranoid models. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/paranoid/} to learn more about soft deletion / paranoid models. + */ + restore(options?: InstanceRestoreOptions): Promise; + + /** + * Increment the value of one or more columns. This is done in the database, which means it does not use + * the values currently stored on the Instance. The increment is done using a + * ```sql + * SET column = column + X + * ``` + * query. To get the correct value after an increment into the Instance you should do a reload. + * + * ```js + * instance.increment('number') // increment number by 1 + * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2 + * instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own + * // value + * ``` + * + * @param 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. + */ + increment( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy, + ): Promise; + + /** + * Decrement the value of one or more columns. This is done in the database, which means it does not use + * the values currently stored on the Instance. The decrement is done using a + * ```sql + * SET column = column - X + * ``` + * query. To get the correct value after an decrement into the Instance you should do a reload. + * + * ```js + * instance.decrement('number') // decrement number by 1 + * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2 + * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own + * // value + * ``` + * + * @param 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 + */ + decrement( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy, + ): Promise; + + /** + * Check whether all values of this and `other` Instance are the same + */ + equals(other: this): boolean; + + /** + * Check if this is equal to one of `others` by calling equals + */ + 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. + */ + toJSON(): T; + toJSON(): object; + + /** + * Returns true if this instance is "soft deleted". + * Throws an error if {@link ModelOptions.paranoid} is not enabled. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/paranoid/} to learn more about soft deletion / paranoid models. + */ + isSoftDeleted(): boolean; +} + +export type ModelDefined = ModelStatic>; + +// remove the existing constructor that tries to return `Model<{},{}>` which would be incompatible with models that have typing defined & replace with proper constructor. +export type ModelStatic = OmitConstructors & { new (): M }; + +/** + * Type will be true is T is branded with Brand, false otherwise + */ +// How this works: +// - `A extends B` will be true if A has *at least* all the properties of B +// - If we do `A extends Omit` - the result will only be true if A did not have Checked to begin with +// - So if we want to check if T is branded, we remove the brand, and check if they list of keys is still the same. +// we exclude Null & Undefined so "field: Brand | null" is still detected as branded +// this is important because "Brand" are transformed into "Brand | null" to not break null & undefined +type IsBranded = keyof NonNullable extends keyof Omit< + NonNullable, + Brand +> + ? false + : true; + +type BrandedKeysOf = { + [P in keyof T]-?: IsBranded extends true ? P : never; +}[keyof T]; + +/** + * Dummy Symbol used as branding by {@link NonAttribute}. + * + * Do not export, Do not use. + * + * @private + */ +declare const NonAttributeBrand: unique symbol; + +/** + * This is a Branded Type. + * You can use it to tag fields from your class that are NOT attributes. + * They will be ignored by {@link InferAttributes} and {@link InferCreationAttributes} + */ +export type NonAttribute = + // we don't brand null & undefined as they can't have properties. + // This means `NonAttribute` will not work, but who makes an attribute that only accepts null? + // Note that `NonAttribute` does work! + T extends null | undefined ? T : T & { [NonAttributeBrand]?: true }; + +/** + * Dummy Symbol used as branding by {@link ForeignKey}. + * + * Do not export, Do not use. + * + * @private + */ +declare const ForeignKeyBrand: unique symbol; + +/** + * This is a Branded Type. + * You can use it to tag fields from your class that are foreign keys. + * They will become optional in {@link Model.init} (as foreign keys are added by association methods, like {@link Model.hasMany}. + */ +export type ForeignKey = + // we don't brand null & undefined as they can't have properties. + // This means `ForeignKey` will not work, but who makes an attribute that only accepts null? + // Note that `ForeignKey` does work! + T extends null | undefined ? T : T & { [ForeignKeyBrand]?: true }; + +/** + * Option bag for {@link InferAttributes}. + * + * - omit: properties to not treat as Attributes. + */ +type InferAttributesOptions = { omit?: Excluded }; + +/** + * Utility type to extract Attributes of a given Model class. + * + * It returns all instance properties defined in the Model, except: + * - those inherited from Model (intermediate inheritance works), + * - the ones whose type is a function, + * - the ones manually excluded using the second parameter. + * - the ones branded using {@link NonAttribute} + * + * It cannot detect whether something is a getter or not, you should use the `Excluded` + * parameter to exclude getter & setters from the attribute list. + * + * @example + * ```javascript + * // listed attributes will be 'id' & 'firstName'. + * class User extends Model> { + * id: number; + * firstName: string; + * } + * ``` + * + * @example + * ```javascript + * // listed attributes will be 'id' & 'firstName'. + * // we're excluding the `name` getter & `projects` attribute using the `omit` option. + * class User extends Model> { + * id: number; + * firstName: string; + * + * // this is a getter, not an attribute. It should not be listed in attributes. + * get name(): string { return this.firstName; } + * // this is an association, it should not be listed in attributes + * projects?: Project[]; + * } + * ``` + * + * @example + * ```javascript + * // listed attributes will be 'id' & 'firstName'. + * // we're excluding the `name` getter & `test` attribute using the `NonAttribute` branded type. + * class User extends Model> { + * id: number; + * firstName: string; + * + * // this is a getter, not an attribute. It should not be listed in attributes. + * get name(): NonAttribute { return this.firstName; } + * // this is an association, it should not be listed in attributes + * projects?: NonAttribute; + * } + * ``` + */ +export type InferAttributes< + M extends Model, + Options extends InferAttributesOptions = { omit: never }, +> = { + [Key in keyof M as InternalInferAttributeKeysFromFields]: M[Key]; +}; + +/** + * Dummy Symbol used as branding by {@link CreationOptional}. + * + * Do not export, Do not use. + * + * @private + */ +declare const CreationAttributeBrand: unique symbol; + +/** + * This is a Branded Type. + * You can use it to tag attributes that can be omitted during Model Creation. + * Use it on attributes that have a default value or are marked as autoIncrement. + * + * For use with {@link InferCreationAttributes}. + * + * @example + * ```typescript + * class User extends Model, InferCreationAttributes> { + * @Attribute(DataTypes.INTEGER) + * @PrimaryKey + * @AutoIncrement + * declare internalId: CreationOptional; + * + * @Attribute(DataTypes.STRING) + * @Default('John Doe') + * declare name: CreationOptional; + * } + * ``` + */ +export type CreationOptional = + // we don't brand null & undefined as they can't have properties. + // This means `CreationOptional` will not work, but who makes an attribute that only accepts null? + // Note that `CreationOptional` does work! + T extends null | undefined ? T : T & { [CreationAttributeBrand]?: true }; + +/** + * Utility type to extract Creation Attributes of a given Model class. + * + * Works like {@link InferAttributes}, but fields that are tagged using + * {@link CreationOptional} will be optional. + * + * @example + * ```javascript + * class User extends Model, InferCreationAttributes> { + * // this attribute is optional in Model#create + * declare id: CreationOptional; + * + * // this attribute is mandatory in Model#create + * declare name: string; + * } + * ``` + */ +export type InferCreationAttributes< + M extends Model, + Options extends InferAttributesOptions = { omit: never }, +> = { + [Key in keyof M as InternalInferAttributeKeysFromFields]: IsBranded< + M[Key], + typeof CreationAttributeBrand + > extends true + ? M[Key] | undefined + : M[Key]; +}; + +/** + * @private + * + * Internal type used by {@link InferCreationAttributes} and {@link InferAttributes} to exclude + * attributes that are: + * - functions + * - branded using {@link NonAttribute} + * - inherited from {@link Model} + * - Excluded manually using the omit option of {@link InferAttributesOptions} + */ +type InternalInferAttributeKeysFromFields< + M extends Model, + Key extends keyof M, + Options extends InferAttributesOptions, +> = + // fields inherited from Model are all excluded + Key extends keyof Model + ? never + : // functions are always excluded + M[Key] extends AnyFunction + ? never + : // fields branded with NonAttribute are excluded + IsBranded extends true + ? never + : // check 'omit' option is provided & exclude those listed in it + Options['omit'] extends string + ? Key extends Options['omit'] + ? never + : Key + : Key; + +// in v7, we should be able to drop InferCreationAttributes and InferAttributes, +// resolving this confusion. +/** + * Returns the creation attributes of a given Model. + * + * This returns the Creation Attributes of a Model, it does not build them. + * If you need to build them, use {@link InferCreationAttributes}. + * + * @example + * ```typescript + * function buildModel(modelClass: ModelStatic, attributes: CreationAttributes) {} + * ``` + */ +// TODO: accept Fn & Literal! +export type CreationAttributes = MakeNullishOptional; + +/** + * Returns the creation attributes of a given Model. + * + * This returns the Attributes of a Model that have already been defined, it does not build them. + * If you need to build them, use {@link InferAttributes}. + * + * @example + * ```typescript + * function getValue(modelClass: ModelStatic, attribute: keyof Attributes) {} + * ``` + */ +export type Attributes = M['_attributes']; + +export type AttributeNames = Extract; + +export type ExtractMethods = { + [K in keyof M as M[K] extends Function ? K : never]: M[K]; +}; diff --git a/packages/core/src/model.js b/packages/core/src/model.js new file mode 100644 index 000000000000..a3608ec40062 --- /dev/null +++ b/packages/core/src/model.js @@ -0,0 +1,4688 @@ +'use strict'; + +import { EMPTY_OBJECT, every, find } from '@sequelize/utils'; +import Dottie from 'dottie'; +import assignWith from 'lodash/assignWith'; +import cloneDeepLodash from 'lodash/cloneDeep'; +import defaultsLodash from 'lodash/defaults'; +import difference from 'lodash/difference'; +import each from 'lodash/each'; +import flattenDepth from 'lodash/flattenDepth'; +import forEach from 'lodash/forEach'; +import forIn from 'lodash/forIn'; +import get from 'lodash/get'; +import intersection from 'lodash/intersection'; +import isEmpty from 'lodash/isEmpty'; +import isEqual from 'lodash/isEqual'; +import isObject from 'lodash/isObject'; +import isPlainObject from 'lodash/isPlainObject'; +import mapValues from 'lodash/mapValues'; +import omit from 'lodash/omit'; +import omitBy from 'lodash/omitBy'; +import pick from 'lodash/pick'; +import pickBy from 'lodash/pickBy'; +import remove from 'lodash/remove'; +import union from 'lodash/union'; +import unionBy from 'lodash/unionBy'; +import uniq from 'lodash/uniq'; +import without from 'lodash/without'; +import assert from 'node:assert'; +import NodeUtil from 'node:util'; +import { AbstractDataType } from './abstract-dialect/data-types'; +import { + Association, + BelongsToAssociation, + BelongsToManyAssociation, + HasManyAssociation, + HasOneAssociation, +} from './associations'; +import { AssociationSecret } from './associations/helpers'; +import * as DataTypes from './data-types'; +import { QueryTypes } from './enums.js'; +import * as SequelizeErrors from './errors'; +import { BaseSqlExpression } from './expression-builders/base-sql-expression.js'; +import { InstanceValidator } from './instance-validator'; +import { + _validateIncludedElements, + combineIncludes, + getModelPkWhere, + setTransactionFromCls, + throwInvalidInclude, +} from './model-internals'; +import { ModelTypeScript } from './model-typescript'; +import { Op } from './operators'; +import { intersects } from './utils/array'; +import { + noDoubleNestedGroup, + noModelDropSchema, + noNewModel, + schemaRenamedToWithSchema, + scopeRenamedToWithScope, +} from './utils/deprecations'; +import { toDefaultValue } from './utils/dialect'; +import { mapFinderOptions, mapOptionFieldNames, mapValueFieldNames } from './utils/format'; +import { logger } from './utils/logger'; +import { isModelStatic, isSameInitialModel } from './utils/model-utils'; +import { + cloneDeep, + defaults, + flattenObjectDeep, + getObjectFromMap, + mergeDefaults, +} from './utils/object'; +import { isWhereEmpty } from './utils/query-builder-utils'; +import { removeTrailingSemicolon } from './utils/string.js'; +import { getComplexKeys } from './utils/where.js'; + +// This list will quickly become dated, but failing to maintain this list just means +// we won't throw a warning when we should. At least most common cases will forever be covered +// so we stop throwing erroneous warnings when we shouldn't. +const validQueryKeywords = new Set([ + 'where', + 'attributes', + 'paranoid', + 'include', + 'order', + 'limit', + 'offset', + 'transaction', + 'lock', + 'raw', + 'logging', + 'benchmark', + 'having', + 'searchPath', + 'rejectOnEmpty', + 'plain', + 'scope', + 'group', + 'through', + 'defaults', + 'distinct', + 'primary', + 'exception', + 'type', + 'hooks', + 'force', + 'name', +]); + +// List of attributes that should not be implicitly passed into subqueries/includes. +const nonCascadingOptions = [ + 'include', + 'attributes', + 'originalAttributes', + 'order', + 'where', + 'limit', + 'offset', + 'plain', + 'group', + 'having', +]; + +/** + * Used to ensure Model.build is used instead of new Model(). + * Do not expose. + */ +const CONSTRUCTOR_SECRET = Symbol('model-constructor-secret'); + +/** + * A Model represents a table in the database. Instances of this class represent a database row. + * + * Model instances operate with the concept of a `dataValues` property, which stores the actual values represented by the + * instance. By default, the values from dataValues can also be accessed directly from the Instance, that is: + * ```js + * instance.field + * // is the same as + * instance.get('field') + * // is the same as + * instance.getDataValue('field') + * ``` + * However, if getters and/or setters are defined for `field` they will be invoked, instead of returning the value from + * `dataValues`. Accessing properties directly or using `get` is preferred for regular use, `getDataValue` should only be + * used for custom getters. + * + * @see {Sequelize#define} for more information about getters and setters + */ +export class Model extends ModelTypeScript { + /** + * Builds a new model instance. + * + * Cannot be used directly. Use {@link Model.build} instead. + * + * @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` + * @param {symbol} secret Secret used to ensure Model.build is used instead of new Model(). Don't forget to pass it up if + * you define a custom constructor. + */ + constructor(values = {}, options = {}, secret) { + super(); + + if (secret !== CONSTRUCTOR_SECRET) { + noNewModel(); + // TODO [>=8]: throw instead of deprecation notice + // throw new Error(`Use ${this.constructor.name}.build() instead of new ${this.constructor.name}()`); + } + + this.constructor.assertIsInitialized(); + + options = { + isNewRecord: true, + _schema: this.modelDefinition.table.schema, + _schemaDelimiter: this.modelDefinition.table.delimiter, + ...options, + model: this.constructor, + }; + + if (options.attributes) { + options.attributes = options.attributes.map(attribute => { + return Array.isArray(attribute) ? attribute[1] : attribute; + }); + } + + if (!options.includeValidated) { + this.constructor._conformIncludes(options, this.constructor); + if (options.include) { + this.constructor._expandIncludeAll(options); + _validateIncludedElements(options); + } + } + + this.dataValues = {}; + this._previousDataValues = {}; + this.uniqno = 1; + this._changed = new Set(); + this._options = omit(options, ['comesFromDatabase']); + + /** + * Returns true if this instance has not yet been persisted to the database + * + * @property isNewRecord + * @returns {boolean} + */ + this.isNewRecord = options.isNewRecord; + + this._initValues(values, options); + } + + _initValues(values, options) { + values = { ...values }; + + if (options.isNewRecord) { + const modelDefinition = this.modelDefinition; + + const defaults = + modelDefinition.defaultValues.size > 0 + ? mapValues(getObjectFromMap(modelDefinition.defaultValues), getDefaultValue => { + const value = getDefaultValue(); + + return value && value instanceof BaseSqlExpression ? value : cloneDeepLodash(value); + }) + : Object.create(null); + + // set id to null if not passed as value, a newly created dao has no id + // removing this breaks bulkCreate + // do after default values since it might have UUID as a default value + if (modelDefinition.primaryKeysAttributeNames.size > 0) { + for (const primaryKeyAttribute of modelDefinition.primaryKeysAttributeNames) { + if (!Object.hasOwn(defaults, primaryKeyAttribute)) { + defaults[primaryKeyAttribute] = null; + } + } + } + + const { + createdAt: createdAtAttrName, + deletedAt: deletedAtAttrName, + updatedAt: updatedAtAttrName, + } = modelDefinition.timestampAttributeNames; + + if (createdAtAttrName && defaults[createdAtAttrName]) { + this.dataValues[createdAtAttrName] = toDefaultValue(defaults[createdAtAttrName]); + delete defaults[createdAtAttrName]; + } + + if (updatedAtAttrName && defaults[updatedAtAttrName]) { + this.dataValues[updatedAtAttrName] = toDefaultValue(defaults[updatedAtAttrName]); + delete defaults[updatedAtAttrName]; + } + + if (deletedAtAttrName && defaults[deletedAtAttrName]) { + this.dataValues[deletedAtAttrName] = toDefaultValue(defaults[deletedAtAttrName]); + delete defaults[deletedAtAttrName]; + } + + for (const key in defaults) { + if (values[key] === undefined) { + this.set(key, toDefaultValue(defaults[key]), { raw: true }); + delete values[key]; + } + } + } + + this.set(values, options); + } + + // validateIncludedElements should have been called before this method + static _paranoidClause(model, options = {}) { + // Apply on each include + // This should be handled before handling where conditions because of logic with returns + // otherwise this code will never run on includes of a already conditionable where + if (options.include) { + for (const include of options.include) { + this._paranoidClause(include.model, include); + } + } + + // apply paranoid when groupedLimit is used + if (get(options, 'groupedLimit.on.through.model.options.paranoid')) { + const throughModel = get(options, 'groupedLimit.on.through.model'); + if (throughModel) { + options.groupedLimit.through = this._paranoidClause( + throughModel, + options.groupedLimit.through, + ); + } + } + + if (!model.options.timestamps || !model.options.paranoid || options.paranoid === false) { + // This model is not paranoid, nothing to do here; + return options; + } + + const modelDefinition = model.modelDefinition; + + const deletedAtCol = modelDefinition.timestampAttributeNames.deletedAt; + const deletedAtAttribute = modelDefinition.attributes.get(deletedAtCol); + const deletedAtObject = Object.create(null); + + let deletedAtDefaultValue = deletedAtAttribute.defaultValue ?? null; + + deletedAtDefaultValue ||= { + [Op.eq]: null, + }; + + deletedAtObject[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; + + if (isWhereEmpty(options.where)) { + options.where = deletedAtObject; + } else { + options.where = { [Op.and]: [deletedAtObject, options.where] }; + } + + return options; + } + + /** + * Returns the attributes of the model. + * + * @returns {object|any} + */ + static getAttributes() { + return getObjectFromMap(this.modelDefinition.attributes); + } + + get validators() { + throw new Error( + 'Model#validators has been removed. Use the validators option on Model.modelDefinition.attributes instead.', + ); + } + + static get _schema() { + throw new Error('Model._schema has been removed. Use Model.modelDefinition instead.'); + } + + static get _schemaDelimiter() { + throw new Error('Model._schemaDelimiter has been removed. Use Model.modelDefinition instead.'); + } + + static _getAssociationDebugList() { + return `The following associations are defined on "${this.name}": ${Object.keys( + this.associations, + ) + .map(associationName => `"${associationName}"`) + .join(', ')}`; + } + + static getAssociation(associationName) { + if (!Object.hasOwn(this.associations, associationName)) { + throw new Error(`Association with alias "${associationName}" does not exist on ${this.name}. +${this._getAssociationDebugList()}`); + } + + return this.associations[associationName]; + } + + static _getAssociationsByModel(model) { + const matchingAssociations = []; + + for (const associationName of Object.keys(this.associations)) { + const association = this.associations[associationName]; + if (!isSameInitialModel(association.target, model)) { + continue; + } + + matchingAssociations.push(association); + } + + return matchingAssociations; + } + + static _normalizeIncludes(options, associationOwner) { + this._conformIncludes(options, associationOwner); + this._expandIncludeAll(options, associationOwner); + } + + static _conformIncludes(options, associationOwner) { + if (!options.include) { + return; + } + + // if include is not an array, wrap in an array + if (!Array.isArray(options.include)) { + options.include = [options.include]; + } else if (options.include.length === 0) { + delete options.include; + + return; + } + + // convert all included elements to { model: Model } form + options.include = options.include.map(include => + this._conformInclude(include, associationOwner), + ); + } + + static _conformInclude(include, associationOwner) { + if (!include) { + throwInvalidInclude(include); + } + + if (!associationOwner || !isModelStatic(associationOwner)) { + throw new TypeError( + `Sequelize sanity check: associationOwner must be a model subclass. Got ${NodeUtil.inspect(associationOwner)} (${typeof associationOwner})`, + ); + } + + if (include._pseudo) { + return include; + } + + if (include.all) { + this._conformIncludes(include, associationOwner); + + return include; + } + + // normalize to IncludeOptions + if (!isPlainObject(include)) { + if (isModelStatic(include)) { + include = { + model: include, + }; + } else { + include = { + association: include, + }; + } + } else { + // copy object so we can mutate it without side effects + include = { ...include }; + } + + if (include.as && !include.association) { + include.association = include.as; + } + + if (!include.association) { + include.association = associationOwner.getAssociationWithModel(include.model, include.as); + } else if (typeof include.association === 'string') { + include.association = associationOwner.getAssociation(include.association); + } else { + if (!(include.association instanceof Association)) { + throwInvalidInclude(include); + } + + if (!isSameInitialModel(include.association.source, associationOwner)) { + throw new Error(`Invalid Include received: the specified association "${include.association.as}" is not defined on model "${associationOwner.name}". It is owned by model "${include.association.source.name}". +${associationOwner._getAssociationDebugList()}`); + } + } + + if (!include.model) { + include.model = include.association.target; + } + + if (!isSameInitialModel(include.model, include.association.target)) { + throw new TypeError( + `Invalid Include received: the specified "model" option ("${include.model.name}") does not match the target ("${include.association.target.name}") of the "${include.association.as}" association.`, + ); + } + + if (!include.as) { + include.as = include.association.as; + } + + this._conformIncludes(include, include.model); + + return include; + } + + static _expandIncludeAllElement(includes, include) { + // check 'all' attribute provided is valid + let { all, nested, ...includeOptions } = include; + + if (Object.keys(includeOptions).length > 0) { + throw new Error( + '"include: { all: true }" does not allow extra options (except for "nested") because they are unsafe. Select includes one by one if you want to specify more options.', + ); + } + + if (all !== true) { + if (!Array.isArray(all)) { + all = [all]; + } + + const validTypes = { + BelongsTo: true, + HasOne: true, + HasMany: true, + One: ['BelongsTo', 'HasOne'], + Has: ['HasOne', 'HasMany'], + Many: ['HasMany'], + }; + + for (let i = 0; i < all.length; i++) { + const type = all[i]; + if (type === 'All') { + all = true; + break; + } + + const types = validTypes[type]; + if (!types) { + throw new SequelizeErrors.EagerLoadingError( + `include all '${type}' is not valid - must be BelongsTo, HasOne, HasMany, One, Has, Many or All`, + ); + } + + if (types !== true) { + // replace type placeholder e.g. 'One' with its constituent types e.g. 'HasOne', 'BelongsTo' + all.splice(i, 1); + i--; + for (const type_ of types) { + if (!all.includes(type_)) { + all.unshift(type_); + i++; + } + } + } + } + } + + const visitedModels = []; + const addAllIncludes = (parent, includes) => { + forEach(parent.associations, association => { + if (all !== true && !all.includes(association.associationType)) { + return; + } + + // 'fromSourceToThroughOne' is a bit hacky and should not be included when { all: true } is specified + // because its parent 'belongsToMany' will be replaced by it in query generator. + if ( + association.parentAssociation instanceof BelongsToManyAssociation && + association === association.parentAssociation.fromSourceToThroughOne + ) { + return; + } + + // skip if the association is already included + if (includes.some(existingInclude => existingInclude.association === association)) { + return; + } + + const newInclude = { association }; + + const model = association.target; + + // skip if recursing over a model whose associations have already been included + // to prevent infinite loops with associations such as this: + // user -> projects -> user + if (nested && visitedModels.includes(model)) { + return; + } + + // include this model + const normalizedNewInclude = this._conformInclude(newInclude, parent); + includes.push(normalizedNewInclude); + + // run recursively if nested + if (nested) { + visitedModels.push(parent); + + const subIncludes = []; + addAllIncludes(model, subIncludes); + visitedModels.pop(); + + if (subIncludes.length > 0) { + normalizedNewInclude.include = subIncludes; + } + } + }); + }; + + addAllIncludes(this, includes); + } + + static _validateIncludedElement(include, tableNames, options) { + tableNames[include.model.table] = true; + + if (include.attributes && !options.raw) { + include.model._expandAttributes(include); + + include.originalAttributes = include.model._injectDependentVirtualAttributes( + include.attributes, + ); + + include = mapFinderOptions(include, include.model); + + if (include.attributes.length > 0) { + each(include.model.primaryKeys, (attr, key) => { + // Include the primary key if it's not already included - take into account that the pk might be aliased (due to a .field prop) + if ( + !include.attributes.some(includeAttr => { + if (attr.field !== key) { + return ( + Array.isArray(includeAttr) && + includeAttr[0] === attr.field && + includeAttr[1] === key + ); + } + + return includeAttr === key; + }) + ) { + include.attributes.unshift(key); + } + }); + } + } else { + include = mapFinderOptions(include, include.model); + } + + // pseudo include just needed the attribute logic, return + if (include._pseudo) { + if (!include.attributes) { + include.attributes = Object.keys(include.model.tableAttributes); + } + + return mapFinderOptions(include, include.model); + } + + // check if the current Model is actually associated with the passed Model - or it's a pseudo include + const association = + include.association || this.getAssociationWithModel(include.model, include.as); + + include.association = association; + include.as ||= association.as; + + // If through, we create a pseudo child include, to ease our parsing later on + if (association instanceof BelongsToManyAssociation) { + if (!include.include) { + include.include = []; + } + + const through = include.association.through; + + include.through = defaultsLodash(include.through || {}, { + model: through.model, + // Through Models are a special case: we always want to load them as the name of the model, not the name of the association + as: through.model.name, + association: { + isSingleAssociation: true, + }, + _pseudo: true, + parent: include, + }); + + if (through.scope) { + include.through.where = include.through.where + ? { [Op.and]: [include.through.where, through.scope] } + : through.scope; + } + + include.include.push(include.through); + tableNames[through.tableName] = true; + } + + // include.model may be the main model, while the association target may be scoped - thus we need to look at association.target/source + let model; + if (include.model.scoped === true) { + // If the passed model is already scoped, keep that + model = include.model; + } else { + // Otherwise use the model that was originally passed to the association + model = + include.association.target.name === include.model.name + ? include.association.target + : include.association.source; + } + + model._injectScope(include); + + // This check should happen after injecting the scope, since the scope may contain a .attributes + if (!include.attributes) { + include.attributes = Object.keys(include.model.tableAttributes); + } + + include = mapFinderOptions(include, include.model); + + if (include.required === undefined) { + include.required = Boolean(include.where); + } + + if (include.association.scope) { + include.where = include.where + ? { [Op.and]: [include.where, include.association.scope] } + : include.association.scope; + } + + if (include.limit && include.separate === undefined) { + include.separate = true; + } + + if (include.separate === true) { + if (!(include.association instanceof HasManyAssociation)) { + throw new TypeError('Only HasMany associations support include.separate'); + } + + include.duplicating = false; + + if ( + options.attributes && + options.attributes.length > 0 && + !flattenDepth(options.attributes, 2).includes(association.sourceKey) + ) { + options.attributes.push(association.sourceKey); + } + + if ( + include.attributes && + include.attributes.length > 0 && + !flattenDepth(include.attributes, 2).includes(association.foreignKey) + ) { + include.attributes.push(association.foreignKey); + } + } + + // Validate child includes + if (Object.hasOwn(include, 'include')) { + _validateIncludedElements(include, tableNames); + } + + return include; + } + + static _expandIncludeAll(options, associationOwner) { + const includes = options.include; + if (!includes) { + return; + } + + for (let index = 0; index < includes.length; index++) { + const include = includes[index]; + + if (include.all) { + includes.splice(index, 1); + index--; + + associationOwner._expandIncludeAllElement(includes, include); + } + } + + for (const include of includes) { + this._expandIncludeAll(include, include.model); + } + } + + static _baseMerge(...args) { + assignWith(...args); + + return args[0]; + } + + static _mergeFunction(objValue, srcValue, key) { + if (key === 'include') { + return combineIncludes(objValue, srcValue); + } + + if (Array.isArray(objValue) && Array.isArray(srcValue)) { + return union(objValue, srcValue); + } + + if (['where', 'having'].includes(key)) { + return combineWheresWithAnd(objValue, srcValue); + } else if (key === 'attributes' && isPlainObject(objValue) && isPlainObject(srcValue)) { + return assignWith(objValue, srcValue, (objValue, srcValue) => { + if (Array.isArray(objValue) && Array.isArray(srcValue)) { + return union(objValue, srcValue); + } + }); + } + + // If we have a possible object/array to clone, we try it. + // Otherwise, we return the original value when it's not undefined, + // or the resulting object in that case. + if (srcValue) { + return cloneDeep(srcValue, true); + } + + return srcValue === undefined ? objValue : srcValue; + } + + static _assignOptions(...args) { + return this._baseMerge(...args, this._mergeFunction); + } + + static _defaultsOptions(target, opts) { + return this._baseMerge(target, opts, (srcValue, objValue, key) => { + return this._mergeFunction(objValue, srcValue, key); + }); + } + + /** + * Remove attribute from model definition. + * Only use if you know what you're doing. + * + * @param {string} attribute name of attribute to remove + */ + static removeAttribute(attribute) { + delete this.modelDefinition.rawAttributes[attribute]; + this.modelDefinition.refreshAttributes(); + } + + /** + * Merges new attributes with the existing ones. + * Only use if you know what you're doing. + * + * Warning: Attributes are not replaced, they are merged. + * + * @param {object} newAttributes + */ + static mergeAttributesDefault(newAttributes) { + const rawAttributes = this.modelDefinition.rawAttributes; + + mergeDefaults(rawAttributes, newAttributes); + + this.modelDefinition.refreshAttributes(); + + return rawAttributes; + } + + /** + * Sync this Model to the DB, that is create the table. + * See {@link Sequelize#sync} for options + * + * @param {object} [options] sync options + * + * @returns {Promise} + */ + static async sync(options) { + options = { ...this.options, ...options }; + options.hooks = options.hooks === undefined ? true : Boolean(options.hooks); + + const modelDefinition = this.modelDefinition; + const physicalAttributes = getObjectFromMap(modelDefinition.physicalAttributes); + const columnDefs = getObjectFromMap(modelDefinition.columns); + + if (options.hooks) { + await this.hooks.runAsync('beforeSync', options); + } + + const tableName = { ...this.table }; + if (options.schema && options.schema !== tableName.schema) { + // Some users sync the same set of tables in different schemas for various reasons + // They then set `searchPath` when running a query to use different schemas. + // See https://github.com/sequelize/sequelize/pull/15274#discussion_r1020770364 + // We only allow this if the tables are in the default schema, because we need to ensure that + // all tables are in the same schema to prevent collisions and `searchPath` only works if we don't specify the schema + // (which we don't for the default schema) + if (tableName.schema !== this.sequelize.dialect.getDefaultSchema()) { + throw new Error( + `The "schema" option in sync can only be used on models that do not already specify a schema, or that are using the default schema. Model ${this.name} already specifies schema ${tableName.schema}`, + ); + } + + tableName.schema = options.schema; + } + + delete options.schema; + + let tableExists; + if (options.force) { + await this.drop({ + ...options, + cascade: this.sequelize.dialect.supports.dropTable.cascade || undefined, + }); + tableExists = false; + } else { + tableExists = await this.queryInterface.tableExists(tableName, options); + } + + if (!tableExists) { + await this.queryInterface.createTable(tableName, physicalAttributes, options, this); + } else { + // enums are always updated, even if alter is not set. createTable calls it too. + await this.queryInterface.ensureEnums(tableName, physicalAttributes, options, this); + } + + if (tableExists && options.alter) { + const tableInfos = await Promise.all([ + this.queryInterface.describeTable(tableName, options), + this.queryInterface.showConstraints(tableName, { + ...options, + constraintType: 'FOREIGN KEY', + }), + ]); + + const columns = tableInfos[0]; + // Use for alter foreign keys + const foreignKeyReferences = tableInfos[1]; + const removedConstraints = {}; + + for (const columnName in physicalAttributes) { + if (!Object.hasOwn(physicalAttributes, columnName)) { + continue; + } + + if (!columns[columnName] && !columns[physicalAttributes[columnName].field]) { + await this.queryInterface.addColumn( + tableName, + physicalAttributes[columnName].field || columnName, + physicalAttributes[columnName], + options, + ); + } + } + + if ( + options.alter === true || + (typeof options.alter === 'object' && options.alter.drop !== false) + ) { + for (const columnName in columns) { + if (!Object.hasOwn(columns, columnName)) { + continue; + } + + const currentAttribute = columnDefs[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 schema = tableName.schema; + const database = this.sequelize.options.replication.write.database; + const foreignReferenceSchema = currentAttribute.references.table.schema; + const foreignReferenceTableName = + typeof references.table === 'object' ? references.table.tableName : references.table; + // Find existed foreign keys + for (const foreignKeyReference of foreignKeyReferences) { + const constraintName = foreignKeyReference.constraintName; + if ( + (constraintName && + (foreignKeyReference.tableCatalog + ? foreignKeyReference.tableCatalog === database + : true) && + (schema ? foreignKeyReference.tableSchema === schema : true) && + foreignKeyReference.referencedTableName === foreignReferenceTableName && + foreignKeyReference.referencedColumnNames.includes(references.key) && + (foreignReferenceSchema + ? foreignKeyReference.referencedTableSchema === foreignReferenceSchema + : true) && + !removedConstraints[constraintName]) || + this.sequelize.dialect.name === 'ibmi' + ) { + // Remove constraint on foreign keys. + await this.queryInterface.removeConstraint(tableName, constraintName, options); + removedConstraints[constraintName] = true; + } + } + } + + await this.queryInterface.changeColumn(tableName, columnName, currentAttribute, options); + } + } + } + + const existingIndexes = await this.queryInterface.showIndex(tableName, options); + const missingIndexes = this.getIndexes() + .filter(item1 => !existingIndexes.some(item2 => item1.name === item2.name)) + .sort((index1, index2) => { + if (this.sequelize.dialect.name === '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; + }); + + for (const index of missingIndexes) { + // TODO: 'options' is ignored by addIndex, making Add Index queries impossible to log. + await this.queryInterface.addIndex(tableName, index, options); + } + + if (options.hooks) { + await this.hooks.runAsync('afterSync', options); + } + + return this; + } + + /** + * Drop the table represented by this Model + * + * @param {object} [options] drop options + * @returns {Promise} + */ + static async drop(options) { + return await this.queryInterface.dropTable(this, options); + } + + /** + * @param {object | string} schema + * @deprecated use {@link Sequelize#dropSchema} or {@link QueryInterface#dropSchema} + */ + // TODO [>=2023-01-01]: remove me in Sequelize >= 8 + static async dropSchema(schema) { + noModelDropSchema(); + + return await this.queryInterface.dropSchema(schema); + } + + /** + * Returns a copy of this model with the corresponding table located in the specified schema. + * + * For postgres, this will actually place the schema in front of the table name (`"schema"."tableName"`), + * while the schema will be prepended to the table name for mysql and sqlite (`'schema.tablename'`). + * + * This method is intended for use cases where the same model is needed in multiple schemas. + * In such a use case it is important to call {@link Model.sync} (or use migrations!) for each model created by this method + * to ensure the models are created in the correct schema. + * + * If a single default schema per model is needed, set the {@link ModelOptions.schema} option instead. + * + * @param {string|object} schema The name of the schema + * + * @returns {Model} + */ + static withSchema(schema) { + if (arguments.length > 1) { + throw new TypeError( + 'Unlike Model.schema, Model.withSchema only accepts 1 argument which may be either a string or an option bag.', + ); + } + + const schemaOptions = typeof schema === 'string' || schema === null ? { schema } : schema; + + schemaOptions.schema ||= + this.sequelize.options.schema || this.sequelize.dialect.getDefaultSchema(); + + return this.getInitialModel()._withScopeAndSchema(schemaOptions, this._scope, this._scopeNames); + } + + // TODO [>=2023-01-01]: remove in Sequelize 8 + static schema(schema, options) { + schemaRenamedToWithSchema(); + + return this.withSchema({ + schema, + schemaDelimiter: typeof options === 'string' ? options : options?.schemaDelimiter, + }); + } + + /** + * Returns the initial model, the one returned by {@link Model.init} or {@link Sequelize#define}, + * before any scope or schema was applied. + */ + static getInitialModel() { + // '_initialModel' is set on model variants (withScope, withSchema, etc) + return this._initialModel ?? this; + } + + /** + * Add a new scope to the model + * + * This is especially useful for adding scopes with includes, when the model you want to + * include is not available at the time this model is defined. + * + * By default, this will throw an error if a scope with that name already exists. + * Use {@link AddScopeOptions.override} in the options object to silence this error. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + * + * @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 + */ + static addScope(name, scope, options) { + if (this !== this.getInitialModel()) { + throw new Error( + `Model.addScope can only be called on the initial model. Use "${this.name}.getInitialModel()" to access the initial model.`, + ); + } + + 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`, + ); + } + + if (name === 'defaultScope') { + this.options.defaultScope = this._scope = scope; + } else { + this.options.scopes[name] = scope; + } + } + + // TODO [>=2023-01-01]: remove in Sequelize 8 + static scope(...options) { + scopeRenamedToWithScope(); + + return this.withScope(...options); + } + + /** + * Creates a copy of this model, with one or more scopes applied. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + * + * @param {?Array|object|string} [scopes] 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. + * + * @example To invoke scope functions you can do + * ```ts + * Model.withScope({ method: ['complexFunction', 'dan@sequelize.com', 42]}).findAll() + * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 + * ``` + * + * @returns {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will + * clear the previous scope. + */ + static withScope(...scopes) { + scopes = scopes.flat().filter(Boolean); + + const initialModel = this.getInitialModel(); + + const mergedScope = {}; + const scopeNames = []; + + for (const option of scopes) { + let scope = null; + let scopeName = null; + + if (isPlainObject(option)) { + if (option.method) { + if ( + Array.isArray(option.method) && + Boolean(initialModel.options.scopes[option.method[0]]) + ) { + scopeName = option.method[0]; + scope = initialModel.options.scopes[scopeName].apply( + initialModel, + option.method.slice(1), + ); + } else if (initialModel.options.scopes[option.method]) { + scopeName = option.method; + scope = initialModel.options.scopes[scopeName].apply(initialModel); + } + } else { + scope = option; + } + } else if (option === 'defaultScope' && isPlainObject(initialModel.options.defaultScope)) { + scope = initialModel.options.defaultScope; + } else { + scopeName = option; + scope = initialModel.options.scopes[scopeName]; + if (typeof scope === 'function') { + scope = scope(); + } + } + + if (!scope) { + throw new SequelizeErrors.SequelizeScopeError( + `"${this.name}.withScope()" has been called with an invalid scope: "${scopeName}" does not exist.`, + ); + } + + this._conformIncludes(scope, this); + // clone scope so it doesn't get modified + this._assignOptions(mergedScope, cloneDeep(scope) ?? {}); + scopeNames.push(scopeName ? scopeName : 'defaultScope'); + } + + const modelDefinition = this.modelDefinition; + + return initialModel._withScopeAndSchema( + { + schema: modelDefinition.table.schema || '', + schemaDelimiter: modelDefinition.table.delimiter || '', + }, + mergedScope, + scopeNames, + ); + } + + // TODO [>=2023-01-01]: remove in Sequelize 8 + /** + * Returns a model without scope. The default scope is also omitted. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + */ + static unscoped() { + scopeRenamedToWithScope(); + + return this.withoutScope(); + } + + /** + * Returns a model without scope. The default scope is also omitted. + * + * See {@link https://sequelize.org/docs/v7/other-topics/scopes/} to learn more about scopes. + */ + static withoutScope() { + return this.withScope(null); + } + + /** + * Returns the base model, with its initial scope. + */ + static withInitialScope() { + const initialModel = this.getInitialModel(); + + const modelDefinition = this.modelDefinition; + const initialModelDefinition = initialModel.modelDefinition; + + if ( + modelDefinition.table.schema !== initialModelDefinition.table.schema || + modelDefinition.table.delimiter !== initialModelDefinition.table.delimiter + ) { + return initialModel.withSchema({ + schema: modelDefinition.table.schema, + schemaDelimiter: modelDefinition.table.delimiter, + }); + } + + return initialModel; + } + + static _withScopeAndSchema(schemaOptions, mergedScope, scopeNames) { + if (!this._modelVariantRefs) { + // technically this weakref is unnecessary because we're referencing ourselves but it simplifies the code + // eslint-disable-next-line no-undef -- eslint doesn't know about WeakRef, this will be resolved once we migrate to TS. + this._modelVariantRefs = new Set([new WeakRef(this)]); + } + + const newTable = this.queryGenerator.extractTableDetails({ + tableName: this.modelDefinition.table.tableName, + schema: schemaOptions.schema, + delimiter: schemaOptions.delimiter, + }); + + for (const modelVariantRef of this._modelVariantRefs) { + const modelVariant = modelVariantRef.deref(); + + if (!modelVariant) { + this._modelVariantRefs.delete(modelVariantRef); + continue; + } + + const variantTable = modelVariant.table; + + if (variantTable.schema !== newTable.schema) { + continue; + } + + if (variantTable.delimiter !== newTable.delimiter) { + continue; + } + + // the item order of these arrays is important! scope('a', 'b') is not equal to scope('b', 'a') + if (!isEqual(modelVariant._scopeNames, scopeNames)) { + continue; + } + + if (!isEqual(modelVariant._scope, mergedScope)) { + continue; + } + + return modelVariant; + } + + const clone = this._createModelVariant({ + schema: schemaOptions.schema, + schemaDelimiter: schemaOptions.schemaDelimiter, + }); + // eslint-disable-next-line no-undef -- eslint doesn't know about WeakRef, this will be resolved once we migrate to TS. + this._modelVariantRefs.add(new WeakRef(clone)); + + clone._scope = mergedScope; + clone._scopeNames = scopeNames; + + if (scopeNames.length !== 1 || scopeNames[0] !== 'defaultScope') { + clone.scoped = true; + } + + return clone; + } + + static _createModelVariant(optionOverrides) { + const model = class extends this {}; + model._initialModel = this; + Object.defineProperty(model, 'name', { value: this.name }); + + model.init(this.modelDefinition.rawAttributes, { + ...this.options, + ...optionOverrides, + }); + + // This is done for legacy reasons, where in a previous design both models shared the same association objects. + // TODO: re-create the associations on the new model instead of sharing them. + Object.assign(model.modelDefinition.associations, this.modelDefinition.associations); + + return model; + } + + /** + * Search for multiple instances. + * See {@link https://sequelize.org/docs/v7/core-concepts/model-querying-basics/} for more information about querying. + * + * __Example of a simple search:__ + * ```js + * Model.findAll({ + * where: { + * attr1: 42, + * attr2: 'cake' + * } + * }) + * ``` + * + * See also: + * - {@link Model.findOne} + * - {@link Sequelize#query} + * + * @param {object} options + * @returns {Promise} A promise that will resolve with the array containing the results of the SELECT query. + */ + 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', + ); + } + + if ( + options !== undefined && + options.attributes && + !Array.isArray(options.attributes) && + !isPlainObject(options.attributes) + ) { + throw new SequelizeErrors.QueryError( + 'The attributes option must be an array of column names or an object', + ); + } + + const modelDefinition = this.modelDefinition; + + this._warnOnInvalidOptions(options, Object.keys(modelDefinition.attributes)); + + const tableNames = {}; + + tableNames[this.table] = true; + options = cloneDeep(options) ?? {}; + + setTransactionFromCls(options, this.sequelize); + + defaultsLodash(options, { hooks: true, model: this }); + + // set rejectOnEmpty option, defaults to model options + options.rejectOnEmpty = Object.hasOwn(options, 'rejectOnEmpty') + ? options.rejectOnEmpty + : this.options.rejectOnEmpty; + + this._conformIncludes(options, this); + this._injectScope(options); + + if (options.hooks) { + await this.hooks.runAsync('beforeFind', options); + this._conformIncludes(options, this); + } + + this._expandAttributes(options); + this._expandIncludeAll(options, options.model); + + if (options.hooks) { + await this.hooks.runAsync('beforeFindAfterExpandIncludeAll', options); + } + + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + + if (options.include) { + options.hasJoin = true; + + _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 (!options.attributes) { + options.attributes = Array.from(modelDefinition.attributes.keys()); + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + } + + mapFinderOptions(options, this); + + options = this._paranoidClause(this, options); + + if (options.hooks) { + await this.hooks.runAsync('beforeFindAfterOptions', options); + } + + const selectOptions = { ...options, tableNames: Object.keys(tableNames) }; + const results = await this.queryInterface.select(this, this.table, selectOptions); + if (options.hooks) { + await this.hooks.runAsync('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(); + } + + return await Model._findSeparate(results, options); + } + + static _warnOnInvalidOptions(options, validColumnNames) { + if (!isPlainObject(options)) { + return; + } + + const unrecognizedOptions = Object.keys(options).filter(k => !validQueryKeywords.has(k)); + const unexpectedModelAttributes = intersection(unrecognizedOptions, validColumnNames); + if (!options.where && unexpectedModelAttributes.length > 0) { + logger.warn( + `Model attributes (${unexpectedModelAttributes.join(', ')}) passed into finder method options of model ${this.name}, but the options.where object is empty. Did you forget to use options.where?`, + ); + } + } + + static _injectDependentVirtualAttributes(attributes) { + const modelDefinition = this.modelDefinition; + + if (modelDefinition.virtualAttributeNames.size === 0) { + return attributes; + } + + if (!attributes || !Array.isArray(attributes)) { + return attributes; + } + + for (const attribute of attributes) { + if ( + modelDefinition.virtualAttributeNames.has(attribute) && + modelDefinition.attributes.get(attribute).type.attributeDependencies + ) { + attributes = attributes.concat( + modelDefinition.attributes.get(attribute).type.attributeDependencies, + ); + } + } + + attributes = uniq(attributes); + + return attributes; + } + + static async _findSeparate(results, options) { + if (!options.include || options.raw || !results) { + return results; + } + + const original = results; + if (options.plain) { + results = [results]; + } + + if (!Array.isArray(results) || results.length === 0) { + return original; + } + + await Promise.all( + options.include.map(async include => { + if (!include.separate) { + return await Model._findSeparate( + results.reduce((memo, result) => { + let associations = result.get(include.association.as); + + // Might be an empty belongsTo relation + if (!associations) { + return memo; + } + + // Force array so we can concat no matter if it's 1:1 or :M + if (!Array.isArray(associations)) { + associations = [associations]; + } + + for (let i = 0, len = associations.length; i !== len; ++i) { + memo.push(associations[i]); + } + + return memo; + }, []), + { + ...omit( + options, + 'include', + 'attributes', + 'order', + 'where', + 'limit', + 'offset', + 'plain', + 'scope', + ), + include: include.include || [], + }, + ); + } + + const map = await include.association.get(results, { + ...omit(options, nonCascadingOptions), + ...omit(include, ['parent', 'association', 'as', 'originalAttributes']), + }); + + for (const result of results) { + result.set(include.association.as, map.get(result.get(include.association.sourceKey)), { + raw: true, + }); + } + }), + ); + + return original; + } + + /** + * Search for a single instance. + * + * Returns the first instance corresponding matching the query. + * If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set. + * + * @param {object} [options] A hash of options to describe the scope of the search + * @returns {Promise} + */ + 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', + ); + } + + options = cloneDeep(options) ?? {}; + // findOne only ever needs one result + // conditional temporarily fixes 14618 + // https://github.com/sequelize/sequelize/issues/14618 + if (options.limit === undefined) { + options.limit = 1; + } + + // Bypass a possible overloaded findAll. + return await Model.findAll.call( + this, + defaultsLodash(options, { + model: this, + plain: true, + }), + ); + } + + /** + * Run an aggregation method on the specified field. + * + * Returns the aggregate result cast to {@link AggregateOptions.dataType}, + * unless `options.plain` is false, in which case the complete data result is returned. + * + * @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 + * + * @returns {Promise} + */ + static async aggregate(attribute, aggregateFunction, options) { + options = cloneDeep(options) ?? {}; + options.model = this; + + // We need to preserve attributes here as the `injectScope` call would inject non aggregate columns. + const prevAttributes = options.attributes; + this._injectScope(options); + options.attributes = prevAttributes; + this._conformIncludes(options, this); + + if (options.include) { + this._expandIncludeAll(options); + _validateIncludedElements(options); + } + + const attrOptions = this.getAttributes()[attribute]; + const field = (attrOptions && attrOptions.field) || attribute; + let aggregateColumn = this.sequelize.col(field); + + if (options.distinct) { + aggregateColumn = this.sequelize.fn('DISTINCT', aggregateColumn); + } + + let { group } = options; + if (Array.isArray(group) && Array.isArray(group[0])) { + noDoubleNestedGroup(); + group = group.flat(); + } + + options.attributes = unionBy( + options.attributes, + group, + [[this.sequelize.fn(aggregateFunction, aggregateColumn), aggregateFunction]], + a => (Array.isArray(a) ? a[1] : a), + ); + + if (!options.dataType) { + if (attrOptions) { + options.dataType = attrOptions.type; + } else { + // Use FLOAT as fallback + options.dataType = new DataTypes.FLOAT(); + } + } else { + options.dataType = this.sequelize.normalizeDataType(options.dataType); + } + + mapOptionFieldNames(options, this); + options = this._paranoidClause(this, options); + + const value = await this.queryInterface.rawSelect(this.table, options, aggregateFunction, this); + + return value; + } + + /** + * 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. + * + * @param {object} [options] options + * @returns {Promise} + */ + static async count(options) { + options = cloneDeep(options) ?? {}; + options = defaultsLodash(options, { hooks: true }); + + setTransactionFromCls(options, this.sequelize); + + options.raw = true; + if (options.hooks) { + await this.hooks.runAsync('beforeCount', options); + } + + 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; + + // counting grouped rows is not possible with `this.aggregate` + // use a subquery to get the count + if (options.group && options.countGroupedRows) { + const query = removeTrailingSemicolon(this.queryGenerator.selectQuery(this.table, options)); + + const queryCountAll = `Select COUNT(*) AS count FROM (${query}) AS Z`; + + const result = await this.sequelize.query(queryCountAll); + + const count = Number(result[0][0].count || result[0][0].COUNT); + + return count; + } + + let col = options.col || '*'; + if (options.include) { + col = `${this.name}.${options.col || this.primaryKeyField}`; + } + + if (options.distinct && col === '*') { + col = this.primaryKeyField; + } + + const result = await this.aggregate(col, 'count', options); + + // When grouping is used, some dialects such as PG are returning the count as string + // --> Manually convert it to number + if (Array.isArray(result)) { + return result.map(item => ({ + ...item, + count: Number(item.count), + })); + } + + return result; + } + + /** + * Finds 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 pagination. + * + * ```js + * 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. + * + * When you add includes, only those which are required (either because they have a where clause, or + * because required` is explicitly set to true on the include) 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 + * + * This function also support grouping, when `group` is provided, the count will be an array of objects + * containing the count for each group and the projected attributes. + * ```js + * User.findAndCountAll({ + * group: 'type' + * }); + * ``` + * + * @param {object} [options] See findAll options + * @returns {Promise<{count: number | number[], rows: Model[]}>} + */ + 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', + ); + } + + const countOptions = cloneDeep(options) ?? {}; + + if (countOptions.attributes && !options.countGroupedRows) { + countOptions.attributes = undefined; + } + + const [count, rows] = await Promise.all([this.count(countOptions), this.findAll(options)]); + + return { + count, + rows: count === 0 ? [] : rows, + }; + } + + /** + * Finds the maximum value of field + * + * @param {string} field attribute / field name + * @param {object} [options] See aggregate + * @returns {Promise<*>} + */ + static async max(field, options) { + return await this.aggregate(field, 'max', options); + } + + /** + * Finds the minimum value of field + * + * @param {string} field attribute / field name + * @param {object} [options] See aggregate + * @returns {Promise<*>} + */ + static async min(field, options) { + return await this.aggregate(field, 'min', options); + } + + /** + * Retrieves the sum of field + * + * @param {string} field attribute / field name + * @param {object} [options] See aggregate + * @returns {Promise} + */ + static async sum(field, options) { + return await this.aggregate(field, 'sum', options); + } + + /** + * Builds a new model instance. + * Unlike {@link Model.create}, the instance is not persisted, you need to call {@link Model#save} yourself. + * + * @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 + * + * @returns {Model|Array} + */ + static build(values, options) { + if (Array.isArray(values)) { + return this.bulkBuild(values, options); + } + + const instance = new this(values, options, CONSTRUCTOR_SECRET); + + // Our Model class adds getters and setters for attributes on the prototype, + // so they can be shadowed by native class properties that are defined on the class that extends Model (See #14300). + // This deletes the instance properties, to un-shadow the getters and setters. + for (const attributeName of this.modelDefinition.attributes.keys()) { + delete instance[attributeName]; + } + + // If there are associations in the instance, we assign them as properties on the instance + // so that they can be accessed directly, instead of having to call `get` and `set`. + // class properties re-assign them to whatever value was set on the class property (or undefined if none) + // so this workaround re-assigns the association after the instance was created. + for (const associationName of Object.keys(this.modelDefinition.associations)) { + instance[associationName] = instance.getDataValue(associationName); + } + + return instance; + } + + /** + * Builds multiple new model instances. + * Unlike {@link Model.create}, the instances are not persisted, you need to call {@link Model#save} yourself. + * + * @param {Array} valueSets An array of objects with key value pairs. + * @param {object} [options] Instance build options + */ + static bulkBuild(valueSets, options) { + options = { isNewRecord: true, ...options }; + + if (!options.includeValidated) { + this._conformIncludes(options, this); + if (options.include) { + this._expandIncludeAll(options); + _validateIncludedElements(options); + } + } + + if (options.attributes) { + options.attributes = options.attributes.map(attribute => { + return Array.isArray(attribute) ? attribute[1] : attribute; + }); + } + + return valueSets.map(values => this.build(values, options)); + } + + /** + * Builds a new model instance and persists it. + * Equivalent to calling {@link Model.build} then {@link Model.save}. + * + * @param {object} values + * @param {object} options + * @returns {Promise} + */ + static async create(values, options) { + options = cloneDeep(options) ?? {}; + + return await this.build(values, { + isNewRecord: true, + attributes: options.fields, + include: options.include, + raw: options.raw, + silent: options.silent, + }).save(options); + } + + /** + * Find an entity that matches the query, or build (but don't save) the entity if none is found. + * The successful result of the promise will be the tuple [instance, initialized]. + * + * @param {object} options find options + * @returns {Promise} + */ + static async findOrBuild(options) { + if (!options || !options.where || arguments.length > 1) { + throw new Error( + 'Missing where attribute in the options parameter passed to findOrBuild. ' + + 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)', + ); + } + + let values; + + let instance = await this.findOne(options); + if (instance === null) { + values = { ...options.defaults }; + if (isPlainObject(options.where)) { + values = defaults(values, options.where); + } + + instance = this.build(values, options); + + return [instance, true]; + } + + return [instance, false]; + } + + /** + * Find an entity that matches the query, or {@link Model.create} the entity if none is found. + * The successful result of the promise will be the tuple [instance, initialized]. + * + * If no transaction is passed in the `options` object, a new transaction will be created internally, to + * prevent the race condition where a matching row is created by another connection after the find but + * before the insert call. + * However, it is not always possible to handle this case in SQLite, specifically if one transaction inserts + * and another tries to select before the first one has committed. + * In this case, an instance of {@link TimeoutError} will be thrown instead. + * + * If a transaction is passed, a savepoint will be created instead, + * and any unique constraint violation will be handled internally. + * + * @param {object} options find and create options + * @returns {Promise} + */ + static async findOrCreate(options) { + if (!options || !options.where || arguments.length > 1) { + throw new Error( + 'Missing where attribute in the options parameter passed to findOrCreate. ' + + 'Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.)', + ); + } + + if (options.connection) { + throw new Error( + 'findOrCreate does not support specifying which connection must be used, because findOrCreate must run in a transaction.', + ); + } + + options = { ...options }; + + const modelDefinition = this.modelDefinition; + + if (options.defaults) { + const defaults = Object.keys(options.defaults); + const unknownDefaults = defaults.filter(name => !modelDefinition.attributes.has(name)); + + if (unknownDefaults.length > 0) { + logger.warn( + `Unknown attributes (${unknownDefaults}) passed to defaults option of findOrCreate`, + ); + } + } + + setTransactionFromCls(options, this.sequelize); + + const internalTransaction = !options.transaction; + let values; + let transaction; + + try { + // TODO: use managed sequelize.transaction() instead + transaction = await this.sequelize.startUnmanagedTransaction(options); + options.transaction = transaction; + + const found = await this.findOne(options); + if (found !== null) { + return [found, false]; + } + + values = { ...options.defaults }; + if (isPlainObject(options.where)) { + values = defaults(values, options.where); + } + + options.exception = true; + options.returning = true; + + 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 [created, true]; + } catch (error) { + if (!(error instanceof SequelizeErrors.UniqueConstraintError)) { + throw error; + } + + const flattenedWhere = flattenObjectDeep(options.where); + const flattenedWhereKeys = Object.keys(flattenedWhere).map(name => name.split('.').at(-1)); + const whereFields = flattenedWhereKeys.map( + name => modelDefinition.attributes.get(name)?.columnName ?? name, + ); + const defaultFields = + options.defaults && + Object.keys(options.defaults) + .filter(name => modelDefinition.attributes.get(name)) + .map(name => modelDefinition.getColumnNameLoose(name)); + + const errFieldKeys = Object.keys(error.fields); + const errFieldsWhereIntersects = intersects(errFieldKeys, whereFields); + if (defaultFields && !errFieldsWhereIntersects && intersects(errFieldKeys, defaultFields)) { + throw error; + } + + if (errFieldsWhereIntersects) { + each(error.fields, (value, key) => { + const name = modelDefinition.columns.get(key).attributeName; + if (value.toString() !== options.where[name].toString()) { + throw new Error( + `${this.name}#findOrCreate: value used for ${name} was not equal for both the find and the create calls, '${options.where[name]}' vs '${value}'`, + ); + } + }); + } + + // Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it! + const otherCreated = await this.findOne( + defaults( + { + transaction: internalTransaction ? null : transaction, + }, + 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 error; + } + + return [otherCreated, false]; + } + } finally { + if (internalTransaction && transaction) { + await transaction.commit(); + } + } + } + + /** + * A more performant {@link Model.findOrCreate} that will not start its own transaction or savepoint (at least not in + * postgres) + * + * It will execute a find call, attempt to create if empty, then attempt to find again if a unique constraint fails. + * + * The successful result of the promise will be the tuple [instance, initialized]. + * + * @param {object} options find options + * @returns {Promise} + */ + static async findCreateFind(options) { + if (!options || !options.where) { + throw new Error('Missing where attribute in the options parameter passed to findCreateFind.'); + } + + let values = { ...options.defaults }; + if (isPlainObject(options.where)) { + values = defaults(values, options.where); + } + + const found = await this.findOne(options); + if (found) { + return [found, false]; + } + + try { + const createOptions = { ...options }; + + // To avoid breaking a postgres transaction, run the create with `ignoreDuplicates`. + if (this.sequelize.dialect.name === 'postgres' && options.transaction) { + createOptions.ignoreDuplicates = true; + } + + const created = await this.create(values, createOptions); + + return [created, true]; + } catch (error) { + if ( + !( + error instanceof SequelizeErrors.UniqueConstraintError || + error instanceof SequelizeErrors.EmptyResultError + ) + ) { + throw error; + } + + const foundAgain = await this.findOne(options); + + return [foundAgain, false]; + } + } + + /** + * Inserts or updates a single entity. An update will be executed if a row which matches the supplied values on + * either the primary key or a unique key is found. Note that the unique index must be defined in your + * sequelize model and not just in the table. Otherwise, you may experience a unique constraint violation, + * because sequelize fails to identify the row that should be updated. + * + * **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 + * + * **Note:** 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. + * + * @param {object} values hash of values to upsert + * @param {object} [options] upsert options + * @returns {Promise>} an array with two elements, the first being the new record and + * the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which + * can't detect this and will always return `null` instead of a boolean). + */ + static async upsert(values, options) { + options = { + hooks: true, + returning: true, + validate: true, + ...cloneDeep(options), + }; + + setTransactionFromCls(options, this.sequelize); + + const modelDefinition = this.modelDefinition; + + const createdAtAttr = modelDefinition.timestampAttributeNames.createdAt; + const updatedAtAttr = modelDefinition.timestampAttributeNames.updatedAt; + const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values; + const instance = this.build(values); + + options.model = this; + options.instance = instance; + + const changed = [...instance._changed]; + if (!options.fields) { + options.fields = changed; + } + + if (options.validate) { + await instance.validate(options); + } + + // Map conflict fields to column names + if (options.conflictFields) { + options.conflictFields = options.conflictFields.map(attrName => { + return modelDefinition.getColumnName(attrName); + }); + } + + // Map field names + const updatedDataValues = pick(instance.dataValues, changed); + const insertValues = mapValueFieldNames( + instance.dataValues, + modelDefinition.attributes.keys(), + this, + ); + const updateValues = mapValueFieldNames(updatedDataValues, options.fields, this); + const now = new Date(); + + // Attach createdAt + if (createdAtAttr && !insertValues[createdAtAttr]) { + const field = modelDefinition.attributes.get(createdAtAttr).columnName || createdAtAttr; + insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; + } + + if (updatedAtAttr && !updateValues[updatedAtAttr]) { + const field = modelDefinition.attributes.get(updatedAtAttr).columnName || updatedAtAttr; + insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now; + } + + // Db2 does not allow NULL values for unique columns. + // Add dummy values if not provided by test case or user. + if (this.sequelize.dialect.name === 'db2') { + // TODO: remove. This is fishy and is going to be a source of bugs (because it replaces null values with arbitrary values that could be actual data). + // If DB2 doesn't support NULL in unique columns, then it should error if the user tries to insert NULL in one. + this.uniqno = this.sequelize.dialect.queryGenerator.addUniqueFields( + insertValues, + this.modelDefinition.rawAttributes, + this.uniqno, + ); + } + + // 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 && + !modelDefinition.attributes.get(this.primaryKeyAttribute).defaultValue + ) { + delete insertValues[this.primaryKeyField]; + delete updateValues[this.primaryKeyField]; + } + + if (options.hooks) { + await this.hooks.runAsync('beforeUpsert', values, options); + } + + const result = await this.queryInterface.upsert( + this.table, + insertValues, + updateValues, + // TODO: this is only used by DB2 & MSSQL, as these dialects require a WHERE clause in their UPSERT implementation. + // but the user should be able to specify a WHERE clause themselves (because we can't perfectly include all UNIQUE constraints in our implementation) + // there is also some incoherence in our implementation: This "where" returns the Primary Key constraint, but all other unique constraints + // are added inside of QueryInterface. Everything should be done inside of QueryInterface instead. + instance.where(false, true) ?? {}, + options, + ); + + const [record] = result; + record.isNewRecord = false; + + if (options.hooks) { + await this.hooks.runAsync('afterUpsert', result, options); + } + + return result; + } + + /** + * Creates and inserts multiple instances in bulk. + * + * The promise resolves with an array of instances. + * + * Please note that, depending on your dialect, the resulting instances may not accurately + * represent the state of their rows in the database. + * This is because MySQL 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 the correct data for the newly created instance, you will need to query for them again. + * + * If validation fails, the promise is rejected with {@link AggregateError} + * + * @param {Array} records List of objects (key/value pairs) to create instances from + * @param {object} [options] Bulk create options + * @returns {Promise>} + */ + static async bulkCreate(records, options = {}) { + if (records.length === 0) { + return []; + } + + const dialect = this.sequelize.dialect.name; + const now = new Date(); + options = cloneDeep(options) ?? {}; + + setTransactionFromCls(options, this.sequelize); + + options.model = this; + + if (!options.includeValidated) { + this._conformIncludes(options, this); + if (options.include) { + this._expandIncludeAll(options); + _validateIncludedElements(options); + } + } + + const instances = records.map(values => + this.build(values, { isNewRecord: true, include: options.include }), + ); + + const recursiveBulkCreate = async (instances, options) => { + options = { + validate: false, + hooks: true, + individualHooks: false, + ignoreDuplicates: false, + ...options, + }; + + if (options.returning === undefined) { + if (options.association) { + options.returning = false; + } else { + options.returning = true; + } + } + + if (options.ignoreDuplicates && ['mssql', 'db2', 'ibmi'].includes(dialect)) { + throw new Error(`${dialect} does not support the ignoreDuplicates option.`); + } + + if ( + options.updateOnDuplicate && + !['mysql', 'mariadb', 'sqlite3', 'postgres', 'ibmi'].includes(dialect) + ) { + throw new Error(`${dialect} does not support the updateOnDuplicate option.`); + } + + const model = options.model; + const modelDefinition = model.modelDefinition; + + options.fields = options.fields || Array.from(modelDefinition.attributes.keys()); + const createdAtAttr = modelDefinition.timestampAttributeNames.createdAt; + const updatedAtAttr = modelDefinition.timestampAttributeNames.updatedAt; + + if (options.updateOnDuplicate !== undefined) { + if (Array.isArray(options.updateOnDuplicate) && options.updateOnDuplicate.length > 0) { + options.updateOnDuplicate = intersection( + without(Object.keys(model.tableAttributes), createdAtAttr), + options.updateOnDuplicate, + ); + } else { + throw new Error('updateOnDuplicate option only supports non-empty array.'); + } + } + + // Run before hook + if (options.hooks) { + await model.hooks.runAsync('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 (error) { + errors.push(new SequelizeErrors.BulkRecordError(error, instance)); + } + }), + ); + + delete options.skip; + if (errors.length > 0) { + 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; + + await instance.save(individualOptions); + }), + ); + } else { + if (options.include && options.include.length > 0) { + await Promise.all( + options.include + .filter(include => include.association instanceof BelongsToAssociation) + .map(async include => { + const associationInstances = []; + const associationInstanceIndexToInstanceMap = []; + + for (const instance of instances) { + const associationInstance = instance.get(include.as); + if (associationInstance) { + associationInstances.push(associationInstance); + associationInstanceIndexToInstanceMap.push(instance); + } + } + + if (associationInstances.length === 0) { + return; + } + + const includeOptions = defaultsLodash(omit(cloneDeep(include), ['association']), { + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + }); + + const createdAssociationInstances = await recursiveBulkCreate( + associationInstances, + includeOptions, + ); + for (const idx in createdAssociationInstances) { + const associationInstance = createdAssociationInstances[idx]; + const instance = associationInstanceIndexToInstanceMap[idx]; + + 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; + + // 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); + } + } + + const out = mapValueFieldNames(values, options.fields, model); + for (const key of modelDefinition.virtualAttributeNames) { + delete out[key]; + } + + return out; + }); + + // Map attributes to fields for serial identification + const fieldMappedAttributes = Object.create(null); + for (const attrName in model.tableAttributes) { + const attribute = modelDefinition.attributes.get(attrName); + fieldMappedAttributes[attribute.columnName] = attribute; + } + + // Map updateOnDuplicate attributes to fields + if (options.updateOnDuplicate) { + options.updateOnDuplicate = options.updateOnDuplicate.map(attrName => { + return modelDefinition.getColumnName(attrName); + }); + + if (options.conflictAttributes) { + options.upsertKeys = options.conflictAttributes.map(attrName => + modelDefinition.getColumnName(attrName), + ); + } else { + const upsertKeys = []; + + for (const i of model.getIndexes()) { + if (i.unique && !i.where) { + // Don't infer partial indexes + upsertKeys.push(...i.fields); + } + } + + 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 => + modelDefinition.getColumnNameLoose(attr), + ); + } + + const results = await model.queryInterface.bulkInsert( + model.table, + records, + options, + fieldMappedAttributes, + ); + if (Array.isArray(results)) { + for (const [i, result] of results.entries()) { + const instance = instances[i]; + + for (const key in result) { + if (!Object.hasOwn(result, key)) { + continue; + } + + if ( + !instance || + (key === model.primaryKeyAttribute && + instance.get(model.primaryKeyAttribute) && + ['mysql', 'mariadb'].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; + } + + const value = result[key]; + const attr = find( + modelDefinition.attributes.values(), + attribute => attribute.attributeName === key || attribute.columnName === key, + ); + const attributeName = attr?.attributeName || key; + instance.dataValues[attributeName] = + value != null && attr?.type instanceof AbstractDataType + ? attr.type.parseDatabaseValue(value) + : value; + instance._previousDataValues[attributeName] = instance.dataValues[attributeName]; + } + } + } + } + + if (options.include && options.include.length > 0) { + await Promise.all( + options.include + .filter( + include => + !( + include.association instanceof BelongsToAssociation || + (include.parent && include.parent.association instanceof BelongsToManyAssociation) + ), + ) + .map(async include => { + const associationInstances = []; + const associationInstanceIndexToInstanceMap = []; + + for (const instance of instances) { + let associated = instance.get(include.as); + if (!Array.isArray(associated)) { + associated = [associated]; + } + + for (const associationInstance of associated) { + if (associationInstance) { + if (!(include.association instanceof BelongsToManyAssociation)) { + associationInstance.set( + include.association.foreignKey, + instance.get( + include.association.sourceKey || instance.constructor.primaryKeyAttribute, + { raw: true }, + ), + { raw: true }, + ); + Object.assign(associationInstance, include.association.scope); + } + + associationInstances.push(associationInstance); + associationInstanceIndexToInstanceMap.push(instance); + } + } + } + + if (associationInstances.length === 0) { + return; + } + + const includeOptions = defaultsLodash(omit(cloneDeep(include), ['association']), { + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + }); + + const createdAssociationInstances = await recursiveBulkCreate( + associationInstances, + includeOptions, + ); + if (include.association instanceof BelongsToManyAssociation) { + const valueSets = []; + + 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 + ...include.association.through.scope, + }; + if (associationInstance[include.association.through.model.name]) { + const throughDefinition = include.association.through.model.modelDefinition; + + for (const attributeName of throughDefinition.attributes.keys()) { + const attribute = throughDefinition.attributes.get(attributeName); + + if ( + attribute._autoGenerated || + attributeName === include.association.foreignKey || + attributeName === include.association.otherKey || + typeof associationInstance[include.association.through.model.name][ + attributeName + ] === 'undefined' + ) { + continue; + } + + values[attributeName] = + associationInstance[include.association.through.model.name][attributeName]; + } + } + + valueSets.push(values); + } + + const throughOptions = defaultsLodash( + omit(cloneDeep(include), ['association', 'attributes']), + { + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + }, + ); + throughOptions.model = include.association.throughModel; + const throughInstances = include.association.throughModel.bulkBuild( + valueSets, + throughOptions, + ); + + await recursiveBulkCreate(throughInstances, throughOptions); + } + }), + ); + } + + // map fields back to attributes + for (const instance of instances) { + const attributeDefs = modelDefinition.attributes; + + for (const attribute of attributeDefs.values()) { + if ( + instance.dataValues[attribute.columnName] !== undefined && + attribute.columnName !== attribute.attributeName + ) { + instance.dataValues[attribute.attributeName] = + instance.dataValues[attribute.columnName]; + // TODO: if a column shares the same name as an attribute, this will cause a bug! + delete instance.dataValues[attribute.columnName]; + } + + instance._previousDataValues[attribute.attributeName] = + instance.dataValues[attribute.attributeName]; + instance.changed(attribute.attributeName, false); + } + + instance.isNewRecord = false; + } + + // Run after hook + if (options.hooks) { + await model.hooks.runAsync('afterBulkCreate', instances, options); + } + + return instances; + }; + + return await recursiveBulkCreate(instances, options); + } + + /** + * Truncates the table associated with the model. + * + * __Danger__: This will completely empty your table! + * + * @param {object} [options] truncate options + * @returns {Promise} + */ + static async truncate(options) { + await this.queryInterface.truncate(this, options); + } + + /** + * Deletes multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. + * + * @param {object} options destroy options + * @returns {Promise} The number of destroyed rows + */ + // TODO: add _UNSTABLE_bulkDestroy, aimed to be a replacement, + // which does the same thing but uses `noHooks` instead of `hooks` and `hardDelete` instead of `force`, + // and does not accept `individualHooks` + static async destroy(options) { + options = cloneDeep(options) ?? {}; + + setTransactionFromCls(options, this.sequelize); + + this._injectScope(options); + + if (options && 'truncate' in options) { + throw new Error( + 'Model#destroy does not support the truncate option. Use Model#truncate instead.', + ); + } + + if (!options?.where) { + throw new Error( + 'As a safeguard, the "destroy" static model method requires explicitly specifying a "where" option. If you actually mean to delete all rows in the table, set the option to a dummy condition such as sql`1 = 1`.', + ); + } + + const modelDefinition = this.modelDefinition; + const attributes = modelDefinition.attributes; + + options = defaultsLodash(options, { + hooks: true, + individualHooks: false, + force: false, + }); + + mapOptionFieldNames(options, this); + options.model = this; + + // Run before hook + if (options.hooks) { + await this.hooks.runAsync('beforeBulkDestroy', options); + } + + let instances; + // Get daos and run beforeDestroy hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ + where: options.where, + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + benchmark: options.benchmark, + }); + + await Promise.all( + instances.map(instance => { + return this.hooks.runAsync('beforeDestroy', instance, options); + }), + ); + } + + let result; + // TODO: rename force -> paranoid: false, as that's how it's called in the instance version + // Run delete query (or update if paranoid) + if (modelDefinition.timestampAttributeNames.deletedAt && !options.force) { + // Set query type appropriately when running soft delete + options.type = QueryTypes.BULKUPDATE; + + const attrValueHash = {}; + const deletedAtAttribute = attributes.get(modelDefinition.timestampAttributeNames.deletedAt); + const deletedAtColumnName = deletedAtAttribute.columnName; + + // FIXME: where must be joined with AND instead of using Object.assign. This won't work with literals! + const where = { + [deletedAtColumnName]: Object.hasOwn(deletedAtAttribute, 'defaultValue') + ? deletedAtAttribute.defaultValue + : null, + }; + + attrValueHash[deletedAtColumnName] = new Date(); + result = await this.queryInterface.bulkUpdate( + this.table, + attrValueHash, + Object.assign(where, options.where), + options, + getObjectFromMap(modelDefinition.attributes), + ); + } else { + result = await this.queryInterface.bulkDelete(this, options); + } + + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => { + return this.hooks.runAsync('afterDestroy', instance, options); + }), + ); + } + + // Run after hook + if (options.hooks) { + await this.hooks.runAsync('afterBulkDestroy', options); + } + + return result; + } + + /** + * Restores multiple paranoid instances. + * Only usable if {@link ModelOptions.paranoid} is true. + * + * @param {object} options restore options + * @returns {Promise} + */ + static async restore(options) { + const modelDefinition = this.modelDefinition; + + if (!modelDefinition.timestampAttributeNames.deletedAt) { + throw new Error('Model is not paranoid'); + } + + options = { + hooks: true, + individualHooks: false, + ...options, + }; + + setTransactionFromCls(options, this.sequelize); + + options.type = QueryTypes.RAW; + options.model = this; + + mapOptionFieldNames(options, this); + + // Run before hook + if (options.hooks) { + await this.hooks.runAsync('beforeBulkRestore', options); + } + + let instances; + // Get daos and run beforeRestore hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ + where: options.where, + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + benchmark: options.benchmark, + paranoid: false, + }); + + await Promise.all( + instances.map(instance => { + return this.hooks.runAsync('beforeRestore', instance, options); + }), + ); + } + + // Run undelete query + const attrValueHash = {}; + const deletedAtAttributeName = modelDefinition.timestampAttributeNames.deletedAt; + const deletedAtAttribute = modelDefinition.attributes.get(deletedAtAttributeName); + const deletedAtDefaultValue = deletedAtAttribute.defaultValue ?? null; + + attrValueHash[deletedAtAttribute.columnName || deletedAtAttributeName] = deletedAtDefaultValue; + options.omitNull = false; + const result = await this.queryInterface.bulkUpdate( + this.table, + attrValueHash, + options.where, + options, + getObjectFromMap(modelDefinition.attributes), + ); + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => { + return this.hooks.runAsync('afterRestore', instance, options); + }), + ); + } + + // Run after hook + if (options.hooks) { + await this.hooks.runAsync('afterBulkRestore', options); + } + + return result; + } + + /** + * Updates multiple instances that match the where options. + * + * The promise resolves with an array of one or two elements: + * - The first element is always the number of affected rows, + * - the second element is the list of affected entities (only supported in postgres and mssql with + * {@link UpdateOptions.returning} true.) + * + * @param {object} values hash of values to update + * @param {object} options update options + * @returns {Promise>} + */ + static async update(values, options) { + options = cloneDeep(options) ?? {}; + + setTransactionFromCls(options, this.sequelize); + + this._injectScope(options); + this._optionsMustContainWhere(options); + + const modelDefinition = this.modelDefinition; + + options = this._paranoidClause( + this, + defaultsLodash(options, { + validate: true, + hooks: true, + individualHooks: false, + returning: false, + force: false, + sideEffects: true, + }), + ); + + options.type = QueryTypes.BULKUPDATE; + + // Clone values so it doesn't get modified for caller scope and ignore undefined values + values = omitBy(values, value => value === undefined); + + const updatedAtAttrName = modelDefinition.timestampAttributeNames.updatedAt; + + // Remove values that are not in the options.fields + if (options.fields && Array.isArray(options.fields)) { + for (const key of Object.keys(values)) { + if (!options.fields.includes(key)) { + delete values[key]; + } + } + } else { + options.fields = intersection( + Object.keys(values), + Array.from(modelDefinition.physicalAttributes.keys()), + ); + if (updatedAtAttrName && !options.fields.includes(updatedAtAttrName)) { + options.fields.push(updatedAtAttrName); + } + } + + if (updatedAtAttrName && !options.silent) { + values[updatedAtAttrName] = this._getDefaultTimestamp(updatedAtAttrName) || new Date(); + } + + options.model = this; + + let valuesUse; + // Validate + if (options.validate) { + const build = this.build(values); + build.set(updatedAtAttrName, values[updatedAtAttrName], { raw: true }); + + if (options.sideEffects) { + Object.assign(values, pick(build.get(), build.changed())); + options.fields = union(options.fields, Object.keys(values)); + } + + // TODO: instead of setting "skip", set the "fields" property on a copy of options that's passed to "validate" + // We want to skip validations for all other fields + options.skip = difference(Array.from(modelDefinition.attributes.keys()), Object.keys(values)); + const attributes = await build.validate(options); + options.skip = undefined; + if (attributes && attributes.dataValues) { + values = pick(attributes.dataValues, Object.keys(values)); + } + } + + // Run before hook + if (options.hooks) { + options.attributes = values; + await this.hooks.runAsync('beforeBulkUpdate', options); + values = options.attributes; + delete options.attributes; + } + + valuesUse = values; + + // Get instances and run beforeUpdate hook on each record individually + let instances; + let updateDoneRowByRow = false; + if (options.individualHooks) { + instances = await this.findAll({ + where: options.where, + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + benchmark: options.benchmark, + paranoid: options.paranoid, + }); + + if (instances.length > 0) { + // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly + // i.e. whether they change values for each record in the same way + let changedValues; + let different = false; + + instances = await Promise.all( + instances.map(async instance => { + // Record updates in instances dataValues + Object.assign(instance.dataValues, values); + // Set the changed fields on the instance + forIn(valuesUse, (newValue, attr) => { + if (newValue !== instance._previousDataValues[attr]) { + instance.setDataValue(attr, newValue); + } + }); + + // Run beforeUpdate hook + await this.hooks.runAsync('beforeUpdate', instance, options); + await this.hooks.runAsync('beforeSave', instance, options); + 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); + } + } + + return instance; + }), + ); + + if (!different) { + const keys = Object.keys(changedValues); + // Hooks do not change values or change them uniformly + if (keys.length > 0) { + // 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; + + return instance.save(individualOptions); + }), + ); + updateDoneRowByRow = true; + } + } + } + + let result; + if (updateDoneRowByRow) { + result = [instances.length, instances]; + } else if ( + isEmpty(valuesUse) || + (Object.keys(valuesUse).length === 1 && valuesUse[updatedAtAttrName]) + ) { + // only updatedAt is being passed, then skip update + result = [0]; + } else { + valuesUse = mapValueFieldNames(valuesUse, options.fields, this); + options = mapOptionFieldNames(options, this); + options.hasTrigger = this.options ? this.options.hasTrigger : false; + + const affectedRows = await this.queryInterface.bulkUpdate( + this.table, + valuesUse, + options.where, + options, + getObjectFromMap(this.modelDefinition.physicalAttributes), + ); + if (options.returning) { + result = [affectedRows.length, affectedRows]; + instances = affectedRows; + } else { + result = [affectedRows]; + } + } + + if (options.individualHooks) { + await Promise.all( + instances.map(async instance => { + await this.hooks.runAsync('afterUpdate', instance, options); + await this.hooks.runAsync('afterSave', instance, options); + }), + ); + result[1] = instances; + } + + // Run after hook + if (options.hooks) { + options.attributes = values; + await this.hooks.runAsync('afterBulkUpdate', options); + delete options.attributes; + } + + return result; + } + + /** + * Runs a describe query on the table. + * + * @param {string} [schema] schema name to search table in + * @param {object} [options] query options + * + * @returns {Promise} hash of attributes and their types + */ + // TODO: move "schema" to options + static async describe(schema, options) { + const table = this.modelDefinition.table; + + return await this.queryInterface.describeTable( + { ...table, schema: schema || table.schema }, + options, + ); + } + + static _getDefaultTimestamp(attributeName) { + const attributes = this.modelDefinition.attributes; + + const attribute = attributes.get(attributeName); + if (attribute?.defaultValue) { + return toDefaultValue(attribute.defaultValue); + } + } + + static _expandAttributes(options) { + if (!isPlainObject(options.attributes)) { + return; + } + + let attributes = Array.from(this.modelDefinition.attributes.keys()); + + if (options.attributes.exclude) { + attributes = attributes.filter(elem => !options.attributes.exclude.includes(elem)); + } + + if (options.attributes.include) { + attributes = attributes.concat(options.attributes.include); + } + + options.attributes = attributes; + } + + // Inject _scope into options. + static _injectScope(options) { + const scope = cloneDeep(this._scope) ?? {}; + this._normalizeIncludes(scope, this); + this._defaultsOptions(options, scope); + } + + static [Symbol.for('nodejs.util.inspect.custom')]() { + return this.name; + } + + static hasAlias(alias) { + return Object.hasOwn(this.associations, alias); + } + + static getAssociations(target) { + return Object.values(this.associations).filter( + association => association.target.name === target.name, + ); + } + + static getAssociationWithModel(targetModel, targetAlias) { + if (targetAlias) { + return this.getAssociation(targetAlias); + } + + if (!targetModel) { + throwInvalidInclude({ model: targetModel, as: targetAlias }); + } + + const matchingAssociations = this._getAssociationsByModel(targetModel); + if (matchingAssociations.length === 0) { + throw new SequelizeErrors.EagerLoadingError( + `Invalid Include received: no associations exist between "${this.name}" and "${targetModel.name}"`, + ); + } + + if (matchingAssociations.length > 1) { + throw new SequelizeErrors.EagerLoadingError( + ` +Ambiguous Include received: +You're trying to include the model "${targetModel.name}", but is associated to "${this.name}" multiple times. + +Instead of specifying a Model, either: +1. pass one of the Association object (available in "${this.name}.associations") in the "association" option, e.g.: + include: { + association: ${this.name}.associations.${matchingAssociations[0].as}, + }, + +2. pass the name of one of the associations in the "association" option, e.g.: + include: { + association: '${matchingAssociations[0].as}', + }, + +"${this.name}" is associated to "${targetModel.name}" through the following associations: ${matchingAssociations.map(association => `"${association.as}"`).join(', ')} +`.trim(), + ); + } + + return matchingAssociations[0]; + } + + /** + * Increments the value of one or more attributes. + * + * The increment is done using a `SET column = column + X WHERE foo = 'bar'` query. + * + * @example increment number by 1 + * ```ts + * Model.increment('number', { where: { foo: 'bar' }); + * ``` + * + * @example increment number and count by 2 + * ```ts + * Model.increment(['number', 'count'], { by: 2, where: { foo: 'bar' } }); + * ``` + * + * @example increment answer by 42, and decrement tries by 1 + * ```ts + * // `by` cannot be used, as each attribute specifies its own value + * Model.increment({ answer: 42, tries: -1}, { 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 an object is provided, each key is incremented by the corresponding value, `by` is ignored. + * @param {object} options increment options + * @param {object} options.where conditions hash + * + * @returns {Promise} an array of affected rows and affected count with `options.returning` true, + * whenever supported by dialect + */ + static async increment(fields, options) { + options ||= {}; + if (typeof fields === 'string') { + fields = [fields]; + } + + const modelDefinition = this.modelDefinition; + const attributeDefs = modelDefinition.attributes; + + if (Array.isArray(fields)) { + fields = fields.map(attributeName => { + const attributeDef = attributeDefs.get(attributeName); + if (attributeDef && attributeDef.columnName !== attributeName) { + return attributeDef.columnName; + } + + return attributeName; + }); + } else if (fields && typeof fields === 'object') { + fields = Object.keys(fields).reduce((rawFields, attributeName) => { + const attributeDef = attributeDefs.get(attributeName); + if (attributeDef && attributeDef.columnName !== attributeName) { + rawFields[attributeDef.columnName] = fields[attributeName]; + } else { + rawFields[attributeName] = fields[attributeName]; + } + + return rawFields; + }, {}); + } + + this._injectScope(options); + this._optionsMustContainWhere(options); + + options = defaults({}, options, { + by: 1, + where: {}, + increment: true, + }); + const isSubtraction = !options.increment; + + mapOptionFieldNames(options, this); + + const where = { ...options.where }; + + // A plain object whose keys are the fields to be incremented and whose values are + // the amounts to be incremented by. + 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 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 (modelDefinition.versionAttributeName) { + incrementAmountsByField[modelDefinition.versionAttributeName] = isSubtraction ? -1 : 1; + } + + const extraAttributesToBeUpdated = {}; + + const updatedAtAttrName = modelDefinition.timestampAttributeNames.updatedAt; + if (!options.silent && updatedAtAttrName && !incrementAmountsByField[updatedAtAttrName]) { + const columnName = modelDefinition.getColumnName(updatedAtAttrName); + extraAttributesToBeUpdated[columnName] = + this._getDefaultTimestamp(updatedAtAttrName) || new Date(); + } + + const tableName = this.table; + let affectedRows; + if (isSubtraction) { + affectedRows = await this.queryInterface.decrement( + this, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ); + } else { + affectedRows = await this.queryInterface.increment( + this, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ); + } + + if (options.returning) { + return [affectedRows, affectedRows.length]; + } + + return [affectedRows]; + } + + /** + * Decrement the value of one or more columns. This is done in the database, which means it does not use the values + * currently stored on the Instance. The decrement is done using a + * ```sql SET column = column - X WHERE foo = 'bar'``` query. To get the correct value after a decrement into the Instance + * you should do a reload. + * + * @example decrement number by 1 + * ```ts + * Model.decrement('number', { where: { foo: 'bar' }); + * ``` + * + * @example decrement number and count by 2 + * ```ts + * Model.decrement(['number', 'count'], { by: 2, where: { foo: 'bar' } }); + * ``` + * + * @example decrement answer by 42, and decrement tries by -1 + * ```ts + * // `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 + * + * @since 4.36.0 + * + * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, + * whenever supported by dialect + */ + static async decrement(fields, options) { + return this.increment(fields, { + by: 1, + ...options, + increment: false, + }); + } + + static _optionsMustContainWhere(options) { + assert(options && options.where, 'Missing where attribute in the options parameter'); + assert( + isPlainObject(options.where) || + Array.isArray(options.where) || + options.where instanceof BaseSqlExpression, + 'Expected plain object, array or sequelize method in the options.where parameter', + ); + } + + /** + * Returns a Where Object that can be used to uniquely select this instance, using the instance's primary keys. + * + * @param {boolean} [checkVersion=false] include version attribute in where hash + * @param {boolean} [nullIfImpossible=false] return null instead of throwing an error if the instance is missing its + * primary keys and therefore no Where object can be built. + * + * @returns {object} + */ + where(checkVersion, nullIfImpossible) { + return getModelPkWhere(this, checkVersion, nullIfImpossible); + } + + toString() { + return `[object SequelizeInstance:${this.constructor.name}]`; + } + + /** + * Returns the underlying data value + * + * Unlike {@link Model#get}, this method returns the value as it was retrieved, bypassing + * getters, cloning, virtual attributes. + * + * @param {string} key The name of the attribute to return. + * @returns {any} + */ + getDataValue(key) { + return this.dataValues[key]; + } + + /** + * Updates the underlying data value + * + * Unlike {@link Model#set}, this method skips any special behavior and directly replaces the raw value. + * + * @param {string} key The name of the attribute to update. + * @param {any} value The new value for that attribute. + */ + setDataValue(key, value) { + const originalValue = this._previousDataValues[key]; + + if (!isEqual(value, originalValue)) { + this.changed(key, true); + } + + this.dataValues[key] = value; + } + + /** + * If no key is given, returns all values of the instance, also invoking virtual getters. + * + * 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} [attributeName] key to get value of + * @param {object} [options] get options + * + * @returns {object|any} + */ + get(attributeName, options) { + if (options === undefined && typeof attributeName === 'object') { + options = attributeName; + attributeName = undefined; + } + + options ??= EMPTY_OBJECT; + + const { attributes, attributesWithGetters } = this.modelDefinition; + + if (attributeName) { + const attribute = attributes.get(attributeName); + if (attribute?.get && !options.raw) { + return attribute.get.call(this, attributeName, options); + } + + if ( + options.plain && + this._options.include && + this._options.includeNames.includes(attributeName) + ) { + if (Array.isArray(this.dataValues[attributeName])) { + return this.dataValues[attributeName].map(instance => instance.get(options)); + } + + if (this.dataValues[attributeName] instanceof Model) { + return this.dataValues[attributeName].get(options); + } + + return this.dataValues[attributeName]; + } + + return this.dataValues[attributeName]; + } + + // TODO: move to its own method instead of overloading. + if ( + attributesWithGetters.size > 0 || + (options.plain && this._options.include) || + options.clone + ) { + const values = Object.create(null); + if (attributesWithGetters.size > 0) { + for (const attributeName2 of attributesWithGetters) { + if (!this._options.attributes?.includes(attributeName2)) { + continue; + } + + values[attributeName2] = this.get(attributeName2, options); + } + } + + for (const attributeName2 in this.dataValues) { + if ( + !Object.hasOwn(values, attributeName2) && + Object.hasOwn(this.dataValues, attributeName2) + ) { + values[attributeName2] = this.get(attributeName2, options); + } + } + + return values; + } + + return this.dataValues; + } + + /** + * Set is used to update values on the instance (the Sequelize representation of the instance that is, remember that + * nothing will be persisted before you actually call `save`). In its most basic form `set` will update a value stored in + * the underlying `dataValues` object. However, if a custom setter function is defined for the key, that function will be + * called instead. To bypass the setter, you can pass `raw: true` in the options object. + * + * If set is called with an object, it will loop over the object, and call set recursively for each key, value pair. If + * you set raw to true, the underlying dataValues will either be set directly to the object passed, or used to extend + * dataValues, if dataValues already contain values. + * + * When set is called, the previous value of the field is stored and sets a changed flag(see `changed`). + * + * Set can also be used to build instances for associations, if you have values for those. + * When using set with associations you need to make sure the property key matches the alias of the association + * while also making sure that the proper include options have been set (from .build() or .findOne()) + * + * If called with a dot.separated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as + * changed. + * + * @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 + * + * @returns {Model} + */ + set(key, value, options) { + let values; + let originalValue; + + const modelDefinition = this.modelDefinition; + + if (typeof key === 'object' && key !== null) { + values = key; + options = value || {}; + + if (options.reset) { + this.dataValues = {}; + for (const key in values) { + this.changed(key, false); + } + } + + const hasDateAttributes = modelDefinition.dateAttributeNames.size > 0; + const hasBooleanAttributes = modelDefinition.booleanAttributeNames.size > 0; + + // 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) && + !hasDateAttributes && + !hasBooleanAttributes + ) { + if (Object.keys(this.dataValues).length > 0) { + Object.assign(this.dataValues, values); + } else { + this.dataValues = values; + } + + // If raw, .changed() shouldn't be true + this._previousDataValues = { ...this.dataValues }; + } else { + // Loop and call set + if (options.attributes) { + const setKeys = data => { + for (const k of data) { + if (values[k] === undefined) { + continue; + } + + this.set(k, values[k], options); + } + }; + + setKeys(options.attributes); + + const virtualAttributes = modelDefinition.virtualAttributeNames; + if (virtualAttributes.size > 0) { + setKeys(virtualAttributes); + } + + if (this._options.includeNames) { + setKeys(this._options.includeNames); + } + } else { + for (const key in values) { + this.set(key, values[key], options); + } + } + + if (options.raw) { + // If raw, .changed() shouldn't be true + this._previousDataValues = { ...this.dataValues }; + } + } + + return this; + } + + if (!options) { + options = {}; + } + + if (!options.raw) { + originalValue = this.dataValues[key]; + } + + const attributeDefinition = modelDefinition.attributes.get(key); + + // If not raw, and there's a custom setter + if (!options.raw && attributeDefinition?.set) { + attributeDefinition.set.call(this, value, key); + // custom setter should have changed value, get that changed value + // TODO: v5 make setters return new value instead of changing internal store + const newValue = this.dataValues[key]; + if (!isEqual(newValue, originalValue)) { + this._previousDataValues[key] = originalValue; + this.changed(key, true); + } + } else { + // Check if we have included models, and if this key matches the include model names/aliases + if (this._options && this._options.include && this._options.includeNames.includes(key)) { + // Pass it on to the include handler + this._setInclude(key, value, options); + + return this; + } + + // Bunch of stuff we won't do when it's raw + if (!options.raw) { + // If the attribute is not in model definition, return + if (!attributeDefinition) { + const jsonAttributeNames = modelDefinition.jsonAttributeNames; + + if (key.includes('.') && jsonAttributeNames.has(key.split('.')[0])) { + const previousNestedValue = Dottie.get(this.dataValues, key); + if (!isEqual(previousNestedValue, value)) { + Dottie.set(this.dataValues, key, value); + this.changed(key.split('.')[0], true); + } + } + + return this; + } + + // If attempting to set primary key and primary key is already defined, return + const primaryKeyNames = modelDefinition.primaryKeysAttributeNames; + if (originalValue && primaryKeyNames.has(key)) { + return this; + } + + // TODO: throw an error when trying to set a read only attribute with to a different value + // If attempting to set read only attributes, return + const readOnlyAttributeNames = modelDefinition.readOnlyAttributeNames; + if (!this.isNewRecord && readOnlyAttributeNames.has(key)) { + return this; + } + } + + // If there's a data type sanitizer + const attributeType = attributeDefinition?.type; + if ( + !options.comesFromDatabase && + value != null && + !(value instanceof BaseSqlExpression) && + attributeType && + // "type" can be a string + attributeType instanceof AbstractDataType + ) { + value = attributeType.sanitize(value, options); + } + + // Set when the value has changed and not raw + if ( + !options.raw && + // True when sequelize method + (value instanceof BaseSqlExpression || + // Otherwise, check for data type type comparators + (value != null && + attributeType && + attributeType instanceof AbstractDataType && + !attributeType.areValuesEqual(value, originalValue, options)) || + ((value == null || !attributeType || !(attributeType instanceof AbstractDataType)) && + !isEqual(value, originalValue))) + ) { + this._previousDataValues[key] = originalValue; + this.changed(key, true); + } + + // set data value + this.dataValues[key] = value; + } + + return this; + } + + setAttributes(updates) { + return this.set(updates); + } + + /** + * If changed is called with a string it will return a boolean indicating whether the value of that key in `dataValues` is + * different from the value in `_previousDataValues`. + * + * If changed is called without an argument, it will return an array of keys that have changed. + * + * If changed is called without an argument and no keys have changed, it will return `false`. + * + * Please note that this function will return `false` when a property from a nested (for example JSON) property + * was edited manually, you must call `changed('key', true)` manually in these cases. + * Writing an entirely new object (eg. deep cloned) will be detected. + * + * @example + * ``` + * const mdl = await MyModel.findOne(); + * mdl.myJsonField.a = 1; + * console.log(mdl.changed()) => false + * mdl.save(); // this will not save anything + * mdl.changed('myJsonField', true); + * console.log(mdl.changed()) => ['myJsonField'] + * mdl.save(); // will save + * ``` + * + * @param {string} [key] key to check or change status of + * @param {any} [value] value to set + * + * @returns {boolean|Array} + */ + changed(key, value) { + if (key === undefined) { + if (this._changed.size > 0) { + return [...this._changed]; + } + + return false; + } + + if (value === true) { + this._changed.add(key); + + return this; + } + + if (value === false) { + this._changed.delete(key); + + return this; + } + + return this._changed.has(key); + } + + /** + * Returns the previous value for key from `_previousDataValues`. + * + * If called without a key, returns the previous values for all values which have changed + * + * @param {string} [key] key to get previous value of + * + * @returns {any|Array} + */ + previous(key) { + if (key) { + return this._previousDataValues[key]; + } + + return pickBy(this._previousDataValues, (value, key) => this.changed(key)); + } + + _setInclude(key, value, options) { + if (!Array.isArray(value)) { + value = [value]; + } + + if (value[0] instanceof Model) { + value = value.map(instance => instance.dataValues); + } + + const include = this._options.includeMap[key]; + const association = include.association; + const primaryKeyAttribute = include.model.primaryKeyAttribute; + const childOptions = { + isNewRecord: this.isNewRecord, + include: include.include, + includeNames: include.includeNames, + includeMap: include.includeMap, + includeValidated: true, + raw: options.raw, + attributes: include.originalAttributes, + comesFromDatabase: options.comesFromDatabase, + }; + let isEmpty; + + if (include.originalAttributes === undefined || include.originalAttributes.length > 0) { + if (association.isSingleAssociation) { + if (Array.isArray(value)) { + value = value[0]; + } + + isEmpty = (value && value[primaryKeyAttribute] === null) || value === null; + this[key] = this.dataValues[key] = isEmpty + ? null + : include.model.build(value, childOptions); + } else { + isEmpty = value[0] && value[0][primaryKeyAttribute] === null; + this[key] = this.dataValues[key] = isEmpty + ? [] + : include.model.bulkBuild(value, childOptions); + } + } + } + + /** + * 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 {@link ValidationError}, + * which will have a property for each of the fields for which the validation failed, with the error message for that + * field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. + * If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. + * In other words, if some other model instance (child) was eager loaded with this instance (parent), + * and you change something in the child, calling `save()` will simply ignore the change that happened on the child. + * + * @param {object} [options] save options + * @returns {Promise} + */ + async save(options) { + if (arguments.length > 1) { + throw new Error('The second argument was removed in favor of the options object.'); + } + + options = cloneDeep(options) ?? {}; + options = defaultsLodash(options, { + hooks: true, + validate: true, + }); + + setTransactionFromCls(options, this.sequelize); + + const modelDefinition = this.modelDefinition; + + if (!options.fields) { + if (this.isNewRecord) { + options.fields = Array.from(modelDefinition.attributes.keys()); + } else { + options.fields = intersection( + this.changed(), + Array.from(modelDefinition.attributes.keys()), + ); + } + + options.defaultFields = options.fields; + } + + if (options.returning === undefined) { + if (options.association) { + options.returning = false; + } else if (this.isNewRecord) { + options.returning = true; + } + } + + // TODO: use modelDefinition.primaryKeyAttributes (plural!) + const primaryKeyName = this.constructor.primaryKeyAttribute; + const primaryKeyAttribute = primaryKeyName && modelDefinition.attributes.get(primaryKeyName); + const createdAtAttr = modelDefinition.timestampAttributeNames.createdAt; + const versionAttr = modelDefinition.versionAttributeName; + const hook = this.isNewRecord ? 'Create' : 'Update'; + const wasNewRecord = this.isNewRecord; + const now = new Date(); + let updatedAtAttr = modelDefinition.timestampAttributeNames.updatedAt; + + if (updatedAtAttr && options.fields.length > 0 && !options.fields.includes(updatedAtAttr)) { + options.fields.push(updatedAtAttr); + } + + if (versionAttr && options.fields.length > 0 && !options.fields.includes(versionAttr)) { + options.fields.push(versionAttr); + } + + if (options.silent === true && !(this.isNewRecord && this.get(updatedAtAttr, { raw: true }))) { + // UpdateAtAttr might have been added as a result of Object.keys(Model.rawAttributes). In that case we have to remove it again + remove(options.fields, val => val === updatedAtAttr); + updatedAtAttr = false; + } + + if (this.isNewRecord === true) { + if (primaryKeyAttribute && primaryKeyAttribute.autoIncrement) { + // Some dialects do not support returning the last inserted ID. + // To overcome this limitation, we check if the dialect implements getNextPrimaryKeyValue, + // so we get the next ID before the insert. + const nextPrimaryKey = await this.constructor.queryInterface.getNextPrimaryKeyValue( + this.constructor.table.tableName, + primaryKeyName, + ); + if (nextPrimaryKey) { + this.set(primaryKeyName, nextPrimaryKey); + } + } + + if (createdAtAttr && !options.fields.includes(createdAtAttr)) { + options.fields.push(createdAtAttr); + } + + if ( + primaryKeyAttribute && + primaryKeyAttribute.defaultValue && + !options.fields.includes(primaryKeyName) + ) { + options.fields.unshift(primaryKeyName); + } + } + + if ( + this.isNewRecord === false && + primaryKeyName && + this.get(primaryKeyName, { raw: true }) === undefined + ) { + throw new Error( + 'You attempted to save an instance with no primary key, this is not allowed since it would result in a global update', + ); + } + + if (updatedAtAttr && !options.silent && options.fields.includes(updatedAtAttr)) { + this.dataValues[updatedAtAttr] = this.constructor._getDefaultTimestamp(updatedAtAttr) || now; + } + + if (this.isNewRecord && createdAtAttr && !this.dataValues[createdAtAttr]) { + this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now; + } + + // Db2 does not allow NULL values for unique columns. + // Add dummy values if not provided by test case or user. + if (this.sequelize.dialect.name === 'db2' && this.isNewRecord) { + // TODO: remove. This is fishy and is going to be a source of bugs (because it replaces null values with arbitrary values that could be actual data). + // If DB2 doesn't support NULL in unique columns, then it should error if the user tries to insert NULL in one. + this.uniqno = this.sequelize.dialect.queryGenerator.addUniqueFields( + this.dataValues, + modelDefinition.rawAttributes, + this.uniqno, + ); + } + + // 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); + } + + await this.constructor.hooks.runAsync(`before${hook}`, this, options); + await this.constructor.hooks.runAsync(`beforeSave`, 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); + } + } + + options.fields = uniq(options.fields.concat(hookChanged)); + } + + if (hookChanged && options.validate) { + // Validate again + + options.skip = difference(Array.from(modelDefinition.attributes.keys()), hookChanged); + await this.validate(options); + delete options.skip; + } + } + + if ( + options.fields.length > 0 && + this.isNewRecord && + this._options.include && + this._options.include.length > 0 + ) { + await Promise.all( + this._options.include + .filter(include => include.association instanceof BelongsToAssociation) + .map(async include => { + const instance = this.get(include.as); + if (!instance) { + return; + } + + const includeOptions = defaultsLodash(omit(cloneDeep(include), ['association']), { + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + parentRecord: this, + }); + + await instance.save(includeOptions); + + await this[include.association.accessors.set](instance, { + save: false, + logging: options.logging, + }); + }), + ); + } + + const realFields = options.fields.filter( + attributeName => !modelDefinition.virtualAttributeNames.has(attributeName), + ); + if (realFields.length === 0) { + return this; + } + + const versionColumnName = versionAttr && modelDefinition.getColumnName(versionAttr); + const values = mapValueFieldNames(this.dataValues, options.fields, this.constructor); + let query; + let args; + let where; + + if (!this.isNewRecord) { + where = this.where(true); + if (versionAttr) { + values[versionColumnName] = Number.parseInt(values[versionColumnName], 10) + 1; + } + + query = 'update'; + args = [this, this.constructor.table, values, where, options]; + } + + if (!this.changed() && !this.isNewRecord) { + return this; + } + + if (this.isNewRecord) { + query = 'insert'; + args = [this, this.constructor.table, 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 { + result.dataValues[versionAttr] = values[versionColumnName]; + } + } + + // Transfer database generated values (defaults, autoincrement, etc) + for (const attribute of modelDefinition.attributes.values()) { + if ( + attribute.columnName && + values[attribute.columnName] !== undefined && + attribute.columnName !== attribute.attributeName + ) { + values[attribute.attributeName] = values[attribute.columnName]; + // TODO: if a column uses the same name as an attribute, this will break! + delete values[attribute.columnName]; + } + } + + Object.assign(values, result.dataValues); + + Object.assign(result.dataValues, values); + if (wasNewRecord && this._options.include && this._options.include.length > 0) { + await Promise.all( + this._options.include + .filter( + include => + !( + include.association instanceof BelongsToAssociation || + (include.parent && include.parent.association instanceof BelongsToManyAssociation) + ), + ) + .map(async include => { + let instances = this.get(include.as); + + if (!instances) { + return; + } + + if (!Array.isArray(instances)) { + instances = [instances]; + } + + const includeOptions = defaultsLodash(omit(cloneDeep(include), ['association']), { + connection: options.connection, + transaction: options.transaction, + logging: options.logging, + parentRecord: this, + }); + + // 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 BelongsToManyAssociation) { + 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, + }; + + const throughModel = include.association.through.model; + if (instance[throughModel.name]) { + const throughDefinition = throughModel.modelDefinition; + for (const attribute of throughDefinition.attributes.values()) { + const { attributeName } = attribute; + + if ( + attribute._autoGenerated || + attributeName === include.association.foreignKey || + attributeName === include.association.otherKey || + typeof instance[throughModel.name][attributeName] === 'undefined' + ) { + continue; + } + + values0[attributeName] = instance[throughModel.name][attributeName]; + } + } + + 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); + await instance.save(includeOptions); + } + }), + ); + }), + ); + } + + // Run after hook + if (options.hooks) { + await this.constructor.hooks.runAsync(`after${hook}`, result, options); + await this.constructor.hooks.runAsync(`afterSave`, result, options); + } + + for (const field of options.fields) { + result._previousDataValues[field] = result.dataValues[field]; + this.changed(field, false); + } + + this.isNewRecord = false; + + return result; + } + + /** + * Refreshes the current instance in-place, i.e. update the object with current data from the DB and return + * the same object. This is different from doing a `find(Instance.id)`, because that would create and + * return a new instance. With this method, all references to the Instance are updated with the new data + * and no new objects are created. + * + * @param {object} [options] Options that are passed on to `Model.find` + * + * @returns {Promise} + */ + async reload(options) { + options = defaults({ where: this.where() }, options, { + include: this._options.include || undefined, + }); + + 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: !options.attributes, + }); + + return this; + } + + /** + * Validate the attribute of this instance according to validation rules set in the model definition. + * + * Emits null if and only if validation successful; otherwise an Error instance containing + * { field name : [error msgs] } entries. + * + * @param {object} [options] Options that are passed to the validator + * @returns {Promise} + */ + async validate(options) { + return new InstanceValidator(this, options).validate(); + } + + /** + * This is the same as calling {@link Model#set} followed by calling {@link Model#save}, + * but it only saves attributes values passed to it, making it safer. + * + * @param {object} values See `set` + * @param {object} options See `save` + * + * @returns {Promise} + */ + async update(values, options) { + // Clone values so it doesn't get modified for caller scope and ignore undefined values + values = omitBy(values, value => value === undefined); + + const changedBefore = this.changed() || []; + + if (this.isNewRecord) { + throw new Error('You attempted to update an instance that is not persisted.'); + } + + options ??= EMPTY_OBJECT; + if (Array.isArray(options)) { + options = { fields: options }; + } + + options = cloneDeep(options); + const setOptions = cloneDeep(options); + setOptions.attributes = options.fields; + this.set(values, setOptions); + + // Now we need to figure out which fields were actually affected by the setter. + const sideEffects = without(this.changed(), ...changedBefore); + const fields = union(Object.keys(values), sideEffects); + + if (!options.fields) { + options.fields = intersection(fields, this.changed()); + options.defaultFields = options.fields; + } + + return await this.save(options); + } + + /** + * Destroys 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 + * @returns {Promise} + */ + async destroy(options) { + options = { + hooks: true, + force: false, + ...options, + }; + + setTransactionFromCls(options, this.sequelize); + + const modelDefinition = this.modelDefinition; + + // Run before hook + if (options.hooks) { + await modelDefinition.hooks.runAsync('beforeDestroy', this, options); + } + + let result; + if (modelDefinition.timestampAttributeNames.deletedAt && options.force === false) { + const attributeName = modelDefinition.timestampAttributeNames.deletedAt; + const attribute = modelDefinition.attributes.get(attributeName); + const 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 { + // TODO: replace "hooks" with "noHooks" in this method and call ModelRepository.destroy instead of queryInterface.delete + const where = this.where(true); + + result = await this.constructor.queryInterface.bulkDelete(this.constructor, { + limit: null, + ...options, + where, + }); + } + + // Run after hook + if (options.hooks) { + await modelDefinition.hooks.runAsync('afterDestroy', this, options); + } + + return result; + } + + /** + * Returns true if this instance is "soft deleted". + * Throws an error if {@link ModelOptions.paranoid} is not enabled. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/paranoid/} to learn more about soft deletion / paranoid models. + * + * @returns {boolean} + */ + isSoftDeleted() { + const modelDefinition = this.modelDefinition; + + const deletedAtAttributeName = modelDefinition.timestampAttributeNames.deletedAt; + if (!deletedAtAttributeName) { + throw new Error('Model is not paranoid'); + } + + const deletedAtAttribute = modelDefinition.attributes.get(deletedAtAttributeName); + const defaultValue = deletedAtAttribute.defaultValue ?? null; + const deletedAt = this.get(deletedAtAttributeName) || null; + const isSet = deletedAt !== defaultValue; + + return isSet; + } + + /** + * Restores the row corresponding to this instance. + * Only available for paranoid models. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/paranoid/} to learn more about soft deletion / paranoid models. + * + * @param {object} [options={}] restore options + * @returns {Promise} + */ + async restore(options) { + const modelDefinition = this.modelDefinition; + const deletedAtAttributeName = modelDefinition.timestampAttributeNames.deletedAt; + + if (!deletedAtAttributeName) { + throw new Error('Model is not paranoid'); + } + + options = { + hooks: true, + force: false, + ...options, + }; + + setTransactionFromCls(options, this.sequelize); + + // Run before hook + if (options.hooks) { + await this.constructor.hooks.runAsync('beforeRestore', this, options); + } + + const deletedAtAttribute = modelDefinition.attributes.get(deletedAtAttributeName); + const deletedAtDefaultValue = deletedAtAttribute.defaultValue ?? null; + + this.setDataValue(deletedAtAttributeName, deletedAtDefaultValue); + const result = await this.save({ ...options, hooks: false, omitNull: false }); + // Run after hook + if (options.hooks) { + await this.constructor.hooks.runAsync('afterRestore', this, options); + + return result; + } + + return result; + } + + /** + * Increment the value of one or more columns. This is done in the database, which means it does not use the values + * currently stored on the Instance. The increment is done using a + * ```sql + * SET column = column + X + * ``` + * query. The updated instance will be returned by default in Postgres. However, in other dialects, you will need to do a + * reload to get the new values. + * + * @example + * instance.increment('number') // increment number by 1 + * + * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2 + * + * // increment answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own value + * instance.increment({ answer: 42, tries: 1}, { by: 2 }) + * + * @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 + * + * @returns {Promise} + * @since 4.0.0 + */ + async increment(fields, options) { + const identifier = this.where(); + + options = cloneDeep(options) ?? {}; + options.where = { ...options.where, ...identifier }; + options.instance = this; + + await this.constructor.increment(fields, options); + + return this; + } + + /** + * Decrement the value of one or more columns. This is done in the database, which means it does not use the values + * currently stored on the Instance. The decrement is done using a + * ```sql + * SET column = column - X + * ``` + * query. The updated instance will be returned by default in Postgres. However, in other dialects, you will need to do a + * reload to get the new values. + * + * @example + * instance.decrement('number') // decrement number by 1 + * + * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2 + * + * // decrement answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own value + * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) + * + * @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 + * @returns {Promise} + */ + async decrement(fields, options) { + return this.increment(fields, { + by: 1, + ...options, + increment: false, + }); + } + + /** + * Check whether this and `other` Instance refer to the same row + * + * @param {Model} other Other instance to compare against + * + * @returns {boolean} + */ + equals(other) { + if (!other || !(other instanceof Model)) { + return false; + } + + const modelDefinition = this.modelDefinition; + const otherModelDefinition = this.modelDefinition; + + if (modelDefinition !== otherModelDefinition) { + return false; + } + + return every(modelDefinition.primaryKeysAttributeNames, attribute => { + return this.get(attribute, { raw: true }) === other.get(attribute, { raw: true }); + }); + } + + /** + * Check if this is equal to one of `others` by calling equals + * + * @param {Array} others An array of instances to check against + * + * @returns {boolean} + */ + equalsOneOf(others) { + return others.some(other => this.equals(other)); + } + + /** + * 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. + * + * @see + * {@link Model#get} + * + * @returns {object} + */ + toJSON() { + return cloneDeepLodash( + this.get({ + plain: true, + }), + ); + } + + /** + * Defines a 1:n association between two models. + * The foreign key is added on the target model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * Profile.hasMany(User) + * ``` + * + * @param {Model} target The model that will be associated with a hasMany relationship + * @param {object} options Options for the association + * @returns {HasManyAssociation} The newly defined association (also available in {@link Model.associations}). + */ + static hasMany(target, options) { + return HasManyAssociation.associate(AssociationSecret, this, target, options); + } + + /** + * Create an N:M association with a join table. Defining `through` is required. + * The foreign keys are added on the through model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * // Automagically generated join model + * User.belongsToMany(Project, { through: 'UserProjects' }) + * + * // Join model with additional attributes + * const UserProjects = sequelize.define('UserProjects', { + * started: DataTypes.BOOLEAN + * }) + * User.belongsToMany(Project, { through: UserProjects }) + * ``` + * + * @param {Model} target Target model + * @param {object} options belongsToMany association options + * @returns {BelongsToManyAssociation} The newly defined association (also available in {@link Model.associations}). + */ + static belongsToMany(target, options) { + return BelongsToManyAssociation.associate(AssociationSecret, this, target, options); + } + + /** + * Creates a 1:1 association between this model (the source) and the provided target. + * The foreign key is added on the target model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * User.hasOne(Profile) + * ``` + * + * @param {Model} target The model that will be associated with hasOne relationship + * @param {object} [options] hasOne association options + * @returns {HasOneAssociation} The newly defined association (also available in {@link Model.associations}). + */ + static hasOne(target, options) { + return HasOneAssociation.associate(AssociationSecret, this, target, options); + } + + /** + * Creates an association between this (the source) and the provided target. + * The foreign key is added on the source Model. + * + * See {@link https://sequelize.org/docs/v7/core-concepts/assocs/} to learn more about associations. + * + * @example + * ```javascript + * Profile.belongsTo(User) + * ``` + * + * @param {Model} target The target model + * @param {object} [options] belongsTo association options + * @returns {BelongsToAssociation} The newly defined association (also available in {@link Model.associations}). + */ + static belongsTo(target, options) { + return BelongsToAssociation.associate(AssociationSecret, this, target, options); + } +} + +/** + * Unpacks an object that only contains a single Op.and key to the value of Op.and + * + * Internal method used by {@link combineWheresWithAnd} + * + * @param {WhereOptions} where The object to unpack + * @example `{ [Op.and]: [a, b] }` becomes `[a, b]` + * @example `{ [Op.and]: { key: val } }` becomes `{ key: val }` + * @example `{ [Op.or]: [a, b] }` remains as `{ [Op.or]: [a, b] }` + * @example `{ [Op.and]: [a, b], key: c }` remains as `{ [Op.and]: [a, b], key: c }` + * @private + */ +function unpackAnd(where) { + if (!isObject(where)) { + return where; + } + + const keys = getComplexKeys(where); + + // object is empty, remove it. + if (keys.length === 0) { + return; + } + + // we have more than just Op.and, keep as-is + if (keys.length !== 1 || keys[0] !== Op.and) { + return where; + } + + const andParts = where[Op.and]; + + return andParts; +} + +function combineWheresWithAnd(whereA, whereB) { + const unpackedA = unpackAnd(whereA); + + if (unpackedA === undefined) { + return whereB; + } + + const unpackedB = unpackAnd(whereB); + + if (unpackedB === undefined) { + return whereA; + } + + return { + [Op.and]: [unpackedA, unpackedB].flat(), + }; +} diff --git a/packages/core/src/operators.ts b/packages/core/src/operators.ts new file mode 100644 index 000000000000..e9900961146e --- /dev/null +++ b/packages/core/src/operators.ts @@ -0,0 +1,601 @@ +/** + * This interface is the type of the {@link Op} object. + * + * This type cannot be used directly, use {@link Op} instead. + */ +export interface OpTypes { + /** + * Operator -|- (PG range is adjacent to operator) + * + * ```js + * [Op.adjacent]: [1, 2] + * ``` + * In SQL + * ```sql + * -|- [1, 2) + * ``` + */ + readonly adjacent: unique symbol; + /** + * Operator ALL + * + * ```js + * [Op.gt]: { + * [Op.all]: literal('SELECT 1') + * } + * ``` + * In SQL + * ```sql + * > ALL (SELECT 1) + * ``` + */ + readonly all: unique symbol; + /** + * Operator AND + * + * ```js + * [Op.and]: {a: 5} + * ``` + * In SQL + * ```sql + * AND (a = 5) + * ``` + */ + readonly and: unique symbol; + /** + * Operator ANY ARRAY (PG only) + * + * ```js + * [Op.any]: [2,3] + * ``` + * In SQL + * ```sql + * ANY (ARRAY[2, 3]::INTEGER[]) + * ``` + * + * Operator LIKE ANY ARRAY (also works for iLike and notLike) + * + * ```js + * [Op.like]: { [Op.any]: ['cat', 'hat']} + * ``` + * In SQL + * ```sql + * LIKE ANY (ARRAY['cat', 'hat']) + * ``` + */ + readonly any: unique symbol; + /** + * Operator BETWEEN + * + * ```js + * [Op.between]: [6, 10] + * ``` + * In SQL + * ```sql + * BETWEEN 6 AND 10 + * ``` + */ + readonly between: unique symbol; + /** + * With dialect specific column identifiers (PG in this example) + * + * ```js + * [Op.col]: 'user.organization_id' + * ``` + * In SQL + * ```sql + * = "user"."organization_id" + * ``` + */ + readonly col: unique symbol; + /** + * Operator <@ (PG array contained by operator) + * + * ```js + * [Op.contained]: [1, 2] + * ``` + * In SQL + * ```sql + * <@ [1, 2) + * ``` + */ + readonly contained: unique symbol; + /** + * Operator @> (PG array contains operator) + * + * ```js + * [Op.contains]: [1, 2] + * ``` + * In SQL + * ```sql + * @> [1, 2) + * ``` + */ + readonly contains: unique symbol; + /** + * Operator LIKE + * + * ```js + * [Op.endsWith]: 'hat' + * ``` + * In SQL + * ```sql + * LIKE '%hat' + * ``` + */ + readonly endsWith: unique symbol; + /** + * Operator = + * + * ```js + * [Op.eq]: 3 + * ``` + * In SQL + * ```sql + * = 3 + * ``` + */ + readonly eq: unique symbol; + /** + * Operator > + * + * ```js + * [Op.gt]: 6 + * ``` + * In SQL + * ```sql + * > 6 + * ``` + */ + readonly gt: unique symbol; + /** + * Operator >= + * + * ```js + * [Op.gte]: 6 + * ``` + * In SQL + * ```sql + * >= 6 + * ``` + */ + readonly gte: unique symbol; + + /** + * Operator ILIKE (case insensitive) (PG only) + * + * ```js + * [Op.iLike]: '%hat' + * ``` + * In SQL + * ```sql + * ILIKE '%hat' + * ``` + */ + readonly iLike: unique symbol; + /** + * Operator IN + * + * ```js + * [Op.in]: [1, 2] + * ``` + * In SQL + * ```sql + * IN [1, 2] + * ``` + */ + readonly in: unique symbol; + /** + * Operator ~* (PG only) + * + * ```js + * [Op.iRegexp]: '^[h|a|t]' + * ``` + * In SQL + * ```sql + * ~* '^[h|a|t]' + * ``` + */ + readonly iRegexp: unique symbol; + /** + * Operator IS + * + * ```js + * [Op.is]: null + * ``` + * In SQL + * ```sql + * IS null + * ``` + */ + readonly is: unique symbol; + + /** + * Operator IS NOT + * + * ```js + * [Op.isNot]: null + * ``` + * In SQL + * ```sql + * IS NOT null + * ``` + */ + readonly isNot: unique symbol; + /** + * Operator LIKE + * + * ```js + * [Op.like]: '%hat' + * ``` + * In SQL + * ```sql + * LIKE '%hat' + * ``` + */ + readonly like: unique symbol; + /** + * Operator < + * + * ```js + * [Op.lt]: 10 + * ``` + * In SQL + * ```sql + * < 10 + * ``` + */ + readonly lt: unique symbol; + /** + * Operator <= + * + * ```js + * [Op.lte]: 10 + * ``` + * In SQL + * ```sql + * <= 10 + * ``` + */ + 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 != + * + * ```js + * [Op.ne]: 20 + * ``` + * In SQL + * ```sql + * != 20 + * ``` + */ + readonly ne: unique symbol; + /** + * Operator &> (PG range does not extend to the left of operator) + * + * ```js + * [Op.noExtendLeft]: [1, 2] + * ``` + * In SQL + * ```sql + * &> [1, 2) + * ``` + */ + readonly noExtendLeft: unique symbol; + /** + * Operator &< (PG range does not extend to the right of operator) + * + * ```js + * [Op.noExtendRight]: [1, 2] + * ``` + * In SQL + * ```sql + * &< [1, 2) + * ``` + */ + readonly noExtendRight: unique symbol; + /** + * Operator NOT + * + * ```js + * [Op.not]: true + * ``` + * In SQL + * ```sql + * IS NOT TRUE + * ``` + */ + readonly not: unique symbol; + /** + * Operator NOT BETWEEN + * + * ```js + * [Op.notBetween]: [11, 15] + * ``` + * In SQL + * ```sql + * NOT BETWEEN 11 AND 15 + * ``` + */ + readonly notBetween: unique symbol; + /** + * Operator NOT LIKE + * + * ```js + * [Op.notEndsWith]: 'hat' + * ``` + * In SQL + * ```sql + * NOT LIKE '%hat' + * ``` + */ + readonly notEndsWith: unique symbol; + /** + * Operator NOT ILIKE (case insensitive) (PG only) + * + * ```js + * [Op.notILike]: '%hat' + * ``` + * In SQL + * ```sql + * NOT ILIKE '%hat' + * ``` + */ + readonly notILike: unique symbol; + /** + * Operator NOT IN + * + * ```js + * [Op.notIn]: [1, 2] + * ``` + * In SQL + * ```sql + * NOT IN [1, 2] + * ``` + */ + readonly notIn: unique symbol; + /** + * Operator !~* (PG only) + * + * ```js + * [Op.notIRegexp]: '^[h|a|t]' + * ``` + * In SQL + * ```sql + * !~* '^[h|a|t]' + * ``` + */ + readonly notIRegexp: unique symbol; + /** + * Operator NOT LIKE + * + * ```js + * [Op.notLike]: '%hat' + * ``` + * In SQL + * ```sql + * NOT LIKE '%hat' + * ``` + */ + readonly notLike: unique symbol; + /** + * Operator NOT REGEXP (MySQL/PG only) + * + * ```js + * [Op.notRegexp]: '^[h|a|t]' + * ``` + * In SQL + * ```sql + * NOT REGEXP/!~ '^[h|a|t]' + * ``` + */ + readonly notRegexp: unique symbol; + /** + * Operator NOT LIKE + * + * ```js + * [Op.notStartsWith]: 'hat' + * ``` + * In SQL + * ```sql + * NOT LIKE 'hat%' + * ``` + */ + readonly notStartsWith: unique symbol; + /** + * Operator LIKE + * + * ```js + * [Op.notSubstring]: 'hat' + * ``` + * In SQL + * ```sql + * NOT LIKE '%hat%' + * ``` + */ + readonly notSubstring: unique symbol; + /** + * Operator OR + * + * ```js + * [Op.or]: [{a: 5}, {a: 6}] + * ``` + * In SQL + * ```sql + * (a = 5 OR a = 6) + * ``` + */ + readonly or: unique symbol; + /** + * Operator && (PG array overlap operator) + * + * ```js + * [Op.overlap]: [1, 2] + * ``` + * In SQL + * ```sql + * && [1, 2) + * ``` + */ + readonly overlap: unique symbol; + /** + * Operator REGEXP (MySQL/PG only) + * + * ```js + * [Op.regexp]: '^[h|a|t]' + * ``` + * In SQL + * ```sql + * REGEXP/~ '^[h|a|t]' + * ``` + */ + readonly regexp: unique symbol; + /** + * Operator LIKE + * + * ```js + * [Op.startsWith]: 'hat' + * ``` + * In SQL + * ```sql + * LIKE 'hat%' + * ``` + */ + readonly startsWith: unique symbol; + /** + * Operator << (PG range strictly left of operator) + * + * ```js + * [Op.strictLeft]: [1, 2] + * ``` + * In SQL + * ```sql + * << [1, 2) + * ``` + */ + readonly strictLeft: unique symbol; + /** + * Operator >> (PG range strictly right of operator) + * + * ```js + * [Op.strictRight]: [1, 2] + * ``` + * In SQL + * ```sql + * >> [1, 2) + * ``` + */ + readonly strictRight: unique symbol; + /** + * Operator LIKE + * + * ```js + * [Op.substring]: 'hat' + * ``` + * In SQL + * ```sql + * LIKE '%hat%' + * ``` + */ + readonly substring: unique symbol; + /** + * Operator VALUES + * + * ```js + * [Op.values]: [4, 5, 6] + * ``` + * In SQL + * ```sql + * VALUES (4), (5), (6) + * ``` + */ + readonly values: unique symbol; + /** + * Operator ?| + * + * ```js + * [Op.anyKeyExists]: ['a', 'b'] + * ``` + * In SQL + * ```sql + * ?| ARRAY['a', 'b'] + * ``` + */ + readonly anyKeyExists: unique symbol; + /** + * Operator ?& + * + * ```js + * [Op.allKeysExist]: ['a', 'b'] + * ``` + * In SQL + * ```sql + * ?& ARRAY['a', 'b'] + * ``` + */ + readonly allKeysExist: unique symbol; +} + +// Note: These symbols are registered in the Global Symbol Registry +// to counter bugs when two different versions of this library are loaded +// Source issue: https://github.com/sequelize/sequelize/issues/8663 +// This is not an endorsement of having two different versions of the library loaded at the same time, +// a lot more is going to silently break if you do this. +export const Op: OpTypes = { + eq: Symbol.for('eq'), + ne: Symbol.for('ne'), + gte: Symbol.for('gte'), + gt: Symbol.for('gt'), + lte: Symbol.for('lte'), + lt: Symbol.for('lt'), + not: Symbol.for('not'), + is: Symbol.for('is'), + isNot: Symbol.for('isNot'), + in: Symbol.for('in'), + notIn: Symbol.for('notIn'), + like: Symbol.for('like'), + notLike: Symbol.for('notLike'), + iLike: Symbol.for('iLike'), + notILike: Symbol.for('notILike'), + startsWith: Symbol.for('startsWith'), + notStartsWith: Symbol.for('notStartsWith'), + endsWith: Symbol.for('endsWith'), + notEndsWith: Symbol.for('notEndsWith'), + substring: Symbol.for('substring'), + notSubstring: Symbol.for('notSubstring'), + regexp: Symbol.for('regexp'), + notRegexp: Symbol.for('notRegexp'), + iRegexp: Symbol.for('iRegexp'), + notIRegexp: Symbol.for('notIRegexp'), + between: Symbol.for('between'), + notBetween: Symbol.for('notBetween'), + overlap: Symbol.for('overlap'), + contains: Symbol.for('contains'), + contained: Symbol.for('contained'), + adjacent: Symbol.for('adjacent'), + strictLeft: Symbol.for('strictLeft'), + strictRight: Symbol.for('strictRight'), + noExtendRight: Symbol.for('noExtendRight'), + noExtendLeft: Symbol.for('noExtendLeft'), + and: Symbol.for('and'), + or: Symbol.for('or'), + any: Symbol.for('any'), + all: Symbol.for('all'), + values: Symbol.for('values'), + col: Symbol.for('col'), + match: Symbol.for('match'), + anyKeyExists: Symbol.for('anyKeyExists'), + allKeysExist: Symbol.for('allKeysExist'), +} as OpTypes; diff --git a/packages/core/src/sequelize-typescript.ts b/packages/core/src/sequelize-typescript.ts new file mode 100644 index 000000000000..22a39751ba7e --- /dev/null +++ b/packages/core/src/sequelize-typescript.ts @@ -0,0 +1,1214 @@ +import type { PartialBy } from '@sequelize/utils'; +import { + SortDirection, + cloneDeepPlainValues, + freezeDeep, + inspect, + isNullish, + isString, + join, + localizedStringComparator, + map, + splitObject, +} from '@sequelize/utils'; +import { cyan, red } from 'ansis'; +import { AsyncLocalStorage } from 'node:async_hooks'; +import semver from 'semver'; +import type { + Connection, + CreateSchemaOptions, + DataType, + DataTypeClassOrInstance, + DestroyOptions, + ModelAttributes, + ModelOptions, + ModelStatic, + QiListSchemasOptions, + QueryOptions, + RawConnectionOptions, + SyncOptions, + TruncateOptions, +} from '.'; +import type { + AbstractConnection, + GetConnectionOptions, +} from './abstract-dialect/connection-manager.js'; +import { normalizeDataType, validateDataType } from './abstract-dialect/data-types-utils.js'; +import type { AbstractDataType } from './abstract-dialect/data-types.js'; +import type { AbstractDialect, ConnectionOptions } from './abstract-dialect/dialect.js'; +import type { EscapeOptions } from './abstract-dialect/query-generator-typescript.js'; +import type { QiDropAllSchemasOptions } from './abstract-dialect/query-interface.types.js'; +import type { AbstractQuery } from './abstract-dialect/query.js'; +import type { AcquireConnectionOptions } from './abstract-dialect/replication-pool.js'; +import { ReplicationPool } from './abstract-dialect/replication-pool.js'; +import { initDecoratedAssociations } from './decorators/legacy/associations.js'; +import { initDecoratedModel } from './decorators/shared/model.js'; +import { ConnectionAcquireTimeoutError } from './errors/connection/connection-acquire-timeout-error.js'; +import { + legacyBuildAddAnyHook, + legacyBuildAddHook, + legacyBuildHasHook, + legacyBuildRemoveHook, + legacyBuildRunHook, +} from './hooks-legacy.js'; +import type { AsyncHookReturn, HookHandler } from './hooks.js'; +import { HookHandlerBuilder } from './hooks.js'; +import { listenForModelDefinition, removeModelDefinition } from './model-definition.js'; +import type { ModelHooks } from './model-hooks.js'; +import { validModelHooks } from './model-hooks.js'; +import { setTransactionFromCls } from './model-internals.js'; +import { ModelSetView } from './model-set-view.js'; +import { + EPHEMERAL_SEQUELIZE_OPTIONS, + PERSISTED_SEQUELIZE_OPTIONS, + importDialect, +} from './sequelize.internals.js'; +import type { QueryRawOptions } from './sequelize.js'; +import { Sequelize } from './sequelize.js'; +import type { NormalizedOptions, Options } from './sequelize.types.js'; +import type { ManagedTransactionOptions, TransactionOptions } from './transaction.js'; +import { + Transaction, + TransactionNestMode, + TransactionType, + assertTransactionIsCompatibleWithOptions, + normalizeTransactionOptions, +} from './transaction.js'; +import { getIntersection } from './utils/array.js'; +import { normalizeReplicationConfig } from './utils/connection-options.js'; +import * as Deprecations from './utils/deprecations.js'; +import { showAllToListSchemas } from './utils/deprecations.js'; +import { removeUndefined, untypedMultiSplitObject } from './utils/object.js'; + +export interface SequelizeHooks extends ModelHooks { + /** + * A hook that is run at the start of {@link Sequelize#define} and {@link Model.init} + */ + beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; + + /** + * A hook that is run at the end of {@link Sequelize#define} and {@link Model.init} + */ + afterDefine(model: ModelStatic): void; + + /** + * A hook that is run before a connection is created + */ + beforeConnect(config: ConnectionOptions): AsyncHookReturn; + + /** + * A hook that is run after a connection is created + */ + afterConnect(connection: AbstractConnection, config: ConnectionOptions): AsyncHookReturn; + + /** + * A hook that is run before a connection is disconnected + */ + beforeDisconnect(connection: AbstractConnection): AsyncHookReturn; + + /** + * A hook that is run after a connection is disconnected + */ + afterDisconnect(connection: unknown): AsyncHookReturn; + beforeQuery(options: QueryOptions, query: AbstractQuery): AsyncHookReturn; + afterQuery(options: QueryOptions, query: AbstractQuery): AsyncHookReturn; + + /** + * A hook that is run at the start of {@link Sequelize#sync} + */ + beforeBulkSync(options: SyncOptions): AsyncHookReturn; + + /** + * A hook that is run at the end of {@link Sequelize#sync} + */ + afterBulkSync(options: SyncOptions): AsyncHookReturn; + + /** + * A hook that is run before a connection to the pool + */ + beforePoolAcquire(options?: AcquireConnectionOptions): AsyncHookReturn; + + /** + * A hook that is run after a connection to the pool + */ + afterPoolAcquire( + connection: AbstractConnection, + options?: AcquireConnectionOptions, + ): AsyncHookReturn; +} + +export interface StaticSequelizeHooks { + /** + * A hook that is run at the beginning of the creation of a Sequelize instance. + */ + beforeInit(options: Options): void; + + /** + * A hook that is run at the end of the creation of a Sequelize instance. + */ + afterInit(sequelize: Sequelize): void; +} + +export interface SequelizeTruncateOptions extends TruncateOptions { + /** + * Most dialects will not allow you to truncate a table while other tables have foreign key references to it (even if they are empty). + * This option will disable those checks while truncating all tables, and re-enable them afterwards. + * + * This option is currently only supported for MySQL, SQLite, and MariaDB. + * + * Postgres can use {@link TruncateOptions.cascade} to achieve a similar goal. + * + * If you're experiencing this problem in other dialects, consider using {@link Sequelize.destroyAll} instead. + */ + withoutForeignKeyChecks?: boolean; +} + +export interface WithConnectionOptions extends PartialBy { + /** + * Close the connection when the callback finishes instead of returning it to the pool. + * This is useful if you want to ensure that the connection is not reused, + * for example if you ran queries that changed session options. + */ + destroyConnection?: boolean; +} + +const staticSequelizeHooks = new HookHandlerBuilder([ + 'beforeInit', + 'afterInit', +]); + +const instanceSequelizeHooks = new HookHandlerBuilder>([ + 'beforeQuery', + 'afterQuery', + 'beforeBulkSync', + 'afterBulkSync', + 'beforeConnect', + 'afterConnect', + 'beforeDisconnect', + 'afterDisconnect', + 'beforeDefine', + 'afterDefine', + 'beforePoolAcquire', + 'afterPoolAcquire', + ...validModelHooks, +]); + +type TransactionCallback = (t: Transaction) => PromiseLike | T; +type SessionCallback = (connection: AbstractConnection) => PromiseLike | T; + +export const SUPPORTED_DIALECTS = Object.freeze([ + 'mysql', + 'postgres', + 'sqlite3', + 'mariadb', + 'mssql', + 'mariadb', + 'mssql', + 'db2', + 'snowflake', + 'ibmi', +] as const); + +// DO NOT MAKE THIS CLASS PUBLIC! +/** + * This is a temporary class used to progressively migrate the Sequelize class to TypeScript by slowly moving its functions here. + * Always use {@link Sequelize} instead. + */ +export abstract class SequelizeTypeScript { + // created by the Sequelize subclass. Will eventually be migrated here. + readonly dialect: Dialect; + readonly options: NormalizedOptions; + + /** + * The options that were used to create this Sequelize instance. + * These are an unmodified copy of the options passed to the constructor. + * They are not normalized or validated. + * + * Mostly available for cloning the Sequelize instance. + * For other uses, we recommend using {@link options} instead. + */ + readonly rawOptions: Options; + + static get hooks(): HookHandler { + return staticSequelizeHooks.getFor(this); + } + + static addHook = legacyBuildAddAnyHook(staticSequelizeHooks); + static removeHook = legacyBuildRemoveHook(staticSequelizeHooks); + static hasHook = legacyBuildHasHook(staticSequelizeHooks); + static hasHooks = legacyBuildHasHook(staticSequelizeHooks); + static runHooks = legacyBuildRunHook(staticSequelizeHooks); + + static beforeInit = legacyBuildAddHook(staticSequelizeHooks, 'beforeInit'); + static afterInit = legacyBuildAddHook(staticSequelizeHooks, 'afterInit'); + + get hooks(): HookHandler> { + return instanceSequelizeHooks.getFor(this); + } + + addHook = legacyBuildAddAnyHook(instanceSequelizeHooks); + removeHook = legacyBuildRemoveHook(instanceSequelizeHooks); + hasHook = legacyBuildHasHook(instanceSequelizeHooks); + hasHooks = legacyBuildHasHook(instanceSequelizeHooks); + runHooks = legacyBuildRunHook(instanceSequelizeHooks); + + beforeQuery = legacyBuildAddHook(instanceSequelizeHooks, 'beforeQuery'); + afterQuery = legacyBuildAddHook(instanceSequelizeHooks, 'afterQuery'); + + beforeBulkSync = legacyBuildAddHook(instanceSequelizeHooks, 'beforeBulkSync'); + afterBulkSync = legacyBuildAddHook(instanceSequelizeHooks, 'afterBulkSync'); + + beforeConnect = legacyBuildAddHook(instanceSequelizeHooks, 'beforeConnect'); + afterConnect = legacyBuildAddHook(instanceSequelizeHooks, 'afterConnect'); + + beforeDisconnect = legacyBuildAddHook(instanceSequelizeHooks, 'beforeDisconnect'); + afterDisconnect = legacyBuildAddHook(instanceSequelizeHooks, 'afterDisconnect'); + + beforeDefine = legacyBuildAddHook(instanceSequelizeHooks, 'beforeDefine'); + afterDefine = legacyBuildAddHook(instanceSequelizeHooks, 'afterDefine'); + + beforePoolAcquire = legacyBuildAddHook(instanceSequelizeHooks, 'beforePoolAcquire'); + afterPoolAcquire = legacyBuildAddHook(instanceSequelizeHooks, 'afterPoolAcquire'); + + beforeValidate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeValidate'); + afterValidate = legacyBuildAddHook(instanceSequelizeHooks, 'afterValidate'); + validationFailed = legacyBuildAddHook(instanceSequelizeHooks, 'validationFailed'); + + beforeCreate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeCreate'); + afterCreate = legacyBuildAddHook(instanceSequelizeHooks, 'afterCreate'); + + beforeDestroy = legacyBuildAddHook(instanceSequelizeHooks, 'beforeDestroy'); + afterDestroy = legacyBuildAddHook(instanceSequelizeHooks, 'afterDestroy'); + + beforeRestore = legacyBuildAddHook(instanceSequelizeHooks, 'beforeRestore'); + afterRestore = legacyBuildAddHook(instanceSequelizeHooks, 'afterRestore'); + + beforeUpdate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeUpdate'); + afterUpdate = legacyBuildAddHook(instanceSequelizeHooks, 'afterUpdate'); + + beforeUpsert = legacyBuildAddHook(instanceSequelizeHooks, 'beforeUpsert'); + afterUpsert = legacyBuildAddHook(instanceSequelizeHooks, 'afterUpsert'); + + beforeSave = legacyBuildAddHook(instanceSequelizeHooks, 'beforeSave'); + afterSave = legacyBuildAddHook(instanceSequelizeHooks, 'afterSave'); + + beforeBulkCreate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeBulkCreate'); + afterBulkCreate = legacyBuildAddHook(instanceSequelizeHooks, 'afterBulkCreate'); + + beforeBulkDestroy = legacyBuildAddHook(instanceSequelizeHooks, 'beforeBulkDestroy'); + afterBulkDestroy = legacyBuildAddHook(instanceSequelizeHooks, 'afterBulkDestroy'); + + beforeBulkRestore = legacyBuildAddHook(instanceSequelizeHooks, 'beforeBulkRestore'); + afterBulkRestore = legacyBuildAddHook(instanceSequelizeHooks, 'afterBulkRestore'); + + beforeBulkUpdate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeBulkUpdate'); + afterBulkUpdate = legacyBuildAddHook(instanceSequelizeHooks, 'afterBulkUpdate'); + + beforeCount = legacyBuildAddHook(instanceSequelizeHooks, 'beforeCount'); + + beforeFind = legacyBuildAddHook(instanceSequelizeHooks, 'beforeFind'); + beforeFindAfterExpandIncludeAll = legacyBuildAddHook( + instanceSequelizeHooks, + 'beforeFindAfterExpandIncludeAll', + ); + + beforeFindAfterOptions = legacyBuildAddHook(instanceSequelizeHooks, 'beforeFindAfterOptions'); + afterFind = legacyBuildAddHook(instanceSequelizeHooks, 'afterFind'); + + beforeSync = legacyBuildAddHook(instanceSequelizeHooks, 'beforeSync'); + afterSync = legacyBuildAddHook(instanceSequelizeHooks, 'afterSync'); + + beforeAssociate = legacyBuildAddHook(instanceSequelizeHooks, 'beforeAssociate'); + afterAssociate = legacyBuildAddHook(instanceSequelizeHooks, 'afterAssociate'); + + readonly #transactionCls: AsyncLocalStorage | undefined; + #databaseVersion: string | undefined; + + /** + * The QueryInterface instance, dialect dependant. + */ + get queryInterface(): Dialect['queryInterface'] { + return this.dialect.queryInterface; + } + + /** + * The QueryGenerator instance, dialect dependant. + */ + get queryGenerator(): Dialect['queryGenerator'] { + return this.dialect.queryGenerator; + } + + get connectionManager(): never { + throw new Error(`Accessing the connection manager is unlikely to be necessary anymore. +If you need to access the pool, you can access it directly through \`sequelize.pool\`. +If you really need to access the connection manager, access it through \`sequelize.dialect.connectionManager\`.`); + } + + readonly #models = new Set(); + readonly models = new ModelSetView(this, this.#models); + #isClosed: boolean = false; + readonly pool: ReplicationPool, ConnectionOptions>; + + get modelManager(): never { + throw new Error('Sequelize#modelManager was removed. Use Sequelize#models instead.'); + } + + /** + * Instantiates sequelize. + * + * The options to connect to the database are specific to your dialect. + * Please refer to the documentation of your dialect on https://sequelize.org to learn about the options you can use. + * + * @param options The option bag. + * @example + * import { PostgresDialect } from '@sequelize/postgres'; + * + * // with database, username, and password in the options object + * const sequelize = new Sequelize({ database, user, password, dialect: PostgresDialect }); + * + * @example + * // with url + * import { MySqlDialect } from '@sequelize/mysql'; + * + * const sequelize = new Sequelize({ + * dialect: MySqlDialect, + * url: 'mysql://localhost:3306/database', + * }) + * + * @example + * // option examples + * import { MsSqlDialect } from '@sequelize/mssql'; + * + * const sequelize = new Sequelize('database', 'username', 'password', { + * // the dialect of the database + * // It is a Dialect class exported from the dialect package + * dialect: MsSqlDialect, + * + * // custom host; + * host: 'my.server.tld', + * // for postgres, you can also specify an absolute path to a directory + * // containing a UNIX socket to connect over + * // host: '/sockets/psql_sockets'. + * + * // custom port; + * port: 12345, + * + * // disable logging or provide a custom logging function; default: console.log + * logging: false, + * + * // This option is specific to MySQL and MariaDB + * socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock', + * + * // the storage engine for sqlite + * // - default ':memory:' + * storage: 'path/to/database.sqlite', + * + * // disable inserting undefined values as NULL + * // - default: false + * omitNull: true, + * + * // A flag that defines if connection should be over ssl or not + * // Dialect-dependent, check the dialect documentation + * ssl: true, + * + * // Specify options, which are used when sequelize.define is called. + * // The following example: + * // define: { timestamps: false } + * // is basically the same as: + * // Model.init(attributes, { timestamps: false }); + * // sequelize.define(name, attributes, { timestamps: false }); + * // so defining the timestamps for each model will be not necessary + * define: { + * underscored: false, + * freezeTableName: false, + * charset: 'utf8', + * collate: 'utf8_general_ci' + * timestamps: true + * }, + * + * // similar for sync: you can define this to always force sync for models + * sync: { force: true }, + * + * // pool configuration used to pool database connections + * pool: { + * max: 5, + * idle: 30000, + * acquire: 60000, + * }, + * + * // isolation level of each transaction + * // defaults to dialect default + * isolationLevel: IsolationLevel.REPEATABLE_READ + * }) + */ + constructor(options: Options) { + if (arguments.length > 2) { + throw new Error( + 'The Sequelize constructor no longer accepts multiple arguments. Please use an options object instead.', + ); + } + + if (isString(options)) { + throw new Error(`The Sequelize constructor no longer accepts a string as the first argument. Please use the "url" option instead. + +Example for Postgres: + +new Sequelize({ + dialect: PostgresDialect, + url: 'postgres://user:pass@localhost/dbname', +});`); + } + + // @ts-expect-error -- sanity check + if (options.pool === false) { + throw new Error( + 'Setting the "pool" option to "false" is not supported since Sequelize 4. To disable the pool, set the "pool"."max" option to 1.', + ); + } + + // @ts-expect-error -- sanity check + if (options.logging === true) { + throw new Error( + 'The "logging" option must be set to a function or false, not true. If you want to log all queries, set it to `console.log`.', + ); + } + + // @ts-expect-error -- sanity check + if (options.operatorsAliases) { + throw new Error( + 'String based operators have been removed. Please use Symbol operators, read more at https://sequelize.org/docs/v7/core-concepts/model-querying-basics/#deprecated-operator-aliases', + ); + } + + if ('dialectModulePath' in options) { + throw new Error( + 'The "dialectModulePath" option has been removed, as it is not compatible with bundlers. Please refer to the documentation of your dialect at https://sequelize.org to learn about the alternative.', + ); + } + + if ('dialectModule' in options) { + throw new Error( + 'The "dialectModule" option has been replaced with an equivalent option specific to your dialect. Please refer to the documentation of your dialect at https://sequelize.org to learn about the alternative.', + ); + } + + if ('typeValidation' in options) { + throw new Error( + 'The typeValidation has been renamed to noTypeValidation, and is false by default', + ); + } + + if (!options.dialect) { + throw new Error('The "dialect" option must be explicitly supplied since Sequelize 4'); + } + + // Synchronize ModelDefinition map with the registered models set + listenForModelDefinition(model => { + const modelName = model.modelDefinition.modelName; + + // @ts-expect-error -- remove this disable once all sequelize.js has been migrated to TS + if (model.sequelize === (this as Sequelize)) { + const existingModel = this.models.get(modelName); + if (existingModel) { + this.#models.delete(existingModel); + // TODO: require the user to explicitly remove the previous model first. + // throw new Error(`A model with the name ${inspect(model.name)} was already registered in this Sequelize instance.`); + } + + this.#models.add(model); + } + }); + + Sequelize.hooks.runSync('beforeInit', options); + + this.rawOptions = freezeDeep(cloneDeepPlainValues(options, true)); + + const DialectClass: typeof AbstractDialect = isString(options.dialect) + ? importDialect(options.dialect) + : (options.dialect as unknown as typeof AbstractDialect); + + const nonUndefinedOptions = removeUndefined(options); + + if (options.hooks) { + this.hooks.addListeners(options.hooks); + } + + const [persistedSequelizeOptions, remainingOptions] = splitObject( + nonUndefinedOptions, + PERSISTED_SEQUELIZE_OPTIONS, + ); + + const dialectOptionNames = DialectClass.getSupportedOptions(); + const connectionOptionNames = [...DialectClass.getSupportedConnectionOptions(), 'url']; + const allSequelizeOptionNames = [ + ...PERSISTED_SEQUELIZE_OPTIONS, + // "url" is a special case. It's a connection option, but it's one that Sequelize accepts, instead of the dialect. + ...EPHEMERAL_SEQUELIZE_OPTIONS.filter(option => option !== 'url'), + ]; + + const allDialectOptionNames = [...dialectOptionNames, ...connectionOptionNames]; + + const conflictingOptions = getIntersection(allSequelizeOptionNames, allDialectOptionNames); + if (conflictingOptions.length > 0) { + throw new Error( + `The following options from ${DialectClass.name} conflict with built-in Sequelize options: ${join( + map(conflictingOptions, option => red(option)), + ', ', + )}. +This is a bug in the dialect implementation itself, not in the user's code. +Please rename these options to a name that is not already used by Sequelize.`, + ); + } + + const [{ dialectOptions, connectionOptions }, unseenKeys] = untypedMultiSplitObject( + remainingOptions, + { + dialectOptions: dialectOptionNames, + connectionOptions: connectionOptionNames, + }, + ); + + for (const key of EPHEMERAL_SEQUELIZE_OPTIONS) { + unseenKeys.delete(key); + } + + if (unseenKeys.size > 0) { + const caseInsensitiveEnComparator = localizedStringComparator('en', SortDirection.ASC, { + sensitivity: 'base', + }); + + throw new Error( + `The following options are not recognized by Sequelize nor ${DialectClass.name}: ${join( + map(unseenKeys, option => red(option)), + ', ', + )}. + +Sequelize accepts the following options: ${allSequelizeOptionNames + .sort(caseInsensitiveEnComparator) + .map(option => cyan(option)) + .join(', ')}. + +${DialectClass.name} accepts the following options (in addition to the Sequelize options): ${[ + ...dialectOptionNames, + ] + .sort(caseInsensitiveEnComparator) + .map(option => cyan(option)) + .join(', ')}. +${DialectClass.name} options can be set at the root of the option bag, like Sequelize options. + +The following options can be used to configure the connection to the database: ${connectionOptionNames + .sort(caseInsensitiveEnComparator) + .map(option => cyan(option)) + .join(', ')}. +Connection options can be used at the root of the option bag, in the "replication" option, and can be modified by the "beforeConnect" hook. +`, + ); + } + + // @ts-expect-error -- The Dialect class must respect this interface + this.dialect = new DialectClass(this, dialectOptions); + + this.options = freezeDeep({ + define: {}, + query: {}, + sync: {}, + timezone: '+00:00', + keepDefaultTimezone: false, + logging: false, + omitNull: false, + // TODO [>7]: remove this option + quoteIdentifiers: true, + retry: { + max: 5, + match: ['SQLITE_BUSY: database is locked'], + }, + transactionType: TransactionType.DEFERRED, + isolationLevel: undefined, + noTypeValidation: false, + benchmark: false, + minifyAliases: false, + logQueryParameters: false, + disableClsTransactions: false, + defaultTransactionNestMode: TransactionNestMode.reuse, + defaultTimestampPrecision: 6, + nullJsonStringification: 'json', + ...persistedSequelizeOptions, + replication: normalizeReplicationConfig( + this.dialect, + connectionOptions as RawConnectionOptions, + options.replication, + ), + }); + + if (options.databaseVersion) { + this.setDatabaseVersion(options.databaseVersion); + } + + if (!this.options.disableClsTransactions) { + this.#transactionCls = new AsyncLocalStorage(); + } + + // if (this.options.define.hooks) { + // throw new Error(`The "define" Sequelize option cannot be used to add hooks to all models. Please remove the "hooks" property from the "define" option you passed to the Sequelize constructor. + // Instead of using this option, you can listen to the same event on all models by adding the listener to the Sequelize instance itself, since all model hooks are forwarded to the Sequelize instance.`); + // } + + if (this.options.quoteIdentifiers === false) { + Deprecations.alwaysQuoteIdentifiers(); + } + + if (!this.dialect.supports.globalTimeZoneConfig && this.options.timezone !== '+00:00') { + throw new Error( + `Setting a custom timezone is not supported by ${this.dialect.name}, dates are always returned as UTC. Please remove the custom timezone option.`, + ); + } + + this.pool = new ReplicationPool, ConnectionOptions>({ + pool: { + max: 5, + min: 0, + idle: 10_000, + acquire: 60_000, + evict: 1000, + maxUses: Infinity, + ...(options.pool ? removeUndefined(options.pool) : undefined), + }, + connect: async (connectOptions: ConnectionOptions): Promise> => { + if (this.isClosed()) { + throw new Error( + 'sequelize.close was called, new connections cannot be established. If you did not mean for the Sequelize instance to be closed permanently, prefer using sequelize.pool.destroyAllNow instead.', + ); + } + + const clonedConnectOptions = cloneDeepPlainValues(connectOptions, true); + await this.hooks.runAsync('beforeConnect', clonedConnectOptions); + + const connection = await this.dialect.connectionManager.connect(clonedConnectOptions); + await this.hooks.runAsync('afterConnect', connection, clonedConnectOptions); + + if (!this.getDatabaseVersionIfExist()) { + await this.#initializeDatabaseVersion(connection); + } + + return connection; + }, + disconnect: async (connection: Connection): Promise => { + await this.hooks.runAsync('beforeDisconnect', connection); + await this.dialect.connectionManager.disconnect(connection); + await this.hooks.runAsync('afterDisconnect', connection); + }, + validate: (connection: Connection): boolean => { + if (options.pool?.validate) { + return options.pool.validate(connection); + } + + return this.dialect.connectionManager.validate(connection); + }, + beforeAcquire: async (acquireOptions: AcquireConnectionOptions): Promise => { + return this.hooks.runAsync('beforePoolAcquire', acquireOptions); + }, + afterAcquire: async ( + connection: Connection, + acquireOptions: AcquireConnectionOptions, + ) => { + return this.hooks.runAsync('afterPoolAcquire', connection, acquireOptions); + }, + timeoutErrorClass: ConnectionAcquireTimeoutError, + readConfig: this.options.replication.read, + writeConfig: this.options.replication.write, + }); + + if (options.models) { + this.addModels(options.models); + } + + // TODO: remove this cast once sequelize-typescript and sequelize have been fully merged + Sequelize.hooks.runSync('afterInit', this as unknown as Sequelize); + } + + #databaseVersionPromise: Promise | null = null; + async #initializeDatabaseVersion(connection: Connection) { + if (this.#databaseVersion) { + return; + } + + if (this.#databaseVersionPromise) { + await this.#databaseVersionPromise; + + return; + } + + this.#databaseVersionPromise = (async () => { + try { + const version = await this.fetchDatabaseVersion({ + logging: false, + connection, + }); + + const parsedVersion = semver.coerce(version)?.version || version; + + this.setDatabaseVersion( + semver.valid(parsedVersion) ? parsedVersion : this.dialect.minimumDatabaseVersion, + ); + } finally { + this.#databaseVersionPromise = null; + } + })(); + + await this.#databaseVersionPromise; + } + + /** + * Close all connections used by this sequelize instance, and free all references so the instance can be garbage collected. + * + * Normally this is done on process exit, so you only need to call this method if you are creating multiple instances, and want + * to garbage collect some of them. + * + * @returns + */ + async close() { + this.#isClosed = true; + + await this.pool.destroyAllNow(); + } + + isClosed() { + return this.#isClosed; + } + + addModels(models: ModelStatic[]) { + const registeredModels = models.filter(model => + initDecoratedModel( + model, + // @ts-expect-error -- remove once this class has been merged back with the Sequelize class + this, + ), + ); + + for (const model of registeredModels) { + initDecoratedAssociations( + model, + // @ts-expect-error -- remove once this class has been merged back with the Sequelize class + this, + ); + } + } + + removeAllModels() { + for (const model of this.#models) { + removeModelDefinition(model); + } + + this.#models.clear(); + } + + /** + * Escape value to be used in raw SQL. + * + * If you are using this to use the value in a {@link sql.literal}, consider using {@link sql} instead, which automatically + * escapes interpolated values. + * + * @param value The value to escape + * @param options + */ + escape(value: unknown, options?: EscapeOptions) { + return this.dialect.queryGenerator.escape(value, options); + } + + /** + * Returns the transaction that is associated to the current asynchronous operation. + * This method returns undefined if no transaction is active in the current asynchronous operation, + * or if the Sequelize "disableClsTransactions" option is true. + */ + getCurrentClsTransaction(): Transaction | undefined { + return this.#transactionCls?.getStore(); + } + + /** + * Start a managed transaction: Sequelize will create a transaction, pass it to your callback, and commit + * it once the promise returned by your callback resolved, or execute a rollback if the promise rejects. + * + * ```ts + * try { + * await sequelize.transaction(() => { + * const user = await User.findOne(...); + * await user.update(...); + * }); + * + * // By now, the transaction has been committed + * } catch { + * // If the transaction callback threw an error, the transaction has been rolled back + * } + * ``` + * + * By default, Sequelize uses AsyncLocalStorage to automatically pass the transaction to all queries executed inside the callback (unless you already pass one or set the `transaction` option to null). + * This can be disabled by setting the Sequelize "disableClsTransactions" option to true. You will then need to pass transactions to your queries manually. + * + * ```ts + * const sequelize = new Sequelize({ + * // ... + * disableClsTransactions: true, + * }) + * + * await sequelize.transaction(transaction => { + * // transactions are not automatically passed around anymore, you need to do it yourself: + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * }); + * ``` + * + * If you want to manage your transaction yourself, use {@link startUnmanagedTransaction}. + * + * @param callback Async callback during which the transaction will be active + */ + transaction(callback: TransactionCallback): Promise; + /** + * @param options Transaction Options + * @param callback Async callback during which the transaction will be active + */ + transaction(options: ManagedTransactionOptions, callback: TransactionCallback): Promise; + async transaction( + optionsOrCallback: ManagedTransactionOptions | TransactionCallback, + maybeCallback?: TransactionCallback, + ): Promise { + let options: ManagedTransactionOptions; + let callback: TransactionCallback; + if (typeof optionsOrCallback === 'function') { + callback = optionsOrCallback; + options = {}; + } else { + callback = maybeCallback!; + options = optionsOrCallback; + } + + if (!callback) { + throw new Error( + 'sequelize.transaction requires a callback. If you wish to start an unmanaged transaction, please use sequelize.startUnmanagedTransaction instead', + ); + } + + const nestMode: TransactionNestMode = + options.nestMode ?? this.options.defaultTransactionNestMode; + + // @ts-expect-error -- will be fixed once this class has been merged back with the Sequelize class + const normalizedOptions = normalizeTransactionOptions(this, options); + + if (nestMode === TransactionNestMode.separate) { + delete normalizedOptions.transaction; + } else { + // @ts-expect-error -- will be fixed once this class has been merged back with the Sequelize class + setTransactionFromCls(normalizedOptions, this); + + // in reuse & savepoint mode, + // we use the same transaction, so we need to make sure it's compatible with the requested options + if (normalizedOptions.transaction) { + assertTransactionIsCompatibleWithOptions(normalizedOptions.transaction, normalizedOptions); + } + } + + const transaction = + nestMode === TransactionNestMode.reuse && normalizedOptions.transaction + ? normalizedOptions.transaction + : new Transaction( + // @ts-expect-error -- will be fixed once this class has been merged back with the Sequelize class + this, + normalizedOptions, + ); + + const isReusedTransaction = transaction === normalizedOptions.transaction; + + const wrappedCallback = async () => { + // We did not create this transaction, so we're not responsible for managing it. + if (isReusedTransaction) { + return callback(transaction); + } + + await transaction.prepareEnvironment(); + + let result; + try { + result = await callback(transaction); + } catch (error) { + try { + await transaction.rollback(); + } catch { + // ignore, because 'rollback' will already print the error before killing the connection + } + + throw error; + } + + await transaction.commit(); + + return result; + }; + + const cls = this.#transactionCls; + if (!cls) { + return wrappedCallback(); + } + + return cls.run(transaction, wrappedCallback); + } + + /** + * We highly recommend using {@link Sequelize#transaction} instead. + * If you really want to use the manual solution, don't forget to commit or rollback your transaction once you are done with it. + * + * Transactions started by this method are not automatically passed to queries. You must pass the transaction object manually, + * even if the Sequelize "disableClsTransactions" option is false. + * + * @example + * ```ts + * try { + * const transaction = await sequelize.startUnmanagedTransaction(); + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * await transaction.commit(); + * } catch(err) { + * await transaction.rollback(); + * } + * ``` + * + * @param options + */ + async startUnmanagedTransaction(options?: TransactionOptions): Promise { + const transaction = new Transaction( + // @ts-expect-error -- remove once this class has been merged back with the Sequelize class + this, + options, + ); + + await transaction.prepareEnvironment(); + + return transaction; + } + + /** + * A slower alternative to {@link truncate} that uses DELETE FROM instead of TRUNCATE, + * but which works with foreign key constraints in dialects that don't support TRUNCATE CASCADE (postgres), + * or temporarily disabling foreign key constraints (mysql, mariadb, sqlite). + * + * @param options + */ + async destroyAll(options?: Omit) { + const sortedModels = this.models.getModelsTopoSortedByForeignKey(); + const models: Iterable = sortedModels ?? this.models; + + // It does not make sense to apply a limit to something that will run on all models + if (options && 'limit' in options) { + throw new Error('sequelize.destroyAll does not support the limit option.'); + } + + if (options && 'truncate' in options) { + throw new Error( + 'sequelize.destroyAll does not support the truncate option. Use sequelize.truncate instead.', + ); + } + + for (const model of models) { + // eslint-disable-next-line no-await-in-loop + await model.destroy({ ...options, where: {} }); + } + } + + /** + * Truncate all models registered in this instance. + * This is done by calling {@link Model.truncate} on each model. + * + * @param options The options passed to {@link Model.truncate}, plus "withoutForeignKeyChecks". + */ + async truncate(options?: SequelizeTruncateOptions): Promise { + const sortedModels = this.models.getModelsTopoSortedByForeignKey(); + const models: ModelStatic[] = sortedModels ?? [...this.models]; + + const hasCyclicDependencies = sortedModels == null; + + if (hasCyclicDependencies && !options?.cascade && !options?.withoutForeignKeyChecks) { + throw new Error( + 'Sequelize#truncate: Some of your models have cyclic references (foreign keys). You need to use the "cascade" or "withoutForeignKeyChecks" options to be able to delete rows from models that have cyclic references.', + ); + } + + if (options?.withoutForeignKeyChecks) { + if (!this.dialect.supports.constraints.foreignKeyChecksDisableable) { + throw new Error( + `Sequelize#truncate: ${this.dialect.name} does not support disabling foreign key checks. The "withoutForeignKeyChecks" option cannot be used.`, + ); + } + + // Dialects that don't support cascade will throw if a foreign key references a table that is truncated, + // even if there are no actual rows in the referencing table. To work around this, we disable foreign key. + return this.queryInterface.withoutForeignKeyChecks(options, async connection => { + const truncateOptions = { ...options, connection }; + + await Promise.all(models.map(async model => model.truncate(truncateOptions))); + }); + } + + if (options?.cascade) { + for (const model of models) { + // If cascade is enabled, we can assume there are foreign keys between the models, so we must truncate them sequentially. + // eslint-disable-next-line no-await-in-loop + await model.truncate(options); + } + + return; + } + + await Promise.all(models.map(async model => model.truncate(options))); + } + + async withConnection(options: WithConnectionOptions, callback: SessionCallback): Promise; + async withConnection(callback: SessionCallback): Promise; + async withConnection( + optionsOrCallback: SessionCallback | WithConnectionOptions, + maybeCallback?: SessionCallback, + ): Promise { + let options: WithConnectionOptions; + let callback: SessionCallback; + if (typeof optionsOrCallback === 'function') { + callback = optionsOrCallback; + options = { type: 'write' }; + } else { + callback = maybeCallback!; + options = { type: 'write', ...optionsOrCallback }; + } + + const connection = await this.pool.acquire(options as GetConnectionOptions); + + try { + return await callback(connection); + } finally { + if (options.destroyConnection) { + await this.pool.destroy(connection); + } else { + this.pool.release(connection); + } + } + } + + /** + * Alias of {@link AbstractQueryInterface#createSchema} + * + * @param schema Name of the schema + * @param options + */ + async createSchema(schema: string, options?: CreateSchemaOptions): Promise { + return this.queryInterface.createSchema(schema, options); + } + + /** + * Alias of {@link AbstractQueryInterface#showAllSchemas} + * + * @deprecated Use {@link AbstractQueryInterface#listSchemas} instead + * @param options + */ + async showAllSchemas(options?: QiListSchemasOptions) { + showAllToListSchemas(); + + return this.queryInterface.listSchemas(options); + } + + /** + * Alias of {@link AbstractQueryInterface#dropSchema} + * + * @param schema + * @param options + */ + async dropSchema(schema: string, options?: QueryRawOptions) { + return this.queryInterface.dropSchema(schema, options); + } + + /** + * Alias of {@link AbstractQueryInterface#dropAllSchemas} + * + * @param options + */ + async dropAllSchemas(options?: QiDropAllSchemasOptions) { + return this.queryInterface.dropAllSchemas(options); + } + + /** + * Throws if the database version hasn't been loaded yet. + * It is automatically loaded the first time Sequelize connects to your database. + * + * You can use {@link Sequelize#authenticate} to cause a first connection. + * + * @returns current version of the dialect that is internally loaded + */ + getDatabaseVersion(): string { + const databaseVersion = this.getDatabaseVersionIfExist(); + + if (databaseVersion == null) { + throw new Error( + 'The current database version is unknown. Please call `sequelize.authenticate()` first to fetch it, or manually configure it through options.', + ); + } + + return databaseVersion; + } + + getDatabaseVersionIfExist(): string | null { + return this.#databaseVersion || null; + } + + setDatabaseVersion(version: string) { + try { + if (semver.lt(version, this.dialect.minimumDatabaseVersion)) { + console.warn( + `Database ${this.dialect.name} version ${inspect(version)} is not supported. The minimum supported version is ${this.dialect.minimumDatabaseVersion}.`, + ); + + Deprecations.unsupportedEngine(); + } + } catch (error) { + console.warn( + `Could not validate the database version, as it is not a valid semver version: ${version}.`, + ); + + console.warn(error); + } + + this.#databaseVersion = version; + } + + /** + * Alias of {@link AbstractQueryInterface#fetchDatabaseVersion} + * + * @param options + */ + async fetchDatabaseVersion(options?: QueryRawOptions) { + return this.queryInterface.fetchDatabaseVersion(options); + } + + /** + * Validate a value against a field specification + * + * @param value The value to validate + * @param type The DataType to validate against + */ + validateValue(value: unknown, type: DataType) { + if (this.options.noTypeValidation || isNullish(value)) { + return; + } + + if (isString(type)) { + return; + } + + type = this.normalizeDataType(type); + + const error = validateDataType(value, type); + if (error) { + throw error; + } + } + + normalizeDataType(Type: string): string; + normalizeDataType(Type: DataTypeClassOrInstance): AbstractDataType; + normalizeDataType(Type: string | DataTypeClassOrInstance): string | AbstractDataType; + normalizeDataType(Type: string | DataTypeClassOrInstance): string | AbstractDataType { + return normalizeDataType(Type, this.dialect); + } +} diff --git a/packages/core/src/sequelize.d.ts b/packages/core/src/sequelize.d.ts new file mode 100644 index 000000000000..3737a2b35e1b --- /dev/null +++ b/packages/core/src/sequelize.d.ts @@ -0,0 +1,676 @@ +import type { Options as RetryAsPromisedOptions } from 'retry-as-promised'; +import type { DataTypes, Op, Options, QueryTypes } from '.'; +import type { DataType } from './abstract-dialect/data-types.js'; +import type { AbstractDialect, ConnectionOptions } from './abstract-dialect/dialect.js'; +import type { + ColumnsDescription, + RawConstraintDescription, +} from './abstract-dialect/query-interface.types'; +import type { + BaseSqlExpression, + DynamicSqlExpression, +} from './expression-builders/base-sql-expression.js'; +import type { cast } from './expression-builders/cast.js'; +import type { col } from './expression-builders/col.js'; +import type { Fn, fn } from './expression-builders/fn.js'; +import type { json } from './expression-builders/json.js'; +import type { literal } from './expression-builders/literal.js'; +import type { where } from './expression-builders/where.js'; +import type { + AttributeOptions, + Attributes, + ColumnReference, + DropOptions, + Hookable, + Logging, + Model, + ModelAttributes, + ModelOptions, + ModelStatic, + Poolable, + Transactionable, +} from './model'; +import type { SUPPORTED_DIALECTS } from './sequelize-typescript.js'; +import { SequelizeTypeScript } from './sequelize-typescript.js'; + +export type RetryOptions = RetryAsPromisedOptions; + +/** + * Additional options for table altering during sync + */ +export interface SyncAlterOptions { + /** + * Prevents any drop statements while altering a table when set to `false` + */ + drop?: boolean; +} + +/** + * Sync Options + */ +export interface SyncOptions extends Logging, Hookable { + /** + * If force is true, each DAO will do DROP TABLE IF EXISTS ..., before it tries to create its own table + */ + force?: boolean; + + /** + * If alter is true, each DAO will do ALTER TABLE ... CHANGE ... + * Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. + */ + alter?: boolean | SyncAlterOptions; + + /** + * The schema that the tables should be created in. This can be overridden for each table in sequelize.define + */ + schema?: string; + + /** + * An optional parameter to specify the schema search_path (Postgres only) + */ + searchPath?: string; +} + +export interface DefaultSetOptions {} + +/** + * Interface for replication Options in the sequelize constructor + */ +export interface ReplicationOptions { + read: ReadonlyArray>; + write?: RawConnectionOptions; +} + +export type RawConnectionOptions = + | (ConnectionOptions & { url?: string }) + | string; + +export interface NormalizedReplicationOptions { + /** + * If empty, read-replication is not enabled, + * and the "write" pool is used for all queries. + */ + read: ReadonlyArray>; + write: ConnectionOptions; +} + +export type DialectName = (typeof SUPPORTED_DIALECTS)[number]; + +export interface LegacyDialectOptions { + [key: string]: any; + account?: string; + role?: string; + warehouse?: string; + schema?: string; + odbcConnectionString?: string; + charset?: string; + timeout?: number; + options?: string | Record; +} + +export interface SetSessionVariablesOptions extends Omit {} + +export type BindOrReplacements = Record | unknown[]; +type FieldMap = { [key: string]: string }; + +/** + * Options for {@link Sequelize#queryRaw}. + */ +export interface QueryRawOptions extends Logging, Transactionable, Poolable { + /** + * If true, sequelize will not try to format the results of the query, or build an instance of a model from + * the result + */ + raw?: boolean; + + /** + * 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. + */ + type?: string; + + /** + * 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 + * + * @default false + */ + nest?: boolean; + + /** + * Sets the query type to `SELECT` and return a single row + */ + plain?: boolean; + + /** + * Either an object of named parameter bindings in the format `$param` or an array of unnamed + * values to bind to `$1`, `$2`, etc in your SQL. + */ + bind?: BindOrReplacements; + + /** + * A sequelize instance used to build the return instance + */ + instance?: Model; + + /** + * Map returned fields to model's fields if `options.model` or `options.instance` is present. + * Mapping will occur before building the model instance. + */ + mapToModel?: boolean; + + retry?: RetryOptions; + + /** + * Map returned fields to arbitrary names for SELECT query type if `options.fieldMaps` is present. + */ + fieldMap?: FieldMap; + + /** + * If false do not prepend the query with the search_path (Postgres only) + */ + supportsSearchPath?: boolean; +} + +export interface QueryRawOptionsWithType extends QueryRawOptions { + type: T; +} + +export interface QueryRawOptionsWithModel extends QueryRawOptions { + /** + * A sequelize model used to build the returned model instances (used to be called callee) + */ + model: ModelStatic; +} + +/** + * Options for {@link Sequelize#query}. + */ +export interface QueryOptions extends QueryRawOptions { + /** + * Either an object of named parameter replacements in the format `:param` or an array of unnamed + * replacements to replace `?` in your SQL. + */ + replacements?: BindOrReplacements | undefined; +} + +export interface QueryOptionsWithType extends QueryOptions { + type: T; +} + +export interface QueryOptionsWithModel extends QueryOptions { + /** + * A sequelize model used to build the returned model instances (used to be called callee) + */ + model: ModelStatic; +} + +/** + * This is the main class, the entry point to sequelize. To use it, you just need to + * import sequelize: + * + * ```ts + * import { Sequelize } from '@sequelize/core'; + * ``` + * + * In addition to sequelize, the connection library for the dialect you want to use + * should also be installed in your project. You don't need to import it however, as + * sequelize will take care of that. + */ +export class Sequelize< + Dialect extends AbstractDialect = AbstractDialect, +> extends SequelizeTypeScript { + // -------------------- Utilities ------------------------------------------------------------------------ + + /** + * Creates a object representing a database function. This can be used in search queries, both in where and + * order parts, and as default values in column definitions. If you want to refer to columns in your + * function, you should use `sequelize.col`, so that the columns are properly interpreted as columns and + * not a strings. + * + * Convert a user's username to upper case + * ```ts + * instance.update({ + * username: fn('upper', col('username')) + * }) + * ``` + * + * @param fn The function you want to call + * @param args All further arguments will be passed as arguments to the function + * + * @deprecated use top level {@link sql.fn} instead + * @hidden + */ + static fn: typeof fn; + /** + * @deprecated use top level {@link sql.fn} instead + * @hidden + */ + fn: typeof fn; + + /** + * Creates a object representing a column in the DB. This is often useful in conjunction with + * `sequelize.fn`, since raw string arguments to fn will be escaped. + * + * @param col The name of the column + * + * @deprecated use top level {@link sql.col} instead + * @hidden + */ + static col: typeof col; + /** + * @deprecated use top level {@link sql.col} instead + * @hidden + */ + col: typeof col; + + /** + * Creates a object representing a call to the cast function. + * + * @param val The value to cast + * @param type The type to cast it to + * + * @deprecated use top level {@link cast} instead + * @hidden + */ + static cast: typeof cast; + /** + * @deprecated use top level {@link cast} instead + * @hidden + */ + cast: typeof cast; + + /** + * Creates a object representing a literal, i.e. something that will not be escaped. + * + * @param val + * + * @deprecated use top level {@link sql.literal} instead + * @hidden + */ + static literal: typeof literal; + /** + * @deprecated use top level {@link sql.literal} instead + * @hidden + */ + literal: typeof literal; + + /** + * An AND query + * + * @param args Each argument will be joined by AND + * + * @deprecated use top level {@link and} instead + * @hidden + */ + static and: typeof and; + /** + * @deprecated use top level {@link and} instead + * @hidden + */ + and: typeof and; + + /** + * An OR query + * + * @param args Each argument will be joined by OR + * + * @deprecated use top level {@link or} instead + * @hidden + */ + static or: typeof or; + + /** + * @deprecated use top level {@link or} instead + * @hidden + */ + or: typeof or; + + /** + * Creates an object representing nested where conditions for postgres's json data-type. + * + * @param conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot + * notation or a string using postgres json syntax. + * @param value An optional value to compare against. + * Produces a string of the form "<json path> = '<value>'"`. + * + * @deprecated use top level {@link json} instead + * @hidden + */ + static json: typeof json; + /** + * @deprecated use top level {@link json} instead + * @hidden + */ + json: typeof json; + + /** + * A way of specifying attr = condition. + * + * The attr can either be an object taken from `Model.rawAttributes` (for example `Model.rawAttributes.id` + * or + * `Model.rawAttributes.name`). The attribute should be defined in your model definition. The attribute can + * also be an object from one of the sequelize utility functions (`sequelize.fn`, `sequelize.col` etc.) + * + * For string attributes, use the regular `{ where: { attr: something }}` syntax. If you don't want your + * string to be escaped, use `sequelize.literal`. + * + * @param 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 comparator Comparator + * @param logic The condition. Can be both a simply type, or a further condition (`.or`, `.and`, `.literal` + * etc.) + * + * @deprecated use top level {@link where} instead + * @hidden + */ + static where: typeof where; + /** + * @deprecated use top level {@link where} instead + * @hidden + */ + where: typeof where; + + /** + * @deprecated use top level {@link Op} instead + * @hidden + */ + static Op: typeof Op; + + /** + * @deprecated use top level {@link DataTypes} instead + * @hidden + */ + static DataTypes: typeof DataTypes; + + /** + * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. + */ + Sequelize: typeof Sequelize; + + readonly dialect: Dialect; + + /** + * @inheritDoc + */ + constructor(options: Options); + + /** + * Returns the specified dialect. + */ + getDialect(): string; + + /** + * Returns the database name. + */ + + getDatabaseName(): string; + + /** + * Returns the dialect-dependant QueryInterface instance. + */ + getQueryInterface(): Dialect['queryInterface']; + + /** + * Define a new model, representing a table in the DB. + * + * The table columns are defined by the hash that is given as the second argument. Each attribute of the + * hash + * represents a column. A short table definition might look like this: + * + * ```js + * class MyModel extends Model {} + * MyModel.init({ + * columnA: { + * type: DataTypes.BOOLEAN, + * validate: { + * is: ["[a-z]",'i'], // will only allow letters + * max: 23, // only allow values <= 23 + * isIn: { + * args: [['en', 'zh']], + * msg: "Must be English or Chinese" + * } + * }, + * field: 'column_a' + * // Other attributes here + * }, + * columnB: DataTypes.STRING, + * columnC: 'MY VERY OWN COLUMN TYPE' + * }, { sequelize }) + * + * sequelize.models.modelName // The model will now be available in models under the name given to define + * ``` + * + * As shown above, column definitions can be either strings, a reference to one of the datatypes that are + * predefined on the Sequelize constructor, or an object that allows you to specify both the type of the + * column, and other attributes such as default values, foreign key constraints and custom setters and + * getters. + * + * For a list of possible data types, see + * https://sequelize.org/docs/v7/other-topics/other-data-types + * + * For more about getters and setters, see + * https://sequelize.org/docs/v7/core-concepts/getters-setters-virtuals/ + * + * For more about instance and class methods, see + * https://sequelize.org/docs/v7/core-concepts/model-basics/#taking-advantage-of-models-being-classes + * + * For more about validation, see + * https://sequelize.org/docs/v7/core-concepts/validations-and-constraints/ + * + * @param modelName The name of the model. The model will be stored in `sequelize.models` under this name + * @param 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 options These options are merged with the default define options provided to the Sequelize + * constructor + */ + define>( + modelName: string, + attributes?: ModelAttributes, + options?: ModelOptions, + ): ModelStatic; + + /** + * Fetch a Model which is already defined + * + * @param modelName The name of a model defined with Sequelize.define + * @deprecated use {@link Sequelize#models} instead. + */ + model(modelName: string): ModelStatic; + + /** + * Checks whether a model with the given name is defined + * + * @param modelName The name of a model defined with Sequelize.define + * @deprecated use {@link Sequelize#models} instead. + */ + isDefined(modelName: string): boolean; + + /** + * 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 `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 + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring + * + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring + * ``` + * + * @param sql + * @param options Query options + */ + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise<[undefined, number]>; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise<[number, number]>; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithModel & { plain: true }, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithModel, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType & { plain: true }, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: QueryOptionsWithType, + ): Promise; + query( + sql: string | BaseSqlExpression, + options: (QueryOptions | QueryOptionsWithType) & { plain: true }, + ): Promise<{ [key: string]: unknown } | null>; + query( + sql: string | BaseSqlExpression, + options?: QueryOptions | QueryOptionsWithType, + ): Promise<[unknown[], unknown]>; + + /** + * Works like {@link Sequelize#query}, but does not inline replacements. Only bind parameters are supported. + * + * @param sql The SQL to execute + * @param options The options for the query. See {@link QueryRawOptions} for details. + */ + queryRaw( + sql: string, + options: QueryRawOptionsWithType, + ): Promise<[undefined, number]>; + queryRaw(sql: string, options: QueryRawOptionsWithType): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithType, + ): Promise<[number, number]>; + queryRaw(sql: string, options: QueryRawOptionsWithType): Promise; + queryRaw(sql: string, options: QueryRawOptionsWithType): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithType, + ): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithType, + ): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithModel & { plain: true }, + ): Promise; + queryRaw(sql: string, options: QueryRawOptionsWithModel): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithType & { plain: true }, + ): Promise; + queryRaw( + sql: string, + options: QueryRawOptionsWithType, + ): Promise; + queryRaw( + sql: string, + options: (QueryRawOptions | QueryRawOptionsWithType) & { plain: true }, + ): Promise<{ [key: string]: unknown } | null>; + queryRaw( + sql: string, + options?: QueryRawOptions | QueryRawOptionsWithType, + ): Promise<[unknown[], unknown]>; + + log(...values: unknown[]): void; + + /** + * Get the fn for random based on the dialect + */ + random(): Fn; + + /** + * 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 variables object with multiple variables. + * @param options Query options. + */ + setSessionVariables(variables: object, options?: SetSessionVariablesOptions): Promise; + + /** + * Sync all defined models to the DB. + * + * @param options Sync Options + */ + sync(options?: SyncOptions): Promise; + + /** + * Drop all tables defined through this sequelize instance. This is done by calling Model.drop on each model + * + * @param options The options passed to each call to Model.drop + */ + drop(options?: DropOptions): Promise; + + /** + * Test the connection by trying to authenticate + * + * @param options Query Options for authentication + */ + authenticate(options?: QueryOptions): Promise; + validate(options?: QueryOptions): Promise; + + normalizeAttribute( + attribute: AttributeOptions | DataType, + ): AttributeOptions; + + /** + * Returns the installed version of Sequelize + */ + static get version(): string; +} + +// Utilities + +/** + * An AND query + * + * @param args Each argument will be joined by AND + */ +export function and(...args: T): { [Op.and]: T }; + +/** + * An OR query + * + * @param args Each argument will be joined by OR + */ +export function or(...args: T): { [Op.or]: T }; + +export type Expression = ColumnReference | DynamicSqlExpression | unknown; diff --git a/packages/core/src/sequelize.internals.ts b/packages/core/src/sequelize.internals.ts new file mode 100644 index 000000000000..4221d2ca9096 --- /dev/null +++ b/packages/core/src/sequelize.internals.ts @@ -0,0 +1,305 @@ +import type { Nullish } from '@sequelize/utils'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import type { Class } from 'type-fest'; +import type { AbstractDialect } from './abstract-dialect/dialect.js'; +import type { Logging, ModelOptions, ModelStatic } from './model.js'; +import type { SequelizeHooks } from './sequelize-typescript.js'; +import type { + DefaultSetOptions, + DialectName, + QueryOptions, + ReplicationOptions, + RetryOptions, + SyncOptions, +} from './sequelize.js'; +import type { PoolOptions } from './sequelize.types.js'; +import type { IsolationLevel, TransactionNestMode, TransactionType } from './transaction.js'; + +export function importDialect(dialect: string): typeof AbstractDialect { + // Requiring the dialect in a switch-case to keep the + // require calls static. (Browserify fix) + switch (dialect) { + case 'mariadb': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/mariadb').MariaDbDialect; + case 'mssql': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/mssql').MsSqlDialect; + case 'mysql': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/mysql').MySqlDialect; + case 'postgres': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/postgres').PostgresDialect; + case 'sqlite': + case 'sqlite3': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/sqlite3').SqliteDialect; + case 'ibmi': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/db2-ibmi').IBMiDialect; + case 'db2': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/db2').Db2Dialect; + case 'snowflake': + // eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves + return require('@sequelize/snowflake').SnowflakeDialect; + default: + throw new Error( + `The dialect ${dialect} is not natively supported. Native dialects: mariadb, mssql, mysql, postgres, sqlite3, ibmi, db2 and snowflake.`, + ); + } +} + +export const PERSISTED_SEQUELIZE_OPTIONS = getSynchronizedTypeKeys< + PersistedSequelizeOptions +>({ + benchmark: undefined, + defaultTimestampPrecision: undefined, + defaultTransactionNestMode: undefined, + define: undefined, + disableClsTransactions: undefined, + isolationLevel: undefined, + keepDefaultTimezone: undefined, + logQueryParameters: undefined, + logging: undefined, + minifyAliases: undefined, + noTypeValidation: undefined, + nullJsonStringification: undefined, + omitNull: undefined, + prependSearchPath: undefined, + query: undefined, + quoteIdentifiers: undefined, + replication: undefined, + retry: undefined, + schema: undefined, + set: undefined, + sync: undefined, + timezone: undefined, + transactionType: undefined, +}); + +/** + * The options that are accessible via {@link Sequelize#options}. + */ +export interface PersistedSequelizeOptions extends Logging { + /** + * The precision for the `createdAt`/`updatedAt`/`deletedAt` DATETIME columns that Sequelize adds to models. + * Can be a number between 0 and 6, or null to use the default precision of the database. Defaults to 6. + * + * @default 6 + */ + defaultTimestampPrecision?: number | null; + + /** + * How nested transaction blocks behave by default. + * See {@link ManagedTransactionOptions#nestMode} for more information. + * + * @default TransactionNestMode.reuse + */ + defaultTransactionNestMode?: TransactionNestMode; + + /** + * Default options for model definitions. See Model.init. + */ + define?: Omit; + + /** + * Disable the use of AsyncLocalStorage to automatically pass transactions started by {@link Sequelize#transaction}. + * You will need to pass transactions around manually if you disable this. + */ + disableClsTransactions?: boolean; + + /** + * Set the default transaction isolation level. + * If not set, does not change the database's default transaction isolation level. + */ + isolationLevel?: IsolationLevel | undefined; + + /** + * A flag that defines if the default timezone is used to convert dates from the database. + * + * @default false + */ + keepDefaultTimezone?: boolean; + + /** + * Set to `true` to show bind parameters in log. + * + * @default false + */ + logQueryParameters?: boolean; + + /** + * Set to `true` to automatically minify aliases generated by sequelize. + * Mostly useful to circumvent the POSTGRES alias limit of 64 characters. + * + * @default false + */ + minifyAliases?: boolean; + + /** + * Disable built in type validators on insert and update, e.g. don't validate that arguments passed to integer + * fields are integer-like. + * + * @default false + */ + noTypeValidation?: boolean; + + /** + * When representing the JavaScript null primitive in a JSON column, Sequelize can + * use either the SQL NULL value, or a JSON 'null'. + * + * Set this to "json" if you want the null to be stored as a JSON 'null'. + * Set this to "sql" if you want the null to be stored as the SQL NULL value. + * Set this to "explicit" if you don't want Sequelize to make any assumptions. + * This means that you won't be able to use the JavaScript null primitive as the top level value of a JSON column, + * you will have to use {@link SQL_NULL} or {@link JSON_NULL} instead. + * + * This only impacts serialization when inserting or updating values. + * Comparing always requires to be explicit. + * + * Read more: https://sequelize.org/docs/v7/querying/json/ + * + * @default json + */ + nullJsonStringification?: 'explicit' | 'json' | 'sql'; + + /** + * A flag that defines if null values should be passed to SQL queries or not. + * + * @default false + */ + omitNull?: boolean; + + // TODO [>7]: remove this option + prependSearchPath?: boolean | undefined; + + /** + * Default options for sequelize.query + */ + query?: QueryOptions; + + /** + * Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of + * them. + * + * @default true + */ + quoteIdentifiers?: boolean; + + /** + * 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`. Connection strings can be used instead of objects. + * + * @default false + */ + replication?: ReplicationOptions | false | Nullish; + + retry?: RetryOptions; + + /** + * If defined, the connection will use the provided schema instead of the default ("public"). + */ + schema?: string; + + /** + * Default options for sequelize.set + */ + set?: DefaultSetOptions; + + /** + * Default options for sequelize.sync + */ + sync?: SyncOptions; + + /** + * 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 supported by Intl.Locale (e.g. 'America/Los_Angeles'); + * this is useful to capture daylight savings time changes. + * + * @default '+00:00' + */ + timezone?: string; + + /** + * Set the default transaction type. See Sequelize.Transaction.TYPES for possible options. Sqlite only. + * + * @default 'DEFERRED' + */ + transactionType?: TransactionType; +} + +export const EPHEMERAL_SEQUELIZE_OPTIONS = getSynchronizedTypeKeys< + EphemeralSequelizeOptions +>({ + databaseVersion: undefined, + dialect: undefined, + hooks: undefined, + models: undefined, + pool: undefined, + url: undefined, +}); + +/** + * Sequelize options that are not persisted in the Sequelize instance. + */ +export interface EphemeralSequelizeOptions { + /** + * The version of the Database Sequelize will connect to. + * If unspecified, or set to 0, Sequelize will retrieve it during its first connection to the Database. + */ + databaseVersion?: string; + + /** + * The dialect of the database you are connecting to. Either the name of the dialect, or a dialect class. + */ + dialect: DialectName | Class; + + /** + * Sets global permanent hooks. + */ + hooks?: Partial>; + + /** + * A list of models to load and init. + * + * This option is only useful if you created your models using decorators. + * Models created using {@link Model.init} or {@link Sequelize#define} don't need to be specified in this option. + * + * Use {@link importModels} to load models dynamically: + * + * @example + * ```ts + * import { User } from './models/user.js'; + * + * new Sequelize({ + * models: [User], + * }); + * ``` + * + * @example + * ```ts + * new Sequelize({ + * models: await importModels(__dirname + '/*.model.ts'), + * }); + * ``` + */ + models?: ModelStatic[]; + + /** + * Connection pool options + */ + pool?: PoolOptions | undefined; + + /** + * The connection URL. + * If other connection options are set, they will override the values set in this URL. + */ + url?: string | undefined; +} diff --git a/packages/core/src/sequelize.js b/packages/core/src/sequelize.js new file mode 100644 index 000000000000..71082eb3e7b0 --- /dev/null +++ b/packages/core/src/sequelize.js @@ -0,0 +1,901 @@ +'use strict'; + +import { EMPTY_OBJECT, shallowClonePojo } from '@sequelize/utils'; +import defaults from 'lodash/defaults'; +import isPlainObject from 'lodash/isPlainObject'; +import map from 'lodash/map'; +import retry from 'retry-as-promised'; +import { AbstractConnectionManager } from './abstract-dialect/connection-manager.js'; +import { AbstractDialect } from './abstract-dialect/dialect.js'; +import { AbstractQueryGenerator } from './abstract-dialect/query-generator.js'; +import { AbstractQueryInterface } from './abstract-dialect/query-interface'; +import { AbstractQuery } from './abstract-dialect/query.js'; +import { Association } from './associations/base.js'; +import { BelongsToAssociation } from './associations/belongs-to'; +import { BelongsToManyAssociation } from './associations/belongs-to-many'; +import { HasManyAssociation } from './associations/has-many'; +import { HasOneAssociation } from './associations/has-one'; +import * as DataTypes from './data-types'; +import { ConstraintChecking, Deferrable } from './deferrable'; +import { IndexHints, ParameterStyle, QueryTypes, TableHints } from './enums.js'; +import * as SequelizeErrors from './errors'; +import { AssociationPath } from './expression-builders/association-path'; +import { Attribute } from './expression-builders/attribute'; +import { BaseSqlExpression } from './expression-builders/base-sql-expression.js'; +import { Cast, cast } from './expression-builders/cast.js'; +import { Col, col } from './expression-builders/col.js'; +import { Fn, fn } from './expression-builders/fn.js'; +import { Identifier } from './expression-builders/identifier'; +import { JsonPath } from './expression-builders/json-path'; +import { JSON_NULL, SQL_NULL } from './expression-builders/json-sql-null.js'; +import { json } from './expression-builders/json.js'; +import { List } from './expression-builders/list'; +import { Literal, literal } from './expression-builders/literal.js'; +import { sql } from './expression-builders/sql'; +import { Value } from './expression-builders/value'; +import { Where, where } from './expression-builders/where.js'; +import { importModels } from './import-models.js'; +import { Model } from './model'; +import { setTransactionFromCls } from './model-internals.js'; +import { ManualOnDelete } from './model-repository.types.js'; +import { Op } from './operators'; +import { SequelizeTypeScript } from './sequelize-typescript'; +import { + COMPLETES_TRANSACTION, + IsolationLevel, + Lock, + Transaction, + TransactionNestMode, + TransactionType, +} from './transaction.js'; +import * as Deprecations from './utils/deprecations'; +import { + noGetDialect, + noGetQueryInterface, + noSequelizeDataType, + noSequelizeIsDefined, + noSequelizeModel, +} from './utils/deprecations'; +import { isModelStatic, isSameInitialModel } from './utils/model-utils'; +import { injectReplacements, mapBindParameters } from './utils/sql'; +import { withSqliteForeignKeysOff } from './utils/sql.js'; +import { useInflection } from './utils/string'; +import { validator as Validator } from './utils/validator-extras'; + +/** + * This is the main class, the entry point to sequelize. + */ +export class Sequelize extends SequelizeTypeScript { + /** + * Returns the specified dialect. + * + * @returns {string} The specified dialect. + */ + getDialect() { + noGetDialect(); + + return this.dialect.name; + } + + /** + * Returns the database name. + * + * @returns {string} The database name. + */ + getDatabaseName() { + throw new Error( + 'getDatabaseName has been removed as it does not make sense in every dialect. Please use the values available in sequelize.options.replication.write for an equivalent option.', + ); + } + + /** + * Returns an instance of AbstractQueryInterface. + * + * @returns {AbstractQueryInterface} An instance (singleton) of AbstractQueryInterface. + */ + getQueryInterface() { + noGetQueryInterface(); + + return this.queryInterface; + } + + /** + * Define a new model, representing a table in the database. + * + * 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() + * + * @see + * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. + * @see + * Model Basics guide + * + * @returns {Model} Newly defined model + * + * @example + * sequelize.define('modelName', { + * columnA: { + * type: DataTypes.BOOLEAN, + * validate: { + * is: ["[a-z]",'i'], // will only allow letters + * max: 23, // only allow values <= 23 + * isIn: { + * args: [['en', 'zh']], + * msg: "Must be English or Chinese" + * } + * }, + * field: 'column_a' + * }, + * columnB: DataTypes.STRING, + * columnC: 'MY VERY OWN COLUMN TYPE' + * }); + * + * sequelize.models.modelName // The model will now be available in models under the name given to define + */ + define(modelName, attributes = EMPTY_OBJECT, options = EMPTY_OBJECT) { + options = shallowClonePojo(options); + + options.modelName = modelName; + options.sequelize = this; + + const model = class extends Model {}; + + model.init(attributes, options); + + return model; + } + + /** + * Fetch a Model which is already defined + * + * @param {string} modelName The name of a model defined with Sequelize.define + * + * @throws Will throw an error if the model is not defined (that is, if sequelize#isDefined returns false) + * @returns {Model} Specified model + */ + model(modelName) { + noSequelizeModel(); + + return this.models.getOrThrow(modelName); + } + + /** + * Checks whether a model with the given name is defined + * + * @param {string} modelName The name of a model defined with Sequelize.define + * + * @returns {boolean} Returns true if model is already defined, otherwise false + */ + isDefined(modelName) { + noSequelizeIsDefined(); + + return this.models.hasByName(modelName); + } + + /** + * 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. + * + * 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 + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring + * + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring + * ``` + * + * @param {string} sql + * @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 {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 {Model} [options.instance] A sequelize model instance whose Model is to be used to build the query result + * @param {ModelStatic} [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 {number} [options.retry.timeout] Maximum duration, in milliseconds, to retry until an error is thrown. + * @param {number} [options.retry.backoffBase=100] Initial backoff duration, in milliseconds. + * @param {number} [options.retry.backoffExponent=1.1] Exponent to increase backoff duration after each retry. + * @param {Function} [options.retry.report] Function that is executed after each retry, called with a message and the current retry options. + * @param {string} [options.retry.name='unknown'] Name used when composing error/reporting messages. + * @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 {boolean} [options.rawErrors=false] Set to `true` to cause errors coming from the underlying connection/database library to be propagated unmodified and unformatted. Else, the default behavior (=false) is to reinterpret errors as sequelize.errors.BaseError objects. + * + * @returns {Promise} + * + * @see {@link Model.build} for more information about instance option. + */ + async query(sql, options) { + options = { ...this.options.query, ...options }; + + if (sql instanceof BaseSqlExpression) { + sql = this.queryGenerator.formatSqlExpression(sql, options); + } + + if (typeof sql === 'object') { + throw new TypeError( + '"sql" cannot be an object. Pass a string instead, and pass bind and replacement parameters through the "options" parameter', + ); + } + + sql = sql.trim(); + + if (options.replacements) { + sql = injectReplacements(sql, this.dialect, options.replacements); + } + + // queryRaw will throw if 'replacements' is specified, as a way to warn users that they are miusing the method. + delete options.replacements; + + return this.queryRaw(sql, options); + } + + async queryRaw(sql, options) { + if (typeof sql !== 'string') { + throw new TypeError('Sequelize#rawQuery requires a string as the first parameter.'); + } + + if (options != null && 'replacements' in options) { + throw new TypeError(`Sequelize#rawQuery does not accept the "replacements" options. +Only bind parameters can be provided, in the dialect-specific syntax. +Use Sequelize#query if you wish to use replacements.`); + } + + options = { ...this.options.query, ...options, bindParameterOrder: null }; + + let bindParameters; + if (options.bind != null) { + const isBindArray = Array.isArray(options.bind); + if (!isPlainObject(options.bind) && !isBindArray) { + throw new TypeError( + 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)', + ); + } + + const mappedResult = mapBindParameters(sql, this.dialect); + + for (const parameterName of mappedResult.parameterSet) { + if (isBindArray) { + if (!/[1-9][0-9]*/.test(parameterName) || options.bind.length < Number(parameterName)) { + throw new Error( + `Query includes bind parameter "$${parameterName}", but no value has been provided for that bind parameter.`, + ); + } + } else if (!(parameterName in options.bind)) { + throw new Error( + `Query includes bind parameter "$${parameterName}", but no value has been provided for that bind parameter.`, + ); + } + } + + sql = mappedResult.sql; + + // used by dialects that support "INOUT" parameters to map the OUT parameters back the the name the dev used. + options.bindParameterOrder = mappedResult.bindOrder; + if (mappedResult.bindOrder == null) { + bindParameters = options.bind; + } else { + bindParameters = mappedResult.bindOrder.map(key => { + if (isBindArray) { + return options.bind[key - 1]; + } + + return options.bind[key]; + }); + } + } + + if (options.instance && !options.model) { + options.model = options.instance.constructor; + } + + if (!options.instance && !options.model) { + options.raw = true; + } + + // map raw fields to model attributes + if (options.mapToModel) { + // TODO: throw if model is not specified + options.fieldMap = options.model?.fieldAttributeMap; + } + + options = defaults(options, { + logging: Object.hasOwn(this.options, 'logging') ? this.options.logging : console.debug, + searchPath: Object.hasOwn(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT', + }); + + if (!options.type) { + if (options.model || options.nest || options.plain) { + options.type = QueryTypes.SELECT; + } else { + options.type = QueryTypes.RAW; + } + } + + // if dialect doesn't support search_path or dialect option + // to prepend searchPath is not true delete the searchPath option + if ( + !this.dialect.supports.searchPath || + !this.options.prependSearchPath || + options.supportsSearchPath === false + ) { + delete options.searchPath; + } else if (!options.searchPath) { + // if user wants to always prepend searchPath (preprendSearchPath = true) + // then set to DEFAULT if none is provided + options.searchPath = 'DEFAULT'; + } + + const checkTransaction = () => { + if (options.transaction && options.transaction.finished && !options[COMPLETES_TRANSACTION]) { + 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; + } + }; + + setTransactionFromCls(options, this); + const retryOptions = { ...this.options.retry, ...options.retry }; + + return await retry(async () => { + checkTransaction(); + + const connection = options.transaction + ? options.transaction.getConnection() + : options.connection + ? options.connection + : await this.pool.acquire({ + useMaster: options.useMaster, + type: options.type === 'SELECT' ? 'read' : 'write', + }); + + if (this.dialect.name === 'db2' && options.alter && options.alter.drop === false) { + connection.dropTable = false; + } + + const query = new this.dialect.Query(connection, this, options); + + try { + await this.hooks.runAsync('beforeQuery', options, query); + checkTransaction(); + + return await query.run(sql, bindParameters, { minifyAliases: options.minifyAliases }); + } finally { + await this.hooks.runAsync('afterQuery', options, query); + if (!options.transaction && !options.connection) { + this.pool.release(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 or MariaDB. + * + * @param {object} variables Object with multiple variables. + * @param {object} [options] query options. + * + * @returns {Promise} + */ + async setSessionVariables(variables, options) { + // Prepare options + options = { ...this.options.setSessionVariables, ...options }; + + if (!['mysql', 'mariadb'].includes(this.dialect.name)) { + throw new Error('sequelize.setSessionVariables is only supported for mysql or mariadb'); + } + + setTransactionFromCls(options, this); + + if ( + (!options.transaction || !(options.transaction instanceof Transaction)) && + !options.connection + ) { + throw new Error( + 'You must specify either options.transaction or options.connection, as sequelize.setSessionVariables is used to set the session options of a connection', + ); + } + + // Override some options, since this isn't a SELECT + options.raw = true; + options.plain = true; + options.type = 'SET'; + + // Generate SQL Query + const query = `SET ${map( + variables, + (v, k) => `@${k} := ${typeof v === 'string' ? `"${v}"` : v}`, + ).join(', ')}`; + + return await this.query(query, options); + } + + /** + * Sync all defined models to the DB. + * + * @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 {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} [options.alter.drop=true] Prevents any drop statements while altering a table when set to `false` + * + * @returns {Promise} + */ + async sync(options) { + options = { + ...this.options.sync, + ...options, + hooks: options ? options.hooks !== false : true, + }; + + if ('match' in options) { + throw new Error( + 'The "match" option has been removed as matching against a database name does not make sense in every dialects.', + ); + } + + if (options.hooks) { + await this.hooks.runAsync('beforeBulkSync', options); + } + + if (options.force) { + await this.drop({ + ...options, + cascade: this.dialect.supports.dropTable.cascade || undefined, + }); + } + + // no models defined, just authenticate + if (this.models.size === 0) { + await this.authenticate(options); + } else { + const models = this.models.getModelsTopoSortedByForeignKey(); + if (models == null) { + return this._syncModelsWithCyclicReferences(options); + } + + // reverse to start with the one model that does not depend on anything + models.reverse(); + + // Topologically sort by foreign key constraints to give us an appropriate + // creation order + for (const model of models) { + await model.sync(options); + } + } + + if (options.hooks) { + await this.hooks.runAsync('afterBulkSync', options); + } + + return this; + } + + /** + * Used instead of sync() when two models reference each-other, so their foreign keys cannot be created immediately. + * + * @param {object} options - sync options + * @private + */ + async _syncModelsWithCyclicReferences(options) { + if (this.dialect.name === 'sqlite3') { + // Optimisation: no need to do this in two passes in SQLite because we can temporarily disable foreign keys + await withSqliteForeignKeysOff(this, options, async () => { + for (const model of this.models) { + await model.sync(options); + } + }); + + return; + } + + // create all tables, but don't create foreign key constraints + for (const model of this.models) { + await model.sync({ ...options, withoutForeignKeyConstraints: true }); + } + + // add foreign key constraints + for (const model of this.models) { + await model.sync({ ...options, force: false, alter: true }); + } + } + + /** + * Drop all tables defined through this sequelize instance. + * This is done by calling {@link Model.drop} on each model. + * + * @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} + */ + async drop(options) { + // if 'cascade' is specified, we don't have to worry about cyclic dependencies. + if (options && options.cascade) { + for (const model of this.models) { + await model.drop(options); + } + } + + const sortedModels = this.models.getModelsTopoSortedByForeignKey(); + + // no cyclic dependency between models, we can delete them in an order that will not cause an error. + if (sortedModels) { + for (const model of sortedModels) { + await model.drop(options); + } + } + + if (this.dialect.name === 'sqlite3') { + // Optimisation: no need to do this in two passes in SQLite because we can temporarily disable foreign keys + await withSqliteForeignKeysOff(this, options, async () => { + for (const model of this.models) { + await model.drop(options); + } + }); + + return; + } + + // has cyclic dependency: we first remove each foreign key, then delete each model. + for (const model of this.models) { + const foreignKeys = await this.queryInterface.showConstraints(model, { + ...options, + constraintType: 'FOREIGN KEY', + }); + + await Promise.all( + foreignKeys.map(foreignKey => { + return this.queryInterface.removeConstraint(model, foreignKey.constraintName, options); + }), + ); + } + + for (const model of this.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 + * + * @returns {Promise} + */ + async authenticate(options) { + options = { + raw: true, + plain: true, + type: QueryTypes.SELECT, + ...options, + }; + + await this.query( + `SELECT 1+1 AS result${this.dialect.name === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + options, + ); + } + + /** + * Get the fn for random based on the dialect + * + * @returns {Fn} + */ + // TODO: replace with sql.random + random() { + if (['postgres', 'sqlite3', 'snowflake'].includes(this.dialect.name)) { + return fn('RANDOM'); + } + + return fn('RAND'); + } + + // Global exports + static Fn = Fn; + static Col = Col; + static Cast = Cast; + static Literal = Literal; + static Where = Where; + static List = List; + static Identifier = Identifier; + static Attribute = Attribute; + static Value = Value; + static AssociationPath = AssociationPath; + static JsonPath = JsonPath; + + static sql = sql; + + // these are all available on the "sql" object, but are exposed for backwards compatibility + static fn = fn; + static col = col; + static cast = cast; + static literal = literal; + static json = json; + static where = where; + + static and = and; + + static or = or; + + static isModelStatic = isModelStatic; + + static isSameInitialModel = isSameInitialModel; + + static importModels = importModels; + + static TransactionNestMode = TransactionNestMode; + static TransactionType = TransactionType; + static Lock = Lock; + static IsolationLevel = IsolationLevel; + + log(...args) { + let options; + + const last = args.at(-1); + + if (last && isPlainObject(last) && Object.hasOwn(last, 'logging')) { + options = last; + + // remove options from set of logged arguments if options.logging is equal to console.log or console.debug + // eslint-disable-next-line no-console -- intended console.log use + if (options.logging === console.log || options.logging === console.debug) { + args.splice(-1, 1); + } + } else { + options = this.options; + } + + if (options.logging) { + if (options.logging === true) { + Deprecations.noTrueLogging(); + options.logging = console.debug; + } + + // second argument is sql-timings, when benchmarking option enabled + if ((this.options.benchmark || options.benchmark) && options.logging === console.debug) { + args = [`${args[0]} Elapsed time: ${args[1]}ms`]; + } + + options.logging(...args); + } + } + + normalizeAttribute(attribute) { + if (!isPlainObject(attribute)) { + attribute = { type: attribute }; + } else { + attribute = { ...attribute }; + } + + if (attribute.values) { + throw new TypeError( + ` +The "values" property has been removed from column definitions. The following is no longer supported: + +sequelize.define('MyModel', { + roles: { + type: DataTypes.ENUM, + values: ['admin', 'user'], + }, +}); + +Instead, define enum values like this: + +sequelize.define('MyModel', { + roles: { + type: DataTypes.ENUM(['admin', 'user']), + }, +}); + +Remove the "values" property to resolve this issue. + `.trim(), + ); + } + + if (!attribute.type) { + return attribute; + } + + attribute.type = this.normalizeDataType(attribute.type); + + return attribute; + } +} + +// Aliases +Sequelize.prototype.fn = Sequelize.fn; +Sequelize.prototype.col = Sequelize.col; +Sequelize.prototype.cast = Sequelize.cast; +Sequelize.prototype.literal = Sequelize.literal; +Sequelize.prototype.and = Sequelize.and; +Sequelize.prototype.or = Sequelize.or; +Sequelize.prototype.json = Sequelize.json; +Sequelize.prototype.where = Sequelize.where; +Sequelize.prototype.validate = Sequelize.prototype.authenticate; + +/** + * Sequelize version number. + */ +// To avoid any errors on startup when this field is unused, only resolve it as needed. +// this is to prevent any potential issues on startup with unusual environments (eg, bundled code) +// where relative paths may fail that are unnecessary. +Object.defineProperty(Sequelize, 'version', { + enumerable: true, + get() { + return require('../package.json').version; + }, +}); + +/** + * Operators symbols to be used for querying data + * + * @see {@link Operators} + */ +Sequelize.Op = Op; + +/** + * 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} + */ +Sequelize.Transaction = Transaction; + +Sequelize.GeoJsonType = require('./geo-json').GeoJsonType; + +/** + * 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; + +Sequelize.Model = Model; + +Sequelize.AbstractQueryInterface = AbstractQueryInterface; +Sequelize.BelongsToAssociation = BelongsToAssociation; +Sequelize.HasOneAssociation = HasOneAssociation; +Sequelize.HasManyAssociation = HasManyAssociation; +Sequelize.BelongsToManyAssociation = BelongsToManyAssociation; + +Sequelize.DataTypes = DataTypes; +for (const dataTypeName in DataTypes) { + Object.defineProperty(Sequelize, dataTypeName, { + get() { + noSequelizeDataType(); + + return DataTypes[dataTypeName]; + }, + }); +} + +/** + * A reference to the deferrable collection. Use this to access the different deferrable options. + * + * @see {@link QueryInterface#addConstraint} + */ +Sequelize.Deferrable = Deferrable; + +/** + * A reference to the deferrable collection. Use this to access the different deferrable options. + * + * @see {@link Transaction.Deferrable} + * @see {@link Sequelize#transaction} + */ +Sequelize.ConstraintChecking = ConstraintChecking; + +/** + * 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 `pluralize` etc. + * + * @param {object} _inflection - `inflection` module + */ +Sequelize.useInflection = useInflection; + +Sequelize.SQL_NULL = SQL_NULL; +Sequelize.JSON_NULL = JSON_NULL; +Sequelize.ManualOnDelete = ManualOnDelete; +Sequelize.ParameterStyle = ParameterStyle; + +Sequelize.AbstractConnectionManager = AbstractConnectionManager; +Sequelize.AbstractQueryGenerator = AbstractQueryGenerator; +Sequelize.AbstractQuery = AbstractQuery; +Sequelize.AbstractDialect = AbstractDialect; + +/** + * Expose various errors available + */ + +for (const error of Object.keys(SequelizeErrors)) { + Sequelize[error] = SequelizeErrors[error]; +} + +/** + * An AND query + * + * @see Model.findAll + * + * @param {...string|object} args Each argument will be joined by AND + * @since v2.0.0-dev3 + * @memberof Sequelize + * + * @returns {Sequelize.and} + */ +export function and(...args) { + return { [Op.and]: args }; +} + +/** + * An OR query + * + * @see + * {@link Model.findAll} + * + * @param {...string|object} args Each argument will be joined by OR + * @since v2.0.0-dev3 + * @memberof Sequelize + * + * @returns {Sequelize.or} + */ +export function or(...args) { + if (args.length === 1) { + return { [Op.or]: args[0] }; + } + + return { [Op.or]: args }; +} diff --git a/packages/core/src/sequelize.types.ts b/packages/core/src/sequelize.types.ts new file mode 100644 index 000000000000..d136b0120841 --- /dev/null +++ b/packages/core/src/sequelize.types.ts @@ -0,0 +1,56 @@ +import type { PartialOrUndefined, StrictRequiredBy } from '@sequelize/utils'; +import type { Connection } from './abstract-dialect/connection-manager.js'; +import type { + AbstractDialect, + ConnectionOptions, + DialectOptions, +} from './abstract-dialect/dialect.js'; +import type { ReplicationPoolOptions } from './abstract-dialect/replication-pool.js'; +import type { + EphemeralSequelizeOptions, + PersistedSequelizeOptions, +} from './sequelize.internals.js'; +import type { NormalizedReplicationOptions } from './sequelize.js'; + +/** + * Connection Pool options. + * + * Used in {@link SequelizeCoreOptions.pool} + */ +export interface PoolOptions + extends PartialOrUndefined { + /** + * A function that validates a connection. + * + * If provided, this overrides the default connection validation built in to sequelize. + */ + validate?: ((connection?: Connection) => boolean) | undefined; +} + +/** + * Options of the {@link Sequelize} constructor used by the core library. + * + * See {@link Options} for the full list of options, including those dialect-specific. + */ +interface SequelizeCoreOptions + extends PersistedSequelizeOptions, + EphemeralSequelizeOptions {} + +/** + * Options for the constructor of the {@link Sequelize} main class. + */ +export type Options = SequelizeCoreOptions & + Omit, keyof SequelizeCoreOptions> & + Omit, keyof SequelizeCoreOptions>; + +export type NormalizedOptions = StrictRequiredBy< + Omit, 'replication'>, + | 'transactionType' + | 'noTypeValidation' + | 'timezone' + | 'disableClsTransactions' + | 'defaultTransactionNestMode' + | 'defaultTimestampPrecision' +> & { + replication: NormalizedReplicationOptions; +}; diff --git a/packages/core/src/sql-string.ts b/packages/core/src/sql-string.ts new file mode 100644 index 000000000000..d6d7416ca4cd --- /dev/null +++ b/packages/core/src/sql-string.ts @@ -0,0 +1,72 @@ +import type { AbstractDataType } from './abstract-dialect/data-types.js'; +import type { AbstractDialect } from './abstract-dialect/dialect.js'; +import * as DataTypes from './data-types'; +import { logger } from './utils/logger'; + +const textDataTypeMap = new Map>(); +export function getTextDataTypeForDialect(dialect: AbstractDialect): AbstractDataType { + let type = textDataTypeMap.get(dialect.name); + if (type == null) { + type = new DataTypes.STRING().toDialectDataType(dialect); + textDataTypeMap.set(dialect.name, type); + } + + return type; +} + +export function bestGuessDataTypeOfVal( + val: unknown, + dialect: AbstractDialect, +): AbstractDataType { + // TODO: cache simple types + switch (typeof val) { + case 'bigint': + return new DataTypes.BIGINT().toDialectDataType(dialect); + + case 'number': { + if (Number.isSafeInteger(val)) { + return new DataTypes.INTEGER().toDialectDataType(dialect); + } + + return new DataTypes.FLOAT().toDialectDataType(dialect); + } + + case 'boolean': + return new DataTypes.BOOLEAN().toDialectDataType(dialect); + + case 'object': + if (Array.isArray(val)) { + if (val.length === 0) { + throw new Error( + `Could not guess type of value ${logger.inspect(val)} because it is an empty array`, + ); + } + + return new DataTypes.ARRAY(bestGuessDataTypeOfVal(val[0], dialect)).toDialectDataType( + dialect, + ); + } + + if (val instanceof Date) { + return new DataTypes.DATE(3).toDialectDataType(dialect); + } + + if (Buffer.isBuffer(val)) { + // TODO: remove dialect-specific hack + if (dialect.name === 'ibmi') { + return new DataTypes.STRING().toDialectDataType(dialect); + } + + return new DataTypes.BLOB().toDialectDataType(dialect); + } + + break; + + case 'string': + return getTextDataTypeForDialect(dialect); + + default: + } + + throw new TypeError(`Could not guess type of value ${logger.inspect(val)}`); +} diff --git a/packages/core/src/transaction.ts b/packages/core/src/transaction.ts new file mode 100644 index 000000000000..1ea26dfa80a4 --- /dev/null +++ b/packages/core/src/transaction.ts @@ -0,0 +1,670 @@ +import type { StrictRequiredBy } from '@sequelize/utils'; +import { EMPTY_OBJECT } from '@sequelize/utils'; +import assert from 'node:assert'; +import type { Class } from 'type-fest'; +import type { AbstractConnection, ConstraintChecking, Logging, Sequelize } from './index.js'; + +type TransactionCallback = (transaction: Transaction) => void | Promise; + +/** + * This an option for {@link QueryRawOptions} which indicates if the query completes the transaction + * + * @private do not expose outside sequelize + */ +export const COMPLETES_TRANSACTION = Symbol('completesTransaction'); + +/** + * The transaction object is used to identify a running transaction. + * It is created by calling `Sequelize.transaction()`. + * To run a query under a transaction, you should pass the transaction in the options object. + * + * @class Transaction + * @see {Sequelize.transaction} + */ +export class Transaction { + sequelize: Sequelize; + + readonly #afterCommitHooks = new Set(); + readonly #afterRollbackHooks = new Set(); + readonly #afterHooks = new Set(); + + readonly #name: string; + readonly #savepoints = new Map(); + readonly options: Readonly; + readonly parent: Transaction | null; + readonly id: string; + #finished: 'commit' | 'rollback' | undefined; + #connection: AbstractConnection | undefined; + + /** + * Creates a new transaction instance + * + * @param sequelize A configured sequelize Instance + * @param options The transaction options. + */ + constructor(sequelize: Sequelize, options: TransactionOptions) { + this.sequelize = sequelize; + + // get dialect specific transaction options + const generateTransactionId = this.sequelize.dialect.queryGenerator.generateTransactionId; + + const normalizedOptions = normalizeTransactionOptions(this.sequelize, options); + this.parent = normalizedOptions.transaction ?? null; + delete normalizedOptions.transaction; + + this.options = Object.freeze(normalizedOptions); + + if (this.parent) { + this.id = this.parent.id; + this.#name = `${this.id}-sp-${this.parent.#savepoints.size}`; + this.parent.#savepoints.set(this.#name, this); + } else { + const id = generateTransactionId(); + this.id = id; + this.#name = id; + } + } + + get finished(): 'commit' | 'rollback' | undefined { + return this.#finished; + } + + getConnection(): AbstractConnection { + if (!this.#connection) { + throw new Error('This transaction is not bound to a connection.'); + } + + return this.#connection; + } + + getConnectionIfExists(): AbstractConnection | undefined { + return this.#connection; + } + + /** + * Commit the transaction. + */ + async commit(): Promise { + if (this.#finished) { + throw new Error( + `Transaction cannot be committed because it has been finished with state: ${this.#finished}`, + ); + } + + this.#finished = 'commit'; + if (this.parent) { + // Savepoints cannot be committed + return; + } + + try { + await this.sequelize.queryInterface._commitTransaction(this, this.options); + + await this.#dispatchHooks(this.#afterCommitHooks); + await this.#dispatchHooks(this.#afterHooks); + + this.#cleanup(); + } catch (error) { + console.warn( + `Committing transaction ${this.id} failed with error ${error instanceof Error ? JSON.stringify(error.message) : String(error)}. We are killing its connection as it is now in an undetermined state.`, + ); + await this.#forceCleanup(); + + throw error; + } finally { + this.#finished = 'commit'; + } + } + + /** + * Rollback (abort) the transaction + */ + async rollback(): Promise { + if (this.#finished) { + throw new Error( + `Transaction cannot be rolled back because it has been finished with state: ${this.finished}`, + ); + } + + if (!this.#connection) { + throw new Error('Transaction cannot be rolled back because it never started'); + } + + this.#finished = 'rollback'; + try { + if (this.parent) { + await this.sequelize.queryInterface._rollbackSavepoint(this.parent, { + ...this.options, + savepointName: this.#name, + }); + } else { + await this.sequelize.queryInterface._rollbackTransaction(this, this.options); + } + + await this.#dispatchHooks(this.#afterRollbackHooks); + await this.#dispatchHooks(this.#afterHooks); + + this.#cleanup(); + } catch (error) { + console.warn( + `Rolling back transaction ${this.id} failed with error ${error instanceof Error ? JSON.stringify(error.message) : String(error)}. We are killing its connection as it is now in an undetermined state.`, + ); + await this.#forceCleanup(); + + throw error; + } + } + + async #dispatchHooks(hooks: Set): Promise { + for (const hook of hooks) { + // eslint-disable-next-line no-await-in-loop -- sequentially call hooks + await Reflect.apply(hook, this, [this]); + } + } + + /** + * Called to acquire a connection to use and set the correct options on the connection. + * We should ensure all the environment that's set up is cleaned up in `cleanup()` below. + */ + async prepareEnvironment(): Promise { + let connection; + if (this.parent) { + connection = this.parent.#connection; + } else { + connection = await this.sequelize.pool.acquire({ + type: this.options.readOnly ? 'read' : 'write', + }); + } + + assert(connection != null, 'Transaction failed to acquire Connection.'); + + connection.uuid = this.id; + + this.#connection = connection; + + try { + await this.#begin(); + await this.#setDeferrable(); + } catch (error) { + try { + await this.rollback(); + } finally { + throw error; // eslint-disable-line no-unsafe-finally -- while this will mask the error thrown by `rollback`, the previous error is more important. + } + } + } + + async #setDeferrable(): Promise { + if (this.options.constraintChecking) { + await this.sequelize.queryInterface.deferConstraints(this.options.constraintChecking, { + transaction: this, + }); + } + } + + /** + * Changes the isolation level of the transaction. + * + * @param isolationLevel + */ + async setIsolationLevel(isolationLevel: IsolationLevel): Promise { + await this.sequelize.queryInterface._setIsolationLevel(this, { + ...this.options, + isolationLevel, + }); + } + + /** + * Begins a transaction + */ + async #begin(): Promise { + const queryInterface = this.sequelize.queryInterface; + + if (this.parent) { + return queryInterface._createSavepoint(this.parent, { + ...this.options, + savepointName: this.#name, + }); + } + + await queryInterface._startTransaction(this, { + ...this.options, + readOnly: this.sequelize.dialect.supports.startTransaction.readOnly + ? this.options.readOnly + : false, + transactionName: this.#name, + }); + } + + #cleanup(): void { + // 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.sequelize.pool.release(this.#connection); + this.#connection.uuid = undefined; + this.#connection = undefined; + } + + /** + * Kills the connection this transaction uses. + * Used as a last resort, for instance because COMMIT or ROLLBACK resulted in an error + * and the transaction is left in a broken state, + * and releasing the connection to the pool would be dangerous. + */ + async #forceCleanup(): Promise { + // 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.#connection.uuid = undefined; + + const connection = this.#connection; + this.#connection = undefined; + + await this.sequelize.pool.destroy(connection); + } + + /** + * Adds a hook that is run after a transaction is committed. + * + * @param callback A callback function that is called with the transaction + */ + afterCommit(callback: TransactionCallback): this { + if (typeof callback !== 'function') { + throw new TypeError('"callback" must be a function'); + } + + this.#afterCommitHooks.add(callback); + + return this; + } + + /** + * Adds a hook that is run after a transaction is rolled back. + * + * @param callback A callback function that is called with the transaction + */ + afterRollback(callback: TransactionCallback): this { + if (typeof callback !== 'function') { + throw new TypeError('"callback" must be a function'); + } + + this.#afterRollbackHooks.add(callback); + + return this; + } + + /** + * Adds a hook that is run after a transaction completes, no matter if it was committed or rolled back. + * + * @param callback A callback function that is called with the transaction + */ + afterTransaction(callback: TransactionCallback): this { + if (typeof callback !== 'function') { + throw new TypeError('"callback" must be a function'); + } + + this.#afterHooks.add(callback); + + return this; + } + + /** + * Types can be set per-transaction by passing `options.type` to `sequelize.transaction`. + * Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`. + * Sqlite only. + * + * Pass in the desired level as the first argument: + * + * @example + * 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) { + * // do something with the err. + * } + * + * @property DEFERRED + * @property IMMEDIATE + * @property EXCLUSIVE + * + * @deprecated use the {@link TransactionType} export + */ + static get TYPES() { + return TransactionType; + } + + /** + * Isolation levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`. + * Sequelize uses the default isolation level of the database, you can override this by passing `options.isolationLevel` in Sequelize constructor options. + * + * Pass in the desired level as the first argument: + * + * @example + * 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) { + * // do something with the err. + * } + * + * @property READ_UNCOMMITTED + * @property READ_COMMITTED + * @property REPEATABLE_READ + * @property SERIALIZABLE + * + * @deprecated use the {@link IsolationLevel} export + */ + static get ISOLATION_LEVELS() { + return IsolationLevel; + } + + /** + * Possible options for row locking. Used in conjunction with `find` calls: + * + * @example + * // t1 is a transaction + * Model.findAll({ + * where: ..., + * transaction: t1, + * lock: t1.LOCK... + * }); + * + * @example Postgres also supports specific locks while eager loading by using OF: + * ```ts + * UserModel.findAll({ + * where: ..., + * include: [TaskModel, ...], + * transaction: t1, + * lock: { + * level: t1.LOCK..., + * of: UserModel + * } + * }); + * ``` + * + * UserModel will be locked but TaskModel won't! + * + * @example You can also skip locked rows: + * ```ts + * // t1 is a transaction + * Model.findAll({ + * where: ..., + * transaction: t1, + * lock: true, + * skipLocked: true + * }); + * ``` + * + * The query will now return any rows that aren't locked by another transaction + * + * @returns possible options for row locking + * @property UPDATE + * @property SHARE + * @property KEY_SHARE Postgres 9.3+ only + * @property NO_KEY_UPDATE Postgres 9.3+ only + * + * @deprecated use the {@link Lock} export + */ + static get LOCK() { + return Lock; + } + + /** + * Same as {@link Transaction.LOCK}, but can also be called on instances of + * transactions to get possible options for row locking directly from the + * instance. + * + * @deprecated use the {@link Lock} export + */ + get LOCK() { + return Lock; + } + + /** + * Get the root transaction if nested, or self if this is a root transaction + */ + get rootTransaction(): Transaction { + if (this.parent !== null) { + return this.parent.rootTransaction; + } + + return this; + } +} + +/** + * Isolations levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`. + * Default to `REPEATABLE_READ` but you can override the default isolation level by passing `options.isolationLevel` in `new Sequelize`. + * + * The possible isolations levels to use when starting a transaction: + * + * ```js + * { + * READ_UNCOMMITTED: "READ UNCOMMITTED", + * READ_COMMITTED: "READ COMMITTED", + * REPEATABLE_READ: "REPEATABLE READ", + * SERIALIZABLE: "SERIALIZABLE" + * } + * ``` + * + * Pass in the desired level as the first argument: + * + * ```js + * try { + * await sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { + * // your transactions + * }); + * // transaction has been committed. Do something after the commit if required. + * } catch(err) { + * // do something with the err. + * } + * ``` + */ +export enum IsolationLevel { + READ_UNCOMMITTED = 'READ UNCOMMITTED', + READ_COMMITTED = 'READ COMMITTED', + REPEATABLE_READ = 'REPEATABLE READ', + SERIALIZABLE = 'SERIALIZABLE', +} + +export enum TransactionType { + DEFERRED = 'DEFERRED', + IMMEDIATE = 'IMMEDIATE', + EXCLUSIVE = 'EXCLUSIVE', +} + +/** + * Possible options for row locking. Used in conjunction with `find` calls: + * + * Usage: + * ```js + * import { LOCK } from '@sequelize/core'; + * + * Model.findAll({ + * transaction, + * lock: LOCK.UPDATE, + * }); + * ``` + * + * Postgres also supports specific locks while eager loading by using OF: + * ```js + * import { LOCK } from '@sequelize/core'; + * + * UserModel.findAll({ + * transaction, + * lock: { + * level: LOCK.KEY_SHARE, + * of: UserModel, + * }, + * }); + * ``` + * UserModel will be locked but other models won't be! + * + * [Read more on transaction locks here](https://sequelize.org/docs/v7/querying/transactions/#locks) + */ +export enum Lock { + UPDATE = 'UPDATE', + SHARE = 'SHARE', + /** + * Postgres 9.3+ only + */ + KEY_SHARE = 'KEY SHARE', + /** + * Postgres 9.3+ only + */ + NO_KEY_UPDATE = 'NO KEY UPDATE', +} + +export enum TransactionNestMode { + /** + * In this mode, nesting a transaction block in another will reuse the parent transaction + * if its options are compatible (or throw an error otherwise). + * + * This is the default mode. + */ + reuse = 'reuse', + + /** + * In this mode, nesting a transaction block will cause the creation of a SAVEPOINT + * on the current transaction if the options provided to the nested transaction block are compatible with the parent one. + */ + savepoint = 'savepoint', + + /** + * In this mode, nesting a transaction block will always create a new transaction, in a separate connection. + * This mode is equivalent to setting the "transaction" option to "null" in the nested transaction block. + * + * Be very careful when using this mode, as it can easily lead to transaction deadlocks if used improperly. + */ + separate = 'separate', +} + +/** + * Options provided when the transaction is created + */ +export interface TransactionOptions extends Logging { + /** + * Whether this transaction will only be used to read data. + * Used to determine whether sequelize is allowed to use a read replication server. + */ + readOnly?: boolean | undefined; + + /** + * Sets the isolation level of the transaction. + */ + isolationLevel?: IsolationLevel | null | undefined; + + /** + * Sets the type of the transaction. Sqlite only + */ + type?: TransactionType | undefined; + + /** + * Sets the constraints to be deferred or immediately checked. PostgreSQL only + */ + constraintChecking?: ConstraintChecking | Class | undefined; + + /** + * Parent transaction. + * Will be retrieved from CLS automatically if not provided or if null. + */ + transaction?: Transaction | null | undefined; +} + +export type NormalizedTransactionOptions = StrictRequiredBy< + Omit, + 'isolationLevel' | 'readOnly' +> & { + constraintChecking?: ConstraintChecking | undefined; + transactionType?: TransactionType | undefined; +}; + +/** + * Options accepted by {@link Sequelize#transaction}. + */ +export interface ManagedTransactionOptions extends TransactionOptions { + /** + * How the transaction block should behave if a parent transaction block exists. + */ + nestMode?: TransactionNestMode; +} + +export function normalizeTransactionOptions( + sequelize: Sequelize, + options: TransactionOptions = EMPTY_OBJECT, +): NormalizedTransactionOptions { + assertSupportedTransactionOptions(sequelize, options); + + return { + ...options, + transactionType: + options.type ?? + (sequelize.dialect.supports.startTransaction.transactionType + ? sequelize.options.transactionType + : undefined), + isolationLevel: + options.isolationLevel === undefined + ? (sequelize.options.isolationLevel ?? null) + : options.isolationLevel, + readOnly: options.readOnly ?? false, + constraintChecking: + typeof options.constraintChecking === 'function' + ? new options.constraintChecking() + : options.constraintChecking, + }; +} + +export function assertTransactionIsCompatibleWithOptions( + transaction: Transaction, + options: NormalizedTransactionOptions, +) { + if (options.isolationLevel !== transaction.options.isolationLevel) { + throw new Error( + `Requested isolation level (${options.isolationLevel ?? 'unspecified'}) is not compatible with the one of the existing transaction (${transaction.options.isolationLevel ?? 'unspecified'})`, + ); + } + + if (options.readOnly !== transaction.options.readOnly) { + throw new Error( + `Requested a transaction in ${options.readOnly ? 'read-only' : 'read/write'} mode, which is not compatible with the existing ${transaction.options.readOnly ? 'read-only' : 'read/write'} transaction`, + ); + } + + if (options.transactionType !== transaction.options.transactionType) { + throw new Error( + `Requested transaction type (${options.transactionType}) is not compatible with the one of the existing transaction (${transaction.options.transactionType})`, + ); + } + + if ( + options.constraintChecking !== transaction.options.constraintChecking && + !options.constraintChecking?.isEqual(transaction.options.constraintChecking) + ) { + throw new Error( + `Requested transaction constraintChecking (${options.constraintChecking ?? 'none'}) is not compatible with the one of the existing transaction (${transaction.options.constraintChecking ?? 'none'})`, + ); + } +} + +function assertSupportedTransactionOptions( + sequelize: Sequelize, + options: TransactionOptions | NormalizedTransactionOptions, +) { + if ( + (('type' in options && options.type) || + ('transactionType' in options && options.transactionType)) && + !sequelize.dialect.supports.startTransaction.transactionType + ) { + throw new Error(`The ${sequelize.dialect.name} dialect does not support transaction types.`); + } +} diff --git a/packages/core/src/utils/array.ts b/packages/core/src/utils/array.ts new file mode 100644 index 000000000000..5d5f5806512f --- /dev/null +++ b/packages/core/src/utils/array.ts @@ -0,0 +1,20 @@ +/** + * Checks if 2 arrays intersect. + * + * @param arr1 + * @param arr2 + * @private + */ +export function intersects(arr1: T[], arr2: T[]): boolean { + return arr1.some(v => arr2.includes(v)); +} + +/** + * Returns the intersection of two arrays. + * + * @param arr1 + * @param arr2 + */ +export function getIntersection(arr1: T[], arr2: T[]): T[] { + return arr1.filter(v => arr2.includes(v)); +} diff --git a/packages/core/src/utils/attribute-syntax.ts b/packages/core/src/utils/attribute-syntax.ts new file mode 100644 index 000000000000..5fb331f5a73d --- /dev/null +++ b/packages/core/src/utils/attribute-syntax.ts @@ -0,0 +1,276 @@ +import { pojo } from '@sequelize/utils'; +import type { SyntaxNode } from 'bnf-parser'; +import { BNF, Compile, ParseError } from 'bnf-parser'; +import memoize from 'lodash/memoize.js'; +import type { Class } from 'type-fest'; +import { AssociationPath } from '../expression-builders/association-path.js'; +import { Attribute } from '../expression-builders/attribute.js'; +import { Cast } from '../expression-builders/cast.js'; +import type { DialectAwareFn } from '../expression-builders/dialect-aware-fn.js'; +import { Unquote } from '../expression-builders/dialect-aware-fn.js'; +import { JsonPath } from '../expression-builders/json-path.js'; + +/** + * Parses the attribute syntax (the syntax of keys in WHERE POJOs) into its "BaseExpression" representation. + * + * @example + * ```ts + * parseAttribute('id') // => attribute('id') + * parseAttribute('$user.id$') // => association(['user'], 'id') + * parseAttribute('json.key') // => jsonPath(attribute('json'), ['key']) + * parseAttribute('name::number') // => cast(attribute('name'), 'number') + * parseAttribute('json.key::number') // => cast(jsonPath(attribute('json'), ['key']), 'number') + * ``` + * + * @param attribute The syntax to parse + */ +export const parseAttributeSyntax = memoize(parseAttributeSyntaxInternal); + +/** + * Parses the syntax supported by nested JSON properties. + * This is a subset of {@link parseAttributeSyntax}, which does not parse associations, and returns raw data + * instead of a BaseExpression. + */ +export const parseNestedJsonKeySyntax = memoize(parseJsonPropertyKeyInternal); + +/** + * List of supported attribute modifiers. + * They can be specified in the attribute syntax, e.g. `foo:upper` will call the `upper` modifier on the `foo` attribute. + * + * All names should be lowercase, as they are case-insensitive. + */ +const builtInModifiers: Record> = pojo({ + unquote: Unquote, +}); + +function getModifier(name: string): Class { + const ModifierClass = builtInModifiers[name.toLowerCase()]; + if (!ModifierClass) { + throw new Error( + `${name} is not a recognized built-in modifier. Here is the list of supported modifiers: ${Object.keys(builtInModifiers).join(', ')}`, + ); + } + + return ModifierClass; +} + +const attributeParser = (() => { + const advancedAttributeBnf = ` + # Entry points + + ## Used when parsing the attribute + attribute ::= ( ...association | ...identifier ) jsonPath? castOrModifiers?; + + ## Used when parsing a nested JSON path used inside of an attribute + ## Difference with "attribute" is in the first part. Instead of accepting: + ## $association.attribute$ & attribute + ## It accepts: + ## key, "quotedKey", and [0] (index access) + partialJsonPath ::= ( ...indexAccess | ...key ) jsonPath? castOrModifiers? ; + + # Internals + + identifier ::= ( "A"->"Z" | "a"->"z" | digit | "_" )+ ; + digit ::= "0"->"9" ; + number ::= ...digit+ ; + association ::= %"$" identifier ("." identifier)* %"$" ; + jsonPath ::= ( ...indexAccess | ...keyAccess )+ ; + indexAccess ::= %"[" number %"]" ; + keyAccess ::= %"." key ; + # path segments accept dashes without needing to be quoted + key ::= nonEmptyString | ( "A"->"Z" | "a"->"z" | digit | "_" | "-" )+ ; + nonEmptyString ::= ...(%"\\"" (anyExceptQuoteOrBackslash | escapedCharacter)+ %"\\"") ; + escapedCharacter ::= %"\\\\" ( "\\"" | "\\\\" ); + any ::= !"" ; + anyExceptQuoteOrBackslash ::= !("\\"" | "\\\\"); + castOrModifiers ::= (...cast | ...modifier)+; + cast ::= %"::" identifier ; + modifier ::= %":" identifier ; + `; + + const parsedAttributeBnf = BNF.parse(advancedAttributeBnf); + if (parsedAttributeBnf instanceof ParseError) { + throw new Error( + `Failed to initialize attribute syntax parser. This is a Sequelize bug: ${parsedAttributeBnf.toString()}`, + ); + } + + return Compile(parsedAttributeBnf); +})(); + +interface UselessNode extends SyntaxNode { + type: Type; + value: WrappedValue; +} + +export interface StringNode extends SyntaxNode { + type: Type; + value: string; +} + +interface AttributeAst extends SyntaxNode { + type: 'attribute'; + value: [ + attribute: StringNode<'association' | 'identifier'>, + jsonPath: UselessNode< + 'jsonPath?', + [ + UselessNode< + 'jsonPath', + [UselessNode<'(...)+', Array>>] + >, + ] + >, + castOrModifiers: UselessNode< + 'castOrModifiers?', + [ + UselessNode< + 'castOrModifiers', + [UselessNode<'(...)+', Array>>] + >, + ] + >, + ]; +} + +function parseAttributeSyntaxInternal( + code: string, +): Cast | JsonPath | AssociationPath | Attribute | DialectAwareFn { + // This function is expensive (parsing produces a lot of objects), but we cache the final result, so it's only + // going to be slow once per attribute. + const parsed = attributeParser.parse(code, false, 'attribute') as AttributeAst | ParseError; + if (parsed instanceof ParseError) { + throw new TypeError(`Failed to parse syntax of attribute. Parse error at index ${parsed.ref.start.index}: +${code} +${' '.repeat(parsed.ref.start.index)}^`); + } + + const [attributeNode, jsonPathNodeRaw, castOrModifiersNodeRaw] = parsed.value; + + let result: Cast | JsonPath | AssociationPath | Attribute | DialectAwareFn = parseAssociationPath( + attributeNode.value, + ); + + const jsonPathNodes = jsonPathNodeRaw.value[0]?.value[0].value; + if (jsonPathNodes) { + const path = jsonPathNodes.map(pathNode => { + return parseJsonPathSegment(pathNode); + }); + + result = new JsonPath(result, path); + } + + const castOrModifierNodes = castOrModifiersNodeRaw.value[0]?.value[0].value; + if (castOrModifierNodes) { + // casts & modifiers can be chained, the last one is applied last + // foo:upper:lower needs to produce LOWER(UPPER(foo)) + for (const castOrModifierNode of castOrModifierNodes) { + if (castOrModifierNode.type === 'cast') { + result = new Cast(result, castOrModifierNode.value); + continue; + } + + const ModifierClass = getModifier(castOrModifierNode.value); + + result = new ModifierClass(result); + } + } + + return result; +} + +function parseAssociationPath(syntax: string): AssociationPath | Attribute { + const path = syntax.split('.'); + + if (path.length > 1) { + const attr = path.pop()!; + + return new AssociationPath(path, attr); + } + + return new Attribute(syntax); +} + +/** + * Do not mutate this! It is memoized to avoid re-parsing the same path over and over. + */ +export interface ParsedJsonPropertyKey { + readonly pathSegments: ReadonlyArray; + /** + * If it's a string, it's a cast. If it's a class, it's a modifier. + */ + readonly castsAndModifiers: ReadonlyArray>; +} + +interface JsonPathAst extends SyntaxNode { + type: 'partialJsonPath'; + value: [ + firstKey: StringNode<'key' | 'indexAccess'>, + jsonPath: UselessNode< + 'jsonPath?', + [ + UselessNode< + 'jsonPath', + [UselessNode<'(...)+', Array>>] + >, + ] + >, + castOrModifiers: UselessNode< + 'castOrModifiers?', + [ + UselessNode< + 'castOrModifiers', + [UselessNode<'(...)+', Array>>] + >, + ] + >, + ]; +} + +function parseJsonPropertyKeyInternal(code: string): ParsedJsonPropertyKey { + const parsed = attributeParser.parse(code, false, 'partialJsonPath') as JsonPathAst | ParseError; + if (parsed instanceof ParseError) { + throw new TypeError(`Failed to parse syntax of json path. Parse error at index ${parsed.ref.start.index}: +${code} +${' '.repeat(parsed.ref.start.index)}^`); + } + + const [firstKey, jsonPathNodeRaw, castOrModifiersNodeRaw] = parsed.value; + + const pathSegments: Array = [parseJsonPathSegment(firstKey)]; + + const jsonPathNodes = jsonPathNodeRaw.value[0]?.value[0].value; + if (jsonPathNodes) { + for (const pathNode of jsonPathNodes) { + pathSegments.push(parseJsonPathSegment(pathNode)); + } + } + + const castOrModifierNodes = castOrModifiersNodeRaw.value[0]?.value[0].value; + const castsAndModifiers: Array> = []; + + if (castOrModifierNodes) { + // casts & modifiers can be chained, the last one is applied last + // foo:upper:lower needs to produce LOWER(UPPER(foo)) + for (const castOrModifierNode of castOrModifierNodes) { + if (castOrModifierNode.type === 'cast') { + castsAndModifiers.push(castOrModifierNode.value); + continue; + } + + const ModifierClass = getModifier(castOrModifierNode.value); + + castsAndModifiers.push(ModifierClass); + } + } + + return { pathSegments, castsAndModifiers }; +} + +function parseJsonPathSegment(node: StringNode): string | number { + if (node.type === 'indexAccess') { + return Number(node.value); + } + + return node.value; +} diff --git a/packages/core/src/utils/buffer.ts b/packages/core/src/utils/buffer.ts new file mode 100644 index 000000000000..32c831083acd --- /dev/null +++ b/packages/core/src/utils/buffer.ts @@ -0,0 +1,8 @@ +export function makeBufferFromTypedArray(arr: ArrayBufferView | ArrayBuffer): Buffer { + return ArrayBuffer.isView(arr) + ? // To avoid a copy, use the typed array's underlying ArrayBuffer to back + // new Buffer, respecting the "view", i.e. byteOffset and byteLength + Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength) + : // Pass through all other types to `Buffer.from` + Buffer.from(arr); +} diff --git a/packages/core/src/utils/check.ts b/packages/core/src/utils/check.ts new file mode 100644 index 000000000000..0ec691f8f4a9 --- /dev/null +++ b/packages/core/src/utils/check.ts @@ -0,0 +1,96 @@ +import { isIterable } from '@sequelize/utils'; +import pickBy from 'lodash/pickBy'; +import type { AbstractDialect } from '../abstract-dialect/dialect.js'; + +/** + * Some dialects emit an Error with a string code, that are not ErrnoException. + * This serves as a more generic check for those cases. + * + * @param val The value to check + */ +export function isErrorWithStringCode(val: unknown): val is Error & { code: string } { + return ( + val instanceof Error && + // @ts-expect-error -- 'code' doesn't exist on Error, but it's dynamically added by Node + typeof val.code === 'string' + ); +} + +export function isDevEnv(): boolean { + return process.env.NODE_ENV !== 'production'; +} + +/** + * For use in per-dialect implementation of methods to warn the user when they use an option that TypeScript declares as valid, + * but that the dialect they use does not support. + * + * @param methodName The name of the method that received the options + * @param dialect The dialect to which the implementation belongs + * @param allSupportableOptions All options that this method *can* support. The ones that are declared in TypeScript typings. + * @param supportedOptions The subset of options that this dialect *actually does* support. + * @param receivedOptions The user provided options passed to the method. + */ +export function rejectInvalidOptions( + methodName: string, + dialect: AbstractDialect, + allSupportableOptions: Set, + supportedOptions: Iterable | Partial>, + receivedOptions: object, +): void { + const receivedOptionNames = Object.keys( + // This removes any undefined or false values from the object + // It is therefore _essential_ that boolean options are false by default! + pickBy(receivedOptions, value => value !== undefined && value !== false), + ); + const parsedSupportedOptions = parseSupportedOptions(dialect, methodName, supportedOptions); + + const unsupportedOptions = receivedOptionNames.filter(optionName => { + return allSupportableOptions.has(optionName as T) && !parsedSupportedOptions.has(optionName); + }); + + if (unsupportedOptions.length > 0) { + throw buildInvalidOptionReceivedError(methodName, dialect.name, unsupportedOptions); + } +} + +const SUPPORTED_OPTIONS_CACHE = new WeakMap>>(); + +function parseSupportedOptions( + dialect: AbstractDialect, + methodName: string, + rawSupportedOptions: Iterable | Partial>, +): Set { + let dialectCache = SUPPORTED_OPTIONS_CACHE.get(dialect); + if (!dialectCache) { + dialectCache = new Map(); + SUPPORTED_OPTIONS_CACHE.set(dialect, dialectCache); + } + + let supportedOptions: Set | undefined = dialectCache.get(methodName); + if (!supportedOptions) { + if (isIterable(rawSupportedOptions)) { + supportedOptions = new Set(rawSupportedOptions); + } else { + supportedOptions = new Set(); + for (const optionName of Object.keys(rawSupportedOptions)) { + if (rawSupportedOptions[optionName]) { + supportedOptions.add(optionName); + } + } + } + + dialectCache.set(methodName, supportedOptions); + } + + return supportedOptions; +} + +export function buildInvalidOptionReceivedError( + methodName: string, + dialectName: string, + invalidOptions: string[], +): Error { + return new Error( + `The following options are not supported by ${methodName} in ${dialectName}: ${invalidOptions.join(', ')}`, + ); +} diff --git a/packages/core/src/utils/class-to-invokable.ts b/packages/core/src/utils/class-to-invokable.ts new file mode 100644 index 000000000000..615c7255ded8 --- /dev/null +++ b/packages/core/src/utils/class-to-invokable.ts @@ -0,0 +1,23 @@ +type Callable any> = A extends new ( + ...args: infer Args +) => infer Instance + ? A & ((...args: Args) => Instance) + : never; + +/** + * Wraps a constructor to not need the `new` keyword using a proxy. + * Only used for data types. + * + * @param constructor The class instance to wrap as invocable. + * @returns Wrapped class instance. + * @private + */ +export function classToInvokable any>( + constructor: Class, +): Callable { + return new Proxy>(constructor as any, { + apply(_target, _thisArg, args: ConstructorParameters) { + return new constructor(...args); + }, + }); +} diff --git a/packages/core/src/utils/connection-options.ts b/packages/core/src/utils/connection-options.ts new file mode 100644 index 000000000000..1b7bb87fa525 --- /dev/null +++ b/packages/core/src/utils/connection-options.ts @@ -0,0 +1,188 @@ +import type { PickByType } from '@sequelize/utils'; +import { + EMPTY_ARRAY, + inspect, + isString, + join, + parseBoolean, + parseFiniteNumber, + parseSafeInteger, + pojo, +} from '@sequelize/utils'; +import type { StringKeyOf } from 'type-fest'; +import type { AbstractDialect, ConnectionOptions } from '../abstract-dialect/dialect.js'; +import type { NormalizedReplicationOptions, RawConnectionOptions } from '../sequelize'; +import type { PersistedSequelizeOptions } from '../sequelize.internals.js'; + +export function normalizeReplicationConfig( + dialect: Dialect, + connectionOptions: RawConnectionOptions, + replicationOption: PersistedSequelizeOptions['replication'], +): NormalizedReplicationOptions { + const normalizedConnectionOptions = normalizeRawConnectionOptions(dialect, connectionOptions); + + return { + write: { + ...normalizedConnectionOptions, + ...(replicationOption && + replicationOption.write && + normalizeRawConnectionOptions(dialect, replicationOption.write)), + }, + read: !replicationOption + ? EMPTY_ARRAY + : replicationOption.read.map(readOptions => { + return { + ...normalizedConnectionOptions, + ...normalizeRawConnectionOptions(dialect, readOptions), + }; + }), + }; +} + +function normalizeRawConnectionOptions( + dialect: Dialect, + options: RawConnectionOptions, +): ConnectionOptions { + if (isString(options)) { + return dialect.parseConnectionUrl(options); + } + + const { url, ...remainingOptions } = options; + + if (url) { + return { + ...dialect.parseConnectionUrl(url), + ...remainingOptions, + }; + } + + return remainingOptions; +} + +export function parseCommonConnectionUrlOptions(options: { + url: URL | string; + + /** + * The list of protocols that the URL can use + */ + allowedProtocols: readonly string[]; + + /** + * The name of the dialect-specific connection option to use for the hostname + */ + hostname: keyof PickByType; + + /** + * The name of the dialect-specific connection option to use for the port + */ + port: keyof PickByType; + + /** + * The name of the dialect-specific connection option to use for the database name + */ + pathname: keyof PickByType; + + /** + * The name of the dialect-specific connection option to use for the username + * + * If not provided, the username will be ignored + */ + username?: keyof PickByType; + + /** + * The name of the dialect-specific connection option to use for the password + * + * If not provided, the password will be ignored + */ + password?: keyof PickByType; + + /** + * The string options that can be set via the search parameters in the URL + */ + stringSearchParams?: ReadonlyArray>>; + + /** + * The boolean options that can be set via the search parameters in the URL. + * Will be parsed as a boolean. + */ + booleanSearchParams?: ReadonlyArray>>; + + /** + * The number options that can be set via the search parameters in the URL. + * Will be parsed as a JS number. + */ + numberSearchParams?: ReadonlyArray>>; +}): TConnectionOptions { + const url: URL = isString(options.url) ? new URL(options.url) : options.url; + + const assignTo = pojo(); + + const scheme = url.protocol.slice(0, -1); + if (!options.allowedProtocols.includes(scheme)) { + throw new Error( + `URL ${inspect(url.toString())} is not a valid connection URL. Expected the protocol to be one of ${options.allowedProtocols.map(inspect).join(', ')}, but it's ${inspect(scheme)}.`, + ); + } + + if (url.hostname) { + // @ts-expect-error -- the above typings ensure this is a string + assignTo[options.hostname] = decodeURIComponent(url.hostname); + } + + if (url.port) { + // @ts-expect-error -- the above typings ensure this is a number + assignTo[options.port] = parseSafeInteger.orThrow(url.port); + } + + if (url.pathname) { + // @ts-expect-error -- the above typings ensure this is a string + assignTo[options.pathname] = decodeURIComponent(url.pathname.replace(/^\//, '')); + } + + if (options.username && url.username) { + // @ts-expect-error -- the above typings ensure this is a string + assignTo[options.username] = decodeURIComponent(url.username); + } + + if (options.password && url.password) { + // @ts-expect-error -- the above typings ensure this is a string + assignTo[options.password] = decodeURIComponent(url.password); + } + + const allSearchParams = new Set([ + ...(options.stringSearchParams ?? EMPTY_ARRAY), + ...(options.booleanSearchParams ?? EMPTY_ARRAY), + ...(options.numberSearchParams ?? EMPTY_ARRAY), + ]); + + if (url.searchParams) { + for (const key of url.searchParams.keys()) { + if (!allSearchParams.has(key)) { + throw new Error( + `Option ${inspect(key)} cannot be set as a connection URL search parameter. Only the following options can be set: ${join(allSearchParams, ', ')}`, + ); + } + + if (options.stringSearchParams?.includes(key as any)) { + // @ts-expect-error -- the above typings ensure this is a string + assignTo[key] = url.searchParams.get(key)!; + } + + try { + if (options.booleanSearchParams?.includes(key as any)) { + // @ts-expect-error -- the above typings ensure this is a boolean + assignTo[key] = parseBoolean.orThrow(url.searchParams.get(key)); + } + + if (options.numberSearchParams?.includes(key as any)) { + // @ts-expect-error -- the above typings ensure this is a number + assignTo[key] = parseFiniteNumber.orThrow(url.searchParams.get(key)); + } + } catch (error) { + throw new Error(`Could not parse URL search parameter ${key}`, { cause: error }); + } + } + } + + return assignTo; +} diff --git a/packages/core/src/utils/dayjs.ts b/packages/core/src/utils/dayjs.ts new file mode 100644 index 000000000000..54f1812ecc13 --- /dev/null +++ b/packages/core/src/utils/dayjs.ts @@ -0,0 +1,35 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +const history = new Map(); + +export function timeZoneToOffsetString(timeZone: string) { + if (isValidTimeZone(timeZone)) { + return dayjs().tz(timeZone).format('Z'); + } + + throw new Error(`Invalid time zone: ${timeZone}`); +} + +export function isValidTimeZone(tz: string) { + if (history.has(tz)) { + return history.get(tz); + } + + let status: boolean; + try { + Intl.DateTimeFormat(undefined, { timeZone: tz }); + + status = true; + } catch { + status = false; + } + + history.set(tz, status); + + return status; +} diff --git a/packages/core/src/utils/deprecations.ts b/packages/core/src/utils/deprecations.ts new file mode 100644 index 000000000000..21eb99d48e21 --- /dev/null +++ b/packages/core/src/utils/deprecations.ts @@ -0,0 +1,142 @@ +import { deprecate } from 'node:util'; + +const noop = () => { + /* noop */ +}; + +export const noTrueLogging = deprecate( + noop, + 'The logging-option should be either a function or false. Default: console.log', + 'SEQUELIZE0002', +); +export const noDoubleNestedGroup = deprecate( + noop, + 'Passing a double nested nested array to `group` is unsupported and will be removed in v6.', + 'SEQUELIZE0005', +); +export const unsupportedEngine = deprecate( + noop, + 'This database engine version is not supported, please update your database server. More information https://sequelize.org/releases/', + 'SEQUELIZE0006', +); +export const useErrorCause = deprecate( + noop, + 'The "parent" and "original" properties in Sequelize errors have been replaced with the native "cause" property. Use that one instead.', + 'SEQUELIZE0007', +); +export const scopeRenamedToWithScope = deprecate( + noop, + 'Model.scope has been renamed to Model.withScope, and Model.unscoped has been renamed to Model.withoutScope', + 'SEQUELIZE0008', +); +export const schemaRenamedToWithSchema = deprecate( + noop, + 'Model.schema has been renamed to Model.withSchema', + 'SEQUELIZE0009', +); +export const noSequelizeDataType = deprecate( + noop, + `Accessing DataTypes on the Sequelize constructor is deprecated. Use the DataTypes object instead. +e.g, instead of using Sequelize.STRING, use DataTypes.STRING`, + 'SEQUELIZE0010', +); +export const noModelDropSchema = deprecate( + noop, + 'Do not use Model.dropSchema. Use Sequelize#dropSchema or QueryInterface#dropSchema instead', + 'SEQUELIZE0011', +); +export const movedSequelizeParam = deprecate( + noop, + 'The "sequelize" instance has been moved from the second parameter bag to the first parameter bag in "beforeAssociate" and "afterAssociate" hooks', + 'SEQUELIZE0012', +); +export const hooksReworked = deprecate( + noop, + 'Sequelize Hooks methods, such as addHook, runHooks, beforeFind, and afterSync… are deprecated in favor of using the methods available through "sequelize.hooks", "Sequelize.hooks" and "YourModel.hooks".', + 'SEQUELIZE0013', +); +export const doNotUseRealDataType = deprecate( + noop, + 'Sequelize 7 has normalized its FLOAT & DOUBLE data types, and made REAL redundant. FLOAT is now always an IEEE-754 single precision floating point, and DOUBLE a double-precision one. Use either instead of REAL.', + 'SEQUELIZE0014', +); +export const noSchemaParameter = deprecate( + noop, + 'The schema parameter in QueryInterface#describeTable has been deprecated, use a TableNameWithSchema object to specify the schema or set the schema globally in the options.', + 'SEQUELIZE0015', +); +export const noSchemaDelimiterParameter = deprecate( + noop, + 'The schemaDelimiter parameter in QueryInterface#describeTable has been deprecated, use a TableNameWithSchema object to specify the schemaDelimiter.', + 'SEQUELIZE0016', +); +export const columnToAttribute = deprecate( + noop, + 'The @Column decorator has been renamed to @Attribute.', + 'SEQUELIZE0017', +); +export const fieldToColumn = deprecate( + noop, + 'The "field" option in attribute definitions has been renamed to "columnName".', + 'SEQUELIZE0018', +); +export const noModelTableName = deprecate( + noop, + 'Model.tableName has been replaced with the more complete Model.modelDefinition.table, or Model.table', + 'SEQUELIZE0019', +); +export const noNewModel = deprecate( + noop, + `Do not use "new YourModel()" to instantiate a model. Use "YourModel.build()" instead. The previous option is being removed to resolve a conflict with class properties. See https://github.com/sequelize/sequelize/issues/14300#issuecomment-1355188077 for more information.`, + 'SEQUELIZE0020', +); +export const noOpCol = deprecate( + noop, + 'Do not use Op.col, use col(), attribute(), or identifier() instead. Read more about these in the Raw Queries guide in the sequelize docs.', + 'SEQUELIZE0021', +); +export const noSqlJson = deprecate( + noop, + 'The json() function used to generate JSON queries is deprecated. All of its features are available through where(), attribute() or jsonPath(). Some of its features have been removed but can be replicated using the "sql" tag. See our Sequelize 7 upgrade guide.', + 'SEQUELIZE0022', +); +export const alwaysQuoteIdentifiers = deprecate( + noop, + 'Setting "quoteIdentifiers" to false is unsafe and it will be removed in v8.', + 'SEQUELIZE0023', +); +export const showAllToListSchemas = deprecate( + noop, + 'Do not use "showAllSchemas". Use QueryInterface#listSchemas instead.', + 'SEQUELIZE0024', +); +export const showAllToListTables = deprecate( + noop, + 'Do not use "showAllTables". Use QueryInterface#listTables instead.', + 'SEQUELIZE0025', +); +export const noDataTypesUuid = deprecate( + noop, + 'Do not use DataTypes.UUIDV1 or DataTypes.UUIDV4. Use sql.uuidV1 or sql.uuidV4 instead.', + 'SEQUELIZE0026', +); +export const noSequelizeModel = deprecate( + noop, + 'Do not use sequelize.model(). Use sequelize.models.get or sequelize.models.getOrThrow instead.', + 'SEQUELIZE0028', +); +export const noSequelizeIsDefined = deprecate( + noop, + 'Do not use sequelize.isDefined(). Use sequelize.models.hasByName instead.', + 'SEQUELIZE0029', +); +export const noGetQueryInterface = deprecate( + noop, + 'Do not use sequelize.getQueryInterface(). Use sequelize.queryInterface instead.', + 'SEQUELIZE0030', +); +export const noGetDialect = deprecate( + noop, + 'Do not use sequelize.getDialect(). Use sequelize.dialect.name instead.', + 'SEQUELIZE0031', +); diff --git a/packages/core/src/utils/dialect.ts b/packages/core/src/utils/dialect.ts new file mode 100644 index 000000000000..5d9443db8f17 --- /dev/null +++ b/packages/core/src/utils/dialect.ts @@ -0,0 +1,77 @@ +import { isPlainObject, isString } from '@sequelize/utils'; +import { randomUUID } from 'node:crypto'; +import NodeUtil from 'node:util'; +import { v1 as uuidv1 } from 'uuid'; +import * as DataTypes from '../abstract-dialect/data-types.js'; +import { DialectAwareFn } from '../expression-builders/dialect-aware-fn.js'; +import { noDataTypesUuid } from './deprecations.js'; + +export function toDefaultValue(value: unknown): unknown { + if (value instanceof DialectAwareFn) { + if (value.supportsJavaScript()) { + return value.applyForJavaScript(); + } + + return undefined; + } + + if (typeof value === 'function') { + const tmp = value(); + if (tmp instanceof DataTypes.AbstractDataType) { + return tmp.toSql(); + } + + return tmp; + } + + if (value instanceof DataTypes.UUIDV1) { + noDataTypesUuid(); + + return uuidv1(); + } + + if (value instanceof DataTypes.UUIDV4) { + noDataTypesUuid(); + + return randomUUID(); + } + + if (value instanceof DataTypes.NOW) { + return new Date(); + } + + if (Array.isArray(value)) { + return [...value]; + } + + if (isPlainObject(value)) { + return { ...(value as object) }; + } + + return value; +} + +export function quoteIdentifier(identifier: string, leftTick: string, rightTick: string): string { + if (!isString(identifier)) { + throw new Error( + `quoteIdentifier received a non-string identifier: ${NodeUtil.inspect(identifier)}`, + ); + } + + // TODO [engine:node@>14]: drop regexp, use replaceAll with a string instead. + const leftTickRegExp = new RegExp(`\\${leftTick}`, 'g'); + + if (leftTick === rightTick) { + return leftTick + identifier.replace(leftTickRegExp, leftTick + leftTick) + rightTick; + } + + const rightTickRegExp = new RegExp(`\\${rightTick}`, 'g'); + + return ( + leftTick + + identifier + .replace(leftTickRegExp, leftTick + leftTick) + .replace(rightTickRegExp, rightTick + rightTick) + + rightTick + ); +} diff --git a/packages/core/src/utils/format.ts b/packages/core/src/utils/format.ts new file mode 100644 index 000000000000..6dbd2b8c83f5 --- /dev/null +++ b/packages/core/src/utils/format.ts @@ -0,0 +1,159 @@ +import forIn from 'lodash/forIn'; +import assert from 'node:assert'; +import type { Attributes, Model, ModelStatic, NormalizedAttributeOptions, WhereOptions } from '..'; + +export type FinderOptions = { + attributes?: string[]; + where?: WhereOptions; +}; + +export type MappedFinderOptions = Omit, 'attributes'> & { + // an array of attribute-column mapping, or just attributes + attributes?: Array<[columnName: string, attributeName: string] | string>; +}; + +/** + * Expand and normalize finder options. + * Mutates the "options" parameter. + * + * @param options + * @param Model + */ +export function mapFinderOptions>>( + options: T, + Model: ModelStatic, +): MappedFinderOptions> { + if (Array.isArray(options.attributes)) { + options.attributes = Model._injectDependentVirtualAttributes(options.attributes); + + const modelDefinition = Model.modelDefinition; + options.attributes = options.attributes.filter( + attributeName => !modelDefinition.virtualAttributeNames.has(attributeName), + ); + } + + mapOptionFieldNames(options, Model); + + return options; +} + +/** + * Used to map field names in attributes + * + * Mutates the "options" parameter. + * + * ⚠️ This function does not map the "where" or "having" options, this is handled by QueryGenerator's WHERE generation. + * + * @param options + * @param Model + */ +export function mapOptionFieldNames( + options: FinderOptions>, + Model: ModelStatic, +): MappedFinderOptions> { + // note: parts of Sequelize rely on this function mutating its inputs. + // be aware that these places need to be fixed before trying to make this a pure function. + // - ephys + + const out: MappedFinderOptions> = options; + + if (Array.isArray(options.attributes)) { + out.attributes = options.attributes.map(attributeName => { + // Object lookups will force any variable to strings, we don't want that for special objects etc + if (typeof attributeName !== 'string') { + return attributeName; + } + + // Map attributes to column names + const columnName: string = Model.modelDefinition.getColumnNameLoose(attributeName); + if (columnName !== attributeName) { + return [columnName, attributeName]; + } + + return attributeName; + }); + } + + return out; +} + +/** + * Used to map field names in values + * + * @param dataValues + * @param attributeNames + * @param ModelClass + */ +export function mapValueFieldNames( // TODO: rename to mapAttributesToColumNames? See https://github.com/sequelize/meetings/issues/17 + dataValues: Record, + attributeNames: Iterable, + ModelClass: ModelStatic, +): Record { + const values: Record = Object.create(null); + const modelDefinition = ModelClass.modelDefinition; + + for (const attributeName of attributeNames) { + if ( + dataValues[attributeName] !== undefined && + !modelDefinition.virtualAttributeNames.has(attributeName) + ) { + // Field name mapping + const columnName = modelDefinition.getColumnNameLoose(attributeName); + + values[columnName] = dataValues[attributeName]; + } + } + + return values; +} + +/** + * Removes entries from `hash` whose value is either null or undefined, unless `omitNull` is false or `allowNull` includes that key. + * + * Keys ending with 'Id' are never removed. + * + * @param hash the object from which entries with nullish values will be removed. + * @param omitNull if false, this method returns the object as-is + * @param options + * @param options.allowNull A list of keys that must be preserved even if their value is null or undefined. + */ +export function removeNullishValuesFromHash( + hash: Record, + omitNull: boolean, + options?: { allowNull?: string[] }, +): Record { + let result = hash; + + const allowNull = options?.allowNull ?? []; + + if (!omitNull) { + return result; + } + + const _hash: { [key: string]: any } = Object.create(null); + + forIn(hash, (val: any, key: string) => { + if (allowNull.includes(key) || key.endsWith('Id') || (val !== null && val !== undefined)) { + _hash[key] = val; + } + }); + + result = _hash; + + return result; +} + +export function getColumnName(attribute: NormalizedAttributeOptions): string { + assert(attribute.fieldName != null, 'getColumnName expects a normalized attribute meta'); + + // field is the column name alias + // if no alias is set, fieldName (the JS name) will be used instead. + return attribute.field || attribute.fieldName; +} + +export function getAttributeName(model: ModelStatic, columnName: string): string | null { + return ( + Object.values(model.getAttributes()).find(attribute => attribute.field === columnName) + ?.fieldName ?? null + ); +} diff --git a/packages/core/src/utils/join-sql-fragments.ts b/packages/core/src/utils/join-sql-fragments.ts new file mode 100644 index 000000000000..c0d53b407653 --- /dev/null +++ b/packages/core/src/utils/join-sql-fragments.ts @@ -0,0 +1,103 @@ +import type { SQLFragment, TruthySQLFragment } from '../generic/sql-fragment'; + +function doesNotWantLeadingSpace(str: string): boolean { + return /^[),;]/.test(str); +} + +function doesNotWantTrailingSpace(str: string): boolean { + return str.endsWith('('); +} + +/** + * 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 parts + * @private + */ +function singleSpaceJoinHelper(parts: string[]): string { + // eslint-disable-next-line unicorn/no-array-reduce -- TODO + 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 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 The joined string. + * + * @private + */ +export function joinSQLFragments(array: SQLFragment[]): string { + if (array.length === 0) { + return ''; + } + + const truthyArray: TruthySQLFragment[] = array.filter((x): x is string | SQLFragment[] => + Boolean(x), + ); + const flattenedArray: string[] = truthyArray.map((fragment: TruthySQLFragment) => { + if (Array.isArray(fragment)) { + return joinSQLFragments(fragment); + } + + return fragment; + }); + + // Ensure strings + for (const fragment of flattenedArray) { + if (fragment && typeof fragment !== 'string') { + throw new JoinSQLFragmentsError( + flattenedArray, + fragment, + `Tried to construct a SQL string with a non-string, non-falsy fragment (${fragment}).`, + ); + } + } + + // Trim fragments + const trimmedArray = flattenedArray.map(x => x.trim()); + + // Skip full-whitespace fragments (empty after the above trim) + const nonEmptyStringArray = trimmedArray.filter(x => x !== ''); + + return singleSpaceJoinHelper(nonEmptyStringArray); +} + +export class JoinSQLFragmentsError extends TypeError { + args: SQLFragment[]; + fragment: any; // iirc this error is only used when we get an invalid fragment. + + constructor(args: SQLFragment[], fragment: any, message: string) { + super(message); + + this.args = args; + this.fragment = fragment; + this.name = 'JoinSQLFragmentsError'; + } +} diff --git a/packages/core/src/utils/json.ts b/packages/core/src/utils/json.ts new file mode 100644 index 000000000000..47804463d968 --- /dev/null +++ b/packages/core/src/utils/json.ts @@ -0,0 +1,31 @@ +/** + * Returns a JSON path identifier that is safe to use in a JSON path. + * + * @param identifier - The identifier to quote. + */ +function quoteJsonPathIdentifier(identifier: string): string { + if (/^[a-z_][a-z0-9_]*$/i.test(identifier)) { + return identifier; + } + + // Escape backslashes and double quotes + return `"${identifier.replaceAll(/["\\]/g, s => `\\${s}`)}"`; +} + +/** + * Builds a JSON path expression from a path. + * + * @param path - The path to build the expression from. + */ +export function buildJsonPath(path: ReadonlyArray): string { + let jsonPathStr = '$'; + for (const pathElement of path) { + if (typeof pathElement === 'number') { + jsonPathStr += `[${pathElement}]`; + } else { + jsonPathStr += `.${quoteJsonPathIdentifier(pathElement)}`; + } + } + + return jsonPathStr; +} diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts new file mode 100644 index 000000000000..7d8bd9a38799 --- /dev/null +++ b/packages/core/src/utils/logger.ts @@ -0,0 +1,68 @@ +/** + * @file Sequelize module for debug and deprecation messages. + * It require a `context` for which messages will be printed. + * + * @module logging + * @access package + */ +import nodeDebug from 'debug'; +import util from 'node:util'; + +/** + * The configuration for sequelize's logging interface. + * + * @access package + */ +export interface LoggerConfig { + /** + * The context which the logger should log in. + * + * @default 'sequelize' + */ + context?: string; +} + +export class Logger { + protected config: LoggerConfig; + + constructor({ context = 'sequelize', ...rest }: Partial = {}) { + this.config = { + context, + ...rest, + }; + } + + /** + * Logs a warning in the logger's context. + * + * @param message The message of the warning. + */ + warn(message: string): void { + console.warn(`(${this.config.context}) Warning: ${message}`); + } + + /** + * Uses node's util.inspect to stringify a value. + * + * @param value The value which should be inspected. + * @returns The string of the inspected value. + */ + inspect(value: unknown): string { + return util.inspect(value, { + showHidden: false, + depth: 1, + }); + } + + /** + * Gets a debugger for a context. + * + * @param name The name of the context. + * @returns A debugger interace which can be used to debug. + */ + debugContext(name: string): nodeDebug.Debugger { + return nodeDebug(`${this.config.context}:${name}`); + } +} + +export const logger = new Logger(); diff --git a/packages/core/src/utils/model-utils.ts b/packages/core/src/utils/model-utils.ts new file mode 100644 index 000000000000..eec0ad31f885 --- /dev/null +++ b/packages/core/src/utils/model-utils.ts @@ -0,0 +1,53 @@ +import { isString } from '@sequelize/utils'; +import type { TableOrModel } from '../abstract-dialect/query-generator.types.js'; +import type { TableNameWithSchema } from '../abstract-dialect/query-interface.js'; +import type { Model, ModelStatic } from '../model'; +import { ModelDefinition } from '../model-definition.js'; + +/** + * Returns true if the value is a model subclass. + * + * @param val The value whose type will be checked + */ +export function isModelStatic(val: any): val is ModelStatic { + // TODO: temporary workaround due to cyclic import. Should not be necessary once Model is fully migrated to TypeScript. + const { Model: TmpModel } = require('../model'); + + return typeof val === 'function' && val.prototype instanceof TmpModel; +} + +/** + * Returns true if a & b are the same initial model, ignoring variants created by {@link Model.withSchema}, {@link Model.withScope}, and the like. + * + * The difference with doing `a === b` is that this method will also + * return true if one of the models is scoped, or a variant with a different schema. + * + * @example + * isSameInitialModel(a, a.withScope('myScope')) // true; + * + * @param a + * @param b + */ +export function isSameInitialModel(a: ModelStatic, b: ModelStatic): boolean { + return isModelStatic(a) && isModelStatic(b) && a.getInitialModel() === b.getInitialModel(); +} + +export function extractModelDefinition(tableOrModel: TableOrModel): ModelDefinition | null { + if (tableOrModel instanceof ModelDefinition) { + return tableOrModel; + } + + if (isModelStatic(tableOrModel)) { + return tableOrModel.modelDefinition; + } + + return null; +} + +export function extractTableIdentifier(tableOrModel: TableOrModel): TableNameWithSchema { + if (isString(tableOrModel)) { + return { tableName: tableOrModel }; + } + + return extractModelDefinition(tableOrModel)?.table ?? (tableOrModel as TableNameWithSchema); +} diff --git a/packages/core/src/utils/object.ts b/packages/core/src/utils/object.ts new file mode 100644 index 000000000000..88e6d1e09003 --- /dev/null +++ b/packages/core/src/utils/object.ts @@ -0,0 +1,293 @@ +import type { ReadonlyMapLike } from '@sequelize/utils'; +import { SetView, combinedIterator, map, pojo } from '@sequelize/utils'; +// @ts-expect-error -- lodash/_baseIsNative is not recognized as a separate module for @types/lodash +import baseIsNative from 'lodash/_baseIsNative'; +import cloneDeepWith from 'lodash/cloneDeepWith'; +import forOwn from 'lodash/forOwn'; +import getValue from 'lodash/get'; +import isEqual from 'lodash/isEqual'; +import isFunction from 'lodash/isFunction'; +import isPlainObject from 'lodash/isPlainObject'; +import isUndefined from 'lodash/isUndefined.js'; +import mergeWith from 'lodash/mergeWith'; +import omitBy from 'lodash/omitBy.js'; +import { getComplexKeys } from './where.js'; + +export const EMPTY_SET = new SetView(new Set()); + +/** + * Deeply merges object `b` into `a`. + * Mutates `a`. + * + * Same concept as _.merge, but doesn't overwrite properties that have already been assigned. + * + * @param a + * @param b + */ +export function mergeDefaults(a: T, b: Partial): T { + 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; + } + + // eslint-disable-next-line no-useless-return -- lodash actually wants us to return `undefined` to fallback to the default customizer. + return; + }); +} + +/** + * An alternative to _.merge, which doesn't clone its arguments. + * + * Does not mutate parameters. + * + * Cloning is a bad idea because options arguments may contain references to sequelize + * models - which again reference database libs which don't like to be cloned (in particular pg-native) + * + * @param args + */ +export function merge(...args: object[]): object { + const result: { [key: string]: any } = Object.create(null); + + for (const obj of args) { + forOwn(obj, (value, key) => { + if (value === undefined) { + return; + } + + if (!result[key]) { + result[key] = value; + } else if (isPlainObject(value) && isPlainObject(result[key])) { + result[key] = merge(result[key], value); + } else if (Array.isArray(value) && Array.isArray(result[key])) { + result[key] = [...value, ...result[key]]; + } else { + result[key] = value; + } + }); + } + + return result; +} + +export function cloneDeep(obj: T, onlyPlain?: boolean): T { + return cloneDeepWith(obj, elem => { + // Do not try to customize cloning of arrays or POJOs + if (Array.isArray(elem) || isPlainObject(elem)) { + return; + } + + // If we specified to clone only plain objects & arrays, we ignore everything else + // In any case, don't clone stuff that's an object, but not a plain one - fx example sequelize models and instances + if (onlyPlain || typeof elem === 'object') { + return elem; + } + + // Preserve special data-types like `fn` across clones. _.get() is used for checking up the prototype chain + if (elem && typeof elem.clone === 'function') { + return elem.clone(); + } + }); +} + +/** + * Receives a tree-like object and returns a plain object which depth is 1. + * + * - Input: + * + * { + * name: 'John', + * address: { + * street: 'Fake St. 123', + * coordinates: { + * longitude: 55.6779627, + * latitude: 12.5964313 + * } + * } + * } + * + * - Output: + * + * { + * name: 'John', + * address.street: 'Fake St. 123', + * address.coordinates.latitude: 55.6779627, + * address.coordinates.longitude: 12.5964313 + * } + * + * @param value an Object + * @returns a flattened object + * @private + */ +export function flattenObjectDeep(value: T): T extends object ? Flatten : T { + if (!isPlainObject(value)) { + // TypeScript doesn't know T is an object due to isPlainObject's typings. Cast to any. + return value as any; + } + + const flattenedObj: { [key: string]: any } = Object.create(null); + + function flattenObject(obj: { [key: string]: any }, subPath?: string) { + for (const key of Object.keys(obj)) { + const pathToProperty = subPath ? `${subPath}.${key}` : key; + if (typeof obj[key] === 'object' && obj[key] !== null) { + flattenObject(obj[key], pathToProperty); + } else { + flattenedObj[pathToProperty] = getValue(obj, key); + } + } + + return flattenedObj; + } + + return flattenObject(value) as any; +} + +// taken from +// https://stackoverflow.com/questions/66614528/flatten-object-with-custom-keys-in-typescript +// because this is typescript black magic +type Flatten = object extends T + ? object + : { + [K in keyof T]-?: ( + x: NonNullable extends infer V + ? V extends object + ? V extends readonly any[] + ? Pick + : Flatten extends infer FV + ? { + [P in keyof FV as `${Extract}.${Extract}`]: FV[P]; + } + : never + : Pick + : never, + ) => void; + } extends Record void> + ? O extends unknown + ? { [K in keyof O]: O[K] } + : never + : never; + +/** + * Assigns own and inherited enumerable string and symbol keyed properties of source + * objects to the destination object. + * + * https://lodash.com/docs/4.17.4#defaults + * + * **Note:** This method mutates `object`. + * + * @param objectIn The destination object. + * @param sources The source objects. + * @returns Returns `object`. + * @private + */ +export function defaults( + objectIn: { [key: PropertyKey]: any }, + ...sources: Array<{ [key: PropertyKey]: any }> +): object { + for (const source of sources) { + if (!source) { + continue; + } + + for (const key of getComplexKeys(source)) { + const value = objectIn[key]; + const objectPrototype: { [key: PropertyKey]: any } = Object.prototype; + + if ( + value === undefined || + (isEqual(value, objectPrototype[key]) && !Object.hasOwn(objectIn, key)) + ) { + objectIn[key] = source[key]; + } + } + } + + return objectIn; +} + +type NoUndefinedField = { [P in keyof T]: Exclude }; + +type NoNullishField = { [P in keyof T]: Exclude }; + +export function removeUndefined(val: T): NoUndefinedField { + return omitBy(val, isUndefined) as NoUndefinedField; +} + +export function removeNullish(val: T): NoNullishField { + return omitBy(val, v => v == null) as NoNullishField; +} + +export function getObjectFromMap( + aMap: Map | ReadonlyMapLike, +): Record { + const record = Object.create(null); + + for (const key of aMap.keys()) { + record[key] = aMap.get(key); + } + + return record; +} + +/** + * Returns all own keys of an object, including non-enumerable ones and symbols. + * + * @param object + */ +export function getAllOwnKeys(object: object): IterableIterator { + return combinedIterator( + Object.getOwnPropertySymbols(object), + Object.getOwnPropertyNames(object), + ); +} + +/** + * Returns all own entries of an object, including non-enumerable ones and symbols. + * + * @param obj + */ +export function getAllOwnEntries(obj: { + [s: PropertyKey]: T; +}): IterableIterator<[key: string | symbol, value: T]>; +export function getAllOwnEntries( + obj: object, +): IterableIterator<[key: string | symbol, value: unknown]>; +export function getAllOwnEntries( + obj: object, +): IterableIterator<[key: string | symbol, value: unknown]> { + // @ts-expect-error -- obj[key] is implicitly any + return map(getAllOwnKeys(obj), key => [key, obj[key]]); +} + +export function untypedMultiSplitObject>( + obj: T, + groups: Record, +): [groups: Record>, unseenKeys: Set] { + const outputGroups: Record> = pojo(); + const unseenKeys = new Set(Object.keys(obj)); + + for (const groupName of Object.keys(groups)) { + const groupKeys = groups[groupName]; + + const groupValues: any = pojo(); + outputGroups[groupName] = groupValues; + + for (const key of groupKeys) { + if (obj[key] === undefined) { + continue; + } + + groupValues[key] = obj[key]; + unseenKeys.delete(key); + } + } + + return [outputGroups, unseenKeys]; +} diff --git a/packages/core/src/utils/query-builder-utils.ts b/packages/core/src/utils/query-builder-utils.ts new file mode 100644 index 000000000000..394988096a4e --- /dev/null +++ b/packages/core/src/utils/query-builder-utils.ts @@ -0,0 +1,44 @@ +import isEmpty from 'lodash/isEmpty.js'; +import type { AbstractDialect } from '../abstract-dialect/dialect.js'; +import * as DataTypes from '../data-types'; +import { DialectAwareFn } from '../expression-builders/dialect-aware-fn.js'; +import { getOperators } from './where.js'; + +/** + * Determine if the default value provided exists and can be described + * in a db schema using the DEFAULT directive. + * + * @param value Any default value. + * @param dialect + * @private + */ +export function defaultValueSchemable(value: unknown, dialect: AbstractDialect): boolean { + if (value === undefined) { + return false; + } + + if (value instanceof DialectAwareFn) { + return value.supportsDialect(dialect); + } + + // TODO this will be schemable when all supported db + // have been normalized for this case + if (value instanceof DataTypes.NOW) { + return false; + } + + if (value instanceof DataTypes.UUIDV1 || value instanceof DataTypes.UUIDV4) { + return false; + } + + return typeof value !== 'function'; +} + +/** + * Returns true if a where clause is empty, even with Symbols + * + * @param obj + */ +export function isWhereEmpty(obj: object): boolean { + return Boolean(obj) && isEmpty(obj) && getOperators(obj).length === 0; +} diff --git a/packages/core/src/utils/sql.ts b/packages/core/src/utils/sql.ts new file mode 100644 index 000000000000..445d967920f2 --- /dev/null +++ b/packages/core/src/utils/sql.ts @@ -0,0 +1,563 @@ +import isPlainObject from 'lodash/isPlainObject'; +import type { AbstractDialect, BindCollector } from '../abstract-dialect/dialect.js'; +import type { EscapeOptions } from '../abstract-dialect/query-generator-typescript.js'; +import type { AddLimitOffsetOptions } from '../abstract-dialect/query-generator.internal-types.js'; +import type { AbstractQueryGenerator } from '../abstract-dialect/query-generator.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; +import type { BindOrReplacements, QueryRawOptions, Sequelize } from '../sequelize.js'; + +type OnBind = (oldName: string) => string; + +type MapSqlOptions = { + onPositionalReplacement?(): void; +}; + +/** + * Internal function used by {@link mapBindParameters} and {@link injectReplacements}. + * Parse bind parameters & replacements in places where they would be valid SQL values. + * + * @param sqlString The SQL that contains the bind parameters & replacements + * @param dialect The dialect of the SQL + * @param replacements if provided, this method will replace ':named' replacements & positional replacements (?) + * @param onBind if provided, sequelize will call this method each time a $bind parameter is found, and replace it with its output. + * @param options Options + * + * @returns The SQL with bind parameters & replacements rewritten in their dialect-specific syntax. + */ +function mapBindParametersAndReplacements( + sqlString: string, + dialect: AbstractDialect, + replacements?: BindOrReplacements, + onBind?: OnBind, + options?: MapSqlOptions, +): string { + const isNamedReplacements = isPlainObject(replacements); + const isPositionalReplacements = Array.isArray(replacements); + const escapeOptions: EscapeOptions = { replacements }; + + let lastConsumedPositionalReplacementIndex = -1; + + let output: string = ''; + + let currentDollarStringTagName = null; + let isString = false; + let isColumn = false; + let previousSliceEnd = 0; + let isSingleLineComment = false; + let isCommentBlock = false; + let stringIsBackslashEscapable = false; + + for (let i = 0; i < sqlString.length; i++) { + const char = sqlString[i]; + + if (isColumn) { + if (char === dialect.TICK_CHAR_RIGHT) { + isColumn = false; + } + + continue; + } + + if (isString) { + if (char === `'` && (!stringIsBackslashEscapable || !isBackslashEscaped(sqlString, i - 1))) { + isString = false; + stringIsBackslashEscapable = false; + } + + continue; + } + + if (currentDollarStringTagName !== null) { + if (char !== '$') { + continue; + } + + const remainingString = sqlString.slice(i, sqlString.length); + + const dollarStringStartMatch = remainingString.match(/^\$(?[a-z_][0-9a-z_]*)?(\$)/i); + const tagName = dollarStringStartMatch?.groups?.name ?? ''; + + if (currentDollarStringTagName === tagName) { + currentDollarStringTagName = null; + } + + continue; + } + + if (isSingleLineComment) { + if (char === '\n') { + isSingleLineComment = false; + } + + continue; + } + + if (isCommentBlock) { + if (char === '*' && sqlString[i + 1] === '/') { + isCommentBlock = false; + } + + continue; + } + + if (char === dialect.TICK_CHAR_LEFT) { + isColumn = true; + continue; + } + + if (char === `'`) { + isString = true; + + // The following query is supported in almost all dialects, + // SELECT E'test'; + // but postgres interprets it as an E-prefixed string, while other dialects interpret it as + // SELECT E 'test'; + // which selects the type E and aliases it to 'test'. + + stringIsBackslashEscapable = + // all ''-style strings in this dialect can be backslash escaped + dialect.canBackslashEscape() || + // checking if this is a postgres-style E-prefixed string, which also supports backslash escaping + (dialect.supports.escapeStringConstants && + // is this a E-prefixed string, such as `E'abc'`, `e'abc'` ? + (sqlString[i - 1] === 'E' || sqlString[i - 1] === 'e') && + // reject things such as `AE'abc'` (the prefix must be exactly E) + canPrecedeNewToken(sqlString[i - 2])); + + continue; + } + + if (char === '-' && sqlString.slice(i, i + 3) === '-- ') { + isSingleLineComment = true; + continue; + } + + if (char === '/' && sqlString.slice(i, i + 2) === '/*') { + isCommentBlock = true; + continue; + } + + // either the start of a $bind parameter, or the start of a $tag$string$tag$ + if (char === '$') { + const previousChar = sqlString[i - 1]; + + // we are part of an identifier + if (/[0-9a-z_]/i.test(previousChar)) { + continue; + } + + const remainingString = sqlString.slice(i, sqlString.length); + + const dollarStringStartMatch = remainingString.match(/^\$(?[a-z_][0-9a-z_]*)?\$/i); + if (dollarStringStartMatch) { + currentDollarStringTagName = dollarStringStartMatch.groups?.name ?? ''; + i += dollarStringStartMatch[0].length - 1; + + continue; + } + + if (onBind) { + // we want to be conservative with what we consider to be a bind parameter to avoid risk of conflict with potential operators + // users need to add a space before the bind parameter (except after '(', ',', and '=') + if (!canPrecedeNewToken(previousChar)) { + continue; + } + + // detect the bind param if it's a valid identifier and it's followed either by '::' (=cast), ')', whitespace of it's the end of the query. + const match = remainingString.match( + /^\$(?([a-z_][0-9a-z_]*|[1-9][0-9]*))(?:\]|\)|,|$|\s|::|;)/i, + ); + const bindParamName = match?.groups?.name; + if (!bindParamName) { + continue; + } + + // we found a bind parameter + const newName: string = onBind(bindParamName); + + // add everything before the bind parameter name + output += sqlString.slice(previousSliceEnd, i); + // continue after the bind parameter name + previousSliceEnd = i + bindParamName.length + 1; + + output += newName; + } + + continue; + } + + if (isNamedReplacements && char === ':') { + const previousChar = sqlString[i - 1]; + // we want to be conservative with what we consider to be a replacement to avoid risk of conflict with potential operators + // users need to add a space before the bind parameter (except after '(', ',', '=', and '[' (for arrays)) + if (!canPrecedeNewToken(previousChar) && previousChar !== '[') { + continue; + } + + const remainingString = sqlString.slice(i, sqlString.length); + + const match = remainingString.match(/^:(?[a-z_][0-9a-z_]*)(?:\)|,|$|\s|::|;|])/i); + const replacementName = match?.groups?.name; + if (!replacementName) { + continue; + } + + // @ts-expect-error -- isPlainObject does not tell typescript that replacements is a plain object, not an array + const replacementValue = replacements[replacementName]; + if ( + !Object.hasOwn(replacements as object, replacementName) || + replacementValue === undefined + ) { + throw new Error( + `Named replacement ":${replacementName}" has no entry in the replacement map.`, + ); + } + + const escapedReplacement = escapeValueWithBackCompat( + replacementValue, + dialect, + escapeOptions, + ); + + // add everything before the bind parameter name + output += sqlString.slice(previousSliceEnd, i); + // continue after the bind parameter name + previousSliceEnd = i + replacementName.length + 1; + + output += escapedReplacement; + + continue; + } + + if (isPositionalReplacements && char === '?') { + const previousChar = sqlString[i - 1]; + + // we want to be conservative with what we consider to be a replacement to avoid risk of conflict with potential operators + // users need to add a space before the bind parameter (except after '(', ',', '=', and '[' (for arrays)) + // -> [ is temporarily added to allow 'ARRAY[:name]' to be replaced + // https://github.com/sequelize/sequelize/issues/14410 will make this obsolete. + if (!canPrecedeNewToken(previousChar) && previousChar !== '[') { + continue; + } + + // don't parse ?| and ?& operators as replacements + const nextChar = sqlString[i + 1]; + if (nextChar === '|' || nextChar === '&') { + continue; + } + + // this is a positional replacement + if (options?.onPositionalReplacement) { + options.onPositionalReplacement(); + } + + const replacementIndex = ++lastConsumedPositionalReplacementIndex; + const replacementValue = replacements[lastConsumedPositionalReplacementIndex]; + + if (replacementValue === undefined) { + throw new Error( + `Positional replacement (?) ${replacementIndex} has no entry in the replacement map (replacements[${replacementIndex}] is undefined).`, + ); + } + + const escapedReplacement = escapeValueWithBackCompat( + replacementValue, + dialect, + escapeOptions, + ); + + // add everything before the bind parameter name + output += sqlString.slice(previousSliceEnd, i); + // continue after the bind parameter name + previousSliceEnd = i + 1; + + output += escapedReplacement; + } + } + + if (isString) { + throw new Error( + `The following SQL query includes an unterminated string literal:\n${sqlString}`, + ); + } + + output += sqlString.slice(previousSliceEnd, sqlString.length); + + return output; +} + +function escapeValueWithBackCompat( + value: unknown, + dialect: AbstractDialect, + escapeOptions: EscapeOptions, +): string { + // Arrays used to be escaped as sql lists, not sql arrays + // now they must be escaped as sql arrays, and the old behavior has been moved to the list() function. + // The problem is that if we receive a list of list, there are cases where we don't want the extra parentheses around the list, + // such as in the case of a bulk insert. + // As a workaround, non-list arrays that contain dynamic values are joined with commas. + if (Array.isArray(value) && value.some(item => item instanceof BaseSqlExpression)) { + return value.map(item => dialect.queryGenerator.escape(item, escapeOptions)).join(', '); + } + + return dialect.queryGenerator.escape(value, escapeOptions); +} + +function canPrecedeNewToken(char: string | undefined): boolean { + return char === undefined || /[\s([>,=]/.test(char); +} + +/** + * Maps bind parameters from Sequelize's format ($1 or $name) to the dialect's format. + * + * @param sqlString + * @param dialect + */ +export function mapBindParameters( + sqlString: string, + dialect: AbstractDialect, +): { + sql: string; + bindOrder: string[] | null; + parameterSet: Set; +} { + const parameterCollector = dialect.createBindCollector(); + const parameterSet = new Set(); + + const newSql = mapBindParametersAndReplacements( + sqlString, + dialect, + undefined, + foundBindParamName => { + parameterSet.add(foundBindParamName); + + return parameterCollector.collect(foundBindParamName); + }, + ); + + return { sql: newSql, bindOrder: parameterCollector.getBindParameterOrder(), parameterSet }; +} + +export function injectReplacements( + sqlString: string, + dialect: AbstractDialect, + replacements: BindOrReplacements, + opts?: MapSqlOptions, +): string { + if (replacements == null) { + return sqlString; + } + + if (!Array.isArray(replacements) && !isPlainObject(replacements)) { + throw new TypeError( + `"replacements" must be an array or a plain object, but received ${JSON.stringify(replacements)} instead.`, + ); + } + + return mapBindParametersAndReplacements(sqlString, dialect, replacements, undefined, opts); +} + +function isBackslashEscaped(string: string, pos: number): boolean { + let escaped = false; + for (let i = pos; i >= 0; i--) { + const char = string[i]; + if (char !== '\\') { + break; + } + + escaped = !escaped; + } + + return escaped; +} + +/** + * Collector for dialects that only support ordered parameters, and whose order matter in the SQL query. (e.g. dialects that use the "?" token for parameters) + * + * @param token The token to use as the bind parameter (e.g. '?' in mysql). + */ +export function createUnspecifiedOrderedBindCollector(token = '?'): BindCollector { + const parameterOrder: string[] = []; + + return { + collect(bindParameterName) { + parameterOrder.push(bindParameterName); + + return token; + }, + getBindParameterOrder() { + return parameterOrder; + }, + }; +} + +/** + * Collector for dialects that only support ordered parameters, but whose order does not matter in the SQL query (e.g. dialect that support parameters names like '$1') + * + * Parameter index starts at 1! + * + * @param prefix The prefix to place before the name of the bind parameter (e.g. @ for mssql, $ for sqlite/postgres) + */ +export function createSpecifiedOrderedBindCollector(prefix = '$'): BindCollector { + const parameterOrder: string[] = []; + + return { + collect(bindParameterName) { + const cachedPosition = parameterOrder.indexOf(bindParameterName); + if (cachedPosition === -1) { + parameterOrder.push(bindParameterName); + + return `${prefix}${parameterOrder.length}`; + } + + return `${prefix}${cachedPosition + 1}`; + }, + getBindParameterOrder() { + return parameterOrder; + }, + }; +} + +/** + * Collector for dialects that support named bind parameters (e.g. @name, $name, etc) + * + * @param parameterPrefix The prefix to place before the name of the bind parameter (e.g. @ for mssql, $ for sqlite/postgres) + */ +export function createNamedParamBindCollector(parameterPrefix: string): BindCollector { + return { + collect(bindParameterName: string) { + return parameterPrefix + bindParameterName; + }, + getBindParameterOrder() { + return null; + }, + }; +} + +export function assertNoReservedBind(bind: BindOrReplacements): void { + if (Array.isArray(bind)) { + return; + } + + for (const key of Object.keys(bind)) { + if (key.startsWith('sequelize_')) { + throw new Error( + 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.', + ); + } + } +} + +export function combineBinds(bindA: BindOrReplacements, bindB: { [key: string]: unknown }) { + if (Array.isArray(bindA)) { + bindA = arrayBindToNamedBind(bindA); + } + + return { + ...bindA, + ...bindB, + }; +} + +function arrayBindToNamedBind(bind: unknown[]): { [key: string]: unknown } { + const out = Object.create(null); + + // eslint-disable-next-line unicorn/no-for-loop -- too slow. + for (let i = 0; i < bind.length; i++) { + out[i + 1] = bind[i]; + } + + return out; +} + +export function escapeMysqlMariaDbString(value: string): string { + // eslint-disable-next-line no-control-regex -- \u001A is intended to be in this regex + value = value.replaceAll(/[\b\0\t\n\r\u001A'\\]/g, s => { + switch (s) { + case '\0': + return '\\0'; + case '\n': + return '\\n'; + case '\r': + return '\\r'; + case '\b': + return '\\b'; + case '\t': + return '\\t'; + case '\u001A': + return '\\Z'; + default: + return `\\${s}`; + } + }); + + return `'${value}'`; +} + +export function formatDb2StyleLimitOffset( + options: AddLimitOffsetOptions, + queryGenerator: AbstractQueryGenerator, +): string { + let fragment = ''; + if (options.offset) { + fragment += ` OFFSET ${queryGenerator.escape(options.offset, options)} ROWS`; + } + + if (options.limit != null) { + fragment += ` FETCH NEXT ${queryGenerator.escape(options.limit, options)} ROWS ONLY`; + } + + return fragment; +} + +export function formatMySqlStyleLimitOffset( + options: AddLimitOffsetOptions, + queryGenerator: AbstractQueryGenerator, +): string { + let fragment = ''; + if (options.limit != null) { + fragment += ` LIMIT ${queryGenerator.escape(options.limit, options)}`; + } else if (options.offset) { + // limit must be specified if offset is specified. + fragment += ` LIMIT 18446744073709551615`; + } + + if (options.offset) { + fragment += ` OFFSET ${queryGenerator.escape(options.offset, options)}`; + } + + return fragment; +} + +export async function withSqliteForeignKeysOff( + sequelize: Sequelize, + options: QueryRawOptions | undefined, + cb: () => Promise, +): Promise { + try { + await sequelize.queryRaw('PRAGMA foreign_keys = OFF', options); + + return await cb(); + } finally { + await sequelize.queryRaw('PRAGMA foreign_keys = ON', options); + } +} + +/** + * Creates a function that can be used to collect bind parameters. + * + * @param bind A mutable object to which bind parameters will be added. + */ +export function createBindParamGenerator( + bind: Record, +): (value: unknown) => string { + let i = 0; + + return (value: unknown): string => { + const bindName = `sequelize_${++i}`; + + bind[bindName] = value; + + return `$${bindName}`; + }; +} diff --git a/packages/core/src/utils/string.ts b/packages/core/src/utils/string.ts new file mode 100644 index 000000000000..c340214c092b --- /dev/null +++ b/packages/core/src/utils/string.ts @@ -0,0 +1,115 @@ +import * as _inflection from 'inflection'; +import lowerFirst from 'lodash/lowerFirst'; +import NodeUtil from 'node:util'; +import type { IndexOptions, TableName } from '../abstract-dialect/query-interface.js'; +import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js'; + +/* Inflection */ +type Inflection = typeof _inflection; + +let inflection: Inflection = _inflection; + +export function useInflection(newInflection: Inflection) { + inflection = newInflection; +} + +/* String utils */ + +export function camelize(str: string): string { + return lowerFirst(str.trim()).replaceAll(/[-_\s]+(.)?/g, (match, c) => c.toUpperCase()); +} + +export function underscoredIf(str: string, condition: boolean): string { + let result = str; + + if (condition) { + result = underscore(str); + } + + return result; +} + +export function underscore(str: string): string { + return inflection.underscore(str); +} + +export function spliceStr(str: string, index: number, count: number, add: string): string { + return str.slice(0, index) + add + str.slice(index + count); +} + +export function singularize(str: string): string { + return inflection.singularize(str); +} + +export function pluralize(str: string): string { + return inflection.pluralize(str); +} + +type NameIndexIndex = { + fields: Array<{ name: string; attribute: string }>; + name: string; +}; + +/** + * + * @param index + * @param index.fields + * @param index.name + * @param tableName + * + * @private + */ +export function nameIndex(index: NameIndexIndex, tableName: TableName) { + if (Object.hasOwn(index, 'name')) { + return index; + } + + index.name = generateIndexName(tableName, index); + + return index; +} + +export function generateIndexName(tableName: TableName, index: IndexOptions): string { + if (typeof tableName !== 'string' && tableName.tableName) { + tableName = tableName.tableName; + } + + if (!index.fields) { + throw new Error(`Index on table ${tableName} has not fields: +${NodeUtil.inspect(index)}`); + } + + const fields = index.fields.map(field => { + if (typeof field === 'string') { + return field; + } + + if (field instanceof BaseSqlExpression) { + throw new Error( + `Index on table ${tableName} uses Sequelize's ${field.constructor.name} as one of its fields. You need to name this index manually.`, + ); + } + + if ('attribute' in field) { + throw new Error('Property "attribute" in IndexField has been renamed to "name"'); + } + + return field.name; + }); + + let out = `${tableName}_${fields.join('_')}`; + + if (index.unique) { + out += '_unique'; + } + + return underscore(out); +} + +export function removeTrailingSemicolon(str: string): string { + if (!str.endsWith(';')) { + return str; + } + + return str.slice(0, Math.max(0, str.length - 1)); +} diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts new file mode 100644 index 000000000000..fcb3c4f2ee78 --- /dev/null +++ b/packages/core/src/utils/types.ts @@ -0,0 +1,71 @@ +import type { NonUndefined, PartialBy } from '@sequelize/utils'; + +export type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + +export type DeepWriteable = { + -readonly [K in keyof T]: T[K] extends Function ? T[K] : DeepWriteable; +}; + +/** + * Returns all shallow properties that accept `undefined` or `null`. + * Does not include Optional properties, only `undefined` or `null`. + * + * @example + * ```typescript + * type UndefinedProps = NullishPropertiesOf<{ + * id: number | undefined, + * createdAt: string | undefined, + * firstName: string | null, // nullable properties are included + * lastName?: string, // optional properties are not included. + * }>; + * + * // is equal to + * + * type UndefinedProps = 'id' | 'createdAt' | 'firstName'; + * ``` + */ +export type NullishPropertiesOf = { + [P in keyof T]-?: undefined extends T[P] ? P : null extends T[P] ? P : never; +}[keyof T]; + +/** + * Makes all shallow properties of an object `optional` if they accept `undefined` or `null` as a value. + * + * @example + * ```typescript + * type MyOptionalType = MakeUndefinedOptional<{ + * id: number | undefined, + * firstName: string, + * lastName: string | null, + * }>; + * + * // is equal to + * + * type MyOptionalType = { + * // this property is optional. + * id?: number | undefined, + * firstName: string, + * // this property is optional. + * lastName?: string | null, + * }; + * ``` + */ +export type MakeNullishOptional = PartialBy>; + +export type NonUndefinedKeys = { + [P in keyof T]: P extends K ? NonUndefined : T[P]; +}; + +export type AllowLowercase = T | Lowercase; + +export type ConstructorKeys = { + [P in keyof T]: T[P] extends new () => any ? P : never; +}[keyof T]; + +type NonConstructorKeys = { [P in keyof T]: T[P] extends new () => any ? never : P }[keyof T]; + +export type OmitConstructors = Pick>; diff --git a/types/lib/utils/validator-extras.d.ts b/packages/core/src/utils/validator-extras.d.ts similarity index 88% rename from types/lib/utils/validator-extras.d.ts rename to packages/core/src/utils/validator-extras.d.ts index a80cd1396294..b9b47daaa558 100644 --- a/types/lib/utils/validator-extras.d.ts +++ b/packages/core/src/utils/validator-extras.d.ts @@ -1,7 +1,6 @@ -// tslint:disable-next-line:no-implicit-dependencies -import * as val from 'validator'; +import type origValidator from 'validator'; -type OrigValidator = typeof val; +type OrigValidator = typeof origValidator; export interface Extensions { notEmpty(str: string): boolean; diff --git a/packages/core/src/utils/validator-extras.js b/packages/core/src/utils/validator-extras.js new file mode 100644 index 000000000000..8a7138392caf --- /dev/null +++ b/packages/core/src/utils/validator-extras.js @@ -0,0 +1,101 @@ +'use strict'; + +import cloneDeep from 'lodash/cloneDeep'; +import forEach from 'lodash/forEach'; + +const validator = cloneDeep(require('validator')); +const dayjs = require('dayjs'); + +export const extensions = { + extend(name, fn) { + this[name] = fn; + + return this; + }, + notEmpty(str) { + return !/^\s*$/.test(str); + }, + // TODO: accept { min, max } object + len(str, min, max) { + return this.isLength(str, min, max); + }, + isUrl(str) { + return this.isURL(str); + }, + isIPv6(str) { + return this.isIP(str, 6); + }, + isIPv4(str) { + return this.isIP(str, 4); + }, + notIn(str, values) { + return !this.isIn(str, values); + }, + regex(str, pattern, modifiers) { + str = String(str); + if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') { + pattern = new RegExp(pattern, modifiers); + } + + return str.match(pattern); + }, + notRegex(str, pattern, modifiers) { + return !this.regex(str, pattern, modifiers); + }, + isDecimal(str) { + return str !== '' && Boolean(/^(?:-?\d+)?(?:\.\d*)?(?:[Ee][+-]?\d+)?$/.test(str)); + }, + min(str, val) { + const number = Number.parseFloat(str); + + return Number.isNaN(number) || number >= val; + }, + max(str, val) { + const number = Number.parseFloat(str); + + return Number.isNaN(number) || number <= val; + }, + not(str, pattern, modifiers) { + return this.notRegex(str, pattern, modifiers); + }, + contains(str, elem) { + return Boolean(elem) && str.includes(elem); + }, + notContains(str, elem) { + return !this.contains(str, elem); + }, + is(str, pattern, modifiers) { + return this.regex(str, pattern, modifiers); + }, +}; + +// instance based validators +validator.isImmutable = function (value, validatorArgs, field, modelInstance) { + return ( + modelInstance.isNewRecord || + modelInstance.dataValues[field] === modelInstance._previousDataValues[field] + ); +}; + +// extra validators +validator.notNull = function (val) { + return val !== null && val !== undefined; +}; + +// https://github.com/chriso/validator.js/blob/6.2.0/validator.js +forEach(extensions, (extend, key) => { + validator[key] = extend; +}); + +// map isNull to isEmpty +// https://github.com/chriso/validator.js/commit/e33d38a26ee2f9666b319adb67c7fc0d3dea7125 +validator.isNull = validator.isEmpty; + +// isDate removed in 7.0.0 +// https://github.com/chriso/validator.js/commit/095509fc707a4dc0e99f85131df1176ad6389fc9 +// TODO: isDate has been added back https://github.com/validatorjs/validator.js/pull/1270 +validator.isDate = function (dateString) { + return dayjs(dateString).isValid(); +}; + +export { validator }; diff --git a/packages/core/src/utils/where.ts b/packages/core/src/utils/where.ts new file mode 100644 index 000000000000..5edb4ab74547 --- /dev/null +++ b/packages/core/src/utils/where.ts @@ -0,0 +1,36 @@ +import { Op as operators } from '../operators.js'; + +/** + * getComplexKeys + * + * @param obj + * @returns All keys including operators + * @private + */ +export function getComplexKeys(obj: object): Array { + return [...getOperators(obj), ...Object.keys(obj)]; +} + +/** + * getComplexSize + * + * @param obj + * @returns Length of object properties including operators if obj is array returns its length + * @private + */ +export function getComplexSize(obj: object | any[]): number { + return Array.isArray(obj) ? obj.length : getComplexKeys(obj).length; +} + +const operatorsSet = new Set(Object.values(operators)); + +/** + * getOperators + * + * @param obj + * @returns All operators properties of obj + * @private + */ +export function getOperators(obj: object): symbol[] { + return Object.getOwnPropertySymbols(obj).filter(s => operatorsSet.has(s)); +} diff --git a/packages/core/test/chai-extensions.d.ts b/packages/core/test/chai-extensions.d.ts new file mode 100644 index 000000000000..6d6147ef6326 --- /dev/null +++ b/packages/core/test/chai-extensions.d.ts @@ -0,0 +1,7 @@ +declare namespace Chai { + interface Assertion { + throwWithCause: Throw; + beNullish(): void; + notBeNullish(): void; + } +} diff --git a/test/config/.docker.env b/packages/core/test/config/.docker.env similarity index 100% rename from test/config/.docker.env rename to packages/core/test/config/.docker.env diff --git a/packages/core/test/config/config.ts b/packages/core/test/config/config.ts new file mode 100644 index 000000000000..423c606b576b --- /dev/null +++ b/packages/core/test/config/config.ts @@ -0,0 +1,157 @@ +import type { ConnectionOptions, Options } from '@sequelize/core'; +import { Db2Dialect } from '@sequelize/db2'; +import { IBMiDialect } from '@sequelize/db2-ibmi'; +import { MariaDbDialect } from '@sequelize/mariadb'; +import { MsSqlDialect } from '@sequelize/mssql'; +import { MySqlDialect } from '@sequelize/mysql'; +import { PostgresDialect } from '@sequelize/postgres'; +import { SnowflakeDialect } from '@sequelize/snowflake'; +import { SqliteDialect } from '@sequelize/sqlite3'; +import { parseSafeInteger } from '@sequelize/utils'; +import path from 'node:path'; + +export const SQLITE_DATABASES_DIR = path.join(__dirname, '..', 'sqlite-databases'); + +export function getSqliteDatabasePath(name: string): string { + return path.join(SQLITE_DATABASES_DIR, name); +} + +const { env } = process; + +export interface DialectConfigs { + mssql: Options; + mysql: Options; + snowflake: Options; + mariadb: Options; + sqlite3: Options; + postgres: Options; + db2: Options; + ibmi: Options; +} + +export interface DialectConnectionConfigs { + mssql: ConnectionOptions; + mysql: ConnectionOptions; + snowflake: ConnectionOptions; + mariadb: ConnectionOptions; + sqlite3: ConnectionOptions; + postgres: ConnectionOptions; + db2: ConnectionOptions; + ibmi: ConnectionOptions; +} + +const seqPort = env.SEQ_PORT ? parseSafeInteger.orThrow(env.SEQ_PORT) : undefined; + +export const CONFIG: DialectConfigs = { + mssql: { + dialect: MsSqlDialect, + authentication: { + type: 'default', + options: { + userName: env.SEQ_MSSQL_USER || env.SEQ_USER || 'SA', + password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'Password12!', + }, + }, + database: env.SEQ_MSSQL_DB || env.SEQ_DB || 'sequelize_test', + encrypt: false, + pool: { + max: parseSafeInteger.orThrow(env.SEQ_MSSQL_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: parseSafeInteger.orThrow(env.SEQ_MSSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + port: parseSafeInteger.orThrow(env.SEQ_MSSQL_PORT || seqPort || 22_019), + requestTimeout: 25_000, + server: env.SEQ_MSSQL_HOST || env.SEQ_HOST || 'localhost', + }, + + mysql: { + dialect: MySqlDialect, + database: env.SEQ_MYSQL_DB || env.SEQ_DB || 'sequelize_test', + user: 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: parseSafeInteger.orThrow( + env.MYSQL_PORT_3306_TCP_PORT || env.SEQ_MYSQL_PORT || seqPort || 20_057, + ), + pool: { + max: parseSafeInteger.orThrow(env.SEQ_MYSQL_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: parseSafeInteger.orThrow(env.SEQ_MYSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + }, + + snowflake: { + dialect: SnowflakeDialect, + username: env.SEQ_SNOWFLAKE_USER || env.SEQ_USER || 'root', + password: env.SEQ_SNOWFLAKE_PW || env.SEQ_PW || '', + database: env.SEQ_SNOWFLAKE_DB || env.SEQ_DB || 'sequelize_test', + account: env.SEQ_SNOWFLAKE_ACCOUNT || env.SEQ_ACCOUNT || 'sequelize_test', + role: env.SEQ_SNOWFLAKE_ROLE || env.SEQ_ROLE || 'role', + warehouse: env.SEQ_SNOWFLAKE_WH || env.SEQ_WH || 'warehouse', + schema: env.SEQ_SNOWFLAKE_SCHEMA || env.SEQ_SCHEMA || '', + }, + + mariadb: { + dialect: MariaDbDialect, + database: env.SEQ_MARIADB_DB || env.SEQ_DB || 'sequelize_test', + user: 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: parseSafeInteger.orThrow( + env.MARIADB_PORT_3306_TCP_PORT || env.SEQ_MARIADB_PORT || seqPort || 21_103, + ), + pool: { + max: Number(env.SEQ_MARIADB_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: Number(env.SEQ_MARIADB_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + }, + + sqlite3: { + dialect: SqliteDialect, + storage: getSqliteDatabasePath('default.sqlite'), + }, + + postgres: { + dialect: PostgresDialect, + database: env.SEQ_PG_DB || env.SEQ_DB || 'sequelize_test', + user: 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: parseSafeInteger.orThrow( + env.POSTGRES_PORT_5432_TCP_PORT || env.SEQ_PG_PORT || seqPort || 23_010, + ), + pool: { + max: Number(env.SEQ_PG_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: Number(env.SEQ_PG_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + minifyAliases: Boolean(env.SEQ_PG_MINIFY_ALIASES), + }, + + db2: { + dialect: Db2Dialect, + database: env.SEQ_DB2_DB || env.SEQ_DB || env.IBM_DB_DBNAME || 'testdb', + username: env.SEQ_DB2_USER || env.SEQ_USER || env.IBM_DB_UID || 'db2inst1', + password: env.SEQ_DB2_PW || env.SEQ_PW || env.IBM_DB_PWD || 'password', + hostname: + env.DB2_PORT_50000_TCP_ADDR || + env.SEQ_DB2_HOST || + env.SEQ_HOST || + env.IBM_DB_HOSTNAME || + '127.0.0.1', + port: env.DB2_PORT_50000_TCP_PORT || env.SEQ_DB2_PORT || seqPort || env.IBM_DB_PORT || 50_000, + pool: { + max: parseSafeInteger.orThrow(env.SEQ_DB2_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: parseSafeInteger.orThrow(env.SEQ_DB2_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + }, + + ibmi: { + dialect: IBMiDialect, + dataSourceName: env.SEQ_IBMI_DB || env.SEQ_DB, + username: env.SEQ_IBMI_USER || env.SEQ_USER, + password: env.SEQ_IBMI_PW || env.SEQ_PW, + pool: { + max: Number(env.SEQ_IBMI_POOL_MAX || env.SEQ_POOL_MAX || env.SEQ_POOL_MAX || 5), + idle: Number(env.SEQ_IBMI_POOL_IDLE || env.SEQ_POOL_IDLE || 3000), + }, + odbcConnectionString: env.SEQ_IBMI_CONN_STR, + }, +}; diff --git a/test/config/mariadb/mariadb.sql b/packages/core/test/config/mariadb/mariadb.sql similarity index 100% rename from test/config/mariadb/mariadb.sql rename to packages/core/test/config/mariadb/mariadb.sql diff --git a/test/config/mysql/mysql.sql b/packages/core/test/config/mysql/mysql.sql similarity index 100% rename from test/config/mysql/mysql.sql rename to packages/core/test/config/mysql/mysql.sql diff --git a/packages/core/test/integration/assets/es6project.js b/packages/core/test/integration/assets/es6project.js new file mode 100644 index 000000000000..895118f2ce18 --- /dev/null +++ b/packages/core/test/integration/assets/es6project.js @@ -0,0 +1,7 @@ +'use strict'; + +exports.default = function (sequelize, DataTypes) { + return sequelize.define(`Project${Number.parseInt(Math.random() * 9_999_999_999_999_999, 10)}`, { + name: DataTypes.STRING, + }); +}; diff --git a/packages/core/test/integration/assets/project.js b/packages/core/test/integration/assets/project.js new file mode 100644 index 000000000000..4ba02e6171f3 --- /dev/null +++ b/packages/core/test/integration/assets/project.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function (sequelize, DataTypes) { + return sequelize.define(`Project${Number.parseInt(Math.random() * 9_999_999_999_999_999, 10)}`, { + name: DataTypes.STRING, + }); +}; diff --git a/test/integration/associations/alias.test.js b/packages/core/test/integration/associations/alias.test.js similarity index 75% rename from test/integration/associations/alias.test.js rename to packages/core/test/integration/associations/alias.test.js index b53067d25958..c703d927d8ad 100644 --- a/test/integration/associations/alias.test.js +++ b/packages/core/test/integration/associations/alias.test.js @@ -1,13 +1,14 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'); +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../support'); describe(Support.getTestDialectTeaser('Alias'), () => { - 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', {}); + it('should uppercase the first letter in alias getter, but not in eager loading', async function () { + const User = this.sequelize.define('user', {}); + const Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'assignments', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'owner', foreignKey: 'userId' }); @@ -21,16 +22,16 @@ describe(Support.getTestDialectTeaser('Alias'), () => { 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' }] }) + 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', async function() { - const User = this.sequelize.define('user', {}), - Task = this.sequelize.define('task', {}); + it('shouldnt touch the passed alias', async function () { + const User = this.sequelize.define('user', {}); + const Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'ASSIGNMENTS', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'OWNER', foreignKey: 'userId' }); @@ -44,16 +45,16 @@ describe(Support.getTestDialectTeaser('Alias'), () => { 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' }] }) + 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', async function() { - const User = this.sequelize.define('user', {}), - Task = this.sequelize.define('task', {}); + it('should allow me to pass my own plural and singular forms to hasMany', async function () { + const User = this.sequelize.define('user', {}); + const Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: { singular: 'task', plural: 'taskz' } }); @@ -66,14 +67,18 @@ describe(Support.getTestDialectTeaser('Alias'), () => { expect(user.taskz).to.be.ok; }); - 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', {}, { + it('should allow me to define plural and singular forms on the model', async function () { + const User = this.sequelize.define('user', {}); + const Task = this.sequelize.define( + 'task', + {}, + { name: { singular: 'assignment', - plural: 'assignments' - } - }); + plural: 'assignments', + }, + }, + ); User.hasMany(Task); diff --git a/packages/core/test/integration/associations/belongs-to-many-mixins.test.ts b/packages/core/test/integration/associations/belongs-to-many-mixins.test.ts new file mode 100644 index 000000000000..2100999f0e68 --- /dev/null +++ b/packages/core/test/integration/associations/belongs-to-many-mixins.test.ts @@ -0,0 +1,240 @@ +import type { + BelongsToManyAddAssociationsMixin, + BelongsToManyCountAssociationsMixin, + BelongsToManyCreateAssociationMixin, + BelongsToManyGetAssociationsMixin, + BelongsToManyHasAssociationsMixin, + BelongsToManyRemoveAssociationsMixin, + BelongsToManySetAssociationsMixin, + CreationOptional, + InferAttributes, + InferCreationAttributes, +} from '@sequelize/core'; +import { Model } from '@sequelize/core'; +import { BelongsToMany } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { beforeAll2, sequelize, setResetMode } from '../support'; + +describe('belongsToMany Mixins', () => { + setResetMode('destroy'); + + const vars = beforeAll2(async () => { + class Article extends Model, InferCreationAttributes
> { + declare id: CreationOptional; + + @BelongsToMany(() => User, { through: 'UserArticle' }) + declare authors?: User[]; + + declare getAuthors: BelongsToManyGetAssociationsMixin; + declare setAuthors: BelongsToManySetAssociationsMixin; + declare addAuthors: BelongsToManyAddAssociationsMixin; + declare removeAuthors: BelongsToManyRemoveAssociationsMixin; + declare createAuthor: BelongsToManyCreateAssociationMixin; + declare hasAuthors: BelongsToManyHasAssociationsMixin; + declare countAuthors: BelongsToManyCountAssociationsMixin; + } + + class User extends Model, InferCreationAttributes> { + declare id: CreationOptional; + } + + sequelize.addModels([Article, User]); + await sequelize.sync({ force: true }); + + return { Article, User }; + }); + + describe('setAssociations', () => { + it('associates target models to the source model', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + expect(article.id).to.be.a('number'); + expect(user.id).to.be.a('number'); + + await article.setAuthors([user]); + + expect(await article.getAuthors()).to.have.length(1); + }); + + it('supports any iterable', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + + await article.setAuthors(new Set([user])); + + expect(await article.getAuthors()).to.have.length(1); + }); + + it('unlinks the previous associations', async () => { + const { User, Article } = vars; + + const [article, user1, user2] = await Promise.all([ + Article.create(), + User.create(), + User.create(), + ]); + + expect(await article.getAuthors()).to.be.empty; + + await article.setAuthors([user1]); + + expect(await article.getAuthors()).to.have.length(1); + + await article.setAuthors([user2]); + + expect(await article.getAuthors()).to.have.length(1); + expect(await article.hasAuthors([user2])).to.be.true; + }); + + it('clears associations when the parameter is null', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + await article.setAuthors([user]); + + expect(await article.getAuthors()).to.have.length(1); + + await article.setAuthors(null); + + expect(await article.getAuthors()).to.be.empty; + }); + + it('supports passing the primary key instead of an object', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + + await article.setAuthors([user.id]); + + expect(await article.getAuthors()).to.have.length(1); + }); + }); + + describe('addAssociations', () => { + it('associates target models to the source model', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + + await article.addAuthors([user]); + + expect(await article.getAuthors()).to.have.length(1); + }); + + it('supports any iterable', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + + await article.addAuthors(new Set([user])); + + expect(await article.getAuthors()).to.have.length(1); + }); + + it('supports passing the primary key instead of an object', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + expect(await article.getAuthors()).to.be.empty; + + await article.addAuthors([user.id]); + + expect(await article.getAuthors()).to.have.length(1); + }); + }); + + describe('removeAssociations', () => { + it('unlinks the target models from the source model', async () => { + const { User, Article } = vars; + + const [article, user1, user2] = await Promise.all([ + Article.create(), + User.create(), + User.create(), + ]); + + await article.setAuthors([user1, user2]); + + expect(await article.getAuthors()).to.have.length(2); + + await article.removeAuthors([user1]); + + expect(await article.getAuthors()).to.have.length(1); + expect(await article.hasAuthors([user1])).to.be.false; + }); + + it('supports any iterable', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + await article.setAuthors([user]); + await article.removeAuthors(new Set([user])); + + expect(await article.getAuthors()).to.have.length(0); + }); + + it('supports passing the primary key instead of an object', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + await article.setAuthors([user]); + await article.removeAuthors([user.id]); + + expect(await article.getAuthors()).to.have.length(0); + }); + }); + + describe('hasAssociations', () => { + it('returns true if the target model is associated to the source model', async () => { + const { User, Article } = vars; + + const [article, user1, user2] = await Promise.all([ + Article.create(), + User.create(), + User.create(), + ]); + + await article.setAuthors([user1]); + + expect(await article.hasAuthors([user1])).to.be.true; + expect(await article.hasAuthors([user2])).to.be.false; + expect(await article.hasAuthors([user1, user2])).to.be.false; + }); + + it('supports any iterable', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + await article.setAuthors([user]); + + expect(await article.hasAuthors(new Set([user]))).to.be.true; + }); + + it('supports passing the primary key instead of an object', async () => { + const { User, Article } = vars; + + const [article, user] = await Promise.all([Article.create(), User.create()]); + + await article.setAuthors([user]); + + expect(await article.hasAuthors([user.id])).to.be.true; + }); + }); +}); diff --git a/packages/core/test/integration/associations/belongs-to-many.test.js b/packages/core/test/integration/associations/belongs-to-many.test.js new file mode 100644 index 000000000000..3485630f72f1 --- /dev/null +++ b/packages/core/test/integration/associations/belongs-to-many.test.js @@ -0,0 +1,3913 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../support'); +const { DataTypes, IsolationLevel, Op, Sequelize, sql } = require('@sequelize/core'); +const assert = require('node:assert'); +const sinon = require('sinon'); + +const current = Support.sequelize; +const dialect = Support.getTestDialect(); + +describe(Support.getTestDialectTeaser('BelongsToMany'), () => { + Support.setResetMode('drop'); + + describe('getAssociations', () => { + 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', as: 'Tasks', inverse: 'Users' }); + this.Task.belongsToMany(this.User, { through: 'UserTasks', as: 'Users', inverse: 'Tasks' }); + + 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', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + 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.startUnmanagedTransaction(), + ]); + + try { + 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); + } finally { + await t.rollback(); + } + }); + } + + it('gets all associated objects with all fields', async function () { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + for (const attributeName of this.Task.modelDefinition.attributes.keys()) { + expect(tasks[0]).to.have.property(attributeName); + } + }); + + 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', 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', async function () { + const john = await this.User.findOne({ + where: { + username: 'John', + }, + }); + + const tasks = await john.getTasks({ + where: { + title: { + [Op.not]: ['Get rich'], + }, + }, + }); + + expect(tasks).to.have.length(1); + }); + + it('supports a where not in on the primary key', async function () { + const john = await this.User.findOne({ + where: { + username: 'John', + }, + }); + + const tasks = await john.getTasks({ + where: { + id: { + [Op.not]: [this.tasks[0].get('id')], + }, + }, + }); + + 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', 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', async function () { + const Label = this.sequelize.define('Label', { + title: DataTypes.STRING, + isActive: DataTypes.BOOLEAN, + }); + const Task = this.Task; + const User = this.User; + + Task.hasMany(Label, { as: 'Labels' }); + Label.belongsTo(Task, { as: 'Tasks' }); + + 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); + }); + + if (current.dialect.supports.schemas) { + it('should support schemas', async function () { + const AcmeUser = this.sequelize + .define('User', { + username: DataTypes.STRING, + }) + .withSchema({ schema: 'acme', schemaDelimiter: '_' }); + const AcmeProject = this.sequelize + .define('Project', { + title: DataTypes.STRING, + active: DataTypes.BOOLEAN, + }) + .withSchema({ schema: 'acme', schemaDelimiter: '_' }); + const AcmeProjectUsers = this.sequelize + .define('ProjectUsers', { + status: DataTypes.STRING, + data: DataTypes.INTEGER, + }) + .withSchema({ schema: 'acme', schemaDelimiter: '_' }); + + AcmeUser.belongsToMany(AcmeProject, { + through: AcmeProjectUsers, + as: 'Projects', + inverse: 'Users', + }); + AcmeProject.belongsToMany(AcmeUser, { + through: AcmeProjectUsers, + as: 'Users', + inverse: 'Projects', + }); + + 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.UserProject).to.be.ok; + expect(project.status).not.to.exist; + expect(project.UserProject.status).to.equal('active'); + await this.sequelize.queryInterface.dropAllTables({ schema: 'acme' }); + await this.sequelize.dropSchema('acme'); + const schemas = await this.sequelize.queryInterface.listSchemas(); + expect(schemas).to.not.include('acme'); + }); + } + + it('supports custom primary keys and foreign keys', async function () { + const User = this.sequelize.define( + 'User', + { + id_user: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: sql.uuidV4, + allowNull: false, + }, + }, + { + tableName: 'tbl_user', + }, + ); + + const Group = this.sequelize.define( + 'Group', + { + id_group: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + }, + }, + { + tableName: 'tbl_group', + }, + ); + + const User_has_Group = this.sequelize.define( + 'User_has_Group', + {}, + { + tableName: 'tbl_user_has_group', + }, + ); + + User.belongsToMany(Group, { + as: 'groups', + through: User_has_Group, + foreignKey: 'id_user', + otherKey: 'id_group', + inverse: { as: 'users' }, + }); + + 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', async function () { + const User = this.sequelize.define( + 'User', + { + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + }, + { + tableName: 'tbl_user', + }, + ); + + const Group = this.sequelize.define( + 'Group', + { + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'group_id', + }, + }, + { + tableName: 'tbl_group', + }, + ); + + const User_has_Group = this.sequelize.define( + 'User_has_Group', + {}, + { + tableName: 'tbl_user_has_group', + }, + ); + + User.belongsToMany(Group, { through: User_has_Group, as: 'Groups', inverse: 'Users' }); + Group.belongsToMany(User, { through: User_has_Group, as: 'Users', inverse: 'Groups' }); + + 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.equal(1); + expect(user.Groups[0].User_has_Group.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(user.Groups[0].User_has_Group.userUserSecondId).to.deep.equal(user.userSecondId); + } else { + expect(user.Groups[0].User_has_Group.userUserSecondId).to.equal(user.userSecondId); + } + + expect(user.Groups[0].User_has_Group.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(user.Groups[0].User_has_Group.groupGroupSecondId).to.deep.equal( + user.Groups[0].groupSecondId, + ); + } else { + expect(user.Groups[0].User_has_Group.groupGroupSecondId).to.equal( + user.Groups[0].groupSecondId, + ); + } + + expect(users.length).to.equal(1); + expect(users[0].toJSON()).to.be.eql(user.toJSON()); + }); + + it('supports non primary key attributes for joins (sourceKey only)', async function () { + const User = this.sequelize.define( + 'User', + { + id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + User.belongsToMany(Group, { + through: 'usergroups', + sourceKey: 'userSecondId', + targetKey: 'groupSecondId', + as: 'Groups', + }); + + 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], + }), + ]); + // Need to add db2 condition for the same. referred to issue: https://github.com/chaijs/chai/issues/102 + expect(users.length).to.equal(2); + expect(users[0].Groups.length).to.equal(1); + expect(users[1].Groups.length).to.equal(1); + expect(users[0].Groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].usergroups.userUserSecondId).to.deep.equal(users[0].userSecondId); + } else { + expect(users[0].Groups[0].usergroups.userUserSecondId).to.equal(users[0].userSecondId); + } + + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.deep.equal( + users[0].Groups[0].groupSecondId, + ); + } else { + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.equal( + users[0].Groups[0].groupSecondId, + ); + } + + expect(users[1].Groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].usergroups.userUserSecondId).to.deep.equal(users[1].userSecondId); + } else { + expect(users[1].Groups[0].usergroups.userUserSecondId).to.equal(users[1].userSecondId); + } + + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.deep.equal( + users[1].Groups[0].groupSecondId, + ); + } else { + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.equal( + users[1].Groups[0].groupSecondId, + ); + } + + expect(groups.length).to.equal(2); + expect(groups[0].users.length).to.equal(1); + expect(groups[1].users.length).to.equal(1); + expect(groups[0].users[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.groupGroupSecondId).to.deep.equal( + groups[0].groupSecondId, + ); + } else { + expect(groups[0].users[0].usergroups.groupGroupSecondId).to.equal(groups[0].groupSecondId); + } + + expect(groups[0].users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.userUserSecondId).to.deep.equal( + groups[0].users[0].userSecondId, + ); + } else { + expect(groups[0].users[0].usergroups.userUserSecondId).to.equal( + groups[0].users[0].userSecondId, + ); + } + + expect(groups[1].users[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.groupGroupSecondId).to.deep.equal( + groups[1].groupSecondId, + ); + } else { + expect(groups[1].users[0].usergroups.groupGroupSecondId).to.equal(groups[1].groupSecondId); + } + + expect(groups[1].users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.userUserSecondId).to.deep.equal( + groups[1].users[0].userSecondId, + ); + } else { + expect(groups[1].users[0].usergroups.userUserSecondId).to.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, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_id'], + }, + ], + }, + ); + + User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); + Group.belongsToMany(User, { through: 'usergroups', 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 [users, groups] = await Promise.all([ + User.findAll({ + where: {}, + include: [Group], + }), + Group.findAll({ + include: [User], + }), + ]); + + expect(users.length).to.equal(2); + expect(users[0].groups.length).to.equal(1); + expect(users[1].groups.length).to.equal(1); + expect(users[0].groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].usergroups.userUserSecondId).to.deep.equal(users[0].userSecondId); + } else { + expect(users[0].groups[0].usergroups.userUserSecondId).to.equal(users[0].userSecondId); + } + + expect(users[0].groups[0].usergroups.groupId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].usergroups.groupId).to.deep.equal(users[0].groups[0].id); + } else { + expect(users[0].groups[0].usergroups.groupId).to.equal(users[0].groups[0].id); + } + + expect(users[1].groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].usergroups.userUserSecondId).to.deep.equal(users[1].userSecondId); + } else { + expect(users[1].groups[0].usergroups.userUserSecondId).to.equal(users[1].userSecondId); + } + + expect(users[1].groups[0].usergroups.groupId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].usergroups.groupId).to.deep.equal(users[1].groups[0].id); + } else { + expect(users[1].groups[0].usergroups.groupId).to.equal(users[1].groups[0].id); + } + + expect(groups.length).to.equal(2); + expect(groups[0].users.length).to.equal(1); + expect(groups[1].users.length).to.equal(1); + expect(groups[0].users[0].usergroups.groupId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.groupId).to.deep.equal(groups[0].id); + } else { + expect(groups[0].users[0].usergroups.groupId).to.equal(groups[0].id); + } + + expect(groups[0].users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.userUserSecondId).to.deep.equal( + groups[0].users[0].userSecondId, + ); + } else { + expect(groups[0].users[0].usergroups.userUserSecondId).to.equal( + groups[0].users[0].userSecondId, + ); + } + + expect(groups[1].users[0].usergroups.groupId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.groupId).to.deep.equal(groups[1].id); + } else { + expect(groups[1].users[0].usergroups.groupId).to.equal(groups[1].id); + } + + expect(groups[1].users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.userUserSecondId).to.deep.equal( + groups[1].users[0].userSecondId, + ); + } else { + expect(groups[1].users[0].usergroups.userUserSecondId).to.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, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + User.belongsToMany(Group, { + through: 'usergroups', + sourceKey: 'userSecondId', + targetKey: 'groupSecondId', + as: 'Groups', + inverse: 'Users', + }); + Group.belongsToMany(User, { + through: 'usergroups', + sourceKey: 'groupSecondId', + targetKey: 'userSecondId', + as: 'Users', + inverse: 'Groups', + }); + + 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.equal(2); + expect(users[0].Groups.length).to.equal(1); + expect(users[1].Groups.length).to.equal(1); + expect(users[0].Groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].usergroups.userUserSecondId).to.deep.equal(users[0].userSecondId); + } else { + expect(users[0].Groups[0].usergroups.userUserSecondId).to.equal(users[0].userSecondId); + } + + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.deep.equal( + users[0].Groups[0].groupSecondId, + ); + } else { + expect(users[0].Groups[0].usergroups.groupGroupSecondId).to.equal( + users[0].Groups[0].groupSecondId, + ); + } + + expect(users[1].Groups[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].usergroups.userUserSecondId).to.deep.equal(users[1].userSecondId); + } else { + expect(users[1].Groups[0].usergroups.userUserSecondId).to.equal(users[1].userSecondId); + } + + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.deep.equal( + users[1].Groups[0].groupSecondId, + ); + } else { + expect(users[1].Groups[0].usergroups.groupGroupSecondId).to.equal( + users[1].Groups[0].groupSecondId, + ); + } + + expect(groups.length).to.equal(2); + expect(groups[0].Users.length).to.equal(1); + expect(groups[1].Users.length).to.equal(1); + expect(groups[0].Users[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].Users[0].usergroups.groupGroupSecondId).to.deep.equal( + groups[0].groupSecondId, + ); + } else { + expect(groups[0].Users[0].usergroups.groupGroupSecondId).to.equal(groups[0].groupSecondId); + } + + expect(groups[0].Users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].Users[0].usergroups.userUserSecondId).to.deep.equal( + groups[0].Users[0].userSecondId, + ); + } else { + expect(groups[0].Users[0].usergroups.userUserSecondId).to.equal( + groups[0].Users[0].userSecondId, + ); + } + + expect(groups[1].Users[0].usergroups.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].Users[0].usergroups.groupGroupSecondId).to.deep.equal( + groups[1].groupSecondId, + ); + } else { + expect(groups[1].Users[0].usergroups.groupGroupSecondId).to.equal(groups[1].groupSecondId); + } + + expect(groups[1].Users[0].usergroups.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].Users[0].usergroups.userUserSecondId).to.deep.equal( + groups[1].Users[0].userSecondId, + ); + } else { + expect(groups[1].Users[0].usergroups.userUserSecondId).to.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, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + const User_has_Group = this.sequelize.define( + 'User_has_Group', + {}, + { + tableName: 'tbl_user_has_group', + indexes: [ + { + unique: true, + fields: ['userUserSecondId', 'groupGroupSecondId'], + name: 'UserHasGroup_Second_Unique', + }, + ], + }, + ); + + User.belongsToMany(Group, { + through: User_has_Group, + sourceKey: 'userSecondId', + targetKey: 'groupSecondId', + }); + + 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.equal(2); + expect(users[0].groups.length).to.equal(1); + expect(users[1].groups.length).to.equal(1); + expect(users[0].groups[0].User_has_Group.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].User_has_Group.userUserSecondId).to.deep.equal( + users[0].userSecondId, + ); + } else { + expect(users[0].groups[0].User_has_Group.userUserSecondId).to.equal(users[0].userSecondId); + } + + expect(users[0].groups[0].User_has_Group.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].User_has_Group.groupGroupSecondId).to.deep.equal( + users[0].groups[0].groupSecondId, + ); + } else { + expect(users[0].groups[0].User_has_Group.groupGroupSecondId).to.equal( + users[0].groups[0].groupSecondId, + ); + } + + expect(users[1].groups[0].User_has_Group.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].User_has_Group.userUserSecondId).to.deep.equal( + users[1].userSecondId, + ); + } else { + expect(users[1].groups[0].User_has_Group.userUserSecondId).to.equal(users[1].userSecondId); + } + + expect(users[1].groups[0].User_has_Group.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].User_has_Group.groupGroupSecondId).to.deep.equal( + users[1].groups[0].groupSecondId, + ); + } else { + expect(users[1].groups[0].User_has_Group.groupGroupSecondId).to.equal( + users[1].groups[0].groupSecondId, + ); + } + + expect(groups.length).to.equal(2); + expect(groups[0].users.length).to.equal(1); + expect(groups[1].users.length).to.equal(1); + expect(groups[0].users[0].User_has_Group.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].User_has_Group.groupGroupSecondId).to.deep.equal( + groups[0].groupSecondId, + ); + } else { + expect(groups[0].users[0].User_has_Group.groupGroupSecondId).to.equal( + groups[0].groupSecondId, + ); + } + + expect(groups[0].users[0].User_has_Group.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].User_has_Group.userUserSecondId).to.deep.equal( + groups[0].users[0].userSecondId, + ); + } else { + expect(groups[0].users[0].User_has_Group.userUserSecondId).to.equal( + groups[0].users[0].userSecondId, + ); + } + + expect(groups[1].users[0].User_has_Group.groupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].User_has_Group.groupGroupSecondId).to.deep.equal( + groups[1].groupSecondId, + ); + } else { + expect(groups[1].users[0].User_has_Group.groupGroupSecondId).to.equal( + groups[1].groupSecondId, + ); + } + + expect(groups[1].users[0].User_has_Group.userUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].User_has_Group.userUserSecondId).to.deep.equal( + groups[1].users[0].userSecondId, + ); + } else { + expect(groups[1].users[0].User_has_Group.userUserSecondId).to.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: sql.uuidV4, + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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.equal(1); + expect(groups1[0].id).to.equal(group1.id); + expect(groups2.length).to.equal(1); + expect(groups2[0].id).to.equal(group2.id); + expect(users1.length).to.equal(1); + expect(users1[0].id).to.equal(user1.id); + expect(users2.length).to.equal(1); + expect(users2[0].id).to.equal(user2.id); + }); + + it('supports non primary key attributes for joins (custom foreignKey)', async function () { + const User = this.sequelize.define( + 'User', + { + id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + User.belongsToMany(Group, { + through: 'usergroups', + foreignKey: 'userId2', + otherKey: 'groupId2', + sourceKey: 'userSecondId', + targetKey: 'groupSecondId', + }); + + 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.equal(2); + expect(users[0].groups.length).to.equal(1); + expect(users[1].groups.length).to.equal(1); + expect(users[0].groups[0].usergroups.userId2).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].usergroups.userId2).to.deep.equal(users[0].userSecondId); + } else { + expect(users[0].groups[0].usergroups.userId2).to.equal(users[0].userSecondId); + } + + expect(users[0].groups[0].usergroups.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].usergroups.groupId2).to.deep.equal( + users[0].groups[0].groupSecondId, + ); + } else { + expect(users[0].groups[0].usergroups.groupId2).to.equal(users[0].groups[0].groupSecondId); + } + + expect(users[1].groups[0].usergroups.userId2).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].usergroups.userId2).to.deep.equal(users[1].userSecondId); + } else { + expect(users[1].groups[0].usergroups.userId2).to.equal(users[1].userSecondId); + } + + expect(users[1].groups[0].usergroups.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].usergroups.groupId2).to.deep.equal( + users[1].groups[0].groupSecondId, + ); + } else { + expect(users[1].groups[0].usergroups.groupId2).to.equal(users[1].groups[0].groupSecondId); + } + + expect(groups.length).to.equal(2); + expect(groups[0].users.length).to.equal(1); + expect(groups[1].users.length).to.equal(1); + expect(groups[0].users[0].usergroups.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.groupId2).to.deep.equal(groups[0].groupSecondId); + } else { + expect(groups[0].users[0].usergroups.groupId2).to.equal(groups[0].groupSecondId); + } + + expect(groups[0].users[0].usergroups.userId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].usergroups.userId2).to.deep.equal( + groups[0].users[0].userSecondId, + ); + } else { + expect(groups[0].users[0].usergroups.userId2).to.equal(groups[0].users[0].userSecondId); + } + + expect(groups[1].users[0].usergroups.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.groupId2).to.deep.equal(groups[1].groupSecondId); + } else { + expect(groups[1].users[0].usergroups.groupId2).to.equal(groups[1].groupSecondId); + } + + expect(groups[1].users[0].usergroups.userId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].usergroups.userId2).to.deep.equal( + groups[1].users[0].userSecondId, + ); + } else { + expect(groups[1].users[0].usergroups.userId2).to.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, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + const User_has_Group = this.sequelize.define( + 'User_has_Group', + { + userId2: { + type: DataTypes.UUID, + allowNull: false, + field: 'user_id2', + }, + groupId2: { + type: DataTypes.UUID, + allowNull: false, + field: 'group_id2', + }, + }, + { + tableName: 'tbl_user_has_group', + indexes: [ + { + unique: true, + fields: ['user_id2', 'group_id2'], + }, + ], + }, + ); + + User.belongsToMany(Group, { + through: User_has_Group, + foreignKey: 'userId2', + otherKey: 'groupId2', + sourceKey: 'userSecondId', + targetKey: 'groupSecondId', + }); + + 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.equal(2); + expect(users[0].groups.length).to.equal(1); + expect(users[1].groups.length).to.equal(1); + expect(users[0].groups[0].User_has_Group.userId2).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].User_has_Group.userId2).to.deep.equal(users[0].userSecondId); + } else { + expect(users[0].groups[0].User_has_Group.userId2).to.equal(users[0].userSecondId); + } + + expect(users[0].groups[0].User_has_Group.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(users[0].groups[0].User_has_Group.groupId2).to.deep.equal( + users[0].groups[0].groupSecondId, + ); + } else { + expect(users[0].groups[0].User_has_Group.groupId2).to.equal( + users[0].groups[0].groupSecondId, + ); + } + + expect(users[1].groups[0].User_has_Group.userId2).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].User_has_Group.userId2).to.deep.equal(users[1].userSecondId); + } else { + expect(users[1].groups[0].User_has_Group.userId2).to.equal(users[1].userSecondId); + } + + expect(users[1].groups[0].User_has_Group.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(users[1].groups[0].User_has_Group.groupId2).to.deep.equal( + users[1].groups[0].groupSecondId, + ); + } else { + expect(users[1].groups[0].User_has_Group.groupId2).to.equal( + users[1].groups[0].groupSecondId, + ); + } + + expect(groups.length).to.equal(2); + expect(groups[0].users.length).to.equal(1); + expect(groups[1].users.length).to.equal(1); + expect(groups[0].users[0].User_has_Group.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].User_has_Group.groupId2).to.deep.equal(groups[0].groupSecondId); + } else { + expect(groups[0].users[0].User_has_Group.groupId2).to.equal(groups[0].groupSecondId); + } + + expect(groups[0].users[0].User_has_Group.userId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].users[0].User_has_Group.userId2).to.deep.equal( + groups[0].users[0].userSecondId, + ); + } else { + expect(groups[0].users[0].User_has_Group.userId2).to.equal(groups[0].users[0].userSecondId); + } + + expect(groups[1].users[0].User_has_Group.groupId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].User_has_Group.groupId2).to.deep.equal(groups[1].groupSecondId); + } else { + expect(groups[1].users[0].User_has_Group.groupId2).to.equal(groups[1].groupSecondId); + } + + expect(groups[1].users[0].User_has_Group.userId2).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].users[0].User_has_Group.userId2).to.deep.equal( + groups[1].users[0].userSecondId, + ); + } else { + expect(groups[1].users[0].User_has_Group.userId2).to.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, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + }, + { + tableName: 'tbl_user', + }, + ); + + const Company = this.sequelize.define( + 'Company', + { + id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'company_id', + }, + }, + { + tableName: 'tbl_company', + }, + ); + + const Group = this.sequelize.define( + 'Group', + { + id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'group_id', + }, + }, + { + tableName: 'tbl_group', + }, + ); + + const Company_has_Group = this.sequelize.define( + 'Company_has_Group', + {}, + { + tableName: 'tbl_company_has_group', + }, + ); + + User.belongsTo(Company); + Company.hasMany(User); + Company.belongsToMany(Group, { through: Company_has_Group }); + Group.belongsToMany(Company, { through: Company_has_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] }], + }), + ]); + }); + }); + + describe('hasAssociations', () => { + beforeEach(function () { + this.Article = this.sequelize.define('Article', { + pk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + title: DataTypes.STRING, + }); + this.Label = this.sequelize.define('Label', { + sk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + text: DataTypes.STRING, + }); + this.ArticleLabel = this.sequelize.define('ArticleLabel'); + }); + + if (current.dialect.supports.transactions) { + it('supports transactions', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + 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.startUnmanagedTransaction(), + ]); + + try { + 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); + } finally { + await t.rollback(); + } + }); + } + + it('answers false if only some labels have been assigned', async function () { + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }), + ]); + + 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', async function () { + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }), + ]); + + 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', async function () { + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }), + ]); + + 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', async function () { + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }), + ]); + + 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', async function () { + this.ArticleLabel = this.sequelize.define('ArticleLabel', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + relevance: { + type: DataTypes.FLOAT, + validate: { + min: 0, + max: 1, + }, + }, + }); + + this.Article.belongsToMany(this.Label, { + through: { model: this.ArticleLabel, unique: false }, + }); + this.Label.belongsToMany(this.Article, { + through: { model: this.ArticleLabel, unique: false }, + }); + + 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: 0.54 }, + }), + article0.addLabel(label20, { + through: { relevance: 0.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', async function () { + this.ArticleLabel = this.sequelize.define('ArticleLabel', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + relevance: { + type: DataTypes.FLOAT, + validate: { + min: 0, + max: 1, + }, + }, + }); + + this.Article.belongsToMany(this.Label, { + through: { model: this.ArticleLabel, unique: false }, + }); + this.Label.belongsToMany(this.Article, { + through: { model: this.ArticleLabel, unique: false }, + }); + + 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: 0.54 }, + }), + article0.addLabel(label20, { + through: { relevance: 0.99 }, + }), + ]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute], + ]); + + await expect(result).to.be.true; + }); + }); + + describe('hasAssociations with binary key', () => { + beforeEach(function () { + const keyDataType = ['mysql', 'mariadb', 'db2', 'ibmi'].includes(dialect) + ? 'BINARY(255)' + : DataTypes.BLOB('tiny'); + this.Article = this.sequelize.define('Article', { + id: { + type: keyDataType, + primaryKey: true, + }, + }); + this.Label = this.sequelize.define('Label', { + id: { + type: keyDataType, + primaryKey: true, + }, + }); + this.ArticleLabel = this.sequelize.define('ArticleLabel'); + + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + return this.sequelize.sync({ force: true }); + }); + + // article.hasLabels returns false for db2 despite article has label + // Problably due to binary id. Hence, disabling it for db2 dialect + if (dialect !== 'db2') { + it('answers true for labels that have been assigned', async function () { + const [article, label] = await Promise.all([ + this.Article.create({ + // note that mariadb appends binary values at the end https://mariadb.com/kb/en/binary/ + id: Buffer.alloc(255), + }), + this.Label.create({ + id: Buffer.alloc(255), + }), + ]); + + await article.addLabel(label); + const result = await article.hasLabels([label]); + await expect(result).to.be.true; + }); + } + + it('answer false for labels that have not been assigned', async function () { + const [article, label] = await Promise.all([ + this.Article.create({ + id: Buffer.alloc(255), + }), + this.Label.create({ + id: Buffer.alloc(255), + }), + ]); + + const result = await article.hasLabels([label]); + expect(result).to.be.false; + }); + }); + + describe('countAssociations', () => { + 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.UserTask = this.sequelize.define('UserTask', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + started: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + }); + + this.User.belongsToMany(this.Task, { through: this.UserTask }); + this.Task.belongsToMany(this.User, { through: this.UserTask }); + + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }), + ]); + + this.tasks = [task1, task2]; + this.user = john; + + return john.setTasks([task1, task2]); + }); + + it('should count all associations', async function () { + expect(await this.user.countTasks({})).to.equal(2); + }); + + it('should count filtered associations', async function () { + expect(await this.user.countTasks({ where: { active: true } })).to.equal(1); + }); + + it('should count scoped associations', async function () { + this.User.belongsToMany(this.Task, { + as: 'activeTasks', + through: this.UserTask, + scope: { + active: true, + }, + }); + + expect(await this.user.countActiveTasks({})).to.equal(1); + }); + + it('should count scoped through associations', async function () { + this.User.belongsToMany(this.Task, { + as: 'startedTasks', + inverse: { + as: 'startedUsers', + }, + through: { + model: this.UserTask, + scope: { + started: true, + }, + }, + }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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('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, + }); + const 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' }); + + 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), + }); + const Member = this.sequelize.define('member', { + member_id: { type: DataTypes.INTEGER, primaryKey: true }, + email: DataTypes.STRING(64), + }); + + Group.belongsToMany(Member, { + through: 'group_members', + foreignKey: 'group_id', + otherKey: 'member_id', + }); + Member.belongsToMany(Group, { + through: 'group_members', + foreignKey: 'member_id', + otherKey: 'group_id', + }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const ItemTag = this.sequelize.define('ItemTag', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + tag_id: { type: DataTypes.INTEGER, unique: false }, + taggable: { type: DataTypes.STRING }, + taggable_id: { type: DataTypes.INTEGER, unique: false }, + }); + const Tag = this.sequelize.define('Tag', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + const Comment = this.sequelize.define('Comment', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + const Post = this.sequelize.define('Post', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + + Post.belongsToMany(Tag, { + through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, + foreignKey: 'taggable_id', + }); + + Comment.belongsToMany(Tag, { + through: { model: ItemTag, unique: false, scope: { taggable: 'comment' } }, + foreignKey: 'taggable_id', + // taggable_id already references Post, we can't make it reference Comment + foreignKeyConstraints: false, + }); + + 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', async function () { + const ItemTag = this.sequelize.define('ItemTag', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + tag_id: { type: DataTypes.INTEGER, unique: false }, + taggable: { type: DataTypes.STRING }, + taggable_id: { type: DataTypes.INTEGER, unique: false }, + }); + const Tag = this.sequelize.define('Tag', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + const Comment = this.sequelize.define('Comment', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + const Post = this.sequelize.define('Post', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: DataTypes.STRING, + }); + + Post.belongsToMany(Tag, { + through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, + foreignKey: 'taggable_id', + }); + + Comment.belongsToMany(Tag, { + through: { model: ItemTag, unique: false, scope: { taggable: 'comment' } }, + foreignKey: 'taggable_id', + // taggable_id already references Post, we can't make it reference Comment + foreignKeyConstraints: false, + }); + + await this.sequelize.sync({ force: true }); + + const [post, comment, tag, secondTag] = await Promise.all([ + Post.create({ name: 'post1' }), + Comment.create({ name: 'comment1' }), + Tag.create({ name: 'tag1' }), + Tag.create({ name: 'tag2' }), + ]); + + await post.setTags([tag, secondTag]); + await comment.setTags([tag, secondTag]); + await post.setTags([tag]); + + const [postTags, commentTags] = await Promise.all([post.getTags(), 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 }, + ); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + 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.startUnmanagedTransaction(), + ]); + + 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', async function () { + const User = this.sequelize.define('user', {}); + const Group = this.sequelize.define('group', {}); + const UserGroups = this.sequelize.define('user_groups', { + isAdmin: DataTypes.BOOLEAN, + }); + + User.belongsToMany(Group, { through: UserGroups }); + Group.belongsToMany(User, { through: UserGroups }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + 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 [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.startUnmanagedTransaction(), + ]); + + 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.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + + const UserTask = sequelize.define('UserTask', { + status: DataTypes.STRING, + }); + + User.belongsToMany(Task, { through: UserTask, as: 'Tasks', inverse: 'Users' }); + await sequelize.sync({ force: true }); + + const [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.startUnmanagedTransaction({ isolationLevel: IsolationLevel.SERIALIZABLE }), + ]); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks', as: 'Tasks', inverse: 'Users' }); + + 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', async function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { + indexes: [ + { + name: 'username_unique', + unique: true, + method: 'BTREE', + fields: ['username'], + }, + ], + }, + ); + const Task = this.sequelize.define( + 'Task', + { title: DataTypes.STRING }, + { + indexes: [ + { + name: 'title_index', + method: 'BTREE', + fields: ['title'], + }, + ], + }, + ); + // create associations + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + await this.sequelize.sync({ force: true }); + }); + + it('should catch EmptyResultError when rejectOnEmpty is set', async function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { rejectOnEmpty: true }, + ); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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'); + }); + }); + + describe('addMultipleAssociations', () => { + it('supports both single instance and array', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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(async function () { + const Project = this.sequelize.define('Project', { + name: DataTypes.STRING, + }); + + const Employee = this.sequelize.define('Employee', { + name: DataTypes.STRING, + }); + + const Participation = this.sequelize.define('Participation', { + role: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: { + args: [2, 50], + msg: 'too bad', + }, + }, + }, + }); + + Project.belongsToMany(Employee, { + as: 'Participants', + through: Participation, + inverse: { as: 'Projects' }, + }); + + 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', async function () { + await expect(this.project.addParticipant(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', async function () { + await expect( + this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } }), + ).to.be.rejected; + }); + }); + + describe('optimizations using bulk create, destroy and update', () => { + beforeEach(function () { + this.User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { timestamps: false }, + ); + this.Task = this.sequelize.define('Task', { title: DataTypes.STRING }, { timestamps: false }); + + this.User.belongsToMany(this.Task, { through: 'UserTasks' }); + this.Task.belongsToMany(this.User, { through: 'UserTasks' }); + + return this.sequelize.sync({ force: true }); + }); + + it('uses one insert into statement', async function () { + const spy = sinon.spy(); + + 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' }), + ]); + + await user.setTasks([task1, task2], { + logging: spy, + }); + + expect(spy.calledTwice).to.be.ok; + }); + + it('uses one delete from statement', async function () { + const spy = sinon.spy(); + + const [user0, task1, task2] = await Promise.all([ + this.User.create({ username: 'foo' }), + this.Task.create({ title: 'task1' }), + this.Task.create({ title: 'task2' }), + ]); + + 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 + + describe('join table creation', () => { + beforeEach(function () { + this.User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { tableName: 'users' }, + ); + this.Task = this.sequelize.define( + 'Task', + { title: DataTypes.STRING }, + { tableName: 'tasks' }, + ); + + this.User.belongsToMany(this.Task, { through: 'user_has_tasks' }); + this.Task.belongsToMany(this.User, { through: 'user_has_tasks' }); + + return this.sequelize.sync({ force: true }); + }); + + it('should work with non integer primary keys', async function () { + const Beacons = this.sequelize.define('Beacon', { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: sql.uuidV4, + }, + name: { + type: DataTypes.STRING, + }, + }); + + // User not to clash with the beforeEach definition + const Users = this.sequelize.define('Usar', { + name: { + type: DataTypes.STRING, + }, + }); + + Beacons.belongsToMany(Users, { through: 'UserBeacons' }); + Users.belongsToMany(Beacons, { through: 'UserBeacons' }); + + await this.sequelize.sync({ force: true }); + }); + + it('makes join table non-paranoid by default', () => { + const paranoidSequelize = Support.createSingleTestSequelizeInstance({ + define: { + paranoid: true, + }, + }); + const ParanoidUser = paranoidSequelize.define('ParanoidUser', {}); + const ParanoidTask = paranoidSequelize.define('ParanoidTask', {}); + + const taskAssociation = ParanoidUser.belongsToMany(ParanoidTask, { through: 'UserTasks' }); + const userAssociation = ParanoidTask.belongsToMany(ParanoidUser, { through: 'UserTasks' }); + + expect(ParanoidUser.options.paranoid).to.be.ok; + expect(ParanoidTask.options.paranoid).to.be.ok; + expect(userAssociation.throughModel).to.equal(taskAssociation.throughModel); + expect(userAssociation.throughModel.options.paranoid).not.to.be.ok; + }); + + it('should allow creation of a paranoid join table', () => { + const paranoidSequelize = Support.createSingleTestSequelizeInstance({ + define: { + paranoid: true, + }, + }); + const ParanoidUser = paranoidSequelize.define('ParanoidUser', {}); + const ParanoidTask = paranoidSequelize.define('ParanoidTask', {}); + + const taskAssociation = ParanoidUser.belongsToMany(ParanoidTask, { + through: { + model: 'UserTasks', + paranoid: true, + }, + }); + const userAssociation = ParanoidTask.belongsToMany(ParanoidUser, { + through: { + model: 'UserTasks', + paranoid: true, + }, + }); + + expect(ParanoidUser.options.paranoid).to.be.ok; + expect(ParanoidTask.options.paranoid).to.be.ok; + expect(userAssociation.throughModel).to.equal(taskAssociation.throughModel); + expect(userAssociation.throughModel.options.paranoid).to.be.ok; + }); + }); + + describe('foreign keys', () => { + it('generates camelCase foreign keys if underscored is not set on the through model', function () { + const User = this.sequelize.define( + 'User', + {}, + { + tableName: 'users', + underscored: true, + timestamps: false, + }, + ); + + const Place = this.sequelize.define( + 'Place', + {}, + { + tableName: 'places', + underscored: true, + timestamps: false, + }, + ); + + User.belongsToMany(Place, { through: 'user_places' }); + Place.belongsToMany(User, { through: 'user_places' }); + + const attributes = this.sequelize.models.getOrThrow('user_places').getAttributes(); + + expect(attributes.placeId.field).to.equal('placeId'); + expect(attributes.userId.field).to.equal('userId'); + }); + + it('generates snake_case foreign keys if underscored is set on the through model', function () { + const User = this.sequelize.define('User', {}, { timestamps: false }); + const Place = this.sequelize.define('Place', {}, { timestamps: false }); + const UserPlaces = this.sequelize.define( + 'UserPlaces', + {}, + { underscored: true, timestamps: false }, + ); + + User.belongsToMany(Place, { through: UserPlaces }); + Place.belongsToMany(User, { through: UserPlaces }); + + const attributes = UserPlaces.getAttributes(); + + expect(attributes.placeId.field).to.equal('place_id'); + expect(attributes.userId.field).to.equal('user_id'); + }); + + it('should infer otherKey from paired BTM relationship with a through string defined', function () { + const User = this.sequelize.define('User', {}); + const Place = this.sequelize.define('Place', {}); + + const Places = User.belongsToMany(Place, { + through: 'user_places', + foreignKey: 'user_id', + otherKey: 'place_id', + }); + + expect(Places.foreignKey).to.equal('user_id'); + expect(Places.pairedWith.foreignKey).to.equal('place_id'); + + expect(Places.otherKey).to.equal('place_id'); + expect(Places.pairedWith.otherKey).to.equal('user_id'); + }); + }); + + describe('foreign key with fields specified', () => { + beforeEach(function () { + this.User = this.sequelize.define('User', { name: DataTypes.STRING }); + this.Project = this.sequelize.define('Project', { name: DataTypes.STRING }); + this.Puppy = this.sequelize.define('Puppy', { breed: DataTypes.STRING }); + + this.User.belongsToMany(this.Project, { + through: 'user_projects', + as: 'Projects', + inverse: { + as: 'Users', + }, + foreignKey: { + field: 'user_id', + name: 'userId', + }, + otherKey: { + field: 'project_id', + name: 'projectId', + }, + }); + }); + + it('should correctly get associations even after a child instance is deleted', async function () { + const spy = sinon.spy(); + + 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(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; + const project = projects[0]; + + expect(project).to.be.ok; + }); + + it('should correctly get associations when doubly linked', async function () { + const spy = sinon.spy(); + await this.sequelize.sync({ force: true }); + + 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', async function () { + this.Group = this.sequelize.define('Group', { groupName: DataTypes.STRING }); + + this.Group.belongsToMany(this.User, { + through: 'group_users', + as: 'Users', + foreignKey: { + field: 'group_id', + name: 'groupId', + }, + otherKey: { + field: 'user_id', + name: 'userId', + }, + }); + this.User.belongsToMany(this.Group, { + through: 'group_users', + as: 'Groups', + foreignKey: { + field: 'user_id', + name: 'userId', + }, + otherKey: { + field: 'group_id', + name: 'groupId', + }, + }); + + 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' }), + ]); + + await user0.addProject(project0); + await group1.addUser(user0); + const group0 = group1; + + // get the group and include both the users in the group and their project's + const groups = await this.Group.findAll({ + where: { id: group0.id }, + include: [ + { + model: this.User, + as: 'Users', + include: [{ model: this.Project, as: 'Projects' }], + }, + ], + }); + + const group = groups[0]; + expect(group).to.be.ok; + + const user = group.Users[0]; + expect(user).to.be.ok; + + const project = user.Projects[0]; + expect(project).to.be.ok; + expect(project.name).to.equal('Good Will Hunting'); + }); + }); + + describe('primary key handling for join table', () => { + beforeEach(function () { + this.User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { tableName: 'users' }, + ); + this.Task = this.sequelize.define( + 'Task', + { title: DataTypes.STRING }, + { tableName: 'tasks' }, + ); + }); + + it('removes the primary key if it was added by sequelize', function () { + this.UserTasks = this.sequelize.define('usertasks', {}); + + this.User.belongsToMany(this.Task, { through: this.UserTasks }); + this.Task.belongsToMany(this.User, { through: this.UserTasks }); + + expect(Object.keys(this.UserTasks.primaryKeys).sort()).to.deep.equal(['taskId', 'userId']); + }); + + it('keeps the primary key if it was added by the user', function () { + this.UserTasks = this.sequelize.define('UserTask', { + id: { + type: DataTypes.INTEGER, + autoincrement: true, + primaryKey: true, + }, + }); + this.UserTasks2 = this.sequelize.define('UserTask2', { + userTasksId: { + type: DataTypes.INTEGER, + autoincrement: true, + primaryKey: true, + }, + }); + + this.User.belongsToMany(this.Task, { through: this.UserTasks }); + this.User.belongsToMany(this.Task, { + through: this.UserTasks2, + as: 'otherTasksB', + inverse: { as: 'otherUsers' }, + }); + + expect(Object.keys(this.UserTasks.primaryKeys)).to.deep.equal(['id']); + expect(Object.keys(this.UserTasks2.primaryKeys)).to.deep.equal(['userTasksId']); + + for (const model of [this.UserTasks, this.UserTasks2]) { + const index = model.getIndexes()[0]; + + expect(index.fields.sort()).to.deep.equal(['taskId', 'userId']); + } + }); + + describe('without sync', () => { + 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', async function () { + this.UsersTasks = this.sequelize.define('UsersTasks', {}, { tableName: 'users_tasks' }); + + this.User.belongsToMany(this.Task, { through: this.UsersTasks }); + this.Task.belongsToMany(this.User, { through: this.UsersTasks }); + + expect(Object.keys(this.UsersTasks.primaryKeys).sort()).to.deep.equal(['taskId', 'userId']); + + const [user, task] = await Promise.all([ + this.User.create({ username: 'foo' }), + this.Task.create({ title: 'foo' }), + ]); + + await user.addTask(task); + await user.setTasks(null); + + expect(await user.getTasks()).to.have.length(0); + }); + }); + }); + + describe('through', () => { + describe('paranoid', () => { + beforeEach(async function () { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define( + 'UserProjects', + {}, + { + paranoid: true, + }, + ); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + await this.sequelize.sync(); + + this.users = await Promise.all([ + this.User.create(), + this.User.create(), + this.User.create(), + ]); + + this.projects = await Promise.all([ + this.Project.create(), + this.Project.create(), + this.Project.create(), + ]); + }); + + it('gets only non-deleted records by default', async function () { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + projectId: this.projects[0].id, + }, + }); + + const result = await this.users[0].getProjects(); + + expect(result.length).to.equal(2); + }); + + it('returns both deleted and non-deleted records with paranoid=false', async function () { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + projectId: this.projects[0].id, + }, + }); + + const result = await this.users[0].getProjects({ through: { paranoid: false } }); + + expect(result.length).to.equal(3); + }); + + it('hasAssociation also respects paranoid option', async function () { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + projectId: this.projects[0].id, + }, + }); + + expect( + await this.users[0].hasProjects(this.projects[0], { through: { paranoid: false } }), + ).to.equal(true); + + expect(await this.users[0].hasProjects(this.projects[0])).to.equal(false); + + expect(await this.users[0].hasProjects(this.projects[1])).to.equal(true); + + expect(await this.users[0].hasProjects(this.projects)).to.equal(false); + }); + }); + + describe('fetching from join table', () => { + beforeEach(function () { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', { + status: DataTypes.STRING, + data: DataTypes.INTEGER, + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + return this.sequelize.sync(); + }); + + it('should contain the data from the join table on .UserProjects a DAO', async function () { + const [user, project0] = await Promise.all([this.User.create(), this.Project.create()]); + + await user.addProject(project0, { through: { status: 'active', data: 42 } }); + const projects = await user.getProjects(); + const project = projects[0]; + + expect(project.userProject).to.be.ok; + expect(project.status).not.to.exist; + expect(project.userProject.status).to.equal('active'); + expect(project.userProject.data).to.equal(42); + }); + + 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()]); + + await user.addProject(project0, { through: { status: 'active', data: 42 } }); + + const users = await this.User.findAll({ + include: [ + { + model: this.Project, + through: { + as: 'myProject', + }, + }, + ], + }); + + const project = users[0].projects[0]; + + expect(project.UserProjects).not.to.exist; + expect(project.status).not.to.exist; + expect(project.myProject).to.be.ok; + expect(project.myProject.status).to.equal('active'); + expect(project.myProject.data).to.equal(42); + }); + + it('should be able to limit the join table attributes returned', async function () { + const [user, project0] = await Promise.all([this.User.create(), this.Project.create()]); + + await user.addProject(project0, { through: { status: 'active', data: 42 } }); + const projects = await user.getProjects({ joinTableAttributes: ['status'] }); + const project = projects[0]; + + expect(project.userProject).to.be.ok; + expect(project.status).not.to.exist; + expect(project.userProject.status).to.equal('active'); + expect(project.userProject.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', async function () { + const [u, p] = await Promise.all([this.User.create(), this.Project.create()]); + + 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', async function () { + const [u, p] = await Promise.all([this.User.create(), this.Project.create()]); + + 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', async function () { + const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); + const Task = this.sequelize.define('Task', {}, { timestamps: false }); + const WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); + + Worker.belongsToMany(Task, { through: WorkerTasks }); + Task.belongsToMany(Worker, { through: WorkerTasks }); + + 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', async function () { + const Worker = this.sequelize.define( + 'Worker', + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + }, + { timestamps: false }, + ); + const Task = this.sequelize.define( + 'Task', + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + }, + { timestamps: false }, + ); + const WorkerTasks = this.sequelize.define( + 'WorkerTasks', + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + }, + { timestamps: false }, + ); + + Worker.belongsToMany(Task, { through: WorkerTasks }); + Task.belongsToMany(Worker, { through: WorkerTasks }); + + 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', async function () { + const Foo = this.sequelize.define('foo', { name: DataTypes.STRING }); + const Bar = this.sequelize.define('bar', { name: DataTypes.STRING }); + const FooBar = this.sequelize.define('foobar', { baz: DataTypes.STRING }); + Foo.belongsToMany(Bar, { through: FooBar }); + Bar.belongsToMany(Foo, { through: FooBar }); + + await this.sequelize.sync({ force: true }); + + const foo0 = await Foo.create( + { + name: 'foo...', + bars: [ + { + name: 'bar...', + foobar: { + baz: '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', async function () { + await this.Project.bulkCreate([{}, {}]); + + const [user, projects] = await Promise.all([ + this.User.create(), + 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', async function () { + const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); + const Task = this.sequelize.define('Task', {}, { timestamps: false }); + const WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); + + Worker.belongsToMany(Task, { through: WorkerTasks }); + Task.belongsToMany(Worker, { through: WorkerTasks }); + + await this.sequelize.sync({ force: true }); + + const [worker0, tasks0] = await Promise.all([ + dialect === 'db2' ? Worker.create({ id: 1 }) : 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', 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', async function () { + const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); + const Task = this.sequelize.define('Task', {}, { timestamps: false }); + const WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); + + Worker.belongsToMany(Task, { through: WorkerTasks }); + Task.belongsToMany(Worker, { through: WorkerTasks }); + + // Test setup + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + dialect === 'db2' ? Worker.create({ id: 1 }) : 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', async function () { + const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); + const Task = this.sequelize.define('Task', {}, { timestamps: false }); + const WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); + + Worker.belongsToMany(Task, { through: WorkerTasks }); + Task.belongsToMany(Worker, { through: WorkerTasks }); + + // Test setup + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + dialect === 'db2' ? Worker.create({ id: 1 }) : 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); + }); + }); + }); + + describe('multiple hasMany', () => { + beforeEach(function () { + this.User = this.sequelize.define('user', { name: DataTypes.STRING }); + this.Project = this.sequelize.define('project', { projectName: DataTypes.STRING }); + }); + + describe('project has owners and users and owners and users have projects', () => { + beforeEach(function () { + this.Project.belongsToMany(this.User, { + as: 'owners', + through: 'projectOwners', + inverse: { as: 'ownedProjects' }, + }); + this.Project.belongsToMany(this.User, { + as: 'users', + through: 'projectUsers', + inverse: { as: 'memberProjects' }, + }); + + return this.sequelize.sync({ force: true }); + }); + + it('correctly sets user and owner', async function () { + const p1 = this.Project.build({ projectName: 'p1' }); + const u1 = this.User.build({ name: 'u1' }); + const u2 = this.User.build({ name: 'u2' }); + + await p1.save(); + + await u1.save(); + await u2.save(); + await p1.setUsers([u1]); + + await p1.setOwners([u2]); + }); + }); + }); + + describe('Foreign key constraints', () => { + beforeEach(function () { + this.Task = this.sequelize.define('task', { title: DataTypes.STRING }); + this.User = this.sequelize.define('user', { username: DataTypes.STRING }); + this.UserTasks = this.sequelize.define('tasksusers', { + userId: DataTypes.INTEGER, + taskId: DataTypes.INTEGER, + }); + }); + + it('can cascade deletes both ways by default', async function () { + this.User.belongsToMany(this.Task, { through: 'tasksusers' }); + this.Task.belongsToMany(this.User, { 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 [tu1, tu2] = await Promise.all([ + this.sequelize.models.getOrThrow('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.models.getOrThrow('tasksusers').findAll({ where: { taskId: task2.id } }), + this.User.findOne({ + where: { 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, { + through: 'tasksusers', + foreignKey: { onDelete: 'RESTRICT' }, + otherKey: { onDelete: 'RESTRICT' }, + }); + + 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([ + 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', async function () { + this.User.belongsToMany(this.Task, { + through: 'tasksusers', + foreignKey: { onDelete: 'RESTRICT' }, + otherKey: { onDelete: 'CASCADE' }, + }); + + 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([ + expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of + // RESTRICT constraint + task2.destroy(), + ]); + + const usertasks = await this.sequelize.models + .getOrThrow('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, { + foreignKeyConstraints: false, + through: 'tasksusers', + inverse: { foreignKeyConstraints: false }, + }); + + const Through = this.sequelize.models.getOrThrow('tasksusers'); + expect(Through.getAttributes().taskId.references).to.eq( + undefined, + 'Attribute taskId should not be a foreign key', + ); + expect(Through.getAttributes().userId.references).to.eq( + undefined, + 'Attribute userId should not be a foreign key', + ); + + 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.models.getOrThrow('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.models.getOrThrow('tasksusers').findAll({ where: { taskId: task2.id } }), + ]); + + expect(ut1).to.have.length(1); + expect(ut2).to.have.length(1); + }); + }); + + describe('Association options', () => { + describe('allows the user to provide an attribute definition object as foreignKey', () => { + it('works when taking a column directly from the object', function () { + const Project = this.sequelize.define('project', {}); + const User = this.sequelize.define('user', { + uid: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + const UserProjects = User.belongsToMany(Project, { + foreignKey: { name: 'user_id', defaultValue: 42 }, + through: 'UserProjects', + }); + expect(UserProjects.through.model.getAttributes().user_id).to.be.ok; + const targetTable = UserProjects.through.model.getAttributes().user_id.references.table; + assert(typeof targetTable === 'object'); + + expect(targetTable).to.deep.equal(User.table); + expect(UserProjects.through.model.getAttributes().user_id.references.key).to.equal('uid'); + expect(UserProjects.through.model.getAttributes().user_id.defaultValue).to.equal(42); + }); + }); + + it('should throw an error if foreignKey and as result in a name clash', function () { + const User = this.sequelize.define('user', { + user: DataTypes.INTEGER, + }); + + expect(User.belongsToMany.bind(User, User, { as: 'user', through: 'UserUser' })).to.throw( + "Naming collision between attribute 'user' and association 'user' on model user. To remedy this, change the \"as\" options in your association definition", + ); + }); + }); + + describe('thisAssociations', () => { + it('should work with this reference', async function () { + const User = this.sequelize.define('User', { + name: DataTypes.STRING(100), + }); + const Follow = this.sequelize.define('Follow'); + + User.belongsToMany(User, { through: Follow, as: 'Users', inverse: { as: 'Fans' } }); + + 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', async function () { + const User = this.sequelize.define('User', { + name: DataTypes.STRING(100), + }); + const UserFollowers = this.sequelize.define('UserFollower'); + + User.belongsToMany(User, { + as: { + singular: 'Follower', + plural: 'Followers', + }, + through: UserFollowers, + inverse: { + as: 'Followings', + }, + }); + + User.belongsToMany(User, { + as: { + singular: 'Invitee', + plural: 'Invitees', + }, + foreignKey: 'inviteeId', + through: 'Invites', + inverse: { + as: 'Hosts', + }, + }); + + 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]), + ]); + }); + }); + + describe('Eager loading', () => { + beforeEach(function () { + this.Individual = this.sequelize.define('individual', { + name: DataTypes.STRING, + }); + this.Hat = this.sequelize.define('hat', { + name: DataTypes.STRING, + }); + this.Event = this.sequelize.define('event', {}); + this.Individual.belongsToMany(this.Hat, { + through: this.Event, + as: { + singular: 'personwearinghat', + plural: 'personwearinghats', + }, + }); + this.Hat.belongsToMany(this.Individual, { + through: this.Event, + as: { + singular: 'hatwornby', + plural: 'hatwornbys', + }, + }); + }); + + 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'); + }); + }); + + describe('Disabling default unique constraint', () => { + beforeEach(function () { + this.Order = this.sequelize.define('order', {}); + this.Good = this.sequelize.define('good', { + name: DataTypes.STRING, + }); + this.OrderItem = this.sequelize.define('orderitem', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + isUpsell: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + }); + + this.Order.belongsToMany(this.Good, { + through: { + model: this.OrderItem, + unique: false, + }, + }); + this.Good.belongsToMany(this.Order, { + through: { + model: this.OrderItem, + unique: false, + }, + }); + }); + + it('should create new relations in parallel', async function () { + await this.sequelize.sync({ force: true }); + + const { Good, Order } = this; + + const order = await Order.create(); + const good = await Good.create({ name: 'Drink' }); + + await Promise.all([ + order.addGood(good, { + through: { + isUpsell: false, + }, + }), + order.addGood(good, { + through: { + isUpsell: true, + }, + }), + ]); + + const orderGoods = await order.getGoods(); + + const [firstGood, secondGood] = orderGoods; + + expect(orderGoods.length).to.equal(2); + expect(firstGood.orderGood.isUpsell).to.be.not.equal(secondGood.orderGood.isUpsell); + }); + + it('should create new relations sequentialy', async function () { + await this.sequelize.sync({ force: true }); + + const { Good, Order } = this; + + const order = await Order.create(); + const good = await Good.create({ name: 'Drink' }); + + await order.addGood(good, { + through: { + isUpsell: false, + }, + }); + await order.addGood(good, { + through: { + isUpsell: true, + }, + }); + + const orderGoods = await order.getGoods(); + + const [firstGood, secondGood] = orderGoods; + + expect(orderGoods.length).to.equal(2); + expect(firstGood.orderGood.isUpsell).to.be.not.equal(secondGood.orderGood.isUpsell); + }); + }); +}); diff --git a/packages/core/test/integration/associations/belongs-to.test.js b/packages/core/test/integration/associations/belongs-to.test.js new file mode 100644 index 000000000000..7bd735627724 --- /dev/null +++ b/packages/core/test/integration/associations/belongs-to.test.js @@ -0,0 +1,1005 @@ +'use strict'; + +const chai = require('chai'); +const sinon = require('sinon'); + +const expect = chai.expect; +const Support = require('../support'); +const { DataTypes, Sequelize } = require('@sequelize/core'); +const assert = require('node:assert'); + +const current = Support.sequelize; +const dialect = Support.getTestDialect(); + +describe(Support.getTestDialectTeaser('BelongsTo'), () => { + describe('Model.associations', () => { + it('should store all associations when associating to the same table multiple times', function () { + const User = this.sequelize.define('User', {}); + const Group = this.sequelize.define('Group', {}); + + Group.belongsTo(User); + Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }); + Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }); + + expect(Object.keys(Group.associations)).to.deep.equal([ + 'user', + 'primaryUsers', + 'secondaryUsers', + ]); + }); + }); + + describe('get', () => { + describe('multiple', () => { + it('should fetch associations for multiple instances', async function () { + const User = this.sequelize.define('User', {}); + const Task = this.sequelize.define('Task', {}); + + Task.User = Task.belongsTo(User, { as: 'user' }); + + 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.get(tasks[0].id).id).to.equal(tasks[0].user.id); + expect(result.get(tasks[1].id).id).to.equal(tasks[1].user.id); + expect(result.get(tasks[2].id)).to.be.undefined; + }); + }); + }); + + describe('getAssociation', () => { + if (current.dialect.supports.transactions) { + it('supports transactions', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Group = sequelize.define('Group', { name: DataTypes.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.startUnmanagedTransaction(); + 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.", async function () { + const User = this.sequelize.define('UserXYZ', { + username: DataTypes.STRING, + gender: DataTypes.STRING, + }); + const Task = this.sequelize.define('TaskXYZ', { + title: DataTypes.STRING, + status: DataTypes.STRING, + }); + + Task.belongsTo(User); + + 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; + }); + + if (current.dialect.supports.schemas) { + it('supports schemas', async function () { + const User = this.sequelize + .define('UserXYZ', { username: DataTypes.STRING, gender: DataTypes.STRING }) + .withSchema('archive'); + const Task = this.sequelize + .define('TaskXYZ', { title: DataTypes.STRING, status: DataTypes.STRING }) + .withSchema('archive'); + + Task.belongsTo(User); + + 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.queryInterface.dropAllTables({ schema: 'archive' }); + await this.sequelize.dropSchema('archive'); + const schemas = await this.sequelize.queryInterface.listSchemas(); + expect(schemas).to.not.include('archive'); + }); + + it('supports schemas when defining custom foreign key attribute #9029', async function () { + const User = this.sequelize + .define('UserXYZ', { + uid: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + }, + }) + .withSchema('archive'); + const Task = this.sequelize + .define('TaskXYZ', { + user_id: { + type: DataTypes.INTEGER, + references: { model: User, key: 'uid' }, + }, + }) + .withSchema('archive'); + + Task.belongsTo(User, { foreignKey: 'user_id' }); + + 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; + }); + } + }); + + describe('setAssociation', () => { + if (current.dialect.supports.transactions) { + it('supports transactions', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Group = sequelize.define('Group', { name: DataTypes.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.startUnmanagedTransaction(); + 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...', async function () { + const User = this.sequelize.define('UserXYZ', { + user_id: { type: DataTypes.INTEGER, primaryKey: true }, + username: DataTypes.STRING, + }); + const Task = this.sequelize.define('TaskXYZ', { + task_id: { type: DataTypes.INTEGER, primaryKey: true }, + title: DataTypes.STRING, + }); + + Task.belongsTo(User, { foreignKey: 'user_id' }); + + 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', async function () { + const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }); + const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); + + Task.belongsTo(User); + + 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', async function () { + const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }); + const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); + + Task.belongsTo(User); + + 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', async function () { + const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }); + const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); + + Task.belongsTo(User); + + 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', async function () { + const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }); + const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); + const spy = sinon.spy(); + + Task.belongsTo(User); + + 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', async function () { + const Comment = this.sequelize.define('comment', { + text: DataTypes.STRING, + }); + + const Post = this.sequelize.define('post', { + title: DataTypes.STRING, + }); + + Post.hasOne(Comment); + Comment.belongsTo(Post); + + 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', async function () { + const Comment = this.sequelize.define('comment', { + text: DataTypes.STRING, + }); + + const Post = this.sequelize.define('post', { + title: DataTypes.STRING, + }); + + Post.hasMany(Comment, { foreignKey: 'post_id' }); + Comment.belongsTo(Post, { foreignKey: 'post_id' }); + + await this.sequelize.sync({ force: true }); + const [post, comment] = await Promise.all([Post.create(), Comment.create()]); + expect(comment.get('post_id')).not.to.be.ok; + + const setter = await comment.setPost(post, { save: false }); + + expect(setter).to.be.undefined; + expect(comment.get('post_id')).to.equal(post.get('id')); + expect(comment.changed('post_id')).to.be.true; + }); + + it('supports setting same association twice', async function () { + const Home = this.sequelize.define('home', {}); + const User = this.sequelize.define('user'); + + Home.belongsTo(User); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + Task.belongsTo(User); + + 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', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Group = sequelize.define('Group', { name: DataTypes.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.startUnmanagedTransaction(); + 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(); + }); + } + }); + + describe('foreign key', () => { + it('should setup underscored field with foreign keys when using underscored', function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { underscored: true }, + ); + const Account = this.sequelize.define( + 'Account', + { name: DataTypes.STRING }, + { underscored: true }, + ); + + User.belongsTo(Account); + + expect(User.getAttributes().accountId).to.exist; + expect(User.getAttributes().accountId.field).to.equal('account_id'); + }); + + it('should use model name when using camelcase', function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { underscored: false }, + ); + const Account = this.sequelize.define( + 'Account', + { name: DataTypes.STRING }, + { underscored: false }, + ); + + User.belongsTo(Account); + + expect(User.getAttributes().accountId).to.exist; + expect(User.getAttributes().accountId.field).to.equal('accountId'); + }); + + it('should support specifying the field of a foreign key', async function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { underscored: false }, + ); + const Account = this.sequelize.define( + 'Account', + { title: DataTypes.STRING }, + { underscored: false }, + ); + + User.belongsTo(Account, { + foreignKey: { + name: 'AccountId', + field: 'account_id', + }, + }); + + expect(User.getAttributes().AccountId).to.exist; + expect(User.getAttributes().AccountId.field).to.equal('account_id'); + + 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', 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 }); + + Entry.belongsTo(User, { + as: 'owner', + foreignKey: { + name: 'ownerId', + allowNull: false, + }, + }); + Entry.belongsTo(Mail, { + as: 'mail', + foreignKey: { + name: 'mailId', + allowNull: false, + }, + }); + Mail.belongsToMany(User, { + as: 'recipients', + through: { + model: 'MailRecipients', + timestamps: false, + }, + otherKey: { + name: 'recipientId', + allowNull: false, + }, + foreignKey: { + name: 'mailId', + allowNull: false, + }, + }); + Mail.hasMany(Entry, { + as: 'entries', + foreignKey: { + name: 'mailId', + allowNull: false, + }, + }); + User.hasMany(Entry, { + as: 'entries', + foreignKey: { + name: 'ownerId', + allowNull: false, + }, + }); + + await this.sequelize.sync({ force: true }); + await User.create(dialect === 'db2' ? { id: 1 } : {}); + const mail = await Mail.create(dialect === 'db2' ? { id: 1 } : {}); + 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', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User); // defaults to SET 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('should be possible to disable them', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKeyConstraints: false }); + + 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', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: { onDelete: 'cascade' } }); + + 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', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: { onDelete: 'restrict' } }); + + 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', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: { onUpdate: 'restrict' } }); + + 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.table; + + await expect( + user.sequelize.queryInterface.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 (!['mssql', 'db2', 'ibmi'].includes(dialect)) { + it('can cascade updates', async function () { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: { onUpdate: 'cascade' } }); + + 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.table; + await user.sequelize.queryInterface.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', async function () { + const User = this.sequelize.define('UserPKBT', { + username: { + type: DataTypes.STRING, + }, + }); + + const Group = this.sequelize.define('GroupPKBT', { + name: { + type: DataTypes.STRING, + primaryKey: true, + }, + }); + + User.belongsTo(Group); + + await this.sequelize.sync({ force: true }); + expect(User.getAttributes().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', 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' }); + + 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.queryInterface.showConstraints(Task, { + constraintType: 'FOREIGN KEY', + }); + expect(foreignKeysDescriptions[0]).to.deep.include({ + referencedColumnNames: ['username'], + referencedTableName: 'Users', + columnNames: ['user_name'], + }); + }); + + it('should support a non-primary unique key as the association column', async function () { + const User = this.sequelize.define('User', { + username: { + type: DataTypes.STRING, + field: 'user_name', + unique: true, + }, + }); + const Task = this.sequelize.define('Task', { + title: DataTypes.STRING, + }); + + Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); + + 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.queryInterface.showConstraints(Task, { + constraintType: 'FOREIGN KEY', + }); + expect(foreignKeysDescriptions[0]).to.deep.include({ + referencedColumnNames: ['user_name'], + referencedTableName: 'Users', + columnNames: ['user_name'], + }); + }); + + 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, + field: 'the_user_name_field', + unique: true, + }, + }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.removeAttribute('id'); + Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); + + 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.queryInterface.showConstraints(Task, { + constraintType: 'FOREIGN KEY', + }); + expect(foreignKeysDescriptions[0]).to.deep.include({ + referencedColumnNames: ['the_user_name_field'], + referencedTableName: 'Users', + columnNames: ['user_name'], + }); + }); + + 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, + field: 'the_user_name_field', + unique: true, + }, + age: { + type: DataTypes.INTEGER, + field: 'the_user_age_field', + primaryKey: true, + }, + weight: { + type: DataTypes.INTEGER, + field: 'the_user_weight_field', + primaryKey: true, + }, + }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); + + 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.queryInterface.showConstraints(Task, { + constraintType: 'FOREIGN KEY', + }); + expect(foreignKeysDescriptions[0]).to.deep.include({ + referencedColumnNames: ['the_user_name_field'], + referencedTableName: 'Users', + columnNames: ['user_name'], + }); + }); + }); + + describe('association options', () => { + it('can specify data type for auto-generated relational keys', async function () { + const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }); + const dataTypes = [DataTypes.INTEGER, DataTypes.STRING]; + const Tasks = {}; + + if (current.dialect.supports.dataTypes.BIGINT) { + dataTypes.push(DataTypes.BIGINT); + } + + for (const dataType of dataTypes) { + const tableName = `TaskXYZ_${dataType.getDataTypeId()}`; + Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING }); + Tasks[dataType].belongsTo(User, { + foreignKey: { name: 'userId', type: dataType }, + foreignKeyConstraints: false, + }); + } + + await this.sequelize.sync({ force: true }); + for (const dataType of dataTypes) { + expect(Tasks[dataType].getAttributes().userId.type).to.be.an.instanceof(dataType); + } + }); + + describe('allows the user to provide an attribute definition object as foreignKey', () => { + it('works with a column that hasnt been defined before', function () { + const Task = this.sequelize.define('task', {}); + const User = this.sequelize.define('user', {}); + + Task.belongsTo(User, { + foreignKey: { + allowNull: false, + name: 'uid', + }, + }); + + expect(Task.getAttributes().uid).to.be.ok; + expect(Task.getAttributes().uid.allowNull).to.be.false; + const targetTable = Task.getAttributes().uid.references.table; + assert(typeof targetTable === 'object'); + + expect(targetTable).to.deep.equal(User.table); + + expect(Task.getAttributes().uid.references.key).to.equal('id'); + }); + + it('works when taking a column directly from the object', function () { + const User = this.sequelize.define('user', { + uid: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + const Profile = this.sequelize.define('project', { + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + }); + + Profile.belongsTo(User, { foreignKey: Profile.getAttributes().user_id }); + + expect(Profile.getAttributes().user_id).to.be.ok; + const targetTable = Profile.getAttributes().user_id.references.table; + assert(typeof targetTable === 'object'); + + expect(targetTable).to.deep.equal(User.table); + expect(Profile.getAttributes().user_id.references.key).to.equal('uid'); + expect(Profile.getAttributes().user_id.allowNull).to.be.false; + }); + + it('works when merging with an existing definition', function () { + const Task = this.sequelize.define('task', { + projectId: { + defaultValue: 42, + type: DataTypes.INTEGER, + }, + }); + const Project = this.sequelize.define('project', {}); + + Task.belongsTo(Project, { foreignKey: { allowNull: true } }); + + expect(Task.getAttributes().projectId).to.be.ok; + expect(Task.getAttributes().projectId.defaultValue).to.equal(42); + expect(Task.getAttributes().projectId.allowNull).to.be.ok; + }); + }); + }); + + describe('Eager loading', () => { + beforeEach(function () { + this.Individual = this.sequelize.define('individual', { + name: DataTypes.STRING, + }); + this.Hat = this.sequelize.define('hat', { + name: DataTypes.STRING, + }); + this.Individual.belongsTo(this.Hat, { + as: 'personwearinghat', + }); + }); + + 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: 'personwearinghat', + }, + ], + }); + + 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/packages/core/test/integration/associations/has-many-mixins.test.ts b/packages/core/test/integration/associations/has-many-mixins.test.ts new file mode 100644 index 000000000000..df80fa818170 --- /dev/null +++ b/packages/core/test/integration/associations/has-many-mixins.test.ts @@ -0,0 +1,449 @@ +import type { + CreationOptional, + HasManyAddAssociationsMixin, + HasManyHasAssociationsMixin, + HasManyRemoveAssociationsMixin, + HasManySetAssociationsMixin, + InferAttributes, + InferCreationAttributes, +} from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { AllowNull, Attribute, HasMany, NotNull } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { + beforeAll2, + createMultiTransactionalTestSequelizeInstance, + sequelize, + setResetMode, +} from '../support'; + +const dialect = sequelize.dialect; + +describe('hasMany Mixins', () => { + setResetMode('destroy'); + + const vars = beforeAll2(async () => { + class Article extends Model, InferCreationAttributes
> { + declare id: CreationOptional; + + @HasMany(() => Label, 'articleId') + declare labels?: Label[]; + + declare setLabels: HasManySetAssociationsMixin; + declare removeLabels: HasManyRemoveAssociationsMixin; + declare hasLabels: HasManyHasAssociationsMixin; + declare addLabels: HasManyAddAssociationsMixin; + + @HasMany(() => NonNullLabel, 'articleId') + declare nonNullLabels?: NonNullLabel[]; + + declare setNonNullLabels: HasManySetAssociationsMixin; + declare removeNonNullLabels: HasManyRemoveAssociationsMixin; + } + + class Label extends Model, InferCreationAttributes
> { + declare id: CreationOptional; + + @HasMany(() => Label, 'articleId') + declare labels?: Label[]; + + declare setLabels: HasManySetAssociationsMixin; + declare removeLabels: HasManyRemoveAssociationsMixin; + } + + class Label extends Model, InferCreationAttributes
> { + declare id: CreationOptional; + + @HasOne(() => Label, 'articleId') + declare label?: Label; + + declare setLabel: HasOneSetAssociationMixin; + + @HasOne(() => NonNullLabel, 'articleId') + declare nonNullLabel?: NonNullLabel; + + declare setNonNullLabel: HasOneSetAssociationMixin; + } + + class Label extends Model, InferCreationAttributes
> { + declare id: CreationOptional; + + @HasOne(() => Label, 'articleId') + declare label?: Label; + + declare setLabel: HasOneSetAssociationMixin; + } + + class Label extends Model, InferCreationAttributes> { + declare id: CreationOptional; + + @Attribute(DataTypes.INTEGER) + @NotNull + declare bId: number; + + @BelongsTo(() => B, { + foreignKey: { + name: 'bId', + onDelete: 'NO ACTION', + }, + }) + declare b?: NonAttribute; + } + + class B extends Model, InferCreationAttributes> { + declare id: CreationOptional; + + @Attribute(DataTypes.INTEGER) + @NotNull + declare cId: number; + + @BelongsTo(() => C, { + foreignKey: { + name: 'cId', + onDelete: 'NO ACTION', + }, + }) + declare c?: NonAttribute; + } + + class C extends Model, InferCreationAttributes> { + declare id: CreationOptional; + } + + sequelize.addModels([A, C, B]); + + await sequelize.sync(); + + const c = await C.create(); + const b = await B.create({ cId: c.id }); + await A.create({ bId: b.id }); + + // drop both tables + await sequelize.destroyAll(); + + expect(await A.count()).to.eq(0); + expect(await B.count()).to.eq(0); + expect(await C.count()).to.eq(0); + }); +}); diff --git a/packages/core/test/integration/sequelize/drop.test.ts b/packages/core/test/integration/sequelize/drop.test.ts new file mode 100644 index 000000000000..76d9b60a8011 --- /dev/null +++ b/packages/core/test/integration/sequelize/drop.test.ts @@ -0,0 +1,71 @@ +import type { ReferentialAction } from '@sequelize/core'; +import { DataTypes, Deferrable } from '@sequelize/core'; +import { sequelize } from '../support'; + +const dialect = sequelize.dialect.name; + +describe('Sequelize#drop', () => { + it('supports dropping cyclic associations', async () => { + const A = sequelize.define('A', { + bId: { + type: DataTypes.INTEGER, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }, + }, + }); + + const B = sequelize.define('B', { + aId: { + type: DataTypes.INTEGER, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }, + }, + }); + + // mssql refuses cyclic references unless ON DELETE and ON UPDATE is set to NO ACTION + const mssqlConstraints = + dialect === 'mssql' + ? { onDelete: 'NO ACTION' as ReferentialAction, onUpdate: 'NO ACTION' as ReferentialAction } + : null; + + // These models both have a foreign key that references the other model. + // Sequelize should be able to create them. + A.belongsTo(B, { foreignKey: { allowNull: false, ...mssqlConstraints } }); + B.belongsTo(A, { foreignKey: { allowNull: false, ...mssqlConstraints } }); + + await sequelize.sync(); + + // drop both tables + await sequelize.drop(); + }); + + it('supports dropping cyclic associations with { cascade: true } in supported dialects', async () => { + if (!sequelize.dialect.supports.dropTable.cascade) { + return; + } + + const A = sequelize.define('A', { + bId: { + type: DataTypes.INTEGER, + }, + }); + + const B = sequelize.define('B', { + aId: { + type: DataTypes.INTEGER, + }, + }); + + // These models both have a foreign key that references the other model. + // Sequelize should be able to create them. + A.belongsTo(B, { foreignKey: { allowNull: false } }); + B.belongsTo(A, { foreignKey: { allowNull: false } }); + + await sequelize.sync(); + + // drop both tables + await sequelize.drop({ cascade: true }); + }); +}); diff --git a/packages/core/test/integration/sequelize/query.test.js b/packages/core/test/integration/sequelize/query.test.js new file mode 100644 index 000000000000..00357119657a --- /dev/null +++ b/packages/core/test/integration/sequelize/query.test.js @@ -0,0 +1,1005 @@ +'use strict'; + +const { expect } = require('chai'); +const { + DatabaseError, + DataTypes, + ForeignKeyConstraintError, + Sequelize, + sql, + UniqueConstraintError, +} = require('@sequelize/core'); +const sinon = require('sinon'); +const dayjs = require('dayjs'); +const { + allowDeprecationsInSuite, + beforeEach2, + createSequelizeInstance, + createSingleTestSequelizeInstance, + destroySequelizeAfterTest, + getTestDialect, + getTestDialectTeaser, + sequelize, +} = require('../support'); + +const dialectName = getTestDialect(); +const queryGenerator = sequelize.queryGenerator; + +const qq = str => { + if (['postgres', 'mssql', 'db2', 'ibmi'].includes(dialectName)) { + return `"${str}"`; + } + + if (['mysql', 'mariadb', 'sqlite3'].includes(dialectName)) { + return `\`${str}\``; + } + + return str; +}; + +describe(getTestDialectTeaser('Sequelize'), () => { + allowDeprecationsInSuite(['SEQUELIZE0023']); + + describe('query', () => { + afterEach(() => { + 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')`; + if (['db2', 'ibmi'].includes(dialectName)) { + 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); + }); + + 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)} (${qq('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(['db2', 'ibmi'].includes(dialectName) ? 1 : 3); + }); + }); + + describe('logging', () => { + it('executes a query with global benchmarking option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = createSingleTestSequelizeInstance({ + logging: logger, + benchmark: true, + }); + + await sequelize.query(`select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`); + 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${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`, + { + 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; + }); + + it('executes a query with queryLabel option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = createSingleTestSequelizeInstance({ + logging: logger, + }); + + await sequelize.query( + `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`, + { + queryLabel: 'tricky select', + }, + ); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match( + /^tricky select[\n]Executing \((\d*|default)\): select 1/, + ); + }); + + it('executes a query with empty string, queryLabel option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = createSingleTestSequelizeInstance({ + logging: logger, + }); + + await sequelize.query( + `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`, + { + queryLabel: '', + }, + ); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/^Executing \((\d*|default)\): select 1/); + }); + + it('executes a query with benchmarking option, queryLabel option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = createSingleTestSequelizeInstance({ + logging: logger, + benchmark: true, + }); + + await sequelize.query( + `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`, + { + queryLabel: 'tricky select', + }, + ); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match( + /^tricky select[\n]Executed \((\d*|default)\): select 1/, + ); + }); + + describe('with logQueryParameters', () => { + const vars = beforeEach2(async () => { + const sequelize = createSequelizeInstance({ + benchmark: true, + logQueryParameters: true, + }); + + const User = sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + username: { + type: DataTypes.STRING, + }, + emailAddress: { + type: DataTypes.STRING, + }, + }, + { + timestamps: false, + }, + ); + + await User.sync({ force: true }); + + return { sequelize, User }; + }); + + afterEach(() => { + return vars.sequelize.close(); + }); + + it('add parameters in log sql', async () => { + let createSql; + let updateSql; + + const user = await vars.User.create( + { + username: 'john', + emailAddress: 'john@gmail.com', + }, + { + logging: s => { + createSql = s; + }, + }, + ); + + user.username = 'li'; + + await user.save({ + logging: s => { + updateSql = s; + }, + }); + + if ( + dialectName === 'db2' || + dialectName === 'postgres' || + dialectName === 'mariadb' || + dialectName === 'mysql' + ) { + // these dialects use positional bind parameters + expect(createSql.endsWith(` with parameters [ 'john', 'john@gmail.com' ]`)).to.eq( + true, + 'bind parameters incorrectly logged for INSERT query', + ); + expect(updateSql.endsWith(` with parameters [ 'li', 1 ]`)).to.eq( + true, + 'bind parameters incorrectly logged for UPDATE query', + ); + } else { + // these dialects use named bind parameters + expect( + createSql.endsWith( + ` with parameters { sequelize_1: 'john', sequelize_2: 'john@gmail.com' }`, + ), + ).to.eq(true, 'bind parameters incorrectly logged for INSERT query'); + expect( + updateSql.endsWith(` with parameters { sequelize_1: 'li', sequelize_2: 1 }`), + ).to.eq(true, 'bind parameters incorrectly logged for UPDATE query'); + } + }); + + it('add parameters in log sql when use bind value', async () => { + let logSql; + let typeCast = dialectName === 'postgres' ? '::text' : ''; + if (['db2'].includes(dialectName)) { + typeCast = '::VARCHAR'; + } + + await vars.sequelize.query( + `select $1${typeCast} as foo, $2${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { + bind: ['foo', 'bar'], + logging: s => { + logSql = s; + }, + }, + ); + + expect(logSql.endsWith(` with parameters [ 'foo', 'bar' ]`)).to.eq( + true, + 'bind parameters incorrectly logged.', + ); + }); + }); + }); + + 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 sequelize = createSequelizeInstance({ + quoteIdentifiers: false, + }); + + destroySequelizeAfterTest(sequelize); + + await sequelize.query(this.insertQuery); + const [users] = await sequelize.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 ${qq('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 ${qq('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 (dialectName === '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 if (dialectName === 'db2') { + it('executes stored procedures', async function () { + const { sequelize } = this; + + await sequelize.query(this.insertQuery); + + try { + await sequelize.query('DROP PROCEDURE foo'); + } catch (error) { + // DB2 does not support DROP PROCEDURE IF EXISTS + // -204 means "FOO" does not exist + // https://www.ibm.com/docs/en/db2-for-zos/11?topic=sec-204 + if (error.cause.sqlcode !== -204) { + throw error; + } + } + + await sequelize.query( + `CREATE PROCEDURE foo() DYNAMIC RESULT SETS 1 LANGUAGE SQL BEGIN DECLARE cr1 CURSOR WITH RETURN FOR SELECT * FROM ${qq(this.User.tableName)}; OPEN cr1; END`, + ); + + const users = await sequelize.query('CALL foo()'); + expect(users.map(u => u.username)).to.include('john'); + }); + } + + 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.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.equal('john'); + expect(users[0].email).to.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.equal('john'); + expect(users[0].email).to.equal('john@gmail.com'); + }); + + // Only run stacktrace tests on Node 12+, since only Node 12+ supports + // async stacktraces + const nodeVersionMatch = process.version.match(/^v(\d+)/); + let nodeMajorVersion = 0; + if (nodeVersionMatch && nodeVersionMatch[1]) { + nodeMajorVersion = Number.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' }); + + await this.UserVisit.sync({ force: true }); + }); + + it('emits raw errors if requested', async function () { + const sql = 'SELECT 1 FROM NotFoundTable'; + + await expect( + this.sequelize.query(sql, { rawErrors: false }), + ).to.eventually.be.rejectedWith(DatabaseError); + + await expect( + this.sequelize.query(sql, { rawErrors: true }), + ).to.eventually.be.rejected.and.not.be.an.instanceOf(DatabaseError); + }); + + 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 (error_) { + error = error_; + } + + expect(error).to.be.instanceOf(DatabaseError); + expect(error.stack).to.contain('query.test'); + }); + + it('emits full stacktraces for unique constraint error', async function () { + let query; + if (['db2', 'ibmi'].includes(dialectName)) { + 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')`; + } else { + 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')`; + } + + let error = null; + try { + // Insert 1 row + await this.sequelize.query(query); + // Try inserting a duplicate row + await this.sequelize.query(query); + } catch (error_) { + error = error_; + } + + 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 { + let query; + if (['db2', 'ibmi'].includes(dialectName)) { + 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')`; + } else { + 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')`; + } + + await this.sequelize.query(query); + } catch (error_) { + error = error_; + } + + expect(error).to.be.instanceOf(ForeignKeyConstraintError); + expect(error.stack).to.contain('query.test'); + }); + }); + } + + describe('rejections', () => { + it('reject if the query is not a string', async function () { + // this is a legacy, removed signature + await this.sequelize + .query( + { query: 'select ? as foo, ? as bar', values: [1, 2] }, + { raw: true, replacements: [1, 2] }, + ) + .should.be.rejectedWith( + Error, + '"sql" cannot be an object. Pass a string instead, and pass bind and replacement parameters through the "options" parameter', + ); + }); + + 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 replacement ":\w+" has no entry in the replacement map\./g, + ); + }); + + it('rejects if replacements is a number', async function () { + await this.sequelize + .query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) + .should.be.rejectedWith( + Error, + '"replacements" must be an array or a plain object, but received 2 instead.', + ); + }); + + it('rejects if a replacement is missing', async function () { + await this.sequelize + .query('select :one as foo, :two as bar', { raw: true, replacements: {} }) + .should.be.rejectedWith( + Error, + /Named replacement ":\w+" has no entry in the replacement map\./g, + ); + }); + + it('rejects if replacements is a string', async function () { + await this.sequelize + .query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) + .should.be.rejectedWith( + Error, + '"replacements" must be an array or a plain object, but received "foobar" instead.', + ); + }); + + it('reject if replacements is not a plain object', async function () { + await this.sequelize + .query('select :one as foo, :two as bar', { + raw: true, + replacements: new URL('http://example.com'), + }) + .should.be.rejectedWith( + Error, + '"replacements" must be an array or a plain object, but received "http://example.com/" instead.', + ); + }); + + it('reject when binds passed with object and numeric $1 is also present', async function () { + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g, + ); + }); + + it('rejects when binds passed as array and a named parameter is also present', async function () { + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g, + ); + }); + + it('reject when bind key is $0 and bind is an 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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g, + ); + }); + + it('reject when bind key is $01 and bind is an 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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./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, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g, + ); + }); + + it('rejects if options.bind is a number', async function () { + await this.sequelize + .query('select $one as foo, $two as bar', { raw: true, bind: 2 }) + .should.be.rejectedWith( + Error, + 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)', + ); + }); + + it('rejects if a bind parameter is not present in options.bind', async function () { + await this.sequelize + .query('select $one as foo, $two as bar', { raw: true, bind: {} }) + .should.be.rejectedWith( + Error, + /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g, + ); + }); + + it('rejects if options.bind is a string', async function () { + await this.sequelize + .query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) + .should.be.rejectedWith( + Error, + 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)', + ); + }); + + it('rejects if options.bind is a non-pojo object', async function () { + await this.sequelize + .query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) + .should.be.rejectedWith( + Error, + 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)', + ); + }); + }); + + // dialects in which the following values will be returned as bigints instead of ints + const isBigInt = dialectName === 'mysql'; + it('dot separated attributes when doing a raw query without nest', async function () { + const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`; + + const results = await this.sequelize.query(sql, { raw: true, nest: false }); + expect(results[0]).to.deep.equal([{ 'foo.bar.baz': isBigInt ? '1' : 1 }]); + }); + + it('destructs dot separated attributes when doing a raw query using nest', async function () { + const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`; + + const result = await this.sequelize.query(sql, { raw: true, nest: true }); + expect(result).to.deep.equal([{ foo: { bar: { baz: isBigInt ? '1' : 1 } } }]); + }); + + it('replaces token with the passed array', async function () { + const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }]; + const result = await this.sequelize.query( + `select ? as ${queryGenerator.quoteIdentifier('foo')}, ? as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }, + ); + expect(result).to.deep.equal(expected); + }); + + it('replaces named parameters with the passed object', async function () { + const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }]; + await expect( + this.sequelize + .query( + `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, replacements: { one: 1, two: 2 } }, + ) + .then(obj => obj[0]), + ).to.eventually.deep.equal(expected); + }); + + it('replaces named parameters with the passed object and ignore those which does not qualify', async function () { + const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: '00:00' }]; + await expect( + this.sequelize + .query( + `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, '00:00' as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, replacements: { one: 1, two: 2 } }, + ) + .then(obj => obj[0]), + ).to.eventually.deep.equal(expected); + }); + + it('replaces named parameters with the passed object using the same key twice', async function () { + const expected = [ + { foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: isBigInt ? '1' : 1 }, + ]; + await expect( + this.sequelize + .query( + `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, :one as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, replacements: { one: 1, two: 2 } }, + ) + .then(obj => obj[0]), + ).to.eventually.deep.equal(expected); + }); + + it('replaces named parameters with the passed object having a null property', async function () { + const expected = [{ foo: isBigInt ? '1' : 1, bar: null }]; + await expect( + this.sequelize + .query( + `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, replacements: { one: 1, two: null } }, + ) + .then(obj => obj[0]), + ).to.eventually.deep.equal(expected); + }); + + // IBM i cannot bind parameter markers for selecting values like in theses + // tests + if (dialectName !== 'ibmi') { + it('binds token with the passed array', async function () { + const expected = [{ foo: 1, bar: 2 }]; + + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + let logSql; + const result = await this.sequelize.query( + `select $1${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $2${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { + type: this.sequelize.QueryTypes.SELECT, + bind: [1, 2], + logging(s) { + logSql = s; + }, + }, + ); + expect(result).to.deep.equal(expected); + if (['postgres', 'sqlite3'].includes(dialectName)) { + expect(logSql).to.include('$1'); + } + }); + + it('binds named parameters with the passed object', async function () { + const expected = [{ foo: 1, bar: 2 }]; + + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + let logSql; + const result = await this.sequelize.query( + `select $one${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $two${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { + raw: true, + bind: { one: 1, two: 2 }, + logging(s) { + logSql = s; + }, + }, + ); + expect(result[0]).to.deep.equal(expected); + if (dialectName === 'postgres') { + expect(logSql).to.include('$1'); + } + + if (dialectName === 'sqlite3') { + expect(logSql).to.include('$one'); + } + }); + + if (dialectName !== 'db2') { + it('binds named parameters with the passed object using the same key twice', async function () { + const typeCast = dialectName === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query( + `select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { + raw: true, + bind: { one: 1, two: 2 }, + logging(s) { + logSql = s; + }, + }, + ); + if (dialectName === 'ibmi') { + expect(result[0]).to.deep.equal([{ FOO: 1, BAR: 2, BAZ: 1 }]); + } else { + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + } + + if (dialectName === '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 = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + const result = await this.sequelize.query( + `select $one${typeCast} as foo, $two${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, bind: { one: 1, two: null } }, + ); + const expected = ['db2', 'ibmi'].includes(dialectName) + ? [{ FOO: 1, BAR: null }] + : [{ foo: 1, bar: null }]; + expect(result[0]).to.deep.equal(expected); + }); + + // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed. + it('does not transform $$ in strings (positional)', async function () { + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + let logSql; + const result = await this.sequelize.query( + `select $1${typeCast} as foo, '$$ / $$1' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { + raw: true, + bind: [1], + logging(s) { + logSql = s; + }, + }, + ); + const expected = ['db2', 'ibmi'].includes(dialectName) + ? [{ FOO: 1, BAR: '$$ / $$1' }] + : [{ foo: 1, bar: '$$ / $$1' }]; + expect(result[0]).to.deep.equal(expected); + if (['postgres', 'sqlite3', 'db2', 'ibmi'].includes(dialectName)) { + expect(logSql).to.include('$1'); + } + }); + + // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed. + it('does not transform $$ in strings (named)', async function () { + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + const result = await this.sequelize.query( + `select $one${typeCast} as foo, '$$ / $$one' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, bind: { one: 1 } }, + ); + const expected = ['db2', 'ibmi'].includes(dialectName) + ? [{ FOO: 1, BAR: '$$ / $$one' }] + : [{ foo: 1, bar: '$$ / $$one' }]; + expect(result[0]).to.deep.equal(expected); + }); + + it(`does not treat a $ as a bind param if it's in the middle of an identifier`, async function () { + const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : ''; + const result = await this.sequelize.query( + `select $one${typeCast} as foo$bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + { raw: true, bind: { one: 1 } }, + ); + const expected = ['db2', 'ibmi'].includes(dialectName) + ? [{ FOO$BAR: 1 }] + : [{ foo$bar: 1 }]; + expect(result[0]).to.deep.equal(expected); + }); + } + + if (['postgres', 'sqlite3', 'mssql'].includes(dialectName)) { + 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: sql.list(['"string"']) }, + }); + expect(result[0]).to.deep.equal([{ foo: '"string"' }]); + }); + } + + it('handles AS in conjunction with functions just fine', async function () { + let datetime = dialectName === 'sqlite3' ? "date('now')" : 'NOW()'; + if (dialectName === 'mssql') { + datetime = 'GETDATE()'; + } + + const [result] = await this.sequelize.query( + `SELECT ${datetime} AS t${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`, + ); + expect(dayjs(result[0].t).isValid()).to.be.true; + }); + + if (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/packages/core/test/integration/sequelize/set-session-variables.test.ts b/packages/core/test/integration/sequelize/set-session-variables.test.ts new file mode 100644 index 000000000000..ea2e4f5a7d22 --- /dev/null +++ b/packages/core/test/integration/sequelize/set-session-variables.test.ts @@ -0,0 +1,83 @@ +import { QueryTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { + createSingleTransactionalTestSequelizeInstance, + getTestDialect, + sequelize, + setResetMode, +} from '../support'; + +const dialectName = getTestDialect(); + +describe('sequelize.setSessionVariables', () => { + if (!['mysql', 'mariadb'].includes(dialectName)) { + return; + } + + setResetMode('none'); + + it(`rejects if no connection or transaction is provided`, async () => { + await expect(sequelize.setSessionVariables({ foo: 'bar' })).to.be.rejectedWith( + Error, + 'specify either options.transaction or options.connection', + ); + }); + + it('supports CLS transactions', async () => { + const clsSequelize = await createSingleTransactionalTestSequelizeInstance(sequelize, { + disableClsTransactions: false, + }); + + await clsSequelize.transaction(async () => { + await clsSequelize.setSessionVariables({ foo: 'bar' }); + const [data] = await clsSequelize.query<{ foo: string }>('SELECT @foo as `foo`', { + type: QueryTypes.SELECT, + }); + expect(data).to.be.ok; + expect(data.foo).to.equal('bar'); + }); + }); + + it('supports manual transactions', async () => { + const transaction = await sequelize.startUnmanagedTransaction(); + + try { + await sequelize.setSessionVariables({ foo: 'bar' }, { transaction }); + const [data] = await sequelize.query<{ foo: string }>('SELECT @foo as `foo`', { + type: QueryTypes.SELECT, + transaction, + }); + expect(data).to.be.ok; + expect(data.foo).to.equal('bar'); + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }); + + it('supports connections', async () => { + await sequelize.withConnection(async connection => { + await sequelize.setSessionVariables({ foo: 'bar' }, { connection }); + const [data] = await sequelize.query<{ foo: string }>('SELECT @foo as `foo`', { + type: QueryTypes.SELECT, + connection, + }); + expect(data).to.be.ok; + expect(data.foo).to.equal('bar'); + }); + }); + + it('supports setting multiple values', async () => { + await sequelize.withConnection(async connection => { + await sequelize.setSessionVariables({ foo: 'bar', foos: 'bars' }, { connection }); + const [data] = await sequelize.query<{ foo: string; foos: string }>( + 'SELECT @foo as `foo`, @foos as `foos`', + { type: QueryTypes.SELECT, connection }, + ); + expect(data).to.be.ok; + expect(data.foo).to.equal('bar'); + expect(data.foos).to.equal('bars'); + }); + }); +}); diff --git a/packages/core/test/integration/sequelize/transaction.test.ts b/packages/core/test/integration/sequelize/transaction.test.ts new file mode 100644 index 000000000000..e1e52400851c --- /dev/null +++ b/packages/core/test/integration/sequelize/transaction.test.ts @@ -0,0 +1,669 @@ +import type { InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { + ConstraintChecking, + DataTypes, + IsolationLevel, + Model, + Transaction, + TransactionNestMode, + TransactionType, +} from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { assert, expect } from 'chai'; +import delay from 'delay'; +import type { SinonStub } from 'sinon'; +import sinon from 'sinon'; +import { + beforeAll2, + createMultiTransactionalTestSequelizeInstance, + createSingleTransactionalTestSequelizeInstance, + getTestDialect, + getTestDialectTeaser, + sequelize, + setResetMode, +} from '../support'; + +const dialectName = sequelize.dialect.name; + +describe(getTestDialectTeaser('Sequelize#transaction'), () => { + if (!sequelize.dialect.supports.transactions) { + return; + } + + let stubs: SinonStub[] = []; + + afterEach(() => { + for (const stub of stubs) { + stub.restore(); + } + + stubs = []; + }); + + describe('nested managed transactions', () => { + it('reuses the parent transaction by default', async () => { + await sequelize.transaction(async transaction1 => { + await sequelize.transaction({ transaction: transaction1 }, async transaction2 => { + expect(transaction1 === transaction2).to.equal( + true, + 'transaction1 and transaction2 should be the same', + ); + }); + }); + }); + + it('requires compatible options if nestMode is set to "reuse"', async () => { + await sequelize.transaction(async transaction1 => { + if (sequelize.dialect.supports.startTransaction.transactionType) { + await expect( + sequelize.transaction( + { transaction: transaction1, type: TransactionType.EXCLUSIVE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested transaction type (EXCLUSIVE) is not compatible with the one of the existing transaction (DEFERRED)', + ); + } else { + await expect( + sequelize.transaction( + { transaction: transaction1, type: TransactionType.EXCLUSIVE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + `The ${sequelize.dialect.name} dialect does not support transaction types.`, + ); + } + + await expect( + sequelize.transaction( + { transaction: transaction1, isolationLevel: IsolationLevel.READ_UNCOMMITTED }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested isolation level (READ UNCOMMITTED) is not compatible with the one of the existing transaction (unspecified)', + ); + + await expect( + sequelize.transaction( + { transaction: transaction1, constraintChecking: ConstraintChecking.IMMEDIATE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested transaction constraintChecking (IMMEDIATE) is not compatible with the one of the existing transaction (none)', + ); + + await expect( + sequelize.transaction({ transaction: transaction1, readOnly: true }, async () => { + /* noop */ + }), + ).to.be.rejectedWith( + 'Requested a transaction in read-only mode, which is not compatible with the existing read/write transaction', + ); + }); + }); + + it('creates a savepoint if nestMode is set to "savepoint"', async () => { + await sequelize.transaction(async transaction1 => { + await sequelize.transaction( + { transaction: transaction1, nestMode: TransactionNestMode.savepoint }, + async transaction2 => { + expect(transaction1 === transaction2).to.equal( + false, + 'transaction1 and transaction2 should not be the same', + ); + expect(transaction2.parent === transaction1).to.equal( + true, + 'transaction2.parent should be transaction1', + ); + }, + ); + }); + }); + + it('requires compatible options if nestMode is set to "savepoint"', async () => { + await sequelize.transaction(async transaction1 => { + const commonOptions = { + transaction: transaction1, + nestMode: TransactionNestMode.savepoint, + }; + + if (sequelize.dialect.supports.startTransaction.transactionType) { + await expect( + sequelize.transaction( + { ...commonOptions, type: TransactionType.EXCLUSIVE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested transaction type (EXCLUSIVE) is not compatible with the one of the existing transaction (DEFERRED)', + ); + } else { + await expect( + sequelize.transaction( + { ...commonOptions, type: TransactionType.EXCLUSIVE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + `The ${sequelize.dialect.name} dialect does not support transaction types.`, + ); + } + + await expect( + sequelize.transaction( + { ...commonOptions, isolationLevel: IsolationLevel.READ_UNCOMMITTED }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested isolation level (READ UNCOMMITTED) is not compatible with the one of the existing transaction (unspecified)', + ); + + await expect( + sequelize.transaction( + { ...commonOptions, constraintChecking: ConstraintChecking.IMMEDIATE }, + async () => { + /* noop */ + }, + ), + ).to.be.rejectedWith( + 'Requested transaction constraintChecking (IMMEDIATE) is not compatible with the one of the existing transaction (none)', + ); + + await expect( + sequelize.transaction({ ...commonOptions, readOnly: true }, async () => { + /* noop */ + }), + ).to.be.rejectedWith( + 'Requested a transaction in read-only mode, which is not compatible with the existing read/write transaction', + ); + }); + }); + + // sqlite cannot have more than one transaction at the same time, so separate is not available. + if (dialectName !== 'sqlite3') { + it('creates a new transaction if nestMode is set to "separate"', async () => { + await sequelize.transaction(async transaction1 => { + await sequelize.transaction( + { transaction: transaction1, nestMode: TransactionNestMode.separate }, + async transaction2 => { + expect(transaction1 === transaction2).to.equal( + false, + 'transaction1 and transaction2 should not be the same', + ); + expect(transaction1.parent === null).to.equal( + true, + 'transaction1.parent should be null', + ); + expect(transaction2.parent === null).to.equal( + true, + 'transaction2.parent should be null', + ); + }, + ); + }); + }); + + it('does not care about option compatibility when nestMode is set to "separate"', async () => { + await sequelize.transaction(async transaction1 => { + await sequelize.transaction( + { + transaction: transaction1, + nestMode: TransactionNestMode.separate, + type: sequelize.dialect.supports.startTransaction.transactionType + ? TransactionType.EXCLUSIVE + : undefined, + isolationLevel: IsolationLevel.READ_UNCOMMITTED, + constraintChecking: sequelize.dialect.supports.constraints.deferrable + ? ConstraintChecking.DEFERRED + : undefined, + readOnly: true, + }, + async () => { + /* noop */ + }, + ); + }); + }); + } + + it(`defaults nestMode to sequelize's defaultTransactionNestMode option`, async () => { + const customSequelize = await createSingleTransactionalTestSequelizeInstance(sequelize, { + defaultTransactionNestMode: TransactionNestMode.savepoint, + }); + + await customSequelize.transaction(async transaction1 => { + await customSequelize.transaction({ transaction: transaction1 }, async transaction2 => { + expect(transaction1 === transaction2).to.equal( + false, + 'transaction1 and transaction2 should not be the same', + ); + expect(transaction2.parent === transaction1).to.equal( + true, + 'transaction2.parent should be transaction1', + ); + }); + }); + }); + }); + + describe('Isolation Levels', () => { + setResetMode('truncate'); + const vars = beforeAll2(async () => { + const transactionSequelize = await createMultiTransactionalTestSequelizeInstance(sequelize); + + class User extends Model, InferCreationAttributes> { + @Attribute(DataTypes.STRING) + @NotNull + declare name: string; + + @Attribute(DataTypes.INTEGER) + @NotNull + declare age: number; + } + + transactionSequelize.addModels([User]); + + await transactionSequelize.sync({ force: true }); + + return { transactionSequelize, User }; + }); + + after(async () => { + return vars.transactionSequelize.close(); + }); + + beforeEach(async () => { + await vars.User.create({ name: 'John Doe', age: 21 }); + }); + + if (sequelize.dialect.supports.settingIsolationLevelDuringTransaction) { + it('should allow setting the isolation level during a transaction', async () => { + const { User, transactionSequelize } = vars; + + await transactionSequelize.transaction(async transaction => { + await transaction.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED); + await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction }); + }); + + if (dialectName !== 'sqlite3') { + await transactionSequelize.transaction(async transaction => { + await transaction.setIsolationLevel(IsolationLevel.READ_COMMITTED); + const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.age).to.equal(22); + }); + + await transactionSequelize.transaction(async transaction => { + await transaction.setIsolationLevel(IsolationLevel.REPEATABLE_READ); + const users = await User.findAll({ transaction }); + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('John Doe'); + expect(users[0].age).to.equal(22); + }); + } + + await transactionSequelize.transaction(async transaction => { + await transaction.setIsolationLevel(IsolationLevel.SERIALIZABLE); + await User.create({ name: 'Jane Doe', age: 21 }, { transaction }); + }); + }); + } + + // SQLite only supports read uncommitted and serializable. + if (dialectName !== 'sqlite3') { + it('should read the most recent committed rows when using the READ COMMITTED isolation level', async () => { + const { User, transactionSequelize } = vars; + + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.READ_COMMITTED }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(1); + await User.create({ name: 'Jane Doe', age: 21 }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(2); // We SHOULD see the created user inside the transaction + }, + ); + }); + } + + // These dialects do not allow dirty reads with isolation level "READ UNCOMMITTED". + if (!['postgres', 'sqlite3'].includes(dialectName)) { + it('should allow dirty read with isolation level "READ UNCOMMITTED"', async () => { + const { User, transactionSequelize } = vars; + const t1 = await transactionSequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.READ_UNCOMMITTED, + }); + + try { + await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction: t1 }); + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.READ_UNCOMMITTED }, + async transaction => { + const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.age).to.equal(22); + }, + ); + } finally { + await t1.rollback(); + const johnDoe = await User.findOne({ where: { name: 'John Doe' } }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.age).to.equal(21); + } + }); + } + + // SQLite only supports read uncommitted and serializable. + if (dialectName !== 'sqlite3') { + it('should prevent dirty read with isolation level "READ COMMITTED"', async () => { + const { User, transactionSequelize } = vars; + const t1 = await transactionSequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.READ_COMMITTED, + }); + + try { + await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction: t1 }); + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.READ_COMMITTED }, + async transaction => { + const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.age).to.equal(21); + }, + ); + } finally { + await t1.rollback(); + const johnDoe = await User.findOne({ where: { name: 'John Doe' } }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.age).to.equal(21); + } + }); + } + + // SQLite only supports read uncommitted and serializable. + if (dialectName !== 'sqlite3') { + it('should allow non-repeatable read with isolation level "READ COMMITTED"', async () => { + const { User, transactionSequelize } = vars; + const t1 = await transactionSequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.READ_COMMITTED, + }); + + try { + const johnDoe = await User.findOne({ where: { name: 'John Doe' }, transaction: t1 }); + assert(johnDoe, 'John Doe should exist'); + expect(johnDoe.name).to.equal('John Doe'); + expect(johnDoe.age).to.equal(21); + + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.READ_COMMITTED }, + async transaction => { + await User.update({ age: 22 }, { where: { name: 'John Doe' }, transaction }); + }, + ); + + const johnDoe1 = await User.findOne({ where: { name: 'John Doe' }, transaction: t1 }); + assert(johnDoe1, 'John Doe should exist'); + expect(johnDoe1.name).to.equal('John Doe'); + expect(johnDoe1.age).to.equal(22); + await t1.commit(); + } catch (error) { + await t1.rollback(); + throw error; + } + }); + } + + // These dialects do not allow phantom reads with isolation level "REPEATABLE READ" as they use snapshot rather than locking. + if (['mariadb', 'mysql', 'postgres'].includes(dialectName)) { + it('should not read newly committed rows when using the REPEATABLE READ isolation level', async () => { + const { User, transactionSequelize } = vars; + + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.REPEATABLE_READ }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(1); + + await User.create({ name: 'Jane Doe', age: 21 }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(1); // We SHOULD NOT see the created user inside the transaction + }, + ); + }); + // SQLite only supports read uncommitted and serializable. + } else if (dialectName !== 'sqlite3') { + it('should allow phantom read with isolation level "REPEATABLE READ"', async () => { + const { User, transactionSequelize } = vars; + const t1 = await transactionSequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.REPEATABLE_READ, + }); + + try { + const users = await User.findAll({ transaction: t1 }); + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('John Doe'); + expect(users[0].age).to.equal(21); + + await transactionSequelize.transaction( + { isolationLevel: IsolationLevel.REPEATABLE_READ }, + async transaction => { + await User.create({ name: 'Jane Doe', age: 21 }, { transaction }); + }, + ); + + const users2 = await User.findAll({ transaction: t1 }); + expect(users2.length).to.equal(2); + expect(users2[0].name).to.equal('John Doe'); + expect(users2[0].age).to.equal(21); + expect(users2[1].name).to.equal('Jane Doe'); + expect(users2[1].age).to.equal(21); + await t1.commit(); + } catch (error) { + await t1.rollback(); + throw error; + } + }); + } + + // PostgreSQL is excluded because it detects Serialization Failure on commit instead of acquiring locks on the read rows + if (!['postgres'].includes(dialectName)) { + it('should block updates after reading a row using SERIALIZABLE', async () => { + const { User, transactionSequelize } = vars; + const transactionSpy = sinon.spy(); + const transaction = await transactionSequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.SERIALIZABLE, + }); + + await User.findAll({ transaction }); + await Promise.all([ + // Update should not succeed before transaction has committed + User.update({ age: 25 }, { where: { name: 'John Doe' } }).then(() => { + expect(transactionSpy).to.have.been.called; + expect(transaction.finished).to.equal('commit'); + }), + + delay(4000) + .then(transactionSpy) + .then(async () => transaction.commit()), + ]); + }); + } + }); + + describe('Transaction#commit', () => { + it('returns a promise that resolves once the transaction has been committed', async () => { + const t = await sequelize.startUnmanagedTransaction(); + + await expect(t.commit()).to.eventually.equal(undefined); + }); + + // we cannot close a sqlite connection, but there also cannot be a network error with sqlite. + // so this test is not necessary for that dialect. + if (dialectName !== 'sqlite3') { + it('does not pollute the pool with broken connections if commit fails', async () => { + const initialPoolSize = sequelize.pool.size; + + stubs.push( + sinon + .stub(sequelize.queryInterface, '_commitTransaction') + .rejects(new Error('Oh no, an error!')), + ); + + const t = await sequelize.startUnmanagedTransaction(); + + await expect(t.commit()).to.be.rejectedWith('Oh no, an error!'); + + // connection should have been destroyed + expect(sequelize.pool.size).to.eq(Math.max(0, initialPoolSize - 1)); + }); + } + }); + + describe('Transaction#rollback', () => { + it('returns a promise that resolves once the transaction has been rolled back', async () => { + const t = await sequelize.startUnmanagedTransaction(); + + await expect(t.rollback()).to.eventually.equal(undefined); + }); + + // we cannot close a sqlite connection, but there also cannot be a network error with sqlite. + // so this test is not necessary for that dialect. + if (dialectName !== 'sqlite3') { + it('does not pollute the pool with broken connections if the rollback fails', async () => { + const initialPoolSize = sequelize.pool.size; + + stubs.push( + sinon + .stub(sequelize.queryInterface, '_rollbackTransaction') + .rejects(new Error('Oh no, an error!')), + ); + + const t = await sequelize.startUnmanagedTransaction(); + + await expect(t.rollback()).to.be.rejectedWith('Oh no, an error!'); + + // connection should have been destroyed + expect(sequelize.pool.size).to.eq(Math.max(0, initialPoolSize - 1)); + }); + } + }); + + if (getTestDialect() !== 'sqlite3' && getTestDialect() !== 'db2') { + it('works for long running transactions', async () => { + const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize); + + interface IUser extends Model, InferCreationAttributes> { + name: string | null; + } + + const User = sequelize2.define( + 'User', + { + name: DataTypes.STRING, + }, + { timestamps: false }, + ); + + await sequelize2.sync({ force: true }); + const t = await sequelize2.startUnmanagedTransaction(); + + let query: string; + switch (getTestDialect()) { + case 'postgres': + query = 'select pg_sleep(2);'; + break; + case 'sqlite3': + query = 'select sqlite3_sleep(2000);'; + break; + case 'mssql': + query = "WAITFOR DELAY '00:00:02';"; + break; + default: + query = 'select sleep(2);'; + break; + } + + await sequelize2.query(query, { transaction: t }); + await User.create({ name: 'foo' }); + await sequelize2.query(query, { transaction: t }); + await t.commit(); + const users = await 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', async () => { + const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize); + const Test = sequelize2.define('Test', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + name: { type: DataTypes.STRING }, + }); + + await sequelize2.sync({ force: true }); + const transaction = await sequelize2.startUnmanagedTransaction(); + 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: having tables with uniqueness constraints', () => { + it('triggers the error event for the second transactions', async () => { + const sequelize2 = await createSingleTransactionalTestSequelizeInstance(sequelize); + + const User = sequelize2.define( + 'User', + { + name: { type: DataTypes.STRING, unique: true }, + }, + { + timestamps: false, + }, + ); + + await User.sync({ force: true }); + + const t1 = await sequelize2.startUnmanagedTransaction(); + const t2 = await sequelize2.startUnmanagedTransaction(); + await User.create({ name: 'omnom' }, { transaction: t1 }); + + await Promise.all([ + (async () => { + try { + return await User.create({ name: 'omnom' }, { transaction: t2 }); + } catch (error) { + expect(error).to.be.ok; + + return t2.rollback(); + } + })(), + + delay(100).then(async () => { + return t1.commit(); + }), + ]); + }); + }); +}); diff --git a/packages/core/test/integration/sequelize/truncate.test.ts b/packages/core/test/integration/sequelize/truncate.test.ts new file mode 100644 index 000000000000..e2e7b97d10fd --- /dev/null +++ b/packages/core/test/integration/sequelize/truncate.test.ts @@ -0,0 +1,106 @@ +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, + Model, +} from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { beforeAll2, sequelize, setResetMode } from '../support'; + +interface IA extends Model, InferCreationAttributes> { + id: CreationOptional; + bId: number | null; +} + +interface IB extends Model, InferCreationAttributes> { + id: CreationOptional; + aId: number | null; +} + +describe('Sequelize#truncate', () => { + setResetMode('destroy'); + + const vars = beforeAll2(async () => { + const A = sequelize.define('A', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + bId: { type: DataTypes.INTEGER }, + }); + + const B = sequelize.define('B', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + aId: { type: DataTypes.INTEGER }, + }); + + // These models both have a foreign key that references the other model. + // Sequelize should be able to create them. + A.belongsTo(B, { foreignKey: { allowNull: true } }); + B.belongsTo(A, { foreignKey: { allowNull: false } }); + + await sequelize.sync(); + + return { A, B }; + }); + + if (sequelize.dialect.supports.truncate.cascade) { + it('supports truncating cyclic associations with { cascade: true }', async () => { + const { A, B } = vars; + + await sequelize.transaction(async transaction => { + const a = await A.create( + { + bId: null, + }, + { transaction }, + ); + + const b = await B.create( + { + aId: a.id, + }, + { transaction }, + ); + + a.bId = b.id; + await a.save({ transaction }); + }); + + // drop both tables + await sequelize.truncate({ cascade: true }); + + expect(await A.count()).to.eq(0); + expect(await B.count()).to.eq(0); + }); + } + + if (sequelize.dialect.supports.constraints.foreignKeyChecksDisableable) { + it('supports truncating cyclic associations with { withoutForeignKeyChecks: true }', async () => { + const { A, B } = vars; + + await sequelize.transaction(async transaction => { + const a = await A.create( + { + bId: null, + }, + { transaction }, + ); + + const b = await B.create( + { + aId: a.id, + }, + { transaction }, + ); + + a.bId = b.id; + await a.save({ transaction }); + }); + + // drop both tables + await sequelize.truncate({ withoutForeignKeyChecks: true }); + + expect(await A.count()).to.eq(0); + expect(await B.count()).to.eq(0); + }); + } +}); diff --git a/packages/core/test/integration/sequelize/with-connection.test.ts b/packages/core/test/integration/sequelize/with-connection.test.ts new file mode 100644 index 000000000000..34718aa8e728 --- /dev/null +++ b/packages/core/test/integration/sequelize/with-connection.test.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { createSingleTestSequelizeInstance } from '../support'; + +describe('sequelize.withConnection', () => { + it('reserves a connection, to ensure multiple queries run on the same connection', async () => { + const sequelize = createSingleTestSequelizeInstance(); + + await sequelize.withConnection(async () => { + expect(sequelize.pool.using).to.eq(1); + }); + + expect(sequelize.pool.available).to.eq(1); + }); + + it('has an option to kill the connection after using it', async () => { + const sequelize = createSingleTestSequelizeInstance(); + + await sequelize.withConnection({ destroyConnection: true }, async () => { + expect(sequelize.pool.using).to.eq(1); + }); + + expect(sequelize.pool.available).to.eq(0); + }); +}); diff --git a/packages/core/test/integration/support.ts b/packages/core/test/integration/support.ts new file mode 100644 index 000000000000..219169104f99 --- /dev/null +++ b/packages/core/test/integration/support.ts @@ -0,0 +1,356 @@ +import type { AbstractDialect, Options } from '@sequelize/core'; +import { QueryTypes, Sequelize } from '@sequelize/core'; +import type { AbstractQuery } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query.js'; +import type { SqliteDialect } from '@sequelize/sqlite3'; +import uniq from 'lodash/uniq'; +import fs from 'node:fs'; +import pTimeout from 'p-timeout'; +import { + createSequelizeInstance, + getSqliteDatabasePath, + getTestDialect, + rand, + resetSequelizeInstance, + sequelize, + setIsIntegrationTestSuite, +} from '../support'; + +setIsIntegrationTestSuite(true); + +// 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 CLEANUP_TIMEOUT = Number.parseInt(process.env.SEQ_TEST_CLEANUP_TIMEOUT ?? '', 10) || 10_000; + +const runningQueries = new Set(); + +before(async () => { + // Sometimes the SYSTOOLSPACE tablespace is not available when running tests on DB2. This creates it. + if (getTestDialect() === 'db2') { + const res = await sequelize.query<{ TBSPACE: string }>( + `SELECT TBSPACE FROM SYSCAT.TABLESPACES WHERE TBSPACE = 'SYSTOOLSPACE'`, + { + type: QueryTypes.SELECT, + }, + ); + + const tableExists = res[0]?.TBSPACE === 'SYSTOOLSPACE'; + + if (!tableExists) { + // needed by dropSchema function + await sequelize.query(` + CREATE TABLESPACE SYSTOOLSPACE IN IBMCATGROUP + MANAGED BY AUTOMATIC STORAGE USING STOGROUP IBMSTOGROUP + EXTENTSIZE 4; + `); + + await sequelize.query(` + CREATE USER TEMPORARY TABLESPACE SYSTOOLSTMPSPACE IN IBMCATGROUP + MANAGED BY AUTOMATIC STORAGE USING STOGROUP IBMSTOGROUP + EXTENTSIZE 4 + `); + } + } + + sequelize.hooks.addListener('beforeQuery', (options, query) => { + runningQueries.add(query); + }); + sequelize.hooks.addListener('afterQuery', (options, query) => { + runningQueries.delete(query); + }); +}); + +/** used to run reset on all used sequelize instances for a given suite */ +const allSequelizeInstances = new Set(); +const sequelizeInstanceSources = new WeakMap(); +Sequelize.hooks.addListener('afterInit', sequelizeInstance => { + allSequelizeInstances.add(sequelizeInstance); + sequelizeInstanceSources.set( + sequelizeInstance, + new Error('A Sequelize instance was created here').stack!, + ); +}); + +const singleTestInstances = new Set(); + +/** + * Creates a sequelize instance that will be disposed of after the current test. + * Can only be used within a test. For before/after hooks, use {@link createSequelizeInstance}. + * + * @param options + */ +export function createSingleTestSequelizeInstance< + Dialect extends AbstractDialect = AbstractDialect, +>(options?: Omit, 'dialect'>): Sequelize { + const instance = createSequelizeInstance(options); + destroySequelizeAfterTest(instance); + + return instance; +} + +export function destroySequelizeAfterTest(sequelizeInstance: Sequelize): void { + singleTestInstances.add(sequelizeInstance); +} + +/** + * Creates a Sequelize instance to use in transaction-related tests. + * You must dispose of this instance manually. + * + * If you're creating the instance within a test, consider using {@link createSingleTransactionalTestSequelizeInstance}. + * + * @param sequelizeOrOptions + * @param overrideOptions + */ +export async function createMultiTransactionalTestSequelizeInstance< + Dialect extends AbstractDialect = AbstractDialect, +>( + sequelizeOrOptions: Sequelize | Options, + overrideOptions?: Partial>, +): Promise { + const baseOptions = + sequelizeOrOptions instanceof Sequelize ? sequelizeOrOptions.rawOptions : sequelizeOrOptions; + + const dialect = getTestDialect(); + + if (dialect === 'sqlite3') { + const p = getSqliteDatabasePath(`transactional-${rand()}.sqlite`); + if (fs.existsSync(p)) { + fs.unlinkSync(p); + } + + const _sequelize = createSequelizeInstance({ + ...(baseOptions as Options), + storage: p, + // allow using multiple connections as we are connecting to a file + pool: { max: 5, idle: 30_000 }, + ...(overrideOptions as Options), + }); + + await _sequelize.sync({ force: true }); + + return _sequelize; + } + + return createSequelizeInstance({ + ...baseOptions, + ...overrideOptions, + }); +} + +/** + * Creates a sequelize instance to use in transaction-related tests. + * This instance will be disposed of after the current test. + * + * Can only be used within a test. For before/after hooks, use {@link createMultiTransactionalTestSequelizeInstance}. + * + * @param sequelizeOrOptions + * @param overrideOptions + */ +export async function createSingleTransactionalTestSequelizeInstance< + Dialect extends AbstractDialect = AbstractDialect, +>( + sequelizeOrOptions: Sequelize | Options, + overrideOptions?: Partial>, +): Promise { + const instance = await createMultiTransactionalTestSequelizeInstance( + sequelizeOrOptions, + overrideOptions, + ); + destroySequelizeAfterTest(instance); + + return instance; +} + +before('first database reset', async () => { + // Reset the DB a single time for the whole suite + await clearDatabase(); +}); + +// TODO: make "none" the default. +type ResetMode = 'none' | 'truncate' | 'destroy' | 'drop'; +let currentSuiteResetMode: ResetMode = 'drop'; + +/** + * Controls how the current test suite will reset the database between each test. + * Note that this does not affect how the database is reset between each suite, only between each test. + * + * @param mode The reset mode to use: + * - `drop`: All tables will be dropped and recreated (default). + * - `none`: The database will not be reset at all. + * - `truncate`: All tables will be truncated, but not dropped. + * - `destroy`: All rows of all tables will be deleted using DELETE FROM, and identity columns will be reset. + */ +export function setResetMode(mode: ResetMode) { + let previousMode: ResetMode | undefined; + before('setResetMode before', async () => { + previousMode = currentSuiteResetMode; + currentSuiteResetMode = mode; + }); + + after('setResetMode after', async () => { + currentSuiteResetMode = previousMode ?? 'drop'; + + // Reset the DB a single time for the whole suite + await clearDatabase(); + }); +} + +afterEach('database reset', async () => { + const sequelizeInstances = uniq([sequelize, ...allSequelizeInstances]); + + for (const sequelizeInstance of sequelizeInstances) { + if (sequelizeInstance.isClosed()) { + allSequelizeInstances.delete(sequelizeInstance); + continue; + } + + if (currentSuiteResetMode === 'none') { + continue; + } + + let hasValidCredentials; + try { + /* eslint-disable no-await-in-loop */ + await sequelizeInstance.authenticate(); + hasValidCredentials = true; + } catch { + hasValidCredentials = false; + } + + if (hasValidCredentials) { + /* eslint-disable no-await-in-loop */ + switch (currentSuiteResetMode) { + case 'drop': + await clearDatabase(sequelizeInstance); + // unregister all models + resetSequelizeInstance(sequelizeInstance); + break; + + case 'truncate': + await sequelizeInstance.truncate({ + ...sequelizeInstance.dialect.supports.truncate, + withoutForeignKeyChecks: + sequelizeInstance.dialect.supports.constraints.foreignKeyChecksDisableable, + }); + break; + + case 'destroy': + await sequelizeInstance.destroyAll({ force: true }); + break; + + default: + break; + /* eslint-enable no-await-in-loop */ + } + } + } + + if (sequelize.isClosed()) { + throw new Error('The main sequelize instance was closed. This is not allowed.'); + } + + await Promise.all( + [...singleTestInstances].map(async instance => { + allSequelizeInstances.delete(instance); + if (!instance.isClosed()) { + await instance.close(); + } + }), + ); + + singleTestInstances.clear(); + + if (allSequelizeInstances.size > 2) { + throw new Error(`There are more than two test-specific sequelize instance. This indicates that some sequelize instances were not closed. +Sequelize instances created in beforeEach/before must be closed in a corresponding afterEach/after block. +Sequelize instances created inside of a test must be closed after the test. + +The following methods can be used to mark a sequelize instance for automatic disposal: +- destroySequelizeAfterTest +- createSingleTransactionalTestSequelizeInstance +- createSingleTestSequelizeInstance +- sequelize.close() + +The sequelize instances were created in the following locations: +${[...allSequelizeInstances] + .map(instance => { + const source = sequelizeInstanceSources.get(instance); + + return source ? ` - ${source}` : ' - unknown'; + }) + .join('\n')} +`); + } +}); + +async function clearDatabaseInternal(customSequelize: Sequelize) { + const qi = customSequelize.queryInterface; + await qi.dropAllTables(); + resetSequelizeInstance(customSequelize); + + if (qi.dropAllEnums) { + await qi.dropAllEnums(); + } + + await dropTestSchemas(customSequelize); + await dropTestDatabases(customSequelize); +} + +export async function clearDatabase(customSequelize: Sequelize = sequelize) { + await pTimeout( + clearDatabaseInternal(customSequelize), + 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 } }, + ); +} + +afterEach('no running queries checker', () => { + if (runningQueries.size > 0) { + throw new Error( + `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${[ + ...runningQueries, + ] + .map((query: AbstractQuery) => ` ${query.uuid}: ${query.sql}`) + .join('\n')}`, + ); + } +}); + +export async function dropTestDatabases(customSequelize: Sequelize = sequelize) { + if (!customSequelize.dialect.supports.multiDatabases) { + return; + } + + const qi = customSequelize.queryInterface; + const databases = await qi.listDatabases({ + skip: [customSequelize.dialect.getDefaultSchema(), 'sequelize_test'], + }); + + if (getTestDialect() === 'db2') { + for (const db of databases) { + // DB2 can sometimes deadlock / timeout when deleting more than one schema at the same time. + // eslint-disable-next-line no-await-in-loop + await qi.dropDatabase(db.name); + } + } else { + await Promise.all(databases.map(async db => qi.dropDatabase(db.name))); + } +} + +export async function dropTestSchemas(customSequelize: Sequelize = sequelize) { + if (!customSequelize.dialect.supports.schemas) { + await customSequelize.drop({}); + + return; + } + + await customSequelize.queryInterface.dropAllSchemas({ + skip: [customSequelize.dialect.getDefaultSchema(), 'sequelize_test'], + }); +} + +export * from '../support'; diff --git a/packages/core/test/integration/timezone.test.js b/packages/core/test/integration/timezone.test.js new file mode 100644 index 000000000000..3d4e7f5c202a --- /dev/null +++ b/packages/core/test/integration/timezone.test.js @@ -0,0 +1,91 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('./support'); + +const dialectName = Support.getTestDialect(); +const dialect = Support.sequelize.dialect; +const queryGenerator = Support.sequelize.queryGenerator; + +describe(Support.getTestDialectTeaser('Timezone'), () => { + if (!dialect.supports.globalTimeZoneConfig) { + return; + } + + before(function () { + this.sequelizeWithTimezone = Support.createSequelizeInstance({ + timezone: '+07:00', + }); + this.sequelizeWithNamedTimezone = Support.createSequelizeInstance({ + timezone: 'America/New_York', + }); + }); + + after(function () { + this.sequelizeWithTimezone.close(); + this.sequelizeWithNamedTimezone.close(); + }); + + it('returns the same value for current timestamp', async function () { + const startQueryTime = Date.now(); + + const now = dialectName === 'mssql' ? 'GETDATE()' : 'now()'; + const query = `SELECT ${now} as ${queryGenerator.quoteIdentifier('now')}`; + + const [now1, now2, now3] = await Promise.all([ + this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), + this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }), + this.sequelizeWithNamedTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }), + ]); + + const elapsedQueryTime = Date.now() - startQueryTime + 1001; + expect(new Date(now1[0].now).getTime()).to.be.closeTo( + new Date(now2[0].now).getTime(), + elapsedQueryTime, + ); + expect(new Date(now1[0].now).getTime()).to.be.closeTo( + new Date(now3[0].now).getTime(), + elapsedQueryTime, + ); + }); + + if (['mysql', 'mariadb'].includes(dialectName)) { + it('handles existing timestamps', async function () { + const NormalUser = this.sequelize.define('user', {}); + const TimezonedUser = this.sequelizeWithTimezone.define('user', {}); + + 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', async function () { + const NormalUser = this.sequelize.define('user', {}); + const TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); + + 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/packages/core/test/integration/transaction.test.js b/packages/core/test/integration/transaction.test.js new file mode 100644 index 000000000000..625ab8ca541b --- /dev/null +++ b/packages/core/test/integration/transaction.test.js @@ -0,0 +1,1267 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('./support'); + +const dialect = Support.getTestDialect(); +const { + DataTypes, + IsolationLevel, + Op, + QueryTypes, + Transaction, + TransactionType, +} = require('@sequelize/core'); +const sinon = require('sinon'); + +const current = Support.sequelize; +const delay = require('delay'); +const pSettle = require('p-settle'); + +describe(Support.getTestDialectTeaser('Transaction'), () => { + if (!current.dialect.supports.transactions) { + return; + } + + beforeEach(function () { + this.sinon = sinon.createSandbox(); + }); + + afterEach(function () { + this.sinon.restore(); + }); + + describe('constructor', () => { + it('stores options', function () { + const transaction = new Transaction(this.sequelize); + expect(transaction.options).to.be.an.instanceOf(Object); + }); + + it('generates an identifier', function () { + const transaction = new Transaction(this.sequelize); + expect(transaction.id).to.exist; + }); + + it('should call dialect specific generateTransactionId method', function () { + const transaction = new Transaction(this.sequelize); + expect(transaction.id).to.exist; + if (dialect === 'mssql') { + expect(transaction.id).to.have.lengthOf(20); + } + }); + }); + + describe('commit', () => { + it('is a commit method available', () => { + expect(Transaction).to.respondTo('commit'); + }); + }); + + describe('rollback', () => { + it('is a rollback method available', () => { + expect(Transaction).to.respondTo('rollback'); + }); + }); + + describe('autoCallback', () => { + it('supports automatically committing', async function () { + await this.sequelize.transaction(async () => {}); + }); + + it('supports automatically rolling back with a thrown error', async function () { + let t; + + await expect( + this.sequelize.transaction(transaction => { + t = transaction; + throw new Error('Yolo'); + }), + ).to.eventually.be.rejected; + + expect(t.finished).to.equal('rollback'); + }); + + it('supports automatically rolling back with a rejection', async function () { + let t; + + await expect( + this.sequelize.transaction(async transaction => { + t = transaction; + throw new Error('Swag'); + }), + ).to.eventually.be.rejected; + + expect(t.finished).to.equal('rollback'); + }); + + it('runs afterCommit & afterTransaction hooks when a transaction is committed', async function () { + const afterCommit = sinon.spy(); + const afterTransaction = sinon.spy(); + const afterRollback = sinon.spy(); + let transaction; + + await this.sequelize.transaction(t => { + transaction = t; + transaction.afterCommit(afterCommit); + transaction.afterRollback(afterRollback); + transaction.afterTransaction(afterTransaction); + + return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); + }); + + expect(afterCommit).to.have.been.calledOnce; + expect(afterCommit).to.have.been.calledWith(transaction); + + expect(afterTransaction).to.have.been.calledOnce; + expect(afterTransaction).to.have.been.calledWith(transaction); + + expect(afterRollback).to.not.have.been.called; + }); + + it('runs afterRollback & afterTransaction hooks when a transaction is rolled back', async function () { + const afterCommit = sinon.spy(); + const afterTransaction = sinon.spy(); + const afterRollback = sinon.spy(); + let transaction; + + try { + await this.sequelize.transaction(t => { + transaction = t; + transaction.afterCommit(afterCommit); + transaction.afterRollback(afterRollback); + transaction.afterTransaction(afterTransaction); + + throw new Error('Rollback'); + }); + } catch { + /* ignore */ + } + + expect(afterRollback).to.have.been.calledOnce; + expect(afterRollback).to.have.been.calledWith(transaction); + + expect(afterTransaction).to.have.been.calledOnce; + expect(afterTransaction).to.have.been.calledWith(transaction); + + expect(afterCommit).to.not.have.been.called; + }); + + it('does not run hooks when a transaction is rolled back from database', async function () { + this.sinon + .stub(this.sequelize.queryInterface, '_commitTransaction') + .rejects(new Error('Oh no, an error!')); + const hook = sinon.spy(); + + await expect( + (async function () { + await this.sequelize.transaction(transaction => { + transaction.afterCommit(hook); + }); + })(), + ).to.eventually.be.rejected; + + expect(hook).to.not.have.been.called; + }); + + if (dialect === 'postgres') { + // 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 hook1 = sinon.spy(); + const hook2 = sinon.spy(); + + const Dots = this.sequelize.define('dots', { color: DataTypes.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 = IsolationLevel.SERIALIZABLE; + + let firstTransactionGotNearCommit = false; + let secondTransactionGotNearCommit = false; + + const firstTransaction = async () => { + await this.sequelize.transaction({ isolationLevel }, async t => { + t.afterCommit(hook1); + await Dots.update( + { color: 'red' }, + { + where: { color: 'green' }, + transaction: t, + }, + ); + await delay(1500); + firstTransactionGotNearCommit = true; + }); + }; + + const secondTransaction = async () => { + await delay(500); + await this.sequelize.transaction({ isolationLevel }, async t => { + t.afterCommit(hook2); + 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); + + expect(hook1).to.not.have.been.called; + expect(hook2).to.have.been.called; + }); + } + }); + + it('does not allow queries after commit', async function () { + const t = await this.sequelize.startUnmanagedTransaction(); + 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', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + 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', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + 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', async function () { + this.sinon + .stub(this.sequelize.dialect.connectionManager, 'connect') + .returns(new Promise(() => {})); + + const transaction = new Transaction(this.sequelize); + + 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', async function () { + await expect( + this.sequelize.startUnmanagedTransaction().then(async t => { + await Promise.all([ + expect(t.rollback()).to.eventually.be.fulfilled, + 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', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + 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', async function () { + const hook = sinon.spy(); + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.startUnmanagedTransaction(); + transaction = t; + transaction.afterCommit(hook); + await t.commit(); + expect(hook).to.have.been.calledOnce; + expect(hook).to.have.been.calledWith(t); + } catch (error) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw error; + } + + throw error; + } + })(), + ).to.eventually.be.fulfilled; + }); + + it('should not run hooks if a non-auto callback transaction is rolled back', async function () { + const hook = sinon.spy(); + + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + t.afterCommit(hook); + await t.rollback(); + expect(hook).to.not.have.been.called; + })(), + ).to.eventually.be.fulfilled; + }); + + it('should not run hooks if a non-auto callback transaction is rolled back in database', async function () { + const hook = sinon.spy(); + + this.sinon + .stub(this.sequelize.queryInterface, '_commitTransaction') + .rejects(new Error('Oh no, an error!')); + + await expect( + (async function () { + const t = await this.sequelize.startUnmanagedTransaction(); + t.afterCommit(hook); + await t.commit(); + })(), + ).to.eventually.be.rejected; + + expect(hook).to.not.have.been.called; + }); + + it('should throw an error if null is passed to afterCommit', async function () { + const hook = null; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.startUnmanagedTransaction(); + transaction = t; + transaction.afterCommit(hook); + + return await t.commit(); + } catch (error) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw error; + } + + throw error; + } + })(), + ).to.eventually.be.rejectedWith('"callback" must be a function'); + }); + + it('should throw an error if undefined is passed to afterCommit', async function () { + const hook = undefined; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.startUnmanagedTransaction(); + transaction = t; + transaction.afterCommit(hook); + + return await t.commit(); + } catch (error) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw error; + } + + throw error; + } + })(), + ).to.eventually.be.rejectedWith('"callback" must be a function'); + }); + + it('should throw an error if an object is passed to afterCommit', async function () { + const hook = {}; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.startUnmanagedTransaction(); + transaction = t; + transaction.afterCommit(hook); + + return await t.commit(); + } catch (error) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw error; + } + + throw error; + } + })(), + ).to.eventually.be.rejectedWith('"callback" must be a function'); + }); + + it('does not allow commits after rollback', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + await t.rollback(); + + return await t.commit(); + })(), + ).to.be.rejectedWith( + 'Transaction cannot be committed because it has been finished with state: rollback', + ); + }); + + it('does not allow rollbacks after commit', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + await t.commit(); + + return await t.rollback(); + })(), + ).to.be.rejectedWith( + 'Transaction cannot be rolled back because it has been finished with state: commit', + ); + }); + + it('does not allow rollbacks after rollback', async function () { + await expect( + (async () => { + const t = await this.sequelize.startUnmanagedTransaction(); + await t.rollback(); + + return await t.rollback(); + })(), + ).to.be.rejectedWith( + 'Transaction cannot be rolled back because it has been finished with state: rollback', + ); + }); + + it('works even if a transaction: null option is passed', async function () { + this.sinon.spy(this.sequelize, 'queryRaw'); + + const t = await this.sequelize.startUnmanagedTransaction({ + transaction: null, + }); + + await t.commit(); + if (this.sequelize.dialect.supports.connectionTransactionMethods) { + expect(this.sequelize.queryRaw.callCount).to.equal(0); + } else { + expect(this.sequelize.queryRaw.callCount).to.be.greaterThan(0); + for (let i = 0; i < this.sequelize.queryRaw.callCount; i++) { + expect(this.sequelize.queryRaw.getCall(i).args[1].transaction).to.equal(t); + } + } + }); + + it('works even if a transaction: undefined option is passed', async function () { + this.sinon.spy(this.sequelize, 'queryRaw'); + + const t = await this.sequelize.startUnmanagedTransaction({ + transaction: undefined, + }); + + await t.commit(); + if (this.sequelize.dialect.supports.connectionTransactionMethods) { + expect(this.sequelize.queryRaw.callCount).to.equal(0); + } else { + expect(this.sequelize.queryRaw.callCount).to.be.greaterThan(0); + for (let i = 0; i < this.sequelize.queryRaw.callCount; i++) { + expect(this.sequelize.queryRaw.getCall(i).args[1].transaction).to.equal(t); + } + } + }); + + if (['mysql', 'mariadb'].includes(dialect)) { + describe('deadlock handling', () => { + // Create the `Task` table and ensure it's initialized with 2 rows + const getAndInitializeTaskModel = async sequelize => { + const Task = sequelize.define('task', { + id: { + type: DataTypes.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: { [Op.eq]: from } }, + lock: transaction.LOCK.UPDATE, + transaction, + }); + + await delay(10); + + await Task.update( + { id: to }, + { + where: { id: { [Op.ne]: to } }, + lock: transaction.LOCK.UPDATE, + transaction, + }, + ); + } catch (error) { + console.log(error.message); + } + + await Task.create({ id: 2 }, { transaction }); + } catch (error) { + console.log(error.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 + // properly, it should only execute a query if we're actually inside a real transaction. If it does + // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by + // throwing an error. + // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. + await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); + + const count = await Task.count(); + // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. + expect(count).to.equal( + 2, + 'transactions were fully rolled-back, and no new rows were added', + ); + }); + + it('should release the connection for a deadlocked transaction (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.pool, 'release'); + 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.pool.release.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: DataTypes.STRING, + }); + await this.sequelize.sync({ force: true }); + await this.sequelize.transaction( + { isolationLevel: IsolationLevel.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 + }, + ); + }); + + // The following code is supposed to cause a deadlock in MariaDB & MySQL + // but starting with MariaDB 10.5.15, this does not happen anymore. + // See https://github.com/sequelize/sequelize/issues/14174 + it.skip('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.startUnmanagedTransaction(); + 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.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.READ_COMMITTED, + }); + const t2Jan = await User.findByPk(id, { transaction: t2 }); + + // Then, we want to see that an attempt to update that row from T2 will be queued until T1 commits. + // However, before commiting T1 we will also perform an update via T1 on the same rows. + // This should cause T2 to notice that it can't function anymore, so it detects a deadlock and automatically rolls itself back (and throws an error). + // Meanwhile, T1 should still be ok. + const executionOrder = []; + const [t2AttemptData, t1AttemptData] = await pSettle([ + (async () => { + try { + executionOrder.push('Begin attempt to update via T2'); + await t2Jan.update({ awesome: false }, { transaction: t2 }); + executionOrder.push('Done updating via T2'); // Shouldn't happen + } catch (error) { + executionOrder.push('Failed to update via T2'); + throw error; + } + + await delay(30); + + try { + // We shouldn't reach this point, but if we do, let's at least commit the transaction + // to avoid forever occupying one connection of the pool with a pending transaction. + executionOrder.push('Attempting to commit T2'); + await t2.commit(); + executionOrder.push('Done committing T2'); + } catch { + executionOrder.push('Failed to commit T2'); + } + })(), + (async () => { + await delay(100); + + try { + executionOrder.push('Begin attempt to update via T1'); + await t1Jan.update({ awesome: true }, { transaction: t1 }); + executionOrder.push('Done updating via T1'); + } catch (error) { + executionOrder.push('Failed to update via T1'); // Shouldn't happen + throw error; + } + + await delay(150); + + try { + executionOrder.push('Attempting to commit T1'); + await t1.commit(); + executionOrder.push('Done committing T1'); + } catch { + executionOrder.push('Failed to commit T1'); // Shouldn't happen + } + })(), + ]); + + expect(t1AttemptData.isFulfilled).to.eq( + true, + 'T1 is not fullfilled, but should have been', + ); + expect(t2AttemptData.isRejected).to.eq(true, 'T2 is not rejected, but should have been'); + 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 === 'sqlite3') { + it('provides persistent transactions', async () => { + const sequelize = await Support.createMultiTransactionalTestSequelizeInstance(); + Support.destroySequelizeAfterTest(sequelize); + const User = sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN, + }); + + const t1 = await sequelize.startUnmanagedTransaction(); + await sequelize.sync({ transaction: t1 }); + const t0 = t1; + await User.create({}, { transaction: t0 }); + await t0.commit(); + const persistentTransaction = await sequelize.startUnmanagedTransaction(); + const users = await User.findAll({ transaction: persistentTransaction }); + expect(users.length).to.equal(1); + + await persistentTransaction.commit(); + }); + } + + if (current.dialect.supports.startTransaction.transactionType) { + describe('transaction types', () => { + it('should support default transaction type DEFERRED', async function () { + const t = await this.sequelize.startUnmanagedTransaction({}); + + await t.rollback(); + expect(t.options.transactionType).to.equal('DEFERRED'); + }); + + for (const key of Object.keys(TransactionType)) { + it(`should allow specification of ${key} type`, async function () { + const t = await this.sequelize.startUnmanagedTransaction({ + type: key, + }); + + await t.rollback(); + expect(t.options.transactionType).to.equal(TransactionType[key]); + }); + } + }); + } + + if (dialect === 'sqlite3') { + it('automatically retries on SQLITE_BUSY failure', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { username: DataTypes.STRING }); + await User.sync({ force: true }); + const newTransactionFunc = async function () { + const t = await sequelize.startUnmanagedTransaction({ type: TransactionType.EXCLUSIVE }); + await User.create({}, { transaction: t }); + + return t.commit(); + }; + + await Promise.all([newTransactionFunc(), newTransactionFunc()]); + const users = await User.findAll(); + expect(users.length).to.equal(2); + }); + + it('fails with SQLITE_BUSY when retry.match is changed', async function () { + const sequelize = await Support.createSingleTransactionalTestSequelizeInstance( + this.sequelize, + ); + const User = sequelize.define('User', { + id: { type: DataTypes.INTEGER, primaryKey: true }, + username: DataTypes.STRING, + }); + await User.sync({ force: true }); + const newTransactionFunc = async function () { + const t = await sequelize.startUnmanagedTransaction({ + type: TransactionType.EXCLUSIVE, + retry: { match: ['NO_MATCH'] }, + }); + // introduce delay to force the busy state race condition to fail + await delay(2000); + 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', + ); + }); + } + + if (current.dialect.supports.lock) { + describe('row locking', () => { + it('supports for update', async function () { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN, + }); + const t1Spy = sinon.spy(); + const t2Spy = sinon.spy(); + + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.startUnmanagedTransaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan', + }, + lock: t1.LOCK.UPDATE, + transaction: t1, + }); + + const t2 = await this.sequelize.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.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', async function () { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN, + }); + + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ username: 'jan' }), User.create({ username: 'joe' })]); + + const t1 = await this.sequelize.startUnmanagedTransaction(); + + const results = await User.findAll({ + limit: 1, + lock: true, + transaction: t1, + }); + + const firstUserId = results[0].id; + const t2 = await this.sequelize.startUnmanagedTransaction(); + + 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', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { + title: DataTypes.STRING, + active: DataTypes.BOOLEAN, + }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const User = this.sequelize.define( + 'User', + { username: DataTypes.STRING }, + { tableName: 'Person' }, + ); + const Task = this.sequelize.define('Task', { + title: DataTypes.STRING, + active: DataTypes.BOOLEAN, + }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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', async function () { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN, + }); + const t1Spy = sinon.spy(); + const t2Spy = sinon.spy(); + + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.startUnmanagedTransaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan', + }, + lock: t1.LOCK.NO_KEY_UPDATE, + transaction: t1, + }); + + const t2 = await this.sequelize.startUnmanagedTransaction(); + + await Promise.all([ + (async () => { + await User.findOne({ + where: { + username: 'jan', + }, + lock: t2.LOCK.KEY_SHARE, + transaction: t2, + }); + + t2Spy(); + + return await t2.commit(); + })(), + (async () => { + await t1Jan.update( + { + awesome: true, + }, + { + transaction: t1, + }, + ); + + await delay(2000); + t1Spy(); + expect(t1Spy).to.have.been.calledAfter(t2Spy); + + return await t1.commit(); + })(), + ]); + }); + } + + 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.startUnmanagedTransaction(); + 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.startUnmanagedTransaction({ + isolationLevel: IsolationLevel.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/packages/core/test/integration/trigger.test.js b/packages/core/test/integration/trigger.test.js new file mode 100644 index 000000000000..127ae9d11aaa --- /dev/null +++ b/packages/core/test/integration/trigger.test.js @@ -0,0 +1,110 @@ +'use strict'; + +const chai = require('chai'); +const { DataTypes } = require('@sequelize/core'); + +const expect = chai.expect; +const Support = require('../support'); + +const dialect = Support.getTestDialect(); +const current = Support.sequelize; + +if (current.dialect.supports.tmpTableTrigger) { + describe(Support.getTestDialectTeaser('Model'), () => { + describe('trigger', () => { + let User; + let triggerQuery = + 'create trigger User_ChangeTracking on [users] for insert,update, delete \n' + + 'as\n' + + 'SET NOCOUNT ON\n' + + 'if exists(select 1 from inserted)\n' + + 'begin\n' + + 'select * from inserted\n' + + 'end\n' + + 'if exists(select 1 from deleted)\n' + + 'begin\n' + + 'select * from deleted\n' + + 'end\n'; + if (dialect === 'db2') { + triggerQuery = + 'CREATE OR REPLACE TRIGGER User_ChangeTracking\n' + + 'AFTER INSERT ON "users"\n' + + 'FOR EACH STATEMENT\n' + + 'BEGIN ATOMIC\n' + + ' SELECT * FROM "users";\n' + + 'END'; + } + + beforeEach(async function () { + User = this.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + }, + }, + { + hasTrigger: true, + }, + ); + + await User.sync({ force: true }); + + await this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); + }); + + it('should return output rows after insert', async () => { + await User.create({ + username: 'triggertest', + }); + + await expect(User.findOne({ username: 'triggertest' })) + .to.eventually.have.property('username') + .which.equals('triggertest'); + }); + + it('should return output rows after instance update', async () => { + const user = await User.create({ + username: 'triggertest', + }); + + 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', async () => { + const user = await User.create({ + username: 'triggertest', + }); + + 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', async () => { + const user = await User.create({ + username: 'triggertest', + }); + + await user.destroy(); + await expect(User.findOne({ username: 'triggertest' })).to.eventually.be.null; + }); + }); + }); +} diff --git a/packages/core/test/integration/utils.test.ts b/packages/core/test/integration/utils.test.ts new file mode 100644 index 000000000000..095d5505614b --- /dev/null +++ b/packages/core/test/integration/utils.test.ts @@ -0,0 +1,118 @@ +import { DataTypes, Op, cast, fn } from '@sequelize/core'; +import { expect } from 'chai'; +import { beforeAll2, getTestDialectTeaser, sequelize, setResetMode } from './support'; + +const dialectName = sequelize.dialect.name; + +describe(getTestDialectTeaser('fn()'), () => { + setResetMode('none'); + + const vars = beforeAll2(async () => { + const Airplane = sequelize.define('Airplane', { + wings: DataTypes.INTEGER, + engines: DataTypes.INTEGER, + }); + + await Airplane.sync({ force: true }); + + await Airplane.bulkCreate([ + { + wings: 2, + engines: 0, + }, + { + wings: 4, + engines: 1, + }, + { + wings: 2, + engines: 2, + }, + ]); + + return { Airplane }; + }); + + // some dialects return the result of arithmetic functions (SUM, COUNT) as integer & floats, others as bigints & decimals. + const arithmeticAsNumber = dialectName === 'sqlite3' || dialectName === 'db2'; + if (dialectName !== 'mssql' && dialectName !== 'ibmi') { + it('accepts condition object (with cast)', async () => { + const type = dialectName === 'mysql' ? 'unsigned' : 'int'; + + const [airplane] = await vars.Airplane.findAll({ + attributes: [ + [fn('COUNT', '*'), 'count'], + [ + fn( + 'SUM', + cast( + { + engines: 1, + }, + type, + ), + ), + 'count-engines', + ], + [ + fn( + 'SUM', + cast( + { + [Op.or]: { + engines: { + [Op.gt]: 1, + }, + wings: 4, + }, + }, + type, + ), + ), + 'count-engines-wings', + ], + ], + }); + + // These values are returned as strings + // See https://github.com/sequelize/sequelize/issues/10533#issuecomment-1254141892 for more details + expect(airplane.get('count')).to.equal(arithmeticAsNumber ? 3 : '3'); + expect(airplane.get('count-engines')).to.equal(arithmeticAsNumber ? 1 : '1'); + expect(airplane.get('count-engines-wings')).to.equal(arithmeticAsNumber ? 2 : '2'); + }); + } + + if (dialectName !== 'mssql' && dialectName !== 'postgres' && dialectName !== 'ibmi') { + it('accepts condition object (auto casting)', async () => { + const [airplane] = await vars.Airplane.findAll({ + attributes: [ + [fn('COUNT', '*'), 'count'], + [ + fn('SUM', { + engines: 1, + }), + 'count-engines', + ], + [ + fn('SUM', { + [Op.or]: { + engines: { + [Op.gt]: 1, + }, + wings: 4, + }, + }), + 'count-engines-wings', + ], + ], + }); + + // These values are returned as strings + // See https://github.com/sequelize/sequelize/issues/10533#issuecomment-1254141892 for more details + // Except for SQLite, which returns them as JS numbers, which the above issue will unify + expect(airplane.get('count')).to.equal(arithmeticAsNumber ? 3 : '3'); + expect(airplane.get('count-engines')).to.equal(arithmeticAsNumber ? 1 : '1'); + expect(airplane.get('count-engines-wings')).to.equal(arithmeticAsNumber ? 2 : '2'); + }); + } +}); diff --git a/packages/core/test/integration/vectors.test.js b/packages/core/test/integration/vectors.test.js new file mode 100644 index 000000000000..6c12bd43abcc --- /dev/null +++ b/packages/core/test/integration/vectors.test.js @@ -0,0 +1,33 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const { DataTypes } = require('@sequelize/core'); +const Support = require('./support'); + +chai.should(); + +describe(Support.getTestDialectTeaser('Vectors'), () => { + it('should not allow insert backslash', async function () { + const Student = this.sequelize.define( + 'student', + { + name: DataTypes.STRING, + }, + { + tableName: 'student', + }, + ); + + 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/packages/core/test/smoke/smoketests.test.js b/packages/core/test/smoke/smoketests.test.js new file mode 100644 index 000000000000..d8da1e668472 --- /dev/null +++ b/packages/core/test/smoke/smoketests.test.js @@ -0,0 +1,509 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../integration/support'); +const { DataTypes, sql } = require('@sequelize/core'); +const sinon = require('sinon'); + +const dialect = Support.getTestDialect(); + +describe(Support.getTestDialectTeaser('Smoke Tests'), () => { + describe('getAssociations', () => { + 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' }); + + 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('gets all associated objects with all fields', async function () { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + for (const attr of Object.keys(tasks[0].getAttributes())) { + expect(tasks[0]).to.have.property(attr); + } + }); + + it('supports non primary key attributes for joins (custom through model)', async function () { + const User = this.sequelize.define( + 'User', + { + id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: sql.uuidV4, + field: 'user_id', + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.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: sql.uuidV4, + field: 'group_id', + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: sql.uuidV4, + field: 'group_second_id', + }, + }, + { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'], + }, + ], + }, + ); + + const User_has_Group = this.sequelize.define( + 'User_has_Group', + {}, + { + tableName: 'tbl_user_has_group', + indexes: [ + { + unique: true, + fields: ['UserUserSecondId', 'GroupGroupSecondId'], + }, + ], + }, + ); + + User.belongsToMany(Group, { through: User_has_Group, sourceKey: 'userSecondId' }); + Group.belongsToMany(User, { through: User_has_Group, sourceKey: 'groupSecondId' }); + + 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.equal(2); + expect(users[0].Groups.length).to.equal(1); + expect(users[1].Groups.length).to.equal(1); + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.deep.equal( + users[0].userSecondId, + ); + } else { + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.equal(users[0].userSecondId); + } + + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.deep.equal( + users[0].Groups[0].groupSecondId, + ); + } else { + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.equal( + users[0].Groups[0].groupSecondId, + ); + } + + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.deep.equal( + users[1].userSecondId, + ); + } else { + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.equal(users[1].userSecondId); + } + + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.deep.equal( + users[1].Groups[0].groupSecondId, + ); + } else { + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.equal( + users[1].Groups[0].groupSecondId, + ); + } + + expect(groups.length).to.equal(2); + expect(groups[0].Users.length).to.equal(1); + expect(groups[1].Users.length).to.equal(1); + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.deep.equal( + groups[0].groupSecondId, + ); + } else { + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.equal( + groups[0].groupSecondId, + ); + } + + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.deep.equal( + groups[0].Users[0].userSecondId, + ); + } else { + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.equal( + groups[0].Users[0].userSecondId, + ); + } + + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.deep.equal( + groups[1].groupSecondId, + ); + } else { + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.equal( + groups[1].groupSecondId, + ); + } + + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + if (dialect === 'db2') { + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.deep.equal( + groups[1].Users[0].userSecondId, + ); + } else { + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.equal( + groups[1].Users[0].userSecondId, + ); + } + }); + }); + + describe('hasAssociations', () => { + beforeEach(function () { + this.Article = this.sequelize.define('Article', { + pk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + title: DataTypes.STRING, + }); + this.Label = this.sequelize.define('Label', { + sk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + text: DataTypes.STRING, + }); + this.ArticleLabel = this.sequelize.define('ArticleLabel'); + + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + return this.sequelize.sync({ force: true }); + }); + + it('answers 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' }), + ]); + + await article.addLabels([label1]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute], + ]); + + expect(result).to.be.false; + }); + }); + + describe('countAssociations', () => { + 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.UserTask = this.sequelize.define('UserTask', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + started: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + }); + + this.User.belongsToMany(this.Task, { through: this.UserTask }); + this.Task.belongsToMany(this.User, { through: this.UserTask }); + + 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 scoped through associations', async function () { + this.User.belongsToMany(this.Task, { + as: 'startedTasks', + through: { + model: this.UserTask, + scope: { + started: true, + }, + }, + }); + + 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('createAssociations', () => { + it('creates a new associated object', async function () { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + 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); + }); + }); + + describe('belongsTo and hasMany at once', () => { + beforeEach(function () { + this.A = this.sequelize.define('a', { name: DataTypes.STRING }); + this.B = this.sequelize.define('b', { name: DataTypes.STRING }); + }); + + describe('target belongs to source', () => { + beforeEach(function () { + this.B.belongsTo(this.A, { as: 'relation1' }); + this.A.belongsToMany(this.B, { as: 'relation2', through: 'AB' }); + this.B.belongsToMany(this.A, { as: 'relation2', through: 'AB' }); + + return this.sequelize.sync({ force: true }); + }); + + it('correctly uses bId in A', async function () { + const a1 = this.A.build({ name: 'a1' }); + const b1 = this.B.build({ name: 'b1' }); + + 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(Support.getTestDialectTeaser('Instance'), () => { + before(function () { + this.clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + this.clock.reset(); + }); + + after(function () { + this.clock.restore(); + }); + + beforeEach(async function () { + this.User = this.sequelize.define('User', { + username: { type: DataTypes.STRING }, + uuidv1: { type: DataTypes.UUID, defaultValue: sql.uuidV1 }, + uuidv4: { type: DataTypes.UUID, defaultValue: sql.uuidV4 }, + touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + aNumber: { type: DataTypes.INTEGER }, + bNumber: { type: DataTypes.INTEGER }, + aDate: { type: DataTypes.DATE }, + + validateTest: { + type: DataTypes.INTEGER, + allowNull: true, + validate: { isInt: true }, + }, + validateCustom: { + type: DataTypes.STRING, + allowNull: true, + validate: { len: { msg: 'Length failed.', args: [1, 20] } }, + }, + + dateAllowNullTrue: { + type: DataTypes.DATE, + allowNull: true, + }, + + isSuperUser: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + }); + + await this.User.sync({ force: true }); + }); + + describe('Escaping', () => { + 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 + + const u1 = await this.User.create({ username: bio }); + const u2 = await this.User.findByPk(u1.id); + expect(u2.username).to.equal(bio); + }); + }); + + describe('values', () => { + it('returns all values', async function () { + const User = this.sequelize.define( + 'UserHelper', + { + username: DataTypes.STRING, + }, + { timestamps: false, logging: false }, + ); + + await User.sync(); + const user = User.build({ username: 'foo' }); + expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); + }); + }); + + describe(Support.getTestDialectTeaser('Model'), () => { + before(function () { + this.clock = sinon.useFakeTimers(); + }); + + after(function () { + this.clock.restore(); + }); + + beforeEach(async function () { + this.User = this.sequelize.define('User', { + username: DataTypes.STRING, + secretValue: DataTypes.STRING, + data: DataTypes.STRING, + intVal: DataTypes.INTEGER, + theDate: DataTypes.DATE, + aBool: DataTypes.BOOLEAN, + }); + + await this.User.sync({ force: true }); + }); + + describe('save', () => { + it('should map the correct fields when saving instance (#10589)', async function () { + const User = this.sequelize.define('User', { + id3: { + field: 'id', + type: DataTypes.INTEGER, + primaryKey: true, + }, + id: { + field: 'id2', + type: DataTypes.INTEGER, + allowNull: false, + }, + id2: { + field: 'id3', + type: DataTypes.INTEGER, + allowNull: false, + }, + }); + + 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); + }); + }); + }); +}); diff --git a/packages/core/test/support.ts b/packages/core/test/support.ts new file mode 100644 index 000000000000..a2916e730bca --- /dev/null +++ b/packages/core/test/support.ts @@ -0,0 +1,642 @@ +import type { AbstractDialect, BoundQuery, DialectName, Options } from '@sequelize/core'; +import { Sequelize } from '@sequelize/core'; +import type { PostgresDialect } from '@sequelize/postgres'; +import { isNotString } from '@sequelize/utils'; +import { isNodeError } from '@sequelize/utils/node'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import chaiDatetime from 'chai-datetime'; +import defaults from 'lodash/defaults'; +import isObject from 'lodash/isObject'; +import type { ExclusiveTestFunction, PendingTestFunction, TestFunction } from 'mocha'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; +import { inspect, isDeepStrictEqual } from 'node:util'; +import sinonChai from 'sinon-chai'; +import type { Class } from 'type-fest'; +import { CONFIG, SQLITE_DATABASES_DIR } from './config/config'; + +export { getSqliteDatabasePath } from './config/config'; + +const expect = chai.expect; + +const packagesDir = path.resolve(__dirname, '..', '..'); + +const NON_DIALECT_PACKAGES = Object.freeze(['utils', 'validator-js', 'core']); + +chai.use(chaiDatetime); +chai.use(chaiAsPromised); +chai.use(sinonChai); + +/** + * `expect(fn).to.throwWithCause()` works like `expect(fn).to.throw()`, except + * that is also checks whether the message is present in the error cause. + */ +chai.Assertion.addMethod('throwWithCause', function throwWithCause(errorConstructor, errorMessage) { + // eslint-disable-next-line @typescript-eslint/no-invalid-this -- this is how chai works + expect(withInlineCause(this._obj)).to.throw(errorConstructor, errorMessage); +}); + +chai.Assertion.addMethod('beNullish', function nullish() { + // eslint-disable-next-line @typescript-eslint/no-invalid-this -- this is how chai works + expect(this._obj).to.not.exist; +}); + +chai.Assertion.addMethod('notBeNullish', function nullish() { + // eslint-disable-next-line @typescript-eslint/no-invalid-this -- this is how chai works + expect(this._obj).to.exist; +}); + +function withInlineCause(cb: () => any): () => void { + return () => { + try { + return cb(); + } catch (error) { + assert(error instanceof Error); + + error.message = inlineErrorCause(error); + + throw error; + } + }; +} + +export function inlineErrorCause(error: unknown): string { + if (!(error instanceof Error)) { + return String(error); + } + + let message = error.message; + + const cause = error.cause; + if (cause instanceof Error) { + message += `\nCaused by: ${inlineErrorCause(cause)}`; + } + + return message; +} + +chai.config.includeStack = true; +chai.should(); + +// Make sure errors get thrown when testing +process.on('uncaughtException', e => { + console.error('An unhandled exception occurred:'); + throw e; +}); + +let onNextUnhandledRejection: ((error: unknown) => any) | null = null; +let unhandledRejections: unknown[] | null = 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; +}); + +// 'support' is requested by dev/check-connection, which is not a mocha context +if (typeof afterEach !== 'undefined') { + afterEach(() => { + onNextUnhandledRejection = null; + unhandledRejections = null; + }); +} + +/** + * Returns a Promise that will reject with the next unhandled rejection that occurs + * during this test (instead of failing the test) + */ +export async function nextUnhandledRejection() { + return new Promise((resolve, reject) => { + onNextUnhandledRejection = reject; + }); +} + +export function createSequelizeInstance( + options?: Omit, 'dialect'>, +): Sequelize { + const dialectName = getTestDialect(); + const config = CONFIG[dialectName]; + + const sequelizeOptions = defaults(options, config, { + // the test suite was written before CLS was turned on by default. + disableClsTransactions: true, + } as const); + + if (dialectName === 'postgres') { + const sequelizePostgresOptions: Options = { + ...(sequelizeOptions as Options), + native: process.env.DIALECT === 'postgres-native', + }; + + return new Sequelize(sequelizePostgresOptions) as unknown as Sequelize; + } + + return new Sequelize(sequelizeOptions as Options); +} + +export function getSupportedDialects() { + return fs.readdirSync(packagesDir).filter(file => !NON_DIALECT_PACKAGES.includes(file)); +} + +export function getTestDialectClass(): Class { + const dialectClass = CONFIG[getTestDialect()].dialect; + + isNotString.assert(dialectClass); + + return dialectClass; +} + +export function getTestDialect(): DialectName { + let envDialect = process.env.DIALECT || ''; + + if (envDialect === 'postgres-native') { + envDialect = 'postgres'; + } + + if (!getSupportedDialects().includes(envDialect)) { + throw new Error( + `The DIALECT environment variable was set to ${JSON.stringify(envDialect)}, which is not a supported dialect. Set it to one of ${getSupportedDialects() + .map(d => JSON.stringify(d)) + .join(', ')} instead.`, + ); + } + + return envDialect as DialectName; +} + +export function getTestDialectTeaser(moduleName: string): string { + let dialect: string = getTestDialect(); + + if (process.env.DIALECT === 'postgres-native') { + dialect = 'postgres-native'; + } + + return `[${dialect.toUpperCase()}] ${moduleName}`; +} + +export function getPoolMax(): number { + return CONFIG[getTestDialect()].pool?.max ?? 1; +} + +type ExpectationKey = 'default' | Permutations; + +export type ExpectationRecord = PartialRecord | Error>; + +type DecrementedDepth = [never, 0, 1, 2, 3]; + +type Permutations = Depth extends 0 + ? never + : T extends any + ? T | `${T} ${Permutations, DecrementedDepth[Depth]>}` + : never; + +type PartialRecord = Partial>; + +export function expectPerDialect(method: () => Out, assertions: ExpectationRecord) { + const expectations: PartialRecord<'default' | DialectName, Out | Error | Expectation> = + Object.create(null); + + for (const [key, value] of Object.entries(assertions)) { + const acceptedDialects = key.split(' ') as Array; + + for (const dialect of acceptedDialects) { + if (dialect === 'default' && acceptedDialects.length > 1) { + throw new Error(`The 'default' expectation cannot be combined with other dialects.`); + } + + if (expectations[dialect] !== undefined) { + throw new Error(`The expectation for ${dialect} was already defined.`); + } + + expectations[dialect] = value; + } + } + + let result: Out | Error; + + try { + result = method(); + } catch (error: unknown) { + assert(error instanceof Error, 'method threw a non-error'); + + result = error; + } + + const expectation = expectations[sequelize.dialect.name] ?? expectations.default; + if (expectation === undefined) { + throw new Error( + `No expectation was defined for ${sequelize.dialect.name} and the 'default' expectation has not been defined.`, + ); + } + + if (expectation instanceof Error) { + assert( + result instanceof Error, + `Expected method to error with "${expectation.message}", but it returned ${inspect(result)}.`, + ); + + expect(inlineErrorCause(result)).to.include(expectation.message); + } else { + assert( + !(result instanceof Error), + `Did not expect query to error, but it errored with ${inlineErrorCause(result)}`, + ); + + const isDefault = expectations[sequelize.dialect.name] === undefined; + assertMatchesExpectation(result, expectation, isDefault); + } +} + +function assertMatchesExpectation( + result: V, + expectation: V | Expectation, + isDefault: boolean, +): void { + if (expectation instanceof Expectation) { + expectation.assert(result, isDefault); + } else { + expect(result).to.deep.equal(expectation); + } +} + +abstract class Expectation { + abstract assert(value: Value, isDefault: boolean): void; +} + +interface SqlExpectationOptions { + genericQuotes?: boolean; +} + +class SqlExpectation extends Expectation { + readonly #sql: string | readonly string[]; + readonly #options: SqlExpectationOptions | undefined; + + constructor(sql: string | readonly string[], options?: SqlExpectationOptions) { + super(); + + this.#sql = sql; + this.#options = options; + } + + #prepareSql(sql: string | readonly string[], isDefault: boolean): string | string[] { + if (Array.isArray(sql)) { + return sql.map(part => this.#prepareSql(part, isDefault)) as string[]; + } + + if (isDefault) { + sql = replaceGenericIdentifierQuotes(sql as string, sequelize.dialect); + } + + return minifySql(sql as string); + } + + assert(value: string | readonly string[], isDefault: boolean) { + expect(this.#prepareSql(value, false)).to.deep.equal( + this.#prepareSql(this.#sql, isDefault || this.#options?.genericQuotes === true), + ); + } +} + +export function toMatchSql(sql: string | string[], options?: SqlExpectationOptions) { + return new SqlExpectation(sql, options); +} + +class RegexExpectation extends Expectation { + constructor(private readonly regex: RegExp) { + super(); + } + + assert(value: string) { + expect(value).to.match(this.regex); + } +} + +export function toMatchRegex(regex: RegExp) { + return new RegexExpectation(regex); +} + +type HasPropertiesInput> = { + [K in keyof Obj]?: any | Expectation | Error; +}; + +class HasPropertiesExpectation> extends Expectation { + constructor(private readonly properties: HasPropertiesInput) { + super(); + } + + assert(value: Obj, isDefault: boolean) { + for (const key of Object.keys(this.properties) as Array) { + assertMatchesExpectation(value[key], this.properties[key], isDefault); + } + } +} + +export function toHaveProperties>( + properties: HasPropertiesInput, +) { + return new HasPropertiesExpectation(properties); +} + +type MaybeLazy = T | (() => T); + +export function expectsql( + query: MaybeLazy, + assertions: { + query: PartialRecord; + bind: PartialRecord; + }, +): void; +export function expectsql( + query: MaybeLazy, + assertions: PartialRecord, +): void; +export function expectsql( + query: MaybeLazy, + assertions: + | { + query: PartialRecord; + bind: PartialRecord; + } + | PartialRecord, +): void { + const rawExpectationMap: PartialRecord = + 'query' in assertions ? assertions.query : assertions; + const expectations: PartialRecord<'default' | DialectName, string | Error> = Object.create(null); + + /** + * The list of expectations that are run against more than one dialect, which enables the transformation of + * identifier quoting to match the dialect. + */ + const combinedExpectations = new Set(); + combinedExpectations.add('default'); + + for (const [key, value] of Object.entries(rawExpectationMap)) { + const acceptedDialects = key.split(' ') as Array; + + if (acceptedDialects.length > 1) { + for (const dialect of acceptedDialects) { + combinedExpectations.add(dialect); + } + } + + for (const dialect of acceptedDialects) { + if (dialect === 'default' && acceptedDialects.length > 1) { + throw new Error(`The 'default' expectation cannot be combined with other dialects.`); + } + + if (expectations[dialect] !== undefined) { + throw new Error(`The expectation for ${dialect} was already defined.`); + } + + expectations[dialect] = value; + } + } + + const dialect = sequelize.dialect; + const usedExpectationName = dialect.name in expectations ? dialect.name : 'default'; + + let expectation = expectations[usedExpectationName]; + if (expectation == null) { + throw new Error( + `Undefined expectation for "${sequelize.dialect.name}"! (expectations: ${JSON.stringify(expectations)})`, + ); + } + + if (combinedExpectations.has(usedExpectationName) && typeof expectation === 'string') { + // replace [...] with the proper quote character for the dialect + // except for ARRAY[...] + expectation = replaceGenericIdentifierQuotes(expectation, dialect); + if (dialect.name === 'ibmi') { + expectation = expectation.trim().replace(/;$/, ''); + } + } + + if (typeof query === 'function') { + try { + query = query(); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw new TypeError( + 'expectsql: function threw something that is not an instance of Error.', + ); + } + + query = error; + } + } + + if (expectation instanceof Error) { + assert( + query instanceof Error, + `Expected query to error with "${expectation.message}", but it is equal to ${JSON.stringify(query)}.`, + ); + + expect(inlineErrorCause(query)).to.include(expectation.message); + } else { + assert( + !(query instanceof Error), + `Expected query to equal:\n${minifySql(expectation)}\n\nBut it errored with:\n${inlineErrorCause(query)}`, + ); + + expect(minifySql(isObject(query) ? query.query : query)).to.equal(minifySql(expectation)); + } + + if ('bind' in assertions) { + const bind = + assertions.bind[sequelize.dialect.name] || assertions.bind.default || assertions.bind; + // @ts-expect-error -- too difficult to type, but this is safe + expect(query.bind).to.deep.equal(bind); + } +} + +function replaceGenericIdentifierQuotes(sql: string, dialect: AbstractDialect): string { + return sql.replaceAll( + /(? isDeepStrictEqual(actual, expected)); +} + +/** + * Reduces insignificant whitespace from SQL string. + * + * @param sql the SQL string + * @returns the SQL string with insignificant whitespace removed. + */ +export function minifySql(sql: string): string { + // replace all consecutive whitespaces with a single plain space character + return ( + sql + .replaceAll(/\s+/g, ' ') + // remove space before comma + .replaceAll(' ,', ',') + // remove space before ) + .replaceAll(' )', ')') + // replace space after ( + .replaceAll('( ', '(') + // remove whitespace at start & end + .trim() + ); +} + +export const sequelize = createSequelizeInstance(); + +export function resetSequelizeInstance(sequelizeInstance: Sequelize = sequelize): void { + sequelizeInstance.removeAllModels(); +} + +// 'support' is requested by dev/check-connection, which is not a mocha context +if (typeof before !== 'undefined') { + before(function onBefore() { + // legacy, remove once all tests have been migrated to not use "this" anymore + // eslint-disable-next-line @typescript-eslint/no-invalid-this + Object.defineProperty(this, 'sequelize', { + value: sequelize, + writable: false, + configurable: false, + }); + }); +} + +type Tester = { + (...params: Params): void; + skip(...params: Params): void; + only(...params: Params): void; +}; +type TestFunctions = ExclusiveTestFunction | TestFunction | PendingTestFunction; + +export function createTester( + cb: (testFunction: TestFunctions, ...args: Params) => void, +): Tester { + function tester(...params: Params) { + cb(it, ...params); + } + + tester.skip = function skippedTester(...params: Params) { + cb(it.skip, ...params); + }; + + tester.only = function onlyTester(...params: Params) { + cb(it.only, ...params); + }; + + return tester; +} + +/** + * Works like {@link beforeEach}, but returns an object that contains the values returned by its latest execution. + * + * @param cb + */ +export function beforeEach2>(cb: () => Promise | T): T { + // it's not the right shape but we're cheating. We'll be updating the value of this object before each test! + const out = {} as T; + + beforeEach(async () => { + const out2 = await cb(); + + Object.assign(out, out2); + }); + + return out; +} + +/** + * Works like {@link before}, but returns an object that contains the values returned by its latest execution. + * + * @param cb + */ +export function beforeAll2>(cb: () => Promise | T): T { + // it's not the right shape but we're cheating. We'll be updating the value of this object before each test! + const out = {} as T; + + before(async () => { + const out2 = await cb(); + + Object.assign(out, out2); + }); + + return out; +} + +export function typeTest(_name: string, _callback: () => void): void { + // This function doesn't do anything. a type test is only checked by TSC and never runs. +} + +export async function unlinkIfExists(filePath: string): Promise { + try { + await fs.promises.unlink(filePath); + } catch (error) { + if (isNodeError(error) && error.code !== 'ENOENT') { + throw error; + } + } +} + +let isIntegrationTestSuite = false; + +export function setIsIntegrationTestSuite(value: boolean): void { + isIntegrationTestSuite = value; +} + +// 'support' is requested by dev/check-connection, which is not a mocha context +if (typeof after !== 'undefined') { + after('delete SQLite databases', async () => { + if (isIntegrationTestSuite) { + // all Sequelize instances must be closed to be able to delete the database files, including the default one. + // Closing is not possible in non-integration test suites, + // as _all_ connections must be mocked (even for sqlite, even though it's a file-based database). + await sequelize.close(); + } + + return fs.promises.rm(SQLITE_DATABASES_DIR, { recursive: true, force: true }); + }); +} + +// TODO: ignoredDeprecations should be removed in favour of EMPTY_ARRAY +const ignoredDeprecations: readonly string[] = [ + 'SEQUELIZE0013', + 'SEQUELIZE0018', + 'SEQUELIZE0019', + 'SEQUELIZE0021', + 'SEQUELIZE0022', +]; +let allowedDeprecations: readonly string[] = ignoredDeprecations; +export function allowDeprecationsInSuite(codes: readonly string[]) { + before(() => { + allowedDeprecations = [...codes, ...ignoredDeprecations]; + }); + + after(() => { + allowedDeprecations = ignoredDeprecations; + }); +} + +// TODO: the DeprecationWarning is only thrown once. We should figure out a way to reset that or move all tests that use deprecated tests to one suite per deprecation. +process.on('warning', (warning: NodeJS.ErrnoException) => { + if (warning.name === 'DeprecationWarning' && !allowedDeprecations.includes(warning.code!)) { + throw warning; + } +}); diff --git a/packages/core/test/teaser.ts b/packages/core/test/teaser.ts new file mode 100644 index 000000000000..88cba2d84c85 --- /dev/null +++ b/packages/core/test/teaser.ts @@ -0,0 +1,10 @@ +const DIALECT = process.env.DIALECT; + +if (!DIALECT) { + throw new Error('Environment variable DIALECT is undefined'); +} + +const header = '#'.repeat(DIALECT.length + 22); +const message = `${header}\n# Running tests for ${DIALECT} #\n${header}`; + +console.info(message); diff --git a/packages/core/test/tsconfig.json b/packages/core/test/tsconfig.json new file mode 100644 index 000000000000..3fca6c15f970 --- /dev/null +++ b/packages/core/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig-preset.json", + "compilerOptions": { + "rootDir": ".", + "types": ["node", "mocha", "sinon", "chai", "sinon-chai", "chai-as-promised", "chai-datetime"], + "noEmit": true, + "exactOptionalPropertyTypes": false, + "experimentalDecorators": true + }, + "include": ["./**/*.ts"] +} diff --git a/packages/core/test/types/associations.ts b/packages/core/test/types/associations.ts new file mode 100644 index 000000000000..e17082db1667 --- /dev/null +++ b/packages/core/test/types/associations.ts @@ -0,0 +1,132 @@ +import type { ForeignKey, InferAttributes } from '@sequelize/core'; +import { Model } from '@sequelize/core'; + +class Car extends Model> { + declare person: ForeignKey; +} + +class Person extends Model> { + declare id: number; +} + +class Country extends Model> { + declare cId: number; +} + +class PersonCountry extends Model> { + declare personId: ForeignKey; + declare countryId: ForeignKey; +} + +// BelongsTo + +Car.belongsTo(Person); +Car.belongsTo(Person, { foreignKey: 'person' }); +Car.belongsTo(Person, { foreignKey: { name: 'person' } }); + +// @ts-expect-error -- this foreign key does not exist on Car +Car.belongsTo(Person, { foreignKey: 'doesnotexist' }); +// @ts-expect-error -- this foreign key does not exist on Car +Car.belongsTo(Person, { foreignKey: { name: 'doesnotexist' } }); + +Car.belongsTo(Person, { targetKey: 'id' }); +// @ts-expect-error -- this should error, if this doesn't error, there is a bug! +Car.belongsTo(Person, { targetKey: 'doesnotexist' }); + +// HasOne + +Person.hasOne(Car); +Person.hasOne(Car, { foreignKey: 'person' }); +Person.hasOne(Car, { foreignKey: { name: 'person' } }); + +// @ts-expect-error -- this foreign key does not exist on Car +Person.hasOne(Car, { foreignKey: 'doesnotexist' }); +// @ts-expect-error -- this foreign key does not exist on Car +Person.hasOne(Car, { foreignKey: { name: 'doesnotexist' } }); + +Person.hasOne(Car, { sourceKey: 'id' }); +// @ts-expect-error -- this should error, if this doesn't error, there is a bug! +Person.hasOne(Car, { sourceKey: 'doesnotexist' }); + +// HasMany + +Person.hasMany(Car); +Person.hasMany(Car, { foreignKey: 'person' }); +Person.hasMany(Car, { foreignKey: { name: 'person' } }); + +// @ts-expect-error -- this foreign key does not exist on Car +Person.hasMany(Car, { foreignKey: 'doesnotexist' }); +// @ts-expect-error -- this foreign key does not exist on Car +Person.hasMany(Car, { foreignKey: { name: 'doesnotexist' } }); + +Person.hasMany(Car, { sourceKey: 'id' }); +// @ts-expect-error -- this should error, if this doesn't error, there is a bug! +Person.hasMany(Car, { sourceKey: 'doesnotexist' }); + +// BelongsToMany + +Person.belongsToMany(Country, { through: 'PersonCountry' }); + +// through model uses weak typings because it's set as a string. ForeignKey and OtherKey are not strictly checked. +Person.belongsToMany(Country, { + through: 'PersonCountry', + foreignKey: 'doesNotMatter', + otherKey: 'doesNotMatterEither', +}); +Person.belongsToMany(Country, { + through: { model: 'PersonCountry' }, + foreignKey: 'doesNotMatter', + otherKey: 'doesNotMatterEither', +}); + +Person.belongsToMany(Country, { + through: PersonCountry, + foreignKey: 'personId', + otherKey: 'countryId', +}); +Person.belongsToMany(Country, { + through: { model: PersonCountry }, + foreignKey: 'personId', + otherKey: 'countryId', +}); + +Person.belongsToMany(Country, { + through: PersonCountry, + // @ts-expect-error -- this must fail, 'through' is strongly defined and ForeignKey does not exist + foreignKey: 'doesNotExist', + otherKey: 'countryId', +}); + +Person.belongsToMany(Country, { + through: PersonCountry, + foreignKey: 'personId', + // @ts-expect-error -- this must fail, 'through' is strongly defined and OtherKey does not exist + otherKey: 'doesNotExist', +}); + +Person.belongsToMany(Country, { + through: { model: PersonCountry }, + foreignKey: 'personId', + // @ts-expect-error -- this must fail, 'through' is strongly defined and OtherKey does not exist + otherKey: 'doesNotExist', +}); + +Person.belongsToMany(Country, { + through: 'PersonCountry', + sourceKey: 'id', + targetKey: 'cId', +}); + +Person.belongsToMany(Country, { + through: 'PersonCountry', + // @ts-expect-error -- this key does not exist on Person + sourceKey: 'doesNotExist', + targetKey: 'cId', +}); + +Person.belongsToMany(Country, { + through: 'PersonCountry', + sourceKey: 'id', + // @ts-expect-error -- this key does not exist on Country + targetKey: 'doesNotExist', +}); diff --git a/packages/core/test/types/attributes.ts b/packages/core/test/types/attributes.ts new file mode 100644 index 000000000000..561ee78cbab1 --- /dev/null +++ b/packages/core/test/types/attributes.ts @@ -0,0 +1,43 @@ +import { Model } from '@sequelize/core'; + +interface UserCreationAttributes { + name: string; +} + +interface UserAttributes extends UserCreationAttributes { + id: number; +} + +class User extends Model implements UserAttributes { + declare id: number; + declare name: string; + + declare readonly projects?: Project[]; + declare readonly address?: Address; +} + +interface ProjectCreationAttributes { + ownerId: number; + name: string; +} + +interface ProjectAttributes extends ProjectCreationAttributes { + id: number; +} + +class Project + extends Model + implements ProjectAttributes +{ + declare id: number; + declare ownerId: number; + declare name: string; +} + +class Address extends Model { + declare userId: number; + declare address: string; +} + +// both models should be accepted in include +User.findAll({ include: [Project, Address] }); diff --git a/packages/core/test/types/bulk-create.ts b/packages/core/test/types/bulk-create.ts new file mode 100644 index 000000000000..46e3ade5e521 --- /dev/null +++ b/packages/core/test/types/bulk-create.ts @@ -0,0 +1,48 @@ +import type { CreationAttributes } from '@sequelize/core'; +import { Model } from '@sequelize/core'; +import { sequelize } from './connection'; + +class TestModel extends Model { + declare id: number; + declare testString: string | null; + declare testEnum: 'd' | 'e' | 'f' | null; +} + +sequelize.transaction(async trx => { + const newItems: Array> = [ + { + testEnum: 'e', + testString: 'abc', + }, + { + testEnum: null, + testString: undefined, + }, + ]; + + const res1: TestModel[] = await TestModel.bulkCreate(newItems, { + benchmark: true, + fields: ['testEnum'], + hooks: true, + logging: console.debug, + returning: true, + transaction: trx, + validate: true, + ignoreDuplicates: true, + }); + + const res2: TestModel[] = await TestModel.bulkCreate(newItems, { + benchmark: true, + fields: ['testEnum'], + hooks: true, + logging: console.debug, + returning: false, + transaction: trx, + validate: true, + updateOnDuplicate: ['testEnum', 'testString'], + }); + + const res3: TestModel[] = await TestModel.bulkCreate(newItems, { + conflictAttributes: ['testEnum', 'testString'], + }); +}); diff --git a/packages/core/test/types/connection.ts b/packages/core/test/types/connection.ts new file mode 100644 index 000000000000..50fcf3f26c41 --- /dev/null +++ b/packages/core/test/types/connection.ts @@ -0,0 +1,45 @@ +import type { SyncOptions } from '@sequelize/core'; +import { QueryTypes, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; +import { expectTypeOf } from 'expect-type'; +import { User } from './models/user'; + +export const sequelize = new Sequelize({ dialect: MySqlDialect }); + +sequelize.afterBulkSync((options: SyncOptions) => { + console.info('synced!'); +}); + +async function test() { + expectTypeOf( + await sequelize.query('SELECT * FROM `test`', { type: QueryTypes.SELECT }), + ).toEqualTypeOf(); + + expectTypeOf( + await sequelize.query('INSERT into test set test=1', { type: QueryTypes.INSERT }), + ).toEqualTypeOf<[number, number]>(); +} + +sequelize.transaction(async transaction => { + expectTypeOf( + await sequelize.query('SELECT * FROM `user`', { + retry: { + max: 123, + report: (msg, options) => {}, + }, + model: User, + transaction, + logging: console.debug, + }), + ).toEqualTypeOf(); +}); + +sequelize.query('SELECT * FROM `user` WHERE status = $1', { + bind: ['active'], + type: QueryTypes.SELECT, +}); + +sequelize.query('SELECT * FROM `user` WHERE status = $status', { + bind: { status: 'active' }, + type: QueryTypes.SELECT, +}); diff --git a/packages/core/test/types/create.ts b/packages/core/test/types/create.ts new file mode 100644 index 000000000000..b9c094003f64 --- /dev/null +++ b/packages/core/test/types/create.ts @@ -0,0 +1,92 @@ +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(), User.create()]); + 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/packages/core/test/types/define.ts b/packages/core/test/types/define.ts new file mode 100644 index 000000000000..99f2ee2e4a4e --- /dev/null +++ b/packages/core/test/types/define.ts @@ -0,0 +1,79 @@ +import type { BuildOptions, Model } from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; +import type { SetOptional } from 'type-fest'; +import { sequelize } from './connection'; + +// I really wouldn't recommend this, but if you want you can still use define() and interfaces + +interface UserAttributes { + id: number; + username: string; + firstName: string; + lastName: string; +} + +interface UserCreationAttributes extends SetOptional {} + +interface UserModel extends Model, UserAttributes {} + +const User = sequelize.define( + 'User', + { + id: { type: DataTypes.INTEGER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +); + +async function test() { + 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.INTEGER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +) as UntypedUserModelStatic; + +UntypedUser.customStaticMethod = () => {}; + +async function testUntyped() { + UntypedUser.customStaticMethod(); + + expectTypeOf().toMatchTypeOf(UntypedUser.build()); + + const user = await UntypedUser.findOne(); + expectTypeOf(user).toEqualTypeOf(); + + if (!user) { + return; + } + + user.firstName = 'John'; + await user.save(); +} diff --git a/packages/core/test/types/destroy.ts b/packages/core/test/types/destroy.ts new file mode 100644 index 000000000000..55188772963f --- /dev/null +++ b/packages/core/test/types/destroy.ts @@ -0,0 +1,14 @@ +import { User } from './models/user'; + +(async () => { + const user = await User.create(); + + await user.destroy({ + hooks: true, + }); + + await User.destroy({ + hooks: false, + where: { firstName: 'John' }, + }); +})(); diff --git a/packages/core/test/types/errors.ts b/packages/core/test/types/errors.ts new file mode 100644 index 000000000000..5e143f71bee0 --- /dev/null +++ b/packages/core/test/types/errors.ts @@ -0,0 +1,12 @@ +import type { + BaseError, + EmptyResultError, + OptimisticLockError, + UniqueConstraintError, +} from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf().toHaveProperty('sql').toBeString(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); diff --git a/packages/core/test/types/find-all.ts b/packages/core/test/types/find-all.ts new file mode 100644 index 000000000000..2e842bf9f447 --- /dev/null +++ b/packages/core/test/types/find-all.ts @@ -0,0 +1,37 @@ +import type { Attributes } from '@sequelize/core'; +import { cast, col } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; +import { User } from './models/user'; + +(async () => { + { + const users = await User.findAll(); + expectTypeOf(users).toEqualTypeOf(); + } + + { + const users = await User.findAll({ raw: false }); + expectTypeOf(users).toEqualTypeOf(); + } + + const rawUsers = await User.findAll({ raw: true }); + expectTypeOf(rawUsers).toEqualTypeOf>>(); + + interface CustomUser { + foo: 'bar'; + } + + const customUsers = await User.findAll({ + attributes: [ + ['bar', 'foo'], + 'firstName', + [col('table.id'), 'xyz'], + [cast(col('createdAt'), 'varchar'), 'abc'], + ], + raw: true, + }); + expectTypeOf(customUsers).toEqualTypeOf(); + + // @ts-expect-error -- this should error, if this doesn't error, there is a bug! + customUsers[0].id = 123; // not an instance +})(); diff --git a/packages/core/test/types/find-by-pk.ts b/packages/core/test/types/find-by-pk.ts new file mode 100644 index 000000000000..bfde2927f9d6 --- /dev/null +++ b/packages/core/test/types/find-by-pk.ts @@ -0,0 +1,152 @@ +import type { Attributes, FindByPkOptions } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; +import { User } from './models/user'; + +(async () => { + expectTypeOf(await User.findByPk(Buffer.from('asdf'))).toEqualTypeOf(); + + // rejectOnEmpty + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + rejectOnEmpty: new Error('error'), + }), + ).toEqualTypeOf(); + + // raw + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: true, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: undefined, + }), + ).toEqualTypeOf(); + + // combination + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: false, + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: false, + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: false, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: true, + rejectOnEmpty: false, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: true, + rejectOnEmpty: true, + }), + ).toEqualTypeOf>(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: true, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: undefined, + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: undefined, + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: undefined, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + // custom parameter + + interface CustomUser { + foo: 'bar'; + } + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + raw: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + attributes: [['bar', 'foo']], + rejectOnEmpty: false, + raw: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findByPk(Buffer.from('asdf'), { + attributes: [['bar', 'foo']], + rejectOnEmpty: true, + raw: true, + }), + ).toEqualTypeOf(); + + async function passDown(params: FindByPkOptions) { + // Unknown ahead of time + // We can't what 'rejectOnEmpty' and 'raw' are set to, so we default to these types: + expectTypeOf(await User.findByPk(Buffer.from('asdf'), params)).toEqualTypeOf(); + } +})(); diff --git a/packages/core/test/types/find-one.ts b/packages/core/test/types/find-one.ts new file mode 100644 index 000000000000..40ab5034297f --- /dev/null +++ b/packages/core/test/types/find-one.ts @@ -0,0 +1,167 @@ +import type { Attributes, FindOptions } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; +import { User } from './models/user'; + +// These attributes exist +User.findOne({ where: { firstName: 'John' } }); +User.findOne({ where: { $firstName$: 'John' } }); + +// These attributes do not exist +// @ts-expect-error -- this should error, if this doesn't error, findOne has a bug! +User.findOne({ where: { blah: 'blah2' } }); +// @ts-expect-error -- this should error, if this doesn't error, findOne has a bug! +User.findOne({ where: { $blah$: 'blah2' } }); + +// $nested.syntax$ is valid +User.findOne({ where: { '$include1.includeAttr$': 'blah2' } }); +// $nested.nested.syntax$ is valid +User.findOne({ where: { '$include1.$include2.includeAttr$': 'blah2' } }); + +(async () => { + expectTypeOf(await User.findOne()).toEqualTypeOf(); + + // rejectOnEmpty + + expectTypeOf( + await User.findOne({ + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + rejectOnEmpty: new Error('error'), + }), + ).toEqualTypeOf(); + + // raw + + expectTypeOf( + await User.findOne({ + raw: true, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findOne({ + raw: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: undefined, + }), + ).toEqualTypeOf(); + + // combination + + expectTypeOf( + await User.findOne({ + raw: false, + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: false, + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: false, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: true, + rejectOnEmpty: false, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findOne({ + raw: true, + rejectOnEmpty: true, + }), + ).toEqualTypeOf>(); + + expectTypeOf( + await User.findOne({ + raw: true, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf | null>(); + + expectTypeOf( + await User.findOne({ + raw: undefined, + rejectOnEmpty: false, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: undefined, + rejectOnEmpty: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + raw: undefined, + rejectOnEmpty: undefined, + }), + ).toEqualTypeOf(); + + // custom parameter + + interface CustomUser { + foo: 'bar'; + } + + expectTypeOf( + await User.findOne({ + raw: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + attributes: [['bar', 'foo']], + rejectOnEmpty: false, + raw: true, + }), + ).toEqualTypeOf(); + + expectTypeOf( + await User.findOne({ + attributes: [['bar', 'foo']], + rejectOnEmpty: true, + raw: true, + }), + ).toEqualTypeOf(); + + async function passDown(params: FindOptions>) { + // Unknown ahead of time + // We can't what 'rejectOnEmpty' and 'raw' are set to, so we default to these types: + expectTypeOf(await User.findOne(params)).toEqualTypeOf(); + } +})(); diff --git a/packages/core/test/types/hooks.ts b/packages/core/test/types/hooks.ts new file mode 100644 index 000000000000..94ca83a55c0c --- /dev/null +++ b/packages/core/test/types/hooks.ts @@ -0,0 +1,212 @@ +import type { + AbstractConnection, + AcquireConnectionOptions, + ConnectionOptions, + FindOptions, + QueryOptions, + SaveOptions, + UpsertOptions, +} from '@sequelize/core'; +import { Model, Sequelize } from '@sequelize/core'; +import type { AbstractQuery } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query.js'; +import type { + AfterAssociateEventData, + AssociationOptions, + BeforeAssociateEventData, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/associations/index.js'; +import type { ValidationOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/instance-validator.js'; +import type { ModelHooks } from '@sequelize/core/_non-semver-use-at-your-own-risk_/model-hooks.js'; +import { MySqlDialect } from '@sequelize/mysql'; +import { expectTypeOf } from 'expect-type'; +import type { SemiDeepWritable } from './type-helpers/deep-writable'; + +{ + class TestModel extends Model {} + + const hooks: Partial> = { + validationFailed(m, options, error) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(error).toEqualTypeOf(); + }, + 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(); + }, + beforeAssociate(data, options) { + expectTypeOf(data).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf>(); + }, + afterAssociate(data, options) { + expectTypeOf(data).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf>(); + }, + }; + + const sequelize = new Sequelize({ dialect: MySqlDialect, hooks }); + TestModel.init({}, { sequelize, hooks }); + + TestModel.addHook('beforeSave', hooks.beforeSave!); + TestModel.addHook('afterSave', hooks.afterSave!); + TestModel.addHook('afterFind', hooks.afterFind!); + TestModel.addHook('beforeUpsert', hooks.beforeUpsert!); + TestModel.addHook('afterUpsert', hooks.afterUpsert!); + + TestModel.beforeSave(hooks.beforeSave!); + TestModel.afterSave(hooks.afterSave!); + TestModel.afterFind(hooks.afterFind!); + + sequelize.beforeSave(hooks.beforeSave!); + sequelize.afterSave(hooks.afterSave!); + sequelize.afterFind(hooks.afterFind!); + sequelize.afterFind('namedAfterFind', hooks.afterFind!); +} + +// #12959 +{ + const hooks: ModelHooks = 0 as any; + + hooks.beforeValidate = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeCreate = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeDestroy = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeRestore = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeUpdate = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeSave = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeBulkCreate = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeBulkDestroy = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeBulkRestore = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeBulkUpdate = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + // hooks.beforeFind = (...args) => { + // expectTypeOf(args).toEqualTypeOf>(); + // }; + // + // hooks.beforeCount = (...args) => { + // expectTypeOf(args).toEqualTypeOf>(); + // }; + // + // hooks.beforeFindAfterExpandIncludeAll = (...args) => { + // expectTypeOf(args).toEqualTypeOf>(); + // }; + // + // hooks.beforeFindAfterOptions = (...args) => { + // expectTypeOf(args).toEqualTypeOf>(); + // }; + + hooks.beforeSync = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; + + hooks.beforeUpsert = (...args) => { + expectTypeOf(args).toEqualTypeOf>(); + }; +} + +const sequelize = new Sequelize({ dialect: MySqlDialect }); + +sequelize.beforeConnect('name', (config: ConnectionOptions) => { + expectTypeOf(config).toMatchTypeOf>(); +}); + +sequelize.beforeConnect((config: ConnectionOptions) => { + expectTypeOf(config).toMatchTypeOf>(); +}); + +sequelize.addHook('beforeConnect', (...args) => { + expectTypeOf(args).toMatchTypeOf<[ConnectionOptions]>(); +}); + +sequelize.beforePoolAcquire('name', (options?: AcquireConnectionOptions) => { + expectTypeOf(options).toMatchTypeOf(); +}); + +sequelize.beforePoolAcquire((options?: AcquireConnectionOptions) => { + expectTypeOf(options).toMatchTypeOf(); +}); + +sequelize.addHook('beforePoolAcquire', (...args: [AcquireConnectionOptions | undefined]) => { + expectTypeOf(args).toMatchTypeOf<[AcquireConnectionOptions | undefined]>(); +}); + +sequelize.afterPoolAcquire( + 'name', + (connection: AbstractConnection, options?: AcquireConnectionOptions) => { + expectTypeOf(connection).toMatchTypeOf(); + expectTypeOf(options).toMatchTypeOf(); + }, +); + +sequelize.afterPoolAcquire((connection: AbstractConnection, options?: AcquireConnectionOptions) => { + expectTypeOf(connection).toMatchTypeOf(); + expectTypeOf(options).toMatchTypeOf(); +}); + +sequelize.addHook( + 'afterPoolAcquire', + (...args: [AbstractConnection | AcquireConnectionOptions | undefined]) => { + expectTypeOf(args).toMatchTypeOf<[AbstractConnection | AcquireConnectionOptions | undefined]>(); + }, +); + +sequelize.beforeQuery((options, query) => { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); +}); + +sequelize.beforeQuery((...args) => { + expectTypeOf(args).toEqualTypeOf>(); +}); + +sequelize.afterQuery((options, query) => { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); +}); + +sequelize.beforeBulkSync((...args) => { + expectTypeOf(args).toEqualTypeOf>(); +}); diff --git a/packages/core/test/types/include.ts b/packages/core/test/types/include.ts new file mode 100644 index 000000000000..c08b44fe59fd --- /dev/null +++ b/packages/core/test/types/include.ts @@ -0,0 +1,48 @@ +import type { HasManyAssociation } from '@sequelize/core'; +import { Model, Sequelize } from '@sequelize/core'; + +class MyModel extends Model { + static associations: { + relation: HasManyAssociation; + }; +} + +class AssociatedModel extends Model {} + +MyModel.findAll({ + include: [ + { + duplicating: true, + limit: 1, + model: AssociatedModel, + on: { + a: 1, + }, + order: [ + ['id', 'DESC'], + ['AssociatedModel', MyModel, 'id', 'DESC'], + [MyModel, 'id'], + ], + separate: true, + where: { state: Sequelize.col('project.state') }, + all: true, + nested: true, + }, + ], +}); + +MyModel.findAll({ + include: [{ all: true }], +}); + +MyModel.findAll({ + include: [ + { + limit: 1, + association: 'relation', + order: [['id', 'DESC'], 'id', [AssociatedModel, MyModel, 'id', 'ASC']], + separate: true, + where: { state: Sequelize.col('project.state') }, + }, + ], +}); diff --git a/packages/core/test/types/infer-attributes.ts b/packages/core/test/types/infer-attributes.ts new file mode 100644 index 000000000000..1f2259c73dba --- /dev/null +++ b/packages/core/test/types/infer-attributes.ts @@ -0,0 +1,147 @@ +import type { + Attributes, + CreationAttributes, + CreationOptional, + ForeignKey, + InferAttributes, + InferCreationAttributes, + NonAttribute, +} from '@sequelize/core'; +import { DataTypes, Model, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; +import { expectTypeOf } from 'expect-type'; + +class Project extends Model> { + declare id: number; +} + +class User extends Model< + InferAttributes, + InferCreationAttributes +> { + declare optionalAttribute: CreationOptional; + declare mandatoryAttribute: string; + + declare optionalArrayAttribute: CreationOptional; + declare mandatoryArrayAttribute: string[]; + + // note: using CreationOptional here is unnecessary, but we still ensure that it works. + declare nullableOptionalAttribute: CreationOptional; + + declare nonAttribute: NonAttribute; + declare nonAttributeArray: NonAttribute; + declare nonAttributeNestedArray: NonAttribute; + + declare omittedAttribute: number; + declare omittedAttributeArray: number[]; + + declare joinedEntity?: NonAttribute; + declare projectId: CreationOptional>; + + instanceMethod() {} + + static staticMethod() {} +} + +User.init( + { + mandatoryArrayAttribute: DataTypes.ARRAY(DataTypes.STRING), + mandatoryAttribute: DataTypes.STRING, + // projectId is omitted but still works, because it is branded with 'ForeignKey' + nullableOptionalAttribute: DataTypes.STRING, + optionalArrayAttribute: DataTypes.ARRAY(DataTypes.STRING), + optionalAttribute: DataTypes.INTEGER, + }, + { sequelize: new Sequelize({ dialect: MySqlDialect }) }, +); + +type UserAttributes = Attributes; +type UserCreationAttributes = CreationAttributes; + +expectTypeOf().not.toBeAny(); +expectTypeOf().not.toBeAny(); + +expectTypeOf().not.toBeNullable(); +expectTypeOf().toBeNullable(); + +expectTypeOf().not.toBeNullable(); +expectTypeOf().not.toBeNullable(); + +expectTypeOf().not.toBeNullable(); +expectTypeOf().toBeNullable(); + +type NonUndefined = T extends undefined ? never : T; + +expectTypeOf().not.toEqualTypeOf< + NonUndefined +>(); + +expectTypeOf().not.toBeNullable(); +expectTypeOf().not.toBeNullable(); + +expectTypeOf().not.toHaveProperty('nonAttribute'); +expectTypeOf().not.toHaveProperty('nonAttribute'); + +expectTypeOf().not.toHaveProperty('nonAttributeArray'); +expectTypeOf().not.toHaveProperty('nonAttributeArray'); + +expectTypeOf().not.toHaveProperty('nonAttributeNestedArray'); +expectTypeOf().not.toHaveProperty('nonAttributeNestedArray'); + +expectTypeOf().not.toHaveProperty('omittedAttribute'); +expectTypeOf().not.toHaveProperty('omittedAttribute'); + +expectTypeOf().not.toHaveProperty('omittedAttributeArray'); +expectTypeOf().not.toHaveProperty('omittedAttributeArray'); + +expectTypeOf().not.toHaveProperty('instanceMethod'); +expectTypeOf().not.toHaveProperty('instanceMethod'); + +expectTypeOf().not.toHaveProperty('staticMethod'); +expectTypeOf().not.toHaveProperty('staticMethod'); + +// brands: + +{ + const user = User.build({ mandatoryArrayAttribute: [], mandatoryAttribute: '' }); + + // ensure branding does not break arrays. + const brandedArray: NonAttribute = user.nonAttributeArray; + const anArray: string[] = user.nonAttributeArray; + const item: boolean = user.nonAttributeArray[0].endsWith(''); +} + +{ + // ensure branding does not break objects + const brandedObject: NonAttribute> = {}; + const anObject: Record = brandedObject; + const item: string = brandedObject.key; +} + +{ + // ensure branding does not break primitives + const brandedString: NonAttribute = ''; + const aString: string = brandedString; +} + +{ + const user = User.build({ mandatoryArrayAttribute: [], mandatoryAttribute: '' }); + const project: Project = user.joinedEntity!; + + // ensure branding does not break objects + const id = project.id; +} + +{ + // ensure branding does not break null + const brandedString: NonAttribute = null; +} + +{ + class User2 extends Model, InferCreationAttributes> { + declare nullableAttribute: string | null; + } + + // this should work, all null attributes are optional in Model.create + User2.create({}); +} diff --git a/packages/core/test/types/max-execution-time.ts b/packages/core/test/types/max-execution-time.ts new file mode 100644 index 000000000000..8b9f07be74c4 --- /dev/null +++ b/packages/core/test/types/max-execution-time.ts @@ -0,0 +1,5 @@ +import { User } from './models/user'; + +User.findAll({ + maxExecutionTimeHintMs: 1000, +}); diff --git a/packages/core/test/types/model-count.ts b/packages/core/test/types/model-count.ts new file mode 100644 index 000000000000..adfb578acf42 --- /dev/null +++ b/packages/core/test/types/model-count.ts @@ -0,0 +1,23 @@ +import { Model, Op } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; + +class MyModel extends Model {} + +expectTypeOf(MyModel.count()).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ group: 'tag' })).toEqualTypeOf< + Promise> +>(); +expectTypeOf(MyModel.count({ group: 'tag', countGroupedRows: true })).toEqualTypeOf< + Promise> +>(); +expectTypeOf(MyModel.count({ col: 'tag', distinct: true })).toEqualTypeOf>(); +expectTypeOf( + MyModel.count({ + where: { + updatedAt: { + [Op.gte]: new Date(), + }, + }, + useMaster: false, + }), +).toEqualTypeOf>(); diff --git a/packages/core/test/types/model.ts b/packages/core/test/types/model.ts new file mode 100644 index 000000000000..800c67b5774b --- /dev/null +++ b/packages/core/test/types/model.ts @@ -0,0 +1,360 @@ +import type { + Association, + BelongsToManyGetAssociationsMixin, + CreationOptional, + HasOneAssociation, + InferAttributes, + InferCreationAttributes, + ModelDefined, +} from '@sequelize/core'; +import { DataTypes, Model, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; +import { expectTypeOf } from 'expect-type'; +import type { SetOptional } from 'type-fest'; + +expectTypeOf().toMatchTypeOf(); + +class MyModel extends Model, InferCreationAttributes> { + declare int: number; + declare str: string | null; + declare virtual: boolean | null; + + static associations: { + other: HasOneAssociation; + }; + + static async customStuff() { + return this.sequelize.query('select 1'); + } +} + +class OtherModel extends Model {} + +const Instance: MyModel = new MyModel({ int: 10 }); + +expectTypeOf(Instance.get('int')).toEqualTypeOf(); + +MyModel.findOne({ + include: [ + { + through: { + as: 'OtherModel', + attributes: ['int'], + }, + }, + ], +}); + +MyModel.findOne({ + include: [{ through: { paranoid: true } }], +}); + +MyModel.findOne({ + include: [{ model: OtherModel, paranoid: true }], +}); + +MyModel.hasOne(OtherModel, { as: 'OtherModelAlias' }); + +MyModel.findOne({ include: ['OtherModelAlias'] }); + +MyModel.findOne({ include: OtherModel }); + +MyModel.findAndCountAll({ include: OtherModel }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.findAndCountAll({ include: OtherModel, group: ['MyModel.int'] }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf>(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.findAndCountAll({ + include: OtherModel, + group: ['MyModel.int'], + countGroupedRows: true, +}).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf>(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.count({ include: OtherModel }).then(count => { + expectTypeOf(count).toEqualTypeOf(); +}); + +MyModel.count({ include: [MyModel], where: { $int$: [10, 120] } }).then(count => { + expectTypeOf(count).toEqualTypeOf(); +}); + +MyModel.count({ group: 'type' }).then(result => { + expectTypeOf(result).toEqualTypeOf>(); + expectTypeOf(result[0]).toMatchTypeOf<{ count: number }>(); +}); + +MyModel.increment('int', { by: 1 }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.increment({ int: 2 }, {}).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.increment(['int'], { by: 3 }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.decrement('int', { by: 1 }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.decrement({ int: 2 }, {}).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.decrement(['int'], { by: 3 }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedRows: MyModel[], affectedCount?: number]>(); +}); + +MyModel.build({ int: 10 }, { include: OtherModel }); + +MyModel.bulkCreate([{ int: 10 }], { include: OtherModel, searchPath: 'public' }); + +MyModel.update({}, { where: { str: 'bar' }, paranoid: false }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedCount: number]>(); +}); + +MyModel.update({}, { where: { str: 'bar' }, returning: false }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedCount: number]>(); +}); + +MyModel.update({}, { where: { str: 'bar' }, returning: true }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedCount: number, affectedRows: MyModel[]]>(); +}); + +MyModel.update({}, { where: { str: 'bar' }, returning: ['str'] }).then(result => { + expectTypeOf(result).toEqualTypeOf<[affectedCount: number, affectedRows: MyModel[]]>(); +}); + +const sequelize = new Sequelize({ dialect: MySqlDialect }); + +MyModel.init( + { + int: DataTypes.INTEGER, + str: DataTypes.STRING, + virtual: { + type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['int']), + get() { + const int: number = this.getDataValue('int'); + + return int + 2; + }, + set(value: number) { + this.setDataValue('int', value - 2); + }, + }, + }, + { + indexes: [ + { + fields: ['foo'], + using: 'gin', + operator: 'jsonb_path_ops', + }, + ], + sequelize, + tableName: 'my_model', + }, +); + +/** + * Tests for findCreateFind() type. + */ +class UserModel extends Model, InferCreationAttributes> { + declare username: string; + declare beta_user: CreationOptional; +} + +UserModel.init( + { + username: { type: DataTypes.STRING, allowNull: false }, + beta_user: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, + }, + { + sequelize, + }, +); + +UserModel.findCreateFind({ + where: { + username: 'new user username', + }, + defaults: { + beta_user: true, + username: 'new user username', + }, +}); + +const rawAttributes = UserModel.getAttributes(); +expectTypeOf(rawAttributes).toHaveProperty('username'); +expectTypeOf(rawAttributes).toHaveProperty('beta_user'); +expectTypeOf(rawAttributes).not.toHaveProperty('non_attribute'); + +/** + * Tests for findOrCreate() type. + */ + +UserModel.findOrCreate({ + // 'create' options + hooks: true, + fields: ['username'], + ignoreDuplicates: true, + returning: true, + validate: true, + raw: true, + isNewRecord: true, + include: [], + + // 'find' options + paranoid: true, + where: { + username: 'jane.doe', + }, + + // 'findOrCreate' options + defaults: { + username: 'jane.doe', + }, +}); + +/** + * Tests for findOrBuild() type. + */ + +UserModel.findOrBuild({ + // 'build' options + raw: true, + isNewRecord: true, + include: [], + + // 'find' options + paranoid: true, + where: { + username: 'jane.doe', + }, + + // 'findOrCreate' options + defaults: { + username: 'jane.doe', + }, +}); + +/** + * Test for primaryKeyAttributes. + */ +class TestModel extends Model {} + +TestModel.primaryKeyAttributes; + +/** + * Test for joinTableAttributes on BelongsToManyGetAssociationsMixin + */ +class SomeModel extends Model { + 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 MyModelAttributes { + id: number; + name: string; +} + +interface CreationAttributes extends SetOptional {} + +const ModelWithAttributes: ModelDefined = 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(); + +class FilmModelExtendToJson extends Model implements FilmToJson { + id!: number; + name?: string; + + toJSON() { + return { id: this.id }; + } +} + +const filmOverrideToJson = FilmModelExtendToJson.build(); + +const result = film.toJSON(); +expectTypeOf(result).toEqualTypeOf(); + +type FilmNoNameToJson = Omit; +const resultDerived = film.toJSON(); +expectTypeOf(resultDerived).toEqualTypeOf(); + +const resultOverrideToJson = filmOverrideToJson.toJSON(); +expectTypeOf(resultOverrideToJson).toEqualTypeOf(); diff --git a/packages/core/test/types/models/user-group.ts b/packages/core/test/types/models/user-group.ts new file mode 100644 index 000000000000..06af1d7905ab --- /dev/null +++ b/packages/core/test/types/models/user-group.ts @@ -0,0 +1,62 @@ +import type { + CreationOptional, + HasManyAddAssociationMixin, + HasManyAddAssociationsMixin, + HasManyAssociation, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + HasManyGetAssociationsMixin, + HasManyHasAssociationMixin, + HasManyRemoveAssociationMixin, + HasManyRemoveAssociationsMixin, + HasManySetAssociationsMixin, + InferAttributes, + InferCreationAttributes, + NonAttribute, +} from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { sequelize } from '../connection'; +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< + InferAttributes, + InferCreationAttributes +> { + static associations: { + users: HasManyAssociation; + }; + + declare id: CreationOptional; + declare name: string; + + // mixins for association (optional) + declare users?: NonAttribute; + declare getUsers: HasManyGetAssociationsMixin; + declare setUsers: HasManySetAssociationsMixin; + declare addUser: HasManyAddAssociationMixin; + declare addUsers: HasManyAddAssociationsMixin; + declare createUser: HasManyCreateAssociationMixin; + declare countUsers: HasManyCountAssociationsMixin; + declare hasUser: HasManyHasAssociationMixin; + declare removeUser: HasManyRemoveAssociationMixin; + declare removeUsers: HasManyRemoveAssociationsMixin; +} + +// attach all the metadata to the model +// instead of this, you could also use decorators +UserGroup.init( + { + name: DataTypes.STRING, + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + }, + { sequelize }, +); + +export const Users = UserGroup.hasMany(User, { as: 'users', foreignKey: 'groupId' }); diff --git a/packages/core/test/types/models/user-post.ts b/packages/core/test/types/models/user-post.ts new file mode 100644 index 000000000000..03e91b8dd440 --- /dev/null +++ b/packages/core/test/types/models/user-post.ts @@ -0,0 +1,50 @@ +import type { Model } from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import type { SetOptional } from 'type-fest'; +import { sequelize } from '../connection'; + +export interface UserPostAttributes { + id: number; + userId: number; + text: string; +} + +export interface UserPostCreationAttributes extends SetOptional {} + +export interface UserPostInstance + extends Model, + UserPostAttributes {} + +/** + * This is a component defined using `sequelize.define` to ensure that various + * functions also work with non-class models, which were the default before + * Sequelize v5. + */ +export const UserPost = sequelize.define( + 'UserPost', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + }, + text: { + type: DataTypes.STRING(255), + allowNull: false, + }, + }, + { + indexes: [ + { + name: 'userId', + fields: ['userId'], + }, + ], + }, +); + +UserPost.findOne({ where: { id: 1 } }); diff --git a/packages/core/test/types/models/user.ts b/packages/core/test/types/models/user.ts new file mode 100644 index 000000000000..ebcc7e7397a3 --- /dev/null +++ b/packages/core/test/types/models/user.ts @@ -0,0 +1,142 @@ +import type { + BelongsToAssociation, + BelongsToCreateAssociationMixin, + BelongsToGetAssociationMixin, + BelongsToSetAssociationMixin, + CreationOptional, + FindOptions, + InferAttributes, + InferCreationAttributes, + ModelStatic, +} from '@sequelize/core'; +import { DataTypes, Model, Op } from '@sequelize/core'; +import { sequelize } from '../connection'; +import { UserGroup } from './user-group'; +import { UserPost } from './user-post'; + +type NonUserAttributes = 'group'; + +export class User extends Model< + InferAttributes, + InferCreationAttributes +> { + static associations: { + group: BelongsToAssociation; + }; + + declare id: CreationOptional; + declare createdAt: CreationOptional; + declare updatedAt: CreationOptional; + + declare username: CreationOptional; + declare firstName: string; + declare lastName: CreationOptional; + declare groupId: CreationOptional; + + // mixins for association (optional) + declare group?: UserGroup; + declare getGroup: BelongsToGetAssociationMixin; + declare setGroup: BelongsToSetAssociationMixin; + declare createGroup: BelongsToCreateAssociationMixin; +} + +User.init( + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + firstName: { + type: DataTypes.STRING, + allowNull: false, + }, + lastName: DataTypes.STRING, + username: DataTypes.STRING, + groupId: DataTypes.INTEGER, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, + }, + { + version: true, + scopes: { + custom(a: number) { + return { + where: { + firstName: a, + }, + }; + }, + custom2() { + return {}; + }, + }, + indexes: [ + { + fields: ['firstName'], + using: 'BTREE', + name: 'firstNameIdx', + concurrently: true, + }, + ], + sequelize, + }, +); + +User.afterSync(() => { + sequelize.queryInterface.addIndex(User.table, { + fields: ['lastName'], + using: 'BTREE', + name: 'lastNameIdx', + concurrently: true, + }); +}); + +// Hooks +User.afterFind((users, options) => { + console.debug('found'); +}); + +// TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly +User.addHook('beforeFind', 'test', (options: FindOptions>) => {}); + +User.addHook('afterDestroy', async (instance, options) => { + // `options` from `afterDestroy` should be passable to `sequelize.transaction` + await instance.sequelize.transaction(options, async () => {}); +}); + +// Model#addScope +User.addScope('withoutLastName', { + where: { + lastName: { + [Op.is]: null, + }, + }, +}); + +User.addScope('withFirstName', (firstName: string) => ({ + where: { firstName }, +})); + +// associate with a class-based model +export const Group = User.belongsTo(UserGroup, { as: 'group', foreignKey: 'groupId' }); +// associate with a sequelize.define model +User.hasMany(UserPost, { as: 'posts', foreignKey: 'userId' }); +UserPost.belongsTo(User, { + foreignKey: 'userId', + targetKey: 'id', + as: 'user', +}); + +// associations refer to their Model +const userType: ModelStatic = User.associations.group.source; +const groupType: ModelStatic = User.associations.group.target; + +// should associate correctly with both sequelize.define and class-based models +User.findOne({ include: [{ model: UserGroup }] }); +User.findOne({ include: [{ model: UserPost }] }); + +User.scope(['custom2', { method: ['custom', 32] }]); +User.withScope(['custom2', { method: ['custom', 32] }]); + +const instance = new User({ username: 'foo', firstName: 'bar', lastName: 'baz' }); +instance.isSoftDeleted(); diff --git a/packages/core/test/types/order.ts b/packages/core/test/types/order.ts new file mode 100644 index 000000000000..b0baf9dc5405 --- /dev/null +++ b/packages/core/test/types/order.ts @@ -0,0 +1,60 @@ +import type { HasOneAssociation } from '@sequelize/core'; +import { Model } from '@sequelize/core'; + +class MyModel extends Model { + static associations: { + relation: HasOneAssociation; + }; +} + +class AssociatedModel extends Model {} + +MyModel.findAll({ + include: [ + { + limit: 1, + association: 'relation', + }, + ], + order: [ + ['id', 'DESC'], + 'id', + [AssociatedModel, MyModel, 'id', 'ASC'], + [ + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + AssociatedModel, + MyModel, + 'id', + 'ASC', + ], + ], +}); diff --git a/packages/core/test/types/query-interface.ts b/packages/core/test/types/query-interface.ts new file mode 100644 index 000000000000..09f7f453deb5 --- /dev/null +++ b/packages/core/test/types/query-interface.ts @@ -0,0 +1,274 @@ +import type { AbstractQueryInterface, TableNameWithSchema } from '@sequelize/core'; +import { DataTypes, Model, col, fn, literal } from '@sequelize/core'; + +declare let queryInterface: AbstractQueryInterface; + +async function test() { + await queryInterface.createTable( + 'nameOfTheNewTable', + { + attr1: DataTypes.STRING, + attr2: DataTypes.INTEGER, + attr3: { + unique: true, + allowNull: false, + defaultValue: false, + type: DataTypes.BOOLEAN, + }, + // foreign key usage + attr4: { + // @ts-expect-error -- unique attribute in createTable is boolean + unique: 'attr4_pk', + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + references: { + key: 'id', + table: 'another_table_name', + }, + type: DataTypes.INTEGER, + }, + attr5: { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + references: { + key: 'id', + table: { schema: '', tableName: 'another_table_name' }, + }, + type: DataTypes.INTEGER, + }, + id: { + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + }, + { + charset: 'latin1', // default: null + collate: 'latin1_general_ci', + engine: 'MYISAM', // default: 'InnoDB' + uniqueKeys: { + test: { + fields: ['attr2', 'attr3'], + }, + }, + }, + ); + await queryInterface.createTable({ tableName: '' }, {}); + + await queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable({ schema: '', tableName: 'nameOfTheExistingTable' }); + + await queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }); + + const bulkInsertRes: Promise = queryInterface.bulkInsert( + { tableName: 'foo' }, + [{}], + {}, + ); + + const bulkInsertResWithAttrs: Promise = queryInterface.bulkInsert( + 'foo', + [{}], + {}, + { bar: { type: DataTypes.JSON } }, + ); + + await queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar' }, {}, {}); + + await queryInterface.dropTrigger({ tableName: 'foo' }, 'foo', {}); + + queryInterface.quoteIdentifier('foo'); + queryInterface.quoteIdentifier('foo', true); + queryInterface.quoteIdentifiers('table.foo'); + + await queryInterface.dropAllTables(); + + await queryInterface.renameTable('Person', 'User'); + await queryInterface.renameTable( + { schema: '', tableName: 'Person' }, + { schema: '', tableName: 'User' }, + ); + + const tableNames: TableNameWithSchema[] = await queryInterface.listTables(); + + /* + attributes will be something like: + + { + 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 + } + } + */ + const attributes = await queryInterface.describeTable('Person'); + + await queryInterface.addColumn( + 'nameOfAnExistingTable', + 'nameOfTheNewAttribute', + DataTypes.STRING, + ); + + // or + + await queryInterface.addColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfTheNewAttribute', + DataTypes.STRING, + ); + + // or + + await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { + allowNull: false, + type: DataTypes.STRING, + }); + + await queryInterface.removeColumn('Person', 'signature'); + + // or + + await queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); + + await queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { + allowNull: false, + defaultValue: 0, + type: DataTypes.FLOAT, + }); + + // or + + await queryInterface.changeColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfAnExistingAttribute', + { + allowNull: false, + defaultValue: 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.addIndex('Foo', { + name: 'foo_include_a', + fields: ['foo_a'], + include: ['foo_b'], + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_include_literal_b', + fields: ['foo_a'], + include: literal('(foo_b)'), + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_include_literal_b', + fields: ['foo_a'], + include: [literal('foo_b')], + }); + + await queryInterface.removeIndex('Person', 'SuperDuperIndex'); + await queryInterface.removeIndex({ schema: '', tableName: 'Person' }, 'SuperDuperIndex'); + await queryInterface.removeIndex({ schema: '', tableName: 'Person' }, 'SuperDuperIndex', { + ifExists: true, + }); + + const indexes = await queryInterface.showIndex('Person'); + indexes.map(index => ({ + name: index.name, + table: index.tableName, + unique: index.unique, + primary: index.primary, + fields: index.fields.map(field => field.attribute), + type: index.type, + })); + + // or + + await queryInterface.removeIndex('Person', ['firstname', 'lastname']); + + await queryInterface.sequelize.transaction(async 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, + }, + }, + ); + + class TestModel extends Model {} + + await queryInterface.upsert('test', { a: 1 }, { b: 2 }, { c: 3 }, { model: TestModel }); + + await queryInterface.insert(null, 'test', {}); +} diff --git a/packages/core/test/types/sequelize.ts b/packages/core/test/types/sequelize.ts new file mode 100644 index 000000000000..40a899f43dbd --- /dev/null +++ b/packages/core/test/types/sequelize.ts @@ -0,0 +1,114 @@ +import type { ConnectionOptions, Fn, ModelStatic } from '@sequelize/core'; +import { Model, Op, QueryTypes, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; + +export const sequelize = new Sequelize({ + dialect: MySqlDialect, + hooks: { + afterConnect: (connection: unknown, config: ConnectionOptions) => { + // noop + }, + }, + retry: { + max: 123, + match: ['hurr'], + timeout: 3000, + backoffBase: 1000, + backoffExponent: 1.2, + report: (msg, options) => {}, + name: 'durr', + }, + keepDefaultTimezone: false, + pool: { + evict: 1000, + }, +}); + +// static members +Sequelize.fn('max', Sequelize.col('age')); +Sequelize.literal('1-2'); +Sequelize.cast('123', 'integer'); +Sequelize.and(); +Sequelize.or(); +Sequelize.json('data.id'); +Sequelize.where(Sequelize.col('ABS'), Op.is, null); + +// instance members +sequelize.fn('max', sequelize.col('age')); +sequelize.literal('1-2'); +sequelize.cast('123', 'integer'); +sequelize.and(); +sequelize.or(); +sequelize.json('data.id'); +sequelize.where(sequelize.col('ABS'), Op.is, null); + +const databaseName = sequelize.getDatabaseName(); + +const conn = sequelize.connectionManager; + +// hooks + +sequelize.beforeCreate('test', () => { + // noop +}); + +sequelize + .addHook('beforeConnect', (config: ConnectionOptions) => { + // noop + }) + .addHook('beforeBulkSync', () => { + // noop + }); + +Sequelize.addHook('beforeInit', () => { + // noop +}).addHook('afterInit', () => { + // noop +}); + +sequelize.beforeConnect(() => {}); + +sequelize.afterConnect(() => {}); + +const rnd: Fn = sequelize.random(); + +class Model1 extends Model {} + +class Model2 extends Model {} + +const MyModel: ModelStatic = sequelize.models.getOrThrow('asd'); +MyModel.hasOne(Model2); +MyModel.findAll(); + +async function test() { + // @ts-expect-error -- this should fail + await sequelize.query(1234); + // @ts-expect-error -- this should fail + await sequelize.query(/test/); + + const [results, meta]: [unknown[], unknown] = await sequelize.query('SELECT * FROM `user`', { + type: QueryTypes.RAW, + }); + + const res2: { count: number } | null = await sequelize.query<{ count: number }>( + 'SELECT COUNT(1) as count FROM `user`', + { + type: QueryTypes.SELECT, + plain: true, + }, + ); + + const res3: { [key: string]: unknown } | null = await sequelize.query( + 'SELECT COUNT(1) as count FROM `user`', + { + plain: true, + }, + ); + + const res4: { [key: string]: unknown } | null = await sequelize.query( + 'SELECT COUNT(1) as count FROM `user` WHERE 1 = 2', + { + plain: true, + }, + ); +} diff --git a/packages/core/test/types/transaction.ts b/packages/core/test/types/transaction.ts new file mode 100644 index 000000000000..691dd1be6f60 --- /dev/null +++ b/packages/core/test/types/transaction.ts @@ -0,0 +1,83 @@ +import { ConstraintChecking, Sequelize, Transaction } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; +import { User } from './models/user'; + +export const sequelize = new Sequelize({ dialect: MySqlDialect }); + +async function trans() { + const a: number = await sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.debug('transaction complete')); + User.create( + { + firstName: 'John', + }, + { + transaction, + }, + ); + + return 1; + }); +} + +async function trans2() { + return sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.debug('transaction complete')); + User.findAll({ + transaction, + lock: transaction.LOCK.UPDATE, + }); + + return 1; + }); +} + +async function trans3() { + return sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.debug('transaction complete')); + User.findAll({ + transaction, + lock: true, + }); + + return 1; + }); +} + +async function trans4() { + return sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.debug('transaction complete')); + User.findAll({ + transaction, + lock: { + level: transaction.LOCK.UPDATE, + of: User, + }, + }); + + return 1; + }); +} + +async function transact() { + const t = await sequelize.startUnmanagedTransaction({ + constraintChecking: ConstraintChecking.DEFERRED(['test']), + isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED, + type: Transaction.TYPES.DEFERRED, + }); + await t.commit(); + await t.rollback(); +} + +transact(); + +async function nestedTransact() { + const tr = await sequelize.startUnmanagedTransaction({ + transaction: await sequelize.startUnmanagedTransaction(), + }); + await tr.commit(); +} + +async function excludeFromTransaction() { + await sequelize.transaction(async t => sequelize.query('SELECT 1', { transaction: null })); +} diff --git a/packages/core/test/types/type-helpers/deep-writable.ts b/packages/core/test/types/type-helpers/deep-writable.ts new file mode 100644 index 000000000000..121dd54cf500 --- /dev/null +++ b/packages/core/test/types/type-helpers/deep-writable.ts @@ -0,0 +1,60 @@ +/** + * Adapted from krzkaczor/ts-essentials + * + * https://github.com/krzkaczor/ts-essentials/blob/v7.1.0/lib/types.ts#L165 + * + * Thank you! + */ + +import type { Model, ModelStatic, Sequelize } from '@sequelize/core'; + +type Builtin = + | string + | number + | boolean + | bigint + | symbol + | undefined + | null + | Function + | Date + | Error + | RegExp; + +type SequelizeBasic = Builtin | Sequelize | Model | 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/packages/core/test/types/typescript-docs/define.ts b/packages/core/test/types/typescript-docs/define.ts new file mode 100644 index 000000000000..a61d4ebf6961 --- /dev/null +++ b/packages/core/test/types/typescript-docs/define.ts @@ -0,0 +1,40 @@ +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, + Model, +} from '@sequelize/core'; +import { DataTypes, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; + +const sequelize = new Sequelize({ + dialect: MySqlDialect, + url: 'mysql://root:asd123@localhost:3306/mydb', +}); + +// We recommend you declare an interface for the attributes, for stricter typechecking + +interface IUserModel + extends Model, InferCreationAttributes> { + // Some fields are optional when calling UserModel.create() or UserModel.build() + id: CreationOptional; + 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/packages/core/test/types/typescript-docs/model-init-no-attributes.ts b/packages/core/test/types/typescript-docs/model-init-no-attributes.ts new file mode 100644 index 000000000000..a43fab1ed2ff --- /dev/null +++ b/packages/core/test/types/typescript-docs/model-init-no-attributes.ts @@ -0,0 +1,50 @@ +import { DataTypes, Model, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; + +const sequelize = new Sequelize({ + dialect: MySqlDialect, + url: 'mysql://root:asd123@localhost:3306/mydb', +}); + +class User extends Model { + declare id: number; + declare name: string; + declare preferredName: string | null; +} + +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/packages/core/test/types/typescript-docs/readme.md b/packages/core/test/types/typescript-docs/readme.md new file mode 100644 index 000000000000..5b2c70609538 --- /dev/null +++ b/packages/core/test/types/typescript-docs/readme.md @@ -0,0 +1,2 @@ +Files in this directory are imported by the documentation. +Check where they are used in the documentation before modifying them. diff --git a/packages/core/test/types/update.ts b/packages/core/test/types/update.ts new file mode 100644 index 000000000000..98ef92c44857 --- /dev/null +++ b/packages/core/test/types/update.ts @@ -0,0 +1,37 @@ +import { Model, col, fn, literal } from '@sequelize/core'; +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/packages/core/test/types/upsert.ts b/packages/core/test/types/upsert.ts new file mode 100644 index 000000000000..9422efb17347 --- /dev/null +++ b/packages/core/test/types/upsert.ts @@ -0,0 +1,67 @@ +import { Model } from '@sequelize/core'; +import { sequelize } from './connection'; + +class TestModel extends Model<{ foo: string; bar: string }, {}> {} + +TestModel.init( + { + foo: '', + bar: '', + }, + { sequelize }, +); + +sequelize.transaction(async trx => { + const res1: [TestModel, boolean | null] = await TestModel.upsert( + {}, + { + benchmark: true, + fields: ['foo'], + hooks: true, + logging: console.debug, + returning: true, + searchPath: 'DEFAULT', + transaction: trx, + validate: true, + }, + ); + + const res2: [TestModel, boolean | null] = await TestModel.upsert( + {}, + { + benchmark: true, + fields: ['foo'], + hooks: true, + logging: console.debug, + returning: false, + searchPath: 'DEFAULT', + transaction: trx, + validate: true, + }, + ); + + const res3: [TestModel, boolean | null] = await TestModel.upsert( + {}, + { + benchmark: true, + fields: ['foo'], + hooks: true, + logging: console.debug, + returning: ['foo'], + searchPath: 'DEFAULT', + transaction: trx, + validate: true, + conflictFields: ['foo', 'bar'], + }, + ); + + const res4: [TestModel, boolean | null] = await TestModel.upsert( + {}, + { + conflictWhere: { + foo: 'abc', + bar: 'def', + }, + }, + ); +}); diff --git a/packages/core/test/types/usage.ts b/packages/core/test/types/usage.ts new file mode 100644 index 000000000000..da2b6143365f --- /dev/null +++ b/packages/core/test/types/usage.ts @@ -0,0 +1,36 @@ +import { Group, User } from './models/user'; + +async function test(): Promise { + let user = await User.findOne({ include: [Group] }); + if (!user) { + return; + } + + User.update({}, { where: {} }); + user.firstName = 'John'; + await user.save(); + await user.setGroup(2); + + user = new User(); + user = new User({ firstName: 'John' }); + + user = await User.findOne(); + + if (!user) { + return; + } + + user.update({}, {}); + user.update( + {}, + { + silent: true, + }, + ); + + const user2 = await User.create({ firstName: 'John', groupId: 1 }); + await User.findAndCountAll({ distinct: true }); + + const user3 = await User.create({ firstName: 'Jane', groupId: 1 }, { validate: false }); + await User.findAndCountAll({ distinct: true }); +} diff --git a/packages/core/test/types/validators.ts b/packages/core/test/types/validators.ts new file mode 100644 index 000000000000..efe3d90e2761 --- /dev/null +++ b/packages/core/test/types/validators.ts @@ -0,0 +1,27 @@ +import { DataTypes, Model, Sequelize } from '@sequelize/core'; +import { MySqlDialect } from '@sequelize/mysql'; + +const sequelize = new Sequelize({ dialect: MySqlDialect }); + +/** + * 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 }, +); diff --git a/packages/core/test/types/where.ts b/packages/core/test/types/where.ts new file mode 100644 index 000000000000..f2a85472ad68 --- /dev/null +++ b/packages/core/test/types/where.ts @@ -0,0 +1,165 @@ +import type { Attributes, InferAttributes, WhereOptions } from '@sequelize/core'; +import { Model, Op, Sequelize, Transaction, and, or } from '@sequelize/core'; +import { expectTypeOf } from 'expect-type'; + +// NOTE: most typing tests for WhereOptions are located in test/unit/sql/where.test.ts + +class MyModel extends Model> { + declare id: number; + declare hi: number; + declare name: string; + declare groupId: number | null; +} + +// Optional values +expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf(); + +{ + // @ts-expect-error -- cannot use column references in Op.any + const a: WhereOptions = { hi: { [Op.eq]: { [Op.any]: [Sequelize.col('SOME_COL')] } } }; + + // @ts-expect-error -- cannot use column references in Op.any + const b: WhereOptions = { hi: { [Op.eq]: { [Op.all]: [Sequelize.col('SOME_COL')] } } }; +} + +// 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') }, + }, + ], +}); + +{ + 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 }); + + // Where as having option + MyModel.findAll({ having: where }); +} + +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', groupId: null } }); + + // search within a specific range + projects = await MyModel.findAll({ where: { id: [1, 2, 3] } }); + + // 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 } }); +} + +// From https://sequelize.org/master/en/v4/docs/models-usage/ + +const where: WhereOptions = {}; +MyModel.findAll({ + where: and( + where, + or({ id: 1 }, { id: 2 }), + and({ id: 1 }, { id: 2 }), + Sequelize.where(Sequelize.col('col'), Op.eq, null), + Sequelize.literal('1 = 2'), + ), +}); + +MyModel.findAll({ + where: or( + where, + or({ id: 1 }, { id: 2 }), + and({ id: 1 }, { id: 2 }), + Sequelize.where(Sequelize.col('col'), Op.eq, null), + Sequelize.literal('1 = 2'), + ), +}); + +MyModel.findAll({ + where: { + [Op.and]: [ + where, + or({ id: 1 }, { id: 2 }), + and({ id: 1 }, { id: 2 }), + Sequelize.where(Sequelize.col('col'), Op.eq, null), + Sequelize.literal('1 = 2'), + ], + }, +}); + +MyModel.findAll({ + where: { + [Op.or]: [ + where, + or({ id: 1 }, { id: 2 }), + and({ id: 1 }, { id: 2 }), + Sequelize.where(Sequelize.col('col'), Op.eq, null), + Sequelize.literal('1 = 2'), + ], + }, +}); + +MyModel.findAll({ + // implicit "AND" + where: [ + where, + or({ id: 1 }, { id: 2 }), + and({ id: 1 }, { id: 2 }), + Sequelize.where(Sequelize.col('col'), Op.eq, null), + Sequelize.literal('1 = 2'), + ], +}); + +MyModel.findAll({ + where: { + id: { + [Op.in]: { + // @ts-expect-error -- cannot use Operator inside another one! + [Op.eq]: [1, 2], + }, + }, + }, +}); + +// @ts-expect-error -- no attribute +MyModel.findAll({ + where: [1, 2], +}); + +MyModel.findAll({ + // @ts-expect-error -- no attribute + where: { [Op.or]: [1, 2] }, +}); + +MyModel.findAll({ + // @ts-expect-error -- no attribute + where: { [Op.and]: { [Op.or]: [1, 2] } }, +}); + +MyModel.findAll({ + where: { + id: { + [Op.eq]: { + // @ts-expect-error -- this is not a valid query + [Op.or]: [1, 2], + }, + }, + }, +}); diff --git a/packages/core/test/unit/associations/association.test.ts b/packages/core/test/unit/associations/association.test.ts new file mode 100644 index 000000000000..a220ff884991 --- /dev/null +++ b/packages/core/test/unit/associations/association.test.ts @@ -0,0 +1,48 @@ +import { AssociationError } from '@sequelize/core'; +import { expect } from 'chai'; +import { getTestDialectTeaser, sequelize } from '../../support'; + +describe(getTestDialectTeaser('belongsTo'), () => { + it('should throw an AssociationError when two associations have the same alias', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.belongsTo(Task, { as: 'task' }); + + expect(() => { + User.belongsTo(Task, { as: 'task' }); + }).to.throw( + AssociationError, + 'You have defined two associations with the same name "task" on the model "User". Use another alias using the "as" parameter.', + ); + }); + + it('should throw an AssociationError when two associations have the same alias (one inferred)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + const association = User.belongsTo(Task); + expect(association.as).to.eq('task'); + + expect(() => { + User.belongsTo(Task, { as: 'task' }); + }).to.throw( + AssociationError, + 'You have defined two associations with the same name "task" on the model "User". Use another alias using the "as" parameter.', + ); + }); + + it('should throw an AssociationError when two associations have the same alias (both inferred)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.belongsTo(Task); + + expect(() => { + User.belongsTo(Task); + }).to.throw( + AssociationError, + 'You have defined two associations with the same name "task" on the model "User". Use another alias using the "as" parameter.', + ); + }); +}); diff --git a/packages/core/test/unit/associations/belongs-to-many.test.ts b/packages/core/test/unit/associations/belongs-to-many.test.ts new file mode 100644 index 000000000000..a8022845a0d1 --- /dev/null +++ b/packages/core/test/unit/associations/belongs-to-many.test.ts @@ -0,0 +1,1380 @@ +import type { + BelongsToGetAssociationMixin, + BelongsToManyAssociation, + BelongsToManySetAssociationsMixin, + CreationOptional, + InferAttributes, + InferCreationAttributes, + ModelStatic, +} from '@sequelize/core'; +import { + AssociationError, + BelongsToAssociation, + DataTypes, + HasManyAssociation, + HasOneAssociation, + Model, +} from '@sequelize/core'; +import { expect } from 'chai'; +import each from 'lodash/each'; +import type { SinonStub } from 'sinon'; +import sinon from 'sinon'; +import { + beforeEach2, + createSequelizeInstance, + getTestDialectTeaser, + resetSequelizeInstance, + sequelize, +} from '../../support'; + +describe(getTestDialectTeaser('belongsToMany'), () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + it('throws when invalid model is passed', () => { + const User = sequelize.define('User'); + + expect(() => { + // @ts-expect-error -- testing that invalid input results in error + User.belongsToMany(); + }).to.throw( + `User.belongsToMany was called with undefined as the target model, but it is not a subclass of Sequelize's Model class`, + ); + }); + + it('creates the join table when through is a string', async () => { + const User = sequelize.define('User'); + const Group = sequelize.define('Group'); + + User.belongsToMany(Group, { as: 'MyGroups', through: 'GroupUser' }); + Group.belongsToMany(User, { as: 'MyUsers', through: 'GroupUser' }); + + expect(sequelize.models.getOrThrow('GroupUser')).to.exist; + }); + + it('should not inherit scopes from parent to join table', () => { + const A = sequelize.define('a'); + const B = sequelize.define( + 'b', + {}, + { + defaultScope: { + where: { + foo: 'bar', + }, + }, + scopes: { + baz: { + where: { + fooz: 'zab', + }, + }, + }, + }, + ); + + B.belongsToMany(A, { through: 'AB' }); + + const AB = sequelize.models.getOrThrow('AB'); + + expect(AB.options.defaultScope).to.deep.equal({}); + expect(AB.options.scopes).to.deep.equal({}); + }); + + it('should not inherit validations from parent to join table', () => { + const A = sequelize.define('a'); + const B = sequelize.define( + 'b', + {}, + { + validate: { + validateModel() { + return true; + }, + }, + }, + ); + + B.belongsToMany(A, { through: 'AB' }); + + const AB = sequelize.models.getOrThrow('AB'); + + expect(AB.options.validate).to.deep.equal({}); + }); + + it('should not override custom methods with association mixin', () => { + const methods = { + getTasks: 'get', + countTasks: 'count', + hasTask: 'has', + hasTasks: 'has', + setTasks: 'set', + addTask: 'add', + addTasks: 'add', + removeTask: 'remove', + removeTasks: 'remove', + createTask: 'create', + }; + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + function originalMethod() {} + + each(methods, (alias, method) => { + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + User.prototype[method] = originalMethod; + }); + + User.belongsToMany(Task, { through: 'UserTasks', as: 'task' }); + + const user = User.build(); + + each(methods, (alias, method) => { + // @ts-expect-error -- dynamic type, not worth typing + expect(user[method]).to.eq(originalMethod); + }); + }); + + it('allows customizing the inverse association name (long form)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.belongsToMany(Task, { through: 'UserTask', as: 'tasks', inverse: { as: 'users' } }); + + expect(Task.associations.users).to.be.ok; + expect(User.associations.tasks).to.be.ok; + }); + + it('allows customizing the inverse association name (shorthand)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.belongsToMany(Task, { through: 'UserTask', as: 'tasks', inverse: 'users' }); + + expect(Task.associations.users).to.be.ok; + expect(User.associations.tasks).to.be.ok; + }); + + it('allows defining two associations with the same through, but with a different scope on the through table', () => { + const User = sequelize.define('User'); + const Post = sequelize.define('Post', { editing: DataTypes.BOOLEAN }); + + User.belongsToMany(Post, { through: 'UserPost' }); + Post.belongsToMany(User, { through: 'UserPost' }); + + User.belongsToMany(Post, { + as: 'editingPosts', + inverse: { + as: 'editingUsers', + }, + through: { + model: 'UserPost', + scope: { + editing: true, + }, + }, + }); + }); + + it('allows defining two associations with the same inverse association', () => { + const User = sequelize.define('User'); + const Post = sequelize.define('Post'); + + const Association1 = Post.belongsToMany(User, { + through: { model: 'UserPost' }, + as: 'categories', + scope: { type: 'category' }, + }); + + const Association2 = Post.belongsToMany(User, { + through: { model: 'UserPost' }, + as: 'tags', + scope: { type: 'tag' }, + }); + + // This means Association1.pairedWith.pairedWith is not always Association1 + // This may be an issue + expect(Association1.pairedWith).to.eq(Association2.pairedWith); + }); + + it('lets you customize the name of the intermediate associations', () => { + const User = sequelize.define('User'); + const Group = sequelize.define('Group'); + const GroupUser = sequelize.define('GroupUser'); + + User.belongsToMany(Group, { + through: GroupUser, + as: 'groups', + throughAssociations: { + toSource: 'toSource', + toTarget: 'toTarget', + fromSource: 'fromSources', + fromTarget: 'fromTargets', + }, + inverse: { + as: 'members', + }, + }); + + expect(Object.keys(User.associations).sort()).to.deep.eq([ + 'fromSource', + 'fromSources', + 'groups', + ]); + expect(Object.keys(Group.associations).sort()).to.deep.eq([ + 'fromTarget', + 'fromTargets', + 'members', + ]); + expect(Object.keys(GroupUser.associations).sort()).to.deep.eq(['toSource', 'toTarget']); + }); + + it('errors when trying to define similar associations with incompatible inverse associations', () => { + const User = sequelize.define('User'); + const Post = sequelize.define('Post'); + + Post.belongsToMany(User, { + through: { model: 'UserPost' }, + as: 'categories', + scope: { type: 'category' }, + }); + + expect(() => { + Post.belongsToMany(User, { + through: { model: 'UserPost' }, + as: 'tags', + scope: { type: 'tag' }, + otherKey: { + onUpdate: 'NO ACTION', + }, + }); + }).to.throw('Defining BelongsToMany association "tags" from Post to User failed'); + }); + + it('errors when trying to define the same association', () => { + const User = sequelize.define('User'); + const Post = sequelize.define('Post'); + + Post.belongsToMany(User, { + through: { model: 'UserPost' }, + }); + + expect(() => { + Post.belongsToMany(User, { through: { model: 'UserPost' } }); + }).to.throw( + 'You have defined two associations with the same name "users" on the model "Post". Use another alias using the "as" parameter', + ); + }); + + it('generates a default association name', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + User.belongsToMany(Task, { through: 'UserTask' }); + + expect(Object.keys(Task.associations)).to.deep.eq(['users', 'usersTasks', 'userTask']); + expect(Object.keys(User.associations)).to.deep.eq(['tasks', 'tasksUsers', 'taskUser']); + }); + + describe('proper syntax', () => { + it('throws an AssociationError if the through option is undefined, true, or null', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + // @ts-expect-error -- we're testing that these do throw + const errorFunction1 = () => User.belongsToMany(Task, { through: true }); + // @ts-expect-error -- see above + const errorFunction2 = () => User.belongsToMany(Task, { through: undefined }); + // @ts-expect-error -- see above + const errorFunction3 = () => User.belongsToMany(Task, { through: null }); + for (const errorFunction of [errorFunction1, errorFunction2, errorFunction3]) { + expect(errorFunction).to.throwWithCause( + AssociationError, + `${User.name}.belongsToMany(${Task.name}) requires a through model, set the "through", or "through.model" options to either a string or a model`, + ); + } + }); + + it('throws an AssociationError for a self-association defined without an alias', () => { + const User = sequelize.define('User', {}); + + const errorFunction = User.belongsToMany.bind(User, User, { through: 'jointable' }); + expect(errorFunction).to.throwWithCause( + AssociationError, + 'Both options "as" and "inverse.as" must be defined for belongsToMany self-associations, and their value must be different.', + ); + }); + }); + + describe('timestamps', () => { + it('follows the global timestamps true option', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + User.belongsToMany(Task, { through: 'user_task1' }); + + expect(sequelize.models.getOrThrow('user_task1').getAttributes()).to.contain.all.keys([ + 'createdAt', + 'updatedAt', + ]); + }); + + it('allows me to override the global timestamps option', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + User.belongsToMany(Task, { through: { model: 'user_task2', timestamps: false } }); + + expect(sequelize.models.getOrThrow('user_task2').getAttributes()).not.to.contain.any.keys([ + 'createdAt', + 'updatedAt', + ]); + }); + + it('follows the global timestamps false option', () => { + const sequelize2 = createSequelizeInstance({ + define: { + timestamps: false, + }, + }); + + const User = sequelize2.define('User', {}); + const Task = sequelize2.define('Task', {}); + + User.belongsToMany(Task, { through: 'user_task3' }); + + expect(sequelize2.models.getOrThrow('user_task3').getAttributes()).not.to.contain.any.keys([ + 'createdAt', + 'updatedAt', + ]); + }); + }); + + describe('optimizations using bulk create, destroy and update', () => { + function getEntities() { + class User extends Model, InferCreationAttributes> { + declare id: CreationOptional; + declare username: string | null; + + declare setTasks: BelongsToManySetAssociationsMixin; + } + + User.init( + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + username: DataTypes.STRING, + }, + { sequelize }, + ); + + class Task extends Model> { + declare id: CreationOptional; + declare title: string | null; + } + + Task.init( + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + title: DataTypes.STRING, + }, + { sequelize }, + ); + + const UserTasks = sequelize.define('UserTasks', {}); + + User.belongsToMany(Task, { through: UserTasks }); + Task.belongsToMany(User, { through: UserTasks }); + + const user = User.build({ + id: 42, + }); + const task1 = Task.build({ + id: 15, + }); + const task2 = Task.build({ + id: 16, + }); + + sinon.stub(UserTasks, 'findAll').resolves([]); + sinon.stub(UserTasks, 'bulkCreate').resolves([]); + sinon.stub(UserTasks, 'destroy').resolves(0); + + return { user, task1, task2, UserTasks }; + } + + afterEach(() => { + sinon.restore(); + }); + + it('uses one insert into statement', async () => { + const { user, task1, task2, UserTasks } = getEntities(); + await user.setTasks([task1, task2]); + + expect(UserTasks.findAll).to.have.been.calledOnce; + expect(UserTasks.bulkCreate).to.have.been.calledOnce; + }); + + it('uses one delete from statement', async () => { + const { user, task1, task2, UserTasks } = getEntities(); + + (UserTasks.findAll as SinonStub) + .onFirstCall() + .resolves([]) + .onSecondCall() + .resolves([ + { userId: 42, taskId: 15 }, + { userId: 42, taskId: 16 }, + ]); + + await user.setTasks([task1, task2]); + await user.setTasks([]); + expect(UserTasks.findAll).to.have.been.calledTwice; + expect(UserTasks.destroy).to.have.been.calledOnce; + }); + }); + + describe('foreign keys', () => { + it('should infer otherKey from paired BTM relationship with a through string defined', () => { + const User = sequelize.define('User', {}); + const Place = sequelize.define('Place', {}); + + const Places = User.belongsToMany(Place, { + through: 'user_places', + foreignKey: 'user_id', + otherKey: 'place_id', + }); + const Users = Place.getAssociation('users') as BelongsToManyAssociation; + + expect(Places.pairedWith).to.equal(Users); + expect(Users.pairedWith).to.equal(Places); + + expect(Places.foreignKey).to.equal('user_id'); + expect(Users.foreignKey).to.equal('place_id'); + + expect(Places.otherKey).to.equal('place_id'); + expect(Users.otherKey).to.equal('user_id'); + }); + + it('should infer otherKey from paired BTM relationship with a through model defined', () => { + const User = sequelize.define('User', {}); + const Place = sequelize.define('Place', {}); + const UserPlace = sequelize.define( + 'UserPlace', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + }, + { timestamps: false }, + ); + + const Places = User.belongsToMany(Place, { + through: UserPlace, + foreignKey: 'user_id', + otherKey: 'place_id', + }); + const Users = Place.getAssociation('users') as BelongsToManyAssociation; + + expect(Places.pairedWith).to.equal(Users); + expect(Users.pairedWith).to.equal(Places); + + expect(Places.foreignKey).to.equal('user_id'); + expect(Users.foreignKey).to.equal('place_id'); + + expect(Places.otherKey).to.equal('place_id'); + expect(Users.otherKey).to.equal('user_id'); + + expect(Object.keys(UserPlace.getAttributes()).length).to.equal(3); // Defined primary key and two foreign keys + }); + + it('should infer foreign keys (camelCase)', () => { + const Person = sequelize.define('Person'); + const PersonChildren = sequelize.define('PersonChildren'); + const Children = Person.belongsToMany(Person, { + as: 'Children', + through: PersonChildren, + inverse: { as: 'Parents' }, + }); + + expect(Children.foreignKey).to.equal('parentId'); + expect(Children.otherKey).to.equal('childId'); + expect(PersonChildren.getAttributes()[Children.foreignKey]).to.be.ok; + expect(PersonChildren.getAttributes()[Children.otherKey]).to.be.ok; + }); + + it('should infer foreign keys (snake_case)', () => { + const Person = sequelize.define('Person', {}, { underscored: true }); + const PersonChildren = sequelize.define('PersonChildren', {}, { underscored: true }); + const Children = Person.belongsToMany(Person, { + as: 'Children', + through: PersonChildren, + inverse: { as: 'Parents' }, + }); + + expect(Children.foreignKey).to.equal('parentId'); + expect(Children.otherKey).to.equal('childId'); + expect(PersonChildren.getAttributes()[Children.foreignKey]).to.be.ok; + expect(PersonChildren.getAttributes()[Children.otherKey]).to.be.ok; + expect(PersonChildren.getAttributes()[Children.foreignKey].columnName).to.equal('parent_id'); + expect(PersonChildren.getAttributes()[Children.otherKey].columnName).to.equal('child_id'); + }); + + it('should create non-null foreign keys by default', () => { + const A = sequelize.define('A'); + const B = sequelize.define('B'); + + const association = A.belongsToMany(B, { through: 'AB' }); + + const attributes = association.throughModel.getAttributes(); + expect(attributes.aId.allowNull).to.be.false; + expect(attributes.bId.allowNull).to.be.false; + }); + + it('allows creating nullable FKs', () => { + const A = sequelize.define('A'); + const B = sequelize.define('B'); + + const association = A.belongsToMany(B, { + through: 'AB', + foreignKey: { allowNull: true }, + otherKey: { allowNull: true }, + }); + + const attributes = association.throughModel.getAttributes(); + expect(attributes.aId.allowNull).to.be.true; + expect(attributes.bId.allowNull).to.be.true; + }); + + it('should add FKs with onDelete=cascade by default', () => { + const A = sequelize.define('A'); + const B = sequelize.define('B'); + + const association = A.belongsToMany(B, { through: 'AB', foreignKey: {} }); + + const attributes = association.throughModel.getAttributes(); + expect(attributes.aId.onDelete).to.eq('CASCADE'); + expect(attributes.bId.onDelete).to.eq('CASCADE'); + }); + }); + + describe('source/target keys', () => { + it('should infer targetKey from paired BTM relationship with a through string defined', () => { + const User = sequelize.define('User', { user_id: DataTypes.UUID }); + const Place = sequelize.define('Place', { place_id: DataTypes.UUID }); + + const Places = User.belongsToMany(Place, { + through: 'user_places', + sourceKey: 'user_id', + targetKey: 'place_id', + }); + const Users = Place.getAssociation('users') as BelongsToManyAssociation; + + expect(Places.pairedWith).to.equal(Users); + expect(Users.pairedWith).to.equal(Places); + + expect(Places.sourceKey).to.equal('user_id'); + expect(Users.sourceKey).to.equal('place_id'); + + expect(Places.targetKey).to.equal('place_id'); + expect(Users.targetKey).to.equal('user_id'); + }); + + it('should infer targetKey from paired BTM relationship with a through model defined', () => { + const User = sequelize.define('User', { user_id: DataTypes.UUID }); + const Place = sequelize.define('Place', { place_id: DataTypes.UUID }); + const UserPlace = sequelize.define( + 'UserPlace', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + }, + { timestamps: false }, + ); + + const Places = User.belongsToMany(Place, { + through: UserPlace, + sourceKey: 'user_id', + targetKey: 'place_id', + }); + const Users = Place.getAssociation('users') as BelongsToManyAssociation; + + expect(Places.pairedWith).to.equal(Users); + expect(Users.pairedWith).to.equal(Places); + + expect(Places.sourceKey).to.equal('user_id'); + expect(Users.sourceKey).to.equal('place_id', 'Users.sourceKey is invalid'); + + expect(Places.targetKey).to.equal('place_id'); + expect(Users.targetKey).to.equal('user_id', 'Users.targetKey is invalid'); + + expect(Object.keys(UserPlace.getAttributes()).length).to.equal(3); // Defined primary key and two foreign keys + }); + }); + + describe('pseudo associations', () => { + it('should setup belongsTo relations to source and target from join model with defined foreign/other keys', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + foreignKey: 'productId', + otherKey: 'tagId', + }); + const TagProducts = Tag.belongsToMany(Product, { + through: ProductTag, + foreignKey: 'tagId', + otherKey: 'productId', + }); + + expect(ProductTags.fromThroughToSource).to.be.an.instanceOf(BelongsToAssociation); + expect(ProductTags.fromThroughToTarget).to.be.an.instanceOf(BelongsToAssociation); + + expect(TagProducts.fromThroughToSource).to.be.an.instanceOf(BelongsToAssociation); + expect(TagProducts.fromThroughToTarget).to.be.an.instanceOf(BelongsToAssociation); + + expect(ProductTags.fromThroughToSource.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromThroughToTarget.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromThroughToSource.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromThroughToTarget.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'productId', 'tagId'].sort(), + ); + }); + + it('should setup hasMany relations to source and target from join model with defined foreign/other keys', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + foreignKey: 'productId', + otherKey: 'tagId', + }); + const TagProducts = Tag.belongsToMany(Product, { + through: ProductTag, + foreignKey: 'tagId', + otherKey: 'productId', + }); + + expect(ProductTags.fromSourceToThrough).to.be.an.instanceOf(HasManyAssociation); + expect(ProductTags.fromTargetToThrough).to.be.an.instanceOf(HasManyAssociation); + + expect(TagProducts.fromSourceToThrough).to.be.an.instanceOf(HasManyAssociation); + expect(TagProducts.fromTargetToThrough).to.be.an.instanceOf(HasManyAssociation); + + expect(ProductTags.fromSourceToThrough.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromTargetToThrough.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromSourceToThrough.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromTargetToThrough.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'tagId', 'productId'].sort(), + ); + }); + + it('should setup hasOne relations to source and target from join model with defined foreign/other keys', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + foreignKey: 'productId', + otherKey: 'tagId', + }); + const TagProducts = Tag.belongsToMany(Product, { + through: ProductTag, + foreignKey: 'tagId', + otherKey: 'productId', + }); + + expect(ProductTags.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(ProductTags.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(TagProducts.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(TagProducts.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(ProductTags.fromSourceToThroughOne.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromTargetToThroughOne.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromSourceToThroughOne.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromTargetToThroughOne.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'productId', 'tagId'].sort(), + ); + }); + + it('should setup hasOne relations to source and target from join model with defined source keys', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + productSecondaryId: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + tagSecondaryId: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + sourceKey: 'productSecondaryId', + targetKey: 'tagSecondaryId', + }); + const TagProducts = Tag.getAssociation('products') as BelongsToManyAssociation; + + expect(ProductTags.foreignKey).to.equal( + 'productProductSecondaryId', + 'generated foreign key for source name (product) + source key (productSecondaryId) should result in productProductSecondaryId', + ); + expect(TagProducts.foreignKey).to.equal('tagTagSecondaryId'); + + expect(ProductTags.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(ProductTags.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(TagProducts.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(TagProducts.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(TagProducts.fromSourceToThroughOne.sourceKey).to.equal(TagProducts.sourceKey); + expect(TagProducts.fromTargetToThroughOne.sourceKey).to.equal(TagProducts.targetKey); + + expect(ProductTags.fromSourceToThroughOne.sourceKey).to.equal(ProductTags.sourceKey); + expect(ProductTags.fromTargetToThroughOne.sourceKey).to.equal(ProductTags.targetKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'productProductSecondaryId', 'tagTagSecondaryId'].sort(), + ); + }); + + it('should setup belongsTo relations to source and target from join model with only foreign keys defined', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + foreignKey: 'product_ID', + otherKey: 'tag_ID', + }); + const TagProducts = Tag.getAssociation('products') as BelongsToManyAssociation; + + expect(ProductTags.fromThroughToSource).to.be.ok; + expect(ProductTags.fromThroughToTarget).to.be.ok; + + expect(TagProducts.fromThroughToSource).to.be.ok; + expect(TagProducts.fromThroughToTarget).to.be.ok; + + expect(ProductTags.fromThroughToSource.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromThroughToTarget.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromThroughToSource.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromThroughToTarget.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'product_ID', 'tag_ID'].sort(), + ); + }); + + it('should setup hasOne relations to source and target from join model with only foreign keys defined', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { + through: ProductTag, + foreignKey: 'product_ID', + otherKey: 'tag_ID', + }); + const TagProducts = Tag.getAssociation('products') as BelongsToManyAssociation; + + expect(ProductTags.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(ProductTags.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(TagProducts.fromSourceToThroughOne).to.be.an.instanceOf(HasOneAssociation); + expect(TagProducts.fromTargetToThroughOne).to.be.an.instanceOf(HasOneAssociation); + + expect(ProductTags.fromSourceToThroughOne.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromTargetToThroughOne.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromSourceToThroughOne.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromTargetToThroughOne.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'product_ID', 'tag_ID'].sort(), + ); + }); + + it('should setup belongsTo relations to source and target from join model with no foreign keys defined', () => { + const Product = sequelize.define('Product', { + title: DataTypes.STRING, + }); + const Tag = sequelize.define('Tag', { + name: DataTypes.STRING, + }); + const ProductTag = sequelize.define( + 'ProductTag', + { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true, + }, + priority: DataTypes.INTEGER, + }, + { + timestamps: false, + }, + ); + + const ProductTags = Product.belongsToMany(Tag, { through: ProductTag }); + const TagProducts = Tag.belongsToMany(Product, { through: ProductTag }); + + expect(ProductTags.fromThroughToSource).to.be.ok; + expect(ProductTags.fromThroughToTarget).to.be.ok; + + expect(TagProducts.fromThroughToSource).to.be.ok; + expect(TagProducts.fromThroughToTarget).to.be.ok; + + expect(ProductTags.fromThroughToSource.foreignKey).to.equal(ProductTags.foreignKey); + expect(ProductTags.fromThroughToTarget.foreignKey).to.equal(ProductTags.otherKey); + + expect(TagProducts.fromThroughToSource.foreignKey).to.equal(TagProducts.foreignKey); + expect(TagProducts.fromThroughToTarget.foreignKey).to.equal(TagProducts.otherKey); + + expect(Object.keys(ProductTag.getAttributes()).length).to.equal(4); + expect(Object.keys(ProductTag.getAttributes()).sort()).to.deep.equal( + ['id', 'priority', 'productId', 'tagId'].sort(), + ); + }); + }); + + describe('associations on the join table', () => { + const vars = beforeEach2(() => { + class User extends Model {} + + class Project extends Model {} + + class UserProject extends Model { + declare getUser: BelongsToGetAssociationMixin; + declare getProject: BelongsToGetAssociationMixin; + } + + sequelize.addModels([User, Project, UserProject]); + + User.belongsToMany(Project, { through: UserProject }); + Project.belongsToMany(User, { through: UserProject }); + + return { User, Project, UserProject }; + }); + + it('should work for belongsTo associations defined before belongsToMany', () => { + expect(vars.UserProject.prototype.getUser).to.be.ok; + }); + + it('should work for belongsTo associations defined after belongsToMany', () => { + expect(vars.UserProject.prototype.getProject).to.be.ok; + }); + }); + + describe('self-associations', () => { + it('does not pair multiple self associations with different through arguments', () => { + const User = sequelize.define('user', {}); + const UserFollower = sequelize.define('userFollowers', {}); + const Invite = sequelize.define('invite', {}); + + const UserFollowers = User.belongsToMany(User, { + as: 'Followers', + inverse: { + as: 'Followings', + }, + through: UserFollower, + }); + + const UserInvites = User.belongsToMany(User, { + as: 'Invites', + inverse: { + as: 'Inviters', + }, + foreignKey: 'InviteeId', + through: Invite, + }); + + expect(UserFollowers.pairedWith).not.to.eq(UserInvites); + expect(UserInvites.pairedWith).not.to.be.eq(UserFollowers); + + expect(UserFollowers.otherKey).not.to.equal(UserInvites.foreignKey); + }); + + it('correctly generates a foreign/other key when none are defined', () => { + const User = sequelize.define('user', {}); + const UserFollower = sequelize.define( + 'userFollowers', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }, + { + timestamps: false, + }, + ); + + const UserFollowers = User.belongsToMany(User, { + as: 'Followers', + inverse: { + as: 'Followings', + }, + through: UserFollower, + }); + + expect(UserFollowers.foreignKey).to.eq('followingId'); + expect(UserFollowers.otherKey).to.eq('followerId'); + + expect(Object.keys(UserFollower.getAttributes()).length).to.equal(3); + }); + + it('works with singular and plural name for self-associations', () => { + // Models taken from https://github.com/sequelize/sequelize/issues/3796 + const Service = sequelize.define('service', {}); + + Service.belongsToMany(Service, { + through: 'Supplements', + as: 'supplements', + inverse: { + as: { singular: 'supplemented', plural: 'supplemented' }, + }, + }); + + expect(Service.prototype).to.have.ownProperty('getSupplements').to.be.a('function'); + + expect(Service.prototype).to.have.ownProperty('addSupplement').to.be.a('function'); + expect(Service.prototype).to.have.ownProperty('addSupplements').to.be.a('function'); + + expect(Service.prototype).to.have.ownProperty('getSupplemented').to.be.a('function'); + expect(Service.prototype).not.to.have.ownProperty('getSupplementeds').to.be.a('function'); + + expect(Service.prototype).to.have.ownProperty('addSupplemented').to.be.a('function'); + expect(Service.prototype).not.to.have.ownProperty('addSupplementeds').to.be.a('function'); + }); + }); + + describe('constraints', () => { + it('work properly when through is a string', () => { + const User = sequelize.define('User', {}); + const Group = sequelize.define('Group', {}); + + User.belongsToMany(Group, { + as: 'MyGroups', + through: 'group_user', + foreignKey: { + onUpdate: 'RESTRICT', + onDelete: 'SET NULL', + }, + otherKey: { + onUpdate: 'SET NULL', + onDelete: 'RESTRICT', + }, + inverse: { + as: 'MyUsers', + }, + }); + + const MyUsers = Group.associations.MyUsers as BelongsToManyAssociation; + const MyGroups = User.associations.MyGroups as BelongsToManyAssociation; + const throughModel = MyUsers.through.model; + + expect(Object.keys(throughModel.getAttributes()).sort()).to.deep.equal( + ['userId', 'groupId', 'createdAt', 'updatedAt'].sort(), + ); + + expect(throughModel === MyGroups.through.model); + expect(throughModel.getAttributes().userId.onUpdate).to.equal('RESTRICT'); + expect(throughModel.getAttributes().userId.onDelete).to.equal('SET NULL'); + expect(throughModel.getAttributes().groupId.onUpdate).to.equal('SET NULL'); + expect(throughModel.getAttributes().groupId.onDelete).to.equal('RESTRICT'); + }); + + it('work properly when through is a model', () => { + const User = sequelize.define('User', {}); + const Group = sequelize.define('Group', {}); + const UserGroup = sequelize.define('GroupUser', {}, { tableName: 'user_groups' }); + + User.belongsToMany(Group, { + as: 'MyGroups', + through: UserGroup, + foreignKey: { + onUpdate: 'RESTRICT', + onDelete: 'SET NULL', + }, + otherKey: { + onUpdate: 'SET NULL', + onDelete: 'RESTRICT', + }, + inverse: { + as: 'MyUsers', + }, + }); + + const MyUsers = Group.associations.MyUsers as BelongsToManyAssociation; + const MyGroups = User.associations.MyGroups as BelongsToManyAssociation; + + expect(MyUsers.through.model === MyGroups.through.model); + + const Through = MyUsers.through.model; + + expect(Object.keys(Through.getAttributes()).sort()).to.deep.equal( + ['userId', 'groupId', 'createdAt', 'updatedAt'].sort(), + ); + + expect(Through.getAttributes().userId.onUpdate).to.equal( + 'RESTRICT', + 'UserId.onUpdate should have been RESTRICT', + ); + expect(Through.getAttributes().userId.onDelete).to.equal( + 'SET NULL', + 'UserId.onDelete should have been SET NULL', + ); + expect(Through.getAttributes().groupId.onUpdate).to.equal( + 'SET NULL', + 'GroupId.OnUpdate should have been SET NULL', + ); + expect(Through.getAttributes().groupId.onDelete).to.equal( + 'RESTRICT', + 'GroupId.onDelete should have been RESTRICT', + ); + }); + + it('makes the foreign keys primary keys', () => { + const User = sequelize.define('User', {}); + const Group = sequelize.define('Group', {}); + + const association = User.belongsToMany(Group, { + as: 'MyGroups', + through: 'GroupUser', + inverse: { + as: 'MyUsers', + }, + }); + + const Through = association.throughModel; + + expect(Object.keys(Through.getAttributes()).sort()).to.deep.equal( + ['createdAt', 'updatedAt', 'groupId', 'userId'].sort(), + ); + expect(Through.getAttributes().userId.primaryKey).to.be.true; + expect(Through.getAttributes().groupId.primaryKey).to.be.true; + // @ts-expect-error -- this property does not exist after normalization + expect(Through.getAttributes().userId.unique).to.be.undefined; + // @ts-expect-error -- this property does not exist after normalization + expect(Through.getAttributes().groupId.unique).to.be.undefined; + }); + + it('generates unique identifier with very long length', () => { + const User = sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }); + const Group = sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }); + const UserGroup = sequelize.define( + 'GroupUser', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + id_user_very_long_field: { + type: DataTypes.INTEGER(1), + }, + id_group_very_long_field: { + type: DataTypes.INTEGER(1), + }, + }, + { tableName: 'table_user_group_with_very_long_name' }, + ); + + User.belongsToMany(Group, { + as: 'MyGroups', + through: UserGroup, + foreignKey: 'id_user_very_long_field', + otherKey: 'id_group_very_long_field', + inverse: { + as: 'MyUsers', + }, + }); + + const MyUsers = Group.associations.MyUsers as BelongsToManyAssociation; + const MyGroups = User.associations.MyGroups as BelongsToManyAssociation; + + const Through = MyUsers.through.model; + expect(Through === MyGroups.through.model); + + expect(Object.keys(Through.getAttributes()).sort()).to.deep.equal( + [ + 'id', + 'createdAt', + 'updatedAt', + 'id_user_very_long_field', + 'id_group_very_long_field', + ].sort(), + ); + + expect(Through.getIndexes()).to.deep.equal([ + { + name: 'table_user_group_with_very_long_name_id_group_very_long_field_id_user_very_long_field_unique', + unique: true, + fields: ['id_user_very_long_field', 'id_group_very_long_field'], + column: 'id_user_very_long_field', + }, + ]); + + // @ts-expect-error -- this property does not exist after normalization + expect(Through.getAttributes().id_user_very_long_field.unique).to.be.undefined; + // @ts-expect-error -- this property does not exist after normalization + expect(Through.getAttributes().id_group_very_long_field.unique).to.be.undefined; + }); + + it('generates unique identifier with custom name', () => { + const User = sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }); + const Group = sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }); + const UserGroup = sequelize.define( + 'GroupUser', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + id_user_very_long_field: { + type: DataTypes.INTEGER(1), + }, + id_group_very_long_field: { + type: DataTypes.INTEGER(1), + }, + }, + { tableName: 'table_user_group_with_very_long_name' }, + ); + + User.belongsToMany(Group, { + as: 'MyGroups', + through: { + model: UserGroup, + unique: 'custom_user_group_unique', + }, + foreignKey: 'id_user_very_long_field', + otherKey: 'id_group_very_long_field', + inverse: { + as: 'MyUsers', + }, + }); + + const MyUsers = Group.associations.MyUsers as BelongsToManyAssociation; + const MyGroups = User.associations.MyGroups as BelongsToManyAssociation; + + expect(MyUsers.through.model === UserGroup); + expect(MyGroups.through.model === UserGroup); + + expect(UserGroup.getIndexes()).to.deep.equal([ + { + name: 'custom_user_group_unique', + unique: true, + fields: ['id_user_very_long_field', 'id_group_very_long_field'], + column: 'id_user_very_long_field', + }, + ]); + + // @ts-expect-error -- this property does not exist after normalization + expect(UserGroup.getAttributes().id_user_very_long_field.unique).to.be.undefined; + // @ts-expect-error -- this property does not exist after normalization + expect(UserGroup.getAttributes().id_group_very_long_field.unique).to.be.undefined; + }); + }); + + describe('association hooks', () => { + let Project: ModelStatic; + let Task: ModelStatic; + + beforeEach(() => { + Project = sequelize.define('Project', { title: DataTypes.STRING }); + Task = sequelize.define('Task', { title: DataTypes.STRING }); + }); + describe('beforeBelongsToManyAssociate', () => { + it('should trigger', () => { + const beforeAssociate = sinon.spy(); + Project.beforeAssociate(beforeAssociate); + Project.belongsToMany(Task, { through: 'projects_and_tasks', hooks: true }); + + const beforeAssociateArgs = beforeAssociate.getCall(0).args; + + expect(beforeAssociate).to.have.been.called; + expect(beforeAssociateArgs.length).to.equal(2); + + const firstArg = beforeAssociateArgs[0]; + expect(Object.keys(firstArg).join(',')).to.equal('source,target,type,sequelize'); + expect(firstArg.source).to.equal(Project); + expect(firstArg.target).to.equal(Task); + expect(firstArg.type.name).to.equal('BelongsToMany'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + it('should not trigger association hooks', () => { + const beforeAssociate = sinon.spy(); + Project.beforeAssociate(beforeAssociate); + Project.belongsToMany(Task, { through: 'projects_and_tasks', hooks: false }); + expect(beforeAssociate).to.not.have.been.called; + }); + }); + + describe('afterBelongsToManyAssociate', () => { + it('should trigger', () => { + const afterAssociate = sinon.spy(); + Project.afterAssociate(afterAssociate); + Project.belongsToMany(Task, { through: 'projects_and_tasks', hooks: true }); + + const afterAssociateArgs = afterAssociate.getCalls()[afterAssociate.callCount - 1].args; + + expect(afterAssociate).to.have.been.called; + expect(afterAssociateArgs.length).to.equal(2); + + const firstArg = afterAssociateArgs[0]; + expect(Object.keys(firstArg).join(',')).to.equal( + 'source,target,type,association,sequelize', + ); + expect(firstArg.source).to.equal(Project); + expect(firstArg.target).to.equal(Task); + expect(firstArg.type.name).to.equal('BelongsToMany'); + expect(firstArg.association.constructor.name).to.equal('BelongsToMany'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + + it('should not trigger association hooks', () => { + const afterAssociate = sinon.spy(); + Project.afterAssociate(afterAssociate); + Project.belongsToMany(Task, { through: 'projects_and_tasks', hooks: false }); + expect(afterAssociate).to.not.have.been.called; + }); + }); + }); +}); diff --git a/packages/core/test/unit/associations/belongs-to.test.ts b/packages/core/test/unit/associations/belongs-to.test.ts new file mode 100644 index 000000000000..cc7a9f1fc816 --- /dev/null +++ b/packages/core/test/unit/associations/belongs-to.test.ts @@ -0,0 +1,248 @@ +import type { CreationOptional, ModelStatic, NonAttribute } from '@sequelize/core'; +import { DataTypes, Deferrable, Model } from '@sequelize/core'; +import { BelongsTo } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import each from 'lodash/each'; +import sinon from 'sinon'; +import { getTestDialectTeaser, sequelize } from '../../support'; + +describe(getTestDialectTeaser('belongsTo'), () => { + it('throws when invalid model is passed', () => { + const User = sequelize.define('User'); + + expect(() => { + // @ts-expect-error -- testing that invalid input results in error + User.belongsTo(); + }).to.throw( + `User.belongsTo was called with undefined as the target model, but it is not a subclass of Sequelize's Model class`, + ); + }); + + it('warn on invalid options', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + expect(() => { + User.belongsTo(Task, { targetKey: 'wowow' }); + }).to.throwWithCause( + 'Unknown attribute "wowow" passed as targetKey, define this attribute on model "Task" first', + ); + }); + + it('should not override custom methods with association mixin', () => { + const methods = { + getTask: 'get', + setTask: 'set', + createTask: 'create', + }; + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + const initialMethod = function wrapper() {}; + + each(methods, (alias, method) => { + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + User.prototype[method] = initialMethod; + }); + + User.belongsTo(Task, { as: 'task' }); + + const user = User.build(); + + each(methods, (alias, method) => { + // @ts-expect-error -- dynamic type, not worth typing + expect(user[method]).to.eq(initialMethod); + }); + }); + + it('should throw an error if "foreignKey" and "as" result in a name clash', () => { + const Person = sequelize.define('person', {}); + const Car = sequelize.define('car', {}); + + expect(() => Car.belongsTo(Person, { foreignKey: 'person' })).to.throw( + "Naming collision between attribute 'person' and association 'person' on model car. To remedy this, change the \"as\" options in your association definition", + ); + }); + + it('should throw an error if an association clashes with the name of an already defined attribute', () => { + const Person = sequelize.define('person', {}); + const Car = sequelize.define('car', { + person: DataTypes.INTEGER, + }); + + expect(() => Car.belongsTo(Person, { as: 'person' })).to.throw( + "Naming collision between attribute 'person' and association 'person' on model car. To remedy this, change the \"as\" options in your association definition", + ); + }); + + it('generates a default association name', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + Task.belongsTo(User); + + expect(Object.keys(Task.associations)).to.deep.eq(['user']); + expect(Object.keys(User.associations)).to.deep.eq([]); + }); + + it('should add a nullable foreign key by default', () => { + const BarUser = sequelize.define('user'); + + const BarProject = sequelize.define('project'); + + BarProject.belongsTo(BarUser, { foreignKey: 'userId' }); + + expect(BarProject.getAttributes().userId.allowNull).to.eq( + undefined, + 'allowNull should be undefined', + ); + }); + + it('sets the foreign key default onDelete to CASCADE if allowNull: false', async () => { + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + const User = sequelize.define('User', { username: DataTypes.STRING }); + + Task.belongsTo(User, { foreignKey: { allowNull: false } }); + + expect(Task.getAttributes().userId.onDelete).to.eq('CASCADE'); + }); + + it(`does not overwrite the 'deferrable' option set in Model.init`, () => { + const A = sequelize.define('A', { + bId: { + type: DataTypes.INTEGER, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }, + }, + }); + + const B = sequelize.define('B'); + + A.belongsTo(B); + + expect(A.getAttributes().bId.references?.deferrable).to.equal(Deferrable.INITIALLY_IMMEDIATE); + }); + + // See https://github.com/sequelize/sequelize/issues/15625 for more details + it('should be possible to define two belongsTo associations with the same target #15625', () => { + class Post extends Model { + declare id: CreationOptional; + + @BelongsTo(() => Author, { + foreignKey: 'authorId', + targetKey: 'id', + inverse: { as: 'myBooks', type: 'hasMany' }, + }) + declare author: NonAttribute; + + declare authorId: number; + + @BelongsTo(() => Author, { + foreignKey: 'coAuthorId', + targetKey: 'id', + inverse: { as: 'notMyBooks', type: 'hasMany' }, + }) + declare coAuthor: NonAttribute; + + declare coAuthorId: number; + } + + class Author extends Model { + declare id: number; + } + + // This would previously fail because the BelongsTo association would create an hasMany association which would + // then try to create a redundant belongsTo association + sequelize.addModels([Post, Author]); + }); + + it(`uses the model's singular name to generate the foreign key name`, () => { + const Book = sequelize.define( + 'Book', + {}, + { + name: { + singular: 'Singular', + plural: 'Plural', + }, + }, + ); + + const Log = sequelize.define('Log', {}, {}); + + Log.belongsTo(Book); + + expect(Log.getAttributes().pluralId).to.not.exist; + expect(Log.getAttributes().singularId).to.exist; + }); + + describe('association hooks', () => { + let Projects: ModelStatic; + let Tasks: ModelStatic; + + beforeEach(() => { + Projects = sequelize.define('Project', { title: DataTypes.STRING }); + Tasks = sequelize.define('Task', { title: DataTypes.STRING }); + }); + + describe('beforeAssociate', () => { + it('should trigger', () => { + const beforeAssociate = sinon.spy(); + Projects.beforeAssociate(beforeAssociate); + Projects.belongsTo(Tasks, { hooks: true }); + + const beforeAssociateArgs = beforeAssociate.getCall(0).args; + + expect(beforeAssociate).to.have.been.called; + expect(beforeAssociateArgs.length).to.equal(2); + + const firstArg = beforeAssociateArgs[0]; + expect(Object.keys(firstArg).join(',')).to.equal('source,target,type,sequelize'); + expect(firstArg.source).to.equal(Projects); + expect(firstArg.target).to.equal(Tasks); + expect(firstArg.type.name).to.equal('BelongsTo'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + it('should not trigger association hooks', () => { + const beforeAssociate = sinon.spy(); + Projects.beforeAssociate(beforeAssociate); + Projects.belongsTo(Tasks, { hooks: false }); + expect(beforeAssociate).to.not.have.been.called; + }); + }); + + describe('afterAssociate', () => { + it('should trigger', () => { + const afterAssociate = sinon.spy(); + Projects.afterAssociate(afterAssociate); + Projects.belongsTo(Tasks, { hooks: true }); + + const afterAssociateArgs = afterAssociate.getCall(0).args; + + expect(afterAssociate).to.have.been.called; + expect(afterAssociateArgs.length).to.equal(2); + + const firstArg = afterAssociateArgs[0]; + + expect(Object.keys(firstArg).join(',')).to.equal( + 'source,target,type,association,sequelize', + ); + expect(firstArg.source).to.equal(Projects); + expect(firstArg.target).to.equal(Tasks); + expect(firstArg.type.name).to.equal('BelongsTo'); + expect(firstArg.association.constructor.name).to.equal('BelongsTo'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + + it('should not trigger association hooks', () => { + const afterAssociate = sinon.spy(); + Projects.afterAssociate(afterAssociate); + Projects.belongsTo(Tasks, { hooks: false }); + expect(afterAssociate).to.not.have.been.called; + }); + }); + }); +}); diff --git a/packages/core/test/unit/associations/dont-modify-options.test.js b/packages/core/test/unit/associations/dont-modify-options.test.js new file mode 100644 index 000000000000..e6120a576c67 --- /dev/null +++ b/packages/core/test/unit/associations/dont-modify-options.test.js @@ -0,0 +1,59 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('associations'), () => { + describe('Test options.foreignKey', () => { + beforeEach(function () { + this.A = this.sequelize.define('A', { + id: { + type: DataTypes.STRING(20), + primaryKey: true, + }, + }); + this.B = this.sequelize.define('B', { + id: { + type: DataTypes.STRING(20), + primaryKey: true, + }, + }); + this.C = this.sequelize.define('C', {}); + }); + + it('should not be overwritten for belongsTo', function () { + const reqValidForeignKey = { foreignKey: { allowNull: false } }; + this.A.belongsTo(this.B, reqValidForeignKey); + this.A.belongsTo(this.C, reqValidForeignKey); + + expect(this.A.getAttributes().cId.type instanceof this.C.getAttributes().id.type.constructor); + }); + + it('should not be overwritten for belongsToMany', function () { + const reqValidForeignKey = { foreignKey: { allowNull: false }, through: 'ABBridge' }; + this.B.belongsToMany(this.A, reqValidForeignKey); + this.A.belongsTo(this.C, reqValidForeignKey); + + expect(this.A.getAttributes().cId.type instanceof this.C.getAttributes().id.type.constructor); + }); + + it('should not be overwritten for hasOne', function () { + const reqValidForeignKey = { foreignKey: { allowNull: false } }; + this.B.hasOne(this.A, reqValidForeignKey); + this.A.belongsTo(this.C, reqValidForeignKey); + + expect(this.A.getAttributes().cId.type instanceof this.C.getAttributes().id.type.constructor); + }); + + it('should not be overwritten for hasMany', function () { + const reqValidForeignKey = { foreignKey: { allowNull: false } }; + this.B.hasMany(this.A, reqValidForeignKey); + this.A.belongsTo(this.C, reqValidForeignKey); + + expect(this.A.getAttributes().cId.type instanceof this.C.getAttributes().id.type.constructor); + }); + }); +}); diff --git a/packages/core/test/unit/associations/has-many.test.ts b/packages/core/test/unit/associations/has-many.test.ts new file mode 100644 index 000000000000..4e3477b37767 --- /dev/null +++ b/packages/core/test/unit/associations/has-many.test.ts @@ -0,0 +1,425 @@ +import type { ForeignKey, HasManySetAssociationsMixin, InferAttributes } from '@sequelize/core'; +import { DataTypes, Model, Op } from '@sequelize/core'; +import { expect } from 'chai'; +import each from 'lodash/each'; +import type { SinonStub } from 'sinon'; +import sinon from 'sinon'; +import { beforeAll2, getTestDialectTeaser, sequelize } from '../../support'; + +describe(getTestDialectTeaser('hasMany'), () => { + it('throws when invalid model is passed', () => { + const User = sequelize.define('User'); + + expect(() => { + // @ts-expect-error -- testing that invalid input results in error + User.hasMany(); + }).to.throw( + `User.hasMany was called with undefined as the target model, but it is not a subclass of Sequelize's Model class`, + ); + }); + + it('forbids alias inference in self-associations', () => { + const User = sequelize.define('User'); + + expect(() => { + User.hasMany(User); + }).to.throwWithCause( + 'Both options "as" and "inverse.as" must be defined for hasMany self-associations, and their value must be different', + ); + }); + + it('allows self-associations with explicit alias', () => { + const Category = sequelize.define('Category'); + + Category.hasMany(Category, { as: 'childCategories', inverse: { as: 'parentCategory' } }); + }); + + it('allows customizing the inverse association name (long form)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.hasMany(Task, { as: 'tasks', inverse: { as: 'user' } }); + + expect(Task.associations.user).to.be.ok; + expect(User.associations.tasks).to.be.ok; + }); + + it('allows customizing the inverse association name (shorthand)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.hasMany(Task, { as: 'tasks', inverse: 'user' }); + + expect(Task.associations.user).to.be.ok; + expect(User.associations.tasks).to.be.ok; + }); + + it('generates a default association name', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + User.hasMany(Task); + + expect(Object.keys(Task.associations)).to.deep.eq(['user']); + expect(Object.keys(User.associations)).to.deep.eq(['tasks']); + }); + + describe('optimizations using bulk create, destroy and update', () => { + const vars = beforeAll2(() => { + class User extends Model> { + declare setTasks: HasManySetAssociationsMixin; + } + + class Task extends Model> {} + + User.init({ username: DataTypes.STRING }, { sequelize }); + Task.init({ title: DataTypes.STRING }, { sequelize }); + User.hasMany(Task); + + const user = User.build({ + id: 42, + }); + const task1 = Task.build({ + id: 15, + }); + const task2 = Task.build({ + id: 16, + }); + + return { User, Task, user, task1, task2 }; + }); + + let findAll: SinonStub; + let update: SinonStub; + + beforeEach(() => { + const { Task } = vars; + + findAll = sinon.stub(Task, 'findAll').resolves([]); + update = sinon.stub(Task, 'update').resolves([0]); + }); + + afterEach(() => { + findAll.restore(); + update.restore(); + }); + + it('uses one update statement for addition', async () => { + const { user, task1, task2 } = vars; + + await user.setTasks([task1, task2]); + expect(findAll).to.have.been.calledOnce; + expect(update).to.have.been.calledOnce; + }); + + it('uses one delete from statement', async () => { + const { user, task1, task2 } = vars; + + findAll + .onFirstCall() + .resolves([]) + .onSecondCall() + .resolves([ + { userId: 42, taskId: 15 }, + { userId: 42, taskId: 16 }, + ]); + + await user.setTasks([task1, task2]); + update.resetHistory(); + await user.setTasks([]); + expect(findAll).to.have.been.calledTwice; + expect(update).to.have.been.calledOnce; + }); + }); + + describe('mixin', () => { + const vars = beforeAll2(() => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + return { User, Task }; + }); + + it('should mixin association methods', () => { + const { User, Task } = vars; + + const as = Math.random().toString(); + const association = User.hasMany(Task, { as }); + + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.get]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.set]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.addMultiple]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.add]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.remove]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.removeMultiple]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.hasSingle]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.hasAll]).to.be.a('function'); + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + expect(User.prototype[association.accessors.count]).to.be.a('function'); + }); + + it('should not override custom methods', () => { + const { User, Task } = vars; + + const methods = { + getTasks: 'get', + countTasks: 'count', + hasTask: 'has', + hasTasks: 'has', + setTasks: 'set', + addTask: 'add', + addTasks: 'add', + removeTask: 'remove', + removeTasks: 'remove', + createTask: 'create', + }; + + function originalMethod() {} + + each(methods, (alias, method) => { + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + User.prototype[method] = originalMethod; + }); + + User.hasMany(Task, { as: 'task' }); + + const user = User.build(); + + each(methods, (alias, method) => { + // @ts-expect-error -- dynamic type, not worth typing + expect(user[method]).to.eq(originalMethod); + }); + }); + + it('should not override attributes', () => { + const { Task } = vars; + + class Project extends Model> { + declare hasTasks: boolean | null; + } + + Project.init( + { + hasTasks: DataTypes.BOOLEAN, + }, + { sequelize }, + ); + + Project.hasMany(Task); + + const project = Project.build(); + + expect(project.hasTasks).not.to.be.a('function'); + }); + }); + + describe('get', () => { + function getModels() { + class User extends Model> {} + + class Task extends Model> { + declare user_id: ForeignKey; + } + + User.init( + { + id: { + type: DataTypes.STRING, + primaryKey: true, + }, + }, + { sequelize }, + ); + Task.init({}, { sequelize }); + + return { Task, User }; + } + + const idA = Math.random().toString(); + const idB = Math.random().toString(); + const idC = Math.random().toString(); + const foreignKey = 'user_id'; + + it('should fetch associations for a single instance', async () => { + const { Task, User } = getModels(); + + const findAll = sinon.stub(Task, 'findAll').resolves([Task.build({}), Task.build({})]); + + const UserTasks = User.hasMany(Task, { foreignKey }); + const actual = UserTasks.get(User.build({ id: idA })); + + const where = { + [foreignKey]: idA, + }; + + expect(findAll).to.have.been.calledOnce; + expect(findAll.firstCall.args[0]?.where).to.deep.equal(where); + + try { + const results = await actual; + expect(results).to.be.an('array'); + expect(results.length).to.equal(2); + } finally { + findAll.restore(); + } + }); + + it('should fetch associations for multiple source instances', async () => { + const { Task, User } = getModels(); + + const UserTasks = User.hasMany(Task, { foreignKey }); + + const findAll = sinon.stub(Task, 'findAll').returns( + Promise.resolve([ + Task.build({ + user_id: idA, + }), + Task.build({ + user_id: idA, + }), + Task.build({ + user_id: idA, + }), + Task.build({ + user_id: idB, + }), + ]), + ); + + const actual = UserTasks.get([ + User.build({ id: idA }), + User.build({ id: idB }), + User.build({ id: idC }), + ]); + + expect(findAll).to.have.been.calledOnce; + expect(findAll.firstCall.args[0]?.where).to.have.property(foreignKey); + // @ts-expect-error -- not worth typing for this test + expect(findAll.firstCall.args[0]?.where[foreignKey]).to.have.property(Op.in); + // @ts-expect-error -- not worth typing for this test + expect(findAll.firstCall.args[0]?.where[foreignKey][Op.in]).to.deep.equal([idA, idB, idC]); + + try { + const result = await actual; + expect(result).to.be.instanceOf(Map); + expect([...result.keys()]).to.deep.equal([idA, idB, idC]); + + expect(result.get(idA)?.length).to.equal(3); + expect(result.get(idB)?.length).to.equal(1); + expect(result.get(idC)?.length).to.equal(0); + } finally { + findAll.restore(); + } + }); + }); + + describe('association hooks', () => { + function getModels() { + class Project extends Model> { + declare title: string | null; + } + + class Task extends Model> { + declare user_id: ForeignKey; + declare title: string | null; + } + + Project.init({ title: DataTypes.STRING }, { sequelize }); + Task.init({ title: DataTypes.STRING }, { sequelize }); + + return { Task, Project }; + } + + describe('beforeHasManyAssociate', () => { + it('should trigger', () => { + const { Task, Project } = getModels(); + + const beforeAssociate = sinon.spy(); + Project.beforeAssociate(beforeAssociate); + Project.hasMany(Task, { hooks: true }); + + const beforeAssociateArgs = beforeAssociate.getCall(0).args; + + expect(beforeAssociate).to.have.been.called; + expect(beforeAssociateArgs.length).to.equal(2); + + const firstArg = beforeAssociateArgs[0]; + expect(Object.keys(firstArg).join(',')).to.equal('source,target,type,sequelize'); + expect(firstArg.source).to.equal(Project); + expect(firstArg.target).to.equal(Task); + expect(firstArg.type.name).to.equal('HasMany'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + + it('should not trigger association hooks', () => { + const { Task, Project } = getModels(); + + const beforeAssociate = sinon.spy(); + Project.beforeAssociate(beforeAssociate); + Project.hasMany(Task, { hooks: false }); + expect(beforeAssociate).to.not.have.been.called; + }); + }); + + describe('afterHasManyAssociate', () => { + it('should trigger', () => { + const { Task, Project } = getModels(); + + const afterAssociate = sinon.spy(); + Project.afterAssociate(afterAssociate); + Project.hasMany(Task, { hooks: true }); + + const afterAssociateArgs = afterAssociate.getCall(0).args; + + expect(afterAssociate).to.have.been.called; + + const firstArg = afterAssociateArgs[0]; + + expect(Object.keys(firstArg).join(',')).to.equal( + 'source,target,type,association,sequelize', + ); + expect(firstArg.source).to.equal(Project); + expect(firstArg.target).to.equal(Task); + expect(firstArg.type.name).to.equal('HasMany'); + expect(firstArg.association.constructor.name).to.equal('HasMany'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + it('should not trigger association hooks', () => { + const { Task, Project } = getModels(); + + const afterAssociate = sinon.spy(); + Project.afterAssociate(afterAssociate); + Project.hasMany(Task, { hooks: false }); + expect(afterAssociate).to.not.have.been.called; + }); + }); + }); +}); diff --git a/packages/core/test/unit/associations/has-one.test.ts b/packages/core/test/unit/associations/has-one.test.ts new file mode 100644 index 000000000000..8d018fec526f --- /dev/null +++ b/packages/core/test/unit/associations/has-one.test.ts @@ -0,0 +1,305 @@ +import type { ModelStatic } from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import each from 'lodash/each'; +import assert from 'node:assert'; +import sinon from 'sinon'; +import { getTestDialectTeaser, sequelize } from '../../support'; + +describe(getTestDialectTeaser('hasOne'), () => { + it('throws when invalid model is passed', () => { + const User = sequelize.define('User'); + + expect(() => { + // @ts-expect-error -- testing that invalid input results in error + User.hasOne(); + }).to.throw( + `User.hasOne was called with undefined as the target model, but it is not a subclass of Sequelize's Model class`, + ); + }); + + it('warn on invalid options', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + expect(() => { + User.hasOne(Task, { sourceKey: 'wowow' }); + }).to.throwWithCause( + 'Unknown attribute "wowow" passed as sourceKey, define this attribute on model "User" first', + ); + }); + + it('forbids alias inference in self-associations', () => { + const User = sequelize.define('User'); + + expect(() => { + User.hasOne(User); + }).to.throwWithCause( + 'Both options "as" and "inverse.as" must be defined for hasOne self-associations, and their value must be different', + ); + }); + + it('allows self-associations with explicit alias', () => { + const User = sequelize.define('User'); + + // this would make more sense as a belongsTo(User, { as: 'mother', inverse: { type: 'many', as: 'children' } }) + User.hasOne(User, { as: 'mother', inverse: { as: 'child' } }); + }); + + it('allows customizing the inverse association name (long form)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.hasMany(Task, { as: 'task', inverse: { as: 'user' } }); + + expect(Task.associations.user).to.be.ok; + expect(User.associations.task).to.be.ok; + }); + + it('allows customizing the inverse association name (shorthand)', () => { + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + User.hasMany(Task, { as: 'task', inverse: 'user' }); + + expect(Task.associations.user).to.be.ok; + expect(User.associations.task).to.be.ok; + }); + + it('generates a default association name', () => { + const User = sequelize.define('User', {}); + const Task = sequelize.define('Task', {}); + + User.hasOne(Task); + + expect(Object.keys(Task.associations)).to.deep.eq(['user']); + expect(Object.keys(User.associations)).to.deep.eq(['task']); + }); + + it('does not use `as` option to generate foreign key name', () => { + // See HasOne.inferForeignKey for explanations as to why "as" is not used when inferring the foreign key. + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + + const association1 = User.hasOne(Task); + expect(association1.foreignKey).to.equal('userId'); + expect(Task.getAttributes().userId).not.to.be.empty; + + const association2 = User.hasOne(Task, { as: 'Shabda' }); + expect(association2.foreignKey).to.equal('userId'); + expect(Task.getAttributes().userId).not.to.be.empty; + }); + + it('should not override custom methods with association mixin', () => { + const methods = { + getTask: 'get', + setTask: 'set', + createTask: 'create', + }; + const User = sequelize.define('User'); + const Task = sequelize.define('Task'); + + function originalFunction() {} + + each(methods, (alias, method) => { + // TODO: remove this eslint-disable once we drop support for TypeScript <= 5.3 + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error + // @ts-ignore -- This only became invalid starting with TS 5.4 + User.prototype[method] = originalFunction; + }); + + User.hasOne(Task, { as: 'task' }); + + const user = User.build(); + + each(methods, (alias, method) => { + // @ts-expect-error -- dynamic type, not worth typing + expect(user[method]).to.eq(originalFunction); + }); + }); + + describe('allows the user to provide an attribute definition object as foreignKey', () => { + it(`works with a column that hasn't been defined before`, () => { + const User = sequelize.define('user', {}); + const Profile = sequelize.define('project', {}); + + User.hasOne(Profile, { + foreignKey: { + allowNull: false, + name: 'uid', + }, + }); + + expect(Profile.getAttributes().uid).to.be.ok; + + const model = Profile.getAttributes().uid.references?.table; + assert(typeof model === 'object'); + + expect(model).to.deep.equal(User.table); + + expect(Profile.getAttributes().uid.references?.key).to.equal('id'); + expect(Profile.getAttributes().uid.allowNull).to.be.false; + }); + + it('works when taking a column directly from the object', () => { + const User = sequelize.define('user', { + uid: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + const Profile = sequelize.define('project', { + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + }); + + User.hasOne(Profile, { foreignKey: Profile.getAttributes().user_id }); + + expect(Profile.getAttributes().user_id).to.be.ok; + const targetTable = Profile.getAttributes().user_id.references?.table; + assert(typeof targetTable === 'object'); + + expect(targetTable).to.deep.equal(User.table); + expect(Profile.getAttributes().user_id.references?.key).to.equal('uid'); + expect(Profile.getAttributes().user_id.allowNull).to.be.false; + }); + + it('works when merging with an existing definition', () => { + const User = sequelize.define('user', { + uid: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + const Project = sequelize.define('project', { + userUid: { + type: DataTypes.INTEGER, + defaultValue: 42, + }, + }); + + User.hasOne(Project, { foreignKey: { allowNull: false } }); + + expect(Project.getAttributes().userUid).to.be.ok; + expect(Project.getAttributes().userUid.allowNull).to.be.false; + const targetTable = Project.getAttributes().userUid.references?.table; + assert(typeof targetTable === 'object'); + + expect(targetTable).to.deep.equal(User.table); + expect(Project.getAttributes().userUid.references?.key).to.equal('uid'); + expect(Project.getAttributes().userUid.defaultValue).to.equal(42); + }); + }); + + it('sets the foreign key default onDelete to CASCADE if allowNull: false', async () => { + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + const User = sequelize.define('User', { username: DataTypes.STRING }); + + User.hasOne(Task, { foreignKey: { allowNull: false } }); + + expect(Task.getAttributes().userId.onDelete).to.eq('CASCADE'); + }); + + it('should throw an error if an association clashes with the name of an already define attribute', () => { + const User = sequelize.define('user', { + attribute: DataTypes.STRING, + }); + const Attribute = sequelize.define('attribute', {}); + + expect(User.hasOne.bind(User, Attribute)).to.throw( + "Naming collision between attribute 'attribute' and association 'attribute' on model user. To remedy this, change the \"as\" options in your association definition", + ); + }); + + describe('Model.associations', () => { + it('should store all associations when associating to the same table multiple times', () => { + const User = sequelize.define('User', {}); + const Group = sequelize.define('Group', {}); + + Group.hasOne(User); + Group.hasOne(User, { + foreignKey: 'primaryGroupId', + as: 'primaryUsers', + inverse: { as: 'primaryGroup' }, + }); + Group.hasOne(User, { + foreignKey: 'secondaryGroupId', + as: 'secondaryUsers', + inverse: { as: 'secondaryGroup' }, + }); + + expect(Object.keys(Group.associations)).to.deep.equal([ + 'user', + 'primaryUsers', + 'secondaryUsers', + ]); + }); + }); + + describe('association hooks', () => { + let Projects: ModelStatic; + let Tasks: ModelStatic; + + beforeEach(() => { + Projects = sequelize.define('Project', { title: DataTypes.STRING }); + Tasks = sequelize.define('Task', { title: DataTypes.STRING }); + }); + + describe('beforeHasOneAssociate', () => { + it('should trigger', () => { + const beforeAssociate = sinon.spy(); + Projects.beforeAssociate(beforeAssociate); + Projects.hasOne(Tasks, { hooks: true }); + + const beforeAssociateArgs = beforeAssociate.getCall(0).args; + + expect(beforeAssociate).to.have.been.called; + expect(beforeAssociateArgs.length).to.equal(2); + + const firstArg = beforeAssociateArgs[0]; + expect(Object.keys(firstArg).join(',')).to.equal('source,target,type,sequelize'); + expect(firstArg.source).to.equal(Projects); + expect(firstArg.target).to.equal(Tasks); + expect(firstArg.type.name).to.equal('HasOne'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + it('should not trigger association hooks', () => { + const beforeAssociate = sinon.spy(); + Projects.beforeAssociate(beforeAssociate); + Projects.hasOne(Tasks, { hooks: false }); + expect(beforeAssociate).to.not.have.been.called; + }); + }); + describe('afterHasOneAssociate', () => { + it('should trigger', () => { + const afterAssociate = sinon.spy(); + Projects.afterAssociate(afterAssociate); + Projects.hasOne(Tasks, { hooks: true }); + + const afterAssociateArgs = afterAssociate.getCall(0).args; + + expect(afterAssociate).to.have.been.called; + expect(afterAssociateArgs.length).to.equal(2); + + const firstArg = afterAssociateArgs[0]; + + expect(Object.keys(firstArg).join(',')).to.equal( + 'source,target,type,association,sequelize', + ); + expect(firstArg.source).to.equal(Projects); + expect(firstArg.target).to.equal(Tasks); + expect(firstArg.type.name).to.equal('HasOne'); + expect(firstArg.association.constructor.name).to.equal('HasOne'); + expect(firstArg.sequelize.constructor.name).to.equal('Sequelize'); + }); + it('should not trigger association hooks', () => { + const afterAssociate = sinon.spy(); + Projects.afterAssociate(afterAssociate); + Projects.hasOne(Tasks, { hooks: false }); + expect(afterAssociate).to.not.have.been.called; + }); + }); + }); +}); diff --git a/packages/core/test/unit/configuration.test.ts b/packages/core/test/unit/configuration.test.ts new file mode 100644 index 000000000000..5aae50908c46 --- /dev/null +++ b/packages/core/test/unit/configuration.test.ts @@ -0,0 +1,170 @@ +import { Sequelize } from '@sequelize/core'; +import { PostgresDialect } from '@sequelize/postgres'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + allowDeprecationsInSuite, + createSequelizeInstance, + getTestDialectClass, + sequelize, +} from '../support'; + +const dialect = getTestDialectClass(); +const dialectName = sequelize.dialect.name; + +describe('Sequelize constructor', () => { + allowDeprecationsInSuite(['SEQUELIZE0027']); + + it('throws when no dialect is supplied', () => { + expect(() => { + // @ts-expect-error -- testing that this throws when the "dialect" option is missing + new Sequelize({}); + }).to.throw(Error); + }); + + it('throws when an invalid dialect is supplied', () => { + expect(() => { + // @ts-expect-error -- testing that this throws + new Sequelize({ dialect: 'some-fancy-dialect' }); + }).to.throw( + Error, + 'The dialect some-fancy-dialect is not natively supported. Native dialects: mariadb, mssql, mysql, postgres, sqlite3, ibmi, db2 and snowflake.', + ); + }); + + it('works when dialect is supplied', () => { + expect(() => { + new Sequelize({ + dialect, + }); + }).not.to.throw(); + }); + + it('throws if pool:false', () => { + expect(() => { + new Sequelize({ + dialect, + // @ts-expect-error -- we're testing that this throws an error + pool: false, + }); + }).to.throw( + 'Setting the "pool" option to "false" is not supported since Sequelize 4. To disable the pool, set the "pool"."max" option to 1.', + ); + }); + + it('warns if the database version is not supported', () => { + const stub = sinon.stub(process, 'emitWarning'); + try { + createSequelizeInstance({ databaseVersion: '0.0.1' }); + expect(stub.getCalls()[0].args[0]).to.contain( + 'This database engine version is not supported, please update your database server.', + ); + } finally { + stub.restore(); + } + }); + + describe('Network Connections', () => { + // This test suite runs only for postgres but the logic is the same for every dialect + if (dialectName !== 'postgres') { + return; + } + + it('should correctly set the host and the port', () => { + const localSequelize = new Sequelize({ + dialect: PostgresDialect, + host: '127.0.0.1', + port: 1234, + }); + expect(localSequelize.options.replication.write.port).to.equal(1234); + expect(localSequelize.options.replication.write.host).to.equal('127.0.0.1'); + }); + + it('accepts a single URI parameter', () => { + const newSequelize = new Sequelize({ + dialect: PostgresDialect, + url: `${dialectName}://user:pass@example.com:9821/dbname`, + }); + + const replication = newSequelize.options.replication; + + expect(replication.write).to.deep.eq({ + database: 'dbname', + user: 'user', + password: 'pass', + host: 'example.com', + port: 9821, + }); + + expect(replication.read).to.deep.eq([]); + }); + + it('supports not providing username, password, or port in URI, but providing them in the option bag', () => { + const options = { + port: 10, + user: 'root', + password: 'pass', + database: 'dbname', + }; + + const newSequelize = new Sequelize({ + dialect: PostgresDialect, + url: `${dialectName}://example.com/dbname`, + ...options, + }); + + expect(newSequelize.options.replication.write).to.deep.eq({ + host: 'example.com', + ...options, + }); + }); + + it('supports connection strings in replication options', async () => { + const url = `${dialectName}://username:password@host:1234/database`; + + const newSequelize = new Sequelize({ + dialect: PostgresDialect, + replication: { + write: url, + read: [url], + }, + }); + + const options = { + host: 'host', + database: 'database', + port: 1234, + user: 'username', + password: 'password', + }; + + expect(newSequelize.options.replication.write).to.deep.eq(options); + expect(newSequelize.options.replication.read).to.deep.eq([options]); + }); + + it('prioritizes the option bag over the URI', () => { + const newSequelize = new Sequelize({ + dialect: PostgresDialect, + url: `${dialectName}://localhost:9821/dbname?ssl=true`, + host: 'localhost2', + user: 'username2', + password: 'password2', + ssl: false, + port: 2000, + database: 'dbname2', + }); + + const replication = newSequelize.options.replication; + + expect(replication.write).to.deep.eq({ + database: 'dbname2', + host: 'localhost2', + password: 'password2', + port: 2000, + ssl: false, + user: 'username2', + }); + expect(replication.read).to.deep.eq([]); + }); + }); +}); diff --git a/packages/core/test/unit/data-types/_utils.ts b/packages/core/test/unit/data-types/_utils.ts new file mode 100644 index 000000000000..daad273a6644 --- /dev/null +++ b/packages/core/test/unit/data-types/_utils.ts @@ -0,0 +1,21 @@ +import type { DataTypeClassOrInstance } from '@sequelize/core'; +import assert from 'node:assert'; +import { createTester, expectsql, sequelize } from '../../support'; + +export const testDataTypeSql = createTester( + (it, description: string, dataType: DataTypeClassOrInstance, expectation) => { + it(description, () => { + let result: Error | string; + + try { + result = + typeof dataType === 'string' ? dataType : sequelize.normalizeDataType(dataType).toSql(); + } catch (error) { + assert(error instanceof Error); + result = error; + } + + return expectsql(result, expectation); + }); + }, +); diff --git a/packages/core/test/unit/data-types/arrays.test.ts b/packages/core/test/unit/data-types/arrays.test.ts new file mode 100644 index 000000000000..dcddaefc142f --- /dev/null +++ b/packages/core/test/unit/data-types/arrays.test.ts @@ -0,0 +1,177 @@ +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { allowDeprecationsInSuite, expectsql, sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const { dialect, queryGenerator } = sequelize; + +describe('DataTypes.ARRAY', () => { + allowDeprecationsInSuite(['SEQUELIZE0014']); + + const unsupportedError = new Error( + `${dialect.name} does not support the ARRAY data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ); + + testDataTypeSql('ARRAY(VARCHAR)', DataTypes.ARRAY(DataTypes.STRING), { + default: unsupportedError, + postgres: 'VARCHAR(255)[]', + }); + + testDataTypeSql('ARRAY(VARCHAR(100))', DataTypes.ARRAY(DataTypes.STRING(100)), { + default: unsupportedError, + postgres: 'VARCHAR(100)[]', + }); + + testDataTypeSql('ARRAY(INTEGER)', DataTypes.ARRAY(DataTypes.INTEGER), { + default: unsupportedError, + postgres: 'INTEGER[]', + }); + + testDataTypeSql('ARRAY(HSTORE)', DataTypes.ARRAY(DataTypes.HSTORE), { + default: unsupportedError, + postgres: 'HSTORE[]', + }); + + testDataTypeSql( + 'ARRAY(ARRAY(VARCHAR(255)))', + DataTypes.ARRAY(DataTypes.ARRAY(DataTypes.STRING)), + { + default: unsupportedError, + postgres: 'VARCHAR(255)[][]', + }, + ); + + testDataTypeSql('ARRAY(TEXT)', DataTypes.ARRAY(DataTypes.TEXT), { + default: unsupportedError, + postgres: 'TEXT[]', + }); + + testDataTypeSql('ARRAY(DATE)', DataTypes.ARRAY(DataTypes.DATE), { + default: unsupportedError, + postgres: 'TIMESTAMP WITH TIME ZONE[]', + }); + + testDataTypeSql('ARRAY(BOOLEAN)', DataTypes.ARRAY(DataTypes.BOOLEAN), { + default: unsupportedError, + postgres: 'BOOLEAN[]', + }); + + testDataTypeSql('ARRAY(DECIMAL)', DataTypes.ARRAY(DataTypes.DECIMAL), { + default: unsupportedError, + postgres: 'DECIMAL[]', + }); + + testDataTypeSql('ARRAY(DECIMAL(6, 4))', DataTypes.ARRAY(DataTypes.DECIMAL(6, 4)), { + default: unsupportedError, + postgres: 'DECIMAL(6, 4)[]', + }); + + testDataTypeSql('ARRAY(DOUBLE)', DataTypes.ARRAY(DataTypes.DOUBLE), { + default: unsupportedError, + postgres: 'DOUBLE PRECISION[]', + }); + + testDataTypeSql('ARRAY(REAL))', DataTypes.ARRAY(DataTypes.REAL), { + default: unsupportedError, + postgres: 'REAL[]', + }); + + testDataTypeSql('ARRAY(JSON)', DataTypes.ARRAY(DataTypes.JSON), { + default: unsupportedError, + postgres: 'JSON[]', + }); + + testDataTypeSql('ARRAY(JSONB)', DataTypes.ARRAY(DataTypes.JSONB), { + default: unsupportedError, + postgres: 'JSONB[]', + }); + + testDataTypeSql('ARRAY(CITEXT)', DataTypes.ARRAY(DataTypes.CITEXT), { + default: unsupportedError, + postgres: 'CITEXT[]', + }); + + testDataTypeSql('ARRAY(UUID)', DataTypes.ARRAY(DataTypes.UUID), { + default: unsupportedError, + postgres: 'UUID[]', + }); + + it('raises an error if no values are defined', () => { + expect(() => { + sequelize.define('omnomnom', { + bla: { type: DataTypes.ARRAY }, + }); + }).to.throwWithCause(Error, 'ARRAY is missing type definition for its values.'); + }); + + describe('validate', () => { + if (!dialect.supports.dataTypes.ARRAY) { + return; + } + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.ARRAY(DataTypes.INTEGER); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid array`); + }); + + it('should not throw if `value` is an array', () => { + const type = DataTypes.ARRAY(DataTypes.STRING); + + expect(() => type.validate(['foo', 'bar'])).not.to.throw(); + }); + }); + }); + + describe('escape', () => { + if (!dialect.supports.dataTypes.ARRAY) { + return; + } + + it('does not not add cast to array of TEXT', () => { + expectsql(queryGenerator.escape(['foo', 'bar'], { type: DataTypes.ARRAY(DataTypes.TEXT) }), { + postgres: `ARRAY['foo','bar']`, + }); + }); + + // Regression test for https://github.com/sequelize/sequelize/issues/16391 + it('adds cast to array of VARCHAR', () => { + expectsql( + queryGenerator.escape(['foo', 'bar'], { type: DataTypes.ARRAY(DataTypes.STRING(64)) }), + { + postgres: `ARRAY['foo','bar']::VARCHAR(64)[]`, + }, + ); + }); + + it('escapes array of JSON', () => { + expectsql( + queryGenerator.escape([{ some: 'nested', more: { nested: true }, answer: 42 }, 43, 'joe'], { + type: DataTypes.ARRAY(DataTypes.JSON), + }), + { + postgres: + 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]', + }, + ); + }); + + if (dialect.supports.dataTypes.JSONB) { + it('escapes array of JSONB', () => { + expectsql( + queryGenerator.escape( + [{ some: 'nested', more: { nested: true }, answer: 42 }, 43, 'joe'], + { type: DataTypes.ARRAY(DataTypes.JSONB) }, + ), + { + postgres: + 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSONB[]', + }, + ); + }); + } + }); +}); diff --git a/packages/core/test/unit/data-types/binary-types.test.ts b/packages/core/test/unit/data-types/binary-types.test.ts new file mode 100644 index 000000000000..1479c0bba120 --- /dev/null +++ b/packages/core/test/unit/data-types/binary-types.test.ts @@ -0,0 +1,68 @@ +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { testDataTypeSql } from './_utils'; + +describe('DataTypes.BLOB', () => { + testDataTypeSql('BLOB', DataTypes.BLOB, { + default: 'BLOB', + 'ibmi db2': 'BLOB(1M)', + mssql: 'VARBINARY(MAX)', + postgres: 'BYTEA', + }); + + testDataTypeSql('BLOB("tiny")', DataTypes.BLOB('tiny'), { + default: 'TINYBLOB', + ibmi: 'BLOB(255)', + mssql: 'VARBINARY(256)', + db2: 'BLOB(255)', + postgres: 'BYTEA', + sqlite3: 'BLOB', + }); + + testDataTypeSql('BLOB("medium")', DataTypes.BLOB('medium'), { + default: 'MEDIUMBLOB', + ibmi: 'BLOB(16M)', + mssql: 'VARBINARY(MAX)', + db2: 'BLOB(16M)', + postgres: 'BYTEA', + sqlite3: 'BLOB', + }); + + testDataTypeSql('BLOB({ length: "medium" })', DataTypes.BLOB({ length: 'medium' }), { + default: 'MEDIUMBLOB', + ibmi: 'BLOB(16M)', + mssql: 'VARBINARY(MAX)', + db2: 'BLOB(16M)', + postgres: 'BYTEA', + sqlite3: 'BLOB', + }); + + testDataTypeSql('BLOB("long")', DataTypes.BLOB('long'), { + default: 'LONGBLOB', + ibmi: 'BLOB(2G)', + mssql: 'VARBINARY(MAX)', + db2: 'BLOB(2G)', + postgres: 'BYTEA', + sqlite3: 'BLOB', + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.BLOB(); + + expect(() => { + type.validate(12_345); + }).to.throw( + ValidationErrorItem, + '12345 is not a valid binary value: Only strings, Buffer, Uint8Array and ArrayBuffer are supported.', + ); + }); + + it('should not throw if `value` is a blob', () => { + const type = DataTypes.BLOB(); + + expect(() => type.validate('foobar')).not.to.throw(); + expect(() => type.validate(Buffer.from('foobar'))).not.to.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/data-types/decimal-numbers.test.ts b/packages/core/test/unit/data-types/decimal-numbers.test.ts new file mode 100644 index 000000000000..5396b37d5267 --- /dev/null +++ b/packages/core/test/unit/data-types/decimal-numbers.test.ts @@ -0,0 +1,320 @@ +import type { DataTypeInstance } from '@sequelize/core'; +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { allowDeprecationsInSuite, sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialect = sequelize.dialect; +const dialectName = dialect.name; + +describe('DataTypes.REAL', () => { + allowDeprecationsInSuite(['SEQUELIZE0014']); + + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the REAL.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('REAL', DataTypes.REAL, { + default: 'REAL', + }); + + testDataTypeSql('REAL.UNSIGNED', DataTypes.REAL.UNSIGNED, { + default: 'REAL UNSIGNED', + 'sqlite3 snowflake ibmi db2 mssql postgres': 'REAL', + }); + + testDataTypeSql('REAL(11, 12)', DataTypes.REAL(11, 12), { + default: 'REAL(11, 12)', + 'sqlite3 snowflake ibmi db2 mssql postgres': 'REAL', + }); + + testDataTypeSql('REAL(11, 12).UNSIGNED', DataTypes.REAL(11, 12).UNSIGNED, { + default: 'REAL(11, 12) UNSIGNED', + 'sqlite3 snowflake ibmi db2 mssql postgres': 'REAL', + }); + + testDataTypeSql( + 'REAL({ precision: 11, scale: 12 }).UNSIGNED', + DataTypes.REAL({ precision: 11, scale: 12 }).UNSIGNED, + { + default: 'REAL(11, 12) UNSIGNED', + 'sqlite3 snowflake ibmi db2 mssql postgres': 'REAL', + }, + ); + + testDataTypeSql('REAL(11, 12).UNSIGNED.ZEROFILL', DataTypes.REAL(11, 12).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'REAL(11, 12) UNSIGNED ZEROFILL', + }); + + testDataTypeSql('REAL(11, 12).ZEROFILL', DataTypes.REAL(11, 12).ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'REAL(11, 12) ZEROFILL', + }); + + testDataTypeSql('REAL(11, 12).ZEROFILL.UNSIGNED', DataTypes.REAL(11, 12).ZEROFILL.UNSIGNED, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'REAL(11, 12) UNSIGNED ZEROFILL', + }); +}); + +describe('DataTypes.DOUBLE', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the DOUBLE.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('DOUBLE', DataTypes.DOUBLE, { + default: 'DOUBLE PRECISION', + 'db2 ibmi': 'DOUBLE', + sqlite3: 'REAL', + snowflake: 'FLOAT', + }); + + testDataTypeSql('DOUBLE.UNSIGNED', DataTypes.DOUBLE.UNSIGNED, { + 'mysql mariadb': 'DOUBLE PRECISION UNSIGNED', + sqlite3: 'REAL', + 'db2 ibmi': 'DOUBLE', + 'postgres mssql': 'DOUBLE PRECISION', + snowflake: 'FLOAT', + }); + + testDataTypeSql('DOUBLE(11, 12)', DataTypes.DOUBLE(11, 12), { + 'mysql mariadb': 'DOUBLE PRECISION(11, 12)', + sqlite3: 'REAL', + 'db2 ibmi': 'DOUBLE', + 'postgres mssql': 'DOUBLE PRECISION', + snowflake: 'FLOAT', + }); + + testDataTypeSql('DOUBLE(11, 12).UNSIGNED', DataTypes.DOUBLE(11, 12).UNSIGNED, { + 'mysql mariadb': 'DOUBLE PRECISION(11, 12) UNSIGNED', + sqlite3: 'REAL', + 'db2 ibmi': 'DOUBLE', + 'postgres mssql': 'DOUBLE PRECISION', + snowflake: 'FLOAT', + }); + + testDataTypeSql('DOUBLE(11, 12).UNSIGNED.ZEROFILL', DataTypes.DOUBLE(11, 12).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'DOUBLE PRECISION(11, 12) UNSIGNED ZEROFILL', + }); + + testDataTypeSql('DOUBLE(11, 12).ZEROFILL', DataTypes.DOUBLE(11, 12).ZEROFILL, { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'DOUBLE PRECISION(11, 12) ZEROFILL', + }); + + testDataTypeSql('DOUBLE(11, 12).ZEROFILL.UNSIGNED', DataTypes.DOUBLE(11, 12).ZEROFILL.UNSIGNED, { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'DOUBLE PRECISION(11, 12) UNSIGNED ZEROFILL', + }); + + it('requires both scale & precision to be specified', () => { + expect(() => DataTypes.DOUBLE(10)).to.throw( + 'The DOUBLE DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.DOUBLE({ precision: 10 })).to.throw( + 'The DOUBLE DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.DOUBLE({ scale: 2 })).to.throw( + 'The DOUBLE DataType requires that the "precision" option be specified if the "scale" option is specified.', + ); + }); +}); + +describe('DataTypes.FLOAT', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the FLOAT.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + // Must be a single-precision floating point if available, + // or a double-precision fallback if not. + testDataTypeSql('FLOAT', DataTypes.FLOAT, { + // FLOAT in snowflake is double-precision (no single-precision support), but single-precision is all others + 'mysql mariadb snowflake': 'FLOAT', + // REAL in sqlite is double-precision (no single-precision support), but single-precision in all others + 'postgres mssql sqlite3 db2 ibmi': 'REAL', + }); + + testDataTypeSql('FLOAT.UNSIGNED', DataTypes.FLOAT.UNSIGNED, { + 'mysql mariadb': 'FLOAT UNSIGNED', + snowflake: 'FLOAT', + 'postgres mssql sqlite3 db2 ibmi': 'REAL', + }); + + testDataTypeSql('FLOAT(11, 12)', DataTypes.FLOAT(11, 12), { + 'mysql mariadb': 'FLOAT(11, 12)', + snowflake: 'FLOAT', + 'postgres mssql sqlite3 db2 ibmi': 'REAL', + }); + + testDataTypeSql('FLOAT(11, 12).UNSIGNED', DataTypes.FLOAT(11, 12).UNSIGNED, { + 'mysql mariadb': 'FLOAT(11, 12) UNSIGNED', + snowflake: 'FLOAT', + 'postgres mssql sqlite3 db2 ibmi': 'REAL', + }); + + testDataTypeSql( + 'FLOAT({ length: 11, decimals: 12 }).UNSIGNED', + DataTypes.FLOAT({ precision: 11, scale: 12 }).UNSIGNED, + { + 'mysql mariadb': 'FLOAT(11, 12) UNSIGNED', + snowflake: 'FLOAT', + 'postgres mssql sqlite3 db2 ibmi': 'REAL', + }, + ); + + testDataTypeSql('FLOAT(11, 12).UNSIGNED.ZEROFILL', DataTypes.FLOAT(11, 12).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'FLOAT(11, 12) UNSIGNED ZEROFILL', + }); + + testDataTypeSql('FLOAT(11, 12).ZEROFILL', DataTypes.FLOAT(11, 12).ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'FLOAT(11, 12) ZEROFILL', + }); + + testDataTypeSql('FLOAT(11, 12).ZEROFILL.UNSIGNED', DataTypes.FLOAT(11, 12).ZEROFILL.UNSIGNED, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'FLOAT(11, 12) UNSIGNED ZEROFILL', + }); + + it('requires both scale & precision to be specified', () => { + expect(() => DataTypes.FLOAT(10)).to.throw( + 'The FLOAT DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.FLOAT({ precision: 10 })).to.throw( + 'The FLOAT DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.FLOAT({ scale: 2 })).to.throw( + 'The FLOAT DataType requires that the "precision" option be specified if the "scale" option is specified.', + ); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.FLOAT(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid float`); + }); + + it('should not throw if `value` is a float', () => { + const type = DataTypes.FLOAT(); + + expect(() => type.validate(1.2)).not.to.throw(); + expect(() => type.validate('1')).not.to.throw(); + expect(() => type.validate('1.2')).not.to.throw(); + expect(() => type.validate('-0.123')).not.to.throw(); + expect(() => type.validate('-0.22250738585072011e-307')).not.to.throw(); + }); + }); +}); + +describe('DECIMAL', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the DECIMAL.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + const unsupportedError = new Error(`${dialectName} does not support the DECIMAL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('DECIMAL', DataTypes.DECIMAL, { + default: new Error( + `${dialectName} does not support unconstrained DECIMAL types. Please specify the "precision" and "scale" options.`, + ), + sqlite3: unsupportedError, + postgres: 'DECIMAL', + }); + + testDataTypeSql('DECIMAL(10, 2)', DataTypes.DECIMAL(10, 2), { + default: 'DECIMAL(10, 2)', + sqlite3: unsupportedError, + }); + + testDataTypeSql( + 'DECIMAL({ precision: 10, scale: 2 })', + DataTypes.DECIMAL({ precision: 10, scale: 2 }), + { + default: 'DECIMAL(10, 2)', + sqlite3: unsupportedError, + }, + ); + + testDataTypeSql('DECIMAL(10, 2).UNSIGNED', DataTypes.DECIMAL(10, 2).UNSIGNED, { + default: 'DECIMAL(10, 2)', + 'mysql mariadb': 'DECIMAL(10, 2) UNSIGNED', + sqlite3: unsupportedError, + }); + + testDataTypeSql('DECIMAL(10, 2).UNSIGNED.ZEROFILL', DataTypes.DECIMAL(10, 2).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + sqlite3: unsupportedError, + 'mysql mariadb': 'DECIMAL(10, 2) UNSIGNED ZEROFILL', + }); + + testDataTypeSql( + 'DECIMAL({ precision: 10, scale: 2 }).UNSIGNED', + DataTypes.DECIMAL({ precision: 10, scale: 2 }).UNSIGNED, + { + default: 'DECIMAL(10, 2)', + 'mysql mariadb': 'DECIMAL(10, 2) UNSIGNED', + sqlite3: unsupportedError, + }, + ); + + it('requires both scale & precision to be specified', () => { + expect(() => DataTypes.DECIMAL(10)).to.throw( + 'The DECIMAL DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.DECIMAL({ precision: 10 })).to.throw( + 'The DECIMAL DataType requires that the "scale" option be specified if the "precision" option is specified.', + ); + expect(() => DataTypes.DECIMAL({ scale: 2 })).to.throw( + 'The DECIMAL DataType requires that the "precision" option be specified if the "scale" option is specified.', + ); + }); + + describe('validate', () => { + const supportsDecimal = dialect.supports.dataTypes.DECIMAL; + if (!supportsDecimal) { + return; + } + + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.DECIMAL(10, 2).toDialectDataType(dialect); + const typeName = supportsDecimal.constrained ? 'decimal(10, 2)' : 'decimal'; + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid ${typeName}`); + + expect(() => { + type.validate('0.1a'); + }).to.throw(ValidationErrorItem, `'0.1a' is not a valid ${typeName}`); + + if (!supportsDecimal.NaN) { + expect(() => { + type.validate(Number.NaN); + }).to.throw(ValidationErrorItem, `NaN is not a valid ${typeName}`); + } else { + expect(() => { + type.validate(Number.NaN); + }).not.to.throw(); + } + }); + + it('should not throw if `value` is a decimal', () => { + const type = DataTypes.DECIMAL(10, 2); + + expect(() => type.validate(123)).not.to.throw(); + expect(() => type.validate(1.2)).not.to.throw(); + expect(() => type.validate(-0.25)).not.to.throw(); + expect(() => type.validate(0.000_000_000_000_1)).not.to.throw(); + expect(() => type.validate('123')).not.to.throw(); + expect(() => type.validate('1.2')).not.to.throw(); + expect(() => type.validate('-0.25')).not.to.throw(); + expect(() => type.validate('0.0000000000001')).not.to.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/data-types/geography.test.ts b/packages/core/test/unit/data-types/geography.test.ts new file mode 100644 index 000000000000..2e4f7cc62847 --- /dev/null +++ b/packages/core/test/unit/data-types/geography.test.ts @@ -0,0 +1,15 @@ +import { DataTypes } from '@sequelize/core'; +import { sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialectName = sequelize.dialect.name; + +// TODO: extend suite to cover all data sub-types, like in geometry.test.ts +describe('GEOGRAPHY', () => { + testDataTypeSql('GEOGRAPHY', DataTypes.GEOGRAPHY, { + default: new Error( + `${dialectName} does not support the GEOGRAPHY data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ), + postgres: 'GEOGRAPHY', + }); +}); diff --git a/packages/core/test/unit/data-types/geometry.test.ts b/packages/core/test/unit/data-types/geometry.test.ts new file mode 100644 index 000000000000..910007e832a4 --- /dev/null +++ b/packages/core/test/unit/data-types/geometry.test.ts @@ -0,0 +1,40 @@ +import { DataTypes, GeoJsonType } from '@sequelize/core'; +import { sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialect = sequelize.dialect; + +describe('GEOMETRY', () => { + const unsupportedError = new Error( + `${dialect.name} does not support the GEOMETRY data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ); + testDataTypeSql('GEOMETRY', DataTypes.GEOMETRY, { + default: unsupportedError, + 'postgres mysql mariadb': 'GEOMETRY', + }); + + testDataTypeSql(`GEOMETRY('POINT')`, DataTypes.GEOMETRY(GeoJsonType.Point), { + default: unsupportedError, + postgres: 'GEOMETRY(POINT)', + 'mysql mariadb': 'POINT', + }); + + testDataTypeSql(`GEOMETRY('LINESTRING')`, DataTypes.GEOMETRY(GeoJsonType.LineString), { + default: unsupportedError, + postgres: 'GEOMETRY(LINESTRING)', + 'mysql mariadb': 'LINESTRING', + }); + + testDataTypeSql(`GEOMETRY('POLYGON')`, DataTypes.GEOMETRY(GeoJsonType.Polygon), { + default: unsupportedError, + postgres: 'GEOMETRY(POLYGON)', + 'mysql mariadb': 'POLYGON', + }); + + testDataTypeSql(`GEOMETRY('POINT',4326)`, DataTypes.GEOMETRY(GeoJsonType.Point, 4326), { + default: unsupportedError, + postgres: 'GEOMETRY(POINT,4326)', + mysql: 'POINT /*!80003 SRID 4326 */', + mariadb: 'POINT REF_SYSTEM_ID=4326', + }); +}); diff --git a/packages/core/test/unit/data-types/integers.test.ts b/packages/core/test/unit/data-types/integers.test.ts new file mode 100644 index 000000000000..a7663e4dd386 --- /dev/null +++ b/packages/core/test/unit/data-types/integers.test.ts @@ -0,0 +1,567 @@ +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialect = sequelize.dialect; +const dialectName = dialect.name; + +describe('DataTypes.TINYINT', () => { + describe('toSql', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the TINYINT.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + const cases = [ + { + title: 'TINYINT', + dataType: DataTypes.TINYINT, + expect: { + // TINYINT in mssql is UNSIGNED. For the signed version, we fallback to TINYINT + check constraint + 'mssql postgres db2 ibmi': 'SMALLINT', + 'mysql mariadb': 'TINYINT', + 'sqlite3 snowflake': 'INTEGER', + }, + }, + { + // This option (length) is ignored when unavailable. + title: 'TINYINT(2)', + dataType: DataTypes.TINYINT(2), + expect: { + 'mssql postgres db2 ibmi': 'SMALLINT', + 'mysql mariadb': 'TINYINT(2)', + 'sqlite3 snowflake': 'INTEGER', + }, + }, + { + title: 'TINYINT({ length: 2 })', + dataType: DataTypes.TINYINT({ length: 2 }), + expect: { + 'mssql postgres db2 ibmi': 'SMALLINT', + 'mysql mariadb': 'TINYINT(2)', + 'sqlite3 snowflake': 'INTEGER', + }, + }, + { + title: 'TINYINT.UNSIGNED', + dataType: DataTypes.TINYINT.UNSIGNED, + expect: { + // Fallback to bigger type + check constraint + 'postgres db2 ibmi': 'SMALLINT', + 'mysql mariadb': 'TINYINT UNSIGNED', + // sqlite3 & snowflake only supports INTEGER as a column type + 'sqlite3 snowflake': 'INTEGER', + // TINYINT is unsigned in mssql + mssql: 'TINYINT', + }, + }, + { + title: 'TINYINT(2).UNSIGNED', + dataType: DataTypes.TINYINT(2).UNSIGNED, + expect: { + 'postgres db2 ibmi': 'SMALLINT', + 'mysql mariadb': 'TINYINT(2) UNSIGNED', + 'sqlite3 snowflake': 'INTEGER', + mssql: 'TINYINT', + }, + }, + { + title: 'TINYINT.UNSIGNED.ZEROFILL', + dataType: DataTypes.TINYINT.UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'TINYINT(2).UNSIGNED.ZEROFILL', + dataType: DataTypes.TINYINT(2).UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT(2) UNSIGNED ZEROFILL', + }, + }, + { + title: 'TINYINT.ZEROFILL', + dataType: DataTypes.TINYINT.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT ZEROFILL', + }, + }, + { + title: 'TINYINT(2).ZEROFILL', + dataType: DataTypes.TINYINT(2).ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT(2) ZEROFILL', + }, + }, + { + title: 'TINYINT.ZEROFILL.UNSIGNED', + dataType: DataTypes.TINYINT.ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'TINYINT(2).ZEROFILL.UNSIGNED', + dataType: DataTypes.TINYINT(2).ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'TINYINT(2) UNSIGNED ZEROFILL', + }, + }, + ]; + + for (const row of cases) { + testDataTypeSql(row.title, row.dataType, row.expect); + } + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.TINYINT(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid tinyint`); + + expect(() => { + type.validate(123.45); + }).to.throw(ValidationErrorItem, '123.45 is not a valid tinyint'); + }); + + it('should not throw if `value` is an integer', () => { + const type = DataTypes.TINYINT(); + + expect(() => type.validate(-128)).not.to.throw(); + expect(() => type.validate('127')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.SMALLINT', () => { + describe('toSql', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the SMALLINT.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + const cases = [ + { + title: 'SMALLINT', + dataType: DataTypes.SMALLINT, + expect: { + default: 'SMALLINT', + 'sqlite3 snowflake': 'INTEGER', + }, + }, + { + title: 'SMALLINT(4)', + dataType: DataTypes.SMALLINT(4), + expect: { + default: 'SMALLINT', + 'sqlite3 snowflake': 'INTEGER', + 'mysql mariadb': 'SMALLINT(4)', + }, + }, + { + title: 'SMALLINT({ length: 4 })', + dataType: DataTypes.SMALLINT({ length: 4 }), + expect: { + default: 'SMALLINT', + 'sqlite3 snowflake': 'INTEGER', + 'mysql mariadb': 'SMALLINT(4)', + }, + }, + { + title: 'SMALLINT.UNSIGNED', + dataType: DataTypes.SMALLINT.UNSIGNED, + expect: { + 'mysql mariadb': 'SMALLINT UNSIGNED', + // sqlite3 & snowflake only supports INTEGER as a column type + 'sqlite3 snowflake': 'INTEGER', + 'postgres db2 ibmi': 'INTEGER', + mssql: 'INT', + }, + }, + { + title: 'SMALLINT(4).UNSIGNED', + dataType: DataTypes.SMALLINT(4).UNSIGNED, + expect: { + 'mysql mariadb': 'SMALLINT(4) UNSIGNED', + 'sqlite3 snowflake': 'INTEGER', + 'postgres db2 ibmi': 'INTEGER', + mssql: 'INT', + }, + }, + { + title: 'SMALLINT.UNSIGNED.ZEROFILL', + dataType: DataTypes.SMALLINT.UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'SMALLINT(4).UNSIGNED.ZEROFILL', + dataType: DataTypes.SMALLINT(4).UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT(4) UNSIGNED ZEROFILL', + }, + }, + { + title: 'SMALLINT.ZEROFILL', + dataType: DataTypes.SMALLINT.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT ZEROFILL', + }, + }, + { + title: 'SMALLINT(4).ZEROFILL', + dataType: DataTypes.SMALLINT(4).ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT(4) ZEROFILL', + }, + }, + { + title: 'SMALLINT.ZEROFILL.UNSIGNED', + dataType: DataTypes.SMALLINT.ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'SMALLINT(4).ZEROFILL.UNSIGNED', + dataType: DataTypes.SMALLINT(4).ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'SMALLINT(4) UNSIGNED ZEROFILL', + }, + }, + ]; + + for (const row of cases) { + testDataTypeSql(row.title, row.dataType, row.expect); + } + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.SMALLINT(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid smallint`); + + expect(() => { + type.validate(123.45); + }).to.throw(ValidationErrorItem, '123.45 is not a valid smallint'); + }); + + it('should not throw if `value` is an integer', () => { + const type = DataTypes.SMALLINT(); + + expect(() => type.validate(-32_768)).not.to.throw(); + expect(() => type.validate('32767')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.MEDIUMINT', () => { + describe('toSql', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the MEDIUMINT.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + const cases = [ + { + title: 'MEDIUMINT', + dataType: DataTypes.MEDIUMINT, + expect: { + 'mariadb mysql': 'MEDIUMINT', + // falls back to larger type + CHECK constraint + 'db2 ibmi mssql postgres snowflake sqlite3': 'INTEGER', + }, + }, + { + title: 'MEDIUMINT(2)', + dataType: DataTypes.MEDIUMINT(2), + expect: { + 'mariadb mysql': 'MEDIUMINT(2)', + 'db2 ibmi mssql postgres snowflake sqlite3': 'INTEGER', + }, + }, + { + title: 'MEDIUMINT({ length: 2 })', + dataType: DataTypes.MEDIUMINT({ length: 2 }), + expect: { + 'mariadb mysql': 'MEDIUMINT(2)', + 'db2 ibmi mssql postgres snowflake sqlite3': 'INTEGER', + }, + }, + { + title: 'MEDIUMINT.UNSIGNED', + dataType: DataTypes.MEDIUMINT.UNSIGNED, + expect: { + 'mariadb mysql': 'MEDIUMINT UNSIGNED', + 'db2 ibmi mssql postgres snowflake sqlite3': 'INTEGER', + }, + }, + { + title: 'MEDIUMINT(2).UNSIGNED', + dataType: DataTypes.MEDIUMINT(2).UNSIGNED, + expect: { + 'mariadb mysql': 'MEDIUMINT(2) UNSIGNED', + 'db2 ibmi mssql postgres snowflake sqlite3': 'INTEGER', + }, + }, + { + title: 'MEDIUMINT.UNSIGNED.ZEROFILL', + dataType: DataTypes.MEDIUMINT.UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'MEDIUMINT(2).UNSIGNED.ZEROFILL', + dataType: DataTypes.MEDIUMINT(2).UNSIGNED.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT(2) UNSIGNED ZEROFILL', + }, + }, + { + title: 'MEDIUMINT.ZEROFILL', + dataType: DataTypes.MEDIUMINT.ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT ZEROFILL', + }, + }, + { + title: 'MEDIUMINT(2).ZEROFILL', + dataType: DataTypes.MEDIUMINT(2).ZEROFILL, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT(2) ZEROFILL', + }, + }, + { + title: 'MEDIUMINT.ZEROFILL.UNSIGNED', + dataType: DataTypes.MEDIUMINT.ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT UNSIGNED ZEROFILL', + }, + }, + { + title: 'MEDIUMINT(2).ZEROFILL.UNSIGNED', + dataType: DataTypes.MEDIUMINT(2).ZEROFILL.UNSIGNED, + expect: { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'MEDIUMINT(2) UNSIGNED ZEROFILL', + }, + }, + ]; + + for (const row of cases) { + testDataTypeSql(row.title, row.dataType, row.expect); + } + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.MEDIUMINT(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid mediumint`); + + expect(() => { + type.validate(123.45); + }).to.throw(ValidationErrorItem, '123.45 is not a valid mediumint'); + }); + + it('should not throw if `value` is an integer', () => { + const type = DataTypes.MEDIUMINT(); + + expect(() => type.validate(-8_388_608)).not.to.throw(); + expect(() => type.validate('8388607')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.INTEGER', () => { + describe('toSql', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the INTEGER.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('INTEGER', DataTypes.INTEGER, { + default: 'INTEGER', + }); + + testDataTypeSql('INTEGER.UNSIGNED', DataTypes.INTEGER.UNSIGNED, { + // sqlite & snowflake are both 64 bits integers (actually snowflake accepts up to 99999999999999999999999999999999999999) + 'sqlite3 snowflake': 'INTEGER', + 'mysql mariadb': 'INTEGER UNSIGNED', + 'ibmi postgres db2 mssql': 'BIGINT', + }); + + testDataTypeSql('INTEGER.UNSIGNED.ZEROFILL', DataTypes.INTEGER.UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mariadb mysql': 'INTEGER UNSIGNED ZEROFILL', + }); + + testDataTypeSql('INTEGER(11)', DataTypes.INTEGER(11), { + default: 'INTEGER', + 'mysql mariadb': 'INTEGER(11)', + }); + + testDataTypeSql('INTEGER({ length: 11 })', DataTypes.INTEGER({ length: 11 }), { + default: 'INTEGER', + 'mysql mariadb': 'INTEGER(11)', + }); + + testDataTypeSql('INTEGER(11).UNSIGNED', DataTypes.INTEGER(11).UNSIGNED, { + 'mysql mariadb': 'INTEGER(11) UNSIGNED', + 'sqlite3 snowflake': 'INTEGER', + 'ibmi postgres db2 mssql': 'BIGINT', + }); + + testDataTypeSql('INTEGER(11).UNSIGNED.ZEROFILL', DataTypes.INTEGER(11).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'INTEGER(11) UNSIGNED ZEROFILL', + }); + + testDataTypeSql('INTEGER(11).ZEROFILL', DataTypes.INTEGER(11).ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'INTEGER(11) ZEROFILL', + }); + + testDataTypeSql('INTEGER(11).ZEROFILL.UNSIGNED', DataTypes.INTEGER(11).ZEROFILL.UNSIGNED, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'INTEGER(11) UNSIGNED ZEROFILL', + }); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.INTEGER(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid integer`); + + expect(() => { + type.validate('123.45'); + }).to.throw(ValidationErrorItem, `'123.45' is not a valid integer`); + + expect(() => { + type.validate(123.45); + }).to.throw(ValidationErrorItem, '123.45 is not a valid integer'); + }); + + it('should not throw if `value` is a valid integer', () => { + const type = DataTypes.INTEGER(); + + expect(() => type.validate('12345')).not.to.throw(); + expect(() => type.validate(12_345)).not.to.throw(); + expect(() => type.validate(12_345n)).not.to.throw(); + }); + }); +}); + +if (dialect.supports.dataTypes.BIGINT) { + describe('DataTypes.BIGINT', () => { + describe('toSql', () => { + const zeroFillUnsupportedError = + new Error(`${dialectName} does not support the BIGINT.ZEROFILL data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + const unsignedUnsupportedError = + new Error(`${dialectName} does not support the BIGINT.UNSIGNED data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('BIGINT', DataTypes.BIGINT, { + default: 'BIGINT', + 'sqlite3 snowflake': 'INTEGER', + }); + + testDataTypeSql('BIGINT.UNSIGNED', DataTypes.BIGINT.UNSIGNED, { + default: unsignedUnsupportedError, + 'mysql mariadb': 'BIGINT UNSIGNED', + // INTEGER in snowflake goes up to 99999999999999999999999999999999999999, which is enough to store an unsigned 64-bit integer. + snowflake: 'INTEGER', + }); + + testDataTypeSql('BIGINT.UNSIGNED.ZEROFILL', DataTypes.BIGINT.UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'BIGINT UNSIGNED ZEROFILL', + }); + + testDataTypeSql('BIGINT(11)', DataTypes.BIGINT(11), { + default: 'BIGINT', + 'sqlite3 snowflake': 'INTEGER', + 'mysql mariadb': 'BIGINT(11)', + }); + + testDataTypeSql('BIGINT({ length: 11 })', DataTypes.BIGINT({ length: 11 }), { + default: 'BIGINT', + 'sqlite3 snowflake': 'INTEGER', + 'mysql mariadb': 'BIGINT(11)', + }); + + testDataTypeSql('BIGINT(11).UNSIGNED', DataTypes.BIGINT(11).UNSIGNED, { + // There is no type big enough to hold values between 0 & 2^32-1 + default: unsignedUnsupportedError, + 'mysql mariadb': 'BIGINT(11) UNSIGNED', + snowflake: 'INTEGER', + }); + + testDataTypeSql('BIGINT(11).UNSIGNED.ZEROFILL', DataTypes.BIGINT(11).UNSIGNED.ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'BIGINT(11) UNSIGNED ZEROFILL', + }); + + testDataTypeSql('BIGINT(11).ZEROFILL', DataTypes.BIGINT(11).ZEROFILL, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'BIGINT(11) ZEROFILL', + }); + + testDataTypeSql('BIGINT(11).ZEROFILL.UNSIGNED', DataTypes.BIGINT(11).ZEROFILL.UNSIGNED, { + default: zeroFillUnsupportedError, + 'mysql mariadb': 'BIGINT(11) UNSIGNED ZEROFILL', + }); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.BIGINT().toDialectDataType(dialect); + + expect(() => { + type.validate('foobar'); + }).to.throw( + ValidationErrorItem, + `'foobar' is not a valid ${type.toString().toLowerCase()}`, + ); + + expect(() => { + type.validate(123.45); + }).to.throw(ValidationErrorItem, `123.45 is not a valid ${type.toString().toLowerCase()}`); + }); + + it('should not throw if `value` is an integer', () => { + const type = DataTypes.BIGINT(); + + expect(() => type.validate('9223372036854775807')).not.to.throw(); + }); + }); + }); +} diff --git a/packages/core/test/unit/data-types/misc-data-types.test.ts b/packages/core/test/unit/data-types/misc-data-types.test.ts new file mode 100644 index 000000000000..3ea3c343895b --- /dev/null +++ b/packages/core/test/unit/data-types/misc-data-types.test.ts @@ -0,0 +1,356 @@ +import type { DataTypeInstance } from '@sequelize/core'; +import { DataTypes, JSON_NULL, SQL_NULL, ValidationErrorItem } from '@sequelize/core'; +import type { ENUM } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import { expect } from 'chai'; +import assert from 'node:assert'; +import { createSequelizeInstance, expectsql, sequelize, typeTest } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const { queryGenerator, dialect } = sequelize; +const dialectName = dialect.name; + +describe('DataTypes.BOOLEAN', () => { + testDataTypeSql('BOOLEAN', DataTypes.BOOLEAN, { + default: 'BOOLEAN', + ibmi: 'SMALLINT', + mssql: 'BIT', + mariadb: 'TINYINT(1)', + mysql: 'TINYINT(1)', + sqlite3: 'INTEGER', + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.BOOLEAN(); + + expect(() => { + type.validate(12_345); + }).to.throw(ValidationErrorItem, '12345 is not a valid boolean'); + }); + + it('should not throw if `value` is a boolean', () => { + const type = DataTypes.BOOLEAN(); + + expect(() => type.validate(true)).not.to.throw(); + expect(() => type.validate(false)).not.to.throw(); + expect(() => type.validate('1')).to.throw(); + expect(() => type.validate('0')).to.throw(); + expect(() => type.validate('true')).to.throw(); + expect(() => type.validate('false')).to.throw(); + }); + }); +}); + +describe('DataTypes.ENUM', () => { + it('produces an enum', () => { + const User = sequelize.define('User', { + anEnum: DataTypes.ENUM('value 1', 'value 2'), + }); + + const enumType = User.getAttributes().anEnum.type; + assert(typeof enumType !== 'string'); + + expectsql(enumType.toSql(), { + postgres: '"public"."enum_Users_anEnum"', + 'mysql mariadb': `ENUM('value 1', 'value 2')`, + // SQL Server does not support enums, we use text + a check constraint instead + mssql: `NVARCHAR(255)`, + sqlite3: 'TEXT', + 'db2 ibmi snowflake': 'VARCHAR(255)', + }); + }); + + it('supports TypeScript enums', () => { + enum Test { + A = 'A', + B = 'B', + C = 'C', + } + + const User = sequelize.define('User', { + enum1: DataTypes.ENUM({ values: Test }), + enum2: DataTypes.ENUM(Test), + }); + + const attributes = User.getAttributes(); + + const enum1: ENUM = attributes.enum1.type as ENUM; + expect(enum1.options.values).to.deep.eq(['A', 'B', 'C']); + + const enum2: ENUM = attributes.enum2.type as ENUM; + expect(enum2.options.values).to.deep.eq(['A', 'B', 'C']); + }); + + it('throws if the TS enum values are not equal to their keys', () => { + enum Test { + A = 'a', + } + + expect(() => { + sequelize.define('User', { + anEnum: DataTypes.ENUM({ values: Test }), + }); + }).to.throwWithCause( + Error, + 'DataTypes.ENUM has been constructed incorrectly: When specifying values as a TypeScript enum or an object of key-values, the values of the object must be equal to their keys.', + ); + }); + + typeTest('accepts readonly arrays', () => { + const values: readonly string[] = ['value 1', 'value 2']; + + sequelize.define('User', { + anEnum: DataTypes.ENUM(values), + }); + }); + + it('raises an error if the legacy "values" property is specified', () => { + expect(() => { + sequelize.define('omnomnom', { + bla: { + type: DataTypes.ENUM('a', 'b'), + // @ts-expect-error -- property should not be specified but we're testing that it throws + values: ['a', 'b'], + }, + }); + }).to.throwWithCause(Error, 'The "values" property has been removed from column definitions.'); + }); + + it('raises an error if no values are defined', () => { + expect(() => { + sequelize.define('omnomnom', { + bla: { type: DataTypes.ENUM }, + }); + }).to.throwWithCause( + Error, + 'DataTypes.ENUM cannot be used without specifying its possible enum values.', + ); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.ENUM('foo'); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid choice for enum [ 'foo' ]`); + }); + + it('should not throw if `value` is a valid choice', () => { + const type = DataTypes.ENUM('foobar', 'foobiz'); + + expect(() => type.validate('foobar')).not.to.throw(); + expect(() => type.validate('foobiz')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.RANGE', () => { + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.RANGE(DataTypes.INTEGER); + + expect(() => { + type.validate('foobar'); + }).to.throw( + ValidationErrorItem, + 'A range must either be an array with two elements, or an empty array for the empty range.', + ); + }); + + it('should throw an error if `value` is not an array with two elements', () => { + const type = DataTypes.RANGE(DataTypes.INTEGER); + + expect(() => { + type.validate([1]); + }).to.throw( + ValidationErrorItem, + 'A range must either be an array with two elements, or an empty array for the empty range.', + ); + }); + + it('should not throw if `value` is a range', () => { + const type = DataTypes.RANGE(DataTypes.INTEGER); + + expect(() => type.validate([1, 2])).not.to.throw(); + }); + }); +}); + +describe('DataTypes.JSON', () => { + testDataTypeSql('JSON', DataTypes.JSON, { + default: new Error( + `${dialectName} does not support the JSON data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ), + + // All dialects must support DataTypes.JSON. If your dialect does not have a native JSON type, use an as-big-as-possible text type instead. + 'mariadb mysql postgres': 'JSON', + // SQL server supports JSON functions, but it is stored as a string with a ISJSON constraint. + mssql: 'NVARCHAR(MAX)', + sqlite3: 'TEXT', + }); + + describe('escape', () => { + if (!dialect.supports.dataTypes.JSON) { + return; + } + + it('escapes plain string', () => { + expectsql(queryGenerator.escape('string', { type: new DataTypes.JSON() }), { + default: `'"string"'`, + mysql: `CAST('"string"' AS JSON)`, + mssql: `N'"string"'`, + }); + }); + + it('escapes plain int', () => { + expectsql(queryGenerator.escape(0, { type: new DataTypes.JSON() }), { + default: `'0'`, + mysql: `CAST('0' AS JSON)`, + mssql: `N'0'`, + }); + expectsql(queryGenerator.escape(123, { type: new DataTypes.JSON() }), { + default: `'123'`, + mysql: `CAST('123' AS JSON)`, + mssql: `N'123'`, + }); + }); + + it('escapes boolean', () => { + expectsql(queryGenerator.escape(true, { type: new DataTypes.JSON() }), { + default: `'true'`, + mysql: `CAST('true' AS JSON)`, + mssql: `N'true'`, + }); + expectsql(queryGenerator.escape(false, { type: new DataTypes.JSON() }), { + default: `'false'`, + mysql: `CAST('false' AS JSON)`, + mssql: `N'false'`, + }); + }); + + it('escapes JS null as the JSON null', () => { + expectsql(queryGenerator.escape(null, { type: new DataTypes.JSON() }), { + default: `'null'`, + mysql: `CAST('null' AS JSON)`, + mssql: `N'null'`, + }); + }); + + it('nested object', () => { + expectsql( + queryGenerator.escape( + { some: 'nested', more: { nested: true }, answer: 42 }, + { type: new DataTypes.JSON() }, + ), + { + default: `'{"some":"nested","more":{"nested":true},"answer":42}'`, + mysql: `CAST('{"some":"nested","more":{"nested":true},"answer":42}' AS JSON)`, + mssql: `N'{"some":"nested","more":{"nested":true},"answer":42}'`, + }, + ); + }); + }); + + describe('with nullJsonStringification = sql', () => { + if (!dialect.supports.dataTypes.JSON) { + return; + } + + const sqlNullQueryGenerator = createSequelizeInstance({ + nullJsonStringification: 'sql', + }).queryGenerator; + + it('escapes JS null as the SQL null', () => { + expectsql(sqlNullQueryGenerator.escape(null, { type: new DataTypes.JSON() }), { + default: `NULL`, + }); + }); + + it('escapes nested JS null as the JSON null', () => { + expectsql(sqlNullQueryGenerator.escape({ a: null }, { type: new DataTypes.JSON() }), { + default: `'{"a":null}'`, + mysql: `CAST('{"a":null}' AS JSON)`, + mssql: `N'{"a":null}'`, + }); + }); + }); + + describe('with nullJsonStringification = explicit', () => { + if (!dialect.supports.dataTypes.JSON) { + return; + } + + const explicitNullQueryGenerator = createSequelizeInstance({ + nullJsonStringification: 'explicit', + }).queryGenerator; + + it('rejects the JS null when used as the top level value', () => { + expect(() => + explicitNullQueryGenerator.escape(null, { type: new DataTypes.JSON() }), + ).to.throw(/"nullJsonStringification" option is set to "explicit"/); + }); + + it('escapes nested JS null as the JSON null', () => { + expectsql(explicitNullQueryGenerator.escape({ a: null }, { type: new DataTypes.JSON() }), { + default: `'{"a":null}'`, + mysql: `CAST('{"a":null}' AS JSON)`, + mssql: `N'{"a":null}'`, + }); + }); + + it('escapes SQL_NULL as NULL', () => { + expectsql(explicitNullQueryGenerator.escape(SQL_NULL, { type: new DataTypes.JSON() }), { + default: `NULL`, + }); + }); + + it('escapes JSON_NULL as NULL', () => { + expectsql(explicitNullQueryGenerator.escape(JSON_NULL, { type: new DataTypes.JSON() }), { + default: `'null'`, + mysql: `CAST('null' AS JSON)`, + mssql: `N'null'`, + }); + }); + }); +}); + +describe('DataTypes.JSONB', () => { + testDataTypeSql('JSONB', DataTypes.JSONB, { + default: new Error( + `${dialectName} does not support the JSONB data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ), + postgres: 'JSONB', + }); +}); + +describe('DataTypes.HSTORE', () => { + describe('toSql', () => { + testDataTypeSql('HSTORE', DataTypes.HSTORE, { + default: new Error( + `${dialectName} does not support the HSTORE data type.\nSee https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`, + ), + postgres: 'HSTORE', + }); + }); + + describe('validate', () => { + if (!sequelize.dialect.supports.dataTypes.HSTORE) { + return; + } + + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.HSTORE(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid hstore`); + }); + + it('should not throw if `value` is an hstore', () => { + const type = DataTypes.HSTORE(); + + expect(() => type.validate({ foo: 'bar' })).not.to.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/data-types/string-types.test.ts b/packages/core/test/unit/data-types/string-types.test.ts new file mode 100644 index 000000000000..e4df01fa7f28 --- /dev/null +++ b/packages/core/test/unit/data-types/string-types.test.ts @@ -0,0 +1,204 @@ +import type { DataTypeInstance } from '@sequelize/core'; +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialect = sequelize.dialect; +const dialectName = dialect.name; + +describe('DataTypes.STRING', () => { + describe('toSql', () => { + const binaryCollationUnsupportedError = + new Error(`${dialectName} does not support the STRING.BINARY data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('STRING', DataTypes.STRING, { + default: 'VARCHAR(255)', + mssql: 'NVARCHAR(255)', + sqlite3: 'TEXT', + }); + + testDataTypeSql('STRING(1234)', DataTypes.STRING(1234), { + default: 'VARCHAR(1234)', + mssql: 'NVARCHAR(1234)', + sqlite3: 'TEXT', + }); + + testDataTypeSql('STRING({ length: 1234 })', DataTypes.STRING({ length: 1234 }), { + default: 'VARCHAR(1234)', + mssql: 'NVARCHAR(1234)', + sqlite3: 'TEXT', + }); + + testDataTypeSql('STRING(1234).BINARY', DataTypes.STRING(1234).BINARY, { + default: 'VARCHAR(1234) BINARY', + 'db2 ibmi': 'VARCHAR(1234) FOR BIT DATA', + sqlite3: 'TEXT COLLATE BINARY', + 'mssql postgres': binaryCollationUnsupportedError, + }); + + testDataTypeSql('STRING.BINARY', DataTypes.STRING.BINARY, { + default: 'VARCHAR(255) BINARY', + 'db2 ibmi': 'VARCHAR(255) FOR BIT DATA', + sqlite3: 'TEXT COLLATE BINARY', + 'mssql postgres': binaryCollationUnsupportedError, + }); + }); + + describe('validate', () => { + it('should not throw if `value` is a string', () => { + const type = new DataTypes.STRING(); + + expect(() => type.validate('foobar')).not.to.throw(); + expect(() => type.validate(12)).to.throw(); + }); + }); +}); + +describe('DataTypes.TEXT', () => { + describe('toSql', () => { + testDataTypeSql('TEXT', DataTypes.TEXT, { + default: 'TEXT', + 'ibmi db2': 'CLOB(2147483647)', + mssql: 'NVARCHAR(MAX)', // in mssql text is actually representing a non unicode text field + }); + + testDataTypeSql('TEXT("tiny")', DataTypes.TEXT('tiny'), { + default: 'TEXT', + 'ibmi db2': 'VARCHAR(256)', + mssql: 'NVARCHAR(256)', + 'mariadb mysql': 'TINYTEXT', + }); + + testDataTypeSql('TEXT({ length: "tiny" })', DataTypes.TEXT({ length: 'tiny' }), { + default: 'TEXT', + 'ibmi db2': 'VARCHAR(256)', + mssql: 'NVARCHAR(256)', + 'mariadb mysql': 'TINYTEXT', + }); + + testDataTypeSql('TEXT("medium")', DataTypes.TEXT('medium'), { + default: 'TEXT', + 'ibmi db2': 'CLOB(16777216)', + mssql: 'NVARCHAR(MAX)', + 'mariadb mysql': 'MEDIUMTEXT', + }); + + testDataTypeSql('TEXT("long")', DataTypes.TEXT('long'), { + default: 'TEXT', + 'ibmi db2': 'CLOB(2147483647)', + mssql: 'NVARCHAR(MAX)', + 'mariadb mysql': 'LONGTEXT', + }); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.TEXT(); + + expect(() => { + type.validate(12_345); + }).to.throw(ValidationErrorItem, '12345 is not a valid string'); + }); + + it('should not throw if `value` is a string', () => { + const type = DataTypes.TEXT(); + + expect(() => type.validate('foobar')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.CITEXT', () => { + describe('toSql', () => { + testDataTypeSql('CITEXT', DataTypes.CITEXT, { + default: + new Error(`${dialectName} does not support the case-insensitive text (CITEXT) data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`), + postgres: 'CITEXT', + sqlite3: 'TEXT COLLATE NOCASE', + }); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type: DataTypeInstance = DataTypes.CITEXT(); + + expect(() => { + type.validate(12_345); + }).to.throw(ValidationErrorItem, '12345 is not a valid string'); + }); + + it('should not throw if `value` is a string', () => { + const type = DataTypes.CITEXT(); + + expect(() => type.validate('foobar')).not.to.throw(); + }); + }); +}); + +describe('DataTypes.CHAR', () => { + describe('toSql', () => { + const binaryNotSupportedError = + new Error(`${dialectName} does not support the CHAR.BINARY data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + const charNotSupportedError = new Error(`${dialectName} does not support the CHAR data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`); + + testDataTypeSql('CHAR', DataTypes.CHAR, { + default: 'CHAR(255)', + sqlite3: charNotSupportedError, + }); + + testDataTypeSql('CHAR(12)', DataTypes.CHAR(12), { + default: 'CHAR(12)', + sqlite3: charNotSupportedError, + }); + + testDataTypeSql('CHAR({ length: 12 })', DataTypes.CHAR({ length: 12 }), { + default: 'CHAR(12)', + sqlite3: charNotSupportedError, + }); + + testDataTypeSql('CHAR(12).BINARY', DataTypes.CHAR(12).BINARY, { + default: 'CHAR(12) BINARY', + 'db2 ibmi': 'CHAR(12) FOR BIT DATA', + sqlite3: charNotSupportedError, + 'postgres mssql': binaryNotSupportedError, + }); + + testDataTypeSql('CHAR.BINARY', DataTypes.CHAR.BINARY, { + default: 'CHAR(255) BINARY', + 'db2 ibmi': 'CHAR(255) FOR BIT DATA', + sqlite3: charNotSupportedError, + 'postgres mssql': binaryNotSupportedError, + }); + }); +}); + +describe('DataTypes.TSVECTOR', () => { + describe('toSql', () => { + testDataTypeSql('TSVECTOR', DataTypes.TSVECTOR, { + default: new Error(`${dialectName} does not support the TSVECTOR data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`), + postgres: 'TSVECTOR', + }); + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.TSVECTOR(); + + expect(() => { + type.validate(12_345); + }).to.throw(ValidationErrorItem, '12345 is not a valid string'); + }); + + it('should not throw if `value` is a string', () => { + const type = DataTypes.TSVECTOR(); + + expect(() => type.validate('foobar')).not.to.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/data-types/temporal-types.test.ts b/packages/core/test/unit/data-types/temporal-types.test.ts new file mode 100644 index 000000000000..e00cd34f144f --- /dev/null +++ b/packages/core/test/unit/data-types/temporal-types.test.ts @@ -0,0 +1,152 @@ +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; +import { testDataTypeSql } from './_utils'; + +const dialect = sequelize.dialect; + +const now = new Date(); +const nowString = now.toISOString(); +const nowDateOnly = nowString.slice(0, 10); + +describe('DataTypes.DATE', () => { + describe('toSql', () => { + testDataTypeSql('DATE', DataTypes.DATE, { + 'db2 ibmi snowflake': 'TIMESTAMP', + postgres: 'TIMESTAMP WITH TIME ZONE', + mssql: 'DATETIMEOFFSET', + 'mariadb mysql': 'DATETIME', + sqlite3: 'TEXT', + }); + + testDataTypeSql('DATE(0)', DataTypes.DATE(0), { + postgres: 'TIMESTAMP(0) WITH TIME ZONE', + mssql: 'DATETIMEOFFSET(0)', + 'mariadb mysql': 'DATETIME(0)', + 'db2 ibmi snowflake': 'TIMESTAMP(0)', + sqlite3: 'TEXT', + }); + + testDataTypeSql('DATE(6)', DataTypes.DATE(6), { + 'db2 ibmi snowflake': 'TIMESTAMP(6)', + postgres: 'TIMESTAMP(6) WITH TIME ZONE', + mssql: 'DATETIMEOFFSET(6)', + mariadb: 'DATETIME(6)', + mysql: 'DATETIME(6)', + sqlite3: 'TEXT', + }); + }); + + const type = DataTypes.DATE().toDialectDataType(dialect); + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid date`); + }); + + it('does not throw if the value is a date string or Date object', () => { + expect(() => type.validate(now)).not.to.throw(); + expect(() => type.validate(nowString)).not.to.throw(); + }); + + if (dialect.supports.dataTypes.DATETIME.infinity) { + it('accepts Infinity/-Infinity', () => { + expect(() => type.validate(Number.POSITIVE_INFINITY)).not.to.throw(); + expect(() => type.validate(Number.NEGATIVE_INFINITY)).not.to.throw(); + }); + } + }); + + describe('sanitize', () => { + it('sanitizes a Date object or string to a Date object', () => { + expect(type.sanitize(now)).to.equalTime(now); + expect(type.sanitize(nowString)).to.equalTime(now); + }); + + if (dialect.supports.dataTypes.DATETIME.infinity) { + it('does not modify numeric Infinity/-Infinity', () => { + expect(type.sanitize(Number.POSITIVE_INFINITY)).to.equal(Number.POSITIVE_INFINITY); + expect(type.sanitize(Number.NEGATIVE_INFINITY)).to.equal(Number.NEGATIVE_INFINITY); + }); + + it('sanitizes string "Infinity"/"-Infinity" to numeric Infinity/-Infinity', () => { + expect(type.sanitize('Infinity')).to.equal(Number.POSITIVE_INFINITY); + expect(type.sanitize('-Infinity')).to.equal(Number.NEGATIVE_INFINITY); + }); + } + }); + + describe('toBindableValue', () => { + if (dialect.supports.dataTypes.DATETIME.infinity) { + it('stringifies numeric Infinity/-Infinity', () => { + expect(type.toBindableValue(Number.POSITIVE_INFINITY)).to.equal('infinity'); + expect(type.toBindableValue(Number.NEGATIVE_INFINITY)).to.equal('-infinity'); + }); + } + }); +}); + +describe('DataTypes.DATEONLY', () => { + describe('toSql', () => { + testDataTypeSql('DATEONLY', DataTypes.DATEONLY, { + default: 'DATE', + sqlite3: 'TEXT', + }); + }); + + const type = DataTypes.DATEONLY().toDialectDataType(dialect); + describe('validate', () => { + if (dialect.supports.dataTypes.DATEONLY.infinity) { + it('DATEONLY should stringify Infinity/-Infinity to infinity/-infinity', () => { + expect(type.toBindableValue(Number.POSITIVE_INFINITY)).to.equal('infinity'); + expect(type.toBindableValue(Number.NEGATIVE_INFINITY)).to.equal('-infinity'); + }); + } + }); + + describe('sanitize', () => { + it('sanitizes a Date object or string to a string', () => { + expect(type.sanitize(now)).to.equal(nowDateOnly); + expect(type.sanitize(nowString)).to.equal(nowDateOnly); + }); + + if (dialect.supports.dataTypes.DATEONLY.infinity) { + it('does not modify numeric infinity', () => { + expect(type.sanitize(Number.POSITIVE_INFINITY)).to.equal(Number.POSITIVE_INFINITY); + expect(type.sanitize(Number.NEGATIVE_INFINITY)).to.equal(Number.NEGATIVE_INFINITY); + }); + + it('sanitizes string Infinity/-Infinity to their numeric counterpart', () => { + expect(type.sanitize('Infinity')).to.equal(Number.POSITIVE_INFINITY); + expect(type.sanitize('-Infinity')).to.equal(Number.NEGATIVE_INFINITY); + }); + } + }); +}); + +describe('DataTypes.TIME', () => { + describe('toSql', () => { + testDataTypeSql('TIME', DataTypes.TIME, { + default: 'TIME', + sqlite3: 'TEXT', + }); + + testDataTypeSql('TIME(6)', DataTypes.TIME(6), { + default: 'TIME(6)', + db2: new Error(`db2 does not support the TIME(precision) data type. +See https://sequelize.org/docs/v7/models/data-types/ for a list of supported data types.`), + sqlite3: 'TEXT', + }); + }); +}); + +describe('DataTypes.NOW', () => { + describe('toSql', () => { + testDataTypeSql('NOW', DataTypes.NOW, { + default: 'NOW', + db2: 'CURRENT TIME', + mssql: 'GETDATE()', + }); + }); +}); diff --git a/packages/core/test/unit/data-types/uuid.test.ts b/packages/core/test/unit/data-types/uuid.test.ts new file mode 100644 index 000000000000..fdf416abe350 --- /dev/null +++ b/packages/core/test/unit/data-types/uuid.test.ts @@ -0,0 +1,110 @@ +import { DataTypes, ValidationErrorItem } from '@sequelize/core'; +import { expect } from 'chai'; +import util from 'node:util'; +import { v1 as generateV1, v4 as generateV4 } from 'uuid'; +import { allowDeprecationsInSuite } from '../../support'; +import { testDataTypeSql } from './_utils'; + +describe('DataTypes.UUID', () => { + describe('toSql', () => { + testDataTypeSql('UUID', DataTypes.UUID, { + postgres: 'UUID', + ibmi: 'CHAR(36)', + db2: 'CHAR(36) FOR BIT DATA', + mssql: 'UNIQUEIDENTIFIER', + 'mariadb mysql': 'CHAR(36) BINARY', + snowflake: 'VARCHAR(36)', + sqlite3: 'TEXT', + }); + }); + + describe('validate', () => { + const allVersions = DataTypes.UUID(); + const v1 = DataTypes.UUID.V1; + const v4 = DataTypes.UUID.V4; + + it('should throw an error if `value` is invalid', () => { + expect(() => { + allVersions.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid uuid`); + + expect(() => { + allVersions.validate(['foobar']); + }).to.throw(ValidationErrorItem, `[ 'foobar' ] is not a valid uuid`); + + const uuidV4 = generateV4(); + expect(() => { + v1.validate(uuidV4); + }).to.throw(ValidationErrorItem, util.format('%O is not a valid uuid (version: 1)', uuidV4)); + + const uuidV1 = generateV1(); + expect(() => { + v4.validate(uuidV1); + }).to.throw(ValidationErrorItem, util.format('%O is not a valid uuid (version: 4)', uuidV1)); + }); + + it('should not throw if `value` is an uuid', () => { + expect(() => allVersions.validate(generateV4())).not.to.throw(); + expect(() => allVersions.validate(generateV1())).not.to.throw(); + expect(() => v1.validate(generateV1())).not.to.throw(); + expect(() => v4.validate(generateV4())).not.to.throw(); + }); + }); +}); + +describe('DataTypes.UUIDV1', () => { + allowDeprecationsInSuite(['SEQUELIZE0026']); + + testDataTypeSql('UUIDV1', DataTypes.UUIDV1, { + default: new Error('toSQL should not be called on DataTypes.UUIDV1'), + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.UUIDV1(); + + expect(() => { + type.validate('foobar'); + }).to.throw(ValidationErrorItem, `'foobar' is not a valid uuid`); + + expect(() => { + type.validate(['foobar']); + }).to.throw(ValidationErrorItem, `[ 'foobar' ] is not a valid uuidv1`); + }); + + it('should not throw if `value` is an uuid', () => { + const type = DataTypes.UUIDV1(); + + expect(() => type.validate(generateV1())).not.to.throw(); + }); + }); +}); + +describe('DataTypes.UUIDV4', () => { + allowDeprecationsInSuite(['SEQUELIZE0026']); + + testDataTypeSql('UUIDV4', DataTypes.UUIDV4, { + default: new Error('toSQL should not be called on DataTypes.UUIDV4'), + }); + + describe('validate', () => { + it('should throw an error if `value` is invalid', () => { + const type = DataTypes.UUIDV4(); + const value = generateV1(); + + expect(() => { + type.validate(value); + }).to.throw(ValidationErrorItem, util.format('%O is not a valid uuidv4', value)); + + expect(() => { + type.validate(['foobar']); + }).to.throw(ValidationErrorItem, `[ 'foobar' ] is not a valid uuidv4`); + }); + + it('should not throw if `value` is an uuid', () => { + const type = DataTypes.UUIDV4(); + + expect(() => type.validate(generateV4())).not.to.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/decorators/associations.test.ts b/packages/core/test/unit/decorators/associations.test.ts new file mode 100644 index 000000000000..36ea68ca14de --- /dev/null +++ b/packages/core/test/unit/decorators/associations.test.ts @@ -0,0 +1,433 @@ +import type { InferAttributes, NonAttribute } from '@sequelize/core'; +import { BelongsToManyAssociation, Model } from '@sequelize/core'; +import { BelongsTo, BelongsToMany, HasMany, HasOne } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import assert from 'node:assert'; +import { resetSequelizeInstance, sequelize, typeTest } from '../../support'; + +const CANNOT_INHERIT_ASSOCIATION_ERROR = + /Models that use @HasOne, @HasMany, or @BelongsToMany associations cannot be inherited from/; +const CANNOT_USE_AS_ERROR = + 'The "as" option is not allowed when using association decorators. The name of the decorated field is used as the association name.'; + +describe('@BelongsTo', () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + it('defines a belongsTo association', () => { + class User extends Model> {} + + class Profile extends Model> { + @BelongsTo(() => User, 'userId') + declare user1: Profile; + + @BelongsTo(() => User, { foreignKey: 'userId' }) + declare user2: User; + + // Added in https://github.com/sequelize/sequelize-typescript/pull/1206 to help with circular dependencies. + @BelongsTo(seq => seq.models.getOrThrow('User'), 'userId') + declare user3: User; + + declare userId: number; + } + + sequelize.addModels([User, Profile]); + + expect(Object.keys(Profile.associations)).to.deep.eq(['user1', 'user2', 'user3']); + expect(Profile.associations.user1.associationType).to.eq('BelongsTo'); + expect(Profile.associations.user1.target).to.eq(User); + expect(Profile.associations.user3.target).to.eq(User); + expect(Profile.associations.user1.foreignKey).to.eq('userId'); + expect(Profile.modelDefinition.attributes.get('userId')!.references).to.deep.include({ + table: User.table, + key: 'id', + }); + }); + + // This test is temporarily disabled until we find a solution that works with generics + // typeTest('errors if the foreign key does not exist on the target model', () => { + // class User extends Model> {} + // + // // eslint-disable-next-line @typescript-eslint/no-unused-vars + // class Profile extends Model> { + // // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + // @BelongsTo(() => User, 'usrId') + // declare user1: Profile; + // + // // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + // @BelongsTo(() => User, { foreignKey: 'usrId' }) + // declare user2: User; + // + // declare userId: number; + // } + // }); + + typeTest('does not error when the model is generic (inheritance)', () => { + class User extends Model> {} + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class Profile = Profile> extends Model> { + @BelongsTo(() => User, 'userId') + declare user: Profile; + + declare userId: number; + } + }); + + it('is inherited', () => { + class DummyModel extends Model> {} + + class BaseUser extends Model> { + @BelongsTo(() => DummyModel, { + foreignKey: 'dummyId', + scope: { + deletedAt: null, + }, + }) + declare dummy?: NonAttribute; + + declare dummyId: number; + } + + class User extends BaseUser {} + + sequelize.addModels([DummyModel, User]); + // register in two steps to make sure you can register a model without registering its parent (when the parent is abstract) + sequelize.addModels([BaseUser]); + + expect(Object.keys(User.associations)).to.deep.eq(['dummy']); + }); + + it('throws when inherited and uses the "inverse" option', () => { + class DummyModel extends Model> {} + + class BaseUser extends Model> { + @BelongsTo(() => DummyModel, { + foreignKey: 'dummyId', + inverse: { + as: 'users', + type: 'hasMany', + }, + }) + declare dummy?: NonAttribute; + + declare dummyId: number; + } + + class User extends BaseUser {} + + expect(() => sequelize.addModels([User, DummyModel])).to.throw( + /Models that use @BelongsTo associations with the "inverse" option cannot be inherited from/, + ); + }); + + it('throws if the same association is declared twice', () => { + class DummyModel extends Model> {} + + class BaseUser extends Model> { + @BelongsTo(() => DummyModel, 'dummyId') + declare dummy?: NonAttribute; + + declare dummyId: number; + } + + class User extends BaseUser { + @BelongsTo(() => DummyModel, 'dummyId') + declare dummy?: NonAttribute; + } + + expect(() => sequelize.addModels([User, DummyModel])).to.throw( + `You have defined two associations with the same name "dummy" on the model "User"`, + ); + }); + + it('throws if the "as" option is used', () => { + class DummyModel extends Model> {} + + expect(() => { + class User extends Model> { + @BelongsTo(() => DummyModel, { + foreignKey: 'dummyId', + // @ts-expect-error -- forbidden option + as: 'dummy', + }) + declare dummy?: NonAttribute; + + declare dummyId: number; + } + + return User; + }).to.throw(CANNOT_USE_AS_ERROR); + }); +}); + +describe('@HasOne', () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + it('defines a hasOne association', () => { + class User extends Model> { + @HasOne(() => Profile, 'userId') + declare profile1: Profile; + + @HasOne(() => Profile, { foreignKey: 'userId' }) + declare profile2: Profile; + + // Added in https://github.com/sequelize/sequelize-typescript/pull/1206 to help with circular dependencies. + @HasOne(seq => seq.models.getOrThrow('Profile'), 'userId') + declare profile3: Profile; + } + + class Profile extends Model> { + declare userId: number; + } + + sequelize.addModels([User, Profile]); + + expect(Object.keys(User.associations)).to.deep.eq(['profile1', 'profile2', 'profile3']); + expect(User.associations.profile1.associationType).to.eq('HasOne'); + expect(User.associations.profile1.target).to.eq(Profile); + expect(User.associations.profile3.target).to.eq(Profile); + expect(User.associations.profile1.foreignKey).to.eq('userId'); + expect(Profile.modelDefinition.attributes.get('userId')!.references).to.deep.include({ + table: User.table, + key: 'id', + }); + }); + + typeTest('errors if the foreign key does not exist on the target model', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class User extends Model> { + // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + @HasOne(() => Profile, 'usrId') + declare profile1: Profile; + + // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + @HasOne(() => Profile, { foreignKey: 'usrId' }) + declare profile2: Profile; + } + + class Profile extends Model> { + declare userId: number; + } + }); + + it('throws when inherited', () => { + class DummyModel extends Model> { + declare dummyId: number; + } + + class BaseUser extends Model> { + @HasOne(() => DummyModel, 'dummyId') + declare dummy?: NonAttribute; + } + + class User extends BaseUser {} + + expect(() => sequelize.addModels([DummyModel, User])).to.throw( + CANNOT_INHERIT_ASSOCIATION_ERROR, + ); + }); + + it('throws if the "as" option is used', () => { + class DummyModel extends Model> { + declare dummyId: number; + } + + expect(() => { + class User extends Model> { + @HasOne(() => DummyModel, { + foreignKey: 'dummyId', + // @ts-expect-error -- forbidden option + as: 'dummy', + }) + declare dummy?: NonAttribute; + } + + return User; + }).to.throw(CANNOT_USE_AS_ERROR); + }); +}); + +describe('@HasMany', () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + it('defines a hasMany association', () => { + class User extends Model> { + @HasMany(() => Profile, 'userId') + declare profile1: Profile; + + @HasMany(() => Profile, { foreignKey: 'userId' }) + declare profile2: Profile; + + // Added in https://github.com/sequelize/sequelize-typescript/pull/1206 to help with circular dependencies. + @HasMany(seq => seq.models.getOrThrow('Profile'), { foreignKey: 'userId' }) + declare profile3: Profile; + } + + class Profile extends Model> { + declare userId: number; + } + + sequelize.addModels([User, Profile]); + + expect(Object.keys(User.associations)).to.deep.eq(['profile1', 'profile2', 'profile3']); + expect(User.associations.profile1.associationType).to.eq('HasMany'); + expect(User.associations.profile1.target).to.eq(Profile); + expect(User.associations.profile3.target).to.eq(Profile); + expect(User.associations.profile1.foreignKey).to.eq('userId'); + expect(Profile.modelDefinition.attributes.get('userId')!.references).to.deep.include({ + table: User.table, + key: 'id', + }); + }); + + typeTest('errors if the foreign key does not exist on the target model', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class User extends Model> { + // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + @HasMany(() => Profile, 'usrId') + declare profile1: Profile; + + // @ts-expect-error -- This must error, "usrId" does not exist on "Profile" + @HasMany(() => Profile, { foreignKey: 'usrId' }) + declare profile2: Profile; + } + + class Profile extends Model> { + declare userId: number; + } + }); + + it('throws when inherited', () => { + class DummyModel extends Model> { + declare dummyId: number; + } + + class BaseUser extends Model> { + @HasMany(() => DummyModel, 'dummyId') + declare dummies?: NonAttribute; + } + + class User extends BaseUser {} + + expect(() => sequelize.addModels([DummyModel, User])).to.throw( + CANNOT_INHERIT_ASSOCIATION_ERROR, + ); + }); + + it('throws if the "as" option is used', () => { + class DummyModel extends Model> { + declare dummyId: number; + } + + expect(() => { + class User extends Model> { + @HasMany(() => DummyModel, { + foreignKey: 'dummyId', + // @ts-expect-error -- forbidden option + as: 'dummy', + }) + declare dummy?: NonAttribute; + } + + return User; + }).to.throw(CANNOT_USE_AS_ERROR); + }); +}); + +describe('@BelongsToMany', () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + it('defines a belongsToMany association', () => { + class User extends Model> { + @BelongsToMany(() => Role, { + through: 'UserRole', + inverse: { as: 'users' }, + }) + declare roles: Role[]; + } + + class Role extends Model> {} + + sequelize.addModels([User, Role]); + + expect(Object.keys(User.associations)).to.deep.eq(['roles', 'rolesUsers', 'roleUser']); + expect(Object.keys(Role.associations)).to.deep.eq(['users', 'usersRoles', 'userRole']); + + const userToRole = User.associations.roles; + + assert(userToRole instanceof BelongsToManyAssociation); + + expect(userToRole.associationType).to.eq('BelongsToMany'); + expect(Role.associations.users.associationType).to.eq('BelongsToMany'); + + expect(userToRole.target).to.eq(Role); + expect(userToRole.throughModel).to.eq(sequelize.models.getOrThrow('UserRole')); + }); + + it('supports lazy target & through', () => { + class UserRole extends Model> {} + + class User extends Model> { + @BelongsToMany(seq => seq.models.getOrThrow('Role'), { + through: seq => seq.models.getOrThrow('UserRole'), + inverse: { as: 'users' }, + }) + declare roles: Role[]; + } + + class Role extends Model> {} + + sequelize.addModels([User, UserRole, Role]); + + const userToRole = User.associations.roles; + assert(userToRole instanceof BelongsToManyAssociation); + expect(userToRole.throughModel).to.eq(UserRole); + + const roleToUser = Role.associations.users; + assert(roleToUser instanceof BelongsToManyAssociation); + expect(roleToUser.throughModel).to.eq(UserRole); + }); + + it('throws when inherited', () => { + class DummyModel extends Model> {} + + class BaseUser extends Model> { + @BelongsToMany(() => DummyModel, { + through: 'DummyUser', + }) + declare dummies?: NonAttribute; + } + + class User extends BaseUser {} + + expect(() => sequelize.addModels([DummyModel, User])).to.throw( + CANNOT_INHERIT_ASSOCIATION_ERROR, + ); + }); + + it('throws if the "as" option is used', () => { + class Role extends Model> {} + + expect(() => { + class User extends Model> { + @BelongsToMany(() => Role, { + // @ts-expect-error -- forbidden option + as: 'roles', + through: 'UserRole', + inverse: { as: 'users' }, + }) + declare roles: Role[]; + } + + return User; + }).to.throw(CANNOT_USE_AS_ERROR); + }); +}); diff --git a/packages/core/test/unit/decorators/attribute-validator.test.ts b/packages/core/test/unit/decorators/attribute-validator.test.ts new file mode 100644 index 000000000000..bb0767048970 --- /dev/null +++ b/packages/core/test/unit/decorators/attribute-validator.test.ts @@ -0,0 +1,24 @@ +import { DataTypes, Model } from '@sequelize/core'; +import { Attribute, ValidateAttribute } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('@ValidateAttribute legacy decorator', () => { + it('can add an attribute validator', async () => { + class User extends Model { + @Attribute(DataTypes.STRING) + @ValidateAttribute({ + myCustomValidator() { + throw new Error('test error'); + }, + }) + declare name: string; + } + + sequelize.addModels([User]); + + const user = User.build({ name: 'test' }); + + await expect(user.validate()).to.be.rejectedWith('test error'); + }); +}); diff --git a/packages/core/test/unit/decorators/attribute.test.ts b/packages/core/test/unit/decorators/attribute.test.ts new file mode 100644 index 000000000000..07d3134c7feb --- /dev/null +++ b/packages/core/test/unit/decorators/attribute.test.ts @@ -0,0 +1,705 @@ +import type { InferAttributes } from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { + AllowNull, + Attribute, + AutoIncrement, + ColumnName, + Comment, + Default, + Index, + NotNull, + PrimaryKey, + Table, + Unique, + createIndexDecorator, +} from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import omit from 'lodash/omit'; +import { sequelize } from '../../support'; + +describe(`@Attribute legacy decorator`, () => { + it('does not init the model itself', () => { + class Test extends Model { + @Attribute(DataTypes.BIGINT) + declare id: bigint; + } + + expect(() => Test.build()).to.throw(/has not been initialized/); + }); + + it('prevents using Model.init', () => { + class Test extends Model { + @Attribute(DataTypes.BIGINT) + declare id: bigint; + } + + expect(() => Test.init({}, { sequelize })).to.throw( + /pass your model to the Sequelize constructor/, + ); + }); + + if (sequelize.dialect.supports.dataTypes.BIGINT) { + it('registers an attribute when sequelize.addModels is called', () => { + class BigIntModel extends Model> { + @Attribute({ type: DataTypes.BIGINT, primaryKey: true }) + declare id: bigint; + + @Attribute(DataTypes.STRING) + declare name: string; + } + + sequelize.addModels([BigIntModel]); + + expect(BigIntModel.getAttributes()).to.have.keys(['id', 'createdAt', 'updatedAt', 'name']); + expect(BigIntModel.getAttributes().id.type).to.be.instanceof(DataTypes.BIGINT); + expect(BigIntModel.getAttributes().id.primaryKey).to.eq(true); + expect(BigIntModel.getAttributes().name.type).to.be.instanceof(DataTypes.STRING); + }); + } else { + it('registers an attribute when sequelize.addModels is called', () => { + class IntModel extends Model> { + @Attribute({ type: DataTypes.INTEGER, primaryKey: true }) + declare id: number; + + @Attribute(DataTypes.STRING) + declare name: string; + } + + sequelize.addModels([IntModel]); + + expect(IntModel.getAttributes()).to.have.keys(['id', 'createdAt', 'updatedAt', 'name']); + expect(IntModel.getAttributes().id.type).to.be.instanceof(DataTypes.INTEGER); + expect(IntModel.getAttributes().id.primaryKey).to.eq(true); + expect(IntModel.getAttributes().name.type).to.be.instanceof(DataTypes.STRING); + }); + } + + it('works on getters', () => { + class User extends Model { + @Attribute(DataTypes.STRING) + get name(): string { + return `My name is ${this.getDataValue('name')}`; + } + + set name(value: string) { + this.setDataValue('name', value); + } + } + + sequelize.addModels([User]); + + const user = User.build({}); + user.name = 'Peter'; + + expect(user.name).to.equal('My name is Peter'); + expect(user.getDataValue('name')).to.equal('Peter'); + }); + + it('works on setters', () => { + class User extends Model { + get name(): string { + return `My name is ${this.getDataValue('name')}`; + } + + @Attribute(DataTypes.STRING) + set name(value: string) { + this.setDataValue('name', value); + } + } + + sequelize.addModels([User]); + + const user = User.build({}); + user.name = 'Peter'; + + expect(user.name).to.equal('My name is Peter'); + expect(user.getDataValue('name')).to.equal('Peter'); + }); + + // different decorators can modify the model's options + it('can be used multiple times', () => { + class User extends Model> { + @Attribute({ type: DataTypes.STRING, primaryKey: true }) + @Attribute({ type: DataTypes.STRING, autoIncrement: true }) + @Attribute(DataTypes.STRING) + declare pk: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().pk.primaryKey).to.equal(true); + expect(User.getAttributes().pk.autoIncrement).to.equal(true); + }); + + it('throws if used multiple times with incompatible options', () => { + expect(() => { + class User extends Model { + @Attribute({ type: DataTypes.STRING, primaryKey: true }) + @Attribute({ type: DataTypes.STRING, primaryKey: false }) + declare pk: string; + } + + return User; + }).to.throw(); + }); + + it('merges validate', () => { + class User extends Model> { + @Attribute({ + type: DataTypes.STRING, + validate: { + not: 'abc', + }, + }) + @Attribute({ + type: DataTypes.STRING, + validate: { + is: 'abc', + }, + }) + declare pk: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().pk.validate).to.deep.equal({ not: 'abc', is: 'abc' }); + }); + + it('rejects conflicting validates', () => { + expect(() => { + class User extends Model> { + @Attribute({ + type: DataTypes.STRING, + validate: { + not: 'abc', + }, + }) + @Attribute({ + type: DataTypes.STRING, + validate: { + not: 'def', + }, + }) + declare pk: string; + } + + return User; + }).to.throw(); + }); + + it('merges "unique"', () => { + class User extends Model> { + @Attribute({ + type: DataTypes.STRING, + unique: true, + }) + @Attribute({ + unique: 'firstName-lastName', + }) + @Unique(['firstName-country']) + declare firstName: string; + + @Attribute({ + type: DataTypes.STRING, + unique: 'firstName-lastName', + }) + declare lastName: string; + + @Attribute(DataTypes.STRING) + @Unique('firstName-country') + declare country: string; + } + + sequelize.addModels([User]); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: ['firstName', 'country'], + column: 'firstName', + unique: true, + name: 'firstName-country', + }, + { + fields: ['firstName', 'lastName'], + column: 'firstName', + unique: true, + name: 'firstName-lastName', + }, + { + fields: ['firstName'], + column: 'firstName', + unique: true, + name: 'users_first_name_unique', + }, + ]); + }); + + it('merges "index"', () => { + class User extends Model> { + @Attribute(DataTypes.STRING) + @Attribute({ + index: 'firstName-lastName', + }) + @Index({ + name: 'firstName-country', + }) + @Index + @ColumnName('first_name') + declare firstName: string; + + @Attribute(DataTypes.STRING) + @Index({ + name: 'firstName-lastName', + attribute: { + collate: 'en_US', + }, + }) + declare lastName: string; + + @Attribute(DataTypes.STRING) + @Index('firstName-country') + declare country: string; + } + + sequelize.addModels([User]); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: ['first_name'], + column: 'firstName', + name: 'users_first_name', + }, + { + fields: ['first_name', 'country'], + column: 'firstName', + name: 'firstName-country', + }, + { + fields: [ + 'first_name', + { + collate: 'en_US', + name: 'lastName', + }, + ], + column: 'firstName', + name: 'firstName-lastName', + }, + ]); + }); + + it('is inherited', () => { + class DummyModel extends Model {} + + function validate() { + return true; + } + + class BaseUser extends Model> { + @Attribute({ + type: DataTypes.INTEGER, + allowNull: false, + columnName: 'id', + defaultValue: 42, + unique: { name: 'primaryKeyUnique' }, + index: { name: 'primaryKeyIndex' }, + primaryKey: true, + autoIncrement: true, + autoIncrementIdentity: true, + comment: 'This is a comment', + references: { + model: DummyModel, + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + validate: { + validate, + }, + get() { + return 42; + }, + set() {}, + }) + declare primaryKey: number; + } + + class User extends BaseUser {} + + sequelize.addModels([DummyModel, User]); + // register in two steps to make sure you can register a model without registering its parent (when the parent is abstract) + sequelize.addModels([BaseUser]); + + const descendantPrimaryKey = User.modelDefinition.rawAttributes.primaryKey; + const parentPrimaryKey = BaseUser.modelDefinition.rawAttributes.primaryKey; + + expect(omit(descendantPrimaryKey, ['type'])).to.deep.equal(omit(parentPrimaryKey, ['type'])); + + expect(descendantPrimaryKey.type).to.be.instanceOf(DataTypes.INTEGER); + + // must be different instances: + expect(descendantPrimaryKey.type).to.not.equal(parentPrimaryKey.type); + expect(descendantPrimaryKey.unique).to.not.equal(parentPrimaryKey.unique); + expect(descendantPrimaryKey.index).to.not.equal(parentPrimaryKey.index); + expect(descendantPrimaryKey.references).to.not.equal(parentPrimaryKey.references); + // model should not be cloned + // @ts-expect-error -- typing is broad, not worth checking + expect(descendantPrimaryKey.references.model).to.equal(parentPrimaryKey.references.model); + expect(descendantPrimaryKey.validate).to.not.equal(parentPrimaryKey.validate); + }); + + it('inherits attributes from multiple levels', () => { + abstract class RootUser extends Model> { + @Attribute(DataTypes.STRING) + declare name: string; + + @Attribute(DataTypes.STRING) + declare age: number; + } + + abstract class BaseUser extends RootUser { + @Attribute(DataTypes.STRING) + declare username: string; + + @Attribute(DataTypes.STRING) + declare password: string; + } + + class User extends BaseUser { + @Attribute(DataTypes.STRING) + declare email: string; + } + + sequelize.addModels([User]); + + expect([...User.modelDefinition.attributes.keys()]).to.deep.equal([ + 'id', + 'email', + 'username', + 'password', + 'name', + 'age', + 'createdAt', + 'updatedAt', + ]); + }); + + it('respects position of inherited attributes', () => { + abstract class RootUser extends Model> { + @Attribute(DataTypes.STRING) + @Attribute({ insertBefore: true }) + declare name: string; + + @Attribute(DataTypes.STRING) + @Attribute({ insertAfter: true }) + declare age: number; + } + + abstract class BaseUser extends RootUser { + @Attribute(DataTypes.STRING) + @Attribute({ insertBefore: true }) + declare username: string; + + @Attribute(DataTypes.STRING) + @Attribute({ insertAfter: true }) + declare password: string; + } + + class User extends BaseUser { + @Attribute(DataTypes.STRING) + declare email: string; + } + + sequelize.addModels([User]); + + expect([...User.modelDefinition.attributes.keys()]).to.deep.equal([ + 'id', + 'name', + 'username', + 'email', + 'password', + 'age', + 'createdAt', + 'updatedAt', + ]); + }); + + it('throws if an attribute is set to be inserted before and after', () => { + expect(() => { + class User extends Model { + @Attribute(DataTypes.STRING) + @Attribute({ insertBefore: true, insertAfter: true }) + declare name: string; + } + + return User; + }).to.throw(`Cannot set both 'insertBefore' and 'insertAfter' to true on the same attribute`); + }); +}); + +describe('createIndexDecorator', () => { + it('makes it possible to create a composite index with options', () => { + const MyIndex = createIndexDecorator('MyIndex', { + name: 'my_custom_index', + type: 'fulltext', + where: { name: null }, + }); + + class User extends Model> { + @Attribute(DataTypes.STRING) + @MyIndex + @ColumnName('first_name') + declare firstName: string; + + @Attribute(DataTypes.STRING) + @MyIndex({ + order: 'DESC', + }) + declare lastName: string; + } + + sequelize.addModels([User]); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: [ + { + name: 'first_name', + }, + { + name: 'lastName', + order: 'DESC', + }, + ], + name: 'my_custom_index', + type: 'fulltext', + where: { name: null }, + }, + ]); + }); + + it('uses a snake-case version of the decorator name as the default index name', () => { + const MyIndex = createIndexDecorator('MyIndex'); + + class User extends Model> { + @Attribute(DataTypes.STRING) + @MyIndex + declare firstName: string; + } + + sequelize.addModels([User]); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: [ + { + name: 'firstName', + }, + ], + name: 'my_index', + }, + ]); + }); +}); + +describe('@AllowNull legacy decorator', () => { + it('sets allowNull to true', () => { + class User extends Model> { + @Attribute(DataTypes.STRING) + @AllowNull + declare name: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().name.allowNull).to.equal(true); + }); + + it('accepts a boolean', () => { + class User extends Model> { + @Attribute(DataTypes.STRING) + @AllowNull(false) + declare name: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().name.allowNull).to.equal(false); + }); +}); + +describe('@NotNull legacy decorator', () => { + it('sets allowNull to false', () => { + class User extends Model> { + @Attribute(DataTypes.STRING) + @NotNull + declare name: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().name.allowNull).to.equal(false); + }); + + it('accepts a boolean', () => { + class User extends Model> { + @Attribute(DataTypes.STRING) + @NotNull(false) + declare name: string; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().name.allowNull).to.equal(true); + }); +}); + +describe('@AutoIncrement legacy decorator', () => { + it('sets autoIncrement to true', () => { + @Table({ noPrimaryKey: true }) + class User extends Model> { + @Attribute(DataTypes.INTEGER) + @AutoIncrement + declare int: number; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().int.autoIncrement).to.equal(true); + }); +}); + +describe('@PrimaryKey legacy decorator', () => { + it('sets primaryKey to true', () => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + @PrimaryKey + declare int: number; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().int.primaryKey).to.equal(true); + }); +}); + +describe('@Comment legacy decorator', () => { + it('sets comment', () => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + @Comment('This is a comment') + declare int: number; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().int.comment).to.equal('This is a comment'); + }); + + it('requires a parameter', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this rejects + @Comment() + declare int: number; + } + + return User; + }).to.throw(); + }); + + it('requires being called', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this throws + @Comment + declare int: number; + } + + return User; + }).to.throw(); + }); +}); + +describe('@Default legacy decorator', () => { + it('sets defaultValue', () => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + @Default(1) + declare int: number; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().int.defaultValue).to.equal(1); + }); + + it('requires a parameter', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this throws + @Default() + declare int: number; + } + + return User; + }).to.throw(); + }); + + it('requires being called', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this throws + @Default + declare int: number; + } + + return User; + }).to.throw(); + }); +}); + +describe('@ColumnName legacy decorator', () => { + it('sets to which column the attribute maps', () => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + @ColumnName('userId') + declare int: number; + } + + sequelize.addModels([User]); + + expect(User.getAttributes().int.field).to.equal('userId'); + }); + + it('requires a parameter', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this throws + @ColumnName() + declare int: number; + } + + return User; + }).to.throw(); + }); + + it('requires being called', () => { + expect(() => { + class User extends Model> { + @Attribute(DataTypes.INTEGER) + // @ts-expect-error -- testing that this throws + @ColumnName + declare int: number; + } + + return User; + }).to.throw(); + }); +}); diff --git a/packages/core/test/unit/decorators/built-in-attributes.test.ts b/packages/core/test/unit/decorators/built-in-attributes.test.ts new file mode 100644 index 000000000000..c7aea6d30f0a --- /dev/null +++ b/packages/core/test/unit/decorators/built-in-attributes.test.ts @@ -0,0 +1,67 @@ +import { Model } from '@sequelize/core'; +import { CreatedAt, DeletedAt, UpdatedAt, Version } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('@CreatedAt', () => { + it('marks a column as the createdAt attribute', () => { + class Test extends Model { + @CreatedAt + declare customCreatedAt: Date; + } + + sequelize.addModels([Test]); + + expect(Test.modelDefinition.timestampAttributeNames).to.deep.equal({ + createdAt: 'customCreatedAt', + updatedAt: 'updatedAt', + }); + }); +}); + +describe('@UpdatedAt', () => { + it('marks a column as the updatedAt attribute', () => { + class Test extends Model { + @UpdatedAt + declare customUpdatedAt: Date; + } + + sequelize.addModels([Test]); + + expect(Test.modelDefinition.timestampAttributeNames).to.deep.equal({ + createdAt: 'createdAt', + updatedAt: 'customUpdatedAt', + }); + }); +}); + +describe('@DeletedAt', () => { + it('marks a column as the deletedAt attribute', () => { + class Test extends Model { + @DeletedAt + declare customDeletedAt: Date | null; + } + + sequelize.addModels([Test]); + + expect(Test.modelDefinition.options.paranoid).to.be.true; + expect(Test.modelDefinition.timestampAttributeNames).to.deep.equal({ + createdAt: 'createdAt', + updatedAt: 'updatedAt', + deletedAt: 'customDeletedAt', + }); + }); +}); + +describe('@Version', () => { + it('marks a column as the version attribute', () => { + class Test extends Model { + @Version + declare customVersionAttribute: number; + } + + sequelize.addModels([Test]); + + expect(Test.modelDefinition.versionAttributeName).to.equal('customVersionAttribute'); + }); +}); diff --git a/packages/core/test/unit/decorators/hooks.test.ts b/packages/core/test/unit/decorators/hooks.test.ts new file mode 100644 index 000000000000..87dea94f5da5 --- /dev/null +++ b/packages/core/test/unit/decorators/hooks.test.ts @@ -0,0 +1,187 @@ +import { Model } from '@sequelize/core'; +import type { ModelHooks } from '@sequelize/core/_non-semver-use-at-your-own-risk_/model-hooks.js'; +import { + AfterAssociate, + AfterBulkCreate, + AfterBulkDestroy, + AfterBulkRestore, + AfterBulkUpdate, + AfterCreate, + AfterDefinitionRefresh, + AfterDestroy, + AfterDestroyMany, + AfterFind, + AfterRestore, + AfterSave, + AfterSync, + AfterUpdate, + AfterUpsert, + AfterValidate, + BeforeAssociate, + BeforeBulkCreate, + BeforeBulkDestroy, + BeforeBulkRestore, + BeforeBulkUpdate, + BeforeCount, + BeforeCreate, + BeforeDefinitionRefresh, + BeforeDestroy, + BeforeDestroyMany, + BeforeFind, + BeforeFindAfterExpandIncludeAll, + BeforeFindAfterOptions, + BeforeRestore, + BeforeSave, + BeforeSync, + BeforeUpdate, + BeforeUpsert, + BeforeValidate, + ValidationFailed, +} from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +// map of hook name to hook decorator +const hookMap: Partial> = { + afterAssociate: AfterAssociate, + afterBulkCreate: AfterBulkCreate, + afterBulkDestroy: AfterBulkDestroy, + afterBulkRestore: AfterBulkRestore, + afterBulkUpdate: AfterBulkUpdate, + afterCreate: AfterCreate, + afterDefinitionRefresh: AfterDefinitionRefresh, + afterDestroy: AfterDestroy, + afterDestroyMany: AfterDestroyMany, + afterFind: AfterFind, + afterRestore: AfterRestore, + afterSave: AfterSave, + afterSync: AfterSync, + afterUpdate: AfterUpdate, + afterUpsert: AfterUpsert, + afterValidate: AfterValidate, + beforeAssociate: BeforeAssociate, + beforeBulkCreate: BeforeBulkCreate, + beforeBulkDestroy: BeforeBulkDestroy, + beforeBulkRestore: BeforeBulkRestore, + beforeBulkUpdate: BeforeBulkUpdate, + beforeCount: BeforeCount, + beforeCreate: BeforeCreate, + beforeDefinitionRefresh: BeforeDefinitionRefresh, + beforeDestroy: BeforeDestroy, + beforeDestroyMany: BeforeDestroyMany, + beforeFind: BeforeFind, + beforeFindAfterExpandIncludeAll: BeforeFindAfterExpandIncludeAll, + beforeFindAfterOptions: BeforeFindAfterOptions, + beforeRestore: BeforeRestore, + beforeSave: BeforeSave, + beforeSync: BeforeSync, + beforeUpdate: BeforeUpdate, + beforeUpsert: BeforeUpsert, + beforeValidate: BeforeValidate, + validationFailed: ValidationFailed, +}; + +for (const [hookName, decorator] of Object.entries(hookMap)) { + describe(`@${hookName} legacy decorator`, () => { + it('adds a hook on the current model', () => { + class MyModel extends Model { + @decorator + static myHook() {} + } + + sequelize.addModels([MyModel]); + + expect(MyModel.hasHooks(hookName as keyof ModelHooks)).to.eq( + true, + `hook ${hookName} incorrectly registered its hook`, + ); + }); + + it('supports a "name" option', () => { + class MyModel extends Model { + @decorator({ name: 'my-hook' }) + static myHook() {} + } + + sequelize.addModels([MyModel]); + + expect(MyModel.hasHooks(hookName as keyof ModelHooks)).to.eq( + true, + `hook ${hookName} incorrectly registered its hook`, + ); + const hookCount = MyModel.hooks.getListenerCount(hookName as keyof ModelHooks); + + MyModel.removeHook(hookName as keyof ModelHooks, 'my-hook'); + + const newHookCount = MyModel.hooks.getListenerCount(hookName as keyof ModelHooks); + + expect(newHookCount).to.eq( + hookCount - 1, + `hook ${hookName} should be possible to remove by name`, + ); + }); + + it('supports symbol methods', () => { + class MyModel extends Model { + @decorator + static [Symbol('myHook')]() {} + } + + sequelize.addModels([MyModel]); + + expect(MyModel.hasHooks(hookName as keyof ModelHooks)).to.eq( + true, + `hook ${hookName} incorrectly registered its hook`, + ); + }); + + it('throws on non-static hooks', () => { + expect(() => { + class MyModel extends Model { + @decorator + nonStaticMethod() {} + } + + sequelize.addModels([MyModel]); + + return MyModel; + }).to.throw(Error, /This decorator can only be used on static properties/); + }); + + it('throws on non-method properties', () => { + expect(() => { + class MyModel extends Model { + @decorator + static nonMethod = 'abc'; + } + + sequelize.addModels([MyModel]); + + return MyModel; + }).to.throw(Error, /is not a method/); + }); + + it('throws if the class is not a model', () => { + expect(() => { + class MyModel { + @decorator + static nonStaticMethod() {} + } + + return MyModel; + }).to.throw(Error, /This decorator can only be used on models/); + }); + + it('throws on reserved methods', () => { + expect(() => { + // @ts-expect-error -- replacing an existing method + class MyModel extends Model { + @decorator + static sync() {} + } + + return MyModel; + }).to.throw(Error, /already exists on the base Model/); + }); + }); +} diff --git a/packages/core/test/unit/decorators/model-validator.test.ts b/packages/core/test/unit/decorators/model-validator.test.ts new file mode 100644 index 000000000000..448caab9ca58 --- /dev/null +++ b/packages/core/test/unit/decorators/model-validator.test.ts @@ -0,0 +1,79 @@ +import { Model } from '@sequelize/core'; +import { ModelValidator } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('@ModelValidator legacy decorator', () => { + it('can add instance validators', async () => { + let receivedThis: any; + + class User extends Model { + @ModelValidator + userValidator(): void { + // eslint-disable-next-line consistent-this,unicorn/no-this-assignment -- testing which value is passed as "this" + receivedThis = this; + + throw new Error('test error'); + } + } + + sequelize.addModels([User]); + + const user = User.build(); + + await expect(user.validate()).to.be.rejectedWith('test error'); + expect(receivedThis).to.equal(user); + }); + + it('can add static validators', async () => { + let receivedThis: any; + let receivedParameters: any; + + class User extends Model { + @ModelValidator + static userValidator(...parameters: unknown[]): void { + // eslint-disable-next-line consistent-this,unicorn/no-this-assignment -- testing which value is passed as "this" + receivedThis = this; + receivedParameters = parameters; + + throw new Error('test error'); + } + } + + sequelize.addModels([User]); + + const user = User.build(); + + await expect(user.validate()).to.be.rejectedWith('test error'); + expect(receivedThis).to.equal(User); + expect(receivedParameters).to.have.length(1); + expect(receivedParameters[0]).to.eq(user); + }); + + it('supports symbol properties', async () => { + const staticKey = Symbol('staticKey'); + const instanceKey = Symbol('instanceKey'); + + class User extends Model { + @ModelValidator + static [staticKey](): void { + throw new Error('test error'); + } + + @ModelValidator + [instanceKey](): void { + throw new Error('test error'); + } + } + + sequelize.addModels([User]); + + expect(Object.getOwnPropertySymbols(User.options.validate)).to.deep.eq([ + instanceKey, + staticKey, + ]); + + const user = User.build(); + await expect(user.validate()).to.be.rejectedWith('test error'); + }); +}); diff --git a/packages/core/test/unit/decorators/table.test.ts b/packages/core/test/unit/decorators/table.test.ts new file mode 100644 index 000000000000..ea08818f28d9 --- /dev/null +++ b/packages/core/test/unit/decorators/table.test.ts @@ -0,0 +1,501 @@ +import { Model, sql } from '@sequelize/core'; +import { Table } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import omit from 'lodash/omit'; +import { sequelize } from '../../support'; + +describe(`@Table legacy decorator`, () => { + it('does not init the model itself', () => { + @Table + class Test extends Model {} + + expect(() => Test.build()).to.throw(/has not been initialized/); + }); + + it('prevents using Model.init', () => { + @Table + class Test extends Model { + declare id: bigint; + } + + expect(() => Test.init({}, { sequelize })).to.throw( + /pass your model to the Sequelize constructor/, + ); + }); + + it('supports specifying options', () => { + @Table({ tableName: 'custom_users' }) + class User extends Model {} + + sequelize.addModels([User]); + + expect(User.table.tableName).to.equal('custom_users'); + }); + + // different decorators can modify the model's options + it('can be used multiple times', () => { + @Table({ tableName: 'custom_users' }) + @Table({ timestamps: false }) + @Table({ tableName: 'custom_users' }) // same value: ignored + class User extends Model {} + + sequelize.addModels([User]); + + expect(User.table.tableName).to.equal('custom_users'); + expect(User.options.timestamps).to.equal(false); + }); + + it('throws if used multiple times with incompatible options', () => { + expect(() => { + @Table({ tableName: 'custom_users' }) + @Table({ tableName: 'custom_use' }) + class User extends Model {} + + return User; + }).to.throw(); + }); + + it('merges indexes', () => { + @Table({ + indexes: [ + { + fields: ['id'], + unique: true, + }, + ], + }) + @Table({ + indexes: [ + { + fields: ['createdAt'], + }, + ], + }) + class User extends Model {} + + sequelize.addModels([User]); + + expect(User.getIndexes()).to.deep.equal([ + { + column: 'createdAt', + fields: ['createdAt'], + name: 'users_created_at', + }, + { + column: 'id', + fields: ['id'], + unique: true, + name: 'users_id_unique', + }, + ]); + }); + + it('does not crash when inheriting options', () => { + @Table.Abstract({}) + class ParentModel extends Model {} + + @Table({ + indexes: [ + { + fields: ['id'], + unique: true, + }, + ], + }) + class User extends ParentModel {} + + sequelize.addModels([User]); + }); + + it('merges scopes', () => { + @Table({ + scopes: { + scope1: {}, + }, + }) + @Table({ + scopes: { + scope2: {}, + }, + }) + class User extends Model {} + + sequelize.addModels([User]); + + expect(User.options.scopes).to.deep.equal({ + scope1: {}, + scope2: {}, + }); + }); + + it('rejects conflicting scopes', () => { + expect(() => { + @Table({ + scopes: { + scope1: {}, + }, + }) + @Table({ + scopes: { + scope1: {}, + }, + }) + class User extends Model {} + + return User; + }).to.throw(); + }); + + it('is inheritable', () => { + function beforeUpdate() {} + + function validate() { + return true; + } + + const literal = sql`1 = 1`; + + @Table({ + // will not be inherited + tableName: 'users', + name: { + plural: 'Users', + singular: 'User', + }, + modelName: 'User', + + // will be inherited (overwritten) + schema: 'custom_schema', + timestamps: false, + paranoid: true, + comment: 'This is a table', + noPrimaryKey: true, + engine: 'InnoDB', + charset: 'utf8', + collate: 'utf8_general_ci', + freezeTableName: true, + deletedAt: 'deleteDate', + createdAt: 'createDate', + updatedAt: 'updateDate', + version: true, + omitNull: true, + underscored: true, + hasTrigger: true, + schemaDelimiter: '_', + initialAutoIncrement: '1000', + // must be cloned + defaultScope: { + // testing that this is cloned correctly + where: literal, + }, + + // will be inherited (merged, see subsequent tests) + validate: { + validate, + }, + scopes: { + scope1: { + // testing that this is cloned correctly + where: literal, + }, + }, + indexes: [ + { + name: 'index1', + fields: [ + // testing that this is cloned correctly + literal, + ], + }, + ], + hooks: { + beforeUpdate, + }, + }) + class BaseUser extends Model {} + + class InheritedUser extends BaseUser {} + + // can be registered even if the base class is not + sequelize.addModels([InheritedUser]); + + // registration order does not matter + sequelize.addModels([BaseUser]); + + const baseOptions = omit(BaseUser.modelDefinition.options, ['sequelize']); + const inheritedOptions = omit(InheritedUser.modelDefinition.options, ['sequelize']); + + // make sure parent options were not modified + expect(baseOptions).to.deep.equal({ + tableName: 'users', + name: { + plural: 'Users', + singular: 'User', + }, + modelName: 'User', + schema: 'custom_schema', + timestamps: false, + paranoid: true, + comment: 'This is a table', + noPrimaryKey: true, + engine: 'InnoDB', + charset: 'utf8', + collate: 'utf8_general_ci', + freezeTableName: true, + deletedAt: 'deleteDate', + createdAt: 'createDate', + updatedAt: 'updateDate', + version: true, + omitNull: true, + underscored: true, + hasTrigger: true, + schemaDelimiter: '_', + initialAutoIncrement: '1000', + defaultScope: { + where: literal, + }, + + validate: { + validate, + }, + scopes: { + scope1: { + where: literal, + }, + }, + indexes: [ + { + name: 'index1', + fields: [literal], + }, + ], + hooks: { + beforeUpdate, + }, + }); + + // make sure options are inherited + expect(inheritedOptions).to.deep.equal({ + // not inherited + tableName: 'InheritedUser', + name: { + plural: 'InheritedUsers', + singular: 'InheritedUser', + }, + modelName: 'InheritedUser', + + // inherited + schema: 'custom_schema', + timestamps: false, + paranoid: true, + comment: 'This is a table', + noPrimaryKey: true, + engine: 'InnoDB', + charset: 'utf8', + collate: 'utf8_general_ci', + freezeTableName: true, + deletedAt: 'deleteDate', + createdAt: 'createDate', + updatedAt: 'updateDate', + version: true, + omitNull: true, + underscored: true, + hasTrigger: true, + schemaDelimiter: '_', + initialAutoIncrement: '1000', + defaultScope: { + where: literal, + }, + validate: { + validate, + }, + scopes: { + scope1: { + where: literal, + }, + }, + indexes: [ + { + name: 'index1', + fields: [literal], + }, + ], + hooks: { + beforeUpdate, + }, + }); + + // must be the same value but a different instance (cloned) + expect(baseOptions.scopes).not.to.equal( + inheritedOptions.scopes, + 'scopes option must be a different instance', + ); + expect(baseOptions.defaultScope).not.to.equal( + inheritedOptions.defaultScope, + 'defaultScope option must be a different instance', + ); + expect(baseOptions.indexes).not.to.equal( + inheritedOptions.indexes, + 'indexes option must be a different instance', + ); + expect(baseOptions.hooks).not.to.equal( + inheritedOptions.hooks, + 'indexes option must be a different instance', + ); + expect(baseOptions.validate).not.to.equal( + inheritedOptions.validate, + 'validate option must be a different instance', + ); + }); + + it('overwrites defaultScope', () => { + const literal = sql`1 = 1`; + + @Table({ + defaultScope: { + where: literal, + }, + }) + class BaseUser extends Model {} + + @Table({ + defaultScope: { + order: ['id'], + }, + }) + class InheritedUser extends BaseUser {} + + sequelize.addModels([BaseUser, InheritedUser]); + + expect(BaseUser.modelDefinition.options.defaultScope).to.deep.equal({ + where: literal, + }); + + expect(InheritedUser.modelDefinition.options.defaultScope).to.deep.equal({ + order: ['id'], + }); + }); + + // the rules for merging are the same as when using the decorator multiple times on the same model + // the details of which are tested in other tests + it('merges validate, scopes, indexes & hooks', () => { + function validate1() { + return true; + } + + function beforeUpdate1() {} + + @Table({ + validate: { + validate1, + }, + scopes: { + scope1: { where: { id: 1 } }, + }, + indexes: [ + { + name: 'index1', + fields: [], + }, + ], + hooks: { + beforeUpdate: beforeUpdate1, + }, + }) + class BaseUser extends Model {} + + function validate2() { + return true; + } + + function beforeUpdate2() {} + + @Table({ + validate: { + validate2, + }, + scopes: { + scope2: { where: { id: 1 } }, + }, + indexes: [ + { + name: 'index2', + fields: [], + }, + ], + hooks: { + beforeUpdate: beforeUpdate2, + }, + }) + class InheritedUser extends BaseUser {} + + sequelize.addModels([InheritedUser]); + + const inheritedOptions = omit(InheritedUser.modelDefinition.options, ['sequelize']); + + expect(inheritedOptions).to.deep.equal({ + // defaults + defaultScope: {}, + freezeTableName: false, + modelName: 'InheritedUser', + name: { + plural: 'InheritedUsers', + singular: 'InheritedUser', + }, + noPrimaryKey: false, + paranoid: false, + schema: '', + schemaDelimiter: '', + tableName: 'InheritedUsers', + timestamps: true, + underscored: false, + + // inherited + hooks: { + beforeUpdate: [beforeUpdate1, beforeUpdate2], + }, + indexes: [ + { + name: 'index1', + fields: [], + }, + { + name: 'index2', + fields: [], + }, + ], + scopes: { + scope1: { where: { id: 1 } }, + scope2: { where: { id: 1 } }, + }, + validate: { + validate1, + validate2, + }, + }); + }); +}); + +describe('@Table.Abstract decorator', () => { + it('registers options but does not cause allow the model to be registered', () => { + @Table.Abstract + class AbstractUser extends Model {} + + sequelize.addModels([AbstractUser]); + + expect(() => AbstractUser.modelDefinition).to.throw(/has not been initialized/); + }); + + it('rejects the tableName & name options', () => { + expect(() => { + // @ts-expect-error -- testing that the options are rejected + @Table.Abstract({ + tableName: 'abc', + name: {}, + }) + class AbstractUser extends Model {} + + return AbstractUser; + }).to.throw('Options "tableName" and "name" cannot be set on abstract models.'); + }); +}); diff --git a/packages/core/test/unit/deep-exports.test.ts b/packages/core/test/unit/deep-exports.test.ts new file mode 100644 index 000000000000..b5176545eec3 --- /dev/null +++ b/packages/core/test/unit/deep-exports.test.ts @@ -0,0 +1,28 @@ +import { expect } from 'chai'; + +/** + * Tests whether users can import files deeper than '@sequelize/core" (eg. "@sequelize/core/package.json'). + * Context: https://github.com/sequelize/sequelize/issues/13787 + */ + +describe('exports', () => { + it('exposes /package.json', async () => { + // TODO: uncomment test once https://nodejs.org/api/esm.html#json-modules are stable + // await import('@sequelize/core/package.json', { + // assert: { type: 'json' } + // }); + + require('@sequelize/core/package.json'); + }); + + it('blocks access to lib files', async () => { + // @ts-expect-error -- we're testing that this will be rejected + await expect(import('@sequelize/core/lib/model')).to.be.rejectedWith( + 'ERR_PACKAGE_PATH_NOT_EXPORTED', + ); + }); + + it('allows access to lib if the user acknowledges that it is unsafe', async () => { + await import('@sequelize/core/_non-semver-use-at-your-own-risk_/model.js'); + }); +}); diff --git a/packages/core/test/unit/dialects/abstract/query-interface.test.ts b/packages/core/test/unit/dialects/abstract/query-interface.test.ts new file mode 100644 index 000000000000..e4742b9fcd1a --- /dev/null +++ b/packages/core/test/unit/dialects/abstract/query-interface.test.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import { sequelize } from '../../../support'; + +describe('QueryInterface', () => { + describe('quoteIdentifier', () => { + // regression test which covers https://github.com/sequelize/sequelize/issues/12627 + it('should quote the identifier', () => { + const identifier = 'identifier'; + const quotedIdentifier = sequelize.queryInterface.quoteIdentifier(identifier); + const expectedQuotedIdentifier = + sequelize.queryInterface.queryGenerator.quoteIdentifier(identifier); + + expect(quotedIdentifier).not.to.be.undefined; + expect(expectedQuotedIdentifier).not.to.be.undefined; + expect(quotedIdentifier).to.equal(expectedQuotedIdentifier); + }); + }); + + describe('quoteIdentifiers', () => { + // regression test which covers https://github.com/sequelize/sequelize/issues/12627 + it('should quote the identifiers', () => { + const identifier = 'table.identifier'; + const quotedIdentifiers = sequelize.queryInterface.quoteIdentifiers(identifier); + const expectedQuotedIdentifiers = + sequelize.queryInterface.queryGenerator.quoteIdentifiers(identifier); + + expect(quotedIdentifiers).not.to.be.undefined; + expect(expectedQuotedIdentifiers).not.to.be.undefined; + expect(quotedIdentifiers).to.equal(expectedQuotedIdentifiers); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/abstract/query.test.js b/packages/core/test/unit/dialects/abstract/query.test.js new file mode 100644 index 000000000000..4bcfa60d7900 --- /dev/null +++ b/packages/core/test/unit/dialects/abstract/query.test.js @@ -0,0 +1,561 @@ +'use strict'; + +const { DataTypes } = require('@sequelize/core'); +const { + AbstractQuery: Query, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query.js'); + +const Support = require('../../../support'); +const chai = require('chai'); +const { match, stub } = require('sinon'); + +const current = Support.sequelize; +const expect = chai.expect; + +describe('[ABSTRACT]', () => { + describe('_groupJoinData', () => { + it('should hash second nested set correctly, when has multiple primary keys and one is a Buffer', () => { + const Team = current.define('team', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + name: { + type: DataTypes.TEXT, + }, + }); + + const Player = current.define('player', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Agent = current.define('agent', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + Team.Player = Team.hasMany(Player, { foreignKey: 'teamId' }); + Team.Agent = Team.hasMany(Agent, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association: Team.Player, + }, + agents: { + model: Agent, + association: Team.Agent, + }, + }, + }; + + const agentOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const agentTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + id: 'a', + 'players.id': '1-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + 'agents.uuid': agentOneUuid, + name: 'vansh', + 'agents.id': 'p', + 'agents.name': 'One', + }, + { + id: 'a', + 'players.id': '2-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + 'agents.uuid': agentTwoUuid, + name: 'joe', + 'agents.id': 'z', + 'agents.name': 'Two', + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(1); + + expect(result[0]).to.have.property('id').and.be.equal('a'); + expect(result[0]).to.have.property('name').and.be.ok; + expect(result[0].agents).to.be.deep.equal([ + { + id: 'p', + uuid: agentOneUuid, + name: 'One', + }, + { + id: 'z', + uuid: agentTwoUuid, + name: 'Two', + }, + ]); + }); + + it('should hash second nested set correctly, when primary is a Buffer', () => { + const Team = current.define('team', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Player = current.define('player', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Agent = current.define('agent', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + }); + + Team.Player = Team.hasMany(Player, { foreignKey: 'teamId' }); + Team.Agent = Team.hasMany(Agent, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association: Team.Player, + }, + agents: { + model: Agent, + association: Team.Agent, + }, + }, + }; + + const agentOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const agentTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + id: 'a', + 'players.id': '1-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + 'agents.uuid': agentOneUuid, + 'agents.name': 'One', + }, + { + id: 'a', + 'players.id': '2-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + 'agents.uuid': agentTwoUuid, + 'agents.name': 'Two', + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(1); + + expect(result[0]).to.have.property('id').and.be.equal('a'); + expect(result[0].agents).to.be.deep.equal([ + { + uuid: agentOneUuid, + name: 'One', + }, + { + uuid: agentTwoUuid, + name: 'Two', + }, + ]); + }); + + it('should hash parents correctly, when has multiple primary keys and one is a Buffer', () => { + const Team = current.define('team', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Player = current.define('player', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const association = Team.hasMany(Player, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association, + }, + }, + }; + + const teamOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const teamTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + uuid: teamOneUuid, + id: 'x', + 'players.id': '1-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + }, + { + uuid: teamTwoUuid, + id: 'y', + 'players.id': '2-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + }, + { + uuid: teamOneUuid, + id: 'x', + 'players.id': '1-2', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-24T11:16:44.000Z'), + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(2); + + expect(result[0]).to.have.property('uuid').and.be.equal(teamOneUuid); + expect(result[0].players).to.be.deep.equal([ + { + id: '1-1', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-03-06T15:47:30.000Z'), + }, + { + id: '1-2', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-24T11:16:44.000Z'), + }, + ]); + + expect(result[1]).to.have.property('uuid').and.be.equal(teamTwoUuid); + expect(result[1].players).to.be.deep.equal([ + { + id: '2-1', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-22T11:16:44.000Z'), + }, + ]); + }); + + it('should hash parents correctly, when primary key is a Buffer', () => { + const Team = current.define('team', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + }); + + const Player = current.define('player', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const association = Team.hasMany(Player, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association, + }, + }, + }; + + const teamOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const teamTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + uuid: teamOneUuid, + 'players.id': '1-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + }, + { + uuid: teamTwoUuid, + 'players.id': '2-1', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + }, + { + uuid: teamOneUuid, + 'players.id': '1-2', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-24T11:16:44.000Z'), + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(2); + + expect(result[0]).to.have.property('uuid').and.be.equal(teamOneUuid); + expect(result[0].players).to.be.deep.equal([ + { + id: '1-1', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-03-06T15:47:30.000Z'), + }, + { + id: '1-2', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-24T11:16:44.000Z'), + }, + ]); + + expect(result[1]).to.have.property('uuid').and.be.equal(teamTwoUuid); + expect(result[1].players).to.be.deep.equal([ + { + id: '2-1', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-22T11:16:44.000Z'), + }, + ]); + }); + + it('should hash nested correctly, when primary key is a Buffer', () => { + const Team = current.define('team', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Player = current.define('player', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + }); + + const association = Team.hasMany(Player, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association, + }, + }, + }; + + const playerOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const playerTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + id: '1', + 'players.uuid': playerOneUuid, + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + }, + { + id: '1', + 'players.uuid': playerTwoUuid, + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(1); + + expect(result[0]).to.have.property('id').and.be.equal('1'); + expect(result[0].players).to.be.deep.equal([ + { + uuid: playerOneUuid, + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-03-06T15:47:30.000Z'), + }, + { + uuid: playerTwoUuid, + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-22T11:16:44.000Z'), + }, + ]); + }); + + it('should hash nested correctly, when has multiple primary keys and one is a Buffer', () => { + const Team = current.define('team', { + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const Player = current.define('player', { + uuid: { + primaryKey: true, + type: 'BINARY(16)', + }, + id: { + primaryKey: true, + type: DataTypes.STRING(1), + }, + }); + + const association = Team.hasMany(Player, { foreignKey: 'teamId' }); + + const includeOptions = { + model: Team, + includeMap: { + players: { + model: Player, + association, + }, + }, + }; + + const playerOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); + const playerTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); + + const data = [ + { + id: '1', + 'players.uuid': playerOneUuid, + 'players.id': 'x', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), + }, + { + id: '1', + 'players.uuid': playerTwoUuid, + 'players.id': 'y', + 'players.created': new Date('2017-03-06T15:47:30.000Z'), + 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), + }, + ]; + + const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); + + expect(result.length).to.equal(1); + + expect(result[0]).to.have.property('id').and.be.equal('1'); + expect(result[0].players).to.be.deep.equal([ + { + uuid: playerOneUuid, + id: 'x', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-03-06T15:47:30.000Z'), + }, + { + uuid: playerTwoUuid, + id: 'y', + created: new Date('2017-03-06T15:47:30.000Z'), + lastModified: new Date('2017-08-22T11:16:44.000Z'), + }, + ]); + }); + }); + + describe('_logQuery', () => { + beforeEach(function () { + this.cls = class MyQuery extends Query {}; + this.sequelizeStub = { + log: stub(), + options: {}, + }; + this.connectionStub = { + uuid: 'test', + }; + }); + + it('logs before and after', function () { + const debugStub = stub(); + const qry = new this.cls(this.connectionStub, this.sequelizeStub, {}); + const complete = qry._logQuery('SELECT 1', debugStub); + complete(); + expect(this.sequelizeStub.log).to.have.been.calledOnce; + expect(this.sequelizeStub.log).to.have.been.calledWithMatch('Executing (test): SELECT 1'); + + expect(debugStub).to.have.been.calledWith('Executing (test): SELECT 1'); + expect(debugStub).to.have.been.calledWith('Executed (test): SELECT 1'); + }); + + it('logs before and after with benchmark', function () { + const debugStub = stub(); + const qry = new this.cls(this.connectionStub, this.sequelizeStub, { benchmark: true }); + const complete = qry._logQuery('SELECT 1', debugStub); + complete(); + expect(this.sequelizeStub.log).to.have.been.calledOnce; + expect(this.sequelizeStub.log).to.have.been.calledWithMatch( + 'Executed (test): SELECT 1', + match.number, + { benchmark: true }, + ); + + expect(debugStub).to.have.been.calledWith('Executing (test): SELECT 1'); + expect(debugStub).to.have.been.calledWith('Executed (test): SELECT 1'); + }); + + it('supports logging bigints', function () { + // this test was added because while most of the time `bigint`s are stringified, + // they're not always. For instance, if the PK is a bigint, calling .save() + // will pass the PK as a bigint instead of a string as a parameter. + // This is fine, as bigints should be supported natively, + // but AbstractQuery#_logQuery used JSON.stringify on parameters, + // which does not support serializing bigints (https://github.com/tc39/proposal-bigint/issues/24) + // This test was added to ensure bigints don't cause a crash + // when `logQueryParameters` is true. + + const sequelizeStub = { + ...this.sequelizeStub, + options: { + ...this.sequelizeStub.options, + logQueryParameters: true, + }, + }; + + const debugStub = stub(); + const qry = new this.cls(this.connectionStub, sequelizeStub, {}); + const complete = qry._logQuery('SELECT 1', debugStub, [1n]); + + complete(); + + expect(debugStub).to.have.been.calledWith( + 'Executing (test): SELECT 1; with parameters [ 1n ]', + ); + expect(debugStub).to.have.been.calledWith( + 'Executed (test): SELECT 1; with parameters [ 1n ]', + ); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/db2/query-generator.test.js b/packages/core/test/unit/dialects/db2/query-generator.test.js new file mode 100644 index 000000000000..27339a8d0647 --- /dev/null +++ b/packages/core/test/unit/dialects/db2/query-generator.test.js @@ -0,0 +1,548 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const { Op } = require('@sequelize/core'); +const { Db2QueryGenerator: QueryGenerator } = require('@sequelize/db2'); +const { createSequelizeInstance } = require('../../../support'); + +if (dialect === 'db2') { + describe('[DB2 Specific] QueryGenerator', () => { + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { + id: 'INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY', + }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + title: 'Add column level comment', + arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], + expectation: { id: 'INTEGER COMMENT Test' }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + { + arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], + expectation: { id: 'INTEGER' }, + }, + // No Default Values allowed for certain types + // TODO: this test is broken. It adds the default value because the data type is a string, which can't have special behaviors. + // change type to an actual Sequelize DataType & re-enable + // { + // title: 'No Default value for DB2 BLOB allowed', + // arguments: [{ id: { type: 'BLOB', defaultValue: Buffer.from([]) } }], + // expectation: { id: 'BLOB DEFAULT ' }, + // }, + { + title: 'No Default value for DB2 TEXT allowed', + arguments: [{ id: { type: 'TEXT', defaultValue: 'abc' } }], + expectation: { id: 'TEXT' }, + }, + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id")' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("pk")' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + autoIncrement: true, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM "myTable";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT "id", "name" FROM "myTable";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."name" = \'foo\';', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id", "DESC";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC, "myTable"."name";', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + context: QueryGenerator, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt");', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt"), "title";', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name" ORDER BY "id" DESC;', + context: QueryGenerator, + }, + { + title: 'Empty having', + arguments: [ + 'myTable', + function () { + return { + having: {}, + }; + }, + ], + expectation: 'SELECT * FROM "myTable";', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'Having in subquery', + arguments: [ + 'myTable', + function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }, + ], + expectation: + 'SELECT "test".* FROM (SELECT * FROM "myTable" AS "test" HAVING "test"."creationYear" > 2002) AS "test";', + context: QueryGenerator, + needsSequelize: true, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name") VALUES ($sequelize_1));', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name") VALUES ($sequelize_1));', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2));', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("data") VALUES ($sequelize_1));', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3));', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3));', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2));', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2));', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("foo") VALUES (NOW()));', + bind: {}, + }, + needsSequelize: true, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: + "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, + { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }, + ], + ], + expectation: `INSERT INTO "myTable" ("name","birthday") VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');`, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: 'INSERT INTO "myTable" ("name","foo") VALUES (\'foo\',1),(\'bar\',2);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',NULL,NULL);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);', + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);', + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: undefined }, + { name: 'bar', foo: 2, undefinedValue: undefined }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue","undefinedValue") VALUES (\'foo\',1,NULL,NULL),(\'bar\',2,NULL,NULL);', + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","value") VALUES (\'foo\',true),(\'bar\',false);', + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2);', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "name"=$sequelize_1 WHERE "name" = $sequelize_2);', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3);', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3);', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2);', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"=NOW() WHERE "name" = $sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: + 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "bar"="foo" WHERE "name" = $sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || `Db2 correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const sequelize = createSequelizeInstance({ + ...(test.context && test.context.options), + }); + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](sequelize); + } + } + + const queryGenerator = sequelize.dialect.queryGenerator; + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mariadb/errors.test.js b/packages/core/test/unit/dialects/mariadb/errors.test.js new file mode 100644 index 000000000000..3761e9ff3957 --- /dev/null +++ b/packages/core/test/unit/dialects/mariadb/errors.test.js @@ -0,0 +1,65 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const { Sequelize } = require('@sequelize/core'); + +const dialect = Support.getTestDialect(); +const queryProto = Support.sequelize.dialect.Query.prototype; + +if (dialect === 'mariadb') { + describe('[MARIADB Specific] ForeignKeyConstraintError - error message parsing', () => { + it('FK Errors with ` quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).', + ); + + fakeErr.errno = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('FK Errors with " quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).', + ); + + fakeErr.errno = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('newlines contained in err message are parsed correctly', () => { + const fakeErr = new Error( + "(conn=43, no: 1062, SQLState: 23000) Duplicate entry 'unique name one\r' for key 'models_uniqueName2_unique'\nsql: INSERT INTO `models` (`id`,`uniqueName1`,`uniqueName2`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?); - parameters:['this is ok','unique name one','2019-01-18 09:05:28.496','2019-01-18 09:05:28.496']", + ); + + fakeErr.errno = 1062; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.fields.models_uniqueName2_unique).to.equal('unique name one\r'); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mariadb/query-generator.test.js b/packages/core/test/unit/dialects/mariadb/query-generator.test.js new file mode 100644 index 000000000000..28f2f3ce7cd0 --- /dev/null +++ b/packages/core/test/unit/dialects/mariadb/query-generator.test.js @@ -0,0 +1,559 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const { Op } = require('@sequelize/core'); +const { MariaDbQueryGenerator } = require('@sequelize/mariadb'); +const { createSequelizeInstance } = require('../../../support'); + +if (dialect === 'mariadb') { + describe('[MARIADB Specific] QueryGenerator', () => { + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { id: 'INTEGER auto_increment PRIMARY KEY' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + title: 'Add column level comment', + arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], + expectation: { id: "INTEGER COMMENT 'Test'" }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + { + arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], + expectation: { id: 'INTEGER AFTER `Bar`' }, + }, + // No Default Values allowed for certain types + { + title: 'No Default value for MariaDB BLOB allowed', + arguments: [{ id: { type: 'BLOB', defaultValue: [] } }], + expectation: { id: 'BLOB' }, + }, + { + title: 'No Default value for MariaDB TEXT allowed', + arguments: [{ id: { type: 'TEXT', defaultValue: [] } }], + expectation: { id: 'TEXT' }, + }, + { + title: 'No Default value for MariaDB GEOMETRY allowed', + arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }], + expectation: { id: 'GEOMETRY' }, + }, + { + title: 'No Default value for MariaDB JSON allowed', + arguments: [{ id: { type: 'JSON', defaultValue: [] } }], + expectation: { id: 'JSON' }, + }, + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + autoIncrement: true, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM `myTable`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT `id`, `name` FROM `myTable`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', + context: MariaDbQueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: MariaDbQueryGenerator, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: MariaDbQueryGenerator, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', + context: MariaDbQueryGenerator, + }, + { + title: 'Empty having', + arguments: [ + 'myTable', + function () { + return { + having: {}, + }; + }, + ], + expectation: 'SELECT * FROM `myTable`;', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + { + title: 'Having in subquery', + arguments: [ + 'myTable', + function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }, + ], + expectation: + 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `test`.`creationYear` > 2002) AS `test`;', + context: MariaDbQueryGenerator, + needsSequelize: true, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO `myTable` (`data`) VALUES ($sequelize_1);', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + { name: 'foo', foo: 1, nullValue: null }, + ], + expectation: { + query: + 'INSERT INTO `mySchema`.`myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: + "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, + { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL,NULL);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: undefined }, + { name: 'bar', foo: 2, undefinedValue: undefined }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`,`undefinedValue`) VALUES ('foo',1,NULL,NULL),('bar',2,NULL,NULL);", + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);", + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: "INSERT IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: [ + 'myTable', + [{ name: 'foo' }, { name: 'bar' }], + { updateOnDuplicate: ['name'] }, + ], + expectation: + "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON DUPLICATE KEY UPDATE `name`=VALUES(`name`);", + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `bar`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `name`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `bar`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || + `MariaDB correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const sequelize = createSequelizeInstance({ + ...test.sequelizeOptions, + ...(test.context && test.context.options), + }); + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](sequelize); + } + } + + const queryGenerator = sequelize.dialect.queryGenerator; + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mssql/connection-manager.test.ts b/packages/core/test/unit/dialects/mssql/connection-manager.test.ts new file mode 100644 index 000000000000..8c8f9757abf9 --- /dev/null +++ b/packages/core/test/unit/dialects/mssql/connection-manager.test.ts @@ -0,0 +1,120 @@ +import type { Options } from '@sequelize/core'; +import { ConnectionError, Sequelize } from '@sequelize/core'; +import { MsSqlDialect } from '@sequelize/mssql'; +import { assert, expect } from 'chai'; +import sinon from 'sinon'; +import { Connection as TediousConnection } from 'tedious'; +import { getTestDialect } from '../../../support'; + +const dialect = getTestDialect(); + +type TestConnection = Omit & { + once(event: string, cb: () => void): void; + removeListener(): void; + on(): void; +}; + +describe('[MSSQL Specific] Connection Manager', () => { + if (dialect !== 'mssql') { + return; + } + + let config: Options; + let instance: Sequelize; + let Connection: Partial; + + beforeEach(() => { + Connection = {}; + + const tediousModule = { + Connection: function fakeConnection() { + return Connection; + }, + } as any; + + config = { + dialect: MsSqlDialect, + server: 'localhost', + authentication: { + type: 'default', + options: { + domain: 'TEST.COM', + userName: 'none', + password: 'none', + }, + }, + pool: {}, + port: 2433, + tediousModule, + }; + + instance = new Sequelize(config); + }); + + it('connectionManager.connect() should reject if end was called and connect was not', async () => { + Connection = { + STATE: TediousConnection.prototype.STATE, + state: undefined, + once(event, cb) { + if (event === 'end') { + setTimeout(() => { + cb(); + }, 500); + } + }, + removeListener: () => {}, + on: () => {}, + }; + + const error = await expect(instance.dialect.connectionManager.connect(config)).to.be.rejected; + + assert(error instanceof ConnectionError); + expect(error.name).to.equal('SequelizeConnectionError'); + assert(error.cause instanceof Error); + expect(error.cause.message).to.equal('Connection was closed by remote server'); + }); + + it('connectionManager.connect() should call connect if state is initialized', async () => { + const connectStub = sinon.stub(); + Connection = { + STATE: TediousConnection.prototype.STATE, + state: TediousConnection.prototype.STATE.INITIALIZED, + connect: connectStub, + once(event, cb) { + if (event === 'connect') { + setTimeout(() => { + cb(); + }, 500); + } + }, + removeListener: () => {}, + on: () => {}, + }; + + await instance.dialect.connectionManager.connect(config); + expect(connectStub.called).to.equal(true); + }); + + it('connectionManager.connect() should not fail with an instanceName but no port specified in config', async () => { + const connectStub = sinon.stub(); + Connection = { + STATE: TediousConnection.prototype.STATE, + state: TediousConnection.prototype.STATE.INITIALIZED, + connect: connectStub, + once(event, cb) { + if (event === 'connect') { + setTimeout(() => { + cb(); + }, 500); + } + }, + removeListener: () => {}, + on: () => {}, + }; + + config.instanceName = 'INSTANCENAME'; + + await instance.dialect.connectionManager.connect(config); + expect(connectStub.called).to.equal(true); + }); +}); diff --git a/packages/core/test/unit/dialects/mssql/query-generator.test.js b/packages/core/test/unit/dialects/mssql/query-generator.test.js new file mode 100644 index 000000000000..c795e25cf8eb --- /dev/null +++ b/packages/core/test/unit/dialects/mssql/query-generator.test.js @@ -0,0 +1,133 @@ +'use strict'; + +const Support = require('../../../support'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +const { DataTypes, Op } = require('@sequelize/core'); +const { MsSqlQueryGenerator: QueryGenerator } = require('@sequelize/mssql'); + +if (current.dialect.name === 'mssql') { + describe('[MSSQL Specific] QueryGenerator', () => { + before(function () { + this.queryGenerator = new QueryGenerator(this.sequelize.dialect); + }); + + 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('bulkInsertQuery', function () { + // normal cases + expectsql( + this.queryGenerator.bulkInsertQuery('myTable', [{ name: 'foo' }, { name: 'bar' }]), + { + mssql: "INSERT INTO [myTable] ([name]) VALUES (N'foo'),(N'bar');", + }, + ); + + expectsql( + this.queryGenerator.bulkInsertQuery('myTable', [ + { username: 'username', firstName: 'firstName', lastName: 'lastName' }, + { firstName: 'user1FirstName', lastName: 'user1LastName' }, + ]), + { + mssql: + "INSERT INTO [myTable] ([username],[firstName],[lastName]) VALUES (N'username',N'firstName',N'lastName'),(NULL,N'user1FirstName',N'user1LastName');", + }, + ); + + expectsql( + this.queryGenerator.bulkInsertQuery('myTable', [ + { firstName: 'firstName', lastName: 'lastName' }, + { firstName: 'user1FirstName', lastName: 'user1LastName' }, + ]), + { + mssql: + "INSERT INTO [myTable] ([firstName],[lastName]) VALUES (N'firstName',N'lastName'),(N'user1FirstName',N'user1LastName');", + }, + ); + + // Bulk Insert With autogenerated primary key + const attributes = { id: { autoIncrement: true } }; + expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ id: null }], {}, attributes), { + mssql: 'INSERT INTO [myTable] DEFAULT VALUES;', + }); + }); + + it('addColumnQuery', function () { + expectsql( + this.queryGenerator.addColumnQuery('myTable', 'myColumn', { type: 'VARCHAR(255)' }), + { + mssql: 'ALTER TABLE [myTable] ADD [myColumn] VARCHAR(255) NULL;', + }, + ); + }); + + it('addColumnQuery with comment', function () { + expectsql( + this.queryGenerator.addColumnQuery('myTable', 'myColumn', { + type: 'VARCHAR(255)', + comment: 'This is a comment', + }), + { + mssql: + 'ALTER TABLE [myTable] ADD [myColumn] VARCHAR(255) NULL; EXEC sp_addextendedproperty ' + + "@name = N'MS_Description', @value = N'This is a comment', " + + "@level0type = N'Schema', @level0name = N'dbo', " + + "@level1type = N'Table', @level1name = [myTable], " + + "@level2type = N'Column', @level2name = [myColumn];", + }, + ); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mssql/query.test.js b/packages/core/test/unit/dialects/mssql/query.test.js new file mode 100644 index 000000000000..f6f09a20d2c7 --- /dev/null +++ b/packages/core/test/unit/dialects/mssql/query.test.js @@ -0,0 +1,113 @@ +'use strict'; + +const { MsSqlQuery: Query } = require('@sequelize/mssql'); +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const sequelize = Support.sequelize; +const expect = require('chai').expect; +const tedious = require('tedious'); + +const connectionStub = { lib: tedious }; + +let query; + +if (dialect === 'mssql') { + describe('[MSSQL Specific] Query', () => { + beforeEach(() => { + const options = { + transaction: { name: 'transactionName' }, + isolationLevel: 'REPEATABLE_READ', + logging: false, + }; + query = new Query(connectionStub, sequelize, options); + }); + + describe('getSQLTypeFromJsType', () => { + const TYPES = tedious.TYPES; + it('should return correct parameter type', () => { + expect(query.getSQLTypeFromJsType(2_147_483_647, TYPES)).to.eql({ + type: TYPES.Int, + typeOptions: {}, + value: 2_147_483_647, + }); + expect(query.getSQLTypeFromJsType(-2_147_483_648, TYPES)).to.eql({ + type: TYPES.Int, + typeOptions: {}, + value: -2_147_483_648, + }); + + expect(query.getSQLTypeFromJsType(2_147_483_648, TYPES)).to.eql({ + type: TYPES.BigInt, + typeOptions: {}, + value: 2_147_483_648, + }); + expect(query.getSQLTypeFromJsType(-2_147_483_649, TYPES)).to.eql({ + type: TYPES.BigInt, + typeOptions: {}, + value: -2_147_483_649, + }); + + expect(query.getSQLTypeFromJsType(2_147_483_647n, TYPES)).to.eql({ + type: TYPES.Int, + typeOptions: {}, + value: 2_147_483_647, + }); + expect(query.getSQLTypeFromJsType(-2_147_483_648n, TYPES)).to.eql({ + type: TYPES.Int, + typeOptions: {}, + value: -2_147_483_648, + }); + + expect(query.getSQLTypeFromJsType(BigInt(Number.MAX_SAFE_INTEGER), TYPES)).to.eql({ + type: TYPES.BigInt, + typeOptions: {}, + value: Number.MAX_SAFE_INTEGER, + }); + expect(query.getSQLTypeFromJsType(BigInt(Number.MIN_SAFE_INTEGER), TYPES)).to.eql({ + type: TYPES.BigInt, + typeOptions: {}, + value: Number.MIN_SAFE_INTEGER, + }); + + const overMaxSafe = BigInt(Number.MAX_SAFE_INTEGER) + 1n; + expect(query.getSQLTypeFromJsType(overMaxSafe, TYPES)).to.eql({ + type: TYPES.VarChar, + typeOptions: {}, + value: overMaxSafe.toString(), + }); + const underMinSafe = BigInt(Number.MIN_SAFE_INTEGER) - 1n; + expect(query.getSQLTypeFromJsType(underMinSafe, TYPES)).to.eql({ + type: TYPES.VarChar, + typeOptions: {}, + value: underMinSafe.toString(), + }); + + const buffer = Buffer.from('abc'); + expect(query.getSQLTypeFromJsType(buffer, TYPES)).to.eql({ + type: TYPES.VarBinary, + typeOptions: {}, + value: buffer, + }); + }); + + 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 }, + value: 1.23, + }); + expect(query.getSQLTypeFromJsType(0.300_000_000_000_000_04, TYPES)).to.eql({ + type: TYPES.Numeric, + typeOptions: { precision: 30, scale: 17 }, + value: 0.300_000_000_000_000_04, + }); + expect(query.getSQLTypeFromJsType(2.5e-15, TYPES)).to.eql({ + type: TYPES.Numeric, + typeOptions: { precision: 30, scale: 16 }, + value: 2.5e-15, + }); + }); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mysql/errors.test.js b/packages/core/test/unit/dialects/mysql/errors.test.js new file mode 100644 index 000000000000..74ef6457c580 --- /dev/null +++ b/packages/core/test/unit/dialects/mysql/errors.test.js @@ -0,0 +1,63 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const { Sequelize } = require('@sequelize/core'); + +const dialect = Support.getTestDialect(); +const queryProto = Support.sequelize.dialect.Query.prototype; + +if (dialect === 'mysql') { + describe('[MYSQL Specific] ForeignKeyConstraintError - error message parsing', () => { + it('FK Errors with ` quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).', + ); + + fakeErr.code = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('FK Errors with " quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).', + ); + + fakeErr.code = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('newlines contained in err message are parsed correctly', () => { + const fakeErr = new Error("Duplicate entry '13888888888\r' for key 'num'"); + + fakeErr.code = 1062; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.fields.num).to.equal('13888888888\r'); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mysql/query-generator.test.js b/packages/core/test/unit/dialects/mysql/query-generator.test.js new file mode 100644 index 000000000000..81c300b9aac7 --- /dev/null +++ b/packages/core/test/unit/dialects/mysql/query-generator.test.js @@ -0,0 +1,553 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const { Op } = require('@sequelize/core'); +const { MySqlQueryGenerator } = require('@sequelize/mysql'); +const { createSequelizeInstance } = require('../../../support'); + +if (dialect === 'mysql') { + describe('[MYSQL Specific] QueryGenerator', () => { + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { id: 'INTEGER auto_increment PRIMARY KEY' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + title: 'Add column level comment', + arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], + expectation: { id: "INTEGER COMMENT 'Test'" }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + { + arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], + expectation: { id: 'INTEGER AFTER `Bar`' }, + }, + // No Default Values allowed for certain types + { + title: 'No Default value for MySQL BLOB allowed', + arguments: [{ id: { type: 'BLOB', defaultValue: [] } }], + expectation: { id: 'BLOB' }, + }, + { + title: 'No Default value for MySQL TEXT allowed', + arguments: [{ id: { type: 'TEXT', defaultValue: [] } }], + expectation: { id: 'TEXT' }, + }, + { + title: 'No Default value for MySQL GEOMETRY allowed', + arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }], + expectation: { id: 'GEOMETRY' }, + }, + { + title: 'No Default value for MySQL JSON allowed', + arguments: [{ id: { type: 'JSON', defaultValue: [] } }], + expectation: { id: 'JSON' }, + }, + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + autoIncrement: true, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM `myTable`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT `id`, `name` FROM `myTable`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', + context: MySqlQueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: MySqlQueryGenerator, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: MySqlQueryGenerator, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', + context: MySqlQueryGenerator, + }, + { + title: 'Empty having', + arguments: [ + 'myTable', + function () { + return { + having: {}, + }; + }, + ], + expectation: 'SELECT * FROM `myTable`;', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + { + title: 'Having in subquery', + arguments: [ + 'myTable', + function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }, + ], + expectation: + 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `test`.`creationYear` > 2002) AS `test`;', + context: MySqlQueryGenerator, + needsSequelize: true, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO `myTable` (`data`) VALUES ($sequelize_1);', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: + "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, + { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL,NULL);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: undefined }, + { name: 'bar', foo: 2, undefinedValue: undefined }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`,`undefinedValue`) VALUES ('foo',1,NULL,NULL),('bar',2,NULL,NULL);", + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);", + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: "INSERT IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: [ + 'myTable', + [{ name: 'foo' }, { name: 'bar' }], + { updateOnDuplicate: ['name'] }, + ], + expectation: + "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON DUPLICATE KEY UPDATE `name`=VALUES(`name`);", + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `bar`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `name`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `bar`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || `MySQL correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const sequelize = createSequelizeInstance({ + ...test.sequelizeOptions, + ...(test.context && test.context.options), + }); + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](sequelize); + } + } + + const queryGenerator = sequelize.queryGenerator; + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/packages/core/test/unit/dialects/mysql/query.test.js b/packages/core/test/unit/dialects/mysql/query.test.js new file mode 100644 index 000000000000..5cc6e63891d9 --- /dev/null +++ b/packages/core/test/unit/dialects/mysql/query.test.js @@ -0,0 +1,36 @@ +'use strict'; + +const { MySqlQuery } = require('@sequelize/mysql'); + +const Support = require('../../../support'); +const chai = require('chai'); +const sinon = require('sinon'); + +const current = Support.sequelize; +const expect = chai.expect; + +describe('[MYSQL/MARIADB Specific] Query', () => { + describe('logWarnings', () => { + beforeEach(() => { + sinon.spy(console, 'debug'); + }); + + afterEach(() => { + console.debug.restore(); + }); + + it('check iterable', async () => { + const validWarning = []; + const invalidWarning = {}; + const warnings = [validWarning, undefined, invalidWarning]; + + const query = new MySqlQuery({}, current, {}); + const stub = sinon.stub(query, 'run'); + stub.onFirstCall().resolves(warnings); + + const results = await query.logWarnings('dummy-results'); + expect('dummy-results').to.equal(results); + expect(true).to.equal(console.debug.calledOnce); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/postgres/enum.test.js b/packages/core/test/unit/dialects/postgres/enum.test.js new file mode 100644 index 000000000000..07f27f5d1436 --- /dev/null +++ b/packages/core/test/unit/dialects/postgres/enum.test.js @@ -0,0 +1,126 @@ +'use strict'; + +const { beforeAll2, expectsql, sequelize } = require('../../../support'); +const { DataTypes } = require('@sequelize/core'); +const { expect } = require('chai'); + +const sql = sequelize.dialect.queryGenerator; + +describe('PostgresQueryGenerator', () => { + if (sequelize.dialect.name !== 'postgres') { + return; + } + + const vars = beforeAll2(() => { + const FooUser = sequelize.define( + 'user', + { + mood: DataTypes.ENUM('happy', 'sad'), + }, + { + schema: 'foo', + }, + ); + + const PublicUser = sequelize.define('user', { + mood: { + type: DataTypes.ENUM('happy', 'sad'), + field: 'theirMood', + }, + }); + + return { FooUser, PublicUser }; + }); + + describe('pgEnumName', () => { + it('does not add schema when options: { schema: false }', () => { + const { FooUser, PublicUser } = vars; + + expect(sql.pgEnumName(PublicUser.table, 'mood', { schema: false })).to.equal( + '"enum_users_mood"', + ); + expect(sql.pgEnumName(FooUser.table, 'theirMood', { schema: false })).to.equal( + '"enum_users_theirMood"', + ); + }); + + it('properly quotes both the schema and the enum name', () => { + const { FooUser, PublicUser } = vars; + + expect( + sql.pgEnumName(PublicUser.table, 'mood', PublicUser.getAttributes().mood.type), + ).to.equal('"public"."enum_users_mood"'); + expect( + sql.pgEnumName(FooUser.table, 'theirMood', FooUser.getAttributes().mood.type), + ).to.equal('"foo"."enum_users_theirMood"'); + }); + }); + + describe('pgEnum', () => { + it('uses schema #3171', () => { + const { FooUser } = vars; + + expectsql(sql.pgEnum(FooUser.table, 'mood', FooUser.getAttributes().mood.type), { + postgres: `DO 'BEGIN CREATE TYPE "foo"."enum_users_mood" AS ENUM(''happy'', ''sad''); EXCEPTION WHEN duplicate_object THEN null; END';`, + }); + }); + + it('does add schema when public', () => { + const { PublicUser } = vars; + + expectsql(sql.pgEnum(PublicUser.table, 'theirMood', PublicUser.getAttributes().mood.type), { + postgres: `DO 'BEGIN CREATE TYPE "public"."enum_users_theirMood" AS ENUM(''happy'', ''sad''); EXCEPTION WHEN duplicate_object THEN null; END';`, + }); + }); + }); + + describe('pgEnumAdd', () => { + it('creates alter type with exists', () => { + const { PublicUser } = vars; + + expectsql(sql.pgEnumAdd(PublicUser.table, 'mood', 'neutral', { after: 'happy' }), { + postgres: + 'ALTER TYPE "public"."enum_users_mood" ADD VALUE IF NOT EXISTS \'neutral\' AFTER \'happy\'', + }); + }); + }); + + describe('pgListEnums', () => { + it('works with schema #3563', () => { + const { FooUser } = vars; + + expectsql(sql.pgListEnums(FooUser.table, 'mood'), { + postgres: `SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value + FROM pg_type t + JOIN pg_enum e ON t.oid = e.enumtypid + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'foo' + AND t.typname='enum_users_mood' + GROUP BY 1`, + }); + }); + + it('uses the default schema if no options given', () => { + expectsql(sql.pgListEnums(), { + postgres: `SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value + FROM pg_type t + JOIN pg_enum e ON t.oid = e.enumtypid + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'public' + GROUP BY 1`, + }); + }); + + it('is not vulnerable to sql injection', () => { + expectsql(sql.pgListEnums({ tableName: `ta'"ble`, schema: `sche'"ma` }, `attri'"bute`), { + postgres: `SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value + FROM pg_type t + JOIN pg_enum e ON t.oid = e.enumtypid + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'sche''"ma' + AND t.typname='enum_ta''"ble_attri''"bute' + GROUP BY 1`, + }); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/postgres/query-generator.test.js b/packages/core/test/unit/dialects/postgres/query-generator.test.js new file mode 100644 index 000000000000..533cac505f99 --- /dev/null +++ b/packages/core/test/unit/dialects/postgres/query-generator.test.js @@ -0,0 +1,1122 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const { Op } = require('@sequelize/core'); +const { PostgresQueryGenerator: QueryGenerator } = require('@sequelize/postgres'); +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const dayjs = require('dayjs'); + +if (dialect.startsWith('postgres')) { + describe('[POSTGRES Specific] QueryGenerator', () => { + Support.allowDeprecationsInSuite(['SEQUELIZE0023']); + + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { id: 'INTEGER SERIAL PRIMARY KEY' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + primaryKey: true, + autoIncrement: true, + autoIncrementIdentity: true, + }, + }, + ], + expectation: { id: 'INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true, comment: 'This is my comment' } }], + expectation: { id: 'INTEGER UNIQUE COMMENT This is my comment' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', unique: true, comment: 'This is my comment' } }, + { context: 'addColumn', key: 'column', table: { schema: 'foo', tableName: 'bar' } }, + ], + expectation: { + id: 'INTEGER UNIQUE; COMMENT ON COLUMN "foo"."bar"."column" IS \'This is my comment\'', + }, + }, + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id")' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("pk")' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + + // Variants when quoteIdentifiers is false + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES Bar (id)' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES Bar (pk)' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT', + }, + context: { options: { quoteIdentifiers: false } }, + }, + ], + + changeColumnQuery: [ + { + arguments: [ + 'myTable', + { + col_1: "ENUM('value 1', 'value 2') NOT NULL", + col_2: "ENUM('value 3', 'value 4') NOT NULL", + }, + ], + expectation: `ALTER TABLE "myTable" ALTER COLUMN "col_1" SET NOT NULL;ALTER TABLE "myTable" ALTER COLUMN "col_1" DROP DEFAULT;DO 'BEGIN CREATE TYPE "public"."enum_myTable_col_1" AS ENUM(''value 1'', ''value 2''); EXCEPTION WHEN duplicate_object THEN null; END';ALTER TABLE "myTable" ALTER COLUMN "col_1" TYPE "public"."enum_myTable_col_1" USING ("col_1"::"public"."enum_myTable_col_1");ALTER TABLE "myTable" ALTER COLUMN "col_2" SET NOT NULL;ALTER TABLE "myTable" ALTER COLUMN "col_2" DROP DEFAULT;DO 'BEGIN CREATE TYPE "public"."enum_myTable_col_2" AS ENUM(''value 3'', ''value 4''); EXCEPTION WHEN duplicate_object THEN null; END';ALTER TABLE "myTable" ALTER COLUMN "col_2" TYPE "public"."enum_myTable_col_2" USING ("col_2"::"public"."enum_myTable_col_2");`, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM "myTable";', + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT "id", "name" FROM "myTable";', + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;', + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."name" = \'foo\';', + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id", "DESC";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC, "myTable"."name";', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt");', + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt"), "title";', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: ['name', 'title'] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name", "title";', + }, + { + arguments: [{ tableName: 'myTable', schema: 'mySchema' }], + expectation: 'SELECT * FROM "mySchema"."myTable";', + }, + { + title: "string in array should escape ' as ''", + arguments: ['myTable', { where: { aliases: { [Op.contains]: ["Queen's"] } } }], + expectation: + 'SELECT * FROM "myTable" WHERE "myTable"."aliases" @> ARRAY[\'Queen\'\'s\']::VARCHAR(255)[];', + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable'], + expectation: 'SELECT * FROM myTable;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT id, name FROM myTable;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: "SELECT * FROM myTable WHERE myTable.name = 'foo';", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { order: ['id DESC'] }], + expectation: 'SELECT * FROM myTable ORDER BY id DESC;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM myTable GROUP BY name;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM myTable GROUP BY name;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { group: ['name', 'title'] }], + expectation: 'SELECT * FROM myTable GROUP BY name, title;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [{ tableName: 'myTable', schema: 'mySchema' }], + expectation: 'SELECT * FROM mySchema.myTable;', + context: { options: { quoteIdentifiers: false } }, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', {}], + expectation: { + query: 'INSERT INTO "myTable" DEFAULT VALUES;', + bind: {}, + }, + }, + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: 'foo' }, {}, { ignoreDuplicates: true }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1) ON CONFLICT DO NOTHING;', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: 'foo' }, {}, { returning: true }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1) RETURNING *;', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: 'foo' }, {}, { ignoreDuplicates: true, returning: true }], + expectation: { + query: + 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1) ON CONFLICT DO NOTHING RETURNING *;', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: `foo';DROP TABLE myTable;` }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO "myTable" ("data") VALUES ($sequelize_1);', + bind: { + sequelize_1: Buffer.from('Sequelize'), + }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO "myTable" ("name","nullValue") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO "myTable" ("name","nullValue") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: undefined }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: 'foo' }], + expectation: { + query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: [ + { tableName: 'myTable', schema: 'mySchema' }, + { name: JSON.stringify({ info: 'Look ma a " quote' }) }, + ], + expectation: { + query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: '{"info":"Look ma a \\" quote"}' }, + }, + }, + { + arguments: [ + { tableName: 'myTable', schema: 'mySchema' }, + { name: "foo';DROP TABLE mySchema.myTable;" }, + ], + expectation: { + query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE mySchema.myTable;" }, + }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO "myTable" ("foo") VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO myTable (name,nullValue) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: null }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO myTable (name,nullValue) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: null }, + }, + context: { options: { omitNull: false, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: null }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', nullValue: undefined }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: 'foo' }], + expectation: { + query: 'INSERT INTO mySchema.myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { tableName: 'myTable', schema: 'mySchema' }, + { name: JSON.stringify({ info: 'Look ma a " quote' }) }, + ], + expectation: { + query: 'INSERT INTO mySchema.myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: '{"info":"Look ma a \\" quote"}' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { tableName: 'myTable', schema: 'mySchema' }, + { name: "foo';DROP TABLE mySchema.myTable;" }, + ], + expectation: { + query: 'INSERT INTO mySchema.myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE mySchema.myTable;" }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: + 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\') ON CONFLICT DO NOTHING;', + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { returning: true }], + expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\') RETURNING *;', + }, + { + arguments: [ + 'myTable', + [{ name: 'foo' }, { name: 'bar' }], + { returning: ['id', 'sentToId'] }, + ], + expectation: + 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\') RETURNING "id", "sentToId";', + }, + { + arguments: [ + 'myTable', + [{ name: 'foo' }, { name: 'bar' }], + { ignoreDuplicates: true, returning: true }, + ], + expectation: + 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\') ON CONFLICT DO NOTHING RETURNING *;', + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: + "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');", + }, + { + arguments: [ + 'myTable', + [ + { + name: 'foo', + birthday: dayjs('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + { + name: 'bar', + birthday: dayjs('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + ], + ], + expectation: + "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: 'INSERT INTO "myTable" ("name","foo") VALUES (\'foo\',1),(\'bar\',2);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","nullValue") VALUES (\'foo\',NULL),(\'bar\',NULL);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","nullValue") VALUES (\'foo\',NULL),(\'bar\',NULL);', + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","nullValue") VALUES (\'foo\',NULL),(\'bar\',NULL);', + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: undefined }, + { name: 'bar', nullValue: undefined }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","nullValue") VALUES (\'foo\',NULL),(\'bar\',NULL);', + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [{ name: 'foo' }, { name: 'bar' }], + ], + expectation: 'INSERT INTO "mySchema"."myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [ + { name: JSON.stringify({ info: 'Look ma a " quote' }) }, + { name: JSON.stringify({ info: 'Look ma another " quote' }) }, + ], + ], + expectation: + 'INSERT INTO "mySchema"."myTable" ("name") VALUES (\'{"info":"Look ma a \\" quote"}\'),(\'{"info":"Look ma another \\" quote"}\');', + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [{ name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'bar' }], + ], + expectation: + 'INSERT INTO "mySchema"."myTable" ("name") VALUES (\'foo\'\';DROP TABLE mySchema.myTable;\'),(\'bar\');', + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [{ name: 'foo' }, { name: 'bar' }], + { updateOnDuplicate: ['name'], upsertKeys: ['name'] }, + ], + expectation: + 'INSERT INTO "mySchema"."myTable" ("name") VALUES (\'foo\'),(\'bar\') ON CONFLICT ("name") DO UPDATE SET "name"=EXCLUDED."name";', + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { + name: 'foo', + birthday: dayjs('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + { + name: 'bar', + birthday: dayjs('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + ], + ], + expectation: + "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2);", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: `INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);`, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: `INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);`, + context: { options: { quoteIdentifiers: false, omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: `INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);`, + context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', nullValue: undefined }, + { name: 'bar', nullValue: undefined }, + ], + ], + expectation: `INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);`, + context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: As above + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [{ name: 'foo' }, { name: 'bar' }], + ], + expectation: `INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar');`, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [ + { name: JSON.stringify({ info: 'Look ma a " quote' }) }, + { name: JSON.stringify({ info: 'Look ma another " quote' }) }, + ], + ], + expectation: + 'INSERT INTO mySchema.myTable (name) VALUES (\'{"info":"Look ma a \\" quote"}\'),(\'{"info":"Look ma another \\" quote"}\');', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + [{ name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'bar' }], + ], + expectation: + "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }, { returning: true }], + expectation: { + query: + 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2 RETURNING *', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "name"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: undefined }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + { tableName: 'myTable', schema: 'mySchema' }, + { name: "foo';DROP TABLE mySchema.myTable;" }, + { name: 'foo' }, + ], + expectation: { + query: + 'UPDATE "mySchema"."myTable" SET "name"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE mySchema.myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE "myTable" SET "bar"=NOW() WHERE "name" = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE "myTable" SET "bar"="foo" WHERE "name" = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET name=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: undefined }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: [ + { schema: 'mySchema', tableName: 'myTable' }, + { name: "foo';DROP TABLE mySchema.myTable;" }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE mySchema.myTable SET name=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE mySchema.myTable;", sequelize_2: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + ], + + createTrigger: [ + { + arguments: ['myTable', 'myTrigger', 'after', ['insert'], 'myFunction', [], []], + expectation: + 'CREATE TRIGGER "myTrigger" AFTER INSERT ON "myTable" EXECUTE PROCEDURE myFunction();', + }, + { + arguments: [ + 'myTable', + 'myTrigger', + 'before', + ['insert', 'update'], + 'myFunction', + [{ name: 'bar', type: 'INTEGER' }], + [], + ], + expectation: + 'CREATE TRIGGER "myTrigger" BEFORE INSERT OR UPDATE ON "myTable" EXECUTE PROCEDURE myFunction(bar INTEGER);', + }, + { + arguments: [ + 'myTable', + 'myTrigger', + 'instead_of', + ['insert', 'update'], + 'myFunction', + [], + ['FOR EACH ROW'], + ], + expectation: + 'CREATE TRIGGER "myTrigger" INSTEAD OF INSERT OR UPDATE ON "myTable" FOR EACH ROW EXECUTE PROCEDURE myFunction();', + }, + { + arguments: [ + 'myTable', + 'myTrigger', + 'after_constraint', + ['insert', 'update'], + 'myFunction', + [{ name: 'bar', type: 'INTEGER' }], + ['FOR EACH ROW'], + ], + expectation: + 'CREATE CONSTRAINT TRIGGER "myTrigger" AFTER INSERT OR UPDATE ON "myTable" FOR EACH ROW EXECUTE PROCEDURE myFunction(bar INTEGER);', + }, + ], + + dropTrigger: [ + { + arguments: ['myTable', 'myTrigger'], + expectation: 'DROP TRIGGER "myTrigger" ON "myTable" RESTRICT;', + }, + ], + + renameTrigger: [ + { + arguments: ['myTable', 'oldTrigger', 'newTrigger'], + expectation: 'ALTER TRIGGER "oldTrigger" ON "myTable" RENAME TO "newTrigger";', + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || + `Postgres correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const newSequelize = Support.createSequelizeInstance({ + ...test.context?.options, + }); + + const queryGenerator = newSequelize.queryGenerator; + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](newSequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](newSequelize); + } + } + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + + describe('fromArray()', () => { + beforeEach(function () { + this.queryGenerator = new QueryGenerator(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/packages/core/test/unit/dialects/postgres/range-data-type.test.ts b/packages/core/test/unit/dialects/postgres/range-data-type.test.ts new file mode 100644 index 000000000000..6af03f294d8b --- /dev/null +++ b/packages/core/test/unit/dialects/postgres/range-data-type.test.ts @@ -0,0 +1,155 @@ +import type { Rangable } from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { createSequelizeInstance, sequelize } from '../../../support'; + +const dialectName = sequelize.dialect.name; + +describe('[POSTGRES Specific] RANGE DataType', () => { + if (!dialectName.startsWith('postgres')) { + return; + } + + const sequelizeWithTzOffset = createSequelizeInstance({ + timezone: '+02:00', + }); + + const dialect = sequelizeWithTzOffset.dialect; + + const integerRangeType = DataTypes.RANGE(DataTypes.INTEGER).toDialectDataType(dialect); + const bigintRangeType = DataTypes.RANGE(DataTypes.BIGINT).toDialectDataType(dialect); + const decimalRangeType = DataTypes.RANGE(DataTypes.DECIMAL).toDialectDataType(dialect); + const dateRangeType = DataTypes.RANGE(DataTypes.DATE).toDialectDataType(dialect); + const dateOnlyRangeType = DataTypes.RANGE(DataTypes.DATEONLY).toDialectDataType(dialect); + + describe('escape', () => { + it('should handle empty objects correctly', () => { + expect(integerRangeType.escape([])).to.equal(`'empty'::int4range`); + }); + + it('should handle null as empty bound', () => { + expect(integerRangeType.escape([null, 1])).to.equal(`'[,1)'::int4range`); + expect(integerRangeType.escape([1, null])).to.equal(`'[1,)'::int4range`); + expect(integerRangeType.escape([null, null])).to.equal(`'[,)'::int4range`); + }); + + it('should handle Infinity/-Infinity as infinity/-infinity bounds', () => { + expect(integerRangeType.escape([Number.POSITIVE_INFINITY, 1])).to.equal( + `'[infinity,1)'::int4range`, + ); + expect(integerRangeType.escape([1, Number.POSITIVE_INFINITY])).to.equal( + `'[1,infinity)'::int4range`, + ); + expect(integerRangeType.escape([Number.NEGATIVE_INFINITY, 1])).to.equal( + `'[-infinity,1)'::int4range`, + ); + expect(integerRangeType.escape([1, Number.NEGATIVE_INFINITY])).to.equal( + `'[1,-infinity)'::int4range`, + ); + expect( + integerRangeType.escape([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), + ).to.equal(`'[-infinity,infinity)'::int4range`); + }); + + it('should throw error when array length is not 0 or 2', () => { + expect(() => { + // @ts-expect-error -- testing that invalid input throws + integerRangeType.escape([1]); + }).to.throw(); + expect(() => { + // @ts-expect-error -- testing that invalid input throws + integerRangeType.escape([1, 2, 3]); + }).to.throw(); + }); + + it('should throw error when non-array parameter is passed', () => { + expect(() => { + // @ts-expect-error -- testing that invalid input throws + integerRangeType.escape({}); + }).to.throw(); + expect(() => { + integerRangeType.escape('test'); + }).to.throw(); + expect(() => { + // @ts-expect-error -- testing that invalid input throws + integerRangeType.escape(); + }).to.throw(); + }); + + it('should handle array of objects with `inclusive` and `value` properties', () => { + expect(integerRangeType.escape([{ inclusive: true, value: 0 }, { value: 1 }])).to.equal( + `'[0,1)'::int4range`, + ); + expect( + integerRangeType.escape([ + { inclusive: true, value: 0 }, + { inclusive: true, value: 1 }, + ]), + ).to.equal(`'[0,1]'::int4range`); + expect(integerRangeType.escape([{ inclusive: false, value: 0 }, 1])).to.equal( + `'(0,1)'::int4range`, + ); + expect(integerRangeType.escape([0, { inclusive: true, value: 1 }])).to.equal( + `'[0,1]'::int4range`, + ); + }); + + it('should handle date values', () => { + expect( + dateRangeType.escape([new Date(Date.UTC(2000, 1, 1)), new Date(Date.UTC(2000, 1, 2))]), + ).to.equal(`'[2000-02-01 02:00:00.000 +02:00,2000-02-02 02:00:00.000 +02:00)'::tstzrange`); + }); + }); + + describe('stringify value', () => { + describe('with null range bounds', () => { + const infiniteRange: Rangable = [null, null]; + const infiniteRangeSQL = `'[,)'`; + + it('should stringify integer range to infinite range', () => { + expect(integerRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::int4range`); + }); + + it('should stringify bigint range to infinite range', () => { + expect(bigintRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::int8range`); + }); + + it('should stringify numeric range to infinite range', () => { + expect(decimalRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::numrange`); + }); + + it('should stringify dateonly ranges to infinite range', () => { + expect(dateOnlyRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::daterange`); + }); + + it('should stringify date ranges to infinite range', () => { + expect(dateRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::tstzrange`); + }); + }); + + describe('with infinite range bounds', () => { + const infiniteRange: Rangable = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]; + const infiniteRangeSQL = "'[-infinity,infinity)'"; + + it('should stringify integer range to infinite range', () => { + expect(integerRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::int4range`); + }); + + it('should stringify bigint range to infinite range', () => { + expect(bigintRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::int8range`); + }); + + it('should stringify numeric range to infinite range', () => { + expect(decimalRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::numrange`); + }); + + it('should stringify dateonly ranges to infinite range', () => { + expect(dateOnlyRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::daterange`); + }); + + it('should stringify date ranges to infinite range', () => { + expect(dateRangeType.escape(infiniteRange)).to.equal(`${infiniteRangeSQL}::tstzrange`); + }); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/snowflake/errors.test.js b/packages/core/test/unit/dialects/snowflake/errors.test.js new file mode 100644 index 000000000000..3b72494fb84c --- /dev/null +++ b/packages/core/test/unit/dialects/snowflake/errors.test.js @@ -0,0 +1,63 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const { Sequelize } = require('@sequelize/core'); + +const dialect = Support.getTestDialect(); +const queryProto = Support.sequelize.dialect.Query.prototype; + +if (dialect === 'snowflake') { + describe('[SNOWFLAKE Specific] ForeignKeyConstraintError - error message parsing', () => { + it('FK Errors with ` quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).', + ); + + fakeErr.code = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('FK Errors with " quotation char are parsed correctly', () => { + const fakeErr = new Error( + 'Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).', + ); + + fakeErr.code = 1451; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.reltype).to.equal('parent'); + expect(parsedErr.table).to.equal('people'); + expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); + expect(parsedErr.value).to.be.undefined; + expect(parsedErr.index).to.equal('brothers_ibfk_1'); + }); + + it('newlines contained in err message are parsed correctly', () => { + const fakeErr = new Error("Duplicate entry '13888888888\r' for key 'num'"); + + fakeErr.code = 1062; + + const parsedErr = queryProto.formatError(fakeErr); + + expect(parsedErr).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(parsedErr.cause).to.equal(fakeErr); + expect(parsedErr.fields.num).to.equal('13888888888\r'); + }); + }); +} diff --git a/packages/core/test/unit/dialects/snowflake/query-generator.test.js b/packages/core/test/unit/dialects/snowflake/query-generator.test.js new file mode 100644 index 000000000000..8b1a2a315527 --- /dev/null +++ b/packages/core/test/unit/dialects/snowflake/query-generator.test.js @@ -0,0 +1,981 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const { Op } = require('@sequelize/core'); +const { SnowflakeQueryGenerator: QueryGenerator } = require('@sequelize/snowflake'); +const { createSequelizeInstance } = require('../../../support'); + +if (dialect === 'snowflake') { + describe('[SNOWFLAKE Specific] QueryGenerator', () => { + Support.allowDeprecationsInSuite(['SEQUELIZE0023']); + + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { id: 'INTEGER AUTOINCREMENT PRIMARY KEY' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + title: 'Add column level comment', + arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], + expectation: { id: "INTEGER COMMENT 'Test'" }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + { + arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], + expectation: { id: 'INTEGER AFTER "Bar"' }, + }, + // No Default Values allowed for certain types + { + title: 'No Default value for SNOWFLAKE BLOB allowed', + arguments: [{ id: { type: 'BLOB', defaultValue: [] } }], + expectation: { id: 'BLOB' }, + }, + { + title: 'No Default value for SNOWFLAKE TEXT allowed', + arguments: [{ id: { type: 'TEXT', defaultValue: [] } }], + expectation: { id: 'TEXT' }, + }, + { + title: 'No Default value for SNOWFLAKE GEOMETRY allowed', + arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }], + expectation: { id: 'GEOMETRY' }, + }, + { + title: 'No Default value for SNOWFLAKE JSON allowed', + arguments: [{ id: { type: 'JSON', defaultValue: [] } }], + expectation: { id: 'JSON' }, + }, + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id")' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES "Bar" ("pk")' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + autoIncrement: true, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL AUTOINCREMENT DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + + // Variants when quoteIdentifiers is false + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES Bar (id)' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES Bar (pk)' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT' }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + autoIncrement: true, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL AUTOINCREMENT DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT', + }, + context: { options: { quoteIdentifiers: false } }, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM "myTable";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT "id", "name" FROM "myTable";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: 'SELECT * FROM "myTable" WHERE "myTable"."name" = \'foo\';', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "id", "DESC";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC;', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC, "myTable"."name";', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + context: QueryGenerator, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name";', + context: QueryGenerator, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt");', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt"), "title";', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM "myTable" GROUP BY "name" ORDER BY "id" DESC;', + context: QueryGenerator, + }, + { + title: 'Empty having', + arguments: [ + 'myTable', + function () { + return { + having: {}, + }; + }, + ], + expectation: 'SELECT * FROM "myTable";', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'Having in subquery', + arguments: [ + 'myTable', + function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }, + ], + expectation: + 'SELECT "test".* FROM (SELECT * FROM "myTable" AS "test" HAVING "test"."creationYear" > 2002) AS "test";', + context: QueryGenerator, + needsSequelize: true, + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable'], + expectation: 'SELECT * FROM myTable;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT id, name FROM myTable;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: "SELECT * FROM myTable WHERE myTable.name = 'foo';", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM myTable ORDER BY id;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM myTable ORDER BY id, DESC;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM myTable ORDER BY myTable.id;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM myTable ORDER BY myTable.id DESC;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM myTable AS myTable ORDER BY myTable.id DESC;', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM myTable AS myTable ORDER BY myTable.id DESC, myTable.name;', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM myTable GROUP BY name;', + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM myTable GROUP BY name;', + context: { options: { quoteIdentifiers: false } }, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM myTable GROUP BY YEAR(createdAt);', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM myTable GROUP BY YEAR(createdAt), title;', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM myTable GROUP BY name ORDER BY id DESC;', + context: { options: { quoteIdentifiers: false } }, + }, + { + title: 'Empty having', + arguments: [ + 'myTable', + function () { + return { + having: {}, + }; + }, + ], + expectation: 'SELECT * FROM myTable;', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + { + title: 'Having in subquery', + arguments: [ + 'myTable', + function () { + return { + subQuery: true, + tableAs: 'test', + having: { creationYear: { [Op.gt]: 2002 } }, + }; + }, + ], + expectation: + 'SELECT test.* FROM (SELECT * FROM myTable AS test HAVING test.creationYear > 2002) AS test;', + context: { options: { quoteIdentifiers: false } }, + needsSequelize: true, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO "myTable" ("data") VALUES ($sequelize_1);', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO "myTable" ("foo") VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], + expectation: { + query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);', + bind: { sequelize_1: "foo';DROP TABLE myTable;" }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1 }], + expectation: { + query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO myTable (data) VALUES ($sequelize_1);', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO myTable (name,foo,nullValue) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO myTable (name,foo,nullValue) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO myTable (foo) VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + context: { options: { quoteIdentifiers: false } }, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: + "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, + { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }, + ], + ], + expectation: `INSERT INTO "myTable" ("name","birthday") VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');`, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: 'INSERT INTO "myTable" ("name","foo") VALUES (\'foo\',1),(\'bar\',2);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',NULL,NULL);', + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);', + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);', + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: undefined }, + { name: 'bar', foo: 2, undefinedValue: undefined }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","foo","nullValue","undefinedValue") VALUES (\'foo\',1,NULL,NULL),(\'bar\',2,NULL,NULL);', + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: + 'INSERT INTO "myTable" ("name","value") VALUES (\'foo\',true),(\'bar\',false);', + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: 'INSERT IGNORE INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');', + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], + expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1 }, + { name: 'bar', foo: 2 }, + ], + ], + expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2);", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', nullValue: null }, + ], + ], + expectation: + "INSERT INTO myTable (name,foo,nullValue) VALUES ('foo',1,NULL),('bar',NULL,NULL);", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO myTable (name,foo,nullValue) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: false, quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO myTable (name,foo,nullValue) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: undefined }, + { name: 'bar', foo: 2, undefinedValue: undefined }, + ], + ], + expectation: + "INSERT INTO myTable (name,foo,nullValue,undefinedValue) VALUES ('foo',1,NULL,NULL),('bar',2,NULL,NULL);", + context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: As above + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: "INSERT INTO myTable (name,value) VALUES ('foo',true),('bar',false);", + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: "INSERT IGNORE INTO myTable (name) VALUES ('foo'),('bar');", + context: { options: { quoteIdentifiers: false } }, + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "name"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE "myTable" SET "bar"=NOW() WHERE "name" = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE "myTable" SET "bar"="foo" WHERE "name" = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + + // Variants when quoteIdentifiers is false + { + arguments: ['myTable', { bar: 2 }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET name=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: "foo';DROP TABLE myTable;", sequelize_2: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false, quoteIdentifiers: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true, quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE myTable SET bar=NOW() WHERE name = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + context: { options: { quoteIdentifiers: false } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE myTable SET bar=foo WHERE name = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + context: { options: { quoteIdentifiers: false } }, + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || + `SNOWFLAKE correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const sequelize = createSequelizeInstance({ + ...(test.context && test.context.options), + }); + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](sequelize); + } + } + + const queryGenerator = sequelize.dialect.queryGenerator; + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/packages/core/test/unit/dialects/snowflake/query.test.js b/packages/core/test/unit/dialects/snowflake/query.test.js new file mode 100644 index 000000000000..e6ebba185598 --- /dev/null +++ b/packages/core/test/unit/dialects/snowflake/query.test.js @@ -0,0 +1,36 @@ +'use strict'; + +const { SnowflakeQuery: Query } = require('@sequelize/snowflake'); + +const Support = require('../../../support'); +const chai = require('chai'); +const sinon = require('sinon'); + +const current = Support.sequelize; +const expect = chai.expect; + +describe('[SNOWFLAKE Specific] Query', () => { + describe('logWarnings', () => { + beforeEach(() => { + sinon.spy(console, 'debug'); + }); + + afterEach(() => { + console.debug.restore(); + }); + + it('check iterable', async () => { + const validWarning = []; + const invalidWarning = {}; + const warnings = [validWarning, undefined, invalidWarning]; + + const query = new Query({}, current, {}); + const stub = sinon.stub(query, 'run'); + stub.onFirstCall().resolves(warnings); + + const results = await query.logWarnings('dummy-results'); + expect('dummy-results').to.equal(results); + expect(true).to.equal(console.debug.calledOnce); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/sqlite/connection-manager.test.js b/packages/core/test/unit/dialects/sqlite/connection-manager.test.js new file mode 100644 index 000000000000..c7bdd736d656 --- /dev/null +++ b/packages/core/test/unit/dialects/sqlite/connection-manager.test.js @@ -0,0 +1,42 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const { Sequelize } = require('@sequelize/core'); +const { SqliteDialect } = require('@sequelize/sqlite3'); + +const dialect = Support.getTestDialect(); + +describe('[SQLITE Specific] ConnectionManager', () => { + if (dialect !== 'sqlite3') { + return; + } + + describe('getConnection', () => { + it('should forward empty string storage to SQLite connector to create temporary disk-based database', async () => { + // storage='' means anonymous disk-based database + const sequelize = new Sequelize({ + dialect: SqliteDialect, + storage: '', + pool: { max: 1, idle: Infinity }, + }); + + const connection = await sequelize.pool.acquire(); + expect(connection.filename).to.equal(''); + }); + + it('supports :memory: database', async () => { + const sequelize = new Sequelize({ + dialect: SqliteDialect, + storage: ':memory:', + pool: { max: 1, idle: Infinity }, + }); + + const connection = await sequelize.pool.acquire(); + expect(connection.filename).to.equal(':memory:'); + }); + }); +}); diff --git a/packages/core/test/unit/dialects/sqlite/query-generator.test.js b/packages/core/test/unit/dialects/sqlite/query-generator.test.js new file mode 100644 index 000000000000..54c2fcad93cc --- /dev/null +++ b/packages/core/test/unit/dialects/sqlite/query-generator.test.js @@ -0,0 +1,545 @@ +'use strict'; + +const each = require('lodash/each'); + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../../support'); + +const dialect = Support.getTestDialect(); +const dayjs = require('dayjs'); +const { SqliteQueryGenerator: QueryGenerator } = require('@sequelize/sqlite3'); +const { createSequelizeInstance } = require('../../../support'); + +if (dialect === 'sqlite3') { + describe('[SQLITE Specific] QueryGenerator', () => { + const suites = { + attributesToSQL: [ + { + arguments: [{ id: 'INTEGER' }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], + expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' }, + }, + { + arguments: [{ id: { type: 'INTEGER' } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: false } }], + expectation: { id: 'INTEGER NOT NULL' }, + }, + { + arguments: [{ id: { type: 'INTEGER', allowNull: true } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], + expectation: { id: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], + expectation: { id: 'INTEGER DEFAULT 0' }, + }, + { + arguments: [{ id: { type: 'INTEGER', defaultValue: undefined } }], + expectation: { id: 'INTEGER' }, + }, + { + arguments: [{ id: { type: 'INTEGER', unique: true } }], + expectation: { id: 'INTEGER UNIQUE' }, + }, + + // New references style + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' }, + }, + { + arguments: [{ id: { type: 'INTEGER', references: { table: 'Bar', key: 'pk' } } }], + expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onDelete: 'CASCADE' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' }, + }, + { + arguments: [ + { id: { type: 'INTEGER', references: { table: 'Bar' }, onUpdate: 'RESTRICT' } }, + ], + expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' }, + }, + { + arguments: [ + { + id: { + type: 'INTEGER', + allowNull: false, + defaultValue: 1, + references: { table: 'Bar' }, + onDelete: 'CASCADE', + onUpdate: 'RESTRICT', + }, + }, + ], + expectation: { + id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT', + }, + }, + ], + + selectQuery: [ + { + arguments: ['myTable'], + expectation: 'SELECT * FROM `myTable`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { attributes: ['id', 'name'] }], + expectation: 'SELECT `id`, `name` FROM `myTable`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { id: 2 } }], + expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { where: { name: 'foo' } }], + expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['id', 'DESC'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: ['myTable.id'] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', + context: QueryGenerator, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + { order: [['id', 'DESC'], ['name']] }, + function (sequelize) { + return sequelize.define('myTable', {}); + }, + ], + expectation: + 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'single string argument should be quoted', + arguments: ['myTable', { group: 'name' }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { group: ['name'] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', + context: QueryGenerator, + }, + { + title: 'functions work for group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt'))], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', + context: QueryGenerator, + needsSequelize: true, + }, + { + title: 'It is possible to mix sequelize.fn and string arguments to group by', + arguments: [ + 'myTable', + function (sequelize) { + return { + group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'], + }; + }, + ], + expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', + context: QueryGenerator, + needsSequelize: true, + }, + { + arguments: ['myTable', { group: ['name', 'title'] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name`, `title`;', + context: QueryGenerator, + }, + { + arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], + expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', + context: QueryGenerator, + }, + ], + + insertQuery: [ + { + arguments: ['myTable', { name: 'foo' }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: 'foo' }, + }, + }, + { + arguments: ['myTable', { name: "'bar'" }], + expectation: { + query: 'INSERT INTO `myTable` (`name`) VALUES ($sequelize_1);', + bind: { sequelize_1: "'bar'" }, + }, + }, + { + arguments: ['myTable', { data: Buffer.from('Sequelize') }], + expectation: { + query: 'INSERT INTO `myTable` (`data`) VALUES ($sequelize_1);', + bind: { sequelize_1: Buffer.from('Sequelize') }, + }, + }, + { + arguments: ['myTable', { name: 'bar', value: null }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'bar', sequelize_2: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: + 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);', + bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], + expectation: { + query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($sequelize_1,$sequelize_2);', + bind: { sequelize_1: 'foo', sequelize_2: 1 }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + foo: sequelize.fn('NOW'), + }; + }, + ], + expectation: { + query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', + bind: {}, + }, + needsSequelize: true, + }, + ], + + bulkInsertQuery: [ + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], + expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: ['myTable', [{ name: "'bar'" }, { name: 'foo' }]], + expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');", + }, + { + arguments: [ + 'myTable', + [ + { + name: 'foo', + birthday: dayjs('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + { + name: 'bar', + birthday: dayjs('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), + }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');", + }, + { + arguments: [ + 'myTable', + [ + { name: 'bar', value: null }, + { name: 'foo', value: 1 }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'bar', value: undefined }, + { name: 'bar', value: 2 }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: true }, + { name: 'bar', value: false }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', value: false }, + { name: 'bar', value: false }, + ], + ], + expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: false } }, + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not + }, + { + arguments: [ + 'myTable', + [ + { name: 'foo', foo: 1, nullValue: null }, + { name: 'bar', foo: 2, nullValue: null }, + ], + ], + expectation: + "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", + context: { options: { omitNull: true } }, // Note: As above + }, + { + arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], + expectation: "INSERT OR IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');", + }, + { + arguments: [ + 'myTable', + [{ name: 'foo' }, { name: 'bar' }], + { updateOnDuplicate: ['name'], upsertKeys: ['name'] }, + ], + expectation: + "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON CONFLICT (`name`) DO UPDATE SET `name`=EXCLUDED.`name`;", + }, + ], + + updateQuery: [ + { + arguments: ['myTable', { name: 'foo' }, { id: 2 }], + expectation: { + query: 'UPDATE `myTable` SET `name`=$sequelize_1 WHERE `id` = $sequelize_2', + bind: { sequelize_1: 'foo', sequelize_2: 2 }, + }, + }, + { + arguments: ['myTable', { name: "'bar'" }, { id: 2 }], + expectation: { + query: 'UPDATE `myTable` SET `name`=$sequelize_1 WHERE `id` = $sequelize_2', + bind: { sequelize_1: "'bar'", sequelize_2: 2 }, + }, + }, + { + arguments: ['myTable', { name: 'bar', value: null }, { id: 2 }], + expectation: { + query: + 'UPDATE `myTable` SET `name`=$sequelize_1,`value`=$sequelize_2 WHERE `id` = $sequelize_3', + bind: { sequelize_1: 'bar', sequelize_2: null, sequelize_3: 2 }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: + 'UPDATE `myTable` SET `bar`=$sequelize_1,`nullValue`=$sequelize_2 WHERE `name` = $sequelize_3', + bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' }, + }, + context: { options: { omitNull: false } }, + }, + { + arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], + expectation: { + query: 'UPDATE `myTable` SET `bar`=$sequelize_1 WHERE `name` = $sequelize_2', + bind: { sequelize_1: 2, sequelize_2: 'foo' }, + }, + context: { options: { omitNull: true } }, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.fn('NOW'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + { + arguments: [ + 'myTable', + function (sequelize) { + return { + bar: sequelize.col('foo'), + }; + }, + { name: 'foo' }, + ], + expectation: { + query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $sequelize_1', + bind: { sequelize_1: 'foo' }, + }, + needsSequelize: true, + }, + ], + foreignKeyCheckQuery: [ + { + title: 'Properly quotes table names', + arguments: ['myTable'], + expectation: 'PRAGMA foreign_key_check(`myTable`);', + }, + { + title: 'Properly quotes table names as schema', + arguments: [{ schema: 'schema', tableName: 'myTable' }], + expectation: 'PRAGMA foreign_key_check(`schema.myTable`);', + }, + ], + }; + + each(suites, (tests, suiteTitle) => { + describe(suiteTitle, () => { + for (const test of tests) { + const query = test.expectation.query || test.expectation; + const title = + test.title || `SQLite correctly returns ${query} for ${JSON.stringify(test.arguments)}`; + it(title, () => { + const sequelize = createSequelizeInstance({ + ...(test.context && test.context.options), + }); + + if (test.needsSequelize) { + if (typeof test.arguments[1] === 'function') { + test.arguments[1] = test.arguments[1](sequelize); + } + + if (typeof test.arguments[2] === 'function') { + test.arguments[2] = test.arguments[2](sequelize); + } + } + + const queryGenerator = sequelize.dialect.queryGenerator; + + const conditions = queryGenerator[suiteTitle](...test.arguments); + expect(conditions).to.deep.equal(test.expectation); + }); + } + }); + }); + }); +} diff --git a/packages/core/test/unit/errors.test.js b/packages/core/test/unit/errors.test.js new file mode 100644 index 000000000000..f014351a074b --- /dev/null +++ b/packages/core/test/unit/errors.test.js @@ -0,0 +1,65 @@ +'use strict'; + +const expect = require('chai').expect; +const errors = require('@sequelize/core/_non-semver-use-at-your-own-risk_/errors/index.js'); + +const { AggregateError } = errors; + +describe('errors', () => { + it('should maintain stack trace with message', () => { + const errorsWithMessage = [ + 'BaseError', + 'ValidationError', + 'InstanceError', + 'EmptyResultError', + 'EagerLoadingError', + 'AssociationError', + 'QueryError', + ]; + + for (const errorName of errorsWithMessage) { + function throwError() { + throw new errors[errorName]('this is a message'); + } + + let err; + try { + throwError(); + } catch (error) { + err = error; + } + + expect(err).to.exist; + const stackParts = err.stack.split('\n'); + const fullErrorName = `Sequelize${errorName}`; + expect(stackParts[0]).to.equal(`${fullErrorName}: this is a message`); + expect(stackParts[1]).to.match(/^ {4}at throwError \(.*errors.test.js:\d+:\d+\)$/); + } + }); + + describe('AggregateError', () => { + it('get .message works', () => { + 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/packages/core/test/unit/hooks.test.js b/packages/core/test/unit/hooks.test.js new file mode 100644 index 000000000000..2f1bbd8dcecc --- /dev/null +++ b/packages/core/test/unit/hooks.test.js @@ -0,0 +1,499 @@ +'use strict'; + +const noop = require('lodash/noop'); + +const chai = require('chai'); +const sinon = require('sinon'); + +const expect = chai.expect; +const Support = require('../support'); +const { DataTypes, Sequelize } = require('@sequelize/core'); + +const sequelize = Support.sequelize; + +describe(Support.getTestDialectTeaser('Hooks'), () => { + beforeEach(function () { + this.Model = sequelize.define('m'); + }); + + it('does not expose non-model hooks', function () { + for (const badHook of [ + 'beforeDefine', + 'afterDefine', + 'beforeConnect', + 'afterConnect', + 'beforePoolAcquire', + 'afterPoolAcquire', + 'beforeDisconnect', + 'afterDisconnect', + 'beforeInit', + 'afterInit', + ]) { + expect(this.Model).to.not.have.property(badHook); + } + }); + + describe('arguments', () => { + it('hooks can modify passed arguments', async function () { + this.Model.addHook('beforeCreate', options => { + options.answer = 41; + }); + + const options = {}; + await this.Model.runHooks('beforeCreate', options); + expect(options.answer).to.equal(41); + }); + }); + + describe('proxies', () => { + beforeEach(() => { + sinon.stub(sequelize, 'queryRaw').resolves([ + { + _previousDataValues: {}, + dataValues: { id: 1, name: 'abc' }, + }, + ]); + }); + + afterEach(() => { + sequelize.queryRaw.restore(); + }); + + describe('defined by options.hooks', () => { + beforeEach(function () { + this.beforeSaveHook = sinon.spy(); + this.afterSaveHook = sinon.spy(); + this.afterCreateHook = sinon.spy(); + + this.Model = sequelize.define( + 'm', + { + name: DataTypes.STRING, + }, + { + hooks: { + beforeSave: this.beforeSaveHook, + afterSave: this.afterSaveHook, + afterCreate: this.afterCreateHook, + }, + }, + ); + }); + + it('calls beforeSave/afterSave', async function () { + await this.Model.create({}); + expect(this.afterCreateHook).to.have.been.calledOnce; + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; + }); + }); + + describe('defined by addHook method', () => { + beforeEach(function () { + this.beforeSaveHook = sinon.spy(); + this.afterSaveHook = sinon.spy(); + + this.Model = sequelize.define('m', { + name: DataTypes.STRING, + }); + + this.Model.addHook('beforeSave', this.beforeSaveHook); + this.Model.addHook('afterSave', this.afterSaveHook); + }); + + it('calls beforeSave/afterSave', async function () { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; + }); + }); + + describe('defined by hook method', () => { + beforeEach(function () { + this.beforeSaveHook = sinon.spy(); + this.afterSaveHook = sinon.spy(); + + this.Model = sequelize.define('m', { + name: DataTypes.STRING, + }); + + this.Model.addHook('beforeSave', this.beforeSaveHook); + this.Model.addHook('afterSave', this.afterSaveHook); + }); + + it('calls beforeSave/afterSave', async function () { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; + }); + }); + }); + + describe('multiple hooks', () => { + beforeEach(function () { + this.hook1 = sinon.spy(); + this.hook2 = sinon.spy(); + this.hook3 = sinon.spy(); + }); + + describe('runs all hooks on success', () => { + afterEach(function () { + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).to.have.been.calledOnce; + expect(this.hook3).to.have.been.calledOnce; + }); + + it('using addHook', async function () { + this.Model.addHook('beforeCreate', this.hook1); + this.Model.addHook('beforeCreate', this.hook2); + this.Model.addHook('beforeCreate', this.hook3); + + await this.Model.runHooks('beforeCreate'); + }); + + it('using function', async function () { + this.Model.beforeCreate(this.hook1); + this.Model.beforeCreate(this.hook2); + this.Model.beforeCreate(this.hook3); + + await this.Model.runHooks('beforeCreate'); + }); + + it('using define', async function () { + await sequelize + .define( + 'M', + {}, + { + hooks: { + beforeCreate: [this.hook1, this.hook2, this.hook3], + }, + }, + ) + .runHooks('beforeCreate'); + }); + + it('using a mixture', async function () { + const Model = sequelize.define( + 'M', + {}, + { + hooks: { + beforeCreate: this.hook1, + }, + }, + ); + Model.beforeCreate(this.hook2); + Model.addHook('beforeCreate', this.hook3); + + await Model.runHooks('beforeCreate'); + }); + }); + + it('stops execution when a hook throws', async function () { + this.Model.beforeCreate(() => { + this.hook1(); + + throw new Error('No!'); + }); + this.Model.beforeCreate(this.hook2); + + 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', async function () { + this.Model.beforeCreate(async () => { + this.hook1(); + + throw new Error('No!'); + }); + this.Model.beforeCreate(this.hook2); + + 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', async function () { + const globalHook = sinon.spy(); + + sequelize.addHook('beforeUpdate', globalHook); + + await this.Model.runHooks('beforeUpdate'); + expect(globalHook).to.have.been.calledOnce; + }); + + it('invokes the global hook, when the model also has a hook', async () => { + const globalHookBefore = sinon.spy(); + const globalHookAfter = sinon.spy(); + const localHook = sinon.spy(); + + sequelize.addHook('beforeUpdate', globalHookBefore); + + const Model = sequelize.define( + 'm', + {}, + { + hooks: { + beforeUpdate: localHook, + }, + }, + ); + + sequelize.addHook('beforeUpdate', globalHookAfter); + + 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); + }); + }); + + it('registers both the global define hook, and the local hook', async () => { + const globalHook = sinon.spy(); + const sequelize = Support.createSequelizeInstance({ + define: { + hooks: { + beforeCreate: globalHook, + }, + }, + }); + + const localHook = sinon.spy(); + + const Model = sequelize.define( + 'M', + {}, + { + hooks: { + beforeUpdate: noop, // Just to make sure we can define other hooks without overwriting the global one + beforeCreate: localHook, + }, + }, + ); + + await Model.runHooks('beforeCreate'); + expect(globalHook).to.have.been.calledOnce; + expect(localHook).to.have.been.calledOnce; + }); + }); + + describe('#removeHook', () => { + it('should remove hook', async function () { + const hook1 = sinon.spy(); + const hook2 = sinon.spy(); + + this.Model.addHook('beforeCreate', 'myHook', hook1); + this.Model.beforeCreate('myHook2', hook2); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + + hook1.resetHistory(); + hook2.resetHistory(); + + this.Model.removeHook('beforeCreate', 'myHook'); + this.Model.removeHook('beforeCreate', 'myHook2'); + + 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', async function () { + const hook1 = sinon.spy(); + const hook2 = sinon.spy(); + const hook3 = sinon.spy(); + const hook4 = sinon.spy(); + + this.Model.addHook('beforeCreate', hook1); + this.Model.addHook('beforeCreate', 'myHook', hook2); + this.Model.beforeCreate('myHook2', hook3); + this.Model.beforeCreate(hook4); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; + + hook1.resetHistory(); + hook2.resetHistory(); + hook3.resetHistory(); + hook4.resetHistory(); + + this.Model.removeHook('beforeCreate', 'myHook'); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).not.to.have.been.called; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; + }); + }); + + describe('#addHook', () => { + it('should add additional hook when previous exists', async function () { + const hook1 = sinon.spy(); + const hook2 = sinon.spy(); + + const Model = this.sequelize.define( + 'Model', + {}, + { + hooks: { beforeCreate: hook1 }, + }, + ); + + Model.addHook('beforeCreate', hook2); + + await Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + }); + }); + + describe('promises', () => { + it('can return a promise', async function () { + this.Model.beforeBulkCreate(async () => { + // This space intentionally left blank + }); + + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + }); + + it('can return undefined', async function () { + this.Model.beforeBulkCreate(() => { + // This space intentionally left blank + }); + + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + }); + + it('can return an error by rejecting', async function () { + this.Model.beforeCreate(async () => { + throw new Error('Forbidden'); + }); + + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + }); + + it('can return an error by throwing', async function () { + this.Model.beforeCreate(() => { + throw new Error('Forbidden'); + }); + + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + }); + }); + + describe('sync hooks', () => { + beforeEach(function () { + this.hook1 = sinon.spy(); + this.hook2 = sinon.spy(); + this.hook3 = sinon.spy(); + this.hook4 = sinon.spy(); + }); + + it('runs all beforInit/afterInit hooks', function () { + Sequelize.addHook('beforeInit', 'h1', this.hook1); + Sequelize.addHook('beforeInit', 'h2', this.hook2); + Sequelize.addHook('afterInit', 'h3', this.hook3); + Sequelize.addHook('afterInit', 'h4', this.hook4); + + Support.createSequelizeInstance(); + + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).to.have.been.calledOnce; + expect(this.hook3).to.have.been.calledOnce; + expect(this.hook4).to.have.been.calledOnce; + + // cleanup hooks on Sequelize + Sequelize.removeHook('beforeInit', 'h1'); + Sequelize.removeHook('beforeInit', 'h2'); + Sequelize.removeHook('afterInit', 'h3'); + Sequelize.removeHook('afterInit', 'h4'); + + Support.createSequelizeInstance(); + + // check if hooks were removed + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).to.have.been.calledOnce; + expect(this.hook3).to.have.been.calledOnce; + expect(this.hook4).to.have.been.calledOnce; + }); + + it('runs all beforDefine/afterDefine hooks', function () { + const sequelize = Support.createSequelizeInstance(); + sequelize.addHook('beforeDefine', this.hook1); + sequelize.addHook('beforeDefine', this.hook2); + sequelize.addHook('afterDefine', this.hook3); + sequelize.addHook('afterDefine', this.hook4); + sequelize.define('Test', {}); + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).to.have.been.calledOnce; + expect(this.hook3).to.have.been.calledOnce; + expect(this.hook4).to.have.been.calledOnce; + }); + }); + + describe('#removal', () => { + before(() => { + sinon.stub(sequelize, 'queryRaw').resolves([ + { + _previousDataValues: {}, + dataValues: { id: 1, name: 'abc' }, + }, + ]); + }); + + after(() => { + sequelize.queryRaw.restore(); + }); + + it('should be able to remove by name', async () => { + const User = sequelize.define('User'); + + const hook1 = sinon.spy(); + const hook2 = sinon.spy(); + + User.addHook('beforeCreate', 'sasuke', hook1); + User.addHook('beforeCreate', 'naruto', hook2); + + await User.create({ username: 'makunouchi' }); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + User.removeHook('beforeCreate', 'sasuke'); + await User.create({ username: 'sendo' }); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledTwice; + }); + + it('should be able to remove by reference', async () => { + const User = sequelize.define('User'); + + const hook1 = sinon.spy(); + const hook2 = sinon.spy(); + + User.addHook('beforeCreate', hook1); + User.addHook('beforeCreate', hook2); + + await User.create({ username: 'makunouchi' }); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + User.removeHook('beforeCreate', hook1); + await User.create({ username: 'sendo' }); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledTwice; + }); + }); +}); diff --git a/packages/core/test/unit/import-models/import-models.test.ts b/packages/core/test/unit/import-models/import-models.test.ts new file mode 100644 index 000000000000..97f7f5a3addb --- /dev/null +++ b/packages/core/test/unit/import-models/import-models.test.ts @@ -0,0 +1,58 @@ +import type { ModelStatic } from '@sequelize/core'; +import { importModels } from '@sequelize/core'; +import { expect } from 'chai'; +import glob from 'fast-glob'; +// @ts-expect-error -- commonjs file +import { Bundler } from './models/bundler'; +// @ts-expect-error -- commonjs file +import Node from './models/node.abstract'; +// @ts-expect-error -- commonjs file +import User from './models/user'; + +describe('importModels', () => { + const dirname = glob.convertPathToPattern(__dirname); + + it('can import models using a single glob path', async () => { + const models = await importModels(`${dirname}/models/*.{ts,js}`); + + expect(models).to.have.length(3); + expect(models[0]).to.eq(Bundler); + expect(models[1]).to.eq(Node); + expect(models[2]).to.eq(User); + }); + + it('can import models using multiple glob paths', async () => { + const models = await importModels([ + `${dirname}/models/bundler.js`, + `${dirname}/models/node.abstract.js`, + `${dirname}/models/user.js`, + ]); + + expect(models).to.have.length(3); + expect(models[0]).to.eq(Bundler); + expect(models[1]).to.eq(Node); + expect(models[2]).to.eq(User); + }); + + it('can exclude results using the second parameter', async () => { + const calls: Array<{ path: string; exportName: string; exportValue: ModelStatic }> = []; + + const models = await importModels( + [`${dirname}/models/*.{ts,js}`], + (path: string, exportName: string, exportValue: ModelStatic) => { + calls.push({ path, exportName, exportValue }); + + return false; + }, + ); + + expect(models.length).to.eq(0); + + expect(calls[0].exportValue).to.eq(Bundler); + expect(calls[1].path.endsWith('test/unit/import-models/models/node.abstract.js')); + expect(calls[1].exportName).to.eq('default'); + expect(calls[1].exportValue).to.eq(Node); + + expect(calls[2].exportValue).to.eq(User); + }); +}); diff --git a/packages/core/test/unit/import-models/models/bundler.js b/packages/core/test/unit/import-models/models/bundler.js new file mode 100644 index 000000000000..398918261b1a --- /dev/null +++ b/packages/core/test/unit/import-models/models/bundler.js @@ -0,0 +1,45 @@ +/** + * + * Simulate the behavior of bundler (esbuild, rollup ...) converting `ECMAScript` to `CommonJS`. + * Source code + * +import { Model } from "@sequelize/core"; + +export class Bundler extends Model {}; + */ +const __defProp = Object.defineProperty; +const __getOwnPropDesc = Object.getOwnPropertyDescriptor; +const __getOwnPropNames = Object.getOwnPropertyNames; +const __hasOwnProp = Object.prototype.hasOwnProperty; +const __export = (target, all) => { + for (const name in all) { + __defProp(target, name, { get: all[name], enumerable: true }); + } +}; + +const __copyProps = (to, from, except, desc) => { + if ((from && typeof from === 'object') || typeof from === 'function') { + for (const key of __getOwnPropNames(from)) { + if (!__hasOwnProp.call(to, key) && key !== except) { + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + } + } + } + + return to; +}; + +const __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod); + +// src/bundler.ts +const bundler_exports = {}; +__export(bundler_exports, { + Bundler: () => Bundler, +}); +module.exports = __toCommonJS(bundler_exports); +const import_core = require('@sequelize/core'); + +const Bundler = class extends import_core.Model {}; diff --git a/packages/core/test/unit/import-models/models/node.abstract.js b/packages/core/test/unit/import-models/models/node.abstract.js new file mode 100644 index 000000000000..3074b00a6a07 --- /dev/null +++ b/packages/core/test/unit/import-models/models/node.abstract.js @@ -0,0 +1,3 @@ +const { Model } = require('@sequelize/core'); + +module.exports = class Node extends Model {}; diff --git a/packages/core/test/unit/import-models/models/user.js b/packages/core/test/unit/import-models/models/user.js new file mode 100644 index 000000000000..49fd59c839ba --- /dev/null +++ b/packages/core/test/unit/import-models/models/user.js @@ -0,0 +1,3 @@ +const Node = require('./node.abstract'); + +module.exports = class User extends Node {}; diff --git a/packages/core/test/unit/increment.test.js b/packages/core/test/unit/increment.test.js new file mode 100644 index 000000000000..07bba6f53685 --- /dev/null +++ b/packages/core/test/unit/increment.test.js @@ -0,0 +1,32 @@ +'use strict'; + +const { expect } = require('chai'); +const { DataTypes } = require('@sequelize/core'); +const { beforeAll2, sequelize } = require('../support'); + +describe('Model.increment', () => { + const vars = beforeAll2(() => { + const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + count: DataTypes.INTEGER, + }); + + return { User }; + }); + + it('should reject if options are missing', async () => { + await expect(vars.User.increment(['id', 'count'])).to.be.rejectedWith( + 'Missing where attribute in the options parameter', + ); + }); + + it('should reject if options.where are missing', async () => { + await expect(vars.User.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/packages/core/test/unit/instance-validator.test.js similarity index 83% rename from test/unit/instance-validator.test.js rename to packages/core/test/unit/instance-validator.test.js index 590d3ef7dc10..a31a6d42ca1b 100644 --- a/test/unit/instance-validator.test.js +++ b/packages/core/test/unit/instance-validator.test.js @@ -1,25 +1,28 @@ 'use strict'; const chai = require('chai'); + const expect = chai.expect; -const Support = require('./support'); -const InstanceValidator = require('../../lib/instance-validator'); +const Support = require('../support'); +const { + InstanceValidator, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/instance-validator.js'); const sinon = require('sinon'); -const SequelizeValidationError = require('../../lib/errors').ValidationError; +const { DataTypes, ValidationError: SequelizeValidationError } = require('@sequelize/core'); describe(Support.getTestDialectTeaser('InstanceValidator'), () => { - beforeEach(function() { + beforeEach(function () { this.User = Support.sequelize.define('user', { fails: { - type: Support.Sequelize.BOOLEAN, + type: DataTypes.BOOLEAN, validate: { isNotTrue(value) { if (value) { - throw Error('Manual model validation failure'); + throw new Error('Manual model validation failure'); } - } - } - } + }, + }, + }, }); }); @@ -29,7 +32,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); describe('validate', () => { - it('runs the validation sequence and hooks when the hooks option is true', function() { + it('runs the validation sequence and hooks when the hooks option is true', function () { const instanceValidator = new InstanceValidator(this.User.build(), { hooks: true }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -40,7 +43,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(_validate).to.not.have.been.called; }); - it('runs the validation sequence but skips hooks if the hooks option is false', function() { + it('runs the validation sequence but skips hooks if the hooks option is false', function () { const instanceValidator = new InstanceValidator(this.User.build(), { hooks: false }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -51,14 +54,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(_validateAndRunHooks).to.not.have.been.called; }); - it('fulfills when validation is successful', async function() { + it('fulfills when validation is successful', async function () { const instanceValidator = new InstanceValidator(this.User.build()); const result = instanceValidator.validate(); await expect(result).to.be.fulfilled; }); - it('rejects with a validation error when validation fails', async 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(); @@ -68,25 +71,28 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { it('has a useful default error message for not null validation failures', async () => { const User = Support.sequelize.define('user', { name: { - type: Support.Sequelize.STRING, - allowNull: false - } + type: DataTypes.STRING, + allowNull: false, + }, }); const instanceValidator = new InstanceValidator(User.build()); const result = instanceValidator.validate(); - await expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); + await expect(result).to.be.rejectedWith( + SequelizeValidationError, + /user\.name cannot be null/, + ); }); }); describe('_validateAndRunHooks', () => { - beforeEach(function() { + beforeEach(function () { this.successfulInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(this.successfulInstanceValidator, '_validate').resolves(); }); - it('should run beforeValidate and afterValidate hooks when _validate is successful', async 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); @@ -97,7 +103,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(afterValidate).to.have.been.calledOnce; }); - it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', async 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(); @@ -110,16 +116,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(afterValidate).to.not.have.been.called; }); - it('should emit an error from after hook when afterValidate fails', async function() { + it('should emit an error from after hook when afterValidate fails', async function () { this.User.afterValidate(() => { throw new Error('after validation error'); }); - await 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', async 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(); @@ -129,7 +137,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(validationFailedHook).to.have.been.calledOnce; }); - it('should not replace the validation error in validationFailed hook by default', async 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(); @@ -139,7 +147,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(err.name).to.equal('SequelizeValidationError'); }); - it('should replace the validation error if validationFailed hook creates a new error', async 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')); diff --git a/packages/core/test/unit/instance/build.test.js b/packages/core/test/unit/instance/build.test.js new file mode 100644 index 000000000000..d66539f150b3 --- /dev/null +++ b/packages/core/test/unit/instance/build.test.js @@ -0,0 +1,115 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes, sql } = require('@sequelize/core'); + +const current = Support.sequelize; +const dialect = current.dialect; + +describe(Support.getTestDialectTeaser('Instance'), () => { + describe('build', () => { + it('should populate NOW default values', async () => { + const Model = current.define( + 'Model', + { + created_time: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: DataTypes.NOW, + }, + updated_time: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: DataTypes.NOW, + }, + ip: { + type: DataTypes.STRING, + validate: { + isIP: true, + }, + }, + ip2: { + type: DataTypes.STRING, + validate: { + isIP: { + msg: 'test', + }, + }, + }, + }, + { + timestamp: false, + }, + ); + const instance = Model.build({ ip: '127.0.0.1', ip2: '0.0.0.0' }); + + expect(instance.get('created_time')).to.be.an.instanceof( + Date, + 'created_time should be a date', + ); + expect(instance.get('updated_time')).to.be.an.instanceof( + Date, + 'updated_time should be a date', + ); + + await instance.validate(); + }); + + it('should populate explicitly undefined UUID primary keys', () => { + const Model = current.define('Model', { + id: { + type: DataTypes.UUID, + primaryKey: true, + allowNull: false, + defaultValue: sql.uuidV4, + }, + }); + const instance = Model.build({ + id: undefined, + }); + + expect(instance.get('id')).not.to.be.undefined; + expect(instance.get('id')).to.be.ok; + }); + + it('should populate undefined columns with default value', () => { + const Model = current.define('Model', { + number1: { + type: DataTypes.INTEGER, + defaultValue: 1, + }, + number2: { + type: DataTypes.INTEGER, + defaultValue: 2, + }, + }); + const instance = Model.build({ + number1: undefined, + }); + + expect(instance.get('number1')).not.to.be.undefined; + expect(instance.get('number1')).to.equal(1); + expect(instance.get('number2')).not.to.be.undefined; + expect(instance.get('number2')).to.equal(2); + }); + + if (dialect.supports.dataTypes.JSONB) { + it('should clone the default values', () => { + const Model = current.define('Model', { + data: { + type: DataTypes.JSONB, + defaultValue: { foo: 'bar' }, + }, + }); + const instance = Model.build(); + instance.data.foo = 'biz'; + + expect(instance.get('data')).to.eql({ foo: 'biz' }); + expect(Model.build().get('data')).to.eql({ foo: 'bar' }); + }); + } + }); +}); diff --git a/packages/core/test/unit/instance/changed.test.ts b/packages/core/test/unit/instance/changed.test.ts new file mode 100644 index 000000000000..e50a9f2df988 --- /dev/null +++ b/packages/core/test/unit/instance/changed.test.ts @@ -0,0 +1,221 @@ +import type { InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { expect } from 'chai'; +import { beforeAll2, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('Model#changed()', () => { + describe('Non-JSON attribute', () => { + const vars = beforeAll2(() => { + class User extends Model, InferCreationAttributes> { + declare name: string | null; + declare birthday: Date | null; + } + + User.init( + { + name: DataTypes.STRING, + birthday: DataTypes.DATE, + }, + { sequelize }, + ); + + return { User }; + }); + + it('returns true when an non-fetched value is changed', () => { + const user = vars.User.build( + { + name: 'a', + }, + { + isNewRecord: false, + raw: true, + }, + ); + + expect(user.changed('name')).to.equal(false, 'name not be considered changed'); + user.set('name', 'b'); + expect(user.changed('name')).to.equal(true, 'name should be considered changed'); + }); + + it('returns false when setting an existing value to the same primitive value', () => { + const user = vars.User.build( + { + name: 'a', + birthday: null, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + user.set('name', 'a'); + user.set('birthday', null); + expect(user.changed('name')).to.equal(false); + expect(user.changed('birthday')).to.equal(false); + }); + + it('returns false when setting a value to the same object value', () => { + const milliseconds = 1_436_921_941_088; + const firstDate = new Date(milliseconds); + const secondDate = new Date(milliseconds); + + const user = vars.User.build( + { + birthday: firstDate, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + user.set('birthday', secondDate); + expect(user.changed('birthday')).to.equal(false); + }); + + it('should return true when a value is modified by setDataValue', () => { + const user = vars.User.build( + { + name: 'a', + }, + { + isNewRecord: false, + raw: true, + }, + ); + + user.setDataValue('name', 'b'); + expect(user.changed('name')).to.equal(true); + }); + + it('should return true when a value is modified by direct assignations', () => { + const user = vars.User.build( + { + name: 'a', + }, + { + isNewRecord: false, + raw: true, + }, + ); + + user.name = 'b'; + + expect(user.changed('name')).to.equal(true); + }); + }); + + if (dialect.supports.dataTypes.JSON) { + describe('JSON attribute', () => { + const vars = beforeAll2(() => { + class User extends Model, InferCreationAttributes> { + declare json: unknown; + } + + User.init( + { + json: DataTypes.JSON, + }, + { sequelize }, + ); + + return { User }; + }); + + it('returns false when setting a value to the same object value', () => { + for (const value of [null, 1, 'asdf', new Date(), [], {}, Buffer.from('')]) { + const t = vars.User.build( + { + json: value, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + t.json = value; + + expect(t.changed('json')).to.be.false; + expect(t.changed()).to.be.false; + } + }); + + it('returns true when setting a value to a different primitive value with Model#set & json.path notation', () => { + const user = vars.User.build( + { + json: { + city: 'Stockholm', + }, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + // @ts-expect-error -- TODO: fix Model#set typings to support this syntax + user.set('json.city', 'Gothenburg'); + expect(user.changed('json')).to.equal(true); + }); + + it('returns true when setting a value to the same primitive value with Model#set & json.path notation', () => { + const user = vars.User.build( + { + json: { + city: 'Gothenburg', + }, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + // @ts-expect-error -- TODO: fix Model#set typings to support this syntax + user.set('json.city', 'Gothenburg'); + expect(user.changed('json')).to.equal(false); + }); + + it('returns true when setting a value to a different object value with set & json.path notation', () => { + const user = vars.User.build( + { + json: { + address: { street: 'Main street', number: '40' }, + }, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + // @ts-expect-error -- TODO: fix Model#set typings to support this syntax + user.set('json.address', { street: 'Second street', number: '1' }); + expect(user.changed('json')).to.equal(true); + }); + + it('returns false when setting a value to the same object value with set & json.path notation', () => { + const user = vars.User.build( + { + json: { + address: { street: 'Main street', number: '40' }, + }, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + // @ts-expect-error -- TODO: fix Model#set typings to support this syntax + user.set('json.address', { street: 'Main street', number: '40' }); + expect(user.changed('json')).to.equal(false); + }); + }); + } +}); diff --git a/packages/core/test/unit/instance/decrement.test.js b/packages/core/test/unit/instance/decrement.test.js new file mode 100644 index 000000000000..c589db9f424b --- /dev/null +++ b/packages/core/test/unit/instance/decrement.test.js @@ -0,0 +1,44 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#decrement', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}); + const instance = User.build({}); + + await expect(instance.decrement()).to.be.rejectedWith( + 'but this model instance is missing the value of its primary key', + ); + }); + + describe('options tests', () => { + let stub; + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves({ + _previousDataValues: { id: 3 }, + dataValues: { id: 1 }, + }); + }); + + after(() => { + stub.restore(); + }); + + it('should allow decrements even if options are not given', async () => { + const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }); + + const instance = User.build({ id: 3 }, { isNewRecord: false }); + await expect(instance.decrement(['id'])).to.be.fulfilled; + }); + }); +}); diff --git a/packages/core/test/unit/instance/destroy.test.js b/packages/core/test/unit/instance/destroy.test.js new file mode 100644 index 000000000000..646e1bcba455 --- /dev/null +++ b/packages/core/test/unit/instance/destroy.test.js @@ -0,0 +1,47 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#destroy', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}); + const instance = User.build({}); + + await expect(instance.destroy()).to.be.rejectedWith( + 'but this model instance is missing the value of its primary key', + ); + }); + + describe('options tests', () => { + let stub; + + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves({ + _previousDataValues: {}, + dataValues: { id: 1 }, + }); + }); + + after(() => { + stub.restore(); + }); + + it('should allow destroys even if options are not given', () => { + const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }); + + const instance = User.build({ id: 1 }, { isNewRecord: false }); + expect(() => { + instance.destroy(); + }).to.not.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/instance/get.test.js b/packages/core/test/unit/instance/get.test.js new file mode 100644 index 000000000000..c80556974213 --- /dev/null +++ b/packages/core/test/unit/instance/get.test.js @@ -0,0 +1,36 @@ +'use strict'; + +const chai = require('chai'); +const sinon = require('sinon'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +const current = Support.sequelize; + +describe(Support.getTestDialectTeaser('Instance'), () => { + describe('get', () => { + beforeEach(function () { + this.getSpy = sinon.spy(); + this.User = current.define('User', { + name: { + type: DataTypes.STRING, + get: this.getSpy, + }, + }); + }); + + it('invokes getter if raw: false', function () { + this.User.build().get('name'); + + expect(this.getSpy).to.have.been.called; + }); + + it('does not invoke getter if raw: true', function () { + this.User.build().get('name', { raw: true }); + + expect(this.getSpy).not.to.have.been.called; + }); + }); +}); diff --git a/packages/core/test/unit/instance/increment.test.js b/packages/core/test/unit/instance/increment.test.js new file mode 100644 index 000000000000..f5b9b11d959a --- /dev/null +++ b/packages/core/test/unit/instance/increment.test.js @@ -0,0 +1,47 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#increment', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}); + const instance = User.build({}); + + await expect(instance.increment()).to.be.rejectedWith( + 'but this model instance is missing the value of its primary key', + ); + }); + + describe('options tests', () => { + let stub; + + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves({ + _previousDataValues: { id: 1 }, + dataValues: { id: 3 }, + }); + }); + + after(() => { + stub.restore(); + }); + + it('should allow increments even if options are not given', () => { + const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }); + + const instance = User.build({ id: 1 }, { isNewRecord: false }); + expect(() => { + instance.increment(['id']); + }).to.not.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/instance/is-soft-deleted.test.js b/packages/core/test/unit/instance/is-soft-deleted.test.js new file mode 100644 index 000000000000..348a810f2cca --- /dev/null +++ b/packages/core/test/unit/instance/is-soft-deleted.test.js @@ -0,0 +1,83 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const { DataTypes } = require('@sequelize/core'); + +const dayjs = require('dayjs'); + +describe(Support.getTestDialectTeaser('Instance'), () => { + describe('isSoftDeleted', () => { + beforeEach(function () { + const User = current.define('User', { + name: DataTypes.STRING, + birthdate: DataTypes.DATE, + meta: DataTypes.TEXT, + deletedAt: { + type: DataTypes.DATE, + }, + }); + + const ParanoidUser = current.define( + 'User', + { + name: DataTypes.STRING, + birthdate: DataTypes.DATE, + meta: DataTypes.TEXT, + deletedAt: { + type: DataTypes.DATE, + }, + }, + { + paranoid: true, + }, + ); + + this.paranoidUser = ParanoidUser.build( + { + name: 'a', + }, + { + isNewRecord: false, + raw: true, + }, + ); + + this.user = User.build( + { + name: 'a', + }, + { + isNewRecord: false, + raw: true, + }, + ); + }); + + it('should not throw if paranoid is set to true', function () { + expect(() => { + this.paranoidUser.isSoftDeleted(); + }).to.not.throw(); + }); + + it('should throw if paranoid is set to false', function () { + expect(() => { + this.user.isSoftDeleted(); + }).to.throw('Model is not paranoid'); + }); + + it('should return false if the soft-delete property is the same as the default value', function () { + this.paranoidUser.setDataValue('deletedAt', null); + expect(this.paranoidUser.isSoftDeleted()).to.be.false; + }); + + it('should return true if the soft-delete property is set', function () { + this.paranoidUser.setDataValue('deletedAt', dayjs().subtract(5, 'days').format()); + expect(this.paranoidUser.isSoftDeleted()).to.be.true; + }); + }); +}); diff --git a/test/unit/instance/previous.test.js b/packages/core/test/unit/instance/previous.test.js similarity index 77% rename from test/unit/instance/previous.test.js rename to packages/core/test/unit/instance/previous.test.js index 6b468a53414b..5a7069ab63e6 100644 --- a/test/unit/instance/previous.test.js +++ b/packages/core/test/unit/instance/previous.test.js @@ -1,9 +1,11 @@ 'use strict'; const chai = require('chai'); + const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + const current = Support.sequelize; describe(Support.getTestDialectTeaser('Instance'), () => { @@ -18,8 +20,8 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, get() { this.getDataValue('textCustom'); - } - } + }, + }, }); const instance = Model.build({ text: 'a', textCustom: 'abc' }); @@ -29,8 +31,8 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('text', 'b'); instance.set('textCustom', 'def'); - expect(instance.previous('text')).to.be.equal('a'); - expect(instance.previous('textCustom')).to.be.equal('abc'); + expect(instance.previous('text')).to.equal('a'); + expect(instance.previous('textCustom')).to.equal('abc'); }); }); }); diff --git a/packages/core/test/unit/instance/reload.test.js b/packages/core/test/unit/instance/reload.test.js new file mode 100644 index 000000000000..75dcf6b1e9ee --- /dev/null +++ b/packages/core/test/unit/instance/reload.test.js @@ -0,0 +1,52 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#reload', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}); + const instance = User.build({}); + + await expect(instance.reload()).to.be.rejectedWith( + 'but this model instance is missing the value of its primary key', + ); + }); + + describe('options tests', () => { + let stub; + + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves({ + _previousDataValues: { id: 1 }, + dataValues: { id: 2 }, + }); + }); + + after(() => { + stub.restore(); + }); + + it('should allow reloads even if options are not given', async () => { + const User = sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + deletedAt: {}, + }, + { + paranoid: true, + }, + ); + + const instance = User.build({ id: 1 }, { isNewRecord: false }); + await expect(instance.reload()).to.be.fulfilled; + }); + }); +}); diff --git a/packages/core/test/unit/instance/restore.test.js b/packages/core/test/unit/instance/restore.test.js new file mode 100644 index 000000000000..44109e8f709e --- /dev/null +++ b/packages/core/test/unit/instance/restore.test.js @@ -0,0 +1,57 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#restore', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}, { paranoid: true }); + const instance = User.build({}, { isNewRecord: false }); + + await expect(instance.restore()).to.be.rejectedWith( + 'save an instance with no primary key, this is not allowed since it would', + ); + }); + + describe('options tests', () => { + let stub; + + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves([ + { + _previousDataValues: { id: 1 }, + dataValues: { id: 2 }, + }, + 1, + ]); + }); + + after(() => { + stub.restore(); + }); + + it('should allow restores even if options are not given', () => { + const User = sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + deletedAt: {}, + }, + { + paranoid: true, + }, + ); + + const instance = User.build({ id: 1 }, { isNewRecord: false }); + expect(() => { + instance.restore(); + }).to.not.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/instance/save.test.js b/packages/core/test/unit/instance/save.test.js new file mode 100644 index 000000000000..0307a8b8e5a4 --- /dev/null +++ b/packages/core/test/unit/instance/save.test.js @@ -0,0 +1,50 @@ +'use strict'; + +const { expect } = require('chai'); +const { sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +describe('Model#save', () => { + it('is not allowed if the instance does not have a primary key defined', async () => { + const User = sequelize.define('User', {}); + const instance = User.build({}, { isNewRecord: false }); + + await expect(instance.save()).to.be.rejectedWith( + 'You attempted to save an instance with no primary key, this is not allowed since it would result in a global update', + ); + }); + + describe('options tests', () => { + let stub; + + before(() => { + stub = sinon.stub(sequelize, 'queryRaw').resolves([ + { + _previousDataValues: {}, + dataValues: { id: 1 }, + }, + 1, + ]); + }); + + after(() => { + stub.restore(); + }); + + it('should allow saves even if options are not given', () => { + const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }); + + const instance = User.build({}); + expect(() => { + instance.save(); + }).to.not.throw(); + }); + }); +}); diff --git a/packages/core/test/unit/instance/set.test.js b/packages/core/test/unit/instance/set.test.js new file mode 100644 index 000000000000..eb5e7efa8007 --- /dev/null +++ b/packages/core/test/unit/instance/set.test.js @@ -0,0 +1,164 @@ +'use strict'; + +const { expect } = require('chai'); +const { beforeAll2, sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const sinon = require('sinon'); + +const dialect = sequelize.dialect; + +describe('Model#set', () => { + if (dialect.supports.dataTypes.JSONB) { + it('sets nested keys in JSON objects', () => { + const User = sequelize.define('User', { + meta: DataTypes.JSONB, + }); + const user = User.build( + { + meta: { + location: 'Stockhollm', + }, + }, + { + isNewRecord: false, + raw: true, + }, + ); + + const meta = user.get('meta'); + + user.set('meta.location', 'Copenhagen'); + expect(user.dataValues['meta.location']).not.to.be.ok; + expect(user.get('meta').location).to.equal('Copenhagen'); + expect(user.get('meta') === meta).to.equal(true); + expect(user.get('meta') === meta).to.equal(true); + }); + + it('doesnt mutate the JSONB defaultValue', () => { + const User = sequelize.define('User', { + meta: { + type: DataTypes.JSONB, + allowNull: false, + defaultValue: {}, + }, + }); + const user1 = User.build({}); + user1.set('meta.location', 'Stockhollm'); + const user2 = User.build({}); + expect(user2.get('meta')).to.deep.equal({}); + }); + } + + it('sets the date "1970-01-01" to previously null field', () => { + const User = sequelize.define('User', { + date: { + type: DataTypes.DATE, + allowNull: true, + }, + }); + const user1 = User.build({ + date: null, + }); + user1.set('date', '1970-01-01'); + expect(user1.get('date')).to.be.ok; + expect(user1.get('date').getTime()).to.equal(new Date('1970-01-01').getTime()); + }); + + it('overwrites non-date originalValue with date', () => { + const User = sequelize.define('User', { + date: DataTypes.DATE, + }); + const user = User.build( + { + date: ' ', + }, + { + isNewRecord: false, + raw: true, + }, + ); + + user.set('date', new Date()); + expect(user.get('date')).to.be.an.instanceof(Date); + expect(user.get('date')).not.to.be.NaN; + }); + + describe('custom setter', () => { + const vars = beforeAll2(() => { + const stubCreate = sinon + .stub(sequelize.queryInterface, 'insert') + .callsFake(async instance => [instance, 1]); + const stubGetNextPrimaryKeyValue = sinon + .stub(sequelize.queryInterface, 'getNextPrimaryKeyValue') + .callsFake(async () => undefined); + const User = sequelize.define('User', { + phoneNumber: { + type: DataTypes.STRING, + set(val) { + if (typeof val === 'object' && val !== null) { + val = `00${val.country}${val.area}${val.local}`; + } + + if (typeof val === 'string') { + // Canonicalize phone number + val = val.replace(/^\+/, '00').replaceAll(/\(0\)|[\s()+./-]/g, ''); + } + + this.setDataValue('phoneNumber', val); + }, + }, + }); + + return { stubCreate, stubGetNextPrimaryKeyValue, User }; + }); + + after(() => { + vars.stubCreate.restore(); + vars.stubGetNextPrimaryKeyValue.restore(); + }); + + it('does not set field to changed if field is set to the same value with custom setter using primitive value', async () => { + const user = vars.User.build({ + phoneNumber: '+1 234 567', + }); + 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; + }); + + it('sets field to changed if field is set to the another value with custom setter using primitive value', async () => { + const user = vars.User.build({ + phoneNumber: '+1 234 567', + }); + 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; + }); + + it('does not set field to changed if field is set to the same value with custom setter using object', async () => { + const user = vars.User.build({ + phoneNumber: '+1 234 567', + }); + 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; + }); + + it('sets field to changed if field is set to the another value with custom setter using object', async () => { + const user = vars.User.build({ + phoneNumber: '+1 234 567', + }); + 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; + }); + }); +}); diff --git a/packages/core/test/unit/instance/to-json.test.ts b/packages/core/test/unit/instance/to-json.test.ts new file mode 100644 index 000000000000..5d4ac1d616ad --- /dev/null +++ b/packages/core/test/unit/instance/to-json.test.ts @@ -0,0 +1,59 @@ +import type { InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('Model#toJSON', () => { + if (!dialect.supports.dataTypes.JSON) { + return; + } + + it('returns copy of json', () => { + const User = sequelize.define('User', { + name: DataTypes.STRING, + }); + const user = User.build({ name: 'my-name' }); + const json1 = user.toJSON(); + + expect(json1).to.deep.equal({ id: null, name: 'my-name' }); + + // remove value from json and ensure it's not changed in the instance + delete json1.name; + + const json2 = user.toJSON(); + expect(json2).to.have.property('name').and.be.equal('my-name'); + }); + + it('returns clone of JSON data-types', () => { + class User extends Model, InferCreationAttributes> { + declare name: string; + declare permissions: { + admin: boolean; + special: string; + }; + } + + User.init( + { + name: DataTypes.STRING, + permissions: DataTypes.JSON, + }, + { sequelize }, + ); + + const user = User.build({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); + const json = user.toJSON(); + + expect(json).to.deep.equal({ + id: null, + name: 'my-name', + permissions: { admin: true, special: 'foobar' }, + }); + + expect(json.permissions).not.to.equal(user.permissions); + json.permissions.admin = false; + expect(user.permissions.admin).to.equal(true); + }); +}); diff --git a/packages/core/test/unit/instance/update.test.ts b/packages/core/test/unit/instance/update.test.ts new file mode 100644 index 000000000000..7e6640769d44 --- /dev/null +++ b/packages/core/test/unit/instance/update.test.ts @@ -0,0 +1,27 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('Model#update', () => { + it('is not allowed if the primary key is not defined', async () => { + const User = sequelize.define('User', { + name: { type: DataTypes.STRING }, + }); + const instance = User.build({}, { isNewRecord: false }); + + await expect(instance.update({ name: 'john' })).to.be.rejectedWith( + 'You attempted to save an instance with no primary key, this is not allowed since', + ); + }); + + it('is not allowed if the primary key is not defined and is a newly created record', async () => { + const User = sequelize.define('User', { + name: { type: DataTypes.STRING }, + }); + const instance = User.build({}, { isNewRecord: true }); + + await expect(instance.update({ name: 'john' })).to.be.rejectedWith( + 'You attempted to update an instance that is not persisted.', + ); + }); +}); diff --git a/packages/core/test/unit/logger.test.ts b/packages/core/test/unit/logger.test.ts new file mode 100644 index 000000000000..190f0f7a6c1b --- /dev/null +++ b/packages/core/test/unit/logger.test.ts @@ -0,0 +1,65 @@ +import { + Logger, + logger as defaultLogger, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { expect } from 'chai'; +import { inspect as nodeInspect } from 'node:util'; +import sinon from 'sinon'; + +describe('logger', () => { + let oldWarn: typeof console.warn; + let fakeWarn: sinon.SinonSpy; + + beforeEach(() => { + oldWarn = console.warn; + fakeWarn = sinon.fake(); + console.warn = fakeWarn; + }); + + afterEach(() => { + console.warn = oldWarn; + }); + + it('creates a default logger in the sequelize context', () => { + defaultLogger.warn('abc'); + + expect(fakeWarn.calledOnceWithExactly('(sequelize) Warning: abc')).to.equal(true); + }); + + it("defaults the context of new loggers to 'sequelize'", () => { + const logger = new Logger(); + + logger.warn('oh no'); + expect(fakeWarn.calledOnceWithExactly('(sequelize) Warning: oh no')).to.equal(true); + }); + + it('respects specified context in new loggers', () => { + const logger = new Logger({ context: 'query-generator' }); + + logger.warn('This feature is not supported for this dialect.'); + + expect( + fakeWarn.calledOnceWithExactly( + '(query-generator) Warning: This feature is not supported for this dialect.', + ), + ).to.equal(true); + }); + + it('inspects a value', () => { + const obj = { + a: 1, + b: 2, + c() { + /* no-op */ + }, + }; + + expect(defaultLogger.inspect(obj)).to.equal(nodeInspect(obj, { showHidden: false, depth: 3 })); + }); + + it('creates a debugger in the correct namespace', () => { + const contextDebugger = defaultLogger.debugContext('query-generator'); + + expect(contextDebugger.namespace).to.equal('sequelize:query-generator'); + }); +}); diff --git a/packages/core/test/unit/model-repository/bulk-destroy.test.ts b/packages/core/test/unit/model-repository/bulk-destroy.test.ts new file mode 100644 index 000000000000..fb8d866d18b4 --- /dev/null +++ b/packages/core/test/unit/model-repository/bulk-destroy.test.ts @@ -0,0 +1,91 @@ +import type { InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { Model } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('ModelRepository#_UNSTABLE_bulkDestroy', () => { + const vars = beforeAll2(() => { + class User extends Model, InferCreationAttributes> { + declare id: number; + } + + sequelize.addModels([User]); + + return { User }; + }); + + afterEach(() => { + vars.User.hooks.removeAllListeners(); + sinon.restore(); + }); + + it('throw an error if the "where" option is not specified', async () => { + const { User } = vars; + const repository = User.modelRepository; + + // @ts-expect-error -- testing that not specifying "where" leads to an error + await expect(repository._UNSTABLE_bulkDestroy({})).to.be.rejectedWith( + 'requires explicitly specifying a "where"', + ); + }); + + it('creates a single DELETE query', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { User } = vars; + const repository = User.modelRepository; + + await repository._UNSTABLE_bulkDestroy({ where: { id: 5 } }); + + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [Users] WHERE [id] = 5`, + mssql: `DELETE FROM [Users] WHERE [id] = 5; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('supports _UNSTABLE_beforeBulkDestroy/_UNSTABLE_afterBulkDestroy hooks', async () => { + sinon.stub(sequelize, 'queryRaw'); + + const { User } = vars; + const repository = User.modelRepository; + + const beforeDestroySpy = sinon.spy(); + const afterDestroySpy = sinon.spy(); + + User.hooks.addListener('_UNSTABLE_beforeBulkDestroy', beforeDestroySpy); + User.hooks.addListener('_UNSTABLE_afterBulkDestroy', afterDestroySpy); + + await repository._UNSTABLE_bulkDestroy({ where: { id: 5 } }); + + expect(beforeDestroySpy.callCount).to.eq(1); + expect(beforeDestroySpy.getCall(0).args).to.deep.eq([ + { manualOnDelete: 'paranoid', where: { id: 5 } }, + ]); + + expect(afterDestroySpy.callCount).to.eq(1); + expect(afterDestroySpy.getCall(0).args).to.deep.eq([ + { manualOnDelete: 'paranoid', where: { id: 5 } }, + undefined, // returned from queryRaw stub + ]); + }); + + it('allows modifying the options in _UNSTABLE_beforeBulkDestroy', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { User } = vars; + const repository = User.modelRepository; + + User.hooks.addListener('_UNSTABLE_beforeBulkDestroy', options => { + options.where = { id: 6 }; + }); + + await repository._UNSTABLE_bulkDestroy({ where: { id: 5 } }); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [Users] WHERE [id] = 6`, + mssql: `DELETE FROM [Users] WHERE [id] = 6; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); +}); diff --git a/packages/core/test/unit/model-repository/destroy.test.ts b/packages/core/test/unit/model-repository/destroy.test.ts new file mode 100644 index 000000000000..16b96a4a8543 --- /dev/null +++ b/packages/core/test/unit/model-repository/destroy.test.ts @@ -0,0 +1,242 @@ +import type { InferAttributes, InferCreationAttributes } from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { Attribute, PrimaryKey, Table, Version } from '@sequelize/core/decorators-legacy'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('ModelRepository#destroy', () => { + const vars = beforeAll2(() => { + @Table({ + noPrimaryKey: true, + }) + class NoPk extends Model, InferCreationAttributes> {} + + class SimpleId extends Model, InferCreationAttributes> { + declare id: number; + } + + class CompositePk extends Model< + InferAttributes, + InferCreationAttributes + > { + @PrimaryKey + @Attribute(DataTypes.INTEGER) + declare id1: number; + + @PrimaryKey + @Attribute(DataTypes.INTEGER) + declare id2: number; + } + + class VersionedSimpleId extends Model< + InferAttributes, + InferCreationAttributes + > { + declare id: number; + + @Version + declare version: number; + } + + sequelize.addModels([SimpleId, CompositePk, VersionedSimpleId, NoPk]); + + return { SimpleId, CompositePk, VersionedSimpleId, NoPk }; + }); + + afterEach(() => { + sinon.restore(); + vars.SimpleId.hooks.removeAllListeners(); + }); + + it('throw an error if the model has no primary key', async () => { + const { NoPk } = vars; + const repository = NoPk.modelRepository; + + const instance = NoPk.build(); + + await expect(repository._UNSTABLE_destroy(instance)).to.be.rejectedWith( + 'does not have a primary key attribute definition', + ); + }); + + it(`throws an error if the model's PK is not loaded`, async () => { + const { SimpleId } = vars; + const repository = SimpleId.modelRepository; + + const instance = SimpleId.build(); + + await expect(repository._UNSTABLE_destroy(instance)).to.be.rejectedWith( + 'missing the value of its primary key', + ); + }); + + it('creates an optimized query for single-entity deletions', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { SimpleId } = vars; + const repository = SimpleId.modelRepository; + + const instance = SimpleId.build({ id: 1 }); + + await repository._UNSTABLE_destroy(instance); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [SimpleIds] WHERE [id] = 1`, + mssql: `DELETE FROM [SimpleIds] WHERE [id] = 1; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('creates an optimized query for non-composite PKs with no version', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { SimpleId } = vars; + const repository = SimpleId.modelRepository; + + const instance1 = SimpleId.build({ id: 1 }); + const instance2 = SimpleId.build({ id: 2 }); + + await repository._UNSTABLE_destroy([instance1, instance2]); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [SimpleIds] WHERE [id] IN (1, 2)`, + mssql: `DELETE FROM [SimpleIds] WHERE [id] IN (1, 2); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('creates a deoptimized query for composite PKs', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { CompositePk } = vars; + const repository = CompositePk.modelRepository; + + const instance1 = CompositePk.build({ id1: 1, id2: 2 }); + const instance2 = CompositePk.build({ id1: 3, id2: 4 }); + + await repository._UNSTABLE_destroy([instance1, instance2]); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [CompositePks] WHERE ([id1] = 1 AND [id2] = 2) OR ([id1] = 3 AND [id2] = 4)`, + mssql: `DELETE FROM [CompositePks] WHERE ([id1] = 1 AND [id2] = 2) OR ([id1] = 3 AND [id2] = 4); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('creates a deoptimized query if the model is versioned', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + + const { VersionedSimpleId } = vars; + const repository = VersionedSimpleId.modelRepository; + + const instance1 = VersionedSimpleId.build({ id: 1, version: 2 }); + const instance2 = VersionedSimpleId.build({ id: 3, version: 4 }); + + await repository._UNSTABLE_destroy([instance1, instance2]); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [VersionedSimpleIds] WHERE ([id] = 1 AND [version] = 2) OR ([id] = 3 AND [version] = 4)`, + mssql: `DELETE FROM [VersionedSimpleIds] WHERE ([id] = 1 AND [version] = 2) OR ([id] = 3 AND [version] = 4); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('supports beforeDestroyMany/afterDestroyMany hooks', async () => { + sinon.stub(sequelize, 'queryRaw'); + + const { SimpleId } = vars; + const repository = SimpleId.modelRepository; + + const instance1 = SimpleId.build({ id: 1 }); + const instance2 = SimpleId.build({ id: 2 }); + + const beforeDestroyManySpy = sinon.spy(); + const afterDestroyManySpy = sinon.spy(); + + SimpleId.hooks.addListener('beforeDestroyMany', beforeDestroyManySpy); + SimpleId.hooks.addListener('afterDestroyMany', afterDestroyManySpy); + + await repository._UNSTABLE_destroy([instance1, instance2]); + + expect(beforeDestroyManySpy.callCount).to.eq(1); + expect(beforeDestroyManySpy.getCall(0).args).to.deep.eq([ + [instance1, instance2], + { manualOnDelete: 'paranoid' }, + ]); + + expect(afterDestroyManySpy.callCount).to.eq(1); + expect(afterDestroyManySpy.getCall(0).args).to.deep.eq([ + [instance1, instance2], + { manualOnDelete: 'paranoid' }, + undefined, // returned from queryRaw stub + ]); + }); + + it('skips beforeDestroyMany/afterDestroyMany hooks if noHooks is passed', async () => { + sinon.stub(sequelize, 'queryRaw'); + + const { SimpleId } = vars; + const repository = SimpleId.modelRepository; + + const instance1 = SimpleId.build({ id: 1 }); + const instance2 = SimpleId.build({ id: 2 }); + + const beforeDestroyManySpy = sinon.spy(); + const afterDestroyManySpy = sinon.spy(); + + SimpleId.hooks.addListener('beforeDestroyMany', beforeDestroyManySpy); + SimpleId.hooks.addListener('afterDestroyMany', afterDestroyManySpy); + + await repository._UNSTABLE_destroy([instance1, instance2], { noHooks: true }); + + expect(beforeDestroyManySpy.callCount).to.eq(0); + expect(afterDestroyManySpy.callCount).to.eq(0); + }); + + it('allows modifying the list of instances in beforeDestroyMany', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + const { SimpleId } = vars; + + const repository = SimpleId.modelRepository; + + const instance1 = SimpleId.build({ id: 1 }); + const instance2 = SimpleId.build({ id: 2 }); + const instance3 = SimpleId.build({ id: 3 }); + + SimpleId.hooks.addListener('beforeDestroyMany', instances => { + instances.push(instance3); + }); + + await repository._UNSTABLE_destroy([instance1, instance2]); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [SimpleIds] WHERE [id] IN (1, 2, 3)`, + mssql: `DELETE FROM [SimpleIds] WHERE [id] IN (1, 2, 3); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('aborts if beforeDestroyMany removes all instances', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + const { SimpleId } = vars; + + const repository = SimpleId.modelRepository; + + const instance1 = SimpleId.build({ id: 1 }); + + SimpleId.hooks.addListener('beforeDestroyMany', instances => { + // remove all instances + instances.splice(0, instances.length); + }); + + await repository._UNSTABLE_destroy([instance1]); + + expect(stub.callCount).to.eq(0); + }); +}); diff --git a/packages/core/test/unit/model/bulk-create.test.js b/packages/core/test/unit/model/bulk-create.test.js new file mode 100644 index 000000000000..ef501141b120 --- /dev/null +++ b/packages/core/test/unit/model/bulk-create.test.js @@ -0,0 +1,70 @@ +'use strict'; + +const { expect } = require('chai'); +const sinon = require('sinon'); +const { beforeAll2, sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +describe('Model#bulkCreate', () => { + const vars = beforeAll2(() => { + const TestModel = sequelize.define( + 'TestModel', + { + accountId: { + type: DataTypes.INTEGER(11).UNSIGNED, + allowNull: false, + field: 'account_id', + }, + purchaseCount: { + type: DataTypes.INTEGER(11).UNSIGNED, + allowNull: false, + underscored: true, + }, + }, + { timestamps: false }, + ); + + const stub = sinon.stub(sequelize.queryInterface, 'bulkInsert').resolves([]); + + return { TestModel, stub }; + }); + + afterEach(() => { + vars.stub.resetHistory(); + }); + + after(() => { + vars.stub.restore(); + }); + + describe('validations', () => { + it('should not fail for renamed fields', async () => { + const { stub, TestModel } = vars; + + await TestModel.bulkCreate([{ accountId: 42, purchaseCount: 4 }], { + validate: true, + }); + + expect(stub.getCall(0).args[1]).to.deep.equal([ + { account_id: 42, purchaseCount: 4, id: null }, + ]); + }); + + if (sequelize.dialect.supports.inserts.updateOnDuplicate) { + it('should map conflictAttributes to column names', async () => { + const { stub, TestModel } = vars; + + // Note that the model also has an id key as its primary key. + await TestModel.bulkCreate([{ accountId: 42, purchaseCount: 3 }], { + conflictAttributes: ['accountId'], + updateOnDuplicate: ['purchaseCount'], + }); + + expect( + // Not worth checking that the reference of the array matches - just the contents. + stub.getCall(0).args[2].upsertKeys, + ).to.deep.equal(['account_id']); + }); + } + }); +}); diff --git a/packages/core/test/unit/model/count.test.js b/packages/core/test/unit/model/count.test.js new file mode 100644 index 000000000000..56f9361c1598 --- /dev/null +++ b/packages/core/test/unit/model/count.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes, Model } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('method count', () => { + before(function () { + this.oldFindAll = Model.findAll; + this.oldAggregate = Model.aggregate; + + Model.findAll = sinon.stub().resolves(); + + this.User = current.define('User', { + username: DataTypes.STRING, + age: DataTypes.INTEGER, + }); + this.Project = current.define('Project', { + name: DataTypes.STRING, + }); + + this.User.hasMany(this.Project); + this.Project.belongsTo(this.User); + }); + + after(function () { + Model.findAll = this.oldFindAll; + Model.aggregate = this.oldAggregate; + }); + + beforeEach(function () { + this.stub = Model.aggregate = sinon.stub().resolves(); + }); + + describe('should pass the same options to model.aggregate as findAndCountAll', () => { + it('with includes', async function () { + const queryObject = { + include: [this.Project], + }; + 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', async function () { + const queryObject = { + attributes: ['username'], + }; + 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/packages/core/test/unit/model/define.test.ts b/packages/core/test/unit/model/define.test.ts new file mode 100644 index 000000000000..86a08650bae4 --- /dev/null +++ b/packages/core/test/unit/model/define.test.ts @@ -0,0 +1,475 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, createSequelizeInstance, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +describe('Model', () => { + describe('define', () => { + it('should allow custom timestamps with underscored: true', () => { + const User = sequelize.define( + 'User', + {}, + { + createdAt: 'createdAt', + updatedAt: 'updatedAt', + timestamps: true, + underscored: true, + }, + ); + + expect(User.getAttributes()).to.haveOwnProperty('createdAt'); + expect(User.getAttributes()).to.haveOwnProperty('updatedAt'); + + expect(User.modelDefinition.timestampAttributeNames.createdAt).to.equal('createdAt'); + expect(User.modelDefinition.timestampAttributeNames.updatedAt).to.equal('updatedAt'); + + expect(User.getAttributes()).not.to.have.property('created_at'); + expect(User.getAttributes()).not.to.have.property('updated_at'); + }); + + it('should throw only when id is added but primaryKey is not set', () => { + expect(() => { + sequelize.define('foo', { + id: DataTypes.INTEGER, + }); + }).to.throw( + "An attribute called 'id' was defined in model 'foos' but primaryKey is not set. This is likely to be an error, which can be fixed by setting its 'primaryKey' option to true. If this is intended, explicitly set its 'primaryKey' option to false", + ); + }); + + it('allows creating an "id" field as the primary key', () => { + const Bar = sequelize.define('bar', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + expect(Bar.getAttributes()).to.have.property('id'); + expect(Bar.getAttributes().id.primaryKey).to.equal(true); + }); + + it('allows creating an "id" field explicitly marked as non primary key', () => { + const Baz = sequelize.define('baz', { + id: { + type: DataTypes.INTEGER, + primaryKey: false, + }, + }); + + expect(Baz.getAttributes()).to.have.property('id'); + expect(Baz.getAttributes().id.primaryKey).to.equal(false); + expect(Baz.primaryKeys).to.deep.eq({}); + }); + + it('should not add the default PK when noPrimaryKey is set to true', () => { + const User = sequelize.define( + 'User', + {}, + { + noPrimaryKey: true, + }, + ); + + expect(User.getAttributes()).not.to.have.property('id'); + }); + + it('should add the default `id` field PK if noPrimary is not set and no PK has been defined manually', () => { + const User = sequelize.define('User', {}); + + expect(User.getAttributes()).to.have.property('id'); + }); + + it('should not add the default `id` field PK if PK has been defined manually', () => { + const User = sequelize.define('User', { + customId: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + expect(User.getAttributes()).not.to.have.property('id'); + }); + + it('should support noPrimaryKey on Sequelize define option', () => { + const sequelizeNoPk = createSequelizeInstance({ + define: { + noPrimaryKey: true, + }, + }); + + const User = sequelizeNoPk.define('User', {}); + + expect(User.options.noPrimaryKey).to.equal(true); + }); + + it('supports marking an attribute as unique', () => { + const User = sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + unique: true, + }, + }); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: ['firstName'], + column: 'firstName', + unique: true, + name: 'users_first_name_unique', + }, + ]); + }); + + it('supports marking multiple attributes as composite unique', () => { + const User = sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + unique: 'firstName-lastName', + }, + lastName: { + type: DataTypes.STRING, + unique: 'firstName-lastName', + }, + }); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: ['firstName', 'lastName'], + column: 'firstName', + unique: true, + name: 'firstName-lastName', + }, + ]); + }); + + it('supports using the same attribute in multiple uniques', () => { + const User = sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + unique: [true, 'firstName-lastName', 'firstName-country'], + }, + lastName: { + type: DataTypes.STRING, + unique: 'firstName-lastName', + }, + country: { + type: DataTypes.STRING, + unique: 'firstName-country', + }, + }); + + expect(User.getIndexes()).to.deep.equal([ + { + fields: ['firstName'], + column: 'firstName', + unique: true, + name: 'users_first_name_unique', + }, + { + fields: ['firstName', 'lastName'], + column: 'firstName', + unique: true, + name: 'firstName-lastName', + }, + { + fields: ['firstName', 'country'], + column: 'firstName', + unique: true, + name: 'firstName-country', + }, + ]); + }); + + it('should throw when the attribute name is ambiguous with $nested.attribute$ syntax', () => { + expect(() => { + sequelize.define('foo', { + $id: DataTypes.INTEGER, + }); + }).to.throw( + 'Name of attribute "$id" in model "foo" cannot start or end with "$" as "$attribute$" is reserved syntax used to reference nested columns in queries.', + ); + + expect(() => { + sequelize.define('foo', { + id$: DataTypes.INTEGER, + }); + }).to.throw( + 'Name of attribute "id$" in model "foo" cannot start or end with "$" as "$attribute$" is reserved syntax used to reference nested columns in queries.', + ); + }); + + it('should throw when the attribute name is ambiguous with json.path syntax', () => { + expect(() => { + sequelize.define('foo', { + 'my.attribute': DataTypes.INTEGER, + }); + }).to.throw( + 'Name of attribute "my.attribute" in model "foo" cannot include the character "." as it would be ambiguous with the syntax used to reference nested columns, and nested json keys, in queries.', + ); + }); + + it('should throw when the attribute name is ambiguous with casting syntax', () => { + expect(() => { + sequelize.define('foo', { + 'id::int': DataTypes.INTEGER, + }); + }).to.throw( + 'Name of attribute "id::int" in model "foo" cannot include the character sequence "::" as it is reserved syntax used to cast attributes in queries.', + ); + }); + + it('should throw when the attribute name is ambiguous with nested-association syntax', () => { + expect(() => { + sequelize.define('foo', { + 'my->attribute': DataTypes.INTEGER, + }); + }).to.throw( + 'Name of attribute "my->attribute" in model "foo" cannot include the character sequence "->" as it is reserved syntax used in SQL generated by Sequelize to target nested associations.', + ); + }); + + it('should defend against null or undefined "unique" attributes', () => { + expect(() => { + sequelize.define('baz', { + foo: { + type: DataTypes.STRING, + // @ts-expect-error -- we're testing that it defends against this + unique: null, + }, + bar: { + type: DataTypes.STRING, + unique: undefined, + }, + bop: { + type: DataTypes.DATE, + }, + }); + }).not.to.throw(); + }); + + it('should throw for unknown data type', () => { + expect(() => { + sequelize.define('bar', { + name: { + // @ts-expect-error -- we're testing that this throws + type: DataTypes.MY_UNKNOWN_TYPE, + }, + }); + }).to.throw('Attribute "bar.name" does not specify its DataType.'); + }); + + it('should throw for notNull validator without allowNull', () => { + expect(() => { + sequelize.define('user', { + name: { + type: DataTypes.STRING, + allowNull: true, + validate: { + notNull: { + msg: 'Please enter the name', + }, + }, + }, + }); + }).to.throwWithCause(`"notNull" validator is only allowed with "allowNull:false"`); + + expect(() => { + sequelize.define('part', { + name: { + type: DataTypes.STRING, + validate: { + notNull: { + msg: 'Please enter the part name', + }, + }, + }, + }); + }).to.throwWithCause(`"notNull" validator is only allowed with "allowNull:false"`); + }); + + it('throws an error if 2 autoIncrements are passed', () => { + expect(() => { + sequelize.define('UserWithTwoAutoIncrements', { + userid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + userscore: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + }); + }).to.throwWithCause( + `Only one autoIncrement attribute is allowed per model, but both 'userscore' and 'userid' are marked as autoIncrement.`, + ); + }); + + it('should set the schema to the global value unless another value is provided', () => { + const sequelizeSchema = createSequelizeInstance({ + define: { + schema: 'mySchema', + }, + }); + + const Model1 = sequelizeSchema.define('Model1'); + expect(Model1.modelDefinition.table.schema).to.equal('mySchema'); + + const Model2 = sequelizeSchema.define('Model2', {}, { schema: undefined }); + expect(Model2.modelDefinition.table.schema).to.equal('mySchema'); + + const Model3 = sequelizeSchema.define('Model3', {}, { schema: 'other_schema' }); + expect(Model3.modelDefinition.table.schema).to.equal('other_schema'); + }); + + describe('datatype warnings', () => { + beforeEach(() => { + sinon.spy(console, 'warn'); + }); + + afterEach(() => { + // @ts-expect-error -- only used in testing + console.warn.restore(); + }); + + it('warns for unsupported FLOAT options', () => { + // must use a new sequelize instance because warnings are only logged once per instance. + const newSequelize = createSequelizeInstance(); + + newSequelize.define('A', { + age: { + type: DataTypes.FLOAT(10, 2), + }, + }); + + if (!['mysql', 'mariadb'].includes(dialectName)) { + // @ts-expect-error -- only used in testing + expect(console.warn.called).to.eq(true, 'console.warn was not called'); + + // @ts-expect-error -- only used in testing + const warnings = console.warn.args.map(args => args[0]); + expect( + warnings.some((msg: string) => + msg.includes( + `does not support FLOAT with scale or precision specified. These options are ignored.`, + ), + ), + ).to.eq(true, 'warning was not logged'); + } else { + // @ts-expect-error -- only used in testing + expect(console.warn.called).to.equal( + false, + 'console.warn was called but it should not have been', + ); + } + }); + }); + + describe('with defaultTimestampPrecision', () => { + describe('not specified', () => { + it('should add the automatic timestamp columns with the default precision of 6', async () => { + const newSequelize = createSequelizeInstance(); + const MyModel = newSequelize.define('MyModel', {}, { paranoid: true }); + + const { physicalAttributes } = MyModel.modelDefinition; + expect(physicalAttributes.get('createdAt')).to.have.nested.property( + 'type.options.precision', + 6, + ); + expect(physicalAttributes.get('updatedAt')).to.have.nested.property( + 'type.options.precision', + 6, + ); + expect(physicalAttributes.get('deletedAt')).to.have.nested.property( + 'type.options.precision', + 6, + ); + }); + }); + + describe('set to a number', () => { + it('should add the automatic timestamp columns with the specified precision', async () => { + const newSequelize = createSequelizeInstance({ + defaultTimestampPrecision: 4, + }); + const MyModel = newSequelize.define('MyModel', {}, { paranoid: true }); + + const { physicalAttributes } = MyModel.modelDefinition; + expect(physicalAttributes.get('createdAt')).to.have.nested.property( + 'type.options.precision', + 4, + ); + expect(physicalAttributes.get('updatedAt')).to.have.nested.property( + 'type.options.precision', + 4, + ); + expect(physicalAttributes.get('deletedAt')).to.have.nested.property( + 'type.options.precision', + 4, + ); + }); + }); + + describe('set to null', () => { + it('should add the automatic timestamp columns with no specified precision', async () => { + const newSequelize = createSequelizeInstance({ + defaultTimestampPrecision: null, + }); + const MyModel = newSequelize.define('MyModel', {}, { paranoid: true }); + + const { physicalAttributes } = MyModel.modelDefinition; + expect(physicalAttributes.get('createdAt')).to.have.nested.property( + 'type.options.precision', + undefined, + ); + expect(physicalAttributes.get('updatedAt')).to.have.nested.property( + 'type.options.precision', + undefined, + ); + expect(physicalAttributes.get('deletedAt')).to.have.nested.property( + 'type.options.precision', + undefined, + ); + }); + }); + }); + }); + + describe('afterDefine / beforeDefine', () => { + const vars = beforeAll2(() => { + sequelize.hooks.addListener('beforeDefine', (attributes, options) => { + options.modelName = 'bar'; + options.name!.plural = 'barrs'; + attributes.type = DataTypes.STRING; + }); + + sequelize.hooks.addListener('afterDefine', factory => { + factory.options.name.singular = 'barr'; + }); + + const TestModel = sequelize.define('foo', { name: DataTypes.STRING }); + + return { TestModel }; + }); + + it('beforeDefine hook can change model name', () => { + const { TestModel } = vars; + expect(TestModel.name).to.equal('bar'); + }); + + it('beforeDefine hook can alter options', () => { + const { TestModel } = vars; + expect(TestModel.options.name.plural).to.equal('barrs'); + }); + + it('beforeDefine hook can alter attributes', () => { + const { TestModel } = vars; + expect(TestModel.getAttributes().type).to.be.ok; + }); + + it('afterDefine hook can alter options', () => { + const { TestModel } = vars; + expect(TestModel.options.name.singular).to.equal('barr'); + }); + + after(() => { + sequelize.hooks.removeAllListeners(); + }); + }); +}); diff --git a/packages/core/test/unit/model/destroy.test.js b/packages/core/test/unit/model/destroy.test.js new file mode 100644 index 000000000000..074adbf3bde6 --- /dev/null +++ b/packages/core/test/unit/model/destroy.test.js @@ -0,0 +1,46 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('method destroy', () => { + const User = current.define('User', { + name: DataTypes.STRING, + secretValue: DataTypes.INTEGER, + }); + + before(function () { + this.stubDelete = sinon.stub(current, 'queryRaw').resolves([]); + }); + + beforeEach(function () { + this.deloptions = { where: { secretValue: '1' } }; + this.cloneOptions = { ...this.deloptions }; + this.stubDelete.resetHistory(); + }); + + afterEach(function () { + delete this.deloptions; + delete this.cloneOptions; + }); + + after(function () { + this.stubDelete.restore(); + }); + + it('can detect complex objects', async () => { + const Where = function () { + this.secretValue = '1'; + }; + + await expect(User.destroy({ where: new Where() })).to.be.rejected; + }); + }); +}); diff --git a/packages/core/test/unit/model/find-all.test.js b/packages/core/test/unit/model/find-all.test.js new file mode 100644 index 000000000000..652350bca2ac --- /dev/null +++ b/packages/core/test/unit/model/find-all.test.js @@ -0,0 +1,146 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes, QueryError } = require('@sequelize/core'); +const { Logger } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'); +const { beforeAll2 } = require('../../support'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('_warnOnInvalidOptions', () => { + beforeEach(function () { + this.loggerSpy = sinon.spy(Logger.prototype, 'warn'); + }); + + afterEach(function () { + this.loggerSpy.restore(); + }); + + it('Warns the user if they use a model attribute without a where clause', function () { + const User = current.define('User', { firstName: 'string' }); + User._warnOnInvalidOptions({ firstName: 12, order: [] }, ['firstName']); + const expectedError = + 'Model attributes (firstName) passed into finder method options of model User, but the options.where object is empty. Did you forget to use options.where?'; + expect(this.loggerSpy.calledWith(expectedError)).to.equal(true); + }); + + it('Does not warn the user if they use a model attribute without a where clause that shares its name with a query option', function () { + const User = current.define('User', { order: 'string' }); + User._warnOnInvalidOptions({ order: [] }, ['order']); + expect(this.loggerSpy.called).to.equal(false); + }); + + it('Does not warn the user if they use valid query options', function () { + const User = current.define('User', { order: 'string' }); + User._warnOnInvalidOptions({ where: { order: 1 }, order: [] }); + expect(this.loggerSpy.called).to.equal(false); + }); + }); + + describe('method findAll', () => { + const vars = beforeAll2(() => { + const MyModel = current.define( + 'MyModel', + { + name: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { MyModel }; + }); + + before(function () { + const { MyModel } = vars; + + this.stub = sinon.stub(current.queryInterface, 'select').callsFake(() => MyModel.build({})); + this._warnOnInvalidOptionsStub = sinon.stub(MyModel, '_warnOnInvalidOptions'); + }); + + beforeEach(function () { + this.stub.resetHistory(); + this._warnOnInvalidOptionsStub.resetHistory(); + }); + + after(function () { + this.stub.restore(); + this._warnOnInvalidOptionsStub.restore(); + }); + + describe('handles input validation', () => { + it('calls _warnOnInvalidOptions', function () { + const { MyModel } = vars; + + MyModel.findAll(); + expect(this._warnOnInvalidOptionsStub.calledOnce).to.equal(true); + }); + + it('Throws an error when the attributes option is formatted incorrectly', async () => { + const { MyModel } = vars; + + await expect(MyModel.findAll({ attributes: 'name' })).to.be.rejectedWith(QueryError); + }); + }); + + describe('attributes include / exclude', () => { + it('allows me to include additional attributes', async function () { + const { MyModel } = vars; + + await MyModel.findAll({ + attributes: { + include: ['foobar'], + }, + }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal(['id', 'name', 'foobar']); + }); + + it('allows me to exclude attributes', async function () { + const { MyModel } = vars; + + await MyModel.findAll({ + attributes: { + exclude: ['name'], + }, + }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal(['id']); + }); + + it('include takes precendence over exclude', async function () { + const { MyModel } = vars; + + await MyModel.findAll({ + attributes: { + exclude: ['name'], + include: ['name'], + }, + }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal(['id', 'name']); + }); + + it('works for models without PK #4607', async function () { + const MyModel = current.define('model', {}, { timestamps: false }); + const Foo = current.define('foo'); + MyModel.hasOne(Foo); + + MyModel.removeAttribute('id'); + + await MyModel.findAll({ + attributes: { + include: ['name'], + }, + include: [Foo], + }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal(['name']); + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/find-and-count-all.test.js b/packages/core/test/unit/model/find-and-count-all.test.js new file mode 100644 index 000000000000..92578019c1ab --- /dev/null +++ b/packages/core/test/unit/model/find-and-count-all.test.js @@ -0,0 +1,45 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('findAndCountAll', () => { + describe('should handle promise rejection', () => { + before(function () { + this.stub = sinon.stub(); + + process.on('unhandledRejection', this.stub); + + this.User = current.define('User', { + username: DataTypes.STRING, + age: DataTypes.INTEGER, + }); + + this.findAll = sinon.stub(this.User, 'findAll').rejects(new Error()); + + this.count = sinon.stub(this.User, 'count').rejects(new Error()); + }); + + after(function () { + this.findAll.resetBehavior(); + this.count.resetBehavior(); + }); + + it('with errors in count and findAll both', async function () { + try { + await this.User.findAndCountAll({}); + throw new Error(); + } catch { + expect(this.stub.callCount).to.eql(0); + } + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/find-by-pk.test.ts b/packages/core/test/unit/model/find-by-pk.test.ts new file mode 100644 index 000000000000..5b787e4b0b27 --- /dev/null +++ b/packages/core/test/unit/model/find-by-pk.test.ts @@ -0,0 +1,88 @@ +import { DataTypes, Model } from '@sequelize/core'; + +import { getTestDialectTeaser, sequelize } from '../../support'; + +import { expect } from 'chai'; + +import sinon from 'sinon'; + +describe(getTestDialectTeaser('Model'), () => { + describe('findByPk', () => { + beforeEach(() => { + sinon.stub(Model, 'findAll').resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should call internal findOne() method if findOne() is overridden', async () => { + const testModel = sequelize.define('model', { + unique1: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + unique2: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + }); + testModel.findOne = sinon.stub(); + + sinon.spy(Model, 'findOne'); + + await testModel.findByPk(1); + testModel.findOne.should.not.have.been.called; + Model.findOne.should.have.been.called; + }); + + it('should use composite primary key when querying table has one', async () => { + const testModel = sequelize.define('model', { + pk1: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + pk2: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + const findOneSpy = sinon.spy(Model, 'findOne'); + await testModel.findByPk({ pk1: 1, pk2: 2 }); + findOneSpy.should.have.been.calledWithMatch({ + where: { pk1: 1, pk2: 2 }, + }); + }); + + it('should throw error if composite primary key args not match key', async () => { + const testModel = sequelize.define('model', { + pk1: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + pk2: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + await expect(testModel.findByPk({ pk1: 1 })).to.eventually.be.rejectedWith(TypeError); + }); + + it('should throw error if wrong type passed and model has composite primary key', async () => { + const testModel = sequelize.define('model', { + pk1: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + pk2: { + type: DataTypes.INTEGER, + primaryKey: true, + }, + }); + + await expect(testModel.findByPk(1)).to.eventually.be.rejectedWith(TypeError); + }); + }); +}); diff --git a/packages/core/test/unit/model/find-create-find.test.js b/packages/core/test/unit/model/find-create-find.test.js new file mode 100644 index 000000000000..6e869a27e432 --- /dev/null +++ b/packages/core/test/unit/model/find-create-find.test.js @@ -0,0 +1,78 @@ +'use strict'; + +const { expect } = require('chai'); +const { EmptyResultError, UniqueConstraintError } = require('@sequelize/core'); +const { beforeAll2, sequelize } = require('../../support'); +const sinon = require('sinon'); + +describe('Model#findCreateFind', () => { + const vars = beforeAll2(() => { + const TestModel = sequelize.define('TestModel', {}); + + return { TestModel }; + }); + + beforeEach(function () { + this.sinon = sinon.createSandbox(); + }); + + afterEach(function () { + this.sinon.restore(); + }); + + it('should return the result of the first find call if not empty', async function () { + const { TestModel } = vars; + const result = {}; + const where = { prop: Math.random().toString() }; + const findSpy = this.sinon.stub(TestModel, 'findOne').resolves(result); + + await expect( + TestModel.findCreateFind({ + 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', async function () { + const { TestModel } = vars; + const result = {}; + const where = { prop: Math.random().toString() }; + const createSpy = this.sinon.stub(TestModel, 'create').resolves(result); + + this.sinon.stub(TestModel, 'findOne').resolves(null); + + await expect( + TestModel.findCreateFind({ + where, + }), + ).to.eventually.eql([result, true]); + + expect(createSpy).to.have.been.calledWith(where); + }); + + for (const Error of [EmptyResultError, UniqueConstraintError]) { + it(`should do a second find if create failed due to an error of type ${Error.name}`, async function () { + const { TestModel } = vars; + const result = {}; + const where = { prop: Math.random().toString() }; + const findSpy = this.sinon.stub(TestModel, 'findOne'); + + this.sinon.stub(TestModel, 'create').rejects(new Error()); + + findSpy.onFirstCall().resolves(null); + findSpy.onSecondCall().resolves(result); + + await expect( + TestModel.findCreateFind({ + where, + }), + ).to.eventually.eql([result, false]); + + expect(findSpy).to.have.been.calledTwice; + expect(findSpy.getCall(1).args[0].where).to.equal(where); + }); + } +}); diff --git a/packages/core/test/unit/model/find-one.test.js b/packages/core/test/unit/model/find-one.test.js new file mode 100644 index 000000000000..e83a697ad071 --- /dev/null +++ b/packages/core/test/unit/model/find-one.test.js @@ -0,0 +1,65 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes, Op, Sequelize } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('method findOne', () => { + before(function () { + this.oldFindAll = Sequelize.Model.findAll; + }); + after(function () { + Sequelize.Model.findAll = this.oldFindAll; + }); + + beforeEach(function () { + this.stub = Sequelize.Model.findAll = sinon.stub().resolves(); + }); + + it('should add limit when using { $ gt on the primary key', async function () { + const Model = current.define('model'); + + await Model.findOne({ where: { id: { [Op.gt]: 42 } } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); + }); + + it('should add limit when using multi-column unique key', async function () { + const Model = current.define('model', { + unique1: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + unique2: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + }); + + await Model.findOne({ where: { unique1: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); + }); + it('should call internal findAll() method if findOne() is overridden', async () => { + const Model = current.define('model', { + unique1: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + unique2: { + type: DataTypes.INTEGER, + unique: 'unique', + }, + }); + Model.findAll = sinon.stub(); + + await Model.findOne(); + Model.findAll.should.not.have.been.called; + Sequelize.Model.findAll.should.have.been.called; + }); + }); +}); diff --git a/packages/core/test/unit/model/get-attributes.test.ts b/packages/core/test/unit/model/get-attributes.test.ts new file mode 100644 index 000000000000..0b24376401c5 --- /dev/null +++ b/packages/core/test/unit/model/get-attributes.test.ts @@ -0,0 +1,94 @@ +import type { DataType, NormalizedAttributeOptions } from '@sequelize/core'; +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { getTestDialectTeaser, sequelize } from '../../support'; + +function assertDataType(property: NormalizedAttributeOptions, dataType: DataType) { + expect(property.type).to.be.instanceof(dataType); +} + +describe(getTestDialectTeaser('Model'), () => { + describe('getAttributes', () => { + it(`returns the model's attributes`, () => { + const Model = sequelize.define('User', { username: DataTypes.STRING }, { timestamps: false }); + const attributes = Model.getAttributes(); + + expect(Object.keys(attributes)).to.eql(['id', 'username']); + + // Type must be casted or it will cause circular references errors + assertDataType(attributes.id, DataTypes.INTEGER); + assertDataType(attributes.username, DataTypes.STRING); + + expect(attributes.id).to.include({ + Model, + allowNull: false, + primaryKey: true, + autoIncrement: true, + _autoGenerated: true, + fieldName: 'id', + _modelAttribute: true, + field: 'id', + }); + expect(attributes.username).to.include({ + Model, + fieldName: 'username', + _modelAttribute: true, + field: 'username', + }); + }); + + it('contains timestamps if enabled', () => { + const Model = sequelize.define('User', { username: DataTypes.STRING }); + const attributes = Model.getAttributes(); + + expect(Object.keys(attributes)).to.include.members(['createdAt', 'updatedAt']); + + // Type must be casted or it will cause circular references errors + assertDataType(attributes.createdAt, DataTypes.DATE); + assertDataType(attributes.updatedAt, DataTypes.DATE); + + expect(attributes.createdAt).to.include({ + allowNull: false, + _autoGenerated: true, + Model, + fieldName: 'createdAt', + _modelAttribute: true, + field: 'createdAt', + }); + expect(attributes.updatedAt).to.include({ + allowNull: false, + _autoGenerated: true, + Model, + fieldName: 'updatedAt', + _modelAttribute: true, + field: 'updatedAt', + }); + }); + + it('contains virtual attributes', () => { + const Model = sequelize.define( + 'User', + { + username: DataTypes.STRING, + virtual: { + type: DataTypes.VIRTUAL, + get() { + return 1; + }, + }, + }, + { timestamps: false }, + ); + const attributes = Model.getAttributes(); + + expect(Object.keys(attributes)).to.include.members(['virtual']); + assertDataType(attributes.virtual, DataTypes.VIRTUAL); + expect(attributes.virtual).to.include({ + Model, + fieldName: 'virtual', + _modelAttribute: true, + field: 'virtual', + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/helpers.test.ts b/packages/core/test/unit/model/helpers.test.ts new file mode 100644 index 000000000000..35760f4de5b4 --- /dev/null +++ b/packages/core/test/unit/model/helpers.test.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { beforeAll2, sequelize } from '../../support'; + +describe('Model#hasAlias', () => { + const vars = beforeAll2(() => { + const User = sequelize.define('user'); + const Task = sequelize.define('task'); + Task.belongsTo(User, { as: 'owner' }); + + return { Task }; + }); + + it('returns true if a model has an association with the specified alias', () => { + const { Task } = vars; + + expect(Task.hasAlias('owner')).to.equal(true); + }); + + it('returns false if a model does not have an association with the specified alias', () => { + const { Task } = vars; + + expect(Task.hasAlias('notOwner')).to.equal(false); + }); +}); diff --git a/packages/core/test/unit/model/include.test.js b/packages/core/test/unit/model/include.test.js new file mode 100644 index 000000000000..aa006fad1d1b --- /dev/null +++ b/packages/core/test/unit/model/include.test.js @@ -0,0 +1,552 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes, Op, Sequelize } = require('@sequelize/core'); +const { + _validateIncludedElements, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'); + +const current = Support.sequelize; + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('all', () => { + it('can expand nested self-reference', () => { + const Referral = current.define('referral'); + + Referral.belongsTo(Referral); + + const options = { include: [{ all: true, nested: true }] }; + + Referral._expandIncludeAll(options, Referral); + + expect(options.include).to.deep.equal([ + { as: 'referral', model: Referral, association: Referral.associations.referral }, + ]); + }); + }); + + describe('_validateIncludedElements', () => { + beforeEach(function () { + this.User = this.sequelize.define('User'); + this.Task = this.sequelize.define('Task', { + title: DataTypes.STRING, + }); + this.Company = this.sequelize.define('Company', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'field_id', + }, + name: DataTypes.STRING, + }); + + this.User.Tasks = this.User.hasMany(this.Task); + this.User.Company = this.User.belongsTo(this.Company); + this.Company.Employees = this.Company.hasMany(this.User); + this.Company.Owner = this.Company.belongsTo(this.User, { + as: 'Owner', + foreignKey: 'ownerId', + }); + }); + + describe('attributes', () => { + it("should not inject the aliased PK again, if it's already there", function () { + let options = _validateIncludedElements({ + model: this.User, + include: [ + { + model: this.Company, + attributes: ['name'], + }, + ], + }); + + expect(options.include[0].attributes).to.deep.equal([['field_id', 'id'], 'name']); + + options = _validateIncludedElements(options); + + // Calling validate again shouldn't add the pk again + expect(options.include[0].attributes).to.deep.equal([['field_id', 'id'], 'name']); + }); + + describe('include / exclude', () => { + it('allows me to include additional attributes', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + model: this.Company, + attributes: { + include: ['foobar'], + }, + }, + ], + }); + + expect(options.include[0].attributes).to.deep.equal([ + ['field_id', 'id'], + 'name', + 'createdAt', + 'updatedAt', + 'ownerId', + 'foobar', + ]); + }); + + it('allows me to exclude attributes', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + model: this.Company, + attributes: { + exclude: ['name'], + }, + }, + ], + }); + + expect(options.include[0].attributes).to.deep.equal([ + ['field_id', 'id'], + 'createdAt', + 'updatedAt', + 'ownerId', + ]); + }); + + it('include takes precendence over exclude', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + model: this.Company, + attributes: { + exclude: ['name'], + include: ['name'], + }, + }, + ], + }); + + expect(options.include[0].attributes).to.deep.equal([ + ['field_id', 'id'], + 'createdAt', + 'updatedAt', + 'ownerId', + 'name', + ]); + }); + }); + }); + + describe('scope', () => { + beforeEach(function () { + this.Project = this.sequelize.define( + 'project', + { + bar: { + type: DataTypes.STRING, + field: 'foo', + }, + }, + { + defaultScope: { + where: { + active: true, + }, + }, + scopes: { + this: { + where: { this: true }, + }, + that: { + where: { that: false }, + limit: 12, + }, + attr: { + attributes: ['baz'], + }, + foobar: { + where: { + bar: 42, + }, + }, + }, + }, + ); + + this.User.hasMany(this.Project); + + this.User.hasMany(this.Project.withScope('this'), { as: 'thisProject' }); + }); + + it('adds the default scope to where', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ model: this.Project, as: 'projects' }], + }); + + expect(options.include[0]).to.have.property('where').which.deep.equals({ active: true }); + }); + + it('adds the where from a scoped model', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ model: this.Project.withScope('that'), as: 'projects' }], + }); + + expect(options.include[0]).to.have.property('where').which.deep.equals({ that: false }); + expect(options.include[0]).to.have.property('limit').which.equals(12); + }); + + it('adds the attributes from a scoped model', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ model: this.Project.withScope('attr'), as: 'projects' }], + }); + + expect(options.include[0]).to.have.property('attributes').which.deep.equals(['baz']); + }); + + it('merges where with the where from a scoped model', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { where: { active: false }, model: this.Project.withScope('that'), as: 'projects' }, + ], + }); + + expect(options.include[0].where).to.deep.equal({ + [Op.and]: [{ that: false }, { active: false }], + }); + }); + + it('add the where from a scoped associated model', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ model: this.Project, as: 'thisProject' }], + }); + + expect(options.include[0]).to.have.property('where').which.deep.equals({ this: true }); + }); + + it('handles a scope with an aliased column (.field)', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ model: this.Project.withScope('foobar'), as: 'projects' }], + }); + + expect(options.include[0]).to.have.property('where').which.deep.equals({ bar: 42 }); + }); + }); + + describe('duplicating', () => { + it('should tag a hasMany association as duplicating: true if undefined', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [this.User.Tasks], + }); + + expect(options.include[0].duplicating).to.equal(true); + }); + + it('should respect include.duplicating for a hasMany', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks, duplicating: false }], + }); + + expect(options.include[0].duplicating).to.equal(false); + }); + }); + + describe('_conformInclude', () => { + it('should expand association from string alias', function () { + const options = { + include: ['Owner'], + }; + Sequelize.Model._conformIncludes(options, this.Company); + + expect(options.include[0]).to.deep.equal({ + model: this.User, + association: this.Company.Owner, + as: 'Owner', + }); + }); + + it('should expand string association', function () { + const options = { + include: [ + { + association: 'Owner', + attributes: ['id'], + }, + ], + }; + Sequelize.Model._conformIncludes(options, this.Company); + + expect(options.include[0]).to.deep.equal({ + model: this.User, + association: this.Company.Owner, + attributes: ['id'], + as: 'Owner', + }); + }); + + it('should throw an error if invalid model is passed', function () { + const options = { + include: [ + { + model: null, + }, + ], + }; + + expect(() => { + Sequelize.Model._conformIncludes(options, this.Company); + }).to.throw( + 'Invalid Include received. Include has to be either a Model, an Association, the name of an association, or a plain object compatible with IncludeOptions.', + ); + }); + + it('should throw an error if invalid association is passed', function () { + const options = { + include: [ + { + association: null, + }, + ], + }; + + expect(() => { + Sequelize.Model._conformIncludes(options, this.Company); + }).to.throw( + 'Invalid Include received. Include has to be either a Model, an Association, the name of an association, or a plain object compatible with IncludeOptions.', + ); + }); + }); + + describe('getAssociationWithModel', () => { + it('returns an association when there is a single unaliased association', function () { + expect(this.User.getAssociationWithModel(this.Task)).to.equal(this.User.Tasks); + }); + + it('returns an association when there is a single aliased association', function () { + const User = this.sequelize.define('User'); + const Task = this.sequelize.define('Task'); + const Tasks = Task.belongsTo(User, { as: 'owner' }); + expect(Task.getAssociationWithModel(User, 'owner')).to.equal(Tasks); + }); + + it('returns an association when there are multiple aliased associations', function () { + expect(this.Company.getAssociationWithModel(this.User, 'Owner')).to.equal( + this.Company.Owner, + ); + }); + }); + + describe('subQuery', () => { + it('should be true if theres a duplicating association', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks }], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + }); + + it('should be false if theres a duplicating association but no limit', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks }], + limit: null, + }); + + expect(options.subQuery).to.equal(false); + }); + + it('should be true if theres a nested duplicating association', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + association: this.User.Company, + include: [this.Company.Employees], + }, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + }); + + it('should be false if theres a nested duplicating association but no limit', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + association: this.User.Company, + include: [this.Company.Employees], + }, + ], + limit: null, + }); + + expect(options.subQuery).to.equal(false); + }); + + it('should tag a required hasMany association', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks, required: true }], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + expect(options.include[0].subQuery).to.equal(false); + expect(options.include[0].subQueryFilter).to.equal(true); + }); + + it('should not tag a required hasMany association with duplicating false', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks, required: true, duplicating: false }], + limit: 3, + }); + + expect(options.subQuery).to.equal(false); + expect(options.include[0].subQuery).to.equal(false); + expect(options.include[0].subQueryFilter).to.equal(false); + }); + + it('should not tag a separate hasMany association with subQuery true', function () { + const options = _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 = _validateIncludedElements({ + model: this.User, + include: [{ association: this.User.Tasks, where: { title: Math.random().toString() } }], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + expect(options.include[0].subQuery).to.equal(false); + expect(options.include[0].subQueryFilter).to.equal(true); + }); + + it('should not tag a hasMany association with where and duplicating false', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + association: this.User.Tasks, + where: { title: Math.random().toString() }, + duplicating: false, + }, + ], + limit: 3, + }); + + 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 required belongsTo alongside a duplicating association', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { association: this.User.Company, required: true }, + { association: this.User.Tasks }, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + expect(options.include[0].subQuery).to.equal(true); + }); + + it('should not tag a required belongsTo alongside a duplicating association with duplicating false', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { association: this.User.Company, required: true }, + { association: this.User.Tasks, duplicating: false }, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(false); + expect(options.include[0].subQuery).to.equal(false); + }); + + it('should tag a belongsTo association with where alongside a duplicating association', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { association: this.User.Company, where: { name: Math.random().toString() } }, + { association: this.User.Tasks }, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + expect(options.include[0].subQuery).to.equal(true); + }); + + it('should tag a required belongsTo association alongside a duplicating association with a nested belongsTo', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { + association: this.User.Company, + required: true, + include: [this.Company.Owner], + }, + this.User.Tasks, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(true); + expect(options.include[0].subQuery).to.equal(true); + expect(options.include[0].include[0].subQuery).to.equal(false); + expect(options.include[0].include[0].parent.subQuery).to.equal(true); + }); + + it('should tag a belongsTo association with where alongside a duplicating association with duplicating false', function () { + const options = _validateIncludedElements({ + model: this.User, + include: [ + { association: this.User.Company, where: { name: Math.random().toString() } }, + { association: this.User.Tasks, duplicating: false }, + ], + limit: 3, + }); + + expect(options.subQuery).to.equal(false); + expect(options.include[0].subQuery).to.equal(false); + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/indexes.test.ts b/packages/core/test/unit/model/indexes.test.ts new file mode 100644 index 000000000000..7f64c81838e7 --- /dev/null +++ b/packages/core/test/unit/model/indexes.test.ts @@ -0,0 +1,224 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('Model indexes', () => { + if (dialect.supports.dataTypes.JSONB) { + it('uses a gin index for JSONB attributes by default', () => { + const Model = sequelize.define('event', { + eventData: { + type: DataTypes.JSONB, + field: 'data', + index: true, + }, + }); + + expect(Model.getIndexes()).to.deep.eq([ + { + column: 'eventData', + fields: ['data'], + using: 'gin', + name: 'events_data', + }, + ]); + }); + } + + it('should set the unique property when type is unique', () => { + const Model = sequelize.define( + 'm', + {}, + { + indexes: [ + { + type: 'unique', + fields: ['firstName'], + }, + { + type: 'UNIQUE', + fields: ['lastName'], + }, + ], + }, + ); + + expect(Model.getIndexes()).to.deep.eq([ + { + fields: ['firstName'], + unique: true, + name: 'ms_first_name_unique', + }, + { + fields: ['lastName'], + unique: true, + name: 'ms_last_name_unique', + }, + ]); + }); + + // Model.getIndexes() is the only source of truth for indexes + it('does not copy model-level indexes to individual attributes', () => { + const User = sequelize.define( + 'User', + { + username: DataTypes.STRING, + }, + { + indexes: [ + { + unique: true, + fields: ['username'], + }, + ], + }, + ); + + // @ts-expect-error -- "unique" gets removed from built attributes + expect(User.getAttributes().username.unique).to.be.undefined; + }); + + it('supports declaring an index on an attribute', () => { + const User = sequelize.define('User', { + name: { + type: DataTypes.STRING, + index: true, + }, + }); + + expect(User.getIndexes()).to.deep.eq([ + { + column: 'name', + fields: ['name'], + name: 'users_name', + }, + ]); + }); + + it('merges indexes with the same name', () => { + const User = sequelize.define( + 'User', + { + firstName: { + type: DataTypes.STRING, + index: 'name', + }, + middleName: { + type: DataTypes.STRING, + index: { + name: 'name', + }, + }, + lastName: { + type: DataTypes.STRING, + index: 'name', + }, + }, + { + indexes: [ + { + name: 'name', + fields: ['nickname'], + }, + ], + }, + ); + + expect(User.getIndexes()).to.deep.eq([ + { + fields: ['nickname', 'firstName', 'middleName', 'lastName'], + name: 'name', + }, + ]); + }); + + it('throws if two indexes with the same name use incompatible options', () => { + expect(() => { + sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + index: { + name: 'name', + unique: true, + }, + }, + lastName: { + type: DataTypes.STRING, + index: { + name: 'name', + unique: false, + }, + }, + }); + }).to.throw( + 'Index "name" has conflicting options: "unique" was defined with different values true and false.', + ); + }); + + it('supports using index & unique at the same time', () => { + const User = sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + unique: true, + index: true, + }, + }); + + expect(User.getIndexes()).to.deep.eq([ + { + fields: ['firstName'], + column: 'firstName', + unique: true, + name: 'users_first_name_unique', + }, + { + fields: ['firstName'], + column: 'firstName', + name: 'users_first_name', + }, + ]); + }); + + it('supports configuring the index attribute options', () => { + const User = sequelize.define('User', { + firstName: { + type: DataTypes.STRING, + columnName: 'first_name', + index: { + name: 'first_last_name', + unique: true, + attribute: { + collate: 'en_US', + operator: 'text_pattern_ops', + order: 'DESC', + }, + }, + }, + lastName: { + type: DataTypes.STRING, + columnName: 'last_name', + index: { + name: 'first_last_name', + unique: true, + }, + }, + }); + + expect(User.getIndexes()).to.deep.eq([ + { + fields: [ + { + name: 'first_name', + collate: 'en_US', + operator: 'text_pattern_ops', + order: 'DESC', + }, + 'last_name', + ], + unique: true, + name: 'first_last_name', + }, + ]); + }); +}); diff --git a/packages/core/test/unit/model/init.test.ts b/packages/core/test/unit/model/init.test.ts new file mode 100644 index 000000000000..05b53c183150 --- /dev/null +++ b/packages/core/test/unit/model/init.test.ts @@ -0,0 +1,27 @@ +import { Model } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('Uninitialized model', () => { + class Test extends Model {} + + it('throws when constructed', () => { + expect(() => Test.build()).to.throw(/has not been initialized/); + }); + + it('throws when .sequelize is accessed', () => { + expect(() => Test.sequelize).to.throw(/has not been initialized/); + }); +}); + +describe('Initialized model', () => { + it('throws if initialized twice', () => { + class Test extends Model {} + + Test.init({}, { sequelize }); + + expect(() => { + Test.init({}, { sequelize }); + }).to.throw(/already been initialized/); + }); +}); diff --git a/packages/core/test/unit/model/overwriting-builtins.test.ts b/packages/core/test/unit/model/overwriting-builtins.test.ts new file mode 100644 index 000000000000..445d4407ccb4 --- /dev/null +++ b/packages/core/test/unit/model/overwriting-builtins.test.ts @@ -0,0 +1,77 @@ +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, + NonAttribute, +} from '@sequelize/core'; +import { DataTypes, Model } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('Model', () => { + // TODO [>=8]: throw if an attribute has the same name as a built-in Model method. + it('does not let attributes shadow built-in model method & properties', () => { + const User = sequelize.define('OverWrittenKeys', { + set: DataTypes.STRING, + }); + + const user = User.build({ set: 'A' }); + expect(user.get('set')).to.equal('A'); + user.set('set', 'B'); + expect(user.get('set')).to.equal('B'); + }); + + it('makes attributes take priority over class properties defined on the model', () => { + // Issue description: https://github.com/sequelize/sequelize/issues/14300 + // Sequelize models add getter & setters on the model prototype for all attributes defined on the model. + // Class properties, which are usually used when using TypeScript or decorators, are added on the model instance, + // and therefore take priority over the prototype getters & setters. + // This test ensures that the attributes are not shadowed by the class properties. + class User extends Model, InferCreationAttributes> { + id: CreationOptional = 10; + firstName: string = 'abc'; + nonAttribute: NonAttribute = 'def'; + } + + User.init( + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + firstName: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { sequelize }, + ); + + const user = User.build({ firstName: 'Zoe' }); + + expect(user.id).to.eq(null); // autoIncrement + expect(user.firstName).to.eq('Zoe'); + expect(user.nonAttribute).to.eq('def'); + }); + + it('makes associations take priority over class properties defined on the model', () => { + // This is the same issue as above, but for associations + class User extends Model, InferCreationAttributes> { + profile?: Profile; + } + + class Profile extends Model, InferCreationAttributes> { + userId: number = 10; + } + + User.init({}, { sequelize }); + Profile.init({}, { sequelize }); + + User.hasOne(Profile, { as: 'profile', foreignKey: 'userId' }); + + // @ts-expect-error -- TODO: add typing for creating associations in build/create + const user = User.build({ profile: {} }, { include: ['profile'] }); + expect(user.profile).to.be.instanceof(Profile); + }); +}); diff --git a/packages/core/test/unit/model/remove-attribute.test.ts b/packages/core/test/unit/model/remove-attribute.test.ts new file mode 100644 index 000000000000..cd04110f290a --- /dev/null +++ b/packages/core/test/unit/model/remove-attribute.test.ts @@ -0,0 +1,33 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import size from 'lodash/size'; +import { getTestDialectTeaser, sequelize } from '../../support'; + +describe(getTestDialectTeaser('Model'), () => { + describe('removeAttribute', () => { + it('should support removing the primary key', () => { + const Model = sequelize.define('m', { + name: DataTypes.STRING, + }); + + expect(Model.primaryKeyAttribute).to.equal('id'); + expect(size(Model.primaryKeys)).to.equal(1); + + Model.removeAttribute('id'); + + expect(Model.primaryKeyAttribute).to.be.null; + expect(size(Model.primaryKeys)).to.equal(0); + }); + + it('should not add undefined attribute after removing primary key', () => { + const Model = sequelize.define('m', { + name: DataTypes.STRING, + }); + + Model.removeAttribute('id'); + + const instance = Model.build(); + expect(instance.dataValues).not.to.include.keys('undefined'); + }); + }); +}); diff --git a/packages/core/test/unit/model/schema.test.ts b/packages/core/test/unit/model/schema.test.ts new file mode 100644 index 000000000000..e95775fe32f6 --- /dev/null +++ b/packages/core/test/unit/model/schema.test.ts @@ -0,0 +1,144 @@ +import { literal } from '@sequelize/core'; +import { expect } from 'chai'; +import assert from 'node:assert'; +import { + allowDeprecationsInSuite, + beforeAll2, + getTestDialectTeaser, + sequelize, +} from '../../support'; + +describe(`${getTestDialectTeaser('Model')}Schemas`, () => { + allowDeprecationsInSuite(['SEQUELIZE0009']); + + if (!sequelize.dialect.supports.schemas) { + return; + } + + const vars = beforeAll2(() => { + const Project = sequelize.define('project'); + const Company = sequelize.define( + 'company', + {}, + { + schema: 'default', + schemaDelimiter: '&', + }, + ); + Project.addScope('scope1', { where: literal('') }); + + return { Project, Company }; + }); + + describe('schema', () => { + it('should work with no default schema', () => { + const { Project } = vars; + + expect(Project.table.schema).to.equal(sequelize.dialect.getDefaultSchema()); + }); + + it('should apply default schema from define', () => { + const { Company } = vars; + + expect(Company.table.schema).to.equal('default'); + }); + + it('returns the same model if the schema is equal', () => { + const { Project } = vars; + + assert( + // eslint-disable-next-line no-self-compare -- value could be different. + Project.withSchema('newSchema') === Project.withSchema('newSchema'), + 'withSchema should have returned the same model if the schema is equal', + ); + }); + + it('returns a new model if the schema is equal, but scope is different', () => { + const { Project } = vars; + + expect(Project.withScope('scope1').withSchema('newSchema')).not.to.equal( + Project.withSchema('newSchema'), + ); + }); + + it('returns the current model if the schema is identical', () => { + const { Project } = vars; + + assert(Project.withSchema('') === Project); + assert(Project.withSchema('test').withSchema('test') === Project.withSchema('test')); + }); + + it('should be able to override the default schema', () => { + const { Company } = vars; + + expect(Company.withSchema('newSchema').table.schema).to.equal('newSchema'); + }); + + it('should be able to override the default schema using deprecated schema', () => { + const { Company } = vars; + + expect(Company.schema('newSchema').table.schema).to.equal('newSchema'); + }); + + it('should be able nullify schema', () => { + const { Company } = vars; + + expect(Company.withSchema(null).table.schema).to.equal(sequelize.dialect.getDefaultSchema()); + }); + + it('should support multiple, coexistent schema models', () => { + const { Company } = vars; + + const schema1 = Company.withSchema('schema1'); + const schema2 = Company.withSchema('schema1'); + + expect(schema1.table.schema).to.equal('schema1'); + expect(schema2.table.schema).to.equal('schema1'); + }); + }); + describe('schema delimiter', () => { + it('should work with no default schema delimiter', () => { + const { Project } = vars; + + expect(Project.table.delimiter).to.equal('.'); + }); + + it('should apply default schema delimiter from define', () => { + const { Company } = vars; + + expect(Company.table.delimiter).to.equal('&'); + }); + + it('should be able to override the default schema delimiter', () => { + const { Company } = vars; + + expect( + Company.withSchema({ schema: Company.table.schema!, schemaDelimiter: '^' }).table.delimiter, + ).to.equal('^'); + }); + + it('should be able to override the default schema delimiter using deprecated schema', () => { + const { Company } = vars; + + expect(Company.schema(Company.table.schema, '^').table.delimiter).to.equal('^'); + }); + + it('should be able to override the default schema delimiter using deprecated schema with schema object', () => { + const { Company } = vars; + + expect( + Company.schema(Company.table.schema, { schemaDelimiter: '^' }).table.delimiter, + ).to.equal('^'); + }); + + it('should support multiple, coexistent schema delimiter models', () => { + const { Company } = vars; + + const schema1 = Company.withSchema({ schema: Company.table.schema!, schemaDelimiter: '$' }); + const schema2 = Company.withSchema({ schema: Company.table.schema!, schemaDelimiter: '#' }); + + expect(schema1.table.delimiter).to.equal('$'); + expect(schema2.table.delimiter).to.equal('#'); + }); + }); +}); diff --git a/packages/core/test/unit/model/scope.test.ts b/packages/core/test/unit/model/scope.test.ts new file mode 100644 index 000000000000..972638608292 --- /dev/null +++ b/packages/core/test/unit/model/scope.test.ts @@ -0,0 +1,1047 @@ +import type { FindOptions } from '@sequelize/core'; +import { DataTypes, Op, Sequelize, col, literal, where } from '@sequelize/core'; +import { expect } from 'chai'; +import assert from 'node:assert'; +import { beforeEach2, getTestDialectTeaser, resetSequelizeInstance } from '../../support'; + +const sequelize = require('../../support').sequelize; + +describe(getTestDialectTeaser('Model'), () => { + beforeEach(() => { + resetSequelizeInstance(); + }); + + function getModels() { + const Project = sequelize.define('project', {}); + const User = sequelize.define( + 'user', + { + password: DataTypes.STRING, + value: DataTypes.INTEGER, + name: DataTypes.STRING, + }, + { + defaultScope: { + attributes: { + exclude: ['password'], + }, + }, + scopes: { + aScope: { + attributes: { + exclude: ['value'], + }, + }, + }, + }, + ); + + const Company = sequelize.define( + 'company', + {}, + { + defaultScope: { + include: [Project], + where: { active: true }, + }, + scopes: { + complexFunction(value: any): FindOptions { + return { + where: literal(`${value} IN (SELECT foobar FROM some_sql_function(foo.bar))`), + }; + }, + somethingTrue: { + where: { + something: true, + somethingElse: 42, + }, + limit: 5, + }, + somethingFalse: { + where: { + something: false, + }, + }, + sequelizeWhere: { + where: where(col('a'), 1), + }, + users: { + include: [{ model: User }], + }, + alsoUsers: { + include: [{ model: User, where: { something: 42 } }], + }, + projects: { + include: [Project], + }, + groupByCompanyId: { + group: ['company.id'], + }, + groupByProjectId: { + group: ['project.id'], + include: [Project], + }, + noArgs() { + // This does not make much sense, since it does not actually need to be in a function, + // In reality it could be used to do for example new Date or random in the scope - but we want it deterministic + + return { + where: { + other_value: 7, + }, + }; + }, + actualValue(value: any) { + return { + where: { + other_value: value, + }, + }; + }, + }, + }, + ); + + Company.hasMany(User); + Company.hasMany(Project); + + return { Project, User, Company }; + } + + describe('withScope', () => { + describe('attribute exclude / include', () => { + const vars = beforeEach2(() => { + const User = sequelize.define( + 'User', + { + password: DataTypes.STRING, + value: DataTypes.INTEGER, + name: DataTypes.STRING, + }, + { + defaultScope: { + attributes: { + exclude: ['password'], + }, + }, + scopes: { + aScope: { + attributes: { + exclude: ['value'], + }, + }, + }, + }, + ); + + return { User }; + }); + + it('should not expand attributes', () => { + const { User } = vars; + expect(User._scope.attributes).to.deep.equal({ exclude: ['password'] }); + }); + + it('should not expand attributes', () => { + const { User } = vars; + expect(User.withScope('aScope')._scope.attributes).to.deep.equal({ exclude: ['value'] }); + }); + + it('should unite attributes with array', () => { + const { User } = vars; + expect(User.withScope('aScope', 'defaultScope')._scope.attributes).to.deep.equal({ + exclude: ['value', 'password'], + }); + }); + + it('should not modify the original scopes when merging them', () => { + const { User } = vars; + expect( + User.withScope('defaultScope', 'aScope').options.defaultScope!.attributes, + ).to.deep.equal({ exclude: ['password'] }); + }); + }); + + it('defaultScope should be an empty object if not overridden', () => { + const Foo = sequelize.define('foo', {}, {}); + + expect(Foo.withScope('defaultScope')._scope).to.deep.equal({}); + }); + + it('should apply default scope', () => { + const { Company, Project } = getModels(); + + expect(Company._scope).to.deep.equal({ + include: [Project], + where: { active: true }, + }); + }); + + it('should be able to merge scopes', () => { + const { Company } = getModels(); + + expect( + Company.withScope('somethingTrue', 'somethingFalse', 'sequelizeWhere')._scope, + ).to.deep.equal({ + where: { + [Op.and]: [ + { something: true, somethingElse: 42 }, + { something: false }, + where(col('a'), 1), + ], + }, + limit: 5, + }); + }); + + it('should support multiple, coexistent scoped models', () => { + const { Company } = getModels(); + + const scoped1 = Company.withScope('somethingTrue'); + const scoped2 = Company.withScope('somethingFalse'); + + expect(scoped1._scope).to.deep.equal({ + where: { + something: true, + somethingElse: 42, + }, + limit: 5, + }); + expect(scoped2._scope).to.deep.equal({ + where: { + something: false, + }, + }); + }); + + it('should work with function scopes', () => { + const { Company } = getModels(); + + expect(Company.withScope({ method: ['actualValue', 11] })._scope).to.deep.equal({ + where: { + other_value: 11, + }, + }); + + expect(Company.withScope('noArgs')._scope).to.deep.equal({ + where: { + other_value: 7, + }, + }); + }); + + it('should work with consecutive function scopes', () => { + const { Company } = getModels(); + + const scope = { method: ['actualValue', 11] }; + expect(Company.withScope(scope)._scope).to.deep.equal({ + where: { + other_value: 11, + }, + }); + + expect(Company.withScope(scope)._scope).to.deep.equal({ + where: { + other_value: 11, + }, + }); + }); + + it('should be able to check default scope name', () => { + const { Company } = getModels(); + + expect(Company._scopeNames).to.include('defaultScope'); + }); + + it('should be able to check custom scope name', () => { + const { Company } = getModels(); + + expect(Company.withScope('users')._scopeNames).to.include('users'); + }); + + it('should be able to check multiple custom scope names', () => { + const { Company } = getModels(); + + expect(Company.withScope('users', 'projects')._scopeNames).to.include.members([ + 'users', + 'projects', + ]); + }); + + it('should be able to merge two scoped includes', () => { + const { Company, Project, User } = getModels(); + + expect(Company.withScope('users', 'projects')._scope).to.deep.equal({ + include: [ + { model: User, association: Company.associations.users, as: 'users' }, + { model: Project, association: Company.associations.projects, as: 'projects' }, + ], + }); + }); + + it('should be able to keep original scope definition clean', () => { + const { Company, Project, User } = getModels(); + + expect(Company.withScope('projects', 'users', 'alsoUsers')._scope).to.deep.equal({ + include: [ + { model: Project, association: Company.associations.projects, as: 'projects' }, + { + model: User, + association: Company.associations.users, + as: 'users', + where: { something: 42 }, + }, + ], + }); + + expect(Company.options.scopes!.alsoUsers).to.deep.equal({ + include: [ + { + model: User, + association: Company.associations.users, + as: 'users', + where: { something: 42 }, + }, + ], + }); + + expect(Company.options.scopes!.users).to.deep.equal({ + include: [{ model: User, association: Company.associations.users, as: 'users' }], + }); + }); + + it('should be able to override the default scope', () => { + const { Company } = getModels(); + + expect(Company.withScope('somethingTrue')._scope).to.deep.equal({ + where: { + something: true, + somethingElse: 42, + }, + limit: 5, + }); + }); + + it('should be able to combine default with another scope', () => { + const { Company, Project } = getModels(); + + expect( + Company.withScope(['defaultScope', { method: ['actualValue', 11] }])._scope, + ).to.deep.equal({ + include: [{ model: Project, association: Company.associations.projects, as: 'projects' }], + where: { + [Op.and]: [{ active: true }, { other_value: 11 }], + }, + }); + }); + + describe('merging where clause', () => { + const testModelScopes = { + whereAttributeIs1: { + where: { + field: 1, + }, + }, + whereAttributeIs2: { + where: { + field: 2, + }, + }, + whereAttributeIs3: { + where: { + field: 3, + }, + }, + whereOtherAttributeIs4: { + where: { + otherField: 4, + }, + }, + whereOpAnd1: { + where: { + [Op.and]: [{ field: 1 }, { field: 1 }], + }, + }, + whereOpAnd2: { + where: { + [Op.and]: [{ field: 2 }, { field: 2 }], + }, + }, + whereOpAnd3: { + where: { + [Op.and]: [{ field: 3 }, { field: 3 }], + }, + }, + whereOpOr1: { + where: { + [Op.or]: [{ field: 1 }, { field: 1 }], + }, + }, + whereOpOr2: { + where: { + [Op.or]: [{ field: 2 }, { field: 2 }], + }, + }, + whereOpOr3: { + where: { + [Op.or]: [{ field: 3 }, { field: 3 }], + }, + }, + whereSequelizeWhere1: { + where: where(col('field'), Op.is, 1), + }, + whereSequelizeWhere2: { + where: where(col('field'), Op.is, 2), + }, + }; + + const vars = beforeEach2(() => { + const TestModel = sequelize.define( + 'TestModel', + {}, + { + scopes: testModelScopes, + }, + ); + + return { TestModel }; + }); + + describe('attributes', () => { + it('should group 2 similar attributes with an Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereAttributeIs1', 'whereAttributeIs2'])._scope; + const expected = { + where: { + [Op.and]: [{ field: 1 }, { field: 2 }], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group multiple similar attributes with an unique Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope([ + 'whereAttributeIs1', + 'whereAttributeIs2', + 'whereAttributeIs3', + ])._scope; + const expected = { + where: { + [Op.and]: [{ field: 1 }, { field: 2 }, { field: 3 }], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group different attributes with an Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereAttributeIs1', 'whereOtherAttributeIs4'])._scope; + const expected = { + where: { + [Op.and]: [{ field: 1 }, { otherField: 4 }], + }, + }; + expect(scope).to.deep.equal(expected); + }); + }); + + describe('and operators', () => { + it('should concatenate 2 Op.and into an unique one', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereOpAnd1', 'whereOpAnd2'])._scope; + const expected = { + where: { + [Op.and]: [{ field: 1 }, { field: 1 }, { field: 2 }, { field: 2 }], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should concatenate multiple Op.and into an unique one', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereOpAnd1', 'whereOpAnd2', 'whereOpAnd3'])._scope; + const expected = { + where: { + [Op.and]: [ + { field: 1 }, + { field: 1 }, + { field: 2 }, + { field: 2 }, + { field: 3 }, + { field: 3 }, + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + }); + + describe('or operators', () => { + it('should group 2 Op.or with an Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereOpOr1', 'whereOpOr2'])._scope; + const expected = { + where: { + [Op.and]: [ + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + { [Op.or]: [{ field: 2 }, { field: 2 }] }, + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group multiple Op.or with an unique Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope(['whereOpOr1', 'whereOpOr2', 'whereOpOr3'])._scope; + const expected = { + where: { + [Op.and]: [ + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + { [Op.or]: [{ field: 2 }, { field: 2 }] }, + { [Op.or]: [{ field: 3 }, { field: 3 }] }, + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group multiple Op.or and Op.and with an unique Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope([ + 'whereOpOr1', + 'whereOpOr2', + 'whereOpAnd1', + 'whereOpAnd2', + ])._scope; + const expected = { + where: { + [Op.and]: [ + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + { [Op.or]: [{ field: 2 }, { field: 2 }] }, + { field: 1 }, + { field: 1 }, + { field: 2 }, + { field: 2 }, + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group multiple Op.and and Op.or with an unique Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope([ + 'whereOpAnd1', + 'whereOpAnd2', + 'whereOpOr1', + 'whereOpOr2', + ])._scope; + const expected = { + where: { + [Op.and]: [ + { field: 1 }, + { field: 1 }, + { field: 2 }, + { field: 2 }, + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + { [Op.or]: [{ field: 2 }, { field: 2 }] }, + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + }); + + describe('sequelize where', () => { + it('should group 2 sequelize.where with an Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope([ + 'whereSequelizeWhere1', + 'whereSequelizeWhere2', + ])._scope; + const expected = { + where: { + [Op.and]: [where(col('field'), Op.is, 1), where(col('field'), Op.is, 2)], + }, + }; + expect(scope).to.deep.equal(expected); + }); + + it('should group 2 sequelize.where and other scopes with an Op.and', () => { + const { TestModel } = vars; + + const scope = TestModel.withScope([ + 'whereAttributeIs1', + 'whereOpAnd1', + 'whereOpOr1', + 'whereSequelizeWhere1', + ])._scope; + const expected = { + where: { + [Op.and]: [ + { field: 1 }, + { field: 1 }, + { field: 1 }, + { [Op.or]: [{ field: 1 }, { field: 1 }] }, + Sequelize.where(col('field'), Op.is, 1), + ], + }, + }; + expect(scope).to.deep.equal(expected); + }); + }); + }); + + it('should be able to use raw queries', () => { + const { Company } = getModels(); + + expect(Company.withScope([{ method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({ + where: literal('qux IN (SELECT foobar FROM some_sql_function(foo.bar))'), + }); + }); + + it('should override the default scope', () => { + const { Company, Project } = getModels(); + + expect( + Company.withScope(['defaultScope', { method: ['complexFunction', 'qux'] }])._scope, + ).to.deep.equal({ + include: [{ model: Project, association: Company.associations.projects, as: 'projects' }], + where: { + [Op.and]: [ + { active: true }, + literal('qux IN (SELECT foobar FROM some_sql_function(foo.bar))'), + ], + }, + }); + }); + + it("should emit an error for scopes that don't exist", () => { + const { Company } = getModels(); + + expect(() => { + Company.withScope('doesntexist'); + }).to.throw( + '"company.withScope()" has been called with an invalid scope: "doesntexist" does not exist.', + ); + }); + + it('should concatenate scope groups', () => { + const { Company, Project } = getModels(); + + expect(Company.withScope('groupByCompanyId', 'groupByProjectId')._scope).to.deep.equal({ + group: ['company.id', 'project.id'], + include: [{ model: Project, association: Company.associations.projects, as: 'projects' }], + }); + }); + }); + + describe('withoutScope', () => { + it('returns a model with no scope (including the default scope)', () => { + const { Company } = getModels(); + + expect(Company.withScope(null)._scope).to.be.empty; + expect(Company.withoutScope()._scope).to.be.empty; + // Yes, being unscoped is also a scope - this prevents inject defaultScope, when including a scoped model, see #4663 + expect(Company.withoutScope().scoped).to.be.true; + }); + + it('returns the same model no matter which variant it was called on', () => { + const { Company } = getModels(); + + assert(Company.withoutScope() === Company.withScope('somethingTrue').withoutScope()); + }); + + it('returns the same model if used with schema', () => { + const { Company } = getModels(); + + assert( + Company.withSchema('schema1').withoutScope() === + Company.withoutScope().withSchema('schema1'), + ); + }); + }); + + describe('withInitialScope', () => { + it('returns the initial model if no schema is defined', () => { + const { Company } = getModels(); + + assert(Company.withScope('somethingTrue').withInitialScope() === Company); + }); + + it('returns the a model with just the schema if one was defined is defined', () => { + const { Company } = getModels(); + + assert( + Company.withSchema('schema1').withInitialScope() === + Company.withInitialScope().withSchema('schema1'), + ); + }); + }); + + describe('addScope', () => { + it('works if the model does not have any initial scopes', () => { + const MyModel = sequelize.define('model'); + + expect(() => { + MyModel.addScope('anything', {}); + }).not.to.throw(); + }); + + it('allows me to add a new scope', () => { + const { Company, Project } = getModels(); + + expect(() => { + Company.withScope('newScope'); + }).to.throw( + '"company.withScope()" has been called with an invalid scope: "newScope" does not exist.', + ); + + Company.addScope('newScope', { + where: { + this: 'that', + }, + include: [{ model: Project }], + }); + + expect(Company.withScope('newScope')._scope).to.deep.equal({ + where: { + this: 'that', + }, + include: [{ model: Project, association: Company.associations.projects, as: 'projects' }], + }); + }); + + it('warns me when overriding an existing scope', () => { + const { Company } = getModels(); + + expect(() => { + Company.addScope('somethingTrue', {}); + }).to.throw( + 'The scope somethingTrue already exists. Pass { override: true } as options to silence this error', + ); + }); + + it('allows me to override an existing scope', () => { + const { Company } = getModels(); + + Company.addScope( + 'somethingTrue', + { + where: { + something: false, + }, + }, + { override: true }, + ); + + expect(Company.withScope('somethingTrue')._scope).to.deep.equal({ + where: { + something: false, + }, + }); + }); + + it('warns me when overriding an existing default scope', () => { + const { Company } = getModels(); + + expect(() => { + Company.addScope('defaultScope', {}); + }).to.throw( + 'The scope defaultScope already exists. Pass { override: true } as options to silence this error', + ); + }); + + it('should not warn if default scope is not defined', () => { + const MyModel = sequelize.define('model'); + + expect(() => { + MyModel.addScope('defaultScope', {}); + }).not.to.throw(); + }); + + it('allows me to override a default scope', () => { + const { Company, Project } = getModels(); + + Company.addScope( + 'defaultScope', + { + include: [{ model: Project }], + }, + { override: true }, + ); + + expect(Company._scope).to.deep.equal({ + include: [{ model: Project }], + }); + }); + + it('should keep exclude and include attributes', () => { + const { Company } = getModels(); + + Company.addScope('newIncludeScope', { + attributes: { + include: ['foobar'], + exclude: ['createdAt'], + }, + }); + + expect(Company.withScope('newIncludeScope')._scope).to.deep.equal({ + attributes: { + include: ['foobar'], + exclude: ['createdAt'], + }, + }); + }); + + it('should be able to merge scopes with the same include', () => { + const { Company, Project } = getModels(); + + Company.addScope('project', { + include: [{ model: Project, where: { something: false, somethingElse: 99 } }], + }); + Company.addScope('alsoProject', { + include: [{ model: Project, where: { something: true }, limit: 1 }], + }); + expect(Company.withScope(['project', 'alsoProject'])._scope).to.deep.equal({ + include: [ + { + model: Project, + where: { + [Op.and]: [{ something: false, somethingElse: 99 }, { something: true }], + }, + association: Company.associations.projects, + as: 'projects', + limit: 1, + }, + ], + }); + }); + }); + + describe('_injectScope', () => { + it('should be able to merge scope and where', () => { + const MyModel = sequelize.define('model'); + MyModel.addScope('defaultScope', { + where: { something: true, somethingElse: 42 }, + limit: 15, + offset: 3, + }); + + const options = { + where: { + something: false, + }, + limit: 9, + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options).to.deep.equal({ + where: { + [Op.and]: [{ something: true, somethingElse: 42 }, { something: false }], + }, + limit: 9, + offset: 3, + }); + }); + + it('should be able to merge scope and having', () => { + const MyModel = sequelize.define('model'); + MyModel.addScope('defaultScope', { + having: { something: true, somethingElse: 42 }, + limit: 15, + offset: 3, + }); + + const options = { + having: { + something: false, + }, + limit: 9, + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options).to.deep.equal({ + having: { + [Op.and]: [{ something: true, somethingElse: 42 }, { something: false }], + }, + limit: 9, + offset: 3, + }); + }); + + it('should be able to merge scoped include', () => { + const { Project } = getModels(); + + const MyModel = sequelize.define('model'); + MyModel.hasMany(Project); + + MyModel.addScope('defaultScope', { + include: [{ model: Project, where: { something: false, somethingElse: 99 } }], + }); + + const options = { + include: [{ model: Project, where: { something: true }, limit: 1 }], + }; + + MyModel._conformIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options.include).to.deep.equal([ + { + model: Project, + where: { + [Op.and]: [{ something: false, somethingElse: 99 }, { something: true }], + }, + association: MyModel.associations.projects, + as: 'projects', + limit: 1, + }, + ]); + }); + + it('should be able to merge aliased includes with the same model', () => { + const { User } = getModels(); + + const MyModel = sequelize.define('model'); + MyModel.hasMany(User, { as: 'someUser' }); + MyModel.hasMany(User, { as: 'otherUser' }); + + MyModel.addScope('defaultScope', { + include: [{ model: User, as: 'someUser' }], + }); + + const options = { + include: [{ model: User, as: 'otherUser' }], + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options.include).to.have.length(2); + expect(options.include[0]).to.deep.equal({ + model: User, + as: 'someUser', + association: MyModel.associations.someUser, + }); + expect(options.include[1]).to.deep.equal({ + model: User, + as: 'otherUser', + association: MyModel.associations.otherUser, + }); + }); + + it('should be able to merge scoped include with include in find', () => { + const { Project, User } = getModels(); + + const MyModel = sequelize.define('model'); + MyModel.hasMany(Project); + MyModel.hasMany(User); + + MyModel.addScope('defaultScope', { + include: [{ model: Project, where: { something: false } }], + }); + + const options = { + include: [{ model: User, where: { something: true } }], + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options.include).to.have.length(2); + expect(options.include[0]).to.deep.equal({ + model: Project, + as: 'projects', + association: MyModel.associations.projects, + where: { something: false }, + }); + expect(options.include[1]).to.deep.equal({ + model: User, + as: 'users', + association: MyModel.associations.users, + where: { something: true }, + }); + }); + + describe('include all', () => { + it('scope with all', () => { + const { User, Project } = getModels(); + + const MyModel = sequelize.define('model'); + MyModel.hasMany(User); + MyModel.hasMany(Project); + MyModel.addScope('defaultScope', { + include: [{ all: true }], + }); + + const options = { + include: [{ model: User, where: { something: true } }], + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options.include).to.have.length(2); + expect(options.include[0]).to.deep.equal({ + model: User, + as: 'users', + association: MyModel.associations.users, + where: { something: true }, + }); + expect(options.include[1]).to.deep.equal({ + model: Project, + as: 'projects', + association: MyModel.associations.projects, + }); + }); + + it('options with all', () => { + const { Project, User } = getModels(); + + const MyModel = sequelize.define('model'); + MyModel.hasMany(User); + MyModel.hasMany(Project); + MyModel.addScope('defaultScope', { + include: [{ model: User, where: { something: true } }], + }); + + const options = { + include: [{ all: true }], + }; + + MyModel._normalizeIncludes(options, MyModel); + MyModel._injectScope(options); + + expect(options.include).to.have.length(2); + expect(options.include[0]).to.deep.equal({ + model: User, + as: 'users', + association: MyModel.associations.users, + where: { something: true }, + }); + expect(options.include[1]).to.deep.equal({ + model: Project, + as: 'projects', + association: MyModel.associations.projects, + }); + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/underscored.test.js b/packages/core/test/unit/model/underscored.test.js new file mode 100644 index 000000000000..a53cfc2fafda --- /dev/null +++ b/packages/core/test/unit/model/underscored.test.js @@ -0,0 +1,123 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('options.underscored', () => { + beforeEach(function () { + this.N = this.sequelize.define( + 'N', + { + id: { + type: DataTypes.STRING(10), + primaryKey: true, + field: 'n_id', + }, + }, + { + underscored: true, + }, + ); + + this.M = this.sequelize.define( + 'M', + { + id: { + type: DataTypes.STRING(20), + primaryKey: true, + field: 'm_id', + }, + }, + { + underscored: true, + }, + ); + this.NM = this.sequelize.define('NM', {}); + }); + + it('should properly set field when defining', function () { + expect(this.N.getAttributes().id.field).to.equal('n_id'); + expect(this.M.getAttributes().id.field).to.equal('m_id'); + }); + + it('hasOne does not override already defined field', function () { + this.N.modelDefinition.rawAttributes.mId = { + type: DataTypes.STRING(20), + field: 'n_m_id', + }; + this.N.modelDefinition.refreshAttributes(); + + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + this.M.hasOne(this.N, { foreignKey: 'mId' }); + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + }); + + it('belongsTo does not override already defined field', function () { + this.N.modelDefinition.rawAttributes.mId = { + type: DataTypes.STRING(20), + field: 'n_m_id', + }; + this.N.modelDefinition.refreshAttributes(); + + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + this.N.belongsTo(this.M, { foreignKey: 'mId' }); + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + }); + + it('hasOne/belongsTo does not override already defined field', function () { + this.N.modelDefinition.rawAttributes.mId = { + type: DataTypes.STRING(20), + field: 'n_m_id', + }; + this.N.modelDefinition.refreshAttributes(); + + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + this.N.belongsTo(this.M, { foreignKey: 'mId' }); + this.M.hasOne(this.N, { foreignKey: 'mId' }); + expect(this.N.getAttributes().mId.field).to.equal('n_m_id'); + }); + + it('hasMany does not override already defined field', function () { + this.M.modelDefinition.rawAttributes.nId = { + type: DataTypes.STRING(20), + field: 'nana_id', + }; + this.M.modelDefinition.refreshAttributes(); + + expect(this.M.getAttributes().nId.field).to.equal('nana_id'); + + this.N.hasMany(this.M, { foreignKey: 'nId' }); + this.M.belongsTo(this.N, { foreignKey: 'nId' }); + + expect(this.M.getAttributes().nId.field).to.equal('nana_id'); + }); + + it('belongsToMany does not override already defined field', function () { + this.NM = this.sequelize.define( + 'NM', + { + n_id: { + type: DataTypes.STRING(10), + field: 'nana_id', + }, + m_id: { + type: DataTypes.STRING(20), + field: 'mama_id', + }, + }, + { + underscored: true, + }, + ); + + this.N.belongsToMany(this.M, { through: this.NM, foreignKey: 'n_id', otherKey: 'm_id' }); + + expect(this.NM.getAttributes().n_id.field).to.equal('nana_id'); + expect(this.NM.getAttributes().m_id.field).to.equal('mama_id'); + }); + }); +}); diff --git a/packages/core/test/unit/model/update.test.js b/packages/core/test/unit/model/update.test.js new file mode 100644 index 000000000000..2232b68ba723 --- /dev/null +++ b/packages/core/test/unit/model/update.test.js @@ -0,0 +1,117 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('method update', () => { + before(function () { + this.User = current.define('User', { + name: DataTypes.STRING, + secretValue: DataTypes.INTEGER, + }); + }); + + beforeEach(function () { + this.stubUpdate = sinon.stub(current.queryInterface, 'bulkUpdate').resolves([]); + this.updates = { name: 'Batman', secretValue: '7' }; + this.cloneUpdates = { ...this.updates }; + }); + + afterEach(function () { + this.stubUpdate.restore(); + }); + + afterEach(function () { + delete this.updates; + delete this.cloneUpdates; + }); + + describe('properly clones input values', () => { + 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', 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', async function () { + const Where = function () { + this.secretValue = '1'; + }; + + await expect(this.User.update(this.updates, { where: new Where() })).to.be.rejected; + }); + }); + + describe('Update with multiple models to the same table', () => { + before(function () { + this.Model1 = current.define( + 'Model1', + { + value: DataTypes.INTEGER, + name: DataTypes.STRING, + isModel2: DataTypes.BOOLEAN, + model1ExclusiveData: DataTypes.STRING, + }, + { + tableName: 'model_table', + }, + ); + + this.Model2 = current.define( + 'Model2', + { + value: DataTypes.INTEGER, + name: DataTypes.STRING, + }, + { + tableName: 'model_table', + }, + ); + }); + + beforeEach(function () { + this.stubQuery = sinon.stub(current, 'queryRaw').resolves([]); + }); + + afterEach(function () { + this.stubQuery.restore(); + }); + + it('updates model1 using model1 model', async function () { + await this.Model1.update( + { + name: 'other name', + model1ExclusiveData: 'only I can update this field', + }, + { + where: { value: 1 }, + }, + ); + expect(this.stubQuery.lastCall.lastArg.model).to.eq(this.Model1); + }); + + it('updates model2 using model2 model', async function () { + await this.Model2.update( + { + name: 'other name', + }, + { + where: { value: 2 }, + }, + ); + expect(this.stubQuery.lastCall.lastArg.model).to.eq(this.Model2); + }); + }); +}); diff --git a/packages/core/test/unit/model/upsert.test.js b/packages/core/test/unit/model/upsert.test.js new file mode 100644 index 000000000000..21162e1e55af --- /dev/null +++ b/packages/core/test/unit/model/upsert.test.js @@ -0,0 +1,105 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); + +const current = Support.sequelize; +const sinon = require('sinon'); +const { DataTypes, Sequelize } = require('@sequelize/core'); + +describe(Support.getTestDialectTeaser('Model'), () => { + if (current.dialect.supports.upserts) { + describe('method upsert', () => { + before(function () { + this.User = current.define('User', { + name: DataTypes.STRING, + virtualValue: { + type: DataTypes.VIRTUAL, + set(val) { + this.value = val; + }, + get() { + return this.value; + }, + }, + value: DataTypes.STRING, + secretValue: { + type: DataTypes.INTEGER, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + field: 'created_at', + }, + }); + + this.UserNoTime = current.define( + 'UserNoTime', + { + name: DataTypes.STRING, + }, + { + timestamps: false, + }, + ); + }); + + beforeEach(function () { + this.query = sinon.stub(current, 'queryRaw').resolves(); + this.stub = sinon + .stub(current.queryInterface, 'upsert') + .resolves([this.User.build(), true]); + }); + + afterEach(function () { + this.query.restore(); + this.stub.restore(); + }); + + 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', async function () { + await this.User.upsert({ + name: 'Young Cat', + virtualValue: '999', + }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ + 'name', + 'value', + 'created_at', + 'updatedAt', + ]); + }); + + it('creates new record with timestamps disabled', async function () { + await this.UserNoTime.upsert({ + name: 'Young Cat', + }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal(['name']); + }); + + it('updates all changed fields by default', async function () { + await this.User.upsert({ + name: 'Old Cat', + virtualValue: '111', + }); + + expect(Object.keys(this.stub.getCall(0).args[2])).to.deep.equal([ + 'name', + 'value', + 'updatedAt', + ]); + }); + }); + } +}); diff --git a/packages/core/test/unit/model/validation.test.js b/packages/core/test/unit/model/validation.test.js new file mode 100644 index 000000000000..07d00636133a --- /dev/null +++ b/packages/core/test/unit/model/validation.test.js @@ -0,0 +1,847 @@ +'use strict'; + +const { expect } = require('chai'); +const sinon = require('sinon'); +const { DataTypes, Op, Sequelize } = require('@sequelize/core'); +const { rand, sequelize } = require('../../support'); + +const dialect = sequelize.dialect; + +describe('InstanceValidator', () => { + describe('validations', () => { + const checks = { + is: { + spec: { args: ['[a-z]', 'i'] }, + fail: '0', + pass: 'a', + }, + not: { + spec: { args: ['[a-z]', 'i'] }, + fail: 'a', + pass: '0', + }, + isEmail: { + fail: 'a', + pass: 'abc@abc.com', + }, + isUrl: { + fail: 'abc', + pass: 'http://abc.com', + }, + isIP: { + fail: 'abc', + pass: '129.89.23.1', + }, + isIPv4: { + fail: 'abc', + pass: '129.89.23.1', + }, + isIPv6: { + fail: '1111:2222:3333::5555:', + pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156', + }, + isAlpha: { + stringOrBoolean: true, + spec: { args: 'en-GB' }, + fail: '012', + pass: 'abc', + }, + isAlphanumeric: { + stringOrBoolean: true, + spec: { args: 'en-GB' }, + fail: '_abc019', + pass: 'abc019', + }, + isNumeric: { + fail: 'abc', + pass: '019', + }, + isInt: { + fail: '9.2', + pass: '-9', + }, + isLowercase: { + fail: 'AB', + pass: 'ab', + }, + isUppercase: { + fail: 'ab', + pass: 'AB', + }, + isDecimal: { + fail: 'a', + pass: '0.2', + }, + isFloat: { + fail: 'a', + pass: '9.2', + }, + isNull: { + fail: '0', + pass: null, + }, + notEmpty: { + fail: ' ', + pass: 'a', + }, + equals: { + spec: { args: 'bla bla bla' }, + fail: 'bla', + pass: 'bla bla bla', + }, + contains: { + spec: { args: 'bla' }, + fail: 'la', + pass: '0bla23', + }, + notContains: { + spec: { args: 'bla' }, + fail: '0bla23', + pass: 'la', + }, + regex: { + spec: { args: ['[a-z]', 'i'] }, + fail: '0', + pass: 'a', + }, + notRegex: { + spec: { args: ['[a-z]', 'i'] }, + fail: 'a', + pass: '0', + }, + len: { + spec: { args: [2, 4] }, + fail: ['1', '12345'], + pass: ['12', '123', '1234'], + raw: true, + }, + len$: { + spec: [2, 4], + fail: ['1', '12345'], + pass: ['12', '123', '1234'], + raw: true, + }, + isUUID: { + spec: { args: 4 }, + fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479', + pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + }, + isDate: { + fail: 'not a date', + pass: '2011-02-04', + }, + isAfter: { + spec: { args: '2011-11-05' }, + fail: '2011-11-04', + pass: '2011-11-06', + }, + isBefore: { + spec: { args: '2011-11-05' }, + fail: '2011-11-06', + pass: '2011-11-04', + }, + isIn: { + spec: { args: 'abcdefghijk' }, + fail: 'ghik', + pass: 'ghij', + }, + notIn: { + spec: { args: 'abcdefghijk' }, + fail: 'ghij', + pass: 'ghik', + }, + max: { + spec: { args: 23 }, + fail: '24', + pass: '23', + }, + max$: { + spec: 23, + fail: '24', + pass: '23', + }, + min: { + spec: { args: 23 }, + fail: '22', + pass: '23', + }, + min$: { + spec: 23, + fail: '22', + pass: '23', + }, + isCreditCard: { + fail: '401288888888188f', + pass: '4012888888881881', + }, + }; + + 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}"`, async function () { + const validations = {}; + const message = `${validator}(${failingValue})`; + + validations[validator] = validatorDetails.spec || {}; + validations[validator].msg = message; + + const UserFail = this.sequelize.define(`User${rand()}`, { + name: { + type: DataTypes.STRING, + validate: validations, + }, + }); + + const failingUser = UserFail.build({ name: 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); + }); + }; + + const 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}"`, async function () { + const validations = {}; + const message = `${validator}(${succeedingValue})`; + + validations[validator] = validatorDetails.spec || {}; + + switch (type) { + case 'msg': { + validations[validator].msg = message; + + break; + } + + case 'args': { + validations[validator].args = validations[validator].args || true; + validations[validator].msg = message; + + break; + } + + case 'true': { + validations[validator] = true; + + break; + } + // No default + } + + const UserSuccess = this.sequelize.define(`User${rand()}`, { + name: { + type: DataTypes.STRING, + validate: validations, + }, + }); + const successfulUser = UserSuccess.build({ name: succeedingValue }); + await expect(successfulUser.validate()).not.to.be.rejected; + }); + }; + + for (let validator in checks) { + if (checks.hasOwnProperty(validator)) { + validator = validator.replace(/\$$/, ''); + const validatorDetails = checks[validator]; + + if (!validatorDetails.raw) { + validatorDetails.fail = Array.isArray(validatorDetails.fail) + ? validatorDetails.fail + : [validatorDetails.fail]; + validatorDetails.pass = Array.isArray(validatorDetails.pass) + ? validatorDetails.pass + : [validatorDetails.pass]; + } + + for (let i = 0; i < validatorDetails.fail.length; i++) { + applyFailTest(validatorDetails, i, validator); + } + + for (let i = 0; i < validatorDetails.pass.length; i++) { + applyPassTest(validatorDetails, i, validator); + applyPassTest(validatorDetails, i, validator, 'msg'); + applyPassTest(validatorDetails, i, validator, 'args'); + if (validatorDetails.stringOrBoolean || validatorDetails.spec === undefined) { + applyPassTest(validatorDetails, i, validator, 'true'); + } + } + } + } + }); + + if (dialect.supports.dataTypes.DECIMAL) { + describe('DECIMAL validator', () => { + let User; + + before(function () { + User = sequelize.define('user', { + decimal: DataTypes.DECIMAL(10, 2), + }); + + this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + it('should allow decimal as a string', async () => { + await expect( + User.create({ + decimal: '12.6', + }), + ).not.to.be.rejected; + }); + + it('should allow decimal big numbers as a string', async () => { + await expect( + User.create({ + decimal: '2321312301230128391820831289123012', + }), + ).not.to.be.rejected; + }); + + it('should allow decimal as scientific notation', async () => { + await Promise.all([ + expect( + User.create({ + decimal: '2321312301230128391820e219', + }), + ).not.to.be.rejected, + expect( + User.create({ + decimal: '2321312301230128391820e+219', + }), + ).not.to.be.rejected, + ]); + }); + }); + } + + describe('datatype validations', () => { + let User; + + before(function () { + User = sequelize.define('user', { + integer: DataTypes.INTEGER, + name: DataTypes.STRING, + awesome: DataTypes.BOOLEAN, + uid: DataTypes.UUID, + date: DataTypes.DATE, + }); + + this.stub = sinon.stub(sequelize, 'queryRaw').callsFake(async () => [User.build({}), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + describe('should not throw', () => { + describe('create', () => { + it('should allow number as a string', async () => { + await expect( + User.create({ + integer: '12', + }), + ).not.to.be.rejected; + }); + + it('should allow dates as a string', async () => { + await expect( + User.findOne({ + where: { + date: '2000-12-16', + }, + }), + ).not.to.be.rejected; + }); + }); + + describe('findAll', () => { + it('should allow $in', async () => { + if (!dialect.supports.dataTypes.ARRAY) { + return; + } + + await expect( + User.findAll({ + where: { + name: { + [Op.like]: { + [Op.any]: ['foo%', 'bar%'], + }, + }, + }, + }), + ).not.to.be.rejected; + }); + + it('should allow $like for uuid if cast', async () => { + await expect( + User.findAll({ + where: { + 'uid::text': { + [Op.like]: '12345678%', + }, + }, + }), + ).not.to.be.rejected; + }); + }); + }); + + describe('should throw validationError', () => { + describe('create', () => { + it('should throw when passing string', async () => { + const error = await expect( + User.create({ + integer: 'jan', + }), + ).to.be.rejectedWith(Sequelize.ValidationError, `'jan' is not a valid integer`); + + expect(error) + .to.have.property('errors') + .that.is.an('array') + .with.lengthOf(1) + .and.with.property(0) + .that.is.an.instanceOf(Sequelize.ValidationErrorItem) + .and.include({ + type: 'Validation error', + path: 'integer', + value: 'jan', + validatorKey: 'INTEGER validator', + }); + }); + + it('should throw when passing decimal', async () => { + await expect( + User.create({ + integer: 4.5, + }), + ) + .to.be.rejectedWith(Sequelize.ValidationError) + .which.eventually.have.property('errors') + .that.is.an('array') + .with.lengthOf(1) + .and.with.property(0) + .that.is.an.instanceOf(Sequelize.ValidationErrorItem) + .and.include({ + type: 'Validation error', + path: 'integer', + value: 4.5, + validatorKey: 'INTEGER validator', + }); + }); + }); + + describe('update', () => { + it('should throw when passing string', async () => { + await expect(User.update({ integer: 'jan' }, { where: {} })) + .to.be.rejectedWith(Sequelize.ValidationError) + .which.eventually.have.property('errors') + .that.is.an('array') + .with.lengthOf(1) + .and.with.property(0) + .that.is.an.instanceOf(Sequelize.ValidationErrorItem) + .and.include({ + type: 'Validation error', + path: 'integer', + value: 'jan', + validatorKey: 'INTEGER validator', + }); + }); + + it('should throw when passing decimal', async () => { + await expect( + User.update( + { + integer: 4.5, + }, + { where: {} }, + ), + ) + .to.be.rejectedWith(Sequelize.ValidationError) + .which.eventually.have.property('errors') + .that.is.an('array') + .with.lengthOf(1) + .and.with.property(0) + .that.is.an.instanceOf(Sequelize.ValidationErrorItem) + .and.include({ + type: 'Validation error', + path: 'integer', + value: 4.5, + validatorKey: 'INTEGER validator', + }); + }); + }); + }); + }); + + describe('custom validation functions', () => { + let User; + + before(function () { + User = sequelize.define( + 'user', + { + integer: { + type: DataTypes.INTEGER, + validate: { + customFn(val, next) { + if (val < 0) { + next('integer must be greater or equal zero'); + } else { + next(); + } + }, + }, + }, + name: DataTypes.STRING, + }, + { + validate: { + customFn() { + if (this.get('name') === 'error') { + throw new Error('Error from model validation promise'); + } + }, + }, + }, + ); + + this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + describe('should not throw', () => { + describe('create', () => { + it('custom validation functions are successful', async () => { + await expect( + User.create({ + integer: 1, + name: 'noerror', + }), + ).not.to.be.rejected; + }); + }); + + describe('update', () => { + it('custom validation functions are successful', async () => { + await expect( + User.update( + { + integer: 1, + name: 'noerror', + }, + { where: {} }, + ), + ).not.to.be.rejected; + }); + }); + }); + + describe('should throw validationerror', () => { + describe('create', () => { + it('custom attribute validation function fails', async () => { + await expect( + User.create({ + integer: -1, + }), + ).to.be.rejectedWith(Sequelize.ValidationError); + }); + + 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', async () => { + await expect( + User.update( + { + integer: -1, + }, + { where: {} }, + ), + ).to.be.rejectedWith(Sequelize.ValidationError); + }); + + it('when custom model validation function fails', async () => { + await expect( + User.update( + { + name: 'error', + }, + { where: {} }, + ), + ).to.be.rejectedWith(Sequelize.ValidationError); + }); + }); + }); + }); + + describe('custom validation functions returning promises', () => { + let User; + + before(function () { + User = sequelize.define( + 'user', + { + name: DataTypes.STRING, + }, + { + validate: { + async customFn() { + if (this.get('name') === 'error') { + throw new Error('Error from model validation promise'); + } + }, + }, + }, + ); + + this.stub = sinon.stub(sequelize, 'queryRaw').resolves([User.build(), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + describe('should not throw', () => { + describe('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', async () => { + await expect( + User.update( + { + name: 'noerror', + }, + { where: {} }, + ), + ).not.to.be.rejected; + }); + }); + }); + + describe('should throw validationerror', () => { + describe('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', async () => { + await expect( + User.update( + { + name: 'error', + }, + { where: {} }, + ), + ).to.be.rejectedWith(Sequelize.ValidationError); + }); + }); + }); + }); + + describe('custom validation functions and null values', () => { + before(function () { + this.customValidator = sinon.fake(function (value) { + if (value === null && this.integer !== 10) { + throw new Error("name can't be null unless integer is 10"); + } + }); + }); + + describe('with allowNull set to true', () => { + before(function () { + this.User = sequelize.define('user', { + integer: DataTypes.INTEGER, + name: { + type: DataTypes.STRING, + allowNull: true, + validate: { + customValidator: this.customValidator, + }, + }, + }); + + this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + describe('should call validator and not throw', () => { + beforeEach(function () { + this.customValidator.resetHistory(); + }); + + it('on create', async function () { + await expect( + this.User.create({ + integer: 10, + name: null, + }), + ).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; + }); + + it('on update', async function () { + await expect( + this.User.update( + { + integer: 10, + name: null, + }, + { where: {} }, + ), + ).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; + }); + }); + + describe('should call validator and throw ValidationError', () => { + beforeEach(function () { + this.customValidator.resetHistory(); + }); + + it('on create', async function () { + await expect( + this.User.create({ + integer: 11, + name: null, + }), + ).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; + }); + + it('on update', async function () { + await expect( + this.User.update( + { + integer: 11, + name: null, + }, + { where: {} }, + ), + ).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; + }); + }); + }); + + describe('with allowNull set to false', () => { + before(function () { + this.User = sequelize.define('user', { + integer: DataTypes.INTEGER, + name: { + type: DataTypes.STRING, + allowNull: false, + validate: { + customValidator: this.customValidator, + }, + }, + }); + + this.stub = sinon.stub(sequelize, 'queryRaw').resolves([this.User.build(), 1]); + }); + + after(function () { + this.stub.restore(); + }); + + describe('should not call validator and throw ValidationError', () => { + beforeEach(function () { + this.customValidator.resetHistory(); + }); + + it('on create', async function () { + await expect( + this.User.create({ + integer: 99, + name: null, + }), + ).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; + }); + + it('on update', async function () { + await expect( + this.User.update( + { + integer: 99, + name: null, + }, + { where: {} }, + ), + ).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; + }); + }); + + describe('should call validator and not throw', () => { + beforeEach(function () { + this.customValidator.resetHistory(); + }); + + it('on create', async function () { + await expect( + this.User.create({ + integer: 99, + name: 'foo', + }), + ).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; + }); + + it('on update', async function () { + await expect( + this.User.update( + { + integer: 99, + name: 'foo', + }, + { where: {} }, + ), + ).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; + }); + }); + }); + }); +}); diff --git a/packages/core/test/unit/model/variants.test.ts b/packages/core/test/unit/model/variants.test.ts new file mode 100644 index 000000000000..4fd8d2ece6cd --- /dev/null +++ b/packages/core/test/unit/model/variants.test.ts @@ -0,0 +1,23 @@ +import { literal } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('Model.getInitialModel', () => { + it('always returns the initial model', () => { + const User = sequelize.define( + 'User', + {}, + { + scopes: { + scope1: { + where: literal(''), + }, + }, + }, + ); + + expect(User.withSchema('abc').getInitialModel()).to.eq(User); + expect(User.withSchema('abc').withScope('scope1').getInitialModel()).to.eq(User); + expect(User.withScope('scope1').getInitialModel()).to.eq(User); + }); +}); diff --git a/packages/core/test/unit/pool.test.ts b/packages/core/test/unit/pool.test.ts new file mode 100644 index 000000000000..99b2584c9796 --- /dev/null +++ b/packages/core/test/unit/pool.test.ts @@ -0,0 +1,300 @@ +import type { AbstractConnection, AbstractDialect, Sequelize } from '@sequelize/core'; +import { ReplicationPool } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/replication-pool.js'; +import type { PostgresDialect } from '@sequelize/postgres'; +import { expect } from 'chai'; +import { Pool } from 'sequelize-pool'; +import type { SinonSandbox, SinonStub } from 'sinon'; +import sinon from 'sinon'; +import type { DialectConnectionConfigs } from '../config/config'; +import { + createSequelizeInstance, + getSqliteDatabasePath, + getTestDialect, + sequelize, +} from '../support'; + +const dialectName = getTestDialect(); + +describe('sequelize.pool', () => { + describe('init', () => { + let sandbox: SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('does not initialize a readOnly pool without replication', () => { + const sequelize2 = createSequelizeInstance({ + replication: null, + }); + + expect(sequelize2.pool).to.be.instanceOf(ReplicationPool); + expect(sequelize2.pool.read).to.be.null; + expect(sequelize2.pool.write).to.be.instanceOf(Pool); + }); + + it('initializes a readOnly pool with replication', () => { + const connectionOptions = sequelize.options.replication.write; + + const sequelize2 = createSequelizeInstance({ + replication: { + write: connectionOptions, + read: [connectionOptions, connectionOptions], + }, + }); + + expect(sequelize2.pool).to.be.instanceOf(ReplicationPool); + expect(sequelize2.pool.read).to.be.instanceOf(Pool); + expect(sequelize2.pool.write).to.be.instanceOf(Pool); + }); + }); + + describe('acquire', () => { + let sequelize2: Sequelize; + let sandbox: SinonSandbox; + + beforeEach(() => { + const connection = {}; + sequelize2 = createSequelizeInstance({ + databaseVersion: sequelize.dialect.minimumDatabaseVersion, + }); + sandbox = sinon.createSandbox(); + sandbox.stub(sequelize2.dialect.connectionManager, 'connect').resolves(connection); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('allows the beforeConnect hook to modify the connection configuration', async () => { + if (dialectName !== 'postgres') { + return; + } + + const user = Math.random().toString(); + const password = Math.random().toString(); + + const typedSequelize = sequelize2 as Sequelize; + + typedSequelize.hooks.addListener('beforeConnect', config => { + config.user = user; + config.password = password; + }); + + await sequelize2.pool.acquire(); + + expect(sequelize2.dialect.connectionManager.connect).to.have.been.calledWith({ + ...sequelize2.options.replication.write, + password, + user, + }); + }); + + it('should call afterConnect', async () => { + const spy = sinon.spy(); + sequelize2.hooks.addListener('afterConnect', spy); + + const connection = await sequelize2.pool.acquire(); + + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(connection); + expect(spy.firstCall.args[1]).to.deep.equal(sequelize2.options.replication.write); + }); + + it('round robins calls to the read pool', async () => { + // TODO https://github.com/sequelize/sequelize/issues/15150 - use pool ID instead + const replica1Overrides: DialectConnectionConfigs = { + postgres: { + host: 'replica1', + }, + mssql: { + server: 'replica1', + }, + mysql: { + host: 'replica1', + }, + sqlite3: { + storage: getSqliteDatabasePath('replica1.db'), + }, + db2: { + database: 'replica1', + }, + mariadb: { + host: 'replica1', + }, + ibmi: { + dataSourceName: 'replica1', + }, + snowflake: { + account: 'replica1', + }, + }; + + const replica2Overrides: DialectConnectionConfigs = { + postgres: { + host: 'replica2', + }, + mssql: { + server: 'replica2', + }, + mysql: { + host: 'replica2', + }, + sqlite3: { + storage: getSqliteDatabasePath('replica2.db'), + }, + db2: { + database: 'replica2', + }, + mariadb: { + host: 'replica2', + }, + ibmi: { + dataSourceName: 'replica2', + }, + snowflake: { + account: 'replica2', + }, + }; + + const connectionOptions = sequelize.options.replication.write; + const sequelize3 = createSequelizeInstance({ + pool: { + max: 5, + }, + replication: { + write: connectionOptions, + read: [ + { ...connectionOptions, ...replica1Overrides[dialectName] }, + { ...connectionOptions, ...replica2Overrides[dialectName] }, + ], + }, + }); + + const connectionManager = sequelize3.dialect.connectionManager; + + const connection = {}; + const connectStub = sandbox + .stub(sequelize3.dialect.connectionManager, 'connect') + .resolves(connection); + sandbox.stub(connectionManager, 'disconnect').resolves(); + sandbox + .stub(sequelize3, 'fetchDatabaseVersion') + .resolves(sequelize3.dialect.minimumDatabaseVersion); + + const getConnection = async () => { + return sequelize3.pool.acquire({ + type: 'read', + useMaster: false, + }); + }; + + await getConnection(); + await getConnection(); + await getConnection(); + expect(connectStub.callCount).to.equal(3); + + const calls = connectStub.getCalls(); + expect(calls[0].args[0]).to.deep.contain(replica1Overrides[dialectName]); + expect(calls[1].args[0]).to.deep.contain(replica2Overrides[dialectName]); + expect(calls[2].args[0]).to.deep.contain(replica1Overrides[dialectName]); + }); + + it('should allow forced reads from the write pool', async () => { + const writeOverride: DialectConnectionConfigs = { + postgres: { + host: 'write', + }, + mssql: { + server: 'write', + }, + mysql: { + host: 'write', + }, + sqlite3: { + storage: getSqliteDatabasePath('write.db'), + }, + db2: { + database: 'write', + }, + mariadb: { + host: 'write', + }, + ibmi: { + dataSourceName: 'write', + }, + snowflake: { + account: 'write', + }, + }; + + const connectionOptions = sequelize.options.replication.write; + const sequelize3 = createSequelizeInstance({ + databaseVersion: sequelize.dialect.minimumDatabaseVersion, + replication: { + write: { ...connectionOptions, ...writeOverride[dialectName] }, + read: [connectionOptions], + }, + }); + + const res: AbstractConnection = {}; + + const connectionManager = sequelize3.dialect.connectionManager; + const connectStub = sandbox.stub(connectionManager, 'connect').resolves(res); + sandbox.stub(connectionManager, 'disconnect').resolves(); + + await sequelize3.pool.acquire({ + type: 'read', + useMaster: true, + }); + + expect(connectStub).to.have.been.calledOnce; + const calls = connectStub.getCalls(); + expect(calls[0].args[0]).to.deep.contain(writeOverride[dialectName]); + }); + }); + + describe('destroy', () => { + let sequelize2: Sequelize; + let connectStub: SinonStub; + let disconnectStub: SinonStub; + + beforeEach(() => { + const connection = {}; + sequelize2 = createSequelizeInstance({ + databaseVersion: sequelize.dialect.minimumDatabaseVersion, + }); + connectStub = sinon + .stub(sequelize2.dialect.connectionManager, 'connect') + .resolves(connection); + disconnectStub = sinon.stub(sequelize2.dialect.connectionManager, 'disconnect'); + }); + + afterEach(() => { + connectStub.reset(); + disconnectStub.reset(); + }); + + it('should call beforeDisconnect and afterDisconnect', async () => { + const connection = await sequelize2.pool.acquire(); + + const beforeDisconnect = sinon.spy(); + const afterDisconnect = sinon.spy(); + + sequelize2.hooks.addListener('beforeDisconnect', beforeDisconnect); + sequelize2.hooks.addListener('afterDisconnect', afterDisconnect); + + await sequelize2.pool.destroy(connection); + + expect(beforeDisconnect.callCount).to.equal(1); + expect(beforeDisconnect.firstCall.args[0]).to.equal(connection); + + expect(afterDisconnect.callCount).to.equal(1); + expect(afterDisconnect.firstCall.args[0]).to.equal(connection); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/add-column-query.test.ts b/packages/core/test/unit/query-generator/add-column-query.test.ts new file mode 100644 index 000000000000..ece57cdcfe1c --- /dev/null +++ b/packages/core/test/unit/query-generator/add-column-query.test.ts @@ -0,0 +1,58 @@ +import { DataTypes } from '@sequelize/core'; +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { beforeAll2, expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +describe('QueryGenerator#addColumnQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + it('generates a ADD COLUMN query in supported dialects', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.addColumnQuery(User.table, 'age', { + type: DataTypes.INTEGER, + }), + { + default: `ALTER TABLE [Users] ADD [age] INTEGER;`, + mssql: `ALTER TABLE [Users] ADD [age] INTEGER NULL;`, + postgres: `ALTER TABLE "Users" ADD COLUMN "age" INTEGER;`, + }, + ); + }); + + it('generates a ADD COLUMN IF NOT EXISTS query in supported dialects', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.addColumnQuery( + User.table, + 'age', + { + type: DataTypes.INTEGER, + }, + { ifNotExists: true }, + ), + { + default: buildInvalidOptionReceivedError('addColumnQuery', dialectName, ['ifNotExists']), + mariadb: 'ALTER TABLE `Users` ADD IF NOT EXISTS `age` INTEGER;', + postgres: `ALTER TABLE "Users" ADD COLUMN IF NOT EXISTS "age" INTEGER;`, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/add-constraint-query.test.ts b/packages/core/test/unit/query-generator/add-constraint-query.test.ts new file mode 100644 index 000000000000..492431e7a0f0 --- /dev/null +++ b/packages/core/test/unit/query-generator/add-constraint-query.test.ts @@ -0,0 +1,876 @@ +import { Deferrable, Op } from '@sequelize/core'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error( + `Add constraint queries are not supported by ${dialect.name} dialect`, +); +const checkNotSupportedError = new Error( + `Check constraints are not supported by ${dialect.name} dialect`, +); +const defaultNotSupportedError = new Error( + `Default constraints are not supported by ${dialect.name} dialect`, +); +const deferrableNotSupportedError = new Error( + `Deferrable constraints are not supported by ${dialect.name} dialect`, +); +const onUpdateNotSupportedError = new Error( + `Foreign key constraint with onUpdate is not supported by ${dialect.name} dialect`, +); + +describe('QueryGenerator#addConstraintQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('throws an error if invalid type', () => { + expectsql( + () => { + return queryGenerator.addConstraintQuery('myTable', { + // @ts-expect-error -- We're testing invalid options + type: 'miss-typed', + fields: ['otherId'], + }); + }, + { + default: new Error( + `Constraint type miss-typed is not supported by ${dialect.name} dialect`, + ), + sqlite3: notSupportedError, + }, + ); + }); + + describe('CHECK constraints', () => { + it('generates a query that adds a check constraint with a name', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'check', + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'ALTER TABLE [myTable] ADD CONSTRAINT [check] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint with an array of values', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'check', + type: 'CHECK', + fields: ['role'], + where: { age: ['admin', 'user', 'guest'] }, + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [check] CHECK ([age] IN ('admin', 'user', 'guest'))`, + mssql: `ALTER TABLE [myTable] ADD CONSTRAINT [check] CHECK ([age] IN (N'admin', N'user', N'guest'))`, + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + queryGenerator.addConstraintQuery(MyModel, { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + queryGenerator.addConstraintQuery(myDefinition, { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint with schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + default: + 'ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint with default schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql( + () => + queryGeneratorSchema.addConstraintQuery('myTable', { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: + 'ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + sqlite3: notSupportedError, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a query that adds a check constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + sqlite3: notSupportedError, + }, + ); + }); + }); + + describe('DEFAULT constraints', () => { + it('generates a query that adds a default constraint with a name', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'default', + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [myTable] ADD CONSTRAINT [default] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + queryGenerator.addConstraintQuery(MyModel, { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + queryGenerator.addConstraintQuery(myDefinition, { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint with schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint with default schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql( + () => + queryGeneratorSchema.addConstraintQuery('myTable', { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a default constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + sqlite3: notSupportedError, + }, + ); + }); + }); + + describe('UNIQUE constraints', () => { + it('generates a query that adds a unique constraint with a name', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'unique', + type: 'UNIQUE', + fields: ['username'], + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [unique] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a deferred unique constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'UNIQUE', + fields: ['username'], + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + sqlite3: notSupportedError, + 'postgres snowflake': `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_uk] UNIQUE ([username]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a query that adds a unique constraint with multiple columns', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'UNIQUE', + fields: ['first_name', 'last_name'], + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_first_name_last_name_uk] UNIQUE ([first_name], [last_name])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { type: 'UNIQUE', fields: ['username'] }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => queryGenerator.addConstraintQuery(MyModel, { type: 'UNIQUE', fields: ['username'] }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + queryGenerator.addConstraintQuery(myDefinition, { type: 'UNIQUE', fields: ['username'] }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint with schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint with unique schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql( + () => + queryGeneratorSchema.addConstraintQuery('myTable', { + type: 'UNIQUE', + fields: ['username'], + }), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a unique constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + sqlite3: notSupportedError, + }, + ); + }); + }); + + describe('FOREIGN KEY constraints', () => { + it('generates a query that adds a foreign key constraint with a name', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'foreign key', + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [foreign key] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a deferred foreign key constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + sqlite3: notSupportedError, + 'postgres snowflake': `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a query that adds a composite foreign key constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId', 'someId'], + references: { table: 'otherTable', fields: ['id', 'someId'] }, + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_someId_otherTable_fk] FOREIGN KEY ([otherId], [someId]) REFERENCES [otherTable] ([id], [someId])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with on delete', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + onDelete: 'CASCADE', + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) ON DELETE CASCADE`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with on update', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + onUpdate: 'CASCADE', + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) ON UPDATE CASCADE`, + sqlite3: notSupportedError, + 'db2 ibmi': onUpdateNotSupportedError, + }, + ); + }); + + it('throws an error if no references is defined', () => { + expectsql( + () => { + // @ts-expect-error -- We're testing invalid options + return queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + }); + }, + { + default: new Error( + 'Invalid foreign key constraint options. `references` object with `table` and `field` must be specified', + ), + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + const OtherModel = sequelize.define('OtherModel', {}); + + expectsql( + () => + queryGenerator.addConstraintQuery(MyModel, { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: OtherModel, field: 'id' }, + }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_otherId_OtherModels_fk] FOREIGN KEY ([otherId]) REFERENCES [OtherModels] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + const OtherModel = sequelize.define('OtherModel', {}); + const otherDefinition = OtherModel.modelDefinition; + + expectsql( + () => + queryGenerator.addConstraintQuery(myDefinition, { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: otherDefinition, field: 'id' }, + }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_otherId_OtherModels_fk] FOREIGN KEY ([otherId]) REFERENCES [OtherModels] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: { tableName: 'otherTable', schema: 'mySchema' }, field: 'id' }, + }, + ), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [mySchema].[otherTable] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with foreign key schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { + table: { tableName: 'otherTable', schema: dialect.getDefaultSchema() }, + field: 'id', + }, + }, + ), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql( + () => + queryGeneratorSchema.addConstraintQuery('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [mySchema].[otherTable] ([id])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a foreign key constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { + table: { tableName: 'otherTable', schema: 'mySchema', delimiter: 'custom' }, + field: 'id', + }, + }, + ), + { + sqlite3: notSupportedError, + }, + ); + }); + }); + + describe('PRIMARY KEY constraints', () => { + it('generates a query that adds a primary key constraint with a name', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + name: 'primary key', + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [primary key] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a deferred primary key constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'PRIMARY KEY', + fields: ['username'], + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + sqlite3: notSupportedError, + 'postgres snowflake': `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a query that adds a primary key constraint with multiple columns', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'PRIMARY KEY', + fields: ['first_name', 'last_name'], + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_first_name_last_name_pk] PRIMARY KEY ([first_name], [last_name])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint', () => { + expectsql( + () => + queryGenerator.addConstraintQuery('myTable', { + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + queryGenerator.addConstraintQuery(MyModel, { type: 'PRIMARY KEY', fields: ['username'] }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + queryGenerator.addConstraintQuery(myDefinition, { + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `ALTER TABLE [MyModels] ADD CONSTRAINT [MyModels_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint with schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint with primary key schema', () => { + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + default: `ALTER TABLE [myTable] ADD CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql( + () => + queryGeneratorSchema.addConstraintQuery('myTable', { + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `ALTER TABLE [mySchema].[myTable] ADD CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that adds a primary key constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.addConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + sqlite3: notSupportedError, + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/arithmetic-query.test.ts b/packages/core/test/unit/query-generator/arithmetic-query.test.ts new file mode 100644 index 000000000000..78c2a7360219 --- /dev/null +++ b/packages/core/test/unit/query-generator/arithmetic-query.test.ts @@ -0,0 +1,120 @@ +import { DataTypes, literal } from '@sequelize/core'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#arithmeticQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + it('uses the specified operator', async () => { + const sqlPlus = queryGenerator.arithmeticQuery('+', 'myTable', {}, { foo: 3 }, {}, {}); + + const sqlMinus = queryGenerator.arithmeticQuery('-', 'myTable', {}, { foo: 3 }, {}, {}); + + expectsql(sqlPlus, { + default: `UPDATE [myTable] SET [foo]=[foo]+ 3`, + mssql: `UPDATE [myTable] SET [foo]=[foo]+ 3 OUTPUT INSERTED.*`, + sqlite3: 'UPDATE `myTable` SET `foo`=`foo`+ 3 RETURNING *', + postgres: `UPDATE "myTable" SET "foo"="foo"+ 3 RETURNING *`, + }); + + expectsql(sqlMinus, { + default: `UPDATE [myTable] SET [foo]=[foo]- 3`, + mssql: `UPDATE [myTable] SET [foo]=[foo]- 3 OUTPUT INSERTED.*`, + sqlite3: 'UPDATE `myTable` SET `foo`=`foo`- 3 RETURNING *', + postgres: `UPDATE "myTable" SET "foo"="foo"- 3 RETURNING *`, + }); + }); + + it('uses the specified operator with literal', async () => { + const sql = queryGenerator.arithmeticQuery('+', 'myTable', {}, { foo: literal('bar') }, {}, {}); + + expectsql(sql, { + default: `UPDATE [myTable] SET [foo]=[foo]+ bar`, + mssql: `UPDATE [myTable] SET [foo]=[foo]+ bar OUTPUT INSERTED.*`, + sqlite3: 'UPDATE `myTable` SET `foo`=`foo`+ bar RETURNING *', + postgres: `UPDATE "myTable" SET "foo"="foo"+ bar RETURNING *`, + }); + }); + + it('supports specifying a WHERE clause', async () => { + const sql = queryGenerator.arithmeticQuery('+', 'myTable', { bar: 'biz' }, { foo: 3 }, {}, {}); + + expectsql(sql, { + default: `UPDATE [myTable] SET [foo]=[foo]+ 3 WHERE [bar] = 'biz'`, + mssql: `UPDATE [myTable] SET [foo]=[foo]+ 3 OUTPUT INSERTED.* WHERE [bar] = N'biz'`, + sqlite3: "UPDATE `myTable` SET `foo`=`foo`+ 3 WHERE `bar` = 'biz' RETURNING *", + postgres: `UPDATE "myTable" SET "foo"="foo"+ 3 WHERE "bar" = 'biz' RETURNING *`, + }); + }); + + it('supports omitting the RETURNING clause', async () => { + const sql = queryGenerator.arithmeticQuery( + '+', + 'myTable', + {}, + { foo: 3 }, + {}, + { returning: false }, + ); + + expectsql(sql, { + default: `UPDATE [myTable] SET [foo]=[foo]+ 3`, + }); + }); + + it('does not cause a syntax error when the minus operator is used with a negative value', async () => { + const sql = queryGenerator.arithmeticQuery('-', 'myTable', {}, { foo: -1 }, {}, {}); + + expectsql(sql, { + default: `UPDATE [myTable] SET [foo]=[foo]- -1`, + mssql: `UPDATE [myTable] SET [foo]=[foo]- -1 OUTPUT INSERTED.*`, + sqlite3: 'UPDATE `myTable` SET `foo`=`foo`- -1 RETURNING *', + postgres: `UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *`, + }); + }); + + // you'll find more replacement tests in query-generator tests + it('parses named replacements in literals', async () => { + const { User } = vars; + + const sql = queryGenerator.arithmeticQuery( + '+', + User.table, + // where + literal('id = :id'), + // increment by field + { + age: literal(':age'), + }, + // extraAttributesToBeUpdated + { + name: literal(':name'), + }, + { + replacements: { + id: 47, + age: 2, + name: 'John', + }, + }, + ); + + expectsql(sql, { + default: `UPDATE [Users] SET [age]=[age]+ 2,[name]='John' WHERE id = 47`, + mssql: `UPDATE [Users] SET [age]=[age]+ 2,[name]=N'John' OUTPUT INSERTED.* WHERE id = 47`, + sqlite3: "UPDATE `Users` SET `age`=`age`+ 2,`name`='John' WHERE id = 47 RETURNING *", + postgres: `UPDATE "Users" SET "age"="age"+ 2,"name"='John' WHERE id = 47 RETURNING *`, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/bulk-delete-query.test.ts b/packages/core/test/unit/query-generator/bulk-delete-query.test.ts new file mode 100644 index 000000000000..ca2003457840 --- /dev/null +++ b/packages/core/test/unit/query-generator/bulk-delete-query.test.ts @@ -0,0 +1,164 @@ +import { DataTypes, literal } from '@sequelize/core'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const limitNotSupportedError = new Error( + 'Using LIMIT in bulkDeleteQuery requires specifying a model or model definition.', +); + +describe('QueryGenerator#bulkDeleteQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a delete query', () => { + expectsql(queryGenerator.bulkDeleteQuery('myTable', { where: { name: 'barry' } }), { + default: `DELETE FROM [myTable] WHERE [name] = 'barry'`, + mssql: `DELETE FROM [myTable] WHERE [name] = N'barry'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + }); + + it('produces a delete query with a limit', () => { + expectsql( + () => queryGenerator.bulkDeleteQuery('myTable', { where: { name: 'barry' }, limit: 10 }), + { + default: `DELETE FROM [myTable] WHERE [name] = 'barry' LIMIT 10`, + sqlite3: + "DELETE FROM `myTable` WHERE rowid IN (SELECT rowid FROM `myTable` WHERE `name` = 'barry' LIMIT 10)", + 'db2 ibmi': `DELETE FROM "myTable" WHERE "name" = 'barry' FETCH NEXT 10 ROWS ONLY`, + 'mssql postgres snowflake': limitNotSupportedError, + }, + ); + }); + + it('produces a delete query with a limit using a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(queryGenerator.bulkDeleteQuery(MyModel, { where: { name: 'barry' }, limit: 10 }), { + default: `DELETE FROM [MyModels] WHERE [name] = 'barry' LIMIT 10`, + mssql: `DELETE FROM [MyModels] WHERE [id] IN (SELECT [id] FROM [MyModels] WHERE [name] = N'barry' ORDER BY [id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: + "DELETE FROM `MyModels` WHERE rowid IN (SELECT rowid FROM `MyModels` WHERE `name` = 'barry' LIMIT 10)", + 'db2 ibmi': `DELETE FROM "MyModels" WHERE "name" = 'barry' FETCH NEXT 10 ROWS ONLY`, + 'postgres snowflake': `DELETE FROM "MyModels" WHERE "id" IN (SELECT "id" FROM "MyModels" WHERE "name" = 'barry' ORDER BY "id" LIMIT 10)`, + }); + }); + + it('produces a delete query with a limit using a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + queryGenerator.bulkDeleteQuery(myDefinition, { where: { name: 'barry' }, limit: 10 }), + { + default: `DELETE FROM [MyModels] WHERE [name] = 'barry' LIMIT 10`, + mssql: `DELETE FROM [MyModels] WHERE [id] IN (SELECT [id] FROM [MyModels] WHERE [name] = N'barry' ORDER BY [id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: + "DELETE FROM `MyModels` WHERE rowid IN (SELECT rowid FROM `MyModels` WHERE `name` = 'barry' LIMIT 10)", + 'db2 ibmi': `DELETE FROM "MyModels" WHERE "name" = 'barry' FETCH NEXT 10 ROWS ONLY`, + 'postgres snowflake': `DELETE FROM "MyModels" WHERE "id" IN (SELECT "id" FROM "MyModels" WHERE "name" = 'barry' ORDER BY "id" LIMIT 10)`, + }, + ); + }); + + // you'll find more replacement tests in query-generator tests + it('produces a delete query with named replacements in literals', () => { + const MyModel = sequelize.define('MyModel', {}); + + const query = queryGenerator.bulkDeleteQuery(MyModel, { + limit: literal(':limit'), + where: literal('name = :name'), + replacements: { + limit: 1, + name: 'Zoe', + }, + }); + + expectsql(query, { + default: `DELETE FROM [MyModels] WHERE name = 'Zoe' LIMIT 1`, + mssql: `DELETE FROM [MyModels] WHERE [id] IN (SELECT [id] FROM [MyModels] WHERE name = N'Zoe' ORDER BY [id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY); SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: `DELETE FROM \`MyModels\` WHERE rowid IN (SELECT rowid FROM \`MyModels\` WHERE name = 'Zoe' LIMIT 1)`, + 'db2 ibmi': `DELETE FROM "MyModels" WHERE name = 'Zoe' FETCH NEXT 1 ROWS ONLY`, + 'postgres snowflake': `DELETE FROM "MyModels" WHERE "id" IN (SELECT "id" FROM "MyModels" WHERE name = 'Zoe' ORDER BY "id" LIMIT 1)`, + }); + }); + + it('fails to produce a delete query with undefined parameter in where', () => { + expectsql(() => queryGenerator.bulkDeleteQuery('myTable', { where: { name: undefined } }), { + default: + new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts. +Value: { name: undefined } +Caused by: "undefined" cannot be escaped`), + }); + }); + + it('produces a delete query with a model where primary key has a field name different from attribute name', () => { + const MyModel = sequelize.define('MyModel', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'my_model_id', + }, + }); + + expectsql(queryGenerator.bulkDeleteQuery(MyModel, { where: { id: 2 } }), { + default: 'DELETE FROM [MyModels] WHERE [my_model_id] = 2', + mssql: 'DELETE FROM [MyModels] WHERE [my_model_id] = 2; SELECT @@ROWCOUNT AS AFFECTEDROWS;', + }); + }); + + it('produces a delete query with a schema', () => { + expectsql( + queryGenerator.bulkDeleteQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { where: { name: 'barry' } }, + ), + { + default: `DELETE FROM [mySchema].[myTable] WHERE [name] = 'barry'`, + mssql: `DELETE FROM [mySchema].[myTable] WHERE [name] = N'barry'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: "DELETE FROM `mySchema.myTable` WHERE `name` = 'barry'", + }, + ); + }); + + it('produces a delete query with a default schema', () => { + expectsql( + queryGenerator.bulkDeleteQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { where: { name: 'barry' } }, + ), + { + default: `DELETE FROM [myTable] WHERE [name] = 'barry'`, + mssql: `DELETE FROM [myTable] WHERE [name] = N'barry'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: "DELETE FROM `myTable` WHERE `name` = 'barry'", + }, + ); + }); + + it('produces a delete query with a globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(queryGeneratorSchema.bulkDeleteQuery('myTable', { where: { name: 'barry' } }), { + default: `DELETE FROM [mySchema].[myTable] WHERE [name] = 'barry'`, + mssql: `DELETE FROM [mySchema].[myTable] WHERE [name] = N'barry'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + sqlite3: "DELETE FROM `mySchema.myTable` WHERE `name` = 'barry'", + }); + }); + + it('produces a delete query with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + queryGenerator.bulkDeleteQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { where: { name: 'barry' } }, + ), + { + sqlite3: "DELETE FROM `mySchemacustommyTable` WHERE `name` = 'barry'", + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/bulk-insert-query.test.ts b/packages/core/test/unit/query-generator/bulk-insert-query.test.ts new file mode 100644 index 000000000000..507e1a52ea11 --- /dev/null +++ b/packages/core/test/unit/query-generator/bulk-insert-query.test.ts @@ -0,0 +1,43 @@ +import { DataTypes, literal } from '@sequelize/core'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#bulkInsertQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + it('parses named replacements in literals', async () => { + const { User } = vars; + + const sql = queryGenerator.bulkInsertQuery( + User.table, + [ + { + firstName: literal(':injection'), + }, + ], + { + replacements: { + injection: 'a string', + }, + }, + ); + + expectsql(sql, { + default: `INSERT INTO [Users] ([firstName]) VALUES ('a string');`, + mssql: `INSERT INTO [Users] ([firstName]) VALUES (N'a string');`, + // TODO: ibmi should be the same as `default`, since the 'returning' option is not specified + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ('a string'))`, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/commit-transaction-query.test.ts b/packages/core/test/unit/query-generator/commit-transaction-query.test.ts new file mode 100644 index 000000000000..b707b6c9f9c5 --- /dev/null +++ b/packages/core/test/unit/query-generator/commit-transaction-query.test.ts @@ -0,0 +1,17 @@ +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error( + `commitTransactionQuery is not supported by the ${dialect.name} dialect.`, +); + +describe('QueryGenerator#commitTransactionQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for committing a transaction', () => { + expectsql(() => queryGenerator.commitTransactionQuery(), { + default: 'COMMIT', + 'db2 ibmi mssql': notSupportedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/create-database-query.test.ts b/packages/core/test/unit/query-generator/create-database-query.test.ts new file mode 100644 index 000000000000..2f407bf35f2f --- /dev/null +++ b/packages/core/test/unit/query-generator/create-database-query.test.ts @@ -0,0 +1,93 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { removeUndefined } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Databases are not supported in ${dialectName}.`); + +describe('QueryGenerator#createDatabaseQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a CREATE DATABASE query in supported dialects', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase'), { + default: notSupportedError, + postgres: 'CREATE DATABASE "myDatabase"', + snowflake: 'CREATE DATABASE IF NOT EXISTS "myDatabase"', + mssql: `IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'myDatabase' ) CREATE DATABASE [myDatabase]`, + }); + }); + + it('supports the collate option', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', { collate: 'en_US.UTF-8' }), { + default: notSupportedError, + postgres: `CREATE DATABASE "myDatabase" LC_COLLATE = 'en_US.UTF-8'`, + snowflake: buildInvalidOptionReceivedError('createDatabaseQuery', dialectName, ['collate']), + mssql: `IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'myDatabase' ) CREATE DATABASE [myDatabase] COLLATE N'en_US.UTF-8'`, + }); + }); + + it('supports the encoding option', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', { encoding: 'UTF8' }), { + default: notSupportedError, + 'mssql snowflake': buildInvalidOptionReceivedError('createDatabaseQuery', dialectName, [ + 'encoding', + ]), + postgres: `CREATE DATABASE "myDatabase" ENCODING = 'UTF8'`, + }); + }); + + it('supports the ctype option', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', { ctype: 'zh_TW.UTF-8' }), { + default: notSupportedError, + 'mssql snowflake': buildInvalidOptionReceivedError('createDatabaseQuery', dialectName, [ + 'ctype', + ]), + postgres: `CREATE DATABASE "myDatabase" LC_CTYPE = 'zh_TW.UTF-8'`, + }); + }); + + it('supports the template option', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', { template: 'template0' }), { + default: notSupportedError, + 'mssql snowflake': buildInvalidOptionReceivedError('createDatabaseQuery', dialectName, [ + 'template', + ]), + postgres: `CREATE DATABASE "myDatabase" TEMPLATE = 'template0'`, + }); + }); + + it('supports the charset option', () => { + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', { charset: 'utf8mb4' }), { + default: notSupportedError, + 'mssql postgres snowflake': buildInvalidOptionReceivedError( + 'createDatabaseQuery', + dialectName, + ['charset'], + ), + }); + }); + + it('supports combining all options', () => { + const optionSupport = { + collate: ['postgres', 'mssql'], + encoding: ['postgres'], + ctype: ['postgres'], + template: ['postgres'], + }; + + const config = removeUndefined({ + collate: optionSupport.collate.includes(dialectName) ? 'en_US.UTF-8' : undefined, + encoding: optionSupport.encoding.includes(dialectName) ? 'UTF8' : undefined, + ctype: optionSupport.ctype.includes(dialectName) ? 'zh_TW.UTF-8' : undefined, + template: optionSupport.template.includes(dialectName) ? 'template0' : undefined, + }); + + expectsql(() => queryGenerator.createDatabaseQuery('myDatabase', config), { + default: notSupportedError, + postgres: `CREATE DATABASE "myDatabase" ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'zh_TW.UTF-8' TEMPLATE = 'template0'`, + snowflake: `CREATE DATABASE IF NOT EXISTS "myDatabase"`, + mssql: `IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'myDatabase') CREATE DATABASE [myDatabase] COLLATE N'en_US.UTF-8'`, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/create-savepoint-query.test.ts b/packages/core/test/unit/query-generator/create-savepoint-query.test.ts new file mode 100644 index 000000000000..6d4af4ec03fb --- /dev/null +++ b/packages/core/test/unit/query-generator/create-savepoint-query.test.ts @@ -0,0 +1,17 @@ +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error(`Savepoints are not supported by ${dialect.name}.`); + +describe('QueryGenerator#createSavepointQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for creating a savepoint', () => { + expectsql(() => queryGenerator.createSavepointQuery('mySavePoint'), { + default: 'SAVEPOINT [mySavePoint]', + mssql: 'SAVE TRANSACTION [mySavePoint]', + snowflake: notSupportedError, + 'db2 ibmi': 'SAVEPOINT "mySavePoint" ON ROLLBACK RETAIN CURSORS', + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/create-schema-query.test.ts b/packages/core/test/unit/query-generator/create-schema-query.test.ts new file mode 100644 index 000000000000..ac624615e5b2 --- /dev/null +++ b/packages/core/test/unit/query-generator/create-schema-query.test.ts @@ -0,0 +1,129 @@ +import { sql } from '@sequelize/core'; +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Schemas are not supported in ${dialectName}.`); + +describe('QueryGenerator#createSchemaQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a CREATE SCHEMA query in supported dialects', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema'), { + default: 'CREATE SCHEMA [mySchema]', + sqlite3: notSupportedError, + }); + }); + + it('supports the authorization option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { authorization: 'myUser' }), { + default: 'CREATE SCHEMA [mySchema] AUTHORIZATION [myUser]', + sqlite3: notSupportedError, + 'mariadb mysql snowflake': buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'authorization', + ]), + }); + }); + + it('supports the authorization option with a literal', () => { + expectsql( + () => queryGenerator.createSchemaQuery('mySchema', { authorization: sql`CURRENT USER` }), + { + default: 'CREATE SCHEMA [mySchema] AUTHORIZATION CURRENT USER', + sqlite3: notSupportedError, + 'mariadb mysql snowflake': buildInvalidOptionReceivedError( + 'createSchemaQuery', + dialectName, + ['authorization'], + ), + }, + ); + }); + + it('supports the charset option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { charset: 'utf8mb4' }), { + default: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, ['charset']), + 'mysql mariadb': `CREATE SCHEMA \`mySchema\` DEFAULT CHARACTER SET 'utf8mb4'`, + sqlite3: notSupportedError, + }); + }); + + it('supports the collate option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { collate: 'en_US.UTF-8' }), { + default: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, ['collate']), + 'mysql mariadb': `CREATE SCHEMA \`mySchema\` DEFAULT COLLATE 'en_US.UTF-8'`, + sqlite3: notSupportedError, + }); + }); + + it('supports the comment option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { comment: 'myComment' }), { + default: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, ['comment']), + snowflake: `CREATE SCHEMA "mySchema" COMMENT 'myComment'`, + sqlite3: notSupportedError, + }); + }); + + it('supports the ifNotExists option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { ifNotExists: true }), { + default: 'CREATE SCHEMA IF NOT EXISTS [mySchema]', + 'db2 ibmi mssql': buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'ifNotExists', + ]), + sqlite3: notSupportedError, + }); + }); + + it('supports the replace option', () => { + expectsql(() => queryGenerator.createSchemaQuery('mySchema', { replace: true }), { + default: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, ['replace']), + 'mariadb snowflake': `CREATE OR REPLACE SCHEMA [mySchema]`, + sqlite3: notSupportedError, + }); + }); + + it('supports specifying all possible combinations', () => { + expectsql( + () => + queryGenerator.createSchemaQuery('mySchema', { + authorization: 'myUser', + charset: 'utf8mb4', + collate: 'en_US.UTF-8', + comment: 'myComment', + ifNotExists: true, + replace: true, + }), + { + default: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'charset', + 'collate', + 'comment', + 'ifNotExists', + 'replace', + ]), + mariadb: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'authorization', + 'comment', + ]), + mysql: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'authorization', + 'comment', + 'replace', + ]), + postgres: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'charset', + 'collate', + 'comment', + 'replace', + ]), + snowflake: buildInvalidOptionReceivedError('createSchemaQuery', dialectName, [ + 'authorization', + 'charset', + 'collate', + ]), + sqlite3: notSupportedError, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/create-table-query.test.ts b/packages/core/test/unit/query-generator/create-table-query.test.ts new file mode 100644 index 000000000000..5df2e914feec --- /dev/null +++ b/packages/core/test/unit/query-generator/create-table-query.test.ts @@ -0,0 +1,730 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, getTestDialect, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const dialectName = getTestDialect(); + +// TODO: check the tests with COMMENT after attributeToSQL quotes the comment +// TODO: double check if all column SQL types are possible results of attributeToSQL after #15533 has been merged +// TODO: see if some logic in handling columns can be moved to attributeToSQL which could make some tests here redundant + +describe('QueryGenerator#createTableQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query to create a table', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'DATE' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE);', + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE); END`, + }); + }); + + it('produces a query to create a table from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(queryGenerator.createTableQuery(MyModel, { myColumn: 'DATE' }), { + default: 'CREATE TABLE IF NOT EXISTS [MyModels] ([myColumn] DATE);', + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `MyModels` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[MyModels]', 'U') IS NULL CREATE TABLE [MyModels] ([myColumn] DATE);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "MyModels" ("myColumn" DATE); END`, + }); + }); + + it('produces a query to create a table from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(queryGenerator.createTableQuery(myDefinition, { myColumn: 'DATE' }), { + default: 'CREATE TABLE IF NOT EXISTS [MyModels] ([myColumn] DATE);', + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `MyModels` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[MyModels]', 'U') IS NULL CREATE TABLE [MyModels] ([myColumn] DATE);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "MyModels" ("myColumn" DATE); END`, + }); + }); + + it('produces a query to create a table with schema in tableName object', () => { + expectsql( + queryGenerator.createTableQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { myColumn: 'DATE' }, + ), + { + default: 'CREATE TABLE IF NOT EXISTS [mySchema].[myTable] ([myColumn] DATE);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `mySchema`.`myTable` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[mySchema].[myTable]', 'U') IS NULL CREATE TABLE [mySchema].[myTable] ([myColumn] DATE);`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `mySchema.myTable` (`myColumn` DATE);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "mySchema"."myTable" ("myColumn" DATE); END`, + }, + ); + }); + + it('produces a query to create a table with default schema in tableName object', () => { + expectsql( + queryGenerator.createTableQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { myColumn: 'DATE' }, + ), + { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE);', + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE); END`, + }, + ); + }); + + it('produces a query to create a table from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(queryGeneratorSchema.createTableQuery('myTable', { myColumn: 'DATE' }), { + default: 'CREATE TABLE IF NOT EXISTS [mySchema].[myTable] ([myColumn] DATE);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `mySchema`.`myTable` (`myColumn` DATE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[mySchema].[myTable]', 'U') IS NULL CREATE TABLE [mySchema].[myTable] ([myColumn] DATE);`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `mySchema.myTable` (`myColumn` DATE);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "mySchema"."myTable" ("myColumn" DATE); END`, + }); + }); + + it('produces a query to create a table with schema and delimiter in tableName object', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + queryGenerator.createTableQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { myColumn: 'DATE' }, + ), + { + sqlite3: 'CREATE TABLE IF NOT EXISTS `mySchemacustommyTable` (`myColumn` DATE);', + }, + ); + }); + + it('produces a query to create a table with multiple columns', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE', secondColumn: 'TEXT' }), + { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE, [secondColumn] TEXT);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, "secondColumn" TEXT); END`, + }, + ); + }); + + it('produces a query to create a table with a primary key', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'DATE PRIMARY KEY' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE, PRIMARY KEY ([myColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, PRIMARY KEY (`myColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, PRIMARY KEY ([myColumn]));`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE PRIMARY KEY);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, PRIMARY KEY ("myColumn")); END`, + }); + }); + + it('produces a query to create a table with multiple primary keys', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'DATE PRIMARY KEY', + secondColumn: 'TEXT PRIMARY KEY', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE, [secondColumn] TEXT, PRIMARY KEY ([myColumn], [secondColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT, PRIMARY KEY (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT, PRIMARY KEY ([myColumn], [secondColumn]));`, + sqlite3: + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE NOT NULL, `secondColumn` TEXT NOT NULL, PRIMARY KEY (`myColumn`, `secondColumn`));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, "secondColumn" TEXT, PRIMARY KEY ("myColumn", "secondColumn")); END`, + }, + ); + }); + + // quoting the identifiers after REFERENCES is done by attributesToSQL + it('produces a query to create a table with references', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE REFERENCES "Bar" ("id")' }), + { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE REFERENCES "Bar" ("id"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, FOREIGN KEY (`myColumn`) REFERENCES "Bar" ("id")) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, FOREIGN KEY ([myColumn]) REFERENCES "Bar" ("id"));`, + 'snowflake db2': + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE REFERENCES "Bar" ("id")); END`, + }, + ); + }); + + it('produces a query to create a table with references and a primary key', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'DATE PRIMARY KEY REFERENCES "Bar" ("id")', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE REFERENCES "Bar" ("id"), PRIMARY KEY ("myColumn"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, PRIMARY KEY (`myColumn`), FOREIGN KEY (`myColumn`) REFERENCES "Bar" ("id")) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, PRIMARY KEY ([myColumn]), FOREIGN KEY ([myColumn]) REFERENCES "Bar" ("id"));`, + sqlite3: + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE PRIMARY KEY REFERENCES "Bar" ("id"));', + 'snowflake db2': + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, PRIMARY KEY ("myColumn"), FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE REFERENCES "Bar" ("id"), PRIMARY KEY ("myColumn")); END`, + }, + ); + }); + + // TODO: REFERENCES should be pushed to the end, this is likely a bug in mysql/mariadb + // mssql and db2 use the same logic but there does not seem to be a valid attributeToSQL result that causes issues + it('produces a query to create a table with references and a comment', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'DATE REFERENCES "Bar" ("id") COMMENT Foo', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE REFERENCES "Bar" ("id") COMMENT Foo);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, FOREIGN KEY (`myColumn`) REFERENCES "Bar" ("id") COMMENT Foo) ENGINE=InnoDB;', + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE REFERENCES "Bar" ("id")); COMMENT ON COLUMN "myTable"."myColumn" IS 'Foo';`, + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, FOREIGN KEY ([myColumn]) REFERENCES "Bar" ("id")); + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo', @level0type = N'Schema', @level0name = N'dbo', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [myColumn];`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id") COMMENT Foo);', + db2: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id")); -- 'Foo', TableName = "myTable", ColumnName = "myColumn";`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE REFERENCES "Bar" ("id") COMMENT Foo); END`, + }, + ); + }); + + it('produces a query to create a table with a non-null column', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'DATE NOT NULL' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE NOT NULL);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE NOT NULL) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE NOT NULL);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE NOT NULL); END`, + }); + }); + + it('produces a query to create a table with schema in tableName object and a comment', () => { + expectsql( + queryGenerator.createTableQuery( + { tableName: 'myTable', schema: 'mySchema' }, + { myColumn: 'DATE COMMENT Foo' }, + ), + { + default: 'CREATE TABLE IF NOT EXISTS [mySchema].[myTable] ([myColumn] DATE COMMENT Foo);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `mySchema`.`myTable` (`myColumn` DATE COMMENT Foo) ENGINE=InnoDB;', + postgres: `CREATE TABLE IF NOT EXISTS "mySchema"."myTable" ("myColumn" DATE); COMMENT ON COLUMN "mySchema"."myTable"."myColumn" IS 'Foo';`, + mssql: `IF OBJECT_ID(N'[mySchema].[myTable]', 'U') IS NULL CREATE TABLE [mySchema].[myTable] ([myColumn] DATE); + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo', @level0type = N'Schema', @level0name = N'mySchema', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [myColumn];`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `mySchema.myTable` (`myColumn` DATE COMMENT Foo);', + db2: `CREATE TABLE IF NOT EXISTS "mySchema"."myTable" ("myColumn" DATE); -- 'Foo', TableName = "mySchema"."myTable", ColumnName = "myColumn";`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "mySchema"."myTable" ("myColumn" DATE COMMENT Foo); END`, + }, + ); + }); + + it('produces a query to create a table with multiple columns with comments', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'DATE COMMENT Foo', + secondColumn: 'DATE COMMENT Foo Bar', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE COMMENT Foo, [secondColumn] DATE COMMENT Foo Bar);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE COMMENT Foo, `secondColumn` DATE COMMENT Foo Bar) ENGINE=InnoDB;', + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" DATE); COMMENT ON COLUMN "myTable"."myColumn" IS 'Foo'; COMMENT ON COLUMN "myTable"."secondColumn" IS 'Foo Bar';`, + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] DATE); + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo', @level0type = N'Schema', @level0name = N'dbo', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [myColumn]; + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo Bar', @level0type = N'Schema', @level0name = N'dbo', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [secondColumn];`, + db2: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" DATE); -- 'Foo', TableName = "myTable", ColumnName = "myColumn"; -- 'Foo Bar', TableName = "myTable", ColumnName = "secondColumn";`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE COMMENT Foo, "secondColumn" DATE COMMENT Foo Bar); END`, + }, + ); + }); + + // TODO: the second COMMENT should likely be replaced by an empty string in DB2 and MSSQL + it('produces a query to create a table with multiple comments in one column', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE COMMENT Foo COMMENT Bar' }), + { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE COMMENT Foo COMMENT Bar);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE COMMENT Foo COMMENT Bar) ENGINE=InnoDB;', + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE); COMMENT ON COLUMN "myTable"."myColumn" IS 'Foo COMMENT Bar';`, + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE COMMENT Foo); + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Bar', @level0type = N'Schema', @level0name = N'dbo', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [myColumn];`, + db2: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE COMMENT Foo); -- 'Bar', TableName = "myTable", ColumnName = "myColumn";`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE COMMENT Foo COMMENT Bar); END`, + }, + ); + }); + + it('produces a query to create a table with a primary key specified after the comment', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE COMMENT Foo PRIMARY KEY' }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] DATE COMMENT Foo, PRIMARY KEY ([myColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE COMMENT Foo, PRIMARY KEY (`myColumn`)) ENGINE=InnoDB;', + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE); COMMENT ON COLUMN "myTable"."myColumn" IS 'Foo PRIMARY KEY';`, + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE); + EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo PRIMARY KEY', @level0type = N'Schema', @level0name = N'dbo', + @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [myColumn];`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE COMMENT Foo PRIMARY KEY);', + db2: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE); -- 'Foo PRIMARY KEY', TableName = "myTable", ColumnName = "myColumn";`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE COMMENT Foo, PRIMARY KEY ("myColumn")); END`, + }, + ); + }); + + it('produces a query to create a table with both a table comment and a column comment', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE COMMENT Foo' }, + { comment: 'Bar' }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['comment']), + 'mariadb mysql': + "CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE COMMENT Foo) ENGINE=InnoDB COMMENT 'Bar';", + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE); COMMENT ON TABLE "myTable" IS 'Bar'; COMMENT ON COLUMN "myTable"."myColumn" IS 'Foo';`, + snowflake: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE COMMENT Foo) COMMENT 'Bar';`, + }, + ); + }); + + // quoting the enum values is done by attributesToSQL + it('produces a query to create a table with an enum', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'ENUM("foo", "bar")' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] ENUM("foo", "bar"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` ENUM("foo", "bar")) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" "public"."enum_myTable_myColumn");', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] ENUM("foo", "bar"));`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" ENUM("foo", "bar")); END`, + }); + }); + + it('produces a query to create a table with various integer types', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'INTEGER', + secondColumn: 'BIGINT', + thirdColumn: 'SMALLINT', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER, [secondColumn] BIGINT, [thirdColumn] SMALLINT);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER, `secondColumn` BIGINT, `thirdColumn` SMALLINT) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER, [secondColumn] BIGINT, [thirdColumn] SMALLINT);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER, "secondColumn" BIGINT, "thirdColumn" SMALLINT); END`, + }, + ); + }); + + it('produces a query to create a table with various integer serial types', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'INTEGER SERIAL', + secondColumn: 'BIGINT SERIAL', + thirdColumn: 'SMALLINT SERIAL', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER SERIAL, [secondColumn] BIGINT SERIAL, [thirdColumn] SMALLINT SERIAL);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER SERIAL, `secondColumn` BIGINT SERIAL, `thirdColumn` SMALLINT SERIAL) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" SERIAL, "secondColumn" BIGSERIAL, "thirdColumn" SMALLSERIAL);', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER SERIAL, [secondColumn] BIGINT SERIAL, [thirdColumn] SMALLINT SERIAL);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER SERIAL, "secondColumn" BIGINT SERIAL, "thirdColumn" SMALLINT SERIAL); END`, + }, + ); + }); + + it('produces a query to create a table with a non-null integer serial', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER SERIAL NOT NULL' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER SERIAL NOT NULL);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER SERIAL NOT NULL) ENGINE=InnoDB;', + postgres: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" SERIAL);', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER SERIAL NOT NULL);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER SERIAL NOT NULL); END`, + }); + }); + + it('produces a query to create a table with an autoincremented integer', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER AUTOINCREMENT' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER AUTOINCREMENT);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER AUTOINCREMENT) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER AUTOINCREMENT);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER AUTOINCREMENT); END`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" INTEGER DEFAULT "myTable_myColumn_seq".NEXTVAL);', + }); + }); + + it('produces a query to create a table with a primary key integer', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER PRIMARY KEY' }), { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER, PRIMARY KEY ([myColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER, PRIMARY KEY (`myColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER, PRIMARY KEY ([myColumn]));`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER PRIMARY KEY);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER, PRIMARY KEY ("myColumn")); END`, + }); + }); + + it('produces a query to create a table with an integer and multiple primary keys', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'INTEGER PRIMARY KEY', + secondColumn: 'TEXT PRIMARY KEY', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER, [secondColumn] TEXT, PRIMARY KEY ([myColumn], [secondColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER, `secondColumn` TEXT, PRIMARY KEY (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER, [secondColumn] TEXT, PRIMARY KEY ([myColumn], [secondColumn]));`, + sqlite3: + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER NOT NULL, `secondColumn` TEXT NOT NULL, PRIMARY KEY (`myColumn`, `secondColumn`));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER, "secondColumn" TEXT, PRIMARY KEY ("myColumn", "secondColumn")); END`, + }, + ); + }); + + it('produces a query to create a table with non-null integers and multiple primary keys', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { + myColumn: 'INTEGER NOT NULL', + secondColumn: 'INTEGER PRIMARY KEY NOT NULL', + thirdColumn: 'TEXT PRIMARY KEY', + }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER NOT NULL, [secondColumn] INTEGER NOT NULL, [thirdColumn] TEXT, PRIMARY KEY ([secondColumn], [thirdColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER NOT NULL, `secondColumn` INTEGER NOT NULL, `thirdColumn` TEXT, PRIMARY KEY (`secondColumn`, `thirdColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER NOT NULL, [secondColumn] INTEGER NOT NULL, [thirdColumn] TEXT, PRIMARY KEY ([secondColumn], [thirdColumn]));`, + sqlite3: + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER NOT NULL, `secondColumn` INTEGER NOT NULL, `thirdColumn` TEXT NOT NULL, PRIMARY KEY (`secondColumn`, `thirdColumn`));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER NOT NULL, "secondColumn" INTEGER NOT NULL, "thirdColumn" TEXT, PRIMARY KEY ("secondColumn", "thirdColumn")); END`, + }, + ); + }); + + it('produces a query to create a table with an autoincremented primary key integer', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER AUTOINCREMENT PRIMARY KEY' }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER AUTOINCREMENT, PRIMARY KEY ([myColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER AUTOINCREMENT, PRIMARY KEY (`myColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER AUTOINCREMENT, PRIMARY KEY ([myColumn]));`, + sqlite3: + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER PRIMARY KEY AUTOINCREMENT);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER AUTOINCREMENT, PRIMARY KEY ("myColumn")); END`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" INTEGER DEFAULT "myTable_myColumn_seq".NEXTVAL, PRIMARY KEY ("myColumn"));', + }, + ); + }); + + it('produces a query to create a table with primary key integer with specified length and unsigned', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER(5) UNSIGNED PRIMARY KEY' }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER(5) UNSIGNED, PRIMARY KEY ([myColumn]));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER(5) UNSIGNED, PRIMARY KEY (`myColumn`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER(5) UNSIGNED, PRIMARY KEY ([myColumn]));`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER PRIMARY KEY);', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER(5) UNSIGNED, PRIMARY KEY ("myColumn")); END`, + }, + ); + }); + + it('produces a query to create a table with integer with specified length and unsigned', () => { + expectsql(queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER(5) UNSIGNED' }), { + default: 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER(5) UNSIGNED);', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER(5) UNSIGNED) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER(5) UNSIGNED);`, + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER(5) UNSIGNED); END`, + }); + }); + + it('produces a query to create a table with integer with references', () => { + expectsql( + queryGenerator.createTableQuery('myTable', { myColumn: 'INTEGER REFERENCES "Bar" ("id")' }), + { + default: + 'CREATE TABLE IF NOT EXISTS [myTable] ([myColumn] INTEGER REFERENCES "Bar" ("id"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` INTEGER, FOREIGN KEY (`myColumn`) REFERENCES "Bar" ("id")) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] INTEGER, FOREIGN KEY ([myColumn]) REFERENCES "Bar" ("id"));`, + 'snowflake db2': + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" INTEGER, FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" INTEGER REFERENCES "Bar" ("id")); END`, + }, + ); + }); + + it('supports the engine option', () => { + expectsql( + () => queryGenerator.createTableQuery('myTable', { myColumn: 'DATE' }, { engine: 'MyISAM' }), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['engine']), + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=MyISAM;', + }, + ); + }); + + it('supports the charset option', () => { + expectsql( + () => + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE' }, { charset: 'utf8mb4' }), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['charset']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;', + }, + ); + }); + + it('supports the collate option', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE' }, + { collate: 'en_US.UTF-8' }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['collate']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB COLLATE en_US.UTF-8;', + }, + ); + }); + + it('supports the rowFormat option', () => { + expectsql( + () => + queryGenerator.createTableQuery('myTable', { myColumn: 'DATE' }, { rowFormat: 'default' }), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['rowFormat']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB ROW_FORMAT=default;', + }, + ); + }); + + it('supports the comment option', () => { + expectsql( + () => queryGenerator.createTableQuery('myTable', { myColumn: 'DATE' }, { comment: 'Foo' }), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['comment']), + 'mariadb mysql': + "CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB COMMENT 'Foo';", + postgres: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE); COMMENT ON TABLE "myTable" IS 'Foo';`, + snowflake: `CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE) COMMENT 'Foo';`, + }, + ); + }); + + it('supports the initialAutoIncrement option', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE' }, + { initialAutoIncrement: 1_000_001 }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, [ + 'initialAutoIncrement', + ]), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE) ENGINE=InnoDB AUTO_INCREMENT=1000001;', + }, + ); + }); + + describe('supports the uniqueKeys option', () => { + // SQLITE does not respect the index name when the index is created through CREATE TABLE + // As such, Sequelize's createTable does not add the constraint in the Sequelize Dialect. + // Instead, `sequelize.sync` calls CREATE INDEX after the table has been created, + // as that query *does* respect the index name. + + it('with an array', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE', secondColumn: 'TEXT' }, + { uniqueKeys: [{ fields: ['myColumn', 'secondColumn'] }] }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT, UNIQUE `uniq_myTable_myColumn_secondColumn` (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "my_table_my_column_second_column" UNIQUE ("myColumn", "secondColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT, CONSTRAINT [my_table_my_column_second_column] UNIQUE ([myColumn], [secondColumn]));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, UNIQUE "uniq_myTable_myColumn_secondColumn" ("myColumn", "secondColumn"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT NOT NULL, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn")); END`, + }, + ); + }); + + it('with an indexName', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE', secondColumn: 'TEXT' }, + { uniqueKeys: { myIndex: { fields: ['myColumn', 'secondColumn'] } } }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT, UNIQUE `myIndex` (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "myIndex" UNIQUE ("myColumn", "secondColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT, CONSTRAINT [myIndex] UNIQUE ([myColumn], [secondColumn]));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, UNIQUE "myIndex" ("myColumn", "secondColumn"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT NOT NULL, CONSTRAINT "myIndex" UNIQUE ("myColumn", "secondColumn"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "myIndex" UNIQUE ("myColumn", "secondColumn")); END`, + }, + ); + }); + + it('with a single field', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE' }, + { uniqueKeys: [{ fields: ['myColumn'] }] }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, UNIQUE `uniq_myTable_myColumn` (`myColumn`)) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, CONSTRAINT "my_table_my_column" UNIQUE ("myColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, CONSTRAINT [my_table_my_column] UNIQUE ([myColumn]));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, UNIQUE "uniq_myTable_myColumn" ("myColumn"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, CONSTRAINT "uniq_myTable_myColumn" UNIQUE ("myColumn"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, CONSTRAINT "uniq_myTable_myColumn" UNIQUE ("myColumn")); END`, + }, + ); + }); + + it('with primary key fields', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE PRIMARY KEY', secondColumn: 'TEXT PRIMARY KEY' }, + { uniqueKeys: [{ fields: ['myColumn', 'secondColumn'] }] }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT, UNIQUE `uniq_myTable_myColumn_secondColumn` (`myColumn`, `secondColumn`), PRIMARY KEY (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "my_table_my_column_second_column" UNIQUE ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn", "secondColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT, CONSTRAINT [my_table_my_column_second_column] UNIQUE ([myColumn], [secondColumn]), PRIMARY KEY ([myColumn], [secondColumn]));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, UNIQUE "uniq_myTable_myColumn_secondColumn" ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn", "secondColumn"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn", "secondColumn"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE, "secondColumn" TEXT, PRIMARY KEY ("myColumn", "secondColumn")); END`, + }, + ); + }); + + it('with a non-null column', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE NOT NULL', secondColumn: 'TEXT' }, + { uniqueKeys: [{ fields: ['myColumn', 'secondColumn'] }] }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE NOT NULL, `secondColumn` TEXT, UNIQUE `uniq_myTable_myColumn_secondColumn` (`myColumn`, `secondColumn`)) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT, CONSTRAINT "my_table_my_column_second_column" UNIQUE ("myColumn", "secondColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE NOT NULL, [secondColumn] TEXT, CONSTRAINT [my_table_my_column_second_column] UNIQUE ([myColumn], [secondColumn]));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT, UNIQUE "uniq_myTable_myColumn_secondColumn" ("myColumn", "secondColumn"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT NOT NULL, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE NOT NULL, "secondColumn" TEXT, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn")); END`, + }, + ); + }); + + it('with a primary key column with references', () => { + expectsql( + () => + queryGenerator.createTableQuery( + 'myTable', + { myColumn: 'DATE PRIMARY KEY REFERENCES "Bar" ("id")', secondColumn: 'TEXT' }, + { uniqueKeys: [{ fields: ['myColumn', 'secondColumn'] }] }, + ), + { + default: buildInvalidOptionReceivedError('createTableQuery', dialectName, ['uniqueKeys']), + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `myTable` (`myColumn` DATE, `secondColumn` TEXT, UNIQUE `uniq_myTable_myColumn_secondColumn` (`myColumn`, `secondColumn`), PRIMARY KEY (`myColumn`), FOREIGN KEY (`myColumn`) REFERENCES "Bar" ("id")) ENGINE=InnoDB;', + postgres: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE REFERENCES "Bar" ("id"), "secondColumn" TEXT, CONSTRAINT "my_table_my_column_second_column" UNIQUE ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn"));', + mssql: `IF OBJECT_ID(N'[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([myColumn] DATE, [secondColumn] TEXT, CONSTRAINT [my_table_my_column_second_column] UNIQUE ([myColumn], [secondColumn]), PRIMARY KEY ([myColumn]), FOREIGN KEY ([myColumn]) REFERENCES "Bar" ("id"));`, + snowflake: + 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT, UNIQUE "uniq_myTable_myColumn_secondColumn" ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn"), FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id"));', + db2: 'CREATE TABLE IF NOT EXISTS "myTable" ("myColumn" DATE, "secondColumn" TEXT NOT NULL, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn"), FOREIGN KEY ("myColumn") REFERENCES "Bar" ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "myTable" ("myColumn" DATE REFERENCES "Bar" ("id"), "secondColumn" TEXT, CONSTRAINT "uniq_myTable_myColumn_secondColumn" UNIQUE ("myColumn", "secondColumn"), PRIMARY KEY ("myColumn")); END`, + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/describe-table-query.test.ts b/packages/core/test/unit/query-generator/describe-table-query.test.ts new file mode 100644 index 000000000000..84ab12a60bc2 --- /dev/null +++ b/packages/core/test/unit/query-generator/describe-table-query.test.ts @@ -0,0 +1,479 @@ +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('QueryGenerator#describeTableQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query to describe a table', () => { + expectsql(() => queryGenerator.describeTableQuery('myTable'), { + default: 'SHOW FULL COLUMNS FROM [myTable];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'myTable' AND c.table_schema = 'public'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'myTable' AND t.TABLE_SCHEMA = N'dbo'`, + sqlite3: 'PRAGMA TABLE_INFO(`myTable`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'myTable' AND TABSCHEMA = 'DB2INST1'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = CURRENT SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'myTable'`, + }); + }); + + it('produces a query to describe a table from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.describeTableQuery(MyModel), { + default: 'SHOW FULL COLUMNS FROM [MyModels];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'MyModels' AND c.table_schema = 'public'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'MyModels' AND t.TABLE_SCHEMA = N'dbo'`, + sqlite3: 'PRAGMA TABLE_INFO(`MyModels`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'MyModels' AND TABSCHEMA = 'DB2INST1'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = CURRENT SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'MyModels'`, + }); + }); + + it('produces a query to describe a table from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.describeTableQuery(myDefinition), { + default: 'SHOW FULL COLUMNS FROM [MyModels];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'MyModels' AND c.table_schema = 'public'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'MyModels' AND t.TABLE_SCHEMA = N'dbo'`, + sqlite3: 'PRAGMA TABLE_INFO(`MyModels`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'MyModels' AND TABSCHEMA = 'DB2INST1'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = CURRENT SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'MyModels'`, + }); + }); + + it('produces a query to describe a table with schema in tableName object', () => { + expectsql( + () => queryGenerator.describeTableQuery({ tableName: 'myTable', schema: 'mySchema' }), + { + default: 'SHOW FULL COLUMNS FROM [mySchema].[myTable];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'myTable' AND c.table_schema = 'mySchema'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'myTable' AND t.TABLE_SCHEMA = N'mySchema'`, + sqlite3: 'PRAGMA TABLE_INFO(`mySchema.myTable`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = 'mySchema' + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'myTable'`, + }, + ); + }); + + it('produces a query to describe a table with default schema in tableName object', () => { + expectsql( + () => + queryGenerator.describeTableQuery({ + tableName: 'myTable', + schema: dialect.getDefaultSchema(), + }), + { + default: 'SHOW FULL COLUMNS FROM [myTable];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'myTable' AND c.table_schema = 'public'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'myTable' AND t.TABLE_SCHEMA = N'dbo'`, + sqlite3: 'PRAGMA TABLE_INFO(`myTable`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'myTable' AND TABSCHEMA = 'DB2INST1'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = CURRENT SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'myTable'`, + }, + ); + }); + + it('produces a query to describe a table from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.describeTableQuery('myTable'), { + default: 'SHOW FULL COLUMNS FROM [mySchema].[myTable];', + postgres: `SELECT + pk.constraint_type as "Constraint", + c.column_name as "Field", + c.column_default as "Default", + c.is_nullable as "Null", + (CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type", + (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", + (SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" + FROM information_schema.columns c + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + WHERE c.table_name = 'myTable' AND c.table_schema = 'mySchema'`, + mssql: `SELECT + c.COLUMN_NAME AS 'Name', + c.DATA_TYPE AS 'Type', + c.CHARACTER_MAXIMUM_LENGTH AS 'Length', + c.IS_NULLABLE as 'IsNull', + COLUMN_DEFAULT AS 'Default', + pk.CONSTRAINT_TYPE AS 'Constraint', + COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', + 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 + 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 + ON pk.table_schema=c.table_schema + AND pk.table_name=c.table_name + AND pk.column_name=c.column_name + INNER JOIN sys.columns AS sc + ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name + LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id + AND prop.minor_id = sc.column_id + AND prop.name = 'MS_Description' + WHERE t.TABLE_NAME = N'myTable' AND t.TABLE_SCHEMA = N'mySchema'`, + sqlite3: 'PRAGMA TABLE_INFO(`mySchema.myTable`)', + db2: `SELECT COLNAME AS "Name", TABNAME AS "Table", TABSCHEMA AS "Schema", + TYPENAME AS "Type", LENGTH AS "Length", SCALE AS "Scale", NULLS AS "IsNull", + DEFAULT AS "Default", COLNO AS "Colno", IDENTITY AS "IsIdentity", KEYSEQ AS "KeySeq", + REMARKS AS "Comment" FROM SYSCAT.COLUMNS WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema'`, + ibmi: `SELECT + QSYS2.SYSCOLUMNS.*, + QSYS2.SYSCST.CONSTRAINT_NAME, + QSYS2.SYSCST.CONSTRAINT_TYPE + FROM QSYS2.SYSCOLUMNS + LEFT OUTER JOIN QSYS2.SYSCSTCOL + ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA + AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME + AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME + LEFT JOIN QSYS2.SYSCST + ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME + WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = 'mySchema' + AND QSYS2.SYSCOLUMNS.TABLE_NAME = 'myTable'`, + }); + }); + + it('produces a query to describe a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.describeTableQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: 'PRAGMA TABLE_INFO(`mySchemacustommyTable`)', + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/drop-database-query.test.ts b/packages/core/test/unit/query-generator/drop-database-query.test.ts new file mode 100644 index 000000000000..680dd080d4ee --- /dev/null +++ b/packages/core/test/unit/query-generator/drop-database-query.test.ts @@ -0,0 +1,36 @@ +import { + allowDeprecationsInSuite, + createSequelizeInstance, + expectsql, + getTestDialect, + sequelize, +} from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Databases are not supported in ${dialectName}.`); + +describe('QueryGenerator#dropDatabaseQuery', () => { + allowDeprecationsInSuite(['SEQUELIZE0023']); + + const queryGenerator = sequelize.queryGenerator; + + it('produces a DROP DATABASE query in supported dialects', () => { + expectsql(() => queryGenerator.dropDatabaseQuery('myDatabase'), { + default: notSupportedError, + 'mssql postgres snowflake': 'DROP DATABASE IF EXISTS [myDatabase]', + }); + }); + + it('omits quotes if quoteIdentifiers is false', async () => { + const noQuoteQueryGenerator = createSequelizeInstance({ + quoteIdentifiers: false, + }).queryGenerator; + + expectsql(() => noQuoteQueryGenerator.dropDatabaseQuery('myDatabase'), { + default: notSupportedError, + mssql: 'DROP DATABASE IF EXISTS [myDatabase]', + 'postgres snowflake': 'DROP DATABASE IF EXISTS myDatabase', + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/drop-schema-query.test.ts b/packages/core/test/unit/query-generator/drop-schema-query.test.ts new file mode 100644 index 000000000000..12c5623df622 --- /dev/null +++ b/packages/core/test/unit/query-generator/drop-schema-query.test.ts @@ -0,0 +1,48 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Schemas are not supported in ${dialectName}.`); + +describe('QueryGenerator#dropSchemaQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a DROP SCHEMA query in supported dialects', () => { + expectsql(() => queryGenerator.dropSchemaQuery('mySchema'), { + default: 'DROP SCHEMA [mySchema]', + db2: 'DROP SCHEMA "mySchema" RESTRICT', + sqlite3: notSupportedError, + }); + }); + + it('produces a DROP SCHEMA IF EXISTS query in supported dialects', () => { + expectsql(() => queryGenerator.dropSchemaQuery('mySchema', { ifExists: true }), { + default: 'DROP SCHEMA IF EXISTS [mySchema]', + 'db2 mssql': buildInvalidOptionReceivedError('dropSchemaQuery', dialectName, ['ifExists']), + sqlite3: notSupportedError, + }); + }); + + it('produces a DROP SCHEMA CASCADE query in supported dialects', () => { + expectsql(() => queryGenerator.dropSchemaQuery('mySchema', { cascade: true }), { + default: 'DROP SCHEMA [mySchema] CASCADE', + 'db2 mariadb mssql mysql': buildInvalidOptionReceivedError('dropSchemaQuery', dialectName, [ + 'cascade', + ]), + sqlite3: notSupportedError, + }); + }); + + it('produces a DROP SCHEMA IF EXISTS CASCADE query in supported dialects', () => { + expectsql(() => queryGenerator.dropSchemaQuery('mySchema', { cascade: true, ifExists: true }), { + default: 'DROP SCHEMA IF EXISTS [mySchema] CASCADE', + 'db2 mssql': buildInvalidOptionReceivedError('dropSchemaQuery', dialectName, [ + 'cascade', + 'ifExists', + ]), + 'mariadb mysql': buildInvalidOptionReceivedError('dropSchemaQuery', dialectName, ['cascade']), + sqlite3: notSupportedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/drop-table-query.test.ts b/packages/core/test/unit/query-generator/drop-table-query.test.ts new file mode 100644 index 000000000000..de34dd6df782 --- /dev/null +++ b/packages/core/test/unit/query-generator/drop-table-query.test.ts @@ -0,0 +1,85 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); +const dialect = sequelize.dialect; + +describe('QueryGenerator#dropTableQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query that drops a table', () => { + expectsql(() => queryGenerator.dropTableQuery('myTable'), { + default: `DROP TABLE IF EXISTS [myTable]`, + }); + }); + + it('produces a query that drops a table with cascade', () => { + expectsql(() => queryGenerator.dropTableQuery('myTable', { cascade: true }), { + default: buildInvalidOptionReceivedError('dropTableQuery', dialectName, ['cascade']), + 'postgres snowflake': `DROP TABLE IF EXISTS "myTable" CASCADE`, + }); + }); + + it('produces a query that drops a table from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.dropTableQuery(MyModel), { + default: `DROP TABLE IF EXISTS [MyModels]`, + }); + }); + + it('produces a query that drops a table from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.dropTableQuery(myDefinition), { + default: `DROP TABLE IF EXISTS [MyModels]`, + }); + }); + + it('produces a query that drops a table with schema', () => { + expectsql(() => queryGenerator.dropTableQuery({ tableName: 'myTable', schema: 'mySchema' }), { + default: `DROP TABLE IF EXISTS [mySchema].[myTable]`, + sqlite3: 'DROP TABLE IF EXISTS `mySchema.myTable`', + }); + }); + + it('produces a query that drops a table with default schema', () => { + expectsql( + () => + queryGenerator.dropTableQuery({ tableName: 'myTable', schema: dialect.getDefaultSchema() }), + { + default: `DROP TABLE IF EXISTS [myTable]`, + }, + ); + }); + + it('produces a query that drops a table from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.dropTableQuery('myTable'), { + default: `DROP TABLE IF EXISTS [mySchema].[myTable]`, + sqlite3: 'DROP TABLE IF EXISTS `mySchema.myTable`', + }); + }); + + it('produces a query that drops a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.dropTableQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: 'DROP TABLE IF EXISTS `mySchemacustommyTable`', + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/get-constraint-snippet.test.ts b/packages/core/test/unit/query-generator/get-constraint-snippet.test.ts new file mode 100644 index 000000000000..64af232085bb --- /dev/null +++ b/packages/core/test/unit/query-generator/get-constraint-snippet.test.ts @@ -0,0 +1,829 @@ +import { Deferrable, Op } from '@sequelize/core'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const checkNotSupportedError = new Error( + `Check constraints are not supported by ${dialect.name} dialect`, +); +const defaultNotSupportedError = new Error( + `Default constraints are not supported by ${dialect.name} dialect`, +); +const deferrableNotSupportedError = new Error( + `Deferrable constraints are not supported by ${dialect.name} dialect`, +); +const onUpdateNotSupportedError = new Error( + `Foreign key constraint with onUpdate is not supported by ${dialect.name} dialect`, +); + +describe('QueryGeneratorInternal#getConstraintSnippet', () => { + const queryGenerator = sequelize.queryGenerator; + const internals = queryGenerator.__TEST__getInternals(); + + it('throws an error if invalid type', () => { + expectsql( + // @ts-expect-error -- We're testing invalid options + () => internals.getConstraintSnippet('myTable', { type: 'miss-typed', fields: ['otherId'] }), + { + default: new Error( + `Constraint type miss-typed is not supported by ${dialect.name} dialect`, + ), + }, + ); + }); + + it('throws an error if field.attribute is used', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'UNIQUE', + fields: [{ attribute: 'otherId', name: 'otherId' }], + }), + { + default: new Error( + 'The field.attribute property has been removed. Use the field.name property instead', + ), + }, + ); + }); + + describe('CHECK constraints', () => { + it('generates a constraint snippet for a check constraint with a name', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'check', + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'CONSTRAINT [check] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint with an array of values', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'check', + type: 'CHECK', + fields: ['role'], + where: { age: ['admin', 'user', 'guest'] }, + }), + { + default: `CONSTRAINT [check] CHECK ([age] IN ('admin', 'user', 'guest'))`, + mssql: `CONSTRAINT [check] CHECK ([age] IN (N'admin', N'user', N'guest'))`, + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + internals.getConstraintSnippet(MyModel, { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'CONSTRAINT [MyModels_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + internals.getConstraintSnippet(myDefinition, { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'CONSTRAINT [MyModels_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint with schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + default: 'CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint with default schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + default: 'CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const internalsSchema = sequelizeSchema.queryGenerator.__TEST__getInternals(); + + expectsql( + () => + internalsSchema.getConstraintSnippet('myTable', { + type: 'CHECK', + fields: ['age'], + where: { age: { [Op.gte]: 10 } }, + }), + { + default: 'CONSTRAINT [myTable_age_ck] CHECK ([age] >= 10)', + snowflake: checkNotSupportedError, + }, + ); + }); + + it('generates a constraint snippet for a check constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'CHECK', fields: ['age'], where: { age: { [Op.gte]: 10 } } }, + ), + { + sqlite3: 'CONSTRAINT `myTable_age_ck` CHECK (`age` >= 10)', + }, + ); + }); + }); + + describe('DEFAULT constraints', () => { + it('generates a constraint snippet for a default constraint with a name', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'default', + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [default] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + internals.getConstraintSnippet(MyModel, { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [MyModels_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + internals.getConstraintSnippet(myDefinition, { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [MyModels_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint with schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint with default schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const internalsSchema = sequelizeSchema.queryGenerator.__TEST__getInternals(); + + expectsql( + () => + internalsSchema.getConstraintSnippet('myTable', { + type: 'DEFAULT', + fields: ['role'], + defaultValue: 'guest', + }), + { + default: defaultNotSupportedError, + mssql: `CONSTRAINT [myTable_role_df] DEFAULT (N'guest') FOR [role]`, + }, + ); + }); + + it('generates a constraint snippet for a default constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'DEFAULT', fields: ['role'], defaultValue: 'guest' }, + ), + { + sqlite3: defaultNotSupportedError, + }, + ); + }); + }); + + describe('UNIQUE constraints', () => { + it('generates a constraint snippet for a unique constraint with a name', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'unique', + type: 'UNIQUE', + fields: ['username'], + }), + { + default: `CONSTRAINT [unique] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a deferred unique constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'UNIQUE', + fields: ['username'], + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + 'postgres snowflake': `CONSTRAINT [myTable_username_uk] UNIQUE ([username]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint with multiple columns', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'UNIQUE', + fields: ['first_name', 'last_name'], + }), + { + default: `CONSTRAINT [myTable_first_name_last_name_uk] UNIQUE ([first_name], [last_name])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint', () => { + expectsql( + () => internals.getConstraintSnippet('myTable', { type: 'UNIQUE', fields: ['username'] }), + { + default: `CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => internals.getConstraintSnippet(MyModel, { type: 'UNIQUE', fields: ['username'] }), + { + default: `CONSTRAINT [MyModels_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + internals.getConstraintSnippet(myDefinition, { type: 'UNIQUE', fields: ['username'] }), + { + default: `CONSTRAINT [MyModels_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint with schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + default: `CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint with unique schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + default: `CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const internalsSchema = sequelizeSchema.queryGenerator.__TEST__getInternals(); + + expectsql( + () => + internalsSchema.getConstraintSnippet('myTable', { type: 'UNIQUE', fields: ['username'] }), + { + default: `CONSTRAINT [myTable_username_uk] UNIQUE ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a unique constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'UNIQUE', fields: ['username'] }, + ), + { + sqlite3: 'CONSTRAINT `myTable_username_uk` UNIQUE (`username`)', + }, + ); + }); + }); + + describe('FOREIGN KEY constraints', () => { + it('generates a constraint snippet for a foreign key constraint with a name', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'foreign key', + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `CONSTRAINT [foreign key] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + }, + ); + }); + + it('generates a constraint snippet for a deferred foreign key constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + 'postgres snowflake': `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a constraint snippet for a composite foreign key constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId', 'someId'], + references: { table: 'otherTable', fields: ['id', 'someId'] }, + }), + { + default: `CONSTRAINT [myTable_otherId_someId_otherTable_fk] FOREIGN KEY ([otherId], [someId]) REFERENCES [otherTable] ([id], [someId])`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with on delete', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + onDelete: 'CASCADE', + }), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) ON DELETE CASCADE`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with on update', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + onUpdate: 'CASCADE', + }), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id]) ON UPDATE CASCADE`, + 'db2 ibmi': onUpdateNotSupportedError, + }, + ); + }); + + it('throws an error if no references is defined', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { type: 'FOREIGN KEY', fields: ['otherId'] }), + { + default: new Error( + 'Invalid foreign key constraint options. `references` object with `table` and `field` must be specified', + ), + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + const OtherModel = sequelize.define('OtherModel', {}); + + expectsql( + () => + internals.getConstraintSnippet(MyModel, { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: OtherModel, field: 'id' }, + }), + { + default: `CONSTRAINT [MyModels_otherId_OtherModels_fk] FOREIGN KEY ([otherId]) REFERENCES [OtherModels] ([id])`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + const OtherModel = sequelize.define('OtherModel', {}); + const otherDefinition = OtherModel.modelDefinition; + + expectsql( + () => + internals.getConstraintSnippet(myDefinition, { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: otherDefinition, field: 'id' }, + }), + { + default: `CONSTRAINT [MyModels_otherId_OtherModels_fk] FOREIGN KEY ([otherId]) REFERENCES [OtherModels] ([id])`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema' }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: { tableName: 'otherTable', schema: 'mySchema' }, field: 'id' }, + }, + ), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [mySchema].[otherTable] ([id])`, + sqlite3: + 'CONSTRAINT `myTable_otherId_otherTable_fk` FOREIGN KEY (`otherId`) REFERENCES `mySchema.otherTable` (`id`)', + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with foreign key schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { + table: { tableName: 'otherTable', schema: dialect.getDefaultSchema() }, + field: 'id', + }, + }, + ), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [otherTable] ([id])`, + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const internalsSchema = sequelizeSchema.queryGenerator.__TEST__getInternals(); + + expectsql( + () => + internalsSchema.getConstraintSnippet('myTable', { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { table: 'otherTable', field: 'id' }, + }), + { + default: `CONSTRAINT [myTable_otherId_otherTable_fk] FOREIGN KEY ([otherId]) REFERENCES [mySchema].[otherTable] ([id])`, + sqlite3: + 'CONSTRAINT `myTable_otherId_otherTable_fk` FOREIGN KEY (`otherId`) REFERENCES `mySchema.otherTable` (`id`)', + }, + ); + }); + + it('generates a constraint snippet for a foreign key constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { + type: 'FOREIGN KEY', + fields: ['otherId'], + references: { + table: { tableName: 'otherTable', schema: 'mySchema', delimiter: 'custom' }, + field: 'id', + }, + }, + ), + { + sqlite3: + 'CONSTRAINT `myTable_otherId_otherTable_fk` FOREIGN KEY (`otherId`) REFERENCES `mySchemacustomotherTable` (`id`)', + }, + ); + }); + }); + + describe('PRIMARY KEY constraints', () => { + it('generates a constraint snippet for a primary key constraint with a name', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + name: 'primary key', + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `CONSTRAINT [primary key] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a deferred primary key constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'PRIMARY KEY', + fields: ['username'], + deferrable: Deferrable.INITIALLY_IMMEDIATE, + }), + { + default: deferrableNotSupportedError, + 'postgres snowflake': `CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username]) DEFERRABLE INITIALLY IMMEDIATE`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint with multiple columns', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { + type: 'PRIMARY KEY', + fields: ['first_name', 'last_name'], + }), + { + default: `CONSTRAINT [myTable_first_name_last_name_pk] PRIMARY KEY ([first_name], [last_name])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint', () => { + expectsql( + () => + internals.getConstraintSnippet('myTable', { type: 'PRIMARY KEY', fields: ['username'] }), + { + default: `CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql( + () => + internals.getConstraintSnippet(MyModel, { type: 'PRIMARY KEY', fields: ['username'] }), + { + default: `CONSTRAINT [MyModels_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql( + () => + internals.getConstraintSnippet(myDefinition, { + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `CONSTRAINT [MyModels_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint with schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema' }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + default: `CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint with primary key schema', () => { + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + default: `CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint with globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const internalsSchema = sequelizeSchema.queryGenerator.__TEST__getInternals(); + + expectsql( + () => + internalsSchema.getConstraintSnippet('myTable', { + type: 'PRIMARY KEY', + fields: ['username'], + }), + { + default: `CONSTRAINT [myTable_username_pk] PRIMARY KEY ([username])`, + }, + ); + }); + + it('generates a constraint snippet for a primary key constraint with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + internals.getConstraintSnippet( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { type: 'PRIMARY KEY', fields: ['username'] }, + ), + { + sqlite3: 'CONSTRAINT `myTable_username_pk` PRIMARY KEY (`username`)', + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/insert-query.test.ts b/packages/core/test/unit/query-generator/insert-query.test.ts new file mode 100644 index 000000000000..35078ad7c33b --- /dev/null +++ b/packages/core/test/unit/query-generator/insert-query.test.ts @@ -0,0 +1,344 @@ +import { DataTypes, ParameterStyle, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#insertQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + // you'll find more replacement tests in query-generator tests + it('parses named replacements in literals', () => { + const { User } = vars; + + const { query, bind } = queryGenerator.insertQuery( + User.table, + { + firstName: literal(':name'), + }, + {}, + { + replacements: { + name: 'Zoe', + }, + }, + ); + + expectsql(query, { + default: `INSERT INTO [Users] ([firstName]) VALUES ('Zoe');`, + mssql: `INSERT INTO [Users] ([firstName]) VALUES (N'Zoe');`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ('Zoe'));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ('Zoe'))`, + }); + expect(bind).to.deep.eq({}); + }); + + it('supports named bind parameters in literals', () => { + const { User } = vars; + + const { query, bind } = queryGenerator.insertQuery(User.table, { + firstName: 'John', + lastName: literal('$lastName'), + username: 'jd', + }); + + expectsql(query, { + default: `INSERT INTO [Users] ([firstName],[lastName],[username]) VALUES ($sequelize_1,$lastName,$sequelize_2);`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ($sequelize_1,$lastName,$sequelize_2));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ($sequelize_1,$lastName,$sequelize_2))`, + }); + + expect(bind).to.deep.eq({ + sequelize_1: 'John', + sequelize_2: 'jd', + }); + }); + + it('parses positional bind parameters in literals', () => { + const { User } = vars; + + const { query, bind } = queryGenerator.insertQuery(User.table, { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }); + + // lastName's bind position being changed from $1 to $2 is intentional: bind array order must match their order in the query in some dialects. + expectsql(query, { + default: `INSERT INTO [Users] ([firstName],[lastName],[username]) VALUES ($sequelize_1,$1,$sequelize_2);`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ($sequelize_1,$1,$sequelize_2));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ($sequelize_1,$1,$sequelize_2))`, + }); + expect(bind).to.deep.eq({ + sequelize_1: 'John', + sequelize_2: 'jd', + }); + }); + + it('throws an error if the bindParam option is used', () => { + const { User } = vars; + + expect(() => { + queryGenerator.insertQuery( + User.table, + { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }, + {}, + // @ts-expect-error -- intentionally testing deprecated option + { bindParam: false }, + ); + }).to.throw('The bindParam option has been removed. Use parameterStyle instead.'); + }); + + it('parses bind parameters in literals even with parameterStyle: REPLACEMENT', () => { + const { User } = vars; + + const { query, bind } = queryGenerator.insertQuery( + User.table, + { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }, + {}, + { + parameterStyle: ParameterStyle.REPLACEMENT, + }, + ); + + expectsql(query, { + default: `INSERT INTO [Users] ([firstName],[lastName],[username]) VALUES ('John',$1,'jd');`, + mssql: `INSERT INTO [Users] ([firstName],[lastName],[username]) VALUES (N'John',$1,N'jd');`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ('John',$1,'jd'));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName","username") VALUES ('John',$1,'jd'))`, + }); + expect(bind).to.be.undefined; + }); + + // This test was added due to a regression where these values were being converted to strings + it('binds number values', () => { + if (!sequelize.dialect.supports.dataTypes.ARRAY) { + return; + } + + const { User } = vars; + + const { query, bind } = queryGenerator.insertQuery(User.tableName, { + numbers: [1, 2, 3], + }); + + expectsql(query, { + default: `INSERT INTO "Users" ([numbers]) VALUES ($sequelize_1);`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("numbers") VALUES ($sequelize_1));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("numbers") VALUES ($sequelize_1))`, + }); + expect(bind).to.deep.eq({ + sequelize_1: [1, 2, 3], + }); + }); + + describe('returning', () => { + it('supports returning: true', () => { + const { User } = vars; + + const { query } = queryGenerator.insertQuery( + User.table, + { + firstName: 'John', + }, + User.getAttributes(), + { + returning: true, + }, + ); + + expectsql(query, { + default: `INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1) RETURNING [id], [firstName];`, + // TODO: insertQuery should throw if returning is not supported + 'mysql mariadb': `INSERT INTO \`Users\` (\`firstName\`) VALUES ($sequelize_1);`, + // TODO: insertQuery should throw if returning is not supported + snowflake: `INSERT INTO "Users" ("firstName") VALUES ($sequelize_1);`, + mssql: + 'INSERT INTO [Users] ([firstName]) OUTPUT INSERTED.[id], INSERTED.[firstName] VALUES ($sequelize_1);', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1));', + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1))', + }); + }); + + it('supports array of strings (column names)', () => { + const { User } = vars; + + const { query } = queryGenerator.insertQuery( + User.table, + { + firstName: 'John', + }, + User.getAttributes(), + { + returning: ['*', 'myColumn'], + }, + ); + + expectsql(query, { + default: `INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1) RETURNING [*], [myColumn];`, + // TODO: insertQuery should throw if returning is not supported + 'mysql mariadb': `INSERT INTO \`Users\` (\`firstName\`) VALUES ($sequelize_1);`, + // TODO: insertQuery should throw if returning is not supported + snowflake: `INSERT INTO "Users" ("firstName") VALUES ($sequelize_1);`, + mssql: + 'INSERT INTO [Users] ([firstName]) OUTPUT INSERTED.[*], INSERTED.[myColumn] VALUES ($sequelize_1);', + // TODO: should only select specified columns + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1));', + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1))', + }); + }); + + it('supports array of literals', () => { + const { User } = vars; + + expectsql( + () => { + return queryGenerator.insertQuery( + User.table, + { + firstName: 'John', + }, + User.getAttributes(), + { + returning: [literal('*')], + }, + ).query; + }, + { + default: `INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1) RETURNING *;`, + // TODO: insertQuery should throw if returning is not supported + 'mysql mariadb': `INSERT INTO \`Users\` (\`firstName\`) VALUES ($sequelize_1);`, + // TODO: insertQuery should throw if returning is not supported + snowflake: `INSERT INTO "Users" ("firstName") VALUES ($sequelize_1);`, + mssql: new Error( + 'literal() cannot be used in the "returning" option array in mssql. Use col(), or a string instead.', + ), + // TODO: should only select specified columns + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1));', + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1))', + }, + ); + }); + + it('binds date values', () => { + const result = queryGenerator.insertQuery('myTable', { + birthday: new Date('2011-03-27T10:01:55Z'), + }); + expectsql(result, { + query: { + default: 'INSERT INTO [myTable] ([birthday]) VALUES ($sequelize_1);', + 'db2 ibmi': + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("birthday") VALUES ($sequelize_1));', + }, + bind: { + mysql: { + sequelize_1: '2011-03-27 10:01:55.000', + }, + mariadb: { + sequelize_1: '2011-03-27 10:01:55.000', + }, + db2: { + sequelize_1: '2011-03-27 10:01:55.000', + }, + ibmi: { + sequelize_1: '2011-03-27 10:01:55.000', + }, + snowflake: { + sequelize_1: '2011-03-27 10:01:55.000', + }, + sqlite3: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + }, + postgres: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + }, + mssql: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + }, + }, + }); + }); + + it('binds boolean values', () => { + const result = queryGenerator.insertQuery('myTable', { positive: true, negative: false }); + expectsql(result, { + query: { + default: + 'INSERT INTO [myTable] ([positive],[negative]) VALUES ($sequelize_1,$sequelize_2);', + 'db2 ibmi': + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("positive","negative") VALUES ($sequelize_1,$sequelize_2));', + }, + bind: { + sqlite3: { + sequelize_1: 1, + sequelize_2: 0, + }, + mysql: { + sequelize_1: 1, + sequelize_2: 0, + }, + mariadb: { + sequelize_1: 1, + sequelize_2: 0, + }, + mssql: { + sequelize_1: 1, + sequelize_2: 0, + }, + postgres: { + sequelize_1: true, + sequelize_2: false, + }, + db2: { + sequelize_1: true, + sequelize_2: false, + }, + ibmi: { + sequelize_1: 1, + sequelize_2: 0, + }, + snowflake: { + sequelize_1: true, + sequelize_2: false, + }, + }, + }); + }); + + // TODO: Should we ignore undefined values instead? undefined is closer to "missing property" than null + it('treats undefined as null', () => { + const { query, bind } = queryGenerator.insertQuery('myTable', { + value: undefined, + name: 'bar', + }); + expectsql(query, { + default: 'INSERT INTO [myTable] ([value],[name]) VALUES ($sequelize_1,$sequelize_2);', + 'db2 ibmi': + 'SELECT * FROM FINAL TABLE (INSERT INTO "myTable" ("value","name") VALUES ($sequelize_1,$sequelize_2));', + }); + + expect(bind).to.deep.eq({ + sequelize_1: null, + sequelize_2: 'bar', + }); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/json-path-extraction-query.test.ts b/packages/core/test/unit/query-generator/json-path-extraction-query.test.ts new file mode 100644 index 000000000000..7edfea4feed6 --- /dev/null +++ b/packages/core/test/unit/query-generator/json-path-extraction-query.test.ts @@ -0,0 +1,138 @@ +import { expectPerDialect, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const dialectName = dialect.name; + +const notSupportedError = new Error(`JSON Paths are not supported in ${dialectName}.`); + +describe('QueryGenerator#jsonPathExtractionQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + if (dialect.supports.jsonExtraction.quoted) { + it('creates a json extract operation (object)', () => { + // "jsonPathExtractionQuery" does not quote the first parameter, because the first parameter is *not* an identifier, + // it can be any SQL expression, e.g. a column name, a function call, a subquery, etc. + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + ['id'], + false, + ), + { + default: notSupportedError, + mariadb: `json_compact(json_extract(\`profile\`,'$.id'))`, + 'mysql sqlite3': `json_extract(\`profile\`,'$.id')`, + postgres: `"profile"->'id'`, + }, + ); + }); + + it('creates a json extract operation (array)', () => { + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + [0], + false, + ), + { + default: notSupportedError, + mariadb: `json_compact(json_extract(\`profile\`,'$[0]'))`, + 'mysql sqlite3': `json_extract(\`profile\`,'$[0]')`, + postgres: `"profile"->0`, + }, + ); + }); + + it('creates a nested json extract operation', () => { + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + ['id', 'username', 0, '0', 'name'], + false, + ), + { + default: notSupportedError, + mariadb: `json_compact(json_extract(\`profile\`,'$.id.username[0]."0".name'))`, + 'mysql sqlite3': `json_extract(\`profile\`,'$.id.username[0]."0".name')`, + postgres: `"profile"#>ARRAY['id','username','0','0','name']::VARCHAR(255)[]`, + }, + ); + }); + + it(`escapes characters such as ", $, and '`, () => { + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + [`"`, `'`, `$`], + false, + ), + { + default: notSupportedError, + mysql: `json_extract(\`profile\`,'$."\\\\""."\\'"."$"')`, + mariadb: `json_compact(json_extract(\`profile\`,'$."\\\\""."\\'"."$"'))`, + sqlite3: `json_extract(\`profile\`,'$."\\""."''"."$"')`, + postgres: `"profile"#>ARRAY['"','''','$']::VARCHAR(255)[]`, + }, + ); + }); + } + + if (dialect.supports.jsonExtraction.unquoted) { + it('creates a json extract+unquote operation (object)', () => { + // "jsonPathExtractionQuery" does not quote the first parameter, because the first parameter is *not* an identifier, + // it can be any SQL expression, e.g. a column name, a function call, a subquery, etc. + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + ['id'], + true, + ), + { + default: notSupportedError, + mssql: `JSON_VALUE([profile], N'$.id')`, + 'mariadb mysql sqlite3': `json_unquote(json_extract(\`profile\`,'$.id'))`, + postgres: `"profile"->>'id'`, + }, + ); + }); + + it('creates a json extract+unquote operation (array)', () => { + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + [0], + true, + ), + { + default: notSupportedError, + mssql: `JSON_VALUE([profile], N'$[0]')`, + 'mariadb mysql sqlite3': `json_unquote(json_extract(\`profile\`,'$[0]'))`, + postgres: `"profile"->>0`, + }, + ); + }); + + it('creates a nested json extract+unquote operation', () => { + expectPerDialect( + () => + queryGenerator.jsonPathExtractionQuery( + queryGenerator.quoteIdentifier('profile'), + ['id', 'username', 0, '0', 'name'], + true, + ), + { + default: notSupportedError, + mssql: `JSON_VALUE([profile], N'$.id.username[0]."0".name')`, + 'mysql mariadb sqlite3': `json_unquote(json_extract(\`profile\`,'$.id.username[0]."0".name'))`, + postgres: `"profile"#>>ARRAY['id','username','0','0','name']::VARCHAR(255)[]`, + }, + ); + }); + } +}); diff --git a/packages/core/test/unit/query-generator/list-databases-query.test.ts b/packages/core/test/unit/query-generator/list-databases-query.test.ts new file mode 100644 index 000000000000..9963cfec41b0 --- /dev/null +++ b/packages/core/test/unit/query-generator/list-databases-query.test.ts @@ -0,0 +1,27 @@ +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Databases are not supported in ${dialectName}.`); + +describe('QueryGenerator#listDatabasesQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query used to list schemas in supported dialects', () => { + expectsql(() => queryGenerator.listDatabasesQuery(), { + default: notSupportedError, + mssql: `SELECT [name] FROM sys.databases WHERE [name] NOT IN (N'master', N'model', N'msdb', N'tempdb')`, + postgres: `SELECT datname AS "name" FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres')`, + snowflake: `SELECT DATABASE_NAME as "name", * FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES WHERE "name" NOT IN ('SNOWFLAKE', 'SNOWFLAKE$GDS')`, + }); + }); + + it('produces a query used to list schemas in supported dialects with skip option', () => { + expectsql(() => queryGenerator.listDatabasesQuery({ skip: ['sample_db'] }), { + default: notSupportedError, + mssql: `SELECT [name] FROM sys.databases WHERE [name] NOT IN (N'master', N'model', N'msdb', N'tempdb', N'sample_db')`, + postgres: `SELECT datname AS "name" FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'sample_db')`, + snowflake: `SELECT DATABASE_NAME as "name", * FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES WHERE "name" NOT IN ('SNOWFLAKE', 'SNOWFLAKE$GDS') AND UPPER("name") NOT IN (UPPER('sample_db'))`, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/list-schemas-query.test.ts b/packages/core/test/unit/query-generator/list-schemas-query.test.ts new file mode 100644 index 000000000000..c63ae40a7753 --- /dev/null +++ b/packages/core/test/unit/query-generator/list-schemas-query.test.ts @@ -0,0 +1,35 @@ +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +const notSupportedError = new Error(`Schemas are not supported in ${dialectName}.`); + +describe('QueryGenerator#listSchemasQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query used to list schemas in supported dialects', () => { + expectsql(() => queryGenerator.listSchemasQuery(), { + db2: `SELECT SCHEMANAME AS "schema" FROM SYSCAT.SCHEMATA WHERE SCHEMANAME NOT LIKE 'SYS%' AND SCHEMANAME NOT IN ('ERRORSCHEMA', 'NULLID', 'SQLJ')`, + ibmi: `SELECT DISTINCT SCHEMA_NAME AS "schema" FROM QSYS2.SYSSCHEMAAUTH WHERE GRANTEE = CURRENT USER AND SCHEMA_NAME NOT LIKE 'Q%' AND SCHEMA_NAME NOT LIKE 'SYS%'`, + mssql: `SELECT [name] AS [schema] FROM sys.schemas WHERE [name] NOT IN (N'dbo', N'guest', N'db_accessadmin', N'db_backupoperator', N'db_datareader', N'db_datawriter', N'db_ddladmin', N'db_denydatareader', N'db_denydatawriter', N'db_owner', N'db_securityadmin', N'INFORMATION_SCHEMA', N'sys')`, + mysql: `SELECT SCHEMA_NAME AS \`schema\` FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys')`, + mariadb: `SELECT SCHEMA_NAME AS \`schema\` FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys')`, + sqlite3: notSupportedError, + postgres: `SELECT schema_name AS "schema" FROM information_schema.schemata WHERE schema_name !~ E'^pg_' AND schema_name NOT IN ('public', 'information_schema', 'tiger', 'tiger_data', 'topology')`, + snowflake: `SELECT SCHEMA_NAME AS "schema" FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'information_schema', 'performance_schema', 'sys')`, + }); + }); + + it('supports a skip option', () => { + expectsql(() => queryGenerator.listSchemasQuery({ skip: ['test', "Te'st2"] }), { + db2: `SELECT SCHEMANAME AS "schema" FROM SYSCAT.SCHEMATA WHERE SCHEMANAME NOT LIKE 'SYS%' AND SCHEMANAME NOT IN ('ERRORSCHEMA', 'NULLID', 'SQLJ', 'test', 'Te''st2')`, + ibmi: `SELECT DISTINCT SCHEMA_NAME AS "schema" FROM QSYS2.SYSSCHEMAAUTH WHERE GRANTEE = CURRENT USER AND SCHEMA_NAME NOT LIKE 'Q%' AND SCHEMA_NAME NOT LIKE 'SYS%' AND SCHEMA_NAME NOT IN ('test', 'Te''st2')`, + mssql: `SELECT [name] AS [schema] FROM sys.schemas WHERE [name] NOT IN (N'dbo', N'guest', N'db_accessadmin', N'db_backupoperator', N'db_datareader', N'db_datawriter', N'db_ddladmin', N'db_denydatareader', N'db_denydatawriter', N'db_owner', N'db_securityadmin', N'INFORMATION_SCHEMA', N'sys', N'test', N'Te''st2')`, + mysql: `SELECT SCHEMA_NAME AS \`schema\` FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys', 'test', 'Te\\'st2')`, + mariadb: `SELECT SCHEMA_NAME AS \`schema\` FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys', 'test', 'Te\\'st2')`, + sqlite3: notSupportedError, + postgres: `SELECT schema_name AS "schema" FROM information_schema.schemata WHERE schema_name !~ E'^pg_' AND schema_name NOT IN ('public', 'information_schema', 'tiger', 'tiger_data', 'topology', 'test', 'Te''st2')`, + snowflake: `SELECT SCHEMA_NAME AS "schema" FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'information_schema', 'performance_schema', 'sys', 'test', 'Te''st2')`, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/list-tables-query.test.ts b/packages/core/test/unit/query-generator/list-tables-query.test.ts new file mode 100644 index 000000000000..536e5a7fba0a --- /dev/null +++ b/packages/core/test/unit/query-generator/list-tables-query.test.ts @@ -0,0 +1,48 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#listTablesQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query that lists all tables', () => { + expectsql(() => queryGenerator.listTablesQuery(), { + db2: `SELECT TABNAME AS "tableName", TRIM(TABSCHEMA) AS "schema" FROM SYSCAT.TABLES WHERE TYPE = 'T' AND TABSCHEMA NOT LIKE 'SYS%' AND TABSCHEMA NOT IN ('ERRORSCHEMA', 'NULLID', 'SQLJ') ORDER BY TABSCHEMA, TABNAME`, + ibmi: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM QSYS2.SYSTABLES WHERE TABLE_TYPE = 'T' AND TABLE_SCHEMA NOT LIKE 'Q%' AND TABLE_SCHEMA NOT LIKE 'SYS%' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + mssql: `SELECT t.name AS [tableName], s.name AS [schema] FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.type = 'U' AND s.name NOT IN (N'db_accessadmin', N'db_backupoperator', N'db_datareader', N'db_datawriter', N'db_ddladmin', N'db_denydatareader', N'db_denydatawriter', N'db_owner', N'db_securityadmin', N'INFORMATION_SCHEMA', N'sys') ORDER BY s.name, t.name`, + mysql: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys') ORDER BY TABLE_SCHEMA, TABLE_NAME`, + sqlite3: `SELECT name AS \`tableName\` FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence'`, + mariadb: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'mysql', 'information_schema', 'performance_schema', 'sys') ORDER BY TABLE_SCHEMA, TABLE_NAME`, + postgres: `SELECT table_name AS "tableName", table_schema AS "schema" FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name != 'spatial_ref_sys' AND table_schema !~ E'^pg_' AND table_schema NOT IN ('information_schema', 'tiger', 'tiger_data', 'topology') ORDER BY table_schema, table_name`, + snowflake: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS', 'information_schema', 'performance_schema', 'sys') ORDER BY TABLE_SCHEMA, TABLE_NAME`, + }); + }); + + it('produces a query that lists all tables with a schema', () => { + expectsql(() => queryGenerator.listTablesQuery({ schema: 'mySchema' }), { + db2: `SELECT TABNAME AS "tableName", TRIM(TABSCHEMA) AS "schema" FROM SYSCAT.TABLES WHERE TYPE = 'T' AND TABSCHEMA = 'mySchema' ORDER BY TABSCHEMA, TABNAME`, + ibmi: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM QSYS2.SYSTABLES WHERE TABLE_TYPE = 'T' AND TABLE_SCHEMA = 'mySchema' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + mssql: `SELECT t.name AS [tableName], s.name AS [schema] FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.type = 'U' AND s.name = N'mySchema' ORDER BY s.name, t.name`, + mysql: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'mySchema' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + sqlite3: buildInvalidOptionReceivedError('listTablesQuery', 'sqlite3', ['schema']), + mariadb: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'mySchema' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + postgres: `SELECT table_name AS "tableName", table_schema AS "schema" FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name != 'spatial_ref_sys' AND table_schema = 'mySchema' ORDER BY table_schema, table_name`, + snowflake: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'mySchema' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + }); + }); + + it('produces a query that lists all tables with the default schema', () => { + expectsql( + () => queryGenerator.listTablesQuery({ schema: sequelize.dialect.getDefaultSchema() }), + { + db2: `SELECT TABNAME AS "tableName", TRIM(TABSCHEMA) AS "schema" FROM SYSCAT.TABLES WHERE TYPE = 'T' AND TABSCHEMA = 'DB2INST1' ORDER BY TABSCHEMA, TABNAME`, + ibmi: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM QSYS2.SYSTABLES WHERE TABLE_TYPE = 'T' AND TABLE_SCHEMA NOT LIKE 'Q%' AND TABLE_SCHEMA NOT LIKE 'SYS%' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + mssql: `SELECT t.name AS [tableName], s.name AS [schema] FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.type = 'U' AND s.name = N'dbo' ORDER BY s.name, t.name`, + mysql: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'sequelize_test' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + sqlite3: buildInvalidOptionReceivedError('listTablesQuery', 'sqlite3', ['schema']), + mariadb: `SELECT TABLE_NAME AS \`tableName\`, TABLE_SCHEMA AS \`schema\` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'sequelize_test' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + postgres: `SELECT table_name AS "tableName", table_schema AS "schema" FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name != 'spatial_ref_sys' AND table_schema = 'public' ORDER BY table_schema, table_name`, + snowflake: `SELECT TABLE_NAME AS "tableName", TABLE_SCHEMA AS "schema" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'PUBLIC' ORDER BY TABLE_SCHEMA, TABLE_NAME`, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/quote-identifier.test.ts b/packages/core/test/unit/query-generator/quote-identifier.test.ts new file mode 100644 index 000000000000..3804742fcd73 --- /dev/null +++ b/packages/core/test/unit/query-generator/quote-identifier.test.ts @@ -0,0 +1,24 @@ +import { expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#quoteIdentifier', () => { + const queryGenerator = sequelize.queryGenerator; + const TICK_RIGHT = sequelize.dialect.TICK_CHAR_RIGHT; + const TICK_LEFT = sequelize.dialect.TICK_CHAR_LEFT; + + it('escapes a value as an identifier', () => { + expectsql(queryGenerator.quoteIdentifier(`'myTable'.'Test'`), { + default: `['myTable'.'Test']`, + }); + }); + + it('escapes identifier quotes', () => { + expectsql( + queryGenerator.quoteIdentifier( + `${TICK_LEFT}myTable${TICK_RIGHT}.${TICK_LEFT}Test${TICK_RIGHT}`, + ), + { + default: `${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}myTable${TICK_RIGHT}${TICK_RIGHT}.${TICK_LEFT}${TICK_LEFT}Test${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT}`, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/remove-column-query.test.ts b/packages/core/test/unit/query-generator/remove-column-query.test.ts new file mode 100644 index 000000000000..d3327727c9b9 --- /dev/null +++ b/packages/core/test/unit/query-generator/remove-column-query.test.ts @@ -0,0 +1,105 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, getTestDialect, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const dialectName = getTestDialect(); +const notSupportedError = new Error(`removeColumnQuery is not supported in ${dialectName}.`); + +describe('QueryGenerator#removeColumnQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('generates a query that drops a column', () => { + expectsql(() => queryGenerator.removeColumnQuery('myTable', 'myColumn'), { + default: 'ALTER TABLE [myTable] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column with cascade', () => { + expectsql(() => queryGenerator.removeColumnQuery('myTable', 'myColumn', { cascade: true }), { + default: buildInvalidOptionReceivedError('removeColumnQuery', dialectName, ['cascade']), + 'db2 ibmi postgres': 'ALTER TABLE [myTable] DROP COLUMN [myColumn] CASCADE', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column with ifExists', () => { + expectsql(() => queryGenerator.removeColumnQuery('myTable', 'myColumn', { ifExists: true }), { + default: buildInvalidOptionReceivedError('removeColumnQuery', dialectName, ['ifExists']), + 'mariadb mssql postgres': 'ALTER TABLE [myTable] DROP COLUMN IF EXISTS [myColumn]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.removeColumnQuery(MyModel, 'myColumn'), { + default: 'ALTER TABLE [MyModels] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.removeColumnQuery(myDefinition, 'myColumn'), { + default: 'ALTER TABLE [MyModels] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column with schema', () => { + expectsql( + () => + queryGenerator.removeColumnQuery({ tableName: 'myTable', schema: 'mySchema' }, 'myColumn'), + { + default: 'ALTER TABLE [mySchema].[myTable] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that drops a column with default schema', () => { + expectsql( + () => + queryGenerator.removeColumnQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + 'myColumn', + ), + { + default: 'ALTER TABLE [myTable] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that drops a column from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.removeColumnQuery('myTable', 'myColumn'), { + default: 'ALTER TABLE [mySchema].[myTable] DROP COLUMN [myColumn]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.removeColumnQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + 'myColumn', + ), + { + sqlite3: notSupportedError, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/remove-constraint-query.test.ts b/packages/core/test/unit/query-generator/remove-constraint-query.test.ts new file mode 100644 index 000000000000..fc556e7563f4 --- /dev/null +++ b/packages/core/test/unit/query-generator/remove-constraint-query.test.ts @@ -0,0 +1,141 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error( + `Remove constraint queries are not supported by ${dialect.name} dialect`, +); + +describe('QueryGenerator#removeConstraintQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('generates a query that drops a constraint', () => { + expectsql(() => queryGenerator.removeConstraintQuery('myTable', 'myConstraint'), { + default: 'ALTER TABLE [myTable] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a constraint with IF EXISTS', () => { + expectsql( + () => queryGenerator.removeConstraintQuery('myTable', 'myConstraint', { ifExists: true }), + { + default: 'ALTER TABLE [myTable] DROP CONSTRAINT IF EXISTS [myConstraint]', + sqlite3: notSupportedError, + 'db2 ibmi mysql snowflake': buildInvalidOptionReceivedError( + 'removeConstraintQuery', + dialect.name, + ['ifExists'], + ), + }, + ); + }); + + it('generates a query that drops a constraint with CASCADE', () => { + expectsql( + () => queryGenerator.removeConstraintQuery('myTable', 'myConstraint', { cascade: true }), + { + default: buildInvalidOptionReceivedError('removeConstraintQuery', dialect.name, [ + 'cascade', + ]), + sqlite3: notSupportedError, + 'postgres snowflake': 'ALTER TABLE [myTable] DROP CONSTRAINT [myConstraint] CASCADE', + }, + ); + }); + + it('generates a query that drops a constraint with IF EXISTS and CASCADE', () => { + expectsql( + () => + queryGenerator.removeConstraintQuery('myTable', 'myConstraint', { + cascade: true, + ifExists: true, + }), + { + default: buildInvalidOptionReceivedError('removeConstraintQuery', dialect.name, [ + 'cascade', + ]), + postgres: 'ALTER TABLE "myTable" DROP CONSTRAINT IF EXISTS "myConstraint" CASCADE', + snowflake: buildInvalidOptionReceivedError('removeConstraintQuery', dialect.name, [ + 'ifExists', + ]), + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that drops a constraint from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.removeConstraintQuery(MyModel, 'myConstraint'), { + default: 'ALTER TABLE [MyModels] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a constraint from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.removeConstraintQuery(myDefinition, 'myConstraint'), { + default: 'ALTER TABLE [MyModels] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a constraint with schema', () => { + expectsql( + () => + queryGenerator.removeConstraintQuery( + { tableName: 'myTable', schema: 'mySchema' }, + 'myConstraint', + ), + { + default: 'ALTER TABLE [mySchema].[myTable] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that drops a constraint with default schema', () => { + expectsql( + () => + queryGenerator.removeConstraintQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + 'myConstraint', + ), + { + default: 'ALTER TABLE [myTable] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }, + ); + }); + + it('generates a query that drops a constraint from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.removeConstraintQuery('myTable', 'myConstraint'), { + default: 'ALTER TABLE [mySchema].[myTable] DROP CONSTRAINT [myConstraint]', + sqlite3: notSupportedError, + }); + }); + + it('generates a query that drops a column with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.removeConstraintQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + 'myConstraint', + ), + { + sqlite3: notSupportedError, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/remove-index-query.test.ts b/packages/core/test/unit/query-generator/remove-index-query.test.ts new file mode 100644 index 000000000000..8e28f0374c72 --- /dev/null +++ b/packages/core/test/unit/query-generator/remove-index-query.test.ts @@ -0,0 +1,216 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +const notImplementedError = new Error( + `removeIndexQuery has not been implemented in ${dialect.name}.`, +); + +describe('QueryGenerator#removeIndexQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a DROP INDEX query from a table', () => { + expectsql(() => queryGenerator.removeIndexQuery('myTable', 'user_foo_bar'), { + default: `DROP INDEX [user_foo_bar] ON [myTable]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "user_foo_bar"`, + postgres: `DROP INDEX "public"."user_foo_bar"`, + snowflake: notImplementedError, + }); + }); + + it('produces a DROP INDEX query from a table with attributes', () => { + expectsql(() => queryGenerator.removeIndexQuery('myTable', ['foo', 'bar']), { + default: `DROP INDEX [my_table_foo_bar] ON [myTable]`, + sqlite3: 'DROP INDEX `my_table_foo_bar`', + ibmi: `BEGIN DROP INDEX "my_table_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "my_table_foo_bar"`, + postgres: `DROP INDEX "public"."my_table_foo_bar"`, + snowflake: notImplementedError, + }); + }); + + it('produces a DROP INDEX with CONCURRENTLY query from a table', () => { + expectsql( + () => queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { concurrently: true }), + { + default: buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, [ + 'concurrently', + ]), + postgres: `DROP INDEX CONCURRENTLY "public"."user_foo_bar"`, + snowflake: notImplementedError, + }, + ); + }); + + it('produces a DROP INDEX with IF EXISTS query from a table', () => { + expectsql( + () => queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { ifExists: true }), + { + default: `DROP INDEX IF EXISTS [user_foo_bar] ON [myTable]`, + sqlite3: 'DROP INDEX IF EXISTS `user_foo_bar`', + postgres: `DROP INDEX IF EXISTS "public"."user_foo_bar"`, + ibmi: `BEGIN IF EXISTS (SELECT * FROM QSYS2.SYSINDEXES WHERE INDEX_NAME = "user_foo_bar") THEN DROP INDEX "user_foo_bar"; COMMIT; END IF; END`, + snowflake: notImplementedError, + 'db2 mysql': buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, [ + 'ifExists', + ]), + }, + ); + }); + + it('produces a DROP INDEX with CASCADE query from a table', () => { + expectsql(() => queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { cascade: true }), { + default: buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, ['cascade']), + postgres: `DROP INDEX "public"."user_foo_bar" CASCADE`, + snowflake: notImplementedError, + }); + }); + + it('produces a DROP INDEX with CASCADE and IF EXISTS query from a table', () => { + expectsql( + () => + queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { + cascade: true, + ifExists: true, + }), + { + default: `DROP INDEX IF EXISTS [user_foo_bar] ON [myTable] CASCADE`, + postgres: `DROP INDEX IF EXISTS "public"."user_foo_bar" CASCADE`, + snowflake: notImplementedError, + 'db2 mysql': buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, [ + 'cascade', + 'ifExists', + ]), + 'ibmi mariadb mssql sqlite3': buildInvalidOptionReceivedError( + 'removeIndexQuery', + dialect.name, + ['cascade'], + ), + }, + ); + }); + + it('produces a DROP INDEX with CONCURRENTLY and IF EXISTS query from a table', () => { + expectsql( + () => + queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { + concurrently: true, + ifExists: true, + }), + { + default: `DROP INDEX CONCURRENTLY IF EXISTS [user_foo_bar] ON [myTable]`, + postgres: `DROP INDEX CONCURRENTLY IF EXISTS "public"."user_foo_bar"`, + snowflake: notImplementedError, + 'db2 mysql': buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, [ + 'concurrently', + 'ifExists', + ]), + 'ibmi mariadb mssql sqlite3': buildInvalidOptionReceivedError( + 'removeIndexQuery', + dialect.name, + ['concurrently'], + ), + }, + ); + }); + + it('throws an error for DROP INDEX with CASCADE and CONCURRENTLY query from a table', () => { + expectsql( + () => + queryGenerator.removeIndexQuery('myTable', 'user_foo_bar', { + cascade: true, + concurrently: true, + }), + { + default: buildInvalidOptionReceivedError('removeIndexQuery', dialect.name, [ + 'cascade', + 'concurrently', + ]), + postgres: new Error( + `Cannot specify both concurrently and cascade options in removeIndexQuery for ${dialect.name} dialect`, + ), + snowflake: notImplementedError, + }, + ); + }); + + it('produces a DROP INDEX query from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.removeIndexQuery(MyModel, 'user_foo_bar'), { + default: `DROP INDEX [user_foo_bar] ON [MyModels]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "user_foo_bar"`, + postgres: `DROP INDEX "public"."user_foo_bar"`, + snowflake: notImplementedError, + }); + }); + + it('produces a DROP INDEX query from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.removeIndexQuery(myDefinition, 'user_foo_bar'), { + default: `DROP INDEX [user_foo_bar] ON [MyModels]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "user_foo_bar"`, + postgres: `DROP INDEX "public"."user_foo_bar"`, + snowflake: notImplementedError, + }); + }); + + it('produces a DROP INDEX query from a table and schema', () => { + expectsql( + () => + queryGenerator.removeIndexQuery( + { tableName: 'myTable', schema: 'mySchema' }, + 'user_foo_bar', + ), + { + default: `DROP INDEX [user_foo_bar] ON [mySchema].[myTable]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + postgres: `DROP INDEX "mySchema"."user_foo_bar"`, + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "user_foo_bar"`, + snowflake: notImplementedError, + }, + ); + }); + + it('produces a DROP INDEX query from a table and default schema', () => { + expectsql( + () => + queryGenerator.removeIndexQuery( + { tableName: 'myTable', schema: dialect.getDefaultSchema() }, + 'user_foo_bar', + ), + { + default: `DROP INDEX [user_foo_bar] ON [myTable]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: `DROP INDEX "user_foo_bar"`, + postgres: `DROP INDEX "public"."user_foo_bar"`, + snowflake: notImplementedError, + }, + ); + }); + + it('produces a DROP INDEX query from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.removeIndexQuery('myTable', 'user_foo_bar'), { + default: `DROP INDEX [user_foo_bar] ON [mySchema].[myTable]`, + sqlite3: 'DROP INDEX `user_foo_bar`', + postgres: `DROP INDEX "mySchema"."user_foo_bar"`, + ibmi: `BEGIN DROP INDEX "user_foo_bar"; COMMIT; END`, + db2: 'DROP INDEX "user_foo_bar"', + snowflake: notImplementedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/rename-table-query.test.ts b/packages/core/test/unit/query-generator/rename-table-query.test.ts new file mode 100644 index 000000000000..312652f14d16 --- /dev/null +++ b/packages/core/test/unit/query-generator/rename-table-query.test.ts @@ -0,0 +1,149 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const changeSchemaNotSetError = new Error( + 'To move a table between schemas, you must set `options.changeSchema` to true.', +); +const moveSchemaNotSupportedError = new Error( + `Moving tables between schemas is not supported by ${dialect.name} dialect.`, +); +const moveSchemaWithRenameNotSupportedError = new Error( + `Renaming a table and moving it to a different schema is not supported by ${dialect.name}.`, +); + +describe('QueryGenerator#renameTableQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query that renames the table', () => { + expectsql(() => queryGenerator.renameTableQuery('oldTable', 'newTable'), { + default: 'ALTER TABLE [oldTable] RENAME TO [newTable]', + mssql: `EXEC sp_rename '[oldTable]', N'newTable'`, + 'db2 ibmi': 'RENAME TABLE "oldTable" TO "newTable"', + }); + }); + + it('produces a query that renames the table from a model', () => { + const OldModel = sequelize.define('oldModel', {}); + const NewModel = sequelize.define('newModel', {}); + + expectsql(() => queryGenerator.renameTableQuery(OldModel, NewModel), { + default: 'ALTER TABLE [oldModels] RENAME TO [newModels]', + mssql: `EXEC sp_rename '[oldModels]', N'newModels'`, + 'db2 ibmi': 'RENAME TABLE "oldModels" TO "newModels"', + }); + }); + + it('produces a query that renames the table from a model definition', () => { + const OldModel = sequelize.define('oldModel', {}); + const oldDefinition = OldModel.modelDefinition; + const NewModel = sequelize.define('newModel', {}); + const newDefinition = NewModel.modelDefinition; + + expectsql(() => queryGenerator.renameTableQuery(oldDefinition, newDefinition), { + default: 'ALTER TABLE [oldModels] RENAME TO [newModels]', + mssql: `EXEC sp_rename '[oldModels]', N'newModels'`, + 'db2 ibmi': 'RENAME TABLE "oldModels" TO "newModels"', + }); + }); + + it('throws an error if `options.changeSchema` is not set when moving table to another schema', () => { + expectsql( + () => + queryGenerator.renameTableQuery( + { tableName: 'oldTable', schema: 'oldSchema' }, + { tableName: 'newTable', schema: 'newSchema' }, + ), + { + default: changeSchemaNotSetError, + 'db2 ibmi': moveSchemaNotSupportedError, + }, + ); + }); + + it('produces a query that moves a table to a different schema', () => { + expectsql( + () => + queryGenerator.renameTableQuery( + { tableName: 'oldTable', schema: 'oldSchema' }, + { tableName: 'oldTable', schema: 'newSchema' }, + { changeSchema: true }, + ), + { + default: 'ALTER TABLE [oldSchema].[oldTable] RENAME TO [newSchema].[oldTable]', + mssql: `ALTER SCHEMA [newSchema] TRANSFER [oldSchema].[oldTable]`, + sqlite3: 'ALTER TABLE `oldSchema.oldTable` RENAME TO `newSchema.oldTable`', + postgres: `ALTER TABLE "oldSchema"."oldTable" SET SCHEMA "newSchema"`, + 'db2 ibmi': buildInvalidOptionReceivedError('renameTableQuery', dialect.name, [ + 'changeSchema', + ]), + }, + ); + }); + + it('produces a query that moves a table to a different schema with a different name', () => { + expectsql( + () => + queryGenerator.renameTableQuery( + { tableName: 'oldTable', schema: 'oldSchema' }, + { tableName: 'newTable', schema: 'newSchema' }, + { changeSchema: true }, + ), + { + default: 'ALTER TABLE [oldSchema].[oldTable] RENAME TO [newSchema].[newTable]', + sqlite3: 'ALTER TABLE `oldSchema.oldTable` RENAME TO `newSchema.newTable`', + 'db2 ibmi': buildInvalidOptionReceivedError('renameTableQuery', dialect.name, [ + 'changeSchema', + ]), + 'mssql postgres': moveSchemaWithRenameNotSupportedError, + }, + ); + }); + + it('produces a query that renames the table with default schema', () => { + expectsql( + () => + queryGenerator.renameTableQuery( + { tableName: 'oldTable', schema: dialect.getDefaultSchema() }, + { tableName: 'newTable', schema: dialect.getDefaultSchema() }, + ), + { + default: 'ALTER TABLE [oldTable] RENAME TO [newTable]', + mssql: `EXEC sp_rename '[oldTable]', N'newTable'`, + 'db2 ibmi': 'RENAME TABLE "oldTable" TO "newTable"', + }, + ); + }); + + it('produces a query that renames the table from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.renameTableQuery('oldTable', 'newTable'), { + default: 'ALTER TABLE [mySchema].[oldTable] RENAME TO [mySchema].[newTable]', + mssql: `EXEC sp_rename '[mySchema].[oldTable]', N'newTable'`, + sqlite3: 'ALTER TABLE `mySchema.oldTable` RENAME TO `mySchema.newTable`', + postgres: `ALTER TABLE "mySchema"."oldTable" RENAME TO "newTable"`, + 'db2 ibmi': 'RENAME TABLE "mySchema"."oldTable" TO "newTable"', + }); + }); + + it('produces a query that renames the table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.renameTableQuery( + { tableName: 'oldTable', schema: 'oldSchema', delimiter: 'custom' }, + { tableName: 'newTable', schema: 'newSchema', delimiter: 'custom' }, + { changeSchema: true }, + ), + { + sqlite3: 'ALTER TABLE `oldSchemacustomoldTable` RENAME TO `newSchemacustomnewTable`', + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/rollback-savepoint-query.test.ts b/packages/core/test/unit/query-generator/rollback-savepoint-query.test.ts new file mode 100644 index 000000000000..8892f054a19d --- /dev/null +++ b/packages/core/test/unit/query-generator/rollback-savepoint-query.test.ts @@ -0,0 +1,16 @@ +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error(`Savepoints are not supported by ${dialect.name}.`); + +describe('QueryGenerator#rollbackSavepointQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for committing a transaction', () => { + expectsql(() => queryGenerator.rollbackSavepointQuery('mySavepoint'), { + default: 'ROLLBACK TO SAVEPOINT [mySavepoint]', + mssql: 'ROLLBACK TRANSACTION [mySavepoint]', + snowflake: notSupportedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/rollback-transaction-query.test.ts b/packages/core/test/unit/query-generator/rollback-transaction-query.test.ts new file mode 100644 index 000000000000..a69f3132d9a2 --- /dev/null +++ b/packages/core/test/unit/query-generator/rollback-transaction-query.test.ts @@ -0,0 +1,17 @@ +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error( + `rollbackTransactionQuery is not supported by the ${dialect.name} dialect.`, +); + +describe('QueryGenerator#rollbackTransactionQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for rolling back a transaction', () => { + expectsql(() => queryGenerator.rollbackTransactionQuery(), { + default: 'ROLLBACK', + 'db2 ibmi mssql': notSupportedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/select-query.test.ts b/packages/core/test/unit/query-generator/select-query.test.ts new file mode 100644 index 000000000000..902a03fa25c3 --- /dev/null +++ b/packages/core/test/unit/query-generator/select-query.test.ts @@ -0,0 +1,1200 @@ +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, + Model, +} from '@sequelize/core'; +import { DataTypes, IndexHints, Op, TableHints, or, sql as sqlTag } from '@sequelize/core'; +import { _validateIncludedElements } from '@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'; +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { expect } from 'chai'; +import { beforeAll2, expectsql, getTestDialect, sequelize } from '../../support'; + +const { attribute, col, cast, where, fn, literal } = sqlTag; +const dialectName = getTestDialect(); + +describe('QueryGenerator#selectQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + interface TUser extends Model, InferCreationAttributes> { + id: CreationOptional; + username: string; + } + + const User = sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + username: DataTypes.STRING, + }, + { timestamps: true }, + ); + + interface TProject extends Model, InferCreationAttributes> { + id: CreationOptional; + duration: bigint; + } + + const Project = sequelize.define( + 'Project', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + duration: DataTypes.INTEGER, + }, + { timestamps: false }, + ); + + const ProjectContributor = sequelize.define('ProjectContributor', {}, { timestamps: false }); + + // project owners + User.hasMany(Project, { as: 'projects' }); + Project.belongsTo(User, { as: 'owner' }); + + // project contributors + Project.belongsToMany(User, { + through: ProjectContributor, + as: 'contributors', + inverse: 'contributedProjects', + }); + + return { User, Project, ProjectContributor }; + }); + + describe('limit/offset', () => { + it('supports offset without limit', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + offset: 1, + }, + User, + ); + + expectsql(sql, { + sqlite3: 'SELECT `id` FROM `Users` AS `User` ORDER BY `User`.`id` LIMIT -1 OFFSET 1;', + postgres: 'SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" OFFSET 1;', + snowflake: 'SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" LIMIT NULL OFFSET 1;', + 'mariadb mysql': + 'SELECT `id` FROM `Users` AS `User` ORDER BY `User`.`id` LIMIT 18446744073709551615 OFFSET 1;', + 'db2 ibmi mssql': `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET 1 ROWS;`, + }); + }); + + it('support limit without offset', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + limit: 10, + }, + User, + ); + + expectsql(sql, { + default: 'SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT 10;', + mssql: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;`, + 'db2 ibmi': `SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" FETCH NEXT 10 ROWS ONLY;`, + }); + }); + + it('supports offset and limit', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + offset: 1, + limit: 10, + }, + User, + ); + + expectsql(sql, { + default: 'SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT 10 OFFSET 1;', + 'db2 ibmi mssql': `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET 1 ROWS FETCH NEXT 10 ROWS ONLY;`, + }); + }); + + it('ignores 0 as offset with a limit', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + offset: 0, + limit: 10, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT 10;`, + mssql: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;`, + 'db2 ibmi': `SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" FETCH NEXT 10 ROWS ONLY;`, + }); + }); + + it('ignores 0 as offset without a limit', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + offset: 0, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User];`, + }); + }); + + it('support 0 as limit', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + limit: 0, + }, + User, + ), + { + default: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT 0;`, + mssql: new Error(`LIMIT 0 is not supported by ${dialectName} dialect.`), + 'db2 ibmi': `SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" FETCH NEXT 0 ROWS ONLY;`, + }, + ); + }); + + it('escapes limit', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + // @ts-expect-error -- testing invalid limit + limit: `';DELETE FROM user`, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT ''';DELETE FROM user';`, + mssql: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY;`, + 'db2 ibmi': `SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" FETCH NEXT ''';DELETE FROM user' ROWS ONLY;`, + 'mariadb mysql': + "SELECT `id` FROM `Users` AS `User` ORDER BY `User`.`id` LIMIT '\\';DELETE FROM user';", + }); + }); + + it('escapes offset', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + limit: 10, + // @ts-expect-error -- testing invalid offset + offset: `';DELETE FROM user`, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] LIMIT 10 OFFSET ''';DELETE FROM user';`, + mssql: `SELECT [id] FROM [Users] AS [User] ORDER BY [User].[id] OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY;`, + 'db2 ibmi': `SELECT "id" FROM "Users" AS "User" ORDER BY "User"."id" OFFSET ''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY;`, + 'mariadb mysql': + "SELECT `id` FROM `Users` AS `User` ORDER BY `User`.`id` LIMIT 10 OFFSET '\\';DELETE FROM user';", + }); + }); + }); + + it('supports querying for bigint values', () => { + const { Project } = vars; + + const sql = queryGenerator.selectQuery( + Project.table, + { + model: Project, + attributes: ['id'], + where: { + duration: { [Op.eq]: 9_007_199_254_740_993n }, + }, + }, + Project, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Projects] AS [Project] WHERE [Project].[duration] = 9007199254740993;`, + }); + }); + + it('supports cast in attributes', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id', [cast(col('createdAt'), 'varchar'), 'createdAt']], + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id], CAST([createdAt] AS VARCHAR) AS [createdAt] FROM [Users] AS [User];`, + }); + }); + + it('supports empty where object', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + where: {}, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User];`, + }); + }); + + it('escapes WHERE clause correctly', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + where: { username: "foo';DROP TABLE mySchema.myTable;" }, + }, + User, + ); + + expectsql(sql, { + default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'foo'';DROP TABLE mySchema.myTable;';`, + 'mysql mariadb': `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'foo\\';DROP TABLE mySchema.myTable;';`, + mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'foo'';DROP TABLE mySchema.myTable;';`, + }); + }); + + if ( + sequelize.dialect.supports.jsonOperations && + sequelize.dialect.supports.jsonExtraction.quoted + ) { + it('accepts json paths in attributes', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: [[attribute('data.email'), 'email']], + }, + User, + ); + + expectsql(sql, { + postgres: `SELECT "data"->'email' AS "email" FROM "Users" AS "User";`, + mariadb: `SELECT json_compact(json_extract(\`data\`,'$.email')) AS \`email\` FROM \`Users\` AS \`User\`;`, + 'sqlite3 mysql': `SELECT json_extract([data],'$.email') AS [email] FROM [Users] AS [User];`, + }); + }); + } + + describe('replacements', () => { + it('parses named replacements in literals', () => { + const { User } = vars; + + // The goal of this test is to test that :replacements are parsed in literals in as many places as possible + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: [[fn('uppercase', literal(':attr')), 'id'], literal(':attr2')], + where: { + username: or( + { [Op.eq]: literal(':data') }, + where(fn('uppercase', cast(literal(':data'), 'string')), Op.eq, literal(':data')), + ), + }, + having: { + username: { + [Op.eq]: literal(':data'), + }, + }, + order: literal(':order'), + limit: literal(':limit'), + offset: literal(':offset'), + group: literal(':group'), + replacements: { + attr: 'id', + attr2: 'id2', + data: 'repl1', + order: 'repl2', + limit: 'repl3', + offset: 'repl4', + group: 'the group', + }, + }, + User, + ); + + expectsql(sql, { + default: ` + SELECT uppercase('id') AS [id], 'id2' + FROM [Users] AS [User] + WHERE [User].[username] = 'repl1' OR [User].[username] = (uppercase(CAST('repl1' AS STRING)) = 'repl1') + GROUP BY 'the group' + HAVING [User].[username] = 'repl1' + ORDER BY 'repl2' + LIMIT 'repl3' + OFFSET 'repl4'; + `, + mssql: ` + SELECT uppercase(N'id') AS [id], N'id2' + FROM [Users] AS [User] + WHERE [User].[username] = N'repl1' OR [User].[username] = (uppercase(CAST(N'repl1' AS STRING)) = N'repl1') + GROUP BY N'the group' + HAVING [User].[username] = N'repl1' + ORDER BY N'repl2' + OFFSET N'repl4' ROWS + FETCH NEXT N'repl3' ROWS ONLY; + `, + 'db2 ibmi': ` + SELECT uppercase('id') AS "id", 'id2' + FROM "Users" AS "User" + WHERE "User"."username" = 'repl1' OR "User"."username" = (uppercase(CAST('repl1' AS STRING)) = 'repl1') + GROUP BY 'the group' + HAVING "User"."username" = 'repl1' + ORDER BY 'repl2' + OFFSET 'repl4' ROWS + FETCH NEXT 'repl3' ROWS ONLY; + `, + }); + }); + + // see the unit tests of 'injectReplacements' for more + it('does not parse replacements in strings in literals', () => { + const { User } = vars; + + // The goal of this test is to test that :replacements are parsed in literals in as many places as possible + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: [literal('id')], + where: literal(`id = ':id'`), + replacements: { + id: 1, + }, + }, + User, + ); + + expectsql(sql, { + default: `SELECT id FROM [Users] AS [User] WHERE id = ':id';`, + }); + }); + + it('parses named replacements in literals in includes', () => { + const { User, Project } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + include: _validateIncludedElements({ + model: User, + include: [ + { + association: User.associations.projects, + attributes: [['id', 'id'], literal(':data'), [literal(':data'), 'id2']], + on: literal(':on'), + where: literal(':where'), + include: [ + { + association: Project.associations.owner, + attributes: [literal(':data2')], + }, + ], + }, + ], + }).include, + replacements: { + data: 'repl1', + data2: 'repl2', + on: 'on', + where: 'where', + }, + }, + User, + ); + + expectsql(sql, { + default: ` + SELECT + [User].[id], + [projects].[id] AS [projects.id], + 'repl1', + 'repl1' AS [projects.id2], + [projects->owner].[id] AS [projects.owner.id], + 'repl2' + FROM [Users] AS [User] + INNER JOIN [Projects] AS [projects] + ON 'on' AND 'where' + LEFT OUTER JOIN [Users] AS [projects->owner] + ON [projects].[ownerId] = [projects->owner].[id]; + `, + mssql: ` + SELECT + [User].[id], + [projects].[id] AS [projects.id], + N'repl1', + N'repl1' AS [projects.id2], + [projects->owner].[id] AS [projects.owner.id], + N'repl2' + FROM [Users] AS [User] + INNER JOIN [Projects] AS [projects] + ON N'on' AND N'where' + LEFT OUTER JOIN [Users] AS [projects->owner] + ON [projects].[ownerId] = [projects->owner].[id]; + `, + ibmi: ` + SELECT + "User"."id", + "projects"."id" AS "projects.id", + 'repl1', + 'repl1' AS "projects.id2", + "projects->owner"."id" AS "projects.owner.id", + 'repl2' + FROM "Users" AS "User" + INNER JOIN "Projects" AS "projects" + ON 'on' AND 'where' + LEFT OUTER JOIN "Users" AS "projects->owner" + ON "projects"."ownerId" = "projects->owner"."id" + `, + }); + }); + + it(`parses named replacements in belongsToMany includes' through tables`, () => { + const { Project } = vars; + + const sql = queryGenerator.selectQuery( + Project.table, + { + model: Project, + attributes: ['id'], + include: _validateIncludedElements({ + model: Project, + include: [ + { + attributes: ['id'], + association: Project.associations.contributors, + through: { + where: literal(':where'), + }, + }, + ], + }).include, + replacements: { + where: 'where', + }, + }, + Project, + ); + + expectsql(sql, { + default: ` + SELECT + [Project].[id], + [contributors].[id] AS [contributors.id], + [contributors->ProjectContributor].[userId] AS [contributors.ProjectContributor.userId], + [contributors->ProjectContributor].[projectId] AS [contributors.ProjectContributor.projectId] + FROM [Projects] AS [Project] + LEFT OUTER JOIN ( + [ProjectContributors] AS [contributors->ProjectContributor] + INNER JOIN [Users] AS [contributors] + ON [contributors].[id] = [contributors->ProjectContributor].[userId] + AND 'where' + ) + ON [Project].[id] = [contributors->ProjectContributor].[projectId]; + `, + mssql: ` + SELECT + [Project].[id], + [contributors].[id] AS [contributors.id], + [contributors->ProjectContributor].[userId] AS [contributors.ProjectContributor.userId], + [contributors->ProjectContributor].[projectId] AS [contributors.ProjectContributor.projectId] + FROM [Projects] AS [Project] + LEFT OUTER JOIN ( + [ProjectContributors] AS [contributors->ProjectContributor] + INNER JOIN [Users] AS [contributors] + ON [contributors].[id] = [contributors->ProjectContributor].[userId] + AND N'where' + ) + ON [Project].[id] = [contributors->ProjectContributor].[projectId]; + `, + }); + }); + + it('parses named replacements in literals in includes (subQuery)', () => { + const { User, Project } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + include: _validateIncludedElements({ + model: User, + include: [ + { + association: User.associations.projects, + attributes: [['id', 'id'], literal(':data'), [literal(':data'), 'id2']], + on: literal(':on'), + where: literal(':where'), + include: [ + { + association: Project.associations.owner, + attributes: [literal(':data2')], + }, + ], + }, + ], + }).include, + limit: literal(':limit'), + offset: literal(':offset'), + order: literal(':order'), + subQuery: true, + replacements: { + data: 'repl1', + data2: 'repl2', + on: 'on', + where: 'where', + limit: 'limit', + offset: 'offset', + order: 'order', + }, + }, + User, + ); + + expectsql(sql, { + default: ` + SELECT + [User].*, + [projects].[id] AS [projects.id], + 'repl1', + 'repl1' AS [projects.id2], + [projects->owner].[id] AS [projects.owner.id], + 'repl2' + FROM ( + SELECT [User].[id] + FROM [Users] AS [User] + ORDER BY 'order' + LIMIT 'limit' + OFFSET 'offset' + ) AS [User] + INNER JOIN [Projects] AS [projects] + ON 'on' AND 'where' + LEFT OUTER JOIN [Users] AS [projects->owner] + ON [projects].[ownerId] = [projects->owner].[id] + ORDER BY 'order'; + `, + mssql: ` + SELECT + [User].*, + [projects].[id] AS [projects.id], + N'repl1', + N'repl1' AS [projects.id2], + [projects->owner].[id] AS [projects.owner.id], + N'repl2' + FROM ( + SELECT [User].[id] + FROM [Users] AS [User] + ORDER BY N'order' + OFFSET N'offset' ROWS + FETCH NEXT N'limit' ROWS ONLY + ) AS [User] + INNER JOIN [Projects] AS [projects] + ON N'on' AND N'where' + LEFT OUTER JOIN [Users] AS [projects->owner] + ON [projects].[ownerId] = [projects->owner].[id] + ORDER BY N'order'; + `, + db2: ` + SELECT + "User".*, + "projects"."id" AS "projects.id", + 'repl1', + 'repl1' AS "projects.id2", + "projects->owner"."id" AS "projects.owner.id", + 'repl2' FROM ( + SELECT "User"."id" + FROM "Users" AS "User" + ORDER BY 'order' + OFFSET 'offset' ROWS + FETCH NEXT 'limit' ROWS ONLY + ) AS "User" + INNER JOIN "Projects" AS "projects" + ON 'on' AND 'where' + LEFT OUTER JOIN "Users" AS "projects->owner" + ON "projects"."ownerId" = "projects->owner"."id" + ORDER BY 'order'; + `, + ibmi: ` + SELECT + "User".*, + "projects"."id" AS "projects.id", + 'repl1', + 'repl1' AS "projects.id2", + "projects->owner"."id" AS "projects.owner.id", + 'repl2' FROM ( + SELECT "User"."id" + FROM "Users" AS "User" + ORDER BY 'order' + OFFSET 'offset' ROWS + FETCH NEXT 'limit' ROWS ONLY + ) AS "User" + INNER JOIN "Projects" AS "projects" + ON 'on' AND 'where' + LEFT OUTER JOIN "Users" AS "projects->owner" + ON "projects"."ownerId" = "projects->owner"."id" + ORDER BY 'order' + `, + }); + }); + + it('rejects positional replacements, because their execution order is hard to determine', () => { + const { User } = vars; + + expect(() => + queryGenerator.selectQuery( + User.table, + { + model: User, + where: { + username: { + [Op.eq]: literal('?'), + }, + }, + replacements: ['repl1', 'repl2', 'repl3'], + }, + User, + ), + ).to.throwWithCause(`The following literal includes positional replacements (?). +Only named replacements (:name) are allowed in literal() because we cannot guarantee the order in which they will be evaluated: +➜ literal("?")`); + }); + + it(`always escapes the attribute if it's provided as a string`, () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: [ + // these used to have special escaping logic, now they're always escaped like any other strings. col, fn, and literal can be used for advanced logic. + ['count(*)', 'count'], + // @ts-expect-error -- test against a vulnerability CVE-2023-22578 + '.*', + // @ts-expect-error -- test against a vulnerability CVE-2023-22578 + '*', + [literal('count(*)'), 'literal_count'], + [fn('count', '*'), 'fn_count_str'], + [fn('count', col('*')), 'fn_count_col'], + [fn('count', literal('*')), 'fn_count_lit'], + [col('a.b'), 'col_a_b'], + [col('a.*'), 'col_a_all'], + [col('*'), 'col_all'], + ], + }, + User, + ); + + expectsql(sql, { + default: ` + SELECT + [count(*)] AS [count], + [.*], + [*], + count(*) AS [literal_count], + count('*') AS [fn_count_str], + count(*) AS [fn_count_col], + count(*) AS [fn_count_lit], + [a].[b] AS [col_a_b], + [a].* AS [col_a_all], + * AS [col_all] + FROM [Users] AS [User];`, + mssql: ` + SELECT + [count(*)] AS [count], + [.*], + [*], + count(*) AS [literal_count], + count(N'*') AS [fn_count_str], + count(*) AS [fn_count_col], + count(*) AS [fn_count_lit], + [a].[b] AS [col_a_b], + [a].* AS [col_a_all], + * AS [col_all] + FROM [Users] AS [User];`, + }); + }); + + it('supports a "having" option', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: [literal('*'), [fn('YEAR', col('createdAt')), 'creationYear']], + group: ['creationYear', 'title'], + having: { creationYear: { [Op.gt]: 2002 } }, + }, + User, + ); + + expectsql(sql, { + default: `SELECT *, YEAR([createdAt]) AS [creationYear] FROM [Users] AS [User] GROUP BY [creationYear], [title] HAVING [User].[creationYear] > 2002;`, + }); + }); + }); + + describe('previously supported values', () => { + it('raw replacements for where', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: ['name IN (?)', [1, 'test', 3, 'derp']], + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got 'name IN (?)'`, + ); + }); + + it('raw replacements for nested where', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: [['name IN (?)', [1, 'test', 3, 'derp']]], + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got 'name IN (?)'`, + ); + }); + + it('raw replacements for having', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + having: ['name IN (?)', [1, 'test', 3, 'derp']], + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got 'name IN (?)'`, + ); + }); + + it('raw replacements for nested having', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + having: [['name IN (?)', [1, 'test', 3, 'derp']]], + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got 'name IN (?)'`, + ); + }); + + it('raw string from where', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: `name = 'something'`, + }); + }).to.throwWithCause(Error, "Support for `{ where: 'raw query' }` has been removed."); + }); + + it('raw string from having', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + having: `name = 'something'`, + }); + }).to.throwWithCause(Error, "Support for `{ where: 'raw query' }` has been removed."); + }); + + it('rejects where: null', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: null, + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got null`, + ); + }); + + it('rejects where: primitive', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: 1, + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got 1`, + ); + }); + + it('rejects where: array of primitives', () => { + expect(() => { + queryGenerator.selectQuery('User', { + attributes: [[col('*'), 'col_all']], + // @ts-expect-error -- this is not a valid value anymore + where: [''], + }); + }).to.throwWithCause( + Error, + `Invalid Query: expected a plain object, an array or a sequelize SQL method but got ''`, + ); + }); + }); + + describe('minifyAliases', () => { + it('minifies custom attributes', () => { + const { User } = vars; + + const sql = queryGenerator.selectQuery( + User.table, + { + minifyAliases: true, + model: User, + attributes: [[literal('1'), 'customAttr']], + order: ['customAttr'], + group: ['customAttr'], + }, + User, + ); + + expectsql(sql, { + default: `SELECT 1 AS [_0] FROM [Users] AS [User] GROUP BY [_0] ORDER BY [_0];`, + }); + }); + }); + + describe('optimizer hints', () => { + it('max execution time hint', () => { + const { User } = vars; + + const notSupportedError = new Error( + `The maxExecutionTimeMs option is not supported by ${dialectName}`, + ); + + expectsql( + () => + queryGenerator.selectQuery( + User.tableName, + { + model: User, + attributes: ['id'], + maxExecutionTimeHintMs: 1000, + }, + User, + ), + { + default: notSupportedError, + mysql: 'SELECT /*+ MAX_EXECUTION_TIME(1000) */ `id` FROM `Users` AS `User`;', + }, + ); + }); + }); + + describe('index hints', () => { + it('should add an index hint', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }], + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'indexHints', + ]), + 'mariadb mysql snowflake': + 'SELECT [id] FROM [Users] AS [User] FORCE INDEX ([index_project_on_name]);', + }, + ); + }); + + it('should add an index hint with multiple values', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + indexHints: [ + { + type: IndexHints.IGNORE, + values: ['index_project_on_name', 'index_project_on_name_and_foo'], + }, + ], + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'indexHints', + ]), + 'mariadb mysql snowflake': + 'SELECT [id] FROM [Users] AS [User] IGNORE INDEX ([index_project_on_name],[index_project_on_name_and_foo]);', + }, + ); + }); + + it('should support index hints on queries with associations', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }], + include: _validateIncludedElements({ + model: User, + include: [ + { + association: User.associations.projects, + attributes: ['id'], + }, + ], + }).include, + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'indexHints', + ]), + 'mariadb mysql snowflake': + 'SELECT [User].[id], [projects].[id] AS [projects.id] FROM [Users] AS [User] FORCE INDEX ([index_project_on_name]) LEFT OUTER JOIN [Projects] AS [projects] ON [User].[id] = [projects].[userId];', + }, + ); + }); + + it('should throw an error if an index hint if the type is not valid', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + // @ts-expect-error -- we are testing invalid values + indexHints: [{ type: 'INVALID', values: ['index_project_on_name'] }], + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'indexHints', + ]), + 'mariadb mysql snowflake': new Error( + `The index hint type "INVALID" is invalid or not supported by dialect "${sequelize.dialect.name}".`, + ), + }, + ); + }); + }); + + describe('table hints', () => { + it('support an array of table hints', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + tableHints: [TableHints.UPDLOCK, TableHints.PAGLOCK], + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'tableHints', + ]), + mssql: `SELECT [id] FROM [Users] AS [User] WITH (UPDLOCK, PAGLOCK);`, + }, + ); + }); + + it('should be able to use table hints on joins', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + tableHints: [TableHints.NOLOCK], + include: _validateIncludedElements({ + model: User, + include: [ + { + association: User.associations.projects, + attributes: ['id'], + }, + ], + }).include, + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'tableHints', + ]), + mssql: `SELECT [User].[id], [projects].[id] AS [projects.id] FROM [Users] AS [User] WITH (NOLOCK) LEFT OUTER JOIN [Projects] AS [projects] WITH (NOLOCK) ON [User].[id] = [projects].[userId];`, + }, + ); + }); + + it('should be able to use separate table hints on joins', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + tableHints: [TableHints.NOLOCK], + include: _validateIncludedElements({ + model: User, + include: [ + { + association: User.associations.projects, + attributes: ['id'], + tableHints: [TableHints.READPAST], + }, + ], + }).include, + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'tableHints', + ]), + mssql: `SELECT [User].[id], [projects].[id] AS [projects.id] FROM [Users] AS [User] WITH (NOLOCK) LEFT OUTER JOIN [Projects] AS [projects] WITH (READPAST) ON [User].[id] = [projects].[userId];`, + }, + ); + }); + + it('should throw an error if a table hint if the type is not valid', () => { + const { User } = vars; + + expectsql( + () => + queryGenerator.selectQuery( + User.table, + { + model: User, + attributes: ['id'], + // @ts-expect-error -- we are testing invalid values + tableHints: ['INVALID'], + }, + User, + ), + { + default: buildInvalidOptionReceivedError('quoteTable', sequelize.dialect.name, [ + 'tableHints', + ]), + mssql: new Error( + `The table hint "INVALID" is invalid or not supported by dialect "${sequelize.dialect.name}".`, + ), + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/set-constraint-checking-query.test.ts b/packages/core/test/unit/query-generator/set-constraint-checking-query.test.ts new file mode 100644 index 000000000000..746031f9e007 --- /dev/null +++ b/packages/core/test/unit/query-generator/set-constraint-checking-query.test.ts @@ -0,0 +1,120 @@ +import { ConstraintChecking } from '@sequelize/core'; +import { expectsql, sequelize } from '../../support'; + +const { name } = sequelize.dialect; +const queryGenerator = sequelize.queryGenerator; +const notSupportedError = new Error(`Deferrable constraints are not supported by ${name} dialect`); + +describe('QueryGenerator#setConstraintCheckingQuery', () => { + describe('DEFERRED constraints', () => { + it('generates a deferred constraint checking query for all constraints', () => { + expectsql( + () => queryGenerator.setConstraintCheckingQuery(new ConstraintChecking.DEFERRED()), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL DEFERRED', + }, + ); + }); + + it('generates a deferred constraint checking query for all constraints with an empty array', () => { + expectsql(() => queryGenerator.setConstraintCheckingQuery(ConstraintChecking.DEFERRED, []), { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL DEFERRED', + }); + }); + + it('generates a deferred constraint checking query for all constraints with an empty array for an instance', () => { + expectsql( + () => queryGenerator.setConstraintCheckingQuery(new ConstraintChecking.DEFERRED([])), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL DEFERRED', + }, + ); + }); + + it('generates a deferred constraint checking query for the specified constraints', () => { + expectsql( + () => + queryGenerator.setConstraintCheckingQuery(ConstraintChecking.DEFERRED, [ + 'test1', + 'test2', + ]), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS "test1", "test2" DEFERRED', + }, + ); + }); + + it('generates a deferred constraint checking query for the specified constraints for an instance', () => { + expectsql( + () => + queryGenerator.setConstraintCheckingQuery( + new ConstraintChecking.DEFERRED(['test1', 'test2']), + ), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS "test1", "test2" DEFERRED', + }, + ); + }); + }); + + describe('IMMEDIATE constraints', () => { + it('generates an immediate constraint checking query for all constraints', () => { + expectsql( + () => queryGenerator.setConstraintCheckingQuery(new ConstraintChecking.IMMEDIATE()), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL IMMEDIATE', + }, + ); + }); + + it('generates an immediate constraint checking query for all constraints with an empty array', () => { + expectsql(() => queryGenerator.setConstraintCheckingQuery(ConstraintChecking.IMMEDIATE, []), { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL IMMEDIATE', + }); + }); + + it('generates an immediate constraint checking query for all constraints with an empty array for an instance', () => { + expectsql( + () => queryGenerator.setConstraintCheckingQuery(new ConstraintChecking.IMMEDIATE([])), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS ALL IMMEDIATE', + }, + ); + }); + + it('generates an immediate constraint checking query for the specified constraints', () => { + expectsql( + () => + queryGenerator.setConstraintCheckingQuery(ConstraintChecking.IMMEDIATE, [ + 'test1', + 'test2', + ]), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS "test1", "test2" IMMEDIATE', + }, + ); + }); + + it('generates an immediate constraint checking query for the specified constraints for an instance', () => { + expectsql( + () => + queryGenerator.setConstraintCheckingQuery( + new ConstraintChecking.IMMEDIATE(['test1', 'test2']), + ), + { + default: notSupportedError, + 'postgres snowflake': 'SET CONSTRAINTS "test1", "test2" IMMEDIATE', + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/set-isolation-level-query.test.ts b/packages/core/test/unit/query-generator/set-isolation-level-query.test.ts new file mode 100644 index 000000000000..e411b822003f --- /dev/null +++ b/packages/core/test/unit/query-generator/set-isolation-level-query.test.ts @@ -0,0 +1,52 @@ +import { IsolationLevel } from '@sequelize/core'; +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error(`Isolation levels are not supported by ${dialect.name}.`); +const queryNotSupportedError = new Error( + `setIsolationLevelQuery is not supported by the ${dialect.name} dialect.`, +); + +describe('QueryGenerator#setIsolationLevelQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for setting the isolation level to READ COMMITTED', () => { + expectsql(() => queryGenerator.setIsolationLevelQuery(IsolationLevel.READ_COMMITTED), { + default: 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', + sqlite3: new Error( + `The ${IsolationLevel.READ_COMMITTED} isolation level is not supported by ${dialect.name}.`, + ), + snowflake: notSupportedError, + 'db2 ibmi mssql': queryNotSupportedError, + }); + }); + + it('should generate a query for setting the isolation level to READ UNCOMMITTED', () => { + expectsql(() => queryGenerator.setIsolationLevelQuery(IsolationLevel.READ_UNCOMMITTED), { + default: 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', + sqlite3: 'PRAGMA read_uncommitted = 1', + snowflake: notSupportedError, + 'db2 ibmi mssql': queryNotSupportedError, + }); + }); + + it('should generate a query for setting the isolation level to REPEATABLE READ', () => { + expectsql(() => queryGenerator.setIsolationLevelQuery(IsolationLevel.REPEATABLE_READ), { + default: 'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ', + sqlite3: new Error( + `The ${IsolationLevel.REPEATABLE_READ} isolation level is not supported by ${dialect.name}.`, + ), + snowflake: notSupportedError, + 'db2 ibmi mssql': queryNotSupportedError, + }); + }); + + it('should generate a query for setting the isolation level to SERIALIZABLE', () => { + expectsql(() => queryGenerator.setIsolationLevelQuery(IsolationLevel.SERIALIZABLE), { + default: 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', + sqlite3: 'PRAGMA read_uncommitted = 0', + snowflake: notSupportedError, + 'db2 ibmi mssql': queryNotSupportedError, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/show-constraints-query.test.ts b/packages/core/test/unit/query-generator/show-constraints-query.test.ts new file mode 100644 index 000000000000..13d268a0555a --- /dev/null +++ b/packages/core/test/unit/query-generator/show-constraints-query.test.ts @@ -0,0 +1,159 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectsql } from '../../support'; + +const sequelize = createSequelizeInstance(); +const dialect = sequelize.dialect; + +describe('QueryGenerator#showConstraintsQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a show constraints query for a table', () => { + expectsql(() => queryGenerator.showConstraintsQuery('myTable'), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'DB2INST1' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = CURRENT SCHEMA ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'myTable' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'public' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'PUBLIC' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'sequelize_test' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query for a table with a constraint name specified', () => { + expectsql(() => queryGenerator.showConstraintsQuery('myTable', { constraintName: 'foo_bar' }), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'DB2INST1' AND c.CONSTNAME = 'foo_bar' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = CURRENT SCHEMA AND c.CONSTRAINT_NAME = 'foo_bar' ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'myTable' AND c.constraintName = N'foo_bar' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'public' AND c.constraint_name = 'foo_bar' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'PUBLIC' AND c.CONSTRAINT_NAME = 'foo_bar' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'sequelize_test' AND c.CONSTRAINT_NAME = 'foo_bar' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query for a table with a constraint type specified', () => { + expectsql( + () => queryGenerator.showConstraintsQuery('myTable', { constraintType: 'FOREIGN KEY' }), + { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'DB2INST1' AND c.TYPE = 'F' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = CURRENT SCHEMA AND c.CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'myTable' AND c.constraintType = N'FOREIGN_KEY_CONSTRAINT' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'public' AND c.constraint_type = 'FOREIGN KEY' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'PUBLIC' AND c.CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'sequelize_test' AND c.CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }, + ); + }); + + it('produces a show constraints query for a table with a column name specified', () => { + expectsql(() => queryGenerator.showConstraintsQuery('myTable', { columnName: 'some_column' }), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'DB2INST1' AND k.COLNAME = 'some_column' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = CURRENT SCHEMA AND k.COLUMN_NAME = 'some_column' ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'myTable' AND c.columnNames = N'some_column' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'public' AND kcu.column_name = 'some_column' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: buildInvalidOptionReceivedError('showConstraintsQuery', dialect.name, [ + 'columnName', + ]), + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'sequelize_test' AND kcu.COLUMN_NAME = 'some_column' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.showConstraintsQuery(MyModel), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'MyModels' AND c.TABSCHEMA = 'DB2INST1' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = CURRENT SCHEMA ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'MyModels' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'MyModels' AND c.table_schema = 'public' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = 'PUBLIC' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'MyModels'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = 'sequelize_test' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.showConstraintsQuery(myDefinition), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'MyModels' AND c.TABSCHEMA = 'DB2INST1' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = CURRENT SCHEMA ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'MyModels' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'MyModels' AND c.table_schema = 'public' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = 'PUBLIC' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'MyModels'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'MyModels' AND c.TABLE_SCHEMA = 'sequelize_test' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query for a table and schema', () => { + expectsql( + () => queryGenerator.showConstraintsQuery({ tableName: 'myTable', schema: 'mySchema' }), + { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'mySchema' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'mySchema' AND t.name = N'myTable' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'mySchema' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'mySchema.myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }, + ); + }); + + it('produces a show constraints query for a table and default schema', () => { + expectsql( + () => + queryGenerator.showConstraintsQuery({ + tableName: 'myTable', + schema: dialect.getDefaultSchema(), + }), + { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'DB2INST1' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = CURRENT SCHEMA ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'dbo' AND t.name = N'myTable' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'public' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'PUBLIC' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'sequelize_test' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }, + ); + }); + + it('produces a show constraints query for a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.showConstraintsQuery('myTable'), { + db2: `SELECT TRIM(c.TABSCHEMA) AS "constraintSchema", c.CONSTNAME AS "constraintName", CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType", TRIM(c.TABSCHEMA) AS "tableSchema", c.TABNAME AS "tableName", k.COLNAME AS "columnNames", TRIM(r.REFTABSCHEMA) AS "referencedTableSchema", r.REFTABNAME AS "referencedTableName", fk.COLNAME AS "referencedColumnNames", CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction", CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction", ck.TEXT AS "definition" FROM SYSCAT.TABCONST c LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA WHERE c.TABNAME = 'myTable' AND c.TABSCHEMA = 'mySchema' ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ`, + ibmi: `SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", k.COLUMN_NAME AS "columnNames", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", fk.COLUMN_NAME AS "referencedColumnNames", r.DELETE_RULE AS "deleteRule", r.UPDATE_RULE AS "updateRule", ch.CHECK_CLAUSE AS "definition", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM QSYS2.SYSCST c LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION`, + mssql: `SELECT DB_NAME() AS constraintCatalog, s.[name] AS constraintSchema, c.constraintName, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType, DB_NAME() AS tableCatalog, s.[name] AS tableSchema, t.[name] AS tableName, c.columnNames, c.referencedTableSchema, c.referencedTableName, c.referencedColumnNames, c.deleteAction, c.updateAction, c.definition FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN (SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id) c ON t.object_id = c.constraintTableId WHERE s.name = N'mySchema' AND t.name = N'myTable' ORDER BY c.constraintName, c.column_id`, + postgres: `SELECT c.constraint_catalog AS "constraintCatalog", c.constraint_schema AS "constraintSchema", c.constraint_name AS "constraintName", c.constraint_type AS "constraintType", c.table_catalog AS "tableCatalog", c.table_schema AS "tableSchema", c.table_name AS "tableName", kcu.column_name AS "columnNames", ccu.table_schema AS "referencedTableSchema", ccu.table_name AS "referencedTableName", ccu.column_name AS "referencedColumnNames", r.delete_rule AS "deleteAction", r.update_rule AS "updateAction", pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition", c.is_deferrable AS "isDeferrable", c.initially_deferred AS "initiallyDeferred" FROM INFORMATION_SCHEMA.table_constraints c LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text WHERE c.table_name = 'myTable' AND c.table_schema = 'mySchema' ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name`, + snowflake: `SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog", c.CONSTRAINT_SCHEMA AS "constraintSchema", c.CONSTRAINT_NAME AS "constraintName", c.CONSTRAINT_TYPE AS "constraintType", c.TABLE_CATALOG AS "tableCatalog", c.TABLE_SCHEMA AS "tableSchema", c.TABLE_NAME AS "tableName", fk.TABLE_SCHEMA AS "referencedTableSchema", fk.TABLE_NAME AS "referencedTableName", r.DELETE_RULE AS "deleteAction", r.UPDATE_RULE AS "updateAction", c.IS_DEFERRABLE AS "isDeferrable", c.INITIALLY_DEFERRED AS "initiallyDeferred" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME`, + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'mySchema.myTable'`, + 'mariadb mysql': `SELECT c.CONSTRAINT_SCHEMA AS constraintSchema, c.CONSTRAINT_NAME AS constraintName, c.CONSTRAINT_TYPE AS constraintType, c.TABLE_SCHEMA AS tableSchema, c.TABLE_NAME AS tableName, kcu.COLUMN_NAME AS columnNames, kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema, kcu.REFERENCED_TABLE_NAME AS referencedTableName, kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames, r.DELETE_RULE AS deleteAction, r.UPDATE_RULE AS updateAction, ch.CHECK_CLAUSE AS definition FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'myTable' AND c.TABLE_SCHEMA = 'mySchema' ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION`, + }); + }); + + it('produces a show constraints query for a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.showConstraintsQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: `SELECT sql FROM sqlite_master WHERE tbl_name = 'mySchemacustommyTable'`, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/show-indexes-query.test.ts b/packages/core/test/unit/query-generator/show-indexes-query.test.ts new file mode 100644 index 000000000000..abdca5b92894 --- /dev/null +++ b/packages/core/test/unit/query-generator/show-indexes-query.test.ts @@ -0,0 +1,203 @@ +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('QueryGenerator#showIndexesQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a SHOW INDEX query from a table', () => { + expectsql(() => queryGenerator.showIndexesQuery('myTable'), { + default: `SHOW INDEX FROM [myTable]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'myTable' AND s.oid = t.relnamespace AND s.nspname = 'public' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'dbo.myTable') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`myTable`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'myTable' AND TABSCHEMA = 'DB2INST1' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = CURRENT SCHEMA and QSYS2.SYSCSTCOL.TABLE_NAME = 'myTable' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = CURRENT SCHEMA + and QSYS2.SYSINDEXES.TABLE_NAME = 'myTable'`, + }); + }); + + it('produces a SHOW INDEX query from a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.showIndexesQuery(MyModel), { + default: `SHOW INDEX FROM [MyModels]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'MyModels' AND s.oid = t.relnamespace AND s.nspname = 'public' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'dbo.MyModels') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`MyModels`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'MyModels' AND TABSCHEMA = 'DB2INST1' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = CURRENT SCHEMA and QSYS2.SYSCSTCOL.TABLE_NAME = 'MyModels' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = CURRENT SCHEMA + and QSYS2.SYSINDEXES.TABLE_NAME = 'MyModels'`, + }); + }); + + it('produces a SHOW INDEX query from a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.showIndexesQuery(myDefinition), { + default: `SHOW INDEX FROM [MyModels]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'MyModels' AND s.oid = t.relnamespace AND s.nspname = 'public' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'dbo.MyModels') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`MyModels`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'MyModels' AND TABSCHEMA = 'DB2INST1' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = CURRENT SCHEMA and QSYS2.SYSCSTCOL.TABLE_NAME = 'MyModels' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = CURRENT SCHEMA + and QSYS2.SYSINDEXES.TABLE_NAME = 'MyModels'`, + }); + }); + + it('produces a SHOW INDEX query from a table and schema', () => { + expectsql(() => queryGenerator.showIndexesQuery({ tableName: 'myTable', schema: 'mySchema' }), { + default: `SHOW INDEX FROM [mySchema].[myTable]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'myTable' AND s.oid = t.relnamespace AND s.nspname = 'mySchema' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'mySchema.myTable') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`mySchema.myTable`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = 'mySchema' and QSYS2.SYSCSTCOL.TABLE_NAME = 'myTable' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = 'mySchema' + and QSYS2.SYSINDEXES.TABLE_NAME = 'myTable'`, + }); + }); + + it('produces a SHOW INDEX query from a table and default schema', () => { + expectsql( + () => + queryGenerator.showIndexesQuery({ + tableName: 'myTable', + schema: dialect.getDefaultSchema(), + }), + { + default: `SHOW INDEX FROM [myTable]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'myTable' AND s.oid = t.relnamespace AND s.nspname = 'public' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'dbo.myTable') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`myTable`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'myTable' AND TABSCHEMA = 'DB2INST1' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = CURRENT SCHEMA and QSYS2.SYSCSTCOL.TABLE_NAME = 'myTable' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = CURRENT SCHEMA + and QSYS2.SYSINDEXES.TABLE_NAME = 'myTable'`, + }, + ); + }); + + it('produces a SHOW INDEX query from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.showIndexesQuery('myTable'), { + default: `SHOW INDEX FROM [mySchema].[myTable]`, + postgres: `SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields, + ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, + pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s + WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND + t.relkind = 'r' and t.relname = 'myTable' AND s.oid = t.relnamespace AND s.nspname = 'mySchema' + GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;`, + mssql: `SELECT I.[name] AS [index_name], I.[type_desc] AS [index_type], C.[name] AS [column_name], IC.[is_descending_key], IC.[is_included_column], I.[is_unique], I.[is_primary_key], I.[is_unique_constraint] + FROM sys.indexes I + INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id + INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id + WHERE I.[object_id] = OBJECT_ID(N'mySchema.myTable') ORDER BY I.[name];`, + sqlite3: 'PRAGMA INDEX_LIST(`mySchema.myTable`)', + snowflake: `SELECT '' FROM DUAL`, + db2: `SELECT i.INDNAME AS "name", i.TABNAME AS "tableName", i.UNIQUERULE AS "keyType", i.INDEXTYPE AS "type", c.COLNAME AS "columnName", c.COLORDER AS "columnOrder" FROM SYSCAT.INDEXES i INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema' ORDER BY i.INDNAME, c.COLSEQ;`, + ibmi: `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, + QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and + QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where + QSYS2.SYSCSTCOL.TABLE_SCHEMA = 'mySchema' and QSYS2.SYSCSTCOL.TABLE_NAME = 'myTable' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, + QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS + left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = 'mySchema' + and QSYS2.SYSINDEXES.TABLE_NAME = 'myTable'`, + }); + }); + + it('produces a SHOW INDEX query with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.showIndexesQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: 'PRAGMA INDEX_LIST(`mySchemacustommyTable`)', + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/start-transaction-query.test.ts b/packages/core/test/unit/query-generator/start-transaction-query.test.ts new file mode 100644 index 000000000000..0c23e7ac91b4 --- /dev/null +++ b/packages/core/test/unit/query-generator/start-transaction-query.test.ts @@ -0,0 +1,98 @@ +import { TransactionType } from '@sequelize/core'; +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const notSupportedError = new Error( + `startTransactionQuery is not supported by the ${dialect.name} dialect.`, +); + +describe('QueryGenerator#startTransactionQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('should generate a query for starting a transaction', () => { + expectsql(() => queryGenerator.startTransactionQuery(), { + default: 'START TRANSACTION', + sqlite3: 'BEGIN DEFERRED TRANSACTION', + 'db2 ibmi mssql': notSupportedError, + }); + }); + + it('should generate a query for starting a transaction with a name', () => { + expectsql(() => queryGenerator.startTransactionQuery({ transactionName: 'myTransaction' }), { + default: 'START TRANSACTION', + snowflake: 'START TRANSACTION NAME "myTransaction"', + sqlite3: 'BEGIN DEFERRED TRANSACTION', + 'db2 ibmi mssql': notSupportedError, + }); + }); + + it('should generate a query for starting a read-only transaction', () => { + expectsql(() => queryGenerator.startTransactionQuery({ readOnly: true }), { + default: buildInvalidOptionReceivedError('startTransactionQuery', dialect.name, ['readOnly']), + 'db2 ibmi mssql': notSupportedError, + 'mariadb mysql postgres': 'START TRANSACTION READ ONLY', + }); + }); + + it('should generate a query for starting a deferred transaction', () => { + expectsql( + () => queryGenerator.startTransactionQuery({ transactionType: TransactionType.DEFERRED }), + { + default: buildInvalidOptionReceivedError('startTransactionQuery', dialect.name, [ + 'transactionType', + ]), + sqlite3: 'BEGIN DEFERRED TRANSACTION', + 'db2 ibmi mssql': notSupportedError, + }, + ); + }); + + it('should generate a query for starting an immediate transaction', () => { + expectsql( + () => queryGenerator.startTransactionQuery({ transactionType: TransactionType.IMMEDIATE }), + { + default: buildInvalidOptionReceivedError('startTransactionQuery', dialect.name, [ + 'transactionType', + ]), + sqlite3: 'BEGIN IMMEDIATE TRANSACTION', + 'db2 ibmi mssql': notSupportedError, + }, + ); + }); + + it('should generate a query for starting an exclusive transaction', () => { + expectsql( + () => queryGenerator.startTransactionQuery({ transactionType: TransactionType.EXCLUSIVE }), + { + default: buildInvalidOptionReceivedError('startTransactionQuery', dialect.name, [ + 'transactionType', + ]), + sqlite3: 'BEGIN EXCLUSIVE TRANSACTION', + 'db2 ibmi mssql': notSupportedError, + }, + ); + }); + + it('should generate a query for starting a transaction with all options', () => { + expectsql( + () => + queryGenerator.startTransactionQuery({ + readOnly: true, + transactionName: 'myTransaction', + transactionType: TransactionType.EXCLUSIVE, + }), + { + default: buildInvalidOptionReceivedError('startTransactionQuery', dialect.name, [ + 'transactionType', + ]), + 'snowflake sqlite3': buildInvalidOptionReceivedError( + 'startTransactionQuery', + dialect.name, + ['readOnly'], + ), + 'db2 ibmi mssql': notSupportedError, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/table-exists-query.test.ts b/packages/core/test/unit/query-generator/table-exists-query.test.ts new file mode 100644 index 000000000000..a99a4dfba560 --- /dev/null +++ b/packages/core/test/unit/query-generator/table-exists-query.test.ts @@ -0,0 +1,102 @@ +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('QueryGenerator#tableExistsQuery', () => { + const queryGenerator = sequelize.queryGenerator; + const defaultSchema = dialect.getDefaultSchema(); + + it('produces a table exists query for a table', () => { + expectsql(() => queryGenerator.tableExistsQuery('myTable'), { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'myTable' AND TABLE_SCHEMA = '${defaultSchema}'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'myTable' AND TABSCHEMA = '${defaultSchema}'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'myTable' AND TABLE_SCHEMA = CURRENT SCHEMA`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'myTable' AND TABLE_SCHEMA = N'${defaultSchema}'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'myTable'`, + }); + }); + + it('produces a table exists query for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectsql(() => queryGenerator.tableExistsQuery(MyModel), { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'MyModels' AND TABLE_SCHEMA = '${defaultSchema}'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'MyModels' AND TABSCHEMA = '${defaultSchema}'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'MyModels' AND TABLE_SCHEMA = CURRENT SCHEMA`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'MyModels' AND TABLE_SCHEMA = N'${defaultSchema}'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'MyModels'`, + }); + }); + + it('produces a table exists query for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectsql(() => queryGenerator.tableExistsQuery(myDefinition), { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'MyModels' AND TABLE_SCHEMA = '${defaultSchema}'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'MyModels' AND TABSCHEMA = '${defaultSchema}'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'MyModels' AND TABLE_SCHEMA = CURRENT SCHEMA`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'MyModels' AND TABLE_SCHEMA = N'${defaultSchema}'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'MyModels'`, + }); + }); + + it('produces a table exists query for a table and schema', () => { + expectsql(() => queryGenerator.tableExistsQuery({ tableName: 'myTable', schema: 'mySchema' }), { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'myTable' AND TABLE_SCHEMA = 'mySchema'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'myTable' AND TABLE_SCHEMA = 'mySchema'`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'myTable' AND TABLE_SCHEMA = N'mySchema'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'mySchema.myTable'`, + }); + }); + + it('produces a table exists query for a table and default schema', () => { + expectsql( + () => + queryGenerator.tableExistsQuery({ + tableName: 'myTable', + schema: dialect.getDefaultSchema(), + }), + { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'myTable' AND TABLE_SCHEMA = '${defaultSchema}'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'myTable' AND TABSCHEMA = '${defaultSchema}'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'myTable' AND TABLE_SCHEMA = CURRENT SCHEMA`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'myTable' AND TABLE_SCHEMA = N'${defaultSchema}'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'myTable'`, + }, + ); + }); + + it('produces a table exists query for a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectsql(() => queryGeneratorSchema.tableExistsQuery('myTable'), { + default: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'myTable' AND TABLE_SCHEMA = 'mySchema'`, + db2: `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = 'myTable' AND TABSCHEMA = 'mySchema'`, + ibmi: `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = 'myTable' AND TABLE_SCHEMA = 'mySchema'`, + mssql: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'myTable' AND TABLE_SCHEMA = N'mySchema'`, + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'mySchema.myTable'`, + }); + }); + + it('produces a table exists query for a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectsql( + () => + queryGenerator.tableExistsQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'mySchemacustommyTable'`, + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/toggle-foreign-key-checks-query.test.ts b/packages/core/test/unit/query-generator/toggle-foreign-key-checks-query.test.ts new file mode 100644 index 000000000000..5f6e152a7573 --- /dev/null +++ b/packages/core/test/unit/query-generator/toggle-foreign-key-checks-query.test.ts @@ -0,0 +1,23 @@ +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); +const queryGenerator = sequelize.queryGenerator; +const notSupportedError = new Error(`${dialectName} does not support toggling foreign key checks`); + +describe('QueryGenerator#getToggleForeignKeyChecksQuery', () => { + it('produces a query that disables foreign key checks', () => { + expectsql(() => queryGenerator.getToggleForeignKeyChecksQuery(false), { + default: notSupportedError, + 'mysql mariadb': 'SET FOREIGN_KEY_CHECKS=0', + sqlite3: 'PRAGMA foreign_keys = OFF', + }); + }); + + it('produces a query that enables foreign key checks', () => { + expectsql(() => queryGenerator.getToggleForeignKeyChecksQuery(true), { + default: notSupportedError, + 'mysql mariadb': 'SET FOREIGN_KEY_CHECKS=1', + sqlite3: 'PRAGMA foreign_keys = ON', + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/truncate-table-query.test.ts b/packages/core/test/unit/query-generator/truncate-table-query.test.ts new file mode 100644 index 000000000000..9dfa1528c1dd --- /dev/null +++ b/packages/core/test/unit/query-generator/truncate-table-query.test.ts @@ -0,0 +1,163 @@ +import { buildInvalidOptionReceivedError } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { createSequelizeInstance, expectPerDialect, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('QueryGenerator#truncateTableQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a TRUNCATE TABLE query for a table', () => { + expectPerDialect(() => queryGenerator.truncateTableQuery('myTable'), { + mssql: 'TRUNCATE TABLE [myTable]', + sqlite3: ['DELETE FROM `myTable`'], + 'db2 ibmi': 'TRUNCATE TABLE "myTable" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `myTable`', + 'postgres snowflake': 'TRUNCATE "myTable"', + }); + }); + + it('produces a TRUNCATE TABLE query with CASCADE for a table', () => { + expectPerDialect(() => queryGenerator.truncateTableQuery('myTable', { cascade: true }), { + default: buildInvalidOptionReceivedError('truncateTableQuery', dialect.name, ['cascade']), + postgres: `TRUNCATE "myTable" CASCADE`, + }); + }); + + it('produces a TRUNCATE TABLE query with RESTART IDENTITY for a table', () => { + expectPerDialect( + () => queryGenerator.truncateTableQuery('myTable', { restartIdentity: true }), + { + default: buildInvalidOptionReceivedError('truncateTableQuery', dialect.name, [ + 'restartIdentity', + ]), + sqlite3: [ + 'DELETE FROM `myTable`', + "DELETE FROM `sqlite_sequence` WHERE `name` = 'myTable'", + ], + postgres: `TRUNCATE "myTable" RESTART IDENTITY`, + }, + ); + }); + + it('produces a TRUNCATE TABLE query with CASCADE and RESTART IDENTITY query for a table', () => { + expectPerDialect( + () => queryGenerator.truncateTableQuery('myTable', { cascade: true, restartIdentity: true }), + { + default: buildInvalidOptionReceivedError('truncateTableQuery', dialect.name, [ + 'cascade', + 'restartIdentity', + ]), + sqlite3: buildInvalidOptionReceivedError('truncateTableQuery', dialect.name, ['cascade']), + postgres: `TRUNCATE "myTable" RESTART IDENTITY CASCADE`, + }, + ); + }); + + it('produces a TRUNCATE TABLE query for a model', () => { + const MyModel = sequelize.define('MyModel', {}); + + expectPerDialect(() => queryGenerator.truncateTableQuery(MyModel), { + mssql: 'TRUNCATE TABLE [MyModels]', + sqlite3: ['DELETE FROM `MyModels`'], + 'db2 ibmi': 'TRUNCATE TABLE "MyModels" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `MyModels`', + 'postgres snowflake': 'TRUNCATE "MyModels"', + }); + }); + + it('produces a TRUNCATE TABLE query for a model definition', () => { + const MyModel = sequelize.define('MyModel', {}); + const myDefinition = MyModel.modelDefinition; + + expectPerDialect(() => queryGenerator.truncateTableQuery(myDefinition), { + mssql: 'TRUNCATE TABLE [MyModels]', + sqlite3: ['DELETE FROM `MyModels`'], + 'db2 ibmi': 'TRUNCATE TABLE "MyModels" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `MyModels`', + 'postgres snowflake': 'TRUNCATE "MyModels"', + }); + }); + + it('produces a TRUNCATE TABLE query from a table and schema', () => { + expectPerDialect( + () => queryGenerator.truncateTableQuery({ tableName: 'myTable', schema: 'mySchema' }), + { + mssql: 'TRUNCATE TABLE [mySchema].[myTable]', + sqlite3: ['DELETE FROM `mySchema.myTable`'], + 'db2 ibmi': 'TRUNCATE TABLE "mySchema"."myTable" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `mySchema`.`myTable`', + 'postgres snowflake': 'TRUNCATE "mySchema"."myTable"', + }, + ); + }); + + it('produces a TRUNCATE TABLE query from a table and default schema', () => { + expectPerDialect( + () => + queryGenerator.truncateTableQuery({ + tableName: 'myTable', + schema: dialect.getDefaultSchema(), + }), + { + mssql: 'TRUNCATE TABLE [myTable]', + sqlite3: ['DELETE FROM `myTable`'], + 'db2 ibmi': 'TRUNCATE TABLE "myTable" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `myTable`', + 'postgres snowflake': 'TRUNCATE "myTable"', + }, + ); + }); + + it('produces a TRUNCATE TABLE query from a table and globally set schema', () => { + const sequelizeSchema = createSequelizeInstance({ schema: 'mySchema' }); + const queryGeneratorSchema = sequelizeSchema.queryGenerator; + + expectPerDialect(() => queryGeneratorSchema.truncateTableQuery('myTable'), { + mssql: 'TRUNCATE TABLE [mySchema].[myTable]', + sqlite3: ['DELETE FROM `mySchema.myTable`'], + 'db2 ibmi': 'TRUNCATE TABLE "mySchema"."myTable" IMMEDIATE', + 'mariadb mysql': 'TRUNCATE `mySchema`.`myTable`', + 'postgres snowflake': 'TRUNCATE "mySchema"."myTable"', + }); + }); + + it('produces a TRUNCATE TABLE query for a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectPerDialect( + () => + queryGenerator.truncateTableQuery({ + tableName: 'myTable', + schema: 'mySchema', + delimiter: 'custom', + }), + { + sqlite3: ['DELETE FROM `mySchemacustommyTable`'], + }, + ); + }); + + it('produces a TRUNCATE TABLE query with RESTART IDENTITY for a table with schema and custom delimiter argument', () => { + // This test is only relevant for dialects that do not support schemas + if (dialect.supports.schemas) { + return; + } + + expectPerDialect( + () => + queryGenerator.truncateTableQuery( + { tableName: 'myTable', schema: 'mySchema', delimiter: 'custom' }, + { restartIdentity: true }, + ), + { + sqlite3: [ + 'DELETE FROM `mySchemacustommyTable`', + "DELETE FROM `sqlite_sequence` WHERE `name` = 'mySchemacustommyTable'", + ], + }, + ); + }); +}); diff --git a/packages/core/test/unit/query-generator/update-query.test.ts b/packages/core/test/unit/query-generator/update-query.test.ts new file mode 100644 index 000000000000..a9397fcd9b43 --- /dev/null +++ b/packages/core/test/unit/query-generator/update-query.test.ts @@ -0,0 +1,248 @@ +import { DataTypes, ParameterStyle, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#updateQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + // you'll find more replacement tests in query-generator tests + it('parses named replacements in literals', async () => { + const { User } = vars; + + const { query, bind } = queryGenerator.updateQuery( + User.table, + { + firstName: literal(':name'), + }, + literal('name = :name'), + { + replacements: { + name: 'Zoe', + }, + }, + ); + + expectsql(query, { + default: `UPDATE [Users] SET [firstName]='Zoe' WHERE name = 'Zoe'`, + mssql: `UPDATE [Users] SET [firstName]=N'Zoe' WHERE name = N'Zoe'`, + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"='Zoe' WHERE name = 'Zoe');`, + }); + expect(bind).to.deep.eq({}); + }); + + it('generates extra bind params', async () => { + const { User } = vars; + + const { query, bind } = queryGenerator.updateQuery( + User.table, + { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }, + {}, + ); + + // lastName's bind position being changed from $1 to $2 is intentional + expectsql(query, { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1,[lastName]=$1,[username]=$sequelize_2', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1,"lastName"=$1,"username"=$sequelize_2);`, + }); + expect(bind).to.deep.eq({ + sequelize_1: 'John', + sequelize_2: 'jd', + }); + }); + + it('throws an error if the bindParam option is used', () => { + const { User } = vars; + + expect(() => { + queryGenerator.updateQuery( + User.table, + { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }, + literal('first_name = $2'), + // @ts-expect-error -- intentionally testing deprecated option + { bindParam: false }, + ); + }).to.throw('The bindParam option has been removed. Use parameterStyle instead.'); + }); + + it('does not generate extra bind params with parameterStyle: REPLACEMENT', async () => { + const { User } = vars; + + const { query, bind } = queryGenerator.updateQuery( + User.table, + { + firstName: 'John', + lastName: literal('$1'), + username: 'jd', + }, + literal('first_name = $2'), + { + parameterStyle: ParameterStyle.REPLACEMENT, + }, + ); + + // lastName's bind position being changed from $1 to $2 is intentional + expectsql(query, { + default: `UPDATE [Users] SET [firstName]='John',[lastName]=$1,[username]='jd' WHERE first_name = $2`, + mssql: `UPDATE [Users] SET [firstName]=N'John',[lastName]=$1,[username]=N'jd' WHERE first_name = $2`, + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"='John',"lastName"=$1,"username"='jd' WHERE first_name = $2);`, + }); + + expect(bind).to.be.undefined; + }); + + it('binds date values', () => { + const result = queryGenerator.updateQuery( + 'myTable', + { + date: new Date('2011-03-27T10:01:55Z'), + }, + { id: 2 }, + ); + + expectsql(result, { + query: { + default: 'UPDATE [myTable] SET [date]=$sequelize_1 WHERE [id] = $sequelize_2', + db2: 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "date"=$sequelize_1 WHERE "id" = $sequelize_2);', + }, + bind: { + mysql: { + sequelize_1: '2011-03-27 10:01:55.000', + sequelize_2: 2, + }, + mariadb: { + sequelize_1: '2011-03-27 10:01:55.000', + sequelize_2: 2, + }, + db2: { + sequelize_1: '2011-03-27 10:01:55.000', + sequelize_2: 2, + }, + ibmi: { + sequelize_1: '2011-03-27 10:01:55.000', + sequelize_2: 2, + }, + snowflake: { + sequelize_1: '2011-03-27 10:01:55.000', + sequelize_2: 2, + }, + sqlite3: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + sequelize_2: 2, + }, + postgres: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + sequelize_2: 2, + }, + mssql: { + sequelize_1: '2011-03-27 10:01:55.000 +00:00', + sequelize_2: 2, + }, + }, + }); + }); + + it('binds boolean values', () => { + const result = queryGenerator.updateQuery( + 'myTable', + { + positive: true, + negative: false, + }, + { id: 2 }, + ); + + expectsql(result, { + query: { + default: + 'UPDATE [myTable] SET [positive]=$sequelize_1,[negative]=$sequelize_2 WHERE [id] = $sequelize_3', + db2: 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "positive"=$sequelize_1,"negative"=$sequelize_2 WHERE "id" = $sequelize_3);', + }, + bind: { + sqlite3: { + sequelize_1: 1, + sequelize_2: 0, + sequelize_3: 2, + }, + mysql: { + sequelize_1: 1, + sequelize_2: 0, + sequelize_3: 2, + }, + mariadb: { + sequelize_1: 1, + sequelize_2: 0, + sequelize_3: 2, + }, + mssql: { + sequelize_1: 1, + sequelize_2: 0, + sequelize_3: 2, + }, + postgres: { + sequelize_1: true, + sequelize_2: false, + sequelize_3: 2, + }, + db2: { + sequelize_1: true, + sequelize_2: false, + sequelize_3: 2, + }, + ibmi: { + sequelize_1: 1, + sequelize_2: 0, + sequelize_3: 2, + }, + snowflake: { + sequelize_1: true, + sequelize_2: false, + sequelize_3: 2, + }, + }, + }); + }); + + // TODO: Should we ignore undefined values instead? undefined is closer to "missing property" than null + it('treats undefined as null', () => { + const { query, bind } = queryGenerator.updateQuery( + 'myTable', + { + value: undefined, + name: 'bar', + }, + { id: 2 }, + ); + + expectsql(query, { + default: + 'UPDATE [myTable] SET [value]=$sequelize_1,[name]=$sequelize_2 WHERE [id] = $sequelize_3', + db2: 'SELECT * FROM FINAL TABLE (UPDATE "myTable" SET "value"=$sequelize_1,"name"=$sequelize_2 WHERE "id" = $sequelize_3);', + }); + + expect(bind).to.deep.eq({ + sequelize_1: null, + sequelize_2: 'bar', + sequelize_3: 2, + }); + }); +}); diff --git a/packages/core/test/unit/query-generator/version-query.test.ts b/packages/core/test/unit/query-generator/version-query.test.ts new file mode 100644 index 000000000000..709e962b6934 --- /dev/null +++ b/packages/core/test/unit/query-generator/version-query.test.ts @@ -0,0 +1,17 @@ +import { expectsql, sequelize } from '../../support'; + +describe('QueryGenerator#versionQuery', () => { + const queryGenerator = sequelize.queryGenerator; + + it('produces a query that returns the database version', () => { + expectsql(() => queryGenerator.versionQuery(), { + 'mariadb mysql': 'SELECT VERSION() as `version`', + postgres: 'SHOW SERVER_VERSION', + mssql: `DECLARE @ms_ver NVARCHAR(20); SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion'))); SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'`, + sqlite3: 'SELECT sqlite_version() as `version`', + snowflake: 'SELECT CURRENT_VERSION() AS "version"', + db2: 'select service_level as "version" from TABLE (sysproc.env_get_inst_info()) as A', + ibmi: `SELECT CONCAT(OS_VERSION, CONCAT('.', OS_RELEASE)) AS "version" FROM SYSIBMADM.ENV_SYS_INFO`, + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/bulk-delete.test.ts b/packages/core/test/unit/query-interface/bulk-delete.test.ts new file mode 100644 index 000000000000..69d074cfe3d7 --- /dev/null +++ b/packages/core/test/unit/query-interface/bulk-delete.test.ts @@ -0,0 +1,45 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#bulkDelete', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.bulkDelete(User, { + where: { firstName: ':id' }, + replacements: { + limit: 1, + id: '123', + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [Users] WHERE [firstName] = ':id'`, + mssql: `DELETE FROM [Users] WHERE [firstName] = N':id'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + + expect(firstCall.args[1]?.bind).to.be.undefined; + }); +}); diff --git a/packages/core/test/unit/query-interface/bulk-insert.test.ts b/packages/core/test/unit/query-interface/bulk-insert.test.ts new file mode 100644 index 000000000000..b504d1e0fbf6 --- /dev/null +++ b/packages/core/test/unit/query-interface/bulk-insert.test.ts @@ -0,0 +1,105 @@ +import { DataTypes, Transaction } from '@sequelize/core'; +import { expect } from 'chai'; +import range from 'lodash/range'; +import sinon from 'sinon'; +import { beforeAll2, expectPerDialect, sequelize, toMatchRegex, toMatchSql } from '../../support'; + +describe('QueryInterface#bulkInsert', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it('uses minimal insert queries when rows <=1000', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw').resolves([[], 0]); + + const users = range(1000).map(i => ({ firstName: `user${i}` })); + await sequelize.queryInterface.bulkInsert(User.table, users); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0).args[0]; + + expectPerDialect(() => firstCall, { + default: toMatchRegex( + /^INSERT INTO (?:`|")Users(?:`|") \((?:`|")firstName(?:`|")\) VALUES (?:\('\w+'\),){999}\('\w+'\);$/, + ), + ibmi: toMatchRegex( + /^SELECT \* FROM FINAL TABLE \(INSERT INTO "Users" \("firstName"\) VALUES (?:\('\w+'\),){999}\('\w+'\)\)$/, + ), + mssql: toMatchRegex( + /^INSERT INTO \[Users\] \(\[firstName\]\) VALUES (?:\(N'\w+'\),){999}\(N'\w+'\);$/, + ), + }); + }); + + it('uses minimal insert queries when rows >1000', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw').resolves([[], 0]); + const transaction = new Transaction(sequelize, {}); + + const users = range(2000).map(i => ({ firstName: `user${i}` })); + await sequelize.queryInterface.bulkInsert(User.table, users, { transaction }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0).args[0]; + + expectPerDialect(() => firstCall, { + default: toMatchRegex( + /^INSERT INTO (?:`|")Users(?:`|") \((?:`|")firstName(?:`|")\) VALUES (?:\('\w+'\),){1999}\('\w+'\);$/, + ), + ibmi: toMatchRegex( + /^SELECT \* FROM FINAL TABLE \(INSERT INTO "Users" \("firstName"\) VALUES (?:\('\w+'\),){1999}\('\w+'\)\)$/, + ), + mssql: toMatchRegex( + /^(?:INSERT INTO \[Users\] \(\[firstName\]\) VALUES (?:\(N'\w+'\),){999}\(N'\w+'\);){2}$/, + ), + }); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw').resolves([[], 0]); + + await sequelize.queryInterface.bulkInsert( + User.table, + [ + { + firstName: ':injection', + }, + ], + { + replacements: { + injection: 'raw sql', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0).args[0]; + + expectPerDialect(() => firstCall, { + default: toMatchSql('INSERT INTO "Users" ("firstName") VALUES (\':injection\');'), + 'mysql mariadb sqlite3': toMatchSql( + "INSERT INTO `Users` (`firstName`) VALUES (':injection');", + ), + mssql: toMatchSql(`INSERT INTO [Users] ([firstName]) VALUES (N':injection');`), + // TODO: db2 should use the same system as ibmi + ibmi: toMatchSql( + `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES (':injection'))`, + ), + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/bulk-update.test.ts b/packages/core/test/unit/query-interface/bulk-update.test.ts new file mode 100644 index 000000000000..7fe18bac8103 --- /dev/null +++ b/packages/core/test/unit/query-interface/bulk-update.test.ts @@ -0,0 +1,142 @@ +import { DataTypes, Op, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#bulkUpdate', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw').resolves([[], 0]); + + await sequelize.queryInterface.bulkUpdate( + User.table, + { + // values + firstName: ':injection', + }, + { + // where + firstName: ':injection', + }, + { + replacements: { + injection: 'raw sql', + }, + }, + {}, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [firstName] = $sequelize_2`, + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "firstName" = $sequelize_2);`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: ':injection', + sequelize_2: ':injection', + }); + }); + + it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => { + const { User } = vars; + sinon.stub(sequelize, 'queryRaw'); + + await expect( + sequelize.queryInterface.bulkUpdate( + User.table, + { + firstName: literal('$sequelize_test'), + }, + {}, + { + bind: { + sequelize_test: 'raw sql', + }, + }, + ), + ).to.be.rejectedWith( + 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.', + ); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.bulkUpdate( + User.table, + { + firstName: 'newName', + }, + { + // where + firstName: { [Op.eq]: literal('$one') }, + }, + { + bind: { one: 'bind1' }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [firstName] = $one', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "firstName" = $one);`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: 'newName', + one: 'bind1', + }); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.bulkUpdate( + User.table, + { + firstName: 'newName', + }, + { + // where + firstName: { [Op.eq]: literal('$1') }, + }, + { + bind: ['bind1'], + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [firstName] = $1', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "firstName" = $1);`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: 'newName', + 1: 'bind1', + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/create-table.test.ts b/packages/core/test/unit/query-interface/create-table.test.ts new file mode 100644 index 000000000000..f81d2c7633eb --- /dev/null +++ b/packages/core/test/unit/query-interface/create-table.test.ts @@ -0,0 +1,122 @@ +import { DataTypes, JSON_NULL, sql } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { createSequelizeInstance, expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('QueryInterface#createTable', () => { + afterEach(() => { + sinon.restore(); + }); + + it('supports sql.uuidV4 default values', async () => { + const localSequelize = + dialect.name === 'postgres' + ? createSequelizeInstance({ + databaseVersion: '13.0.0', + }) + : sequelize; + + const stub = sinon.stub(localSequelize, 'queryRaw'); + + await localSequelize.queryInterface.createTable('table', { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: sql.uuidV4, + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + postgres: + 'CREATE TABLE IF NOT EXISTS "table" ("id" UUID DEFAULT gen_random_uuid(), PRIMARY KEY ("id"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `table` (`id` CHAR(36) BINARY, PRIMARY KEY (`id`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[table]', 'U') IS NULL CREATE TABLE [table] ([id] UNIQUEIDENTIFIER DEFAULT NEWID(), PRIMARY KEY ([id]));`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `table` (`id` TEXT PRIMARY KEY);', + snowflake: 'CREATE TABLE IF NOT EXISTS "table" ("id" VARCHAR(36), PRIMARY KEY ("id"));', + db2: 'CREATE TABLE IF NOT EXISTS "table" ("id" CHAR(36) FOR BIT DATA NOT NULL, PRIMARY KEY ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "table" ("id" CHAR(36), PRIMARY KEY ("id")); END`, + }); + }); + + if (dialect.name === 'postgres') { + // gen_random_uuid was added in postgres 13 + it('supports sql.uuidV4 default values (postgres < 13)', async () => { + const localSequelize = createSequelizeInstance({ + databaseVersion: '12.0.0', + }); + + const stub = sinon.stub(localSequelize, 'queryRaw'); + + await localSequelize.queryInterface.createTable('table', { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: sql.uuidV4, + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + postgres: + 'CREATE TABLE IF NOT EXISTS "table" ("id" UUID DEFAULT uuid_generate_v4(), PRIMARY KEY ("id"));', + }); + }); + } + + it('supports sql.uuidV1 default values', async () => { + const stub = sinon.stub(sequelize, 'queryRaw'); + await sequelize.queryInterface.createTable('table', { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: sql.uuidV1, + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + postgres: + 'CREATE TABLE IF NOT EXISTS "table" ("id" UUID DEFAULT uuid_generate_v1(), PRIMARY KEY ("id"));', + mysql: + 'CREATE TABLE IF NOT EXISTS `table` (`id` CHAR(36) BINARY DEFAULT (UUID()), PRIMARY KEY (`id`)) ENGINE=InnoDB;', + mariadb: + 'CREATE TABLE IF NOT EXISTS `table` (`id` CHAR(36) BINARY DEFAULT UUID(), PRIMARY KEY (`id`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[table]', 'U') IS NULL CREATE TABLE [table] ([id] UNIQUEIDENTIFIER, PRIMARY KEY ([id]));`, + sqlite3: 'CREATE TABLE IF NOT EXISTS `table` (`id` TEXT PRIMARY KEY);', + snowflake: 'CREATE TABLE IF NOT EXISTS "table" ("id" VARCHAR(36), PRIMARY KEY ("id"));', + db2: 'CREATE TABLE IF NOT EXISTS "table" ("id" CHAR(36) FOR BIT DATA NOT NULL, PRIMARY KEY ("id"));', + ibmi: `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE "table" ("id" CHAR(36), PRIMARY KEY ("id")); END`, + }); + }); + + it('supports JSON_NULL default values', async () => { + if (!dialect.supports.dataTypes.JSON) { + return; + } + + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.createTable('table', { + json: { + type: DataTypes.JSON, + defaultValue: JSON_NULL, + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + postgres: `CREATE TABLE IF NOT EXISTS "table" ("json" JSON DEFAULT 'null');`, + 'mariadb mysql': 'CREATE TABLE IF NOT EXISTS `table` (`json` JSON) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[table]', 'U') IS NULL CREATE TABLE [table] ([json] NVARCHAR(MAX) DEFAULT N'null');`, + sqlite3: "CREATE TABLE IF NOT EXISTS `table` (`json` TEXT DEFAULT 'null');", + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/decrement.test.ts b/packages/core/test/unit/query-interface/decrement.test.ts new file mode 100644 index 000000000000..90898537f473 --- /dev/null +++ b/packages/core/test/unit/query-interface/decrement.test.ts @@ -0,0 +1,59 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#decrement', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.decrement( + User, + User.table, + // where + { firstName: ':firstName' }, + // incrementAmountsByField + { age: ':age' }, + // extraAttributesToBeUpdated + { name: ':name' }, + // options + { + returning: [':data'], + replacements: { + age: 1, + id: 2, + data: 3, + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `UPDATE [Users] SET [age]=[age]- ':age',[name]=':name' WHERE [firstName] = ':firstName'`, + mssql: `UPDATE [Users] SET [age]=[age]- N':age',[name]=N':name' OUTPUT INSERTED.[:data] WHERE [firstName] = N':firstName'`, + sqlite3: + "UPDATE `Users` SET `age`=`age`- ':age',`name`=':name' WHERE `firstName` = ':firstName' RETURNING `:data`", + postgres: `UPDATE "Users" SET "age"="age"- ':age',"name"=':name' WHERE "firstName" = ':firstName' RETURNING ":data"`, + }); + expect(firstCall.args[1]?.bind).to.be.undefined; + }); +}); diff --git a/packages/core/test/unit/query-interface/delete.test.ts b/packages/core/test/unit/query-interface/delete.test.ts new file mode 100644 index 000000000000..52ada38268da --- /dev/null +++ b/packages/core/test/unit/query-interface/delete.test.ts @@ -0,0 +1,45 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#delete', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.bulkDelete(User, { + where: { firstName: ':id' }, + replacements: { + limit: 1, + id: '123', + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `DELETE FROM [Users] WHERE [firstName] = ':id'`, + mssql: `DELETE FROM [Users] WHERE [firstName] = N':id'; SELECT @@ROWCOUNT AS AFFECTEDROWS;`, + }); + + expect(firstCall.args[1]?.bind).to.be.undefined; + }); +}); diff --git a/packages/core/test/unit/query-interface/drop-table.test.ts b/packages/core/test/unit/query-interface/drop-table.test.ts new file mode 100644 index 000000000000..b1b9261dfd24 --- /dev/null +++ b/packages/core/test/unit/query-interface/drop-table.test.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { expectsql, getTestDialect, sequelize } from '../../support'; + +const dialectName = getTestDialect(); + +describe('QueryInterface#dropTable', () => { + afterEach(() => { + sinon.restore(); + }); + + it('produces a DROP TABLE query with cascade', async () => { + if (sequelize.dialect.supports.dropTable.cascade) { + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.dropTable('myTable', { cascade: true }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'DROP TABLE IF EXISTS [myTable] CASCADE', + }); + } else { + await expect( + sequelize.queryInterface.dropTable('myTable', { cascade: true }), + ).to.be.rejectedWith( + `The following options are not supported by dropTableQuery in ${dialectName}: cascade`, + ); + } + }); +}); diff --git a/packages/core/test/unit/query-interface/increment.test.ts b/packages/core/test/unit/query-interface/increment.test.ts new file mode 100644 index 000000000000..a89ed0a8746a --- /dev/null +++ b/packages/core/test/unit/query-interface/increment.test.ts @@ -0,0 +1,59 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#increment', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.increment( + User, + User.table, + // where + { firstName: ':firstName' }, + // incrementAmountsByField + { age: ':age' }, + // extraAttributesToBeUpdated + { name: ':name' }, + // options + { + returning: [':data'], + replacements: { + age: 1, + id: 2, + data: 3, + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `UPDATE [Users] SET [age]=[age]+ ':age',[name]=':name' WHERE [firstName] = ':firstName'`, + mssql: `UPDATE [Users] SET [age]=[age]+ N':age',[name]=N':name' OUTPUT INSERTED.[:data] WHERE [firstName] = N':firstName'`, + sqlite3: + "UPDATE `Users` SET `age`=`age`+ ':age',`name`=':name' WHERE `firstName` = ':firstName' RETURNING `:data`", + postgres: `UPDATE "Users" SET "age"="age"+ ':age',"name"=':name' WHERE "firstName" = ':firstName' RETURNING ":data"`, + }); + expect(firstCall.args[1]?.bind).to.be.undefined; + }); +}); diff --git a/packages/core/test/unit/query-interface/insert.test.ts b/packages/core/test/unit/query-interface/insert.test.ts new file mode 100644 index 000000000000..f55b6083a19f --- /dev/null +++ b/packages/core/test/unit/query-interface/insert.test.ts @@ -0,0 +1,140 @@ +import { DataTypes, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#insert', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.insert( + null, + User.table, + { + firstName: 'Zoe', + }, + { + returning: [':data'], + replacements: { + data: 'abc', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1);', + sqlite3: 'INSERT INTO `Users` (`firstName`) VALUES ($sequelize_1) RETURNING `:data`;', + postgres: `INSERT INTO "Users" ("firstName") VALUES ($sequelize_1) RETURNING ":data";`, + mssql: `INSERT INTO [Users] ([firstName]) OUTPUT INSERTED.[:data] VALUES ($sequelize_1);`, + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName") VALUES ($sequelize_1))`, + }); + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: 'Zoe', + }); + }); + + it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => { + const { User } = vars; + sinon.stub(sequelize, 'queryRaw'); + + await expect( + sequelize.queryInterface.insert( + null, + User.table, + { + firstName: literal('$sequelize_test'), + }, + { + bind: { + sequelize_test: 'test', + }, + }, + ), + ).to.be.rejectedWith( + 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.', + ); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.insert( + null, + User.table, + { + firstName: literal('$firstName'), + lastName: 'Doe', + }, + { + bind: { + firstName: 'John', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($firstName,$sequelize_1);', + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName") VALUES ($firstName,$sequelize_1));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName") VALUES ($firstName,$sequelize_1))`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + firstName: 'John', + sequelize_1: 'Doe', + }); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.insert( + null, + User.table, + { + firstName: literal('$1'), + lastName: 'Doe', + }, + { + bind: ['John'], + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($1,$sequelize_1);', + db2: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName") VALUES ($1,$sequelize_1));`, + ibmi: `SELECT * FROM FINAL TABLE (INSERT INTO "Users" ("firstName","lastName") VALUES ($1,$sequelize_1))`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + 1: 'John', + sequelize_1: 'Doe', + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/raw-select.test.ts b/packages/core/test/unit/query-interface/raw-select.test.ts new file mode 100644 index 000000000000..49a3dc1d426d --- /dev/null +++ b/packages/core/test/unit/query-interface/raw-select.test.ts @@ -0,0 +1,51 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#rawSelect', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse user-provided data as replacements', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.rawSelect( + User.table, + { + // @ts-expect-error -- we'll fix the typings when we migrate query-generator to TypeScript + attributes: ['id'], + where: { + username: 'some :data', + }, + replacements: { + data: "OR ' = ", + }, + }, + 'id', + User, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, + mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/select.test.ts b/packages/core/test/unit/query-interface/select.test.ts new file mode 100644 index 000000000000..32c424bc9583 --- /dev/null +++ b/packages/core/test/unit/query-interface/select.test.ts @@ -0,0 +1,46 @@ +import { DataTypes } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#select', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse user-provided data as replacements', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.select(User, User.table, { + // @ts-expect-error -- we'll fix the typings when we migrate query-generator to TypeScript + attributes: ['id'], + where: { + username: 'some :data', + }, + replacements: { + data: "OR ' = ", + }, + }); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = 'some :data';`, + mssql: `SELECT [id] FROM [Users] AS [User] WHERE [User].[username] = N'some :data';`, + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/update.test.ts b/packages/core/test/unit/query-interface/update.test.ts new file mode 100644 index 000000000000..0cb7421f5205 --- /dev/null +++ b/packages/core/test/unit/query-interface/update.test.ts @@ -0,0 +1,144 @@ +import { DataTypes, Op, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +describe('QueryInterface#update', () => { + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + const instance = User.build(); + + await sequelize.queryInterface.update( + instance, + User.table, + { firstName: ':name' }, + { firstName: ':firstName' }, + { + returning: [':data'], + replacements: { + name: 'Zoe', + data: 'abc', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [firstName] = $sequelize_2', + sqlite3: + 'UPDATE `Users` SET `firstName`=$sequelize_1 WHERE `firstName` = $sequelize_2 RETURNING `:data`', + postgres: + 'UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "firstName" = $sequelize_2 RETURNING ":data"', + mssql: + 'UPDATE [Users] SET [firstName]=$sequelize_1 OUTPUT INSERTED.[:data] WHERE [firstName] = $sequelize_2', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "firstName" = $sequelize_2);`, + }); + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: ':name', + sequelize_2: ':firstName', + }); + }); + + it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => { + const { User } = vars; + sinon.stub(sequelize, 'queryRaw'); + + const instance = User.build(); + + await expect( + sequelize.queryInterface.update( + instance, + User.table, + { firstName: 'newName' }, + { id: literal('$sequelize_test') }, + { + bind: { + sequelize_test: 'test', + }, + }, + ), + ).to.be.rejectedWith( + 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.', + ); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + const instance = User.build(); + + await sequelize.queryInterface.update( + instance, + User.table, + { firstName: 'newName' }, + { id: { [Op.eq]: literal('$id') } }, + { + bind: { + id: 'test', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [id] = $id', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "id" = $id);`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: 'newName', + id: 'test', + }); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + const instance = User.build(); + + await sequelize.queryInterface.update( + instance, + User.table, + { firstName: 'newName' }, + { id: { [Op.eq]: literal('$1') } }, + { + bind: ['test'], + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: 'UPDATE [Users] SET [firstName]=$sequelize_1 WHERE [id] = $1', + db2: `SELECT * FROM FINAL TABLE (UPDATE "Users" SET "firstName"=$sequelize_1 WHERE "id" = $1);`, + }); + + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: 'newName', + 1: 'test', + }); + }); +}); diff --git a/packages/core/test/unit/query-interface/upsert.test.ts b/packages/core/test/unit/query-interface/upsert.test.ts new file mode 100644 index 000000000000..76861783dfd4 --- /dev/null +++ b/packages/core/test/unit/query-interface/upsert.test.ts @@ -0,0 +1,262 @@ +import { DataTypes, literal } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, expectsql, sequelize } from '../../support'; + +const dialectName = sequelize.dialect.name; + +describe('QueryInterface#upsert', () => { + if (!sequelize.dialect.supports.upserts) { + return; + } + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + firstName: DataTypes.STRING, + }, + { timestamps: false }, + ); + + return { User }; + }); + + afterEach(() => { + sinon.restore(); + }); + + // you'll find more replacement tests in query-generator tests + it('does not parse replacements outside of raw sql', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.upsert( + User.tableName, + { firstName: ':name' }, + { firstName: ':name' }, + { id: ':id' }, + { + model: User, + replacements: { + name: 'Zoe', + data: 'abc', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: + 'INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1) ON CONFLICT ([id]) DO UPDATE SET [firstName]=EXCLUDED.[firstName];', + 'mariadb mysql': + 'INSERT INTO `Users` (`firstName`) VALUES ($sequelize_1) ON DUPLICATE KEY UPDATE `firstName`=$sequelize_1;', + mssql: ` + MERGE INTO [Users] WITH(HOLDLOCK) + AS [Users_target] + USING (VALUES(N':name')) AS [Users_source]([firstName]) + ON [Users_target].[id] = [Users_source].[id] + WHEN MATCHED THEN + UPDATE SET [Users_target].[firstName] = N':name' + WHEN NOT MATCHED THEN + INSERT ([firstName]) VALUES(N':name') OUTPUT $action, INSERTED.*; + `, + db2: ` + MERGE INTO "Users" + AS "Users_target" + USING (VALUES(':name')) AS "Users_source"("firstName") + ON "Users_target"."id" = "Users_source"."id" + WHEN MATCHED THEN + UPDATE SET "Users_target"."firstName" = ':name' + WHEN NOT MATCHED THEN + INSERT ("firstName") VALUES(':name'); + `, + }); + + if (dialectName === 'mssql' || dialectName === 'db2') { + expect(firstCall.args[1]?.bind).to.be.undefined; + } else { + expect(firstCall.args[1]?.bind).to.deep.eq({ + sequelize_1: ':name', + }); + } + }); + + it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => { + const { User } = vars; + sinon.stub(sequelize, 'queryRaw'); + + await expect( + sequelize.queryInterface.upsert( + User.tableName, + { firstName: literal('$sequelize_test') }, + { firstName: ':name' }, + { id: ':id' }, + { + model: User, + bind: { + sequelize_test: 'test', + }, + }, + ), + ).to.be.rejectedWith( + 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.', + ); + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.upsert( + User.tableName, + { + firstName: literal('$firstName'), + lastName: 'Doe', + }, + {}, + // TODO: weird mssql/db2 specific behavior that should be unified + dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {}, + { + model: User, + bind: { + firstName: 'John', + }, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: + 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($firstName,$sequelize_1) ON CONFLICT ([id]) DO NOTHING;', + 'mariadb mysql': + 'INSERT INTO `Users` (`firstName`,`lastName`) VALUES ($firstName,$sequelize_1) ON DUPLICATE KEY UPDATE `id`=`id`;', + mssql: ` + MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target] + USING (VALUES($firstName, N'Doe')) AS [Users_source]([firstName], [lastName]) + ON [Users_target].[id] = [Users_source].[id] + WHEN NOT MATCHED THEN + INSERT ([firstName], [lastName]) VALUES($firstName, N'Doe') + OUTPUT $action, INSERTED.*; + `, + db2: ` + MERGE INTO "Users" AS "Users_target" + USING (VALUES($firstName, 'Doe')) AS "Users_source"("firstName", "lastName") + ON "Users_target"."id" = "Users_source"."id" + WHEN NOT MATCHED THEN + INSERT ("firstName", "lastName") VALUES($firstName, 'Doe'); + `, + }); + + if (dialectName === 'mssql' || dialectName === 'db2') { + expect(firstCall.args[1]?.bind).to.deep.eq({ + firstName: 'John', + }); + } else { + expect(firstCall.args[1]?.bind).to.deep.eq({ + firstName: 'John', + sequelize_1: 'Doe', + }); + } + }); + + it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.upsert( + User.tableName, + { + firstName: literal('$1'), + lastName: 'Doe', + }, + {}, + // TODO: weird mssql/db2 specific behavior that should be unified + dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {}, + { + model: User, + bind: ['John'], + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: + 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($1,$sequelize_1) ON CONFLICT ([id]) DO NOTHING;', + 'mariadb mysql': + 'INSERT INTO `Users` (`firstName`,`lastName`) VALUES ($1,$sequelize_1) ON DUPLICATE KEY UPDATE `id`=`id`;', + mssql: ` + MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target] + USING (VALUES($1, N'Doe')) AS [Users_source]([firstName], [lastName]) + ON [Users_target].[id] = [Users_source].[id] + WHEN NOT MATCHED THEN + INSERT ([firstName], [lastName]) VALUES($1, N'Doe') + OUTPUT $action, INSERTED.*; + `, + db2: ` + MERGE INTO "Users" AS "Users_target" + USING (VALUES($1, 'Doe')) AS "Users_source"("firstName", "lastName") + ON "Users_target"."id" = "Users_source"."id" + WHEN NOT MATCHED THEN + INSERT ("firstName", "lastName") VALUES($1, 'Doe'); + `, + }); + + // mssql does not generate any bind parameter + if (dialectName === 'mssql' || dialectName === 'db2') { + expect(firstCall.args[1]?.bind).to.deep.eq(['John']); + } else { + expect(firstCall.args[1]?.bind).to.deep.eq({ + 1: 'John', + sequelize_1: 'Doe', + }); + } + }); + + it('binds parameters if they are literals', async () => { + const { User } = vars; + const stub = sinon.stub(sequelize, 'queryRaw'); + + await sequelize.queryInterface.upsert( + User.tableName, + { + firstName: 'Jonh', + counter: literal('`counter` + 1'), + }, + { + counter: literal('`counter` + 1'), + }, + // TODO: weird mssql/db2 specific behavior that should be unified + dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {}, + { + model: User, + }, + ); + + expect(stub.callCount).to.eq(1); + const firstCall = stub.getCall(0); + expectsql(firstCall.args[0], { + default: + 'INSERT INTO `Users` (`firstName`,`counter`) VALUES ($sequelize_1,`counter` + 1) ON DUPLICATE KEY UPDATE `counter`=`counter` + 1;', + postgres: + 'INSERT INTO "Users" ("firstName","counter") VALUES ($sequelize_1,`counter` + 1) ON CONFLICT ("id") DO UPDATE SET "counter"=EXCLUDED."counter";', + mssql: ` + MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target] + USING (VALUES(N'Jonh', \`counter\` + 1)) AS [Users_source]([firstName], [counter]) + ON [Users_target].[id] = [Users_source].[id] WHEN MATCHED THEN UPDATE SET [Users_target].[counter] = \`counter\` + 1 + WHEN NOT MATCHED THEN INSERT ([firstName], [counter]) VALUES(N'Jonh', \`counter\` + 1) OUTPUT $action, INSERTED.*; + `, + sqlite3: + 'INSERT INTO `Users` (`firstName`,`counter`) VALUES ($sequelize_1,`counter` + 1) ON CONFLICT (`id`) DO UPDATE SET `counter`=EXCLUDED.`counter`;', + db2: ` + MERGE INTO "Users" AS "Users_target" + USING (VALUES('Jonh', \`counter\` + 1)) AS "Users_source"("firstName", "counter") + ON "Users_target"."id" = "Users_source"."id" WHEN MATCHED THEN UPDATE SET "Users_target"."counter" = \`counter\` + 1 + WHEN NOT MATCHED THEN INSERT ("firstName", "counter") VALUES('Jonh', \`counter\` + 1); + `, + }); + }); +}); diff --git a/packages/core/test/unit/sequelize.test.ts b/packages/core/test/unit/sequelize.test.ts new file mode 100644 index 000000000000..be37de1de609 --- /dev/null +++ b/packages/core/test/unit/sequelize.test.ts @@ -0,0 +1,109 @@ +import { Sequelize, sql } from '@sequelize/core'; +import { expect } from 'chai'; +import type { SinonStub } from 'sinon'; +import sinon from 'sinon'; +import { beforeEach2, createSequelizeInstance, sequelize } from '../support'; + +describe('Sequelize', () => { + describe('version', () => { + it('should be a string', () => { + expect(typeof Sequelize.version).to.eq('string'); + }); + }); + + describe('query', () => { + let stubs: Array> = []; + + afterEach(() => { + for (const stub of stubs) { + stub.restore(); + } + + stubs = []; + }); + + it('supports sql expressions', async () => { + // mock sequelize.queryRaw using sinon + stubs.push(sinon.stub(sequelize, 'queryRaw').resolves([[], 0])); + + await sequelize.query(sql`SELECT * FROM "users" WHERE id = ${1} AND id2 = :id2`, { + replacements: { + id2: 2, + }, + }); + + expect(sequelize.queryRaw).to.have.been.calledWith( + 'SELECT * FROM "users" WHERE id = 1 AND id2 = 2', + ); + }); + }); + + describe('close', () => { + it('clears the pool & closes Sequelize', async () => { + const options = { + replication: null, + }; + + const sequelize2 = createSequelizeInstance(options); + + const poolClearSpy = sinon.spy(sequelize2.pool, 'destroyAllNow'); + + await sequelize2.close(); + + expect(poolClearSpy.calledOnce).to.be.true; + expect(sequelize2.isClosed()).to.be.true; + }); + }); + + describe('init', () => { + afterEach(async () => { + Sequelize.hooks.removeAllListeners(); + }); + + it('beforeInit hook can alter options', () => { + Sequelize.hooks.addListener('beforeInit', options => { + options.databaseVersion = sequelize.dialect.minimumDatabaseVersion; + }); + + const seq = createSequelizeInstance(); + + expect(seq.getDatabaseVersion()).to.equal(sequelize.dialect.minimumDatabaseVersion); + }); + + it('afterInit hook cannot alter options', () => { + Sequelize.hooks.addListener('afterInit', sequelize2 => { + // @ts-expect-error -- only exists in some dialects but the principle remains identical + sequelize2.options.protocol = 'udp'; + }); + + expect(() => createSequelizeInstance()).to.throw(); + }); + }); + + describe('log', () => { + it('is disabled by default', () => { + expect(sequelize.options.logging).to.equal(false); + }); + + describe('with a custom function for logging', () => { + const vars = beforeEach2(() => { + const spy = sinon.spy(); + + return { spy, sequelize: createSequelizeInstance({ logging: spy }) }; + }); + + it('calls the custom logger method', () => { + vars.sequelize.log('om nom'); + expect(vars.spy.calledOnce).to.be.true; + }); + + it('calls the custom logger method with options', () => { + const message = 'om nom'; + const timeTaken = 5; + const options = { correlationId: 'ABC001' }; + vars.sequelize.log(message, timeTaken, options); + expect(vars.spy.withArgs(message, timeTaken, options).calledOnce).to.be.true; + }); + }); + }); +}); diff --git a/packages/core/test/unit/sql/add-column.test.js b/packages/core/test/unit/sql/add-column.test.js new file mode 100644 index 000000000000..80fc01bdd300 --- /dev/null +++ b/packages/core/test/unit/sql/add-column.test.js @@ -0,0 +1,122 @@ +'use strict'; + +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +const sql = current.dialect.queryGenerator; + +const customSequelize = Support.createSequelizeInstance({ + schema: 'custom', +}); +const customSql = customSequelize.dialect.queryGenerator; + +describe(Support.getTestDialectTeaser('SQL'), () => { + describe('addColumn', () => { + const User = current.define('User', {}, { timestamps: false }); + + if (['mysql', 'mariadb'].includes(current.dialect.name)) { + it('properly generate alter queries', () => { + return expectsql( + sql.addColumnQuery( + User.table, + 'level_id', + current.normalizeAttribute({ + type: DataTypes.FLOAT, + allowNull: false, + }), + ), + { + mariadb: 'ALTER TABLE `Users` ADD `level_id` FLOAT NOT NULL;', + mysql: 'ALTER TABLE `Users` ADD `level_id` FLOAT NOT NULL;', + }, + ); + }); + + it('properly generate alter queries for foreign keys', () => { + return expectsql( + sql.addColumnQuery( + User.table, + 'level_id', + current.normalizeAttribute({ + type: DataTypes.INTEGER, + references: { + table: 'level', + key: 'id', + }, + onUpdate: 'cascade', + onDelete: 'cascade', + }), + ), + { + mariadb: + 'ALTER TABLE `Users` ADD `level_id` INTEGER, ADD CONSTRAINT `Users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', + mysql: + 'ALTER TABLE `Users` ADD `level_id` INTEGER, ADD CONSTRAINT `Users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', + }, + ); + }); + + it('properly generate alter queries with FIRST', () => { + return expectsql( + sql.addColumnQuery( + User.table, + 'test_added_col_first', + current.normalizeAttribute({ + type: DataTypes.STRING, + first: true, + }), + ), + { + mariadb: 'ALTER TABLE `Users` ADD `test_added_col_first` VARCHAR(255) FIRST;', + mysql: 'ALTER TABLE `Users` ADD `test_added_col_first` VARCHAR(255) FIRST;', + }, + ); + }); + + it('properly generates alter queries with column level comment', () => { + return expectsql( + sql.addColumnQuery( + User.table, + 'column_with_comment', + current.normalizeAttribute({ + type: DataTypes.STRING, + comment: 'This is a comment', + }), + ), + { + mariadb: + "ALTER TABLE `Users` ADD `column_with_comment` VARCHAR(255) COMMENT 'This is a comment';", + mysql: + "ALTER TABLE `Users` ADD `column_with_comment` VARCHAR(255) COMMENT 'This is a comment';", + }, + ); + }); + } + + it('defaults the schema to the one set in the Sequelize options', () => { + const User = customSequelize.define('User', {}, { timestamps: false }); + + return expectsql( + customSql.addColumnQuery( + User.table, + 'level_id', + customSequelize.normalizeAttribute({ + type: DataTypes.FLOAT, + allowNull: false, + }), + ), + { + 'mariadb mysql': 'ALTER TABLE `custom`.`Users` ADD `level_id` FLOAT NOT NULL;', + postgres: 'ALTER TABLE "custom"."Users" ADD COLUMN "level_id" REAL NOT NULL;', + sqlite3: 'ALTER TABLE `custom.Users` ADD `level_id` REAL NOT NULL;', + mssql: 'ALTER TABLE [custom].[Users] ADD [level_id] REAL NOT NULL;', + db2: 'ALTER TABLE "custom"."Users" ADD "level_id" REAL NOT NULL;', + snowflake: 'ALTER TABLE "custom"."Users" ADD "level_id" FLOAT NOT NULL;', + ibmi: 'ALTER TABLE "custom"."Users" ADD "level_id" REAL NOT NULL', + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/sql/change-column.test.js b/packages/core/test/unit/sql/change-column.test.js new file mode 100644 index 000000000000..d96a70a90e37 --- /dev/null +++ b/packages/core/test/unit/sql/change-column.test.js @@ -0,0 +1,90 @@ +'use strict'; + +const sinon = require('sinon'); +const { beforeAll2, expectsql, sequelize } = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +describe('QueryInterface#changeColumn', () => { + if (sequelize.dialect.name === 'sqlite3') { + return; + } + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'users', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + level_id: { + type: DataTypes.INTEGER, + }, + }, + { timestamps: false }, + ); + + const stub = sinon.stub(sequelize, 'queryRaw').resolvesArg(0); + + return { User, stub }; + }); + + beforeEach(() => { + vars.stub.resetHistory(); + }); + + after(() => { + vars.stub.restore(); + }); + + it('properly generate alter queries', async () => { + const { User } = vars; + + const sql = await sequelize.queryInterface.changeColumn(User.table, 'level_id', { + type: DataTypes.FLOAT, + allowNull: false, + }); + + expectsql(sql, { + ibmi: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET DATA TYPE REAL NOT NULL', + mssql: 'ALTER TABLE [users] ALTER COLUMN [level_id] REAL NOT NULL;', + db2: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET DATA TYPE REAL ALTER COLUMN "level_id" SET NOT NULL;', + mariadb: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', + mysql: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', + postgres: + 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE REAL;', + snowflake: + 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE FLOAT;', + }); + }); + + it('properly generate alter queries for foreign keys', async () => { + const { User } = vars; + + const sql = await sequelize.queryInterface.changeColumn(User.table, 'level_id', { + type: DataTypes.INTEGER, + references: { + table: 'level', + key: 'id', + }, + onUpdate: 'cascade', + onDelete: 'cascade', + }); + + expectsql(sql, { + ibmi: 'ALTER TABLE "users" ADD CONSTRAINT "level_id" FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE', + mssql: + 'ALTER TABLE [users] ADD FOREIGN KEY ([level_id]) REFERENCES [level] ([id]) ON DELETE CASCADE;', + db2: 'ALTER TABLE "users" ADD CONSTRAINT "level_id_foreign_idx" FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE;', + mariadb: + 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', + mysql: + 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', + postgres: + 'ALTER TABLE "users" ADD FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;', + snowflake: + 'ALTER TABLE "users" ADD FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;', + }); + }); +}); diff --git a/packages/core/test/unit/sql/create-table.test.js b/packages/core/test/unit/sql/create-table.test.js new file mode 100644 index 000000000000..26b09ddd3b03 --- /dev/null +++ b/packages/core/test/unit/sql/create-table.test.js @@ -0,0 +1,142 @@ +'use strict'; + +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +const sql = current.dialect.queryGenerator; + +describe(Support.getTestDialectTeaser('SQL'), () => { + if (current.dialect.name === 'snowflake') { + return; + } + + describe('createTable', () => { + describe('with enums', () => { + it('references enum in the right schema #3171', () => { + const FooUser = current.define( + 'user', + { + mood: DataTypes.ENUM('happy', 'sad'), + }, + { + schema: 'foo', + timestamps: false, + }, + ); + + expectsql( + sql.createTableQuery(FooUser.table, sql.attributesToSQL(FooUser.getAttributes()), {}), + { + sqlite3: + 'CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `mood` TEXT);', + db2: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) , "mood" VARCHAR(255) CHECK ("mood" IN(\'happy\', \'sad\')), PRIMARY KEY ("id"));', + postgres: + 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));', + 'mariadb mysql': + "CREATE TABLE IF NOT EXISTS `foo`.`users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;", + mssql: `IF OBJECT_ID(N'[foo].[users]', 'U') IS NULL CREATE TABLE [foo].[users] ([id] INTEGER NOT NULL IDENTITY(1,1) , [mood] NVARCHAR(255) CHECK ([mood] IN(N'happy', N'sad')), PRIMARY KEY ([id]));`, + ibmi: `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' + BEGIN END; + CREATE TABLE "foo"."users" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) , "mood" VARCHAR(255) CHECK ("mood" IN('happy', 'sad')), PRIMARY KEY ("id")); + END`, + }, + ); + }); + }); + + describe('with references', () => { + it('references right schema when adding foreign key #9029', () => { + const BarUser = current.define('user', {}, { timestamps: false }).withSchema('bar'); + + const BarProject = current + .define( + 'project', + { + user_id: { + type: DataTypes.INTEGER, + references: { model: BarUser }, + onUpdate: 'CASCADE', + onDelete: 'NO ACTION', + }, + }, + { + timestamps: false, + }, + ) + .withSchema('bar'); + + BarProject.belongsTo(BarUser, { foreignKey: 'user_id' }); + + expectsql( + sql.createTableQuery( + BarProject.table, + sql.attributesToSQL(BarProject.getAttributes()), + {}, + ), + { + sqlite3: + 'CREATE TABLE IF NOT EXISTS `bar.projects` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user_id` INTEGER REFERENCES `bar.users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE);', + db2: 'CREATE TABLE IF NOT EXISTS "bar"."projects" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) , "user_id" INTEGER, PRIMARY KEY ("id"), FOREIGN KEY ("user_id") REFERENCES "bar"."users" ("id") ON DELETE NO ACTION);', + postgres: + 'CREATE TABLE IF NOT EXISTS "bar"."projects" ("id" SERIAL , "user_id" INTEGER REFERENCES "bar"."users" ("id") ON DELETE NO ACTION ON UPDATE CASCADE, PRIMARY KEY ("id"));', + 'mariadb mysql': + 'CREATE TABLE IF NOT EXISTS `bar`.`projects` (`id` INTEGER NOT NULL auto_increment , `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `bar`.`users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[bar].[projects]', 'U') IS NULL CREATE TABLE [bar].[projects] ([id] INTEGER NOT NULL IDENTITY(1,1) , [user_id] INTEGER NULL, PRIMARY KEY ([id]), FOREIGN KEY ([user_id]) REFERENCES [bar].[users] ([id]) ON DELETE NO ACTION);`, + ibmi: `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' + BEGIN END; + CREATE TABLE "bar"."projects" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) , "user_id" INTEGER REFERENCES "bar"."users" ("id") ON DELETE NO ACTION, PRIMARY KEY ("id")); + END`, + }, + ); + }); + }); + + describe('with references on primary key', () => { + it('references on primary key #9461', () => { + const File = current.define('file', {}, { timestamps: false }); + const Image = current.define( + 'image', + { + id: { + primaryKey: true, + autoIncrement: true, + type: DataTypes.INTEGER, + references: { + model: File, + key: 'id', + }, + }, + }, + { + timestamps: false, + }, + ); + + expectsql( + sql.createTableQuery(Image.table, sql.attributesToSQL(Image.getAttributes()), {}), + { + sqlite3: + 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER PRIMARY KEY AUTOINCREMENT REFERENCES `files` (`id`));', + postgres: + 'CREATE TABLE IF NOT EXISTS "images" ("id" SERIAL REFERENCES "files" ("id"), PRIMARY KEY ("id"));', + db2: 'CREATE TABLE IF NOT EXISTS "images" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) , PRIMARY KEY ("id"), FOREIGN KEY ("id") REFERENCES "files" ("id"));', + mariadb: + 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;', + mysql: + 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;', + mssql: `IF OBJECT_ID(N'[images]', 'U') IS NULL CREATE TABLE [images] ([id] INTEGER IDENTITY(1,1) , PRIMARY KEY ([id]), FOREIGN KEY ([id]) REFERENCES [files] ([id]));`, + ibmi: `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' + BEGIN END; + CREATE TABLE "images" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) REFERENCES "files" ("id"), PRIMARY KEY ("id")); + END`, + }, + ); + }); + }); + }); +}); diff --git a/packages/core/test/unit/sql/generateJoin.test.js b/packages/core/test/unit/sql/generateJoin.test.js new file mode 100644 index 000000000000..a0b2fb6c58ba --- /dev/null +++ b/packages/core/test/unit/sql/generateJoin.test.js @@ -0,0 +1,439 @@ +'use strict'; + +const at = require('lodash/at'); + +const { beforeAll2, expectsql, sequelize } = require('../../support'); +const { DataTypes, Model, Op } = require('@sequelize/core'); +const { + _validateIncludedElements, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'); + +const sql = sequelize.queryGenerator; + +describe('QueryGenerator#generateJoin', () => { + const expectJoin = function (path, options, expectation) { + Model._conformIncludes(options, options.model); + options = _validateIncludedElements(options); + + const include = at(options, path)[0]; + + const join = sql.generateJoin(include, { + options, + subQuery: + options.subQuery === undefined + ? options.limit && options.hasMultiAssociation + : options.subQuery, + }); + + return expectsql(`${join.join} ${join.body} ON ${join.condition}`, expectation); + }; + + const vars = beforeAll2(() => { + const User = sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id_user', + }, + companyId: { + type: DataTypes.INTEGER, + field: 'company_id', + }, + }, + { + tableName: 'user', + }, + ); + + const Task = sequelize.define( + 'Task', + { + title: DataTypes.STRING, + userId: { + type: DataTypes.INTEGER, + field: 'user_id', + }, + }, + { + tableName: 'task', + }, + ); + + const Company = sequelize.define( + 'Company', + { + name: DataTypes.STRING, + ownerId: { + type: DataTypes.INTEGER, + field: 'owner_id', + }, + public: { + type: DataTypes.BOOLEAN, + }, + }, + { + tableName: 'company', + }, + ); + + const Profession = sequelize.define( + 'Profession', + { + name: DataTypes.STRING, + }, + { + tableName: 'profession', + }, + ); + + User.Tasks = User.hasMany(Task, { as: 'Tasks', foreignKey: 'userId', inverse: 'User' }); + User.Company = User.belongsTo(Company, { as: 'Company', foreignKey: 'companyId' }); + User.Profession = User.belongsTo(Profession, { as: 'Profession', foreignKey: 'professionId' }); + Profession.Professionals = Profession.hasMany(User, { + as: 'Professionals', + foreignKey: 'professionId', + inverse: 'Profession', + }); + Company.Employees = Company.hasMany(User, { + as: 'Employees', + foreignKey: 'companyId', + inverse: 'Company', + }); + Company.Owner = Company.belongsTo(User, { as: 'Owner', foreignKey: 'ownerId' }); + + return { User, Task, Company, Profession }; + }); + + /* + * BelongsTo + */ + + it('Generates a join query for a belongsTo association', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + include: [User.Company], + }, + { + default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id]', + }, + ); + }); + + it('Generates a belongsTo join query with an extra OR "on" condition', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + include: [ + { + association: User.Company, + where: { public: true }, + or: true, + }, + ], + }, + { + default: + 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = true', + ibmi: 'INNER JOIN "company" AS "Company" ON "User"."company_id" = "Company"."id" OR "Company"."public" = 1', + sqlite3: + 'INNER JOIN `company` AS `Company` ON `User`.`company_id` = `Company`.`id` OR `Company`.`public` = 1', + mssql: + 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = 1', + }, + ); + }); + + it('Generates a nested belongsTo join query', () => { + const { Profession, User } = vars; + + expectJoin( + 'include[0].include[0]', + { + model: Profession, + include: [ + { + association: Profession.Professionals, + limit: 3, + include: [User.Company], + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [company] AS [Professionals->Company] ON [Professionals].[company_id] = [Professionals->Company].[id]', + }, + ); + }); + + it('supports subQuery = true', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + subQuery: true, + include: [User.Company], + }, + { + default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]', + }, + ); + }); + + it('supports subQuery = true with required = false and nested WHERE', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + subQuery: true, + include: [ + { + association: User.Company, + required: false, + where: { name: 'ABC' }, + }, + ], + }, + { + default: + "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = 'ABC'", + mssql: + "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = N'ABC'", + }, + ); + }); + + it('supports "right = true"', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + subQuery: true, + include: [ + { + association: User.Company, + right: true, + }, + ], + }, + { + default: `${sequelize.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]`, + }, + ); + }); + + it('supports nested includes with subQuery = true', () => { + const { Company, User } = vars; + + expectJoin( + 'include[0].include[0]', + { + subQuery: true, + model: User, + include: [ + { + association: User.Company, + include: [Company.Owner], + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]', + }, + ); + }); + + it('supports double nested includes', () => { + const { Company, User } = vars; + + expectJoin( + 'include[0].include[0].include[0]', + { + model: User, + subQuery: true, + include: [ + { + association: User.Company, + include: [ + { + association: Company.Owner, + include: [User.Profession], + }, + ], + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [profession] AS [Company->Owner->Profession] ON [Company->Owner].[professionId] = [Company->Owner->Profession].[id]', + }, + ); + }); + + it('supports nested includes with required = true', () => { + const { Company, User } = vars; + + expectJoin( + 'include[0].include[0]', + { + model: User, + subQuery: true, + include: [ + { + association: User.Company, + required: true, + include: [Company.Owner], + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]', + }, + ); + }); + + it('supports required = true', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + subQuery: true, + include: [{ association: User.Company, required: true }], + }, + { + default: 'INNER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]', + }, + ); + }); + + // /* + // * HasMany + // */ + + it('supports hasMany', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + include: [User.Tasks], + }, + { default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id_user] = [Tasks].[user_id]' }, + ); + }); + + it('supports hasMany with subQuery = true', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + subQuery: true, + include: [User.Tasks], + }, + { + // The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of + default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]', + }, + ); + }); + + it('supports hasMany with "on" condition', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + include: [ + { + association: User.Tasks, + on: { + [Op.or]: [ + { '$User.id_user$': { [Op.col]: 'Tasks.user_id' } }, + { '$Tasks.user_id$': 2 }, + ], + }, + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id_user] = [Tasks].[user_id] OR [Tasks].[user_id] = 2', + }, + ); + }); + + it('supports hasMany with "on" condition (2)', () => { + const { User } = vars; + + expectJoin( + 'include[0]', + { + model: User, + include: [ + { + association: User.Tasks, + on: { user_id: { [Op.col]: 'User.alternative_id' } }, + }, + ], + }, + { + default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [Tasks].[user_id] = [User].[alternative_id]', + }, + ); + }); + + it('supports nested hasMany', () => { + const { Company, User } = vars; + + expectJoin( + 'include[0].include[0]', + { + subQuery: true, + model: User, + include: [ + { + association: User.Company, + include: [ + { + association: Company.Owner, + on: { + [Op.or]: [ + { '$Company.owner_id$': { [Op.col]: 'Company.Owner.id_user' } }, + { '$Company.Owner.id_user$': 2 }, + ], + }, + }, + ], + }, + ], + }, + { + default: + 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user] OR [Company->Owner].[id_user] = 2', + }, + ); + }); +}); diff --git a/packages/core/test/unit/sql/group.test.js b/packages/core/test/unit/sql/group.test.js new file mode 100644 index 000000000000..f327048c072f --- /dev/null +++ b/packages/core/test/unit/sql/group.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const { DataTypes } = require('@sequelize/core'); +const { beforeAll2, expectsql, sequelize } = require('../../support'); + +const sql = sequelize.dialect.queryGenerator; + +describe('QueryGenerator#selectQuery with "group"', () => { + function expectSelect(options, expectation) { + const model = options.model; + + return expectsql( + sql.selectQuery(options.table || (model && model.table), options, options.model), + expectation, + ); + } + + const vars = beforeAll2(() => { + const User = sequelize.define('User', { + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false, + }, + }); + + return { User }; + }); + + it('supports simple GROUP BY', () => { + const { User } = vars; + + expectSelect( + { + model: User, + group: ['name'], + }, + { + default: 'SELECT * FROM `Users` AS `User` GROUP BY `name`;', + postgres: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + db2: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + ibmi: 'SELECT * FROM "Users" AS "User" GROUP BY "name"', + mssql: 'SELECT * FROM [Users] AS [User] GROUP BY [name];', + snowflake: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + }, + ); + }); + + it('does not add GROUP BY if it is empty', () => { + const { User } = vars; + + expectSelect( + { + model: User, + group: [], + }, + { + default: 'SELECT * FROM `Users` AS `User`;', + postgres: 'SELECT * FROM "Users" AS "User";', + db2: 'SELECT * FROM "Users" AS "User";', + ibmi: 'SELECT * FROM "Users" AS "User"', + mssql: 'SELECT * FROM [Users] AS [User];', + snowflake: 'SELECT * FROM "Users" AS "User";', + }, + ); + }); +}); diff --git a/packages/core/test/unit/sql/index.test.js b/packages/core/test/unit/sql/index.test.js new file mode 100644 index 000000000000..c646239e6a2b --- /dev/null +++ b/packages/core/test/unit/sql/index.test.js @@ -0,0 +1,418 @@ +'use strict'; + +const Support = require('../../support'); +const { literal, Op } = require('@sequelize/core'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +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 + +const TICK_LEFT = Support.sequelize.dialect.TICK_CHAR_LEFT; +const TICK_RIGHT = Support.sequelize.dialect.TICK_CHAR_RIGHT; + +describe(Support.getTestDialectTeaser('SQL'), () => { + if (current.dialect.name === 'snowflake') { + return; + } + + describe('addIndex', () => { + it('naming', () => { + expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), { + default: 'CREATE INDEX [table_column1_column2] ON [table] ([column1], [column2])', + 'mariadb mysql': + 'ALTER TABLE `table` ADD INDEX `table_column1_column2` (`column1`, `column2`)', + }); + + if (current.dialect.supports.schemas) { + expectsql(sql.addIndexQuery('schema.table', ['column1', 'column2'], {}), { + default: + 'CREATE INDEX [schema_table_column1_column2] ON [schema.table] ([column1], [column2])', + 'mariadb mysql': + 'ALTER TABLE `schema.table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)', + }); + + expectsql( + sql.addIndexQuery( + { + schema: 'schema', + tableName: 'table', + }, + ['column1', 'column2'], + {}, + 'schema_table', + ), + { + default: + 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])', + db2: 'CREATE INDEX "schema"."schema_table_column1_column2" ON "schema"."table" ("column1", "column2")', + 'mariadb mysql': + 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)', + }, + ); + + expectsql( + sql.addIndexQuery( + // quoteTable will produce '"schema"."table"' + // that is a perfectly valid table name, so passing it to quoteTable again (through addIndexQuery) must produce this: + // '"""schema"".""table"""' + // the double-quotes are duplicated because they are escaped + sql.quoteTable({ + schema: 'schema', + tableName: 'table', + }), + ['column1', 'column2'], + {}, + ), + { + // using TICK variables directly because it's impossible for expectsql to know whether the TICK inside ticks is meant to be a tick or just part of the string + default: `CREATE INDEX ${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}schema${TICK_RIGHT}${TICK_RIGHT}_${TICK_LEFT}${TICK_LEFT}table${TICK_RIGHT}${TICK_RIGHT}_column1_column2${TICK_RIGHT} ON ${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}schema${TICK_RIGHT}${TICK_RIGHT}.${TICK_LEFT}${TICK_LEFT}table${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ([column1], [column2])`, + 'mariadb mysql': + 'ALTER TABLE ```schema``.``table``` ADD INDEX ```schema``_``table``_column1_column2` (`column1`, `column2`)', + }, + ); + } + }); + + it('type and using', () => { + expectsql( + sql.addIndexQuery('User', ['fieldC'], { + type: 'FULLTEXT', + concurrently: true, + }), + { + ibmi: 'CREATE INDEX "user_field_c" ON "User" ("fieldC")', + sqlite3: 'CREATE INDEX `user_field_c` ON `User` (`fieldC`)', + db2: 'CREATE INDEX "user_field_c" ON "User" ("fieldC")', + mssql: 'CREATE FULLTEXT INDEX [user_field_c] ON [User] ([fieldC])', + postgres: 'CREATE INDEX CONCURRENTLY "user_field_c" ON "User" ("fieldC")', + mariadb: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)', + mysql: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)', + }, + ); + + expectsql( + sql.addIndexQuery( + 'User', + ['fieldB', { attribute: 'fieldA', collate: 'en_US', order: 'DESC', length: 5 }], + { + name: 'a_b_uniq', + unique: true, + using: 'BTREE', + parser: 'foo', + }, + ), + { + sqlite3: + 'CREATE UNIQUE INDEX `a_b_uniq` ON `User` (`fieldB`, `fieldA` COLLATE `en_US` DESC)', + mssql: 'CREATE UNIQUE INDEX [a_b_uniq] ON [User] ([fieldB], [fieldA] DESC)', + db2: 'CREATE UNIQUE INDEX "a_b_uniq" ON "User" ("fieldB", "fieldA" DESC)', + ibmi: `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42891' + BEGIN END; + ALTER TABLE "User" ADD CONSTRAINT "a_b_uniq" UNIQUE ("fieldB", "fieldA" DESC); + END`, + postgres: + 'CREATE UNIQUE INDEX "a_b_uniq" ON "User" USING BTREE ("fieldB", "fieldA" COLLATE "en_US" DESC)', + mariadb: + 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo', + mysql: + 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo', + }, + ); + }); + + it('POJO field', () => { + expectsql( + sql.addIndexQuery( + 'table', + [{ name: 'column', collate: 'BINARY', length: 5, order: 'DESC' }], + {}, + 'table', + ), + { + default: 'CREATE INDEX [table_column] ON [table] ([column] COLLATE [BINARY] DESC)', + mssql: 'CREATE INDEX [table_column] ON [table] ([column] DESC)', + db2: 'CREATE INDEX "table_column" ON "table" ("column" DESC)', + ibmi: 'CREATE INDEX "table_column" ON "table" ("column" DESC)', + mariadb: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)', + mysql: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)', + }, + ); + }); + + it('function', () => { + expectsql( + sql.addIndexQuery('table', [current.fn('UPPER', current.col('test'))], { name: 'myindex' }), + { + default: 'CREATE INDEX [myindex] ON [table] (UPPER([test]))', + mariadb: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))', + mysql: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))', + }, + ); + }); + + if (current.dialect.supports.index.using === 2) { + it('USING', () => { + expectsql( + sql.addIndexQuery('table', { + fields: ['event'], + using: 'gin', + }), + { + postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event")', + }, + ); + }); + } + + if (current.dialect.supports.index.where) { + it('WHERE', () => { + expectsql( + sql.addIndexQuery('table', { + fields: ['type'], + where: { + type: 'public', + }, + }), + { + ibmi: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'public\'', + sqlite3: "CREATE INDEX `table_type` ON `table` (`type`) WHERE `type` = 'public'", + db2: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'public\'', + postgres: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'public\'', + mssql: "CREATE INDEX [table_type] ON [table] ([type]) WHERE [type] = N'public'", + }, + ); + + expectsql( + sql.addIndexQuery('table', { + fields: ['type'], + where: { + type: { + [Op.or]: ['group', 'private'], + }, + }, + }), + { + ibmi: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'group\' OR "type" = \'private\'', + sqlite3: + "CREATE INDEX `table_type` ON `table` (`type`) WHERE `type` = 'group' OR `type` = 'private'", + db2: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'group\' OR "type" = \'private\'', + postgres: + 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'group\' OR "type" = \'private\'', + mssql: + "CREATE INDEX [table_type] ON [table] ([type]) WHERE [type] = N'group' OR [type] = N'private'", + }, + ); + + expectsql( + sql.addIndexQuery('table', { + fields: ['type'], + where: { + type: { + [Op.ne]: null, + }, + }, + }), + { + ibmi: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" IS NOT NULL', + sqlite3: 'CREATE INDEX `table_type` ON `table` (`type`) WHERE `type` IS NOT NULL', + db2: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" IS NOT NULL', + postgres: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" IS NOT NULL', + mssql: 'CREATE INDEX [table_type] ON [table] ([type]) WHERE [type] IS NOT NULL', + }, + ); + }); + } + + if (current.dialect.supports.dataTypes.JSONB) { + it('operator', () => { + expectsql( + sql.addIndexQuery('table', { + fields: ['event'], + using: 'gin', + operator: 'jsonb_path_ops', + }), + { + postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event" jsonb_path_ops)', + }, + ); + }); + } + + 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)', + }, + ); + }); + } + + it('include columns with unique index', () => { + expectsql( + () => + sql.addIndexQuery('User', { + name: 'email_include_name', + fields: ['email'], + include: ['first_name', 'last_name'], + unique: true, + }), + { + default: new Error( + `The include attribute for indexes is not supported by ${current.dialect.name} dialect`, + ), + mssql: + 'CREATE UNIQUE INDEX [email_include_name] ON [User] ([email]) INCLUDE ([first_name], [last_name])', + 'db2 postgres': + 'CREATE UNIQUE INDEX "email_include_name" ON "User" ("email") INCLUDE ("first_name", "last_name")', + }, + ); + }); + + it('include columns with non-unique index', () => { + expectsql( + () => + sql.addIndexQuery('User', { + name: 'email_include_name', + fields: ['email'], + include: ['first_name', 'last_name'], + }), + { + db2: new Error('DB2 does not support non-unique indexes with INCLUDE syntax.'), + default: new Error( + `The include attribute for indexes is not supported by ${current.dialect.name} dialect`, + ), + mssql: + 'CREATE INDEX [email_include_name] ON [User] ([email]) INCLUDE ([first_name], [last_name])', + postgres: + 'CREATE INDEX "email_include_name" ON "User" ("email") INCLUDE ("first_name", "last_name")', + }, + ); + }); + + it('include columns using a liternal with non-unique index', () => { + expectsql( + () => + sql.addIndexQuery('User', { + name: 'email_include_name', + fields: ['email'], + include: literal('(first_name, last_name)'), + }), + { + db2: new Error('DB2 does not support non-unique indexes with INCLUDE syntax.'), + default: new Error( + `The include attribute for indexes is not supported by ${current.dialect.name} dialect`, + ), + mssql: + 'CREATE INDEX [email_include_name] ON [User] ([email]) INCLUDE (first_name, last_name)', + postgres: + 'CREATE INDEX "email_include_name" ON "User" ("email") INCLUDE (first_name, last_name)', + }, + ); + }); + + it('include columns using an array of liternals with non-unique index', () => { + expectsql( + () => + sql.addIndexQuery('User', { + name: 'email_include_name', + fields: ['email'], + include: [literal('first_name'), literal('last_name')], + }), + { + db2: new Error('DB2 does not support non-unique indexes with INCLUDE syntax.'), + default: new Error( + `The include attribute for indexes is not supported by ${current.dialect.name} dialect`, + ), + mssql: + 'CREATE INDEX [email_include_name] ON [User] ([email]) INCLUDE (first_name, last_name)', + postgres: + 'CREATE INDEX "email_include_name" ON "User" ("email") INCLUDE (first_name, last_name)', + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/sql/insert.test.js b/packages/core/test/unit/sql/insert.test.js new file mode 100644 index 000000000000..dc0ae9b3d045 --- /dev/null +++ b/packages/core/test/unit/sql/insert.test.js @@ -0,0 +1,451 @@ +'use strict'; + +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const { expect } = require('chai'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +const sql = current.dialect.queryGenerator; +const dialect = current.dialect; + +// 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('insert', () => { + it('with temp table for trigger', () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + }, + }, + { + timestamps: false, + hasTrigger: true, + }, + ); + + const options = { + returning: true, + hasTrigger: true, + }; + expectsql( + sql.insertQuery(User.table, { user_name: 'triggertest' }, User.getAttributes(), options), + { + query: { + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("user_name") VALUES ($sequelize_1))', + mssql: + 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id], INSERTED.[user_name] INTO @tmp VALUES ($sequelize_1); SELECT * FROM @tmp;', + sqlite3: + 'INSERT INTO `users` (`user_name`) VALUES ($sequelize_1) RETURNING `id`, `user_name`;', + postgres: + 'INSERT INTO "users" ("user_name") VALUES ($sequelize_1) RETURNING "id", "user_name";', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("user_name") VALUES ($sequelize_1));', + snowflake: 'INSERT INTO "users" ("user_name") VALUES ($sequelize_1);', + default: 'INSERT INTO `users` (`user_name`) VALUES ($sequelize_1);', + }, + bind: { sequelize_1: 'triggertest' }, + }, + ); + }); + + it('allow insert primary key with 0', () => { + const M = Support.sequelize.define('m', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + }); + + expectsql(sql.insertQuery(M.table, { id: 0 }, M.getAttributes()), { + query: { + mssql: + 'SET IDENTITY_INSERT [ms] ON; INSERT INTO [ms] ([id]) VALUES ($sequelize_1); SET IDENTITY_INSERT [ms] OFF;', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "ms" ("id") VALUES ($sequelize_1));', + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "ms" ("id") VALUES ($sequelize_1))', + postgres: 'INSERT INTO "ms" ("id") VALUES ($sequelize_1);', + snowflake: 'INSERT INTO "ms" ("id") VALUES ($sequelize_1);', + default: 'INSERT INTO `ms` (`id`) VALUES ($sequelize_1);', + }, + bind: { sequelize_1: 0 }, + }); + }); + + it( + current.dialect.supports.inserts.onConflictWhere + ? 'adds conflictWhere clause to generated queries' + : 'throws error if conflictWhere is provided', + () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + primaryKey: true, + }, + password: { + type: DataTypes.STRING, + field: 'pass_word', + }, + createdAt: { + type: DataTypes.DATE, + field: 'created_at', + }, + updatedAt: { + type: DataTypes.DATE, + field: 'updated_at', + }, + }, + { + timestamps: true, + }, + ); + + const upsertKeys = ['user_name']; + + let result; + + try { + result = sql.insertQuery( + User.table, + { user_name: 'testuser', pass_word: '12345' }, + User.fieldRawAttributesMap, + { + updateOnDuplicate: ['user_name', 'pass_word', 'updated_at'], + conflictWhere: { + user_name: 'test where value', + }, + upsertKeys, + }, + ); + } catch (error) { + result = error; + } + + expectsql(result, { + default: new Error('missing dialect support for conflictWhere option'), + 'postgres sqlite3': `INSERT INTO [users] ([user_name],[pass_word]) VALUES ($sequelize_1,$sequelize_2) ON CONFLICT ([user_name]) WHERE [user_name] = 'test where value' DO UPDATE SET [user_name]=EXCLUDED.[user_name],[pass_word]=EXCLUDED.[pass_word],[updated_at]=EXCLUDED.[updated_at];`, + }); + }, + ); + }); + + describe('dates', () => { + if (!dialect.supports.globalTimeZoneConfig) { + it('rejects specifying the global timezone option', () => { + expect(() => Support.createSequelizeInstance({ timezone: 'CET' })).to.throw( + 'Setting a custom timezone is not supported', + ); + }); + } else { + it('supports the global timezone option', () => { + const timezoneSequelize = Support.createSequelizeInstance({ + timezone: 'CET', + }); + + const User = timezoneSequelize.define( + 'user', + { + date: { + type: DataTypes.DATE(3), + }, + }, + { + timestamps: false, + }, + ); + + expectsql( + timezoneSequelize.dialect.queryGenerator.insertQuery( + User.table, + { date: new Date(Date.UTC(2015, 0, 20)) }, + User.getAttributes(), + {}, + ), + { + query: { + default: 'INSERT INTO [users] ([date]) VALUES ($sequelize_1);', + }, + bind: { + // these dialects change the DB-side timezone, and the input doesn't specify the timezone offset, so we have to offset the value ourselves + // because it will be interpreted as CET by the dialect. + snowflake: { sequelize_1: '2015-01-20 01:00:00.000' }, + mysql: { sequelize_1: '2015-01-20 01:00:00.000' }, + mariadb: { sequelize_1: '2015-01-20 01:00:00.000' }, + // These dialects do specify the offset, so they can use whichever offset they want. + postgres: { sequelize_1: '2015-01-20 01:00:00.000 +01:00' }, + }, + }, + ); + }); + } + + it('formats the date correctly when inserting', () => { + const User = current.define( + 'user', + { + date: { + type: DataTypes.DATE(3), + }, + }, + { + timestamps: false, + }, + ); + + expectsql( + current.dialect.queryGenerator.insertQuery( + User.table, + { date: new Date(Date.UTC(2015, 0, 20)) }, + User.getAttributes(), + {}, + ), + { + query: { + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("date") VALUES ($sequelize_1))', + postgres: 'INSERT INTO "users" ("date") VALUES ($sequelize_1);', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("date") VALUES ($sequelize_1));', + snowflake: 'INSERT INTO "users" ("date") VALUES ($sequelize_1);', + mssql: 'INSERT INTO [users] ([date]) VALUES ($sequelize_1);', + default: 'INSERT INTO `users` (`date`) VALUES ($sequelize_1);', + }, + bind: { + ibmi: { sequelize_1: '2015-01-20 00:00:00.000' }, + db2: { sequelize_1: '2015-01-20 00:00:00.000' }, + snowflake: { sequelize_1: '2015-01-20 00:00:00.000' }, + mysql: { sequelize_1: '2015-01-20 00:00:00.000' }, + mariadb: { sequelize_1: '2015-01-20 00:00:00.000' }, + sqlite3: { sequelize_1: '2015-01-20 00:00:00.000 +00:00' }, + mssql: { sequelize_1: '2015-01-20 00:00:00.000 +00:00' }, + postgres: { sequelize_1: '2015-01-20 00:00:00.000 +00:00' }, + }, + }, + ); + }); + + it('formats date correctly when sub-second precision is explicitly specified', () => { + const User = current.define( + 'user', + { + date: { + type: DataTypes.DATE(3), + }, + }, + { + timestamps: false, + }, + ); + + expectsql( + current.dialect.queryGenerator.insertQuery( + User.table, + { date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89)) }, + User.getAttributes(), + {}, + ), + { + query: { + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("date") VALUES ($sequelize_1))', + postgres: 'INSERT INTO "users" ("date") VALUES ($sequelize_1);', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("date") VALUES ($sequelize_1));', + snowflake: 'INSERT INTO "users" ("date") VALUES ($sequelize_1);', + mssql: 'INSERT INTO [users] ([date]) VALUES ($sequelize_1);', + default: 'INSERT INTO `users` (`date`) VALUES ($sequelize_1);', + }, + bind: { + ibmi: { sequelize_1: '2015-01-20 01:02:03.089' }, + db2: { sequelize_1: '2015-01-20 01:02:03.089' }, + snowflake: { sequelize_1: '2015-01-20 01:02:03.089' }, + mariadb: { sequelize_1: '2015-01-20 01:02:03.089' }, + mysql: { sequelize_1: '2015-01-20 01:02:03.089' }, + sqlite3: { sequelize_1: '2015-01-20 01:02:03.089 +00:00' }, + postgres: { sequelize_1: '2015-01-20 01:02:03.089 +00:00' }, + mssql: { sequelize_1: '2015-01-20 01:02:03.089 +00:00' }, + }, + }, + ); + }); + }); + + describe('strings', () => { + it('formats null characters correctly when inserting', () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + }, + }, + { + timestamps: false, + }, + ); + + expectsql(sql.insertQuery(User.table, { user_name: 'null\0test' }, User.getAttributes()), { + query: { + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("user_name") VALUES ($sequelize_1))', + postgres: 'INSERT INTO "users" ("user_name") VALUES ($sequelize_1);', + db2: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("user_name") VALUES ($sequelize_1));', + snowflake: 'INSERT INTO "users" ("user_name") VALUES ($sequelize_1);', + mssql: 'INSERT INTO [users] ([user_name]) VALUES ($sequelize_1);', + default: 'INSERT INTO `users` (`user_name`) VALUES ($sequelize_1);', + }, + bind: { + postgres: { sequelize_1: 'null\u0000test' }, + default: { sequelize_1: 'null\0test' }, + }, + }); + }); + }); + + describe('bulkCreate', () => { + it('bulk create with onDuplicateKeyUpdate', () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + primaryKey: true, + }, + password: { + type: DataTypes.STRING, + field: 'pass_word', + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + timestamps: true, + }, + ); + + // mapping primary keys to their "field" override values + const primaryKeys = User.primaryKeyAttributes.map( + attr => User.getAttributes()[attr].field || attr, + ); + + expectsql( + sql.bulkInsertQuery( + User.table, + [{ user_name: 'testuser', pass_word: '12345' }], + { updateOnDuplicate: ['user_name', 'pass_word', 'updated_at'], upsertKeys: primaryKeys }, + User.fieldRawAttributesMap, + ), + { + default: "INSERT INTO `users` (`user_name`,`pass_word`) VALUES ('testuser','12345');", + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "users" ("user_name","pass_word") VALUES (\'testuser\',\'12345\'))', + snowflake: + 'INSERT INTO "users" ("user_name","pass_word") VALUES (\'testuser\',\'12345\');', + postgres: + '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";', + mssql: "INSERT INTO [users] ([user_name],[pass_word]) VALUES (N'testuser',N'12345');", + db2: 'INSERT INTO "users" ("user_name","pass_word") VALUES (\'testuser\',\'12345\');', + mariadb: + "INSERT INTO `users` (`user_name`,`pass_word`) VALUES ('testuser','12345') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);", + mysql: + "INSERT INTO `users` (`user_name`,`pass_word`) VALUES ('testuser','12345') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);", + sqlite3: + "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.table, [{ 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);', + db2: 'INSERT INTO "ms" VALUES (1);INSERT INTO "ms" ("id") VALUES (0),(NULL);', + ibmi: 'SELECT * FROM FINAL TABLE (INSERT INTO "ms" ("id") VALUES (0),(DEFAULT))', + snowflake: 'INSERT INTO "ms" ("id") VALUES (0),(NULL);', + default: 'INSERT INTO `ms` (`id`) VALUES (0),(NULL);', + }, + }, + ); + }); + + if (current.dialect.supports.inserts.updateOnDuplicate) { + it('correctly generates SQL for conflictWhere', () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + primaryKey: true, + }, + password: { + type: DataTypes.STRING, + field: 'pass_word', + }, + createdAt: { + type: DataTypes.DATE, + field: 'created_at', + }, + updatedAt: { + type: DataTypes.DATE, + field: 'updated_at', + }, + deletedAt: { + type: DataTypes.DATE, + field: 'deleted_at', + }, + }, + { + timestamps: true, + }, + ); + + // mapping primary keys to their "field" override values + const primaryKeys = User.primaryKeyAttributes.map( + attr => User.getAttributes()[attr].field || attr, + ); + + let result; + + try { + result = sql.bulkInsertQuery( + User.table, + [{ user_name: 'testuser', pass_word: '12345' }], + { + updateOnDuplicate: ['user_name', 'pass_word', 'updated_at'], + upsertKeys: primaryKeys, + conflictWhere: { deleted_at: null }, + }, + User.fieldRawAttributesMap, + ); + } catch (error) { + result = error; + } + + expectsql(result, { + default: new Error(`conflictWhere not supported for dialect ${dialect.name}`), + 'postgres sqlite3': + "INSERT INTO [users] ([user_name],[pass_word]) VALUES ('testuser','12345') ON CONFLICT ([user_name]) WHERE [deleted_at] IS NULL DO UPDATE SET [user_name]=EXCLUDED.[user_name],[pass_word]=EXCLUDED.[pass_word],[updated_at]=EXCLUDED.[updated_at];", + }); + }); + } + }); +}); diff --git a/packages/core/test/unit/sql/literal.test.ts b/packages/core/test/unit/sql/literal.test.ts new file mode 100644 index 000000000000..907a2a945c1b --- /dev/null +++ b/packages/core/test/unit/sql/literal.test.ts @@ -0,0 +1,276 @@ +import { Op, cast, fn, json, sql, where } from '@sequelize/core'; +import { expectsql, sequelize } from '../../support'; + +const dialect = sequelize.dialect; +const queryGenerator = sequelize.queryGenerator; + +describe('json', () => { + if (!dialect.supports.jsonOperations || !dialect.supports.jsonExtraction.quoted) { + return; + } + + it('supports WhereOptions', () => { + const conditions = { + metadata: { + language: 'icelandic', + pg_rating: { dk: 'G' }, + }, + another_json_field: { x: 1 }, + }; + + expectsql(() => queryGenerator.escape(json(conditions)), { + postgres: `("metadata"->'language' = '"icelandic"' AND "metadata"#>ARRAY['pg_rating','dk']::VARCHAR(255)[] = '"G"') AND "another_json_field"->'x' = '1'`, + sqlite3: `(json_extract(\`metadata\`,'$.language') = '"icelandic"' AND json_extract(\`metadata\`,'$.pg_rating.dk') = '"G"') AND json_extract(\`another_json_field\`,'$.x') = '1'`, + mariadb: `(json_compact(json_extract(\`metadata\`,'$.language')) = '"icelandic"' AND json_compact(json_extract(\`metadata\`,'$.pg_rating.dk')) = '"G"') AND json_compact(json_extract(\`another_json_field\`,'$.x')) = '1'`, + mysql: `(json_extract(\`metadata\`,'$.language') = CAST('"icelandic"' AS JSON) AND json_extract(\`metadata\`,'$.pg_rating.dk') = CAST('"G"' AS JSON)) AND json_extract(\`another_json_field\`,'$.x') = CAST('1' AS JSON)`, + }); + }); + + it('supports the json path notation', () => { + const path = 'metadata.pg_rating.dk'; + + expectsql(() => queryGenerator.escape(json(path)), { + postgres: `"metadata"#>ARRAY['pg_rating','dk']::VARCHAR(255)[]`, + mariadb: `json_compact(json_extract(\`metadata\`,'$.pg_rating.dk'))`, + 'sqlite3 mysql': `json_extract(\`metadata\`,'$.pg_rating.dk')`, + }); + }); + + it('supports numbers in the dot notation', () => { + expectsql(() => queryGenerator.escape(json('profile.id.0.1')), { + postgres: `"profile"#>ARRAY['id','0','1']::VARCHAR(255)[]`, + mariadb: `json_compact(json_extract(\`profile\`,'$.id."0"."1"'))`, + 'sqlite3 mysql': `json_extract(\`profile\`,'$.id."0"."1"')`, + }); + }); + + it('can take a value to compare against', () => { + const path = 'metadata.pg_rating.is'; + const value = 'U'; + + expectsql(() => queryGenerator.escape(json(path, value)), { + postgres: `"metadata"#>ARRAY['pg_rating','is']::VARCHAR(255)[] = '"U"'`, + sqlite3: `json_extract(\`metadata\`,'$.pg_rating.is') = '"U"'`, + mariadb: `json_compact(json_extract(\`metadata\`,'$.pg_rating.is')) = '"U"'`, + mysql: `json_extract(\`metadata\`,'$.pg_rating.is') = CAST('"U"' AS JSON)`, + }); + }); + + // TODO: add a way to let `where` know what the type of the value is in raw queries + // it('accepts a condition object', () => { + // expectsql(queryGenerator.escape(json({ id: 1 })), { + // postgres: `"id" = '1'`, + // }); + // }); + // + // it('column named "json"', () => { + // expectsql(queryGenerator.escape(where(json('json'), Op.eq, {})), { + // postgres: `("json"#>>'{}') = '{}'`, + // }); + // }); + + it('accepts a nested condition object', () => { + expectsql(() => queryGenerator.escape(json({ profile: { id: 1 } })), { + postgres: `"profile"->'id' = '1'`, + sqlite3: `json_extract(\`profile\`,'$.id') = '1'`, + mariadb: `json_compact(json_extract(\`profile\`,'$.id')) = '1'`, + mysql: `json_extract(\`profile\`,'$.id') = CAST('1' AS JSON)`, + }); + }); + + it('accepts multiple condition object', () => { + expectsql( + () => queryGenerator.escape(json({ property: { value: 1 }, another: { value: 'string' } })), + { + postgres: `"property"->'value' = '1' AND "another"->'value' = '"string"'`, + sqlite3: `json_extract(\`property\`,'$.value') = '1' AND json_extract(\`another\`,'$.value') = '"string"'`, + mariadb: `json_compact(json_extract(\`property\`,'$.value')) = '1' AND json_compact(json_extract(\`another\`,'$.value')) = '"string"'`, + mysql: `json_extract(\`property\`,'$.value') = CAST('1' AS JSON) AND json_extract(\`another\`,'$.value') = CAST('"string"' AS JSON)`, + }, + ); + }); + + it('can be used inside of where', () => { + expectsql(() => queryGenerator.escape(where(json('profile.id'), '1')), { + postgres: `"profile"->'id' = '"1"'`, + sqlite3: `json_extract(\`profile\`,'$.id') = '"1"'`, + mariadb: `json_compact(json_extract(\`profile\`,'$.id')) = '"1"'`, + mysql: `json_extract(\`profile\`,'$.id') = CAST('"1"' AS JSON)`, + }); + }); +}); + +describe('cast', () => { + it('accepts condition object (auto casting)', () => { + expectsql( + () => + queryGenerator.escape( + fn( + 'SUM', + cast( + { + [Op.or]: { + foo: 'foo', + bar: 'bar', + }, + }, + 'int', + ), + ), + ), + { + default: `SUM(CAST(([foo] = 'foo' OR [bar] = 'bar') AS INT))`, + mssql: `SUM(CAST(([foo] = N'foo' OR [bar] = N'bar') AS INT))`, + }, + ); + }); +}); + +describe('fn', () => { + // this was a band-aid over a deeper problem ('$bind' being considered to be a bind parameter when it's a string), which has been fixed + it('should not escape $ in fn() arguments', () => { + const out = queryGenerator.escape(fn('upper', '$user')); + + expectsql(out, { + default: `upper('$user')`, + mssql: `upper(N'$user')`, + }); + }); + + it('accepts all sorts of values as arguments', () => { + const out = queryGenerator.escape( + fn( + 'concat', + 'user', + 1, + true, + new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), + fn('lower', 'user'), + ), + ); + + expectsql(out, { + postgres: `concat('user', 1, true, '2011-03-27 10:01:55.000 +00:00', lower('user'))`, + mssql: `concat(N'user', 1, 1, N'2011-03-27 10:01:55.000 +00:00', lower(N'user'))`, + sqlite3: `concat('user', 1, 1, '2011-03-27 10:01:55.000 +00:00', lower('user'))`, + ibmi: `concat('user', 1, 1, '2011-03-27 10:01:55.000', lower('user'))`, + default: `concat('user', 1, true, '2011-03-27 10:01:55.000', lower('user'))`, + }); + }); + + it('accepts arrays', () => { + if (!dialect.supports.dataTypes.ARRAY) { + return; + } + + const out = queryGenerator.escape(fn('concat', ['abc'])); + + expectsql(out, { + default: `concat(ARRAY['abc'])`, + postgres: `concat(ARRAY['abc']::VARCHAR(255)[])`, + }); + }); +}); + +describe('sql.join', () => { + it('joins parts with a separator', () => { + const columns = ['a', 'b', 'c']; + + // SQL expression parts, string separator + expectsql( + queryGenerator.escape( + sql`SELECT ${sql.join( + columns.map(col => sql.identifier(col)), + ', ', + )} FROM users`, + ), + { + default: `SELECT [a], [b], [c] FROM users`, + }, + ); + + // string parts, SQL expression separator + expectsql( + queryGenerator.escape( + sql`SELECT a FROM users WHERE id IN (${sql.join(['id1', 'id2', 'id3'], sql`, `)}) FROM users`, + ), + { + default: `SELECT a FROM users WHERE id IN ('id1', 'id2', 'id3') FROM users`, + mssql: `SELECT a FROM users WHERE id IN (N'id1', N'id2', N'id3') FROM users`, + }, + ); + }); +}); + +describe('sql.identifier', () => { + it('accepts strings', () => { + const out = queryGenerator.escape(sql.identifier('foo')); + + expectsql(out, { + default: `[foo]`, + }); + }); + + it('accepts table structures', () => { + const out = queryGenerator.escape(sql.identifier({ schema: 'foo', tableName: 'bar' })); + + expectsql(out, { + default: `[foo].[bar]`, + sqlite3: '`foo.bar`', + }); + }); + + it('accepts model classes', () => { + const User = sequelize.define( + 'User', + {}, + { + schema: 'schema', + tableName: 'users', + }, + ); + + const out = queryGenerator.escape(sql.identifier(User)); + + expectsql(out, { + default: `[schema].[users]`, + sqlite3: '`schema.users`', + }); + }); + + it('accepts model definitions', () => { + const User = sequelize.define( + 'User', + {}, + { + schema: 'schema', + tableName: 'users', + }, + ); + + const out = queryGenerator.escape(sql.identifier(User.modelDefinition)); + + expectsql(out, { + default: `[schema].[users]`, + sqlite3: '`schema.users`', + }); + }); + + it('accepts multiple parameters', () => { + const User = sequelize.define( + 'User', + {}, + { + schema: 'schema', + tableName: 'table', + }, + ); + + const out = queryGenerator.escape(sql.identifier('database', User, 'column')); + + expectsql(out, { + default: `[database].[schema].[table].[column]`, + sqlite3: '`database`.`schema.table`.`column`', + }); + }); +}); diff --git a/packages/core/test/unit/sql/order.test.js b/packages/core/test/unit/sql/order.test.js new file mode 100644 index 000000000000..aa6f7285eacf --- /dev/null +++ b/packages/core/test/unit/sql/order.test.js @@ -0,0 +1,438 @@ +'use strict'; + +const chai = require('chai'); + +const expect = chai.expect; +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); +const { + _validateIncludedElements, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'); +const { beforeAll2 } = require('../../support'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +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 + +describe('QueryGenerator#selectQuery with "order"', () => { + function expectSelect(options, expectation) { + const model = options.model; + + return expectsql( + sql.selectQuery(options.table || (model && model.table), options, options.model), + expectation, + ); + } + + const vars = beforeAll2(() => { + // models + const User = Support.sequelize.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id', + }, + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false, + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + tableName: 'user', + timestamps: true, + }, + ); + + const Project = Support.sequelize.define( + 'Project', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id', + }, + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false, + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + tableName: 'project', + timestamps: true, + }, + ); + + const ProjectUser = Support.sequelize.define( + 'ProjectUser', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id', + }, + userId: { + type: DataTypes.INTEGER, + field: 'user_id', + allowNull: false, + }, + projectId: { + type: DataTypes.INTEGER, + field: 'project_id', + allowNull: false, + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + tableName: 'project_user', + timestamps: true, + }, + ); + + const Task = Support.sequelize.define( + 'Task', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id', + }, + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false, + }, + projectId: { + type: DataTypes.INTEGER, + field: 'project_id', + allowNull: false, + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + tableName: 'task', + timestamps: true, + }, + ); + + const Subtask = Support.sequelize.define( + 'Subtask', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id', + }, + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false, + }, + taskId: { + type: DataTypes.INTEGER, + field: 'task_id', + allowNull: false, + }, + createdAt: { + field: 'created_at', + }, + updatedAt: { + field: 'updated_at', + }, + }, + { + tableName: 'subtask', + timestamps: true, + }, + ); + + // Relations + User.belongsToMany(Project, { + as: 'ProjectUserProjects', + inverse: { + as: 'ProjectUserUsers', + }, + through: ProjectUser, + foreignKey: 'user_id', + otherKey: 'project_id', + }); + + Project.belongsToMany(User, { + as: 'ProjectUserUsers', + inverse: { + as: 'ProjectUserProjects', + }, + through: ProjectUser, + foreignKey: 'project_id', + otherKey: 'user_id', + }); + + Project.hasMany(Task, { + as: 'Tasks', + foreignKey: 'project_id', + inverse: 'Project', + }); + + Task.belongsTo(Project, { + as: 'Project', + foreignKey: 'project_id', + }); + + Task.hasMany(Subtask, { + as: 'Subtasks', + foreignKey: 'task_id', + inverse: 'Task', + }); + + Subtask.belongsTo(Task, { + as: 'Task', + foreignKey: 'task_id', + }); + + return { User, Project, ProjectUser, Task, Subtask }; + }); + + it('supports "order"', () => { + const { Project, Subtask, Task } = vars; + + expectSelect( + { + model: Subtask, + attributes: ['id', 'name', 'createdAt'], + include: _validateIncludedElements({ + include: [ + { + association: Subtask.associations.Task, + required: true, + attributes: ['id', 'name', 'createdAt'], + include: [ + { + association: Task.associations.Project, + required: true, + attributes: ['id', 'name', 'createdAt'], + }, + ], + }, + ], + model: Subtask, + }).include, + order: [ + // order with multiple simple association syntax with direction + [ + { + model: Task, + as: 'Task', + }, + { + model: Project, + as: 'Project', + }, + 'createdAt', + 'ASC', + ], + // order with multiple simple association syntax without direction + [ + { + model: Task, + as: 'Task', + }, + { + model: Project, + as: 'Project', + }, + 'createdAt', + ], + + // order with simple association syntax with direction + [ + { + model: Task, + as: 'Task', + }, + 'createdAt', + 'ASC', + ], + // order with simple association syntax without direction + [ + { + model: Task, + as: 'Task', + }, + 'createdAt', + ], + + // through model object as array with direction + [Task, Project, 'createdAt', 'ASC'], + // through model object as array without direction + [Task, Project, 'createdAt'], + + // model object as array with direction + [Task, 'createdAt', 'ASC'], + // model object as array without direction + [Task, 'createdAt'], + + // through association object as array with direction + [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'ASC'], + // through association object as array without direction + [Subtask.associations.Task, Task.associations.Project, 'createdAt'], + + // association object as array with direction + [Subtask.associations.Task, 'createdAt', 'ASC'], + // association object as array without direction + [Subtask.associations.Task, 'createdAt'], + + // through association name order as array with direction + ['Task', 'Project', 'createdAt', 'ASC'], + // through association name as array without direction + ['Task', 'Project', 'createdAt'], + + // association name as array with direction + ['Task', 'createdAt', 'ASC'], + // association name as array without direction + ['Task', 'createdAt'], + + // main order as array with direction + ['createdAt', 'ASC'], + // main order as array without direction + ['createdAt'], + // main order as string + 'createdAt', + ], + }, + { + default: + 'SELECT [Subtask].[id], [Subtask].[name], [Subtask].[createdAt], [Task].[id] AS [Task.id], [Task].[name] AS [Task.name], [Task].[created_at] AS [Task.createdAt], [Task->Project].[id] AS [Task.Project.id], [Task->Project].[name] AS [Task.Project.name], [Task->Project].[created_at] AS [Task.Project.createdAt] FROM [subtask] AS [Subtask] INNER JOIN [task] AS [Task] ON [Subtask].[task_id] = [Task].[id] INNER JOIN [project] AS [Task->Project] ON [Task].[project_id] = [Task->Project].[id] ORDER BY [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Subtask].[created_at] ASC, [Subtask].[created_at], [Subtask].[created_at];', + postgres: + 'SELECT "Subtask"."id", "Subtask"."name", "Subtask"."createdAt", "Task"."id" AS "Task.id", "Task"."name" AS "Task.name", "Task"."created_at" AS "Task.createdAt", "Task->Project"."id" AS "Task.Project.id", "Task->Project"."name" AS "Task.Project.name", "Task->Project"."created_at" AS "Task.Project.createdAt" FROM "subtask" AS "Subtask" INNER JOIN "task" AS "Task" ON "Subtask"."task_id" = "Task"."id" INNER JOIN "project" AS "Task->Project" ON "Task"."project_id" = "Task->Project"."id" ORDER BY "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Subtask"."created_at" ASC, "Subtask"."created_at", "Subtask"."created_at";', + }, + ); + }); + + it('supports random ordering', () => { + const { Subtask } = vars; + + expectSelect( + { + model: Subtask, + attributes: ['id', 'name'], + order: [Support.sequelize.random()], + }, + { + ibmi: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND()', + mssql: 'SELECT [id], [name] FROM [subtask] AS [Subtask] ORDER BY RAND();', + db2: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RAND();', + mariadb: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', + mysql: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', + postgres: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();', + snowflake: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();', + sqlite3: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RANDOM();', + }, + ); + }); + + describe('Invalid', () => { + it('Error on invalid association', () => { + const { Project, Subtask } = vars; + + return expect( + Subtask.findAll({ + order: [[Project, 'createdAt', 'ASC']], + }), + ).to.eventually.be.rejectedWith( + Error, + 'Invalid Include received: no associations exist between "Subtask" and "Project"', + ); + }); + + it('Error on invalid structure', () => { + const { Subtask, Task } = vars; + + return expect( + Subtask.findAll({ + order: [[Subtask.associations.Task, 'createdAt', Task.associations.Project, 'ASC']], + }), + ).to.eventually.be.rejectedWith(Error, 'Unknown structure passed to order / group: Project'); + }); + + it('Error when the order is a string', () => { + const { Subtask } = vars; + + return expect( + Subtask.findAll({ + order: 'i am a silly string', + }), + ).to.eventually.be.rejectedWith( + Error, + 'Order must be type of array or instance of a valid sequelize method.', + ); + }); + + it('Error when the order contains a `{raw: "..."}` object', () => { + const { Subtask } = vars; + + return expect( + Subtask.findAll({ + order: [ + { + raw: 'this should throw an error', + }, + ], + }), + ).to.eventually.be.rejectedWith( + Error, + 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.', + ); + }); + + it('Error when the order contains a `{raw: "..."}` object wrapped in an array', () => { + const { Subtask } = vars; + + return expect( + Subtask.findAll({ + order: [ + [ + { + raw: 'this should throw an error', + }, + ], + ], + }), + ).to.eventually.be.rejectedWith( + Error, + 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.', + ); + }); + }); +}); diff --git a/packages/core/test/unit/sql/select.test.js b/packages/core/test/unit/sql/select.test.js new file mode 100644 index 000000000000..3d3ce5963dd9 --- /dev/null +++ b/packages/core/test/unit/sql/select.test.js @@ -0,0 +1,1469 @@ +'use strict'; + +const Support = require('../../support'); +const { DataTypes, Op } = require('@sequelize/core'); +const util = require('node:util'); +const { + _validateIncludedElements, +} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'); +const { beforeAll2, createSequelizeInstance } = require('../../support'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +const sql = current.queryGenerator; + +const TICK_LEFT = Support.sequelize.dialect.TICK_CHAR_LEFT; +const TICK_RIGHT = Support.sequelize.dialect.TICK_CHAR_RIGHT; + +// 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('select', () => { + function expectSelect(options, expectation) { + const model = options.model; + + return expectsql( + () => sql.selectQuery(options.table || (model && model.table), options, options.model), + expectation, + ); + } + + const testsql = function (options, expectation, testFunction = it) { + testFunction(util.inspect(options, { depth: 2 }), () => { + expectSelect(options, expectation); + }); + }; + + testsql.only = (options, expectation) => testsql(options, expectation, it.only); + + testsql( + { + table: 'User', + attributes: ['email', ['first_name', 'firstName']], + where: { + email: 'jon.snow@gmail.com', + }, + order: [['email', 'DESC']], + limit: 10, + }, + { + default: + "SELECT [email], [first_name] AS [firstName] FROM [User] WHERE [User].[email] = 'jon.snow@gmail.com' ORDER BY [email] DESC LIMIT 10;", + db2: 'SELECT "email", "first_name" AS "firstName" FROM "User" WHERE "User"."email" = \'jon.snow@gmail.com\' ORDER BY "email" DESC FETCH NEXT 10 ROWS ONLY;', + mssql: + "SELECT [email], [first_name] AS [firstName] FROM [User] WHERE [User].[email] = N'jon.snow@gmail.com' ORDER BY [email] DESC OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;", + ibmi: 'SELECT "email", "first_name" AS "firstName" FROM "User" WHERE "User"."email" = \'jon.snow@gmail.com\' ORDER BY "email" DESC FETCH NEXT 10 ROWS ONLY', + }, + ); + + testsql( + { + table: 'User', + attributes: ['email', ['first_name', 'firstName'], ['last_name', 'lastName']], + order: [['last_name', 'ASC']], + groupedLimit: { + limit: 3, + on: 'companyId', + values: [1, 5], + }, + }, + { + default: `SELECT [User].* FROM (${[ + `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 1 ORDER BY [last_name] ASC LIMIT 3) AS sub`, + `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 5 ORDER BY [last_name] ASC LIMIT 3) AS sub`, + ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')}) AS [User];`, + 'db2 ibmi': `SELECT [User].* FROM (${[ + `SELECT * FROM (SELECT "email", "first_name" AS "firstName", "last_name" AS "lastName" FROM "User" WHERE "User"."companyId" = 1 ORDER BY "last_name" ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT "email", "first_name" AS "firstName", "last_name" AS "lastName" FROM "User" WHERE "User"."companyId" = 5 ORDER BY "last_name" ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')}) AS [User];`, + mssql: `SELECT [User].* FROM (${[ + `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 1 ORDER BY [last_name] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 5 ORDER BY [last_name] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ')}) AS [User];`, + }, + ); + + describe('With BelongsToMany', () => { + const vars = beforeAll2(() => { + const User = Support.sequelize.define('user', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id_user', + }, + }); + + const Project = Support.sequelize.define('project', { + title: DataTypes.STRING, + }); + + const ProjectUser = Support.sequelize.define( + 'project_user', + { + userId: { + type: DataTypes.INTEGER, + field: 'user_id', + }, + projectId: { + type: DataTypes.INTEGER, + field: 'project_id', + }, + }, + { timestamps: false }, + ); + + User.Projects = User.belongsToMany(Project, { through: ProjectUser }); + Project.belongsToMany(User, { through: ProjectUser }); + + return { User, Project, ProjectUser }; + }); + + it('supports groupedLimit', () => { + const { User } = vars; + + expectSelect( + { + table: User.table, + model: User, + attributes: [['id_user', 'id']], + order: [['last_name', 'ASC']], + groupedLimit: { + limit: 3, + on: User.Projects, + values: [1, 5], + }, + }, + { + default: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + 'db2 ibmi': `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + mssql: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + }, + ); + }); + + it('supports groupedLimit with through', () => { + const { User } = vars; + + expectSelect( + { + table: User.table, + model: User, + attributes: [['id_user', 'id']], + order: [['last_name', 'ASC']], + groupedLimit: { + limit: 3, + through: { + where: { + status: 1, + }, + }, + on: User.Projects, + values: [1, 5], + }, + }, + { + default: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 1 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 5 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + 'db2 ibmi': `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 1 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 5 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + mssql: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 1 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND ([project_user].[project_id] = 5 + AND [project_user].[status] = 1) + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + }, + ); + }); + + it('supports groupedLimit with through and where', () => { + const { User } = vars; + + expectSelect( + { + table: User.table, + model: User, + attributes: [['id_user', 'id']], + order: [['id_user', 'ASC']], + where: { + age: { + [Op.gte]: 21, + }, + }, + groupedLimit: { + limit: 3, + on: User.Projects, + values: [1, 5], + }, + }, + { + default: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC LIMIT 3 + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + 'db2 ibmi': `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + mssql: `SELECT [user].* FROM (${[ + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 1 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + `SELECT * FROM ( + SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_user].[user_id] AS [project_user.userId], [project_user].[project_id] AS [project_user.projectId] + FROM [users] AS [user] + INNER JOIN [project_users] AS [project_user] + ON [user].[id_user] = [project_user].[user_id] + AND [project_user].[project_id] = 5 + WHERE [user].[age] >= 21 + ORDER BY [subquery_order_0] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY + ) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] ORDER BY [subquery_order_0] ASC;`, + }, + ); + }); + }); + + describe('With HasMany', () => { + const vars = beforeAll2(() => { + const User = Support.sequelize.define( + 'user', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id_user', + }, + email: DataTypes.STRING, + firstName: { + type: DataTypes.STRING, + field: 'first_name', + }, + lastName: { + type: DataTypes.STRING, + field: 'last_name', + }, + }, + { + tableName: 'users', + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + userId: { + type: DataTypes.INTEGER, + field: 'user_id', + }, + }, + { + tableName: 'post', + }, + ); + + User.Posts = User.hasMany(Post, { foreignKey: 'userId', as: 'POSTS' }); + + const Comment = Support.sequelize.define( + 'Comment', + { + title: DataTypes.STRING, + postId: { + type: DataTypes.INTEGER, + field: 'post_id', + }, + }, + { + tableName: 'comment', + }, + ); + + Post.Comments = Post.hasMany(Comment, { foreignKey: 'postId', as: 'COMMENTS' }); + + const include = _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + }, + ], + model: User, + }).include; + + return { User, Post, include }; + }); + + it('supports groupedLimit', () => { + const { include, User } = vars; + + expectSelect( + { + table: User.table, + model: User, + include, + attributes: [ + ['id_user', 'id'], + 'email', + ['first_name', 'firstName'], + ['last_name', 'lastName'], + ], + order: [['last_name', 'ASC']], + groupedLimit: { + limit: 3, + on: 'companyId', + values: [1, 5], + }, + }, + { + default: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC LIMIT 3) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC LIMIT 3) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id];`, + 'db2 ibmi': `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id];`, + mssql: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id];`, + }, + ); + }); + + it('supports order, limit, offset', () => { + const { include, User } = vars; + + expectSelect( + { + table: User.table, + model: User, + include, + attributes: [ + ['id_user', 'id'], + 'email', + ['first_name', 'firstName'], + ['last_name', 'lastName'], + ], + // [last_name] is not wrapped in a literal, so it's a column name and must be escaped + // as [[[last_name]]] + order: [ + [ + '[last_name]' + .replaceAll('[', Support.sequelize.dialect.TICK_CHAR_LEFT) + .replaceAll(']', Support.sequelize.dialect.TICK_CHAR_RIGHT), + 'ASC', + ], + ], + limit: 30, + offset: 10, + hasMultiAssociation: true, // must be set only for mssql dialect here + subQuery: true, + }, + { + default: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (SELECT [user].[id_user] AS [id], [user].[email], [user].[first_name] AS [firstName], [user].[last_name] AS [lastName] FROM [users] AS [user] ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC LIMIT 30 OFFSET 10) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id_user] = [POSTS].[user_id] ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC;`, + 'db2 ibmi mssql': `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (SELECT [user].[id_user] AS [id], [user].[email], [user].[first_name] AS [firstName], [user].[last_name] AS [lastName] FROM [users] AS [user] ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC OFFSET 10 ROWS FETCH NEXT 30 ROWS ONLY) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id_user] = [POSTS].[user_id] ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC;`, + }, + ); + }); + + it('supports order, limit, offset without subQuery', () => { + const { include, User } = vars; + + // By default, SELECT with include of a multi association & limit will be ran as a subQuery + // This checks the result when the query is forced to be ran without a subquery + expectSelect( + { + table: User.table, + model: User, + include, + attributes: [ + ['id_user', 'id'], + 'email', + ['first_name', 'firstName'], + ['last_name', 'lastName'], + ], + // [last_name] is not wrapped in a literal, so it's a column name and must be escaped + // as [[[last_name]]] + order: [ + [ + '[last_name]' + .replaceAll('[', Support.sequelize.dialect.TICK_CHAR_LEFT) + .replaceAll(']', Support.sequelize.dialect.TICK_CHAR_RIGHT), + 'ASC', + ], + ], + limit: 30, + offset: 10, + hasMultiAssociation: true, // must be set only for mssql dialect here + subQuery: false, + }, + { + default: `SELECT [user].[id_user] AS [id], [user].[email], [user].[first_name] AS [firstName], [user].[last_name] AS [lastName], [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] + FROM [users] AS [user] LEFT OUTER JOIN [post] AS [POSTS] + ON [user].[id_user] = [POSTS].[user_id] + ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC LIMIT 30 OFFSET 10;`, + 'db2 ibmi mssql': `SELECT [user].[id_user] AS [id], [user].[email], [user].[first_name] AS [firstName], [user].[last_name] AS [lastName], [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] + FROM [users] AS [user] LEFT OUTER JOIN [post] AS [POSTS] + ON [user].[id_user] = [POSTS].[user_id] + ORDER BY [user].${TICK_LEFT}${TICK_LEFT}${TICK_LEFT}last_name${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} ASC OFFSET 10 ROWS FETCH NEXT 30 ROWS ONLY;`, + }, + ); + }); + + it('supports nested includes', () => { + const { Post, User } = vars; + const nestedInclude = _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + include: [ + { + attributes: ['title'], + association: Post.Comments, + }, + ], + }, + ], + model: User, + }).include; + + expectSelect( + { + table: User.table, + model: User, + include: nestedInclude, + attributes: [ + ['id_user', 'id'], + 'email', + ['first_name', 'firstName'], + ['last_name', 'lastName'], + ], + order: [['last_name', 'ASC']], + groupedLimit: { + limit: 3, + on: 'companyId', + values: [1, 5], + }, + }, + { + default: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title], [POSTS->COMMENTS].[id] AS [POSTS.COMMENTS.id], [POSTS->COMMENTS].[title] AS [POSTS.COMMENTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC LIMIT 3) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC LIMIT 3) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id] LEFT OUTER JOIN [comment] AS [POSTS->COMMENTS] ON [POSTS].[id] = [POSTS->COMMENTS].[post_id];`, + 'db2 ibmi': `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title], [POSTS->COMMENTS].[id] AS [POSTS.COMMENTS.id], [POSTS->COMMENTS].[title] AS [POSTS.COMMENTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id] LEFT OUTER JOIN [comment] AS [POSTS->COMMENTS] ON [POSTS].[id] = [POSTS->COMMENTS].[post_id];`, + mssql: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title], [POSTS->COMMENTS].[id] AS [POSTS.COMMENTS.id], [POSTS->COMMENTS].[title] AS [POSTS.COMMENTS.title] FROM (${[ + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [lastName] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [lastName] ASC OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY) AS sub`, + ].join( + current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ', + )}) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id] LEFT OUTER JOIN [comment] AS [POSTS->COMMENTS] ON [POSTS].[id] = [POSTS->COMMENTS].[post_id];`, + }, + ); + }); + }); + + it('include (left outer join)', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + ibmi: 'SELECT "User"."name", "User"."age", "posts"."id" AS "posts.id", "posts"."title" AS "posts.title" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "posts" ON "User"."id" = "posts"."user_id"', + default: + 'SELECT [User].[name], [User].[age], [posts].[id] AS [posts.id], [posts].[title] AS [posts.title] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [posts] ON [User].[id] = [posts].[user_id];', + }, + ); + }); + + it('include (right outer join)', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + right: true, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + default: `SELECT [User].[name], [User].[age], [posts].[id] AS [posts.id], [posts].[title] AS [posts.title] FROM [User] AS [User] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [Post] AS [posts] ON [User].[id] = [posts].[user_id];`, + }, + ); + }); + + it('include through (right outer join)', () => { + const User = Support.sequelize.define('user', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id_user', + }, + }); + const Project = Support.sequelize.define('project', { + title: DataTypes.STRING, + }); + + const ProjectUser = Support.sequelize.define( + 'project_user', + { + userId: { + type: DataTypes.INTEGER, + field: 'user_id', + }, + projectId: { + type: DataTypes.INTEGER, + field: 'project_id', + }, + }, + { timestamps: false }, + ); + + User.belongsToMany(Project, { through: ProjectUser }); + Project.belongsToMany(User, { through: ProjectUser }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['id_user', 'id'], + include: _validateIncludedElements({ + include: [ + { + model: Project, + right: true, + }, + ], + model: User, + }).include, + model: User, + // the order here is important, because a different piece of code is responsible for naming the through table name in ORDER BY + // than in LEFT JOIN + order: [['projects', ProjectUser, 'userId', 'ASC']], + }, + 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] + ORDER BY [projects->project_user].[user_id] ASC;`, + }, + ); + }); + + describe('include (subQuery alias)', () => { + const vars = beforeAll2(() => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'postaliasname' }); + + return { User }; + }); + + it('w/o filters', () => { + const { User } = vars; + + expectsql( + sql.selectQuery( + 'User', + { + table: User.table, + model: User, + attributes: ['name', 'age'], + include: _validateIncludedElements({ + model: User, + 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], [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 EXISTS ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE [postaliasname].[user_id] = [User].[id]) ) AS [User];`, + }, + ); + }); + + it('w/ nested column filter', () => { + const { User } = vars; + + expectsql( + () => + sql.selectQuery( + 'User', + { + table: User.table, + model: User, + attributes: ['name', 'age'], + where: { '$postaliasname.title$': 'test' }, + include: _validateIncludedElements({ + model: User, + 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], [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 EXISTS ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE [postaliasname].[user_id] = [User].[id]) ) 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.table, + model: Company, + attributes: ['name', 'public'], + where: { '$Users.profession.name$': 'test', [Op.and]: { scopeId: [42] } }, + include: _validateIncludedElements({ + include: [ + { + association: Company.Users, + attributes: [], + include: [ + { + association: User.Profession, + attributes: [], + required: true, + }, + ], + subQuery: true, + required: true, + }, + ], + model: Company, + }).include, + limit: 5, + offset: 0, + subQuery: true, + }, + Company, + ), + { + default: + 'SELECT [Company].* FROM (' + + 'SELECT [Company].[name], [Company].[public], [Company].[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 ` + + 'EXISTS ( SELECT [Users].[companyId] FROM [Users] AS [Users] ' + + 'INNER JOIN [Professions] AS [profession] ON [Users].[professionId] = [profession].[id] ' + + `WHERE [Users].[companyId] = [Company].[id] ) ORDER BY [Company].[id] LIMIT 5) AS [Company];`, + 'db2 ibmi': + 'SELECT [Company].* FROM (' + + 'SELECT [Company].[name], [Company].[public], [Company].[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 ` + + 'EXISTS ( SELECT [Users].[companyId] FROM [Users] AS [Users] ' + + 'INNER JOIN [Professions] AS [profession] ON [Users].[professionId] = [profession].[id] ' + + `WHERE [Users].[companyId] = [Company].[id] ) ` + + `ORDER BY [Company].[id] FETCH NEXT 5 ROWS ONLY) AS [Company];`, + mssql: + 'SELECT [Company].* FROM (' + + 'SELECT [Company].[name], [Company].[public], [Company].[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 ` + + 'EXISTS ( SELECT [Users].[companyId] FROM [Users] AS [Users] ' + + 'INNER JOIN [Professions] AS [profession] ON [Users].[professionId] = [profession].[id] ' + + `WHERE [Users].[companyId] = [Company].[id] ) ` + + `ORDER BY [Company].[id] OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY) AS [Company];`, + }, + ); + }); + + it('properly stringify IN values as per field definition', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + data: DataTypes.BLOB, + }, + { + freezeTableName: true, + }, + ); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age', 'data'], + where: { + data: ['123'], + }, + }, + User, + ), + { + ibmi: `SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN (BLOB(X'313233'))`, + db2: `SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN (BLOB('123'));`, + postgres: `SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN ('\\x313233');`, + snowflake: `SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN (X'313233');`, + 'mariadb mysql sqlite3': + "SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X'313233');", + mssql: + 'SELECT [name], [age], [data] FROM [User] AS [User] WHERE [User].[data] IN (0x313233);', + }, + ); + }); + + describe('attribute escaping', () => { + it('plain attributes (1)', () => { + expectsql( + sql.selectQuery('User', { + attributes: [ + '* FROM [User];' + .replaceAll('[', Support.sequelize.dialect.TICK_CHAR_LEFT) + .replaceAll(']', Support.sequelize.dialect.TICK_CHAR_RIGHT), + ], + }), + { + default: `SELECT ${TICK_LEFT}* FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT};${TICK_RIGHT} FROM ${TICK_LEFT}User${TICK_RIGHT};`, + }, + ); + }); + + it('plain attributes (2)', () => { + expectsql( + sql.selectQuery('User', { + attributes: ['* FROM User; DELETE FROM User;SELECT id'], + }), + { + default: 'SELECT [* FROM User; DELETE FROM User;SELECT id] FROM [User];', + ibmi: 'SELECT "* FROM User; DELETE FROM User;SELECT id" FROM "User"', + }, + ); + }); + + it('plain attributes (3)', () => { + expectsql( + sql.selectQuery('User', { + attributes: [`a', * FROM User; DELETE FROM User;SELECT id`], + }), + { + default: `SELECT [a', * FROM User; DELETE FROM User;SELECT id] FROM [User];`, + mssql: `SELECT [a', * FROM User; DELETE FROM User;SELECT id] FROM [User];`, + ibmi: `SELECT "a', * FROM User; DELETE FROM User;SELECT id" FROM "User"`, + }, + ); + }); + + it('plain attributes (4)', () => { + expectsql( + sql.selectQuery('User', { + attributes: ['*, COUNT(*) FROM User; DELETE FROM User;SELECT id'], + }), + { + default: 'SELECT [*, COUNT(*) FROM User; DELETE FROM User;SELECT id] FROM [User];', + ibmi: 'SELECT "*, COUNT(*) FROM User; DELETE FROM User;SELECT id" FROM "User"', + }, + ); + }); + + it('aliased attributes (1)', () => { + expectsql( + sql.selectQuery('User', { + attributes: [ + // this is not wrapped in `literal()`, so it's a column name. + // [ & ] will be escaped as [[ & ]] + [ + '* FROM [User]; DELETE FROM [User];SELECT [id]' + .replaceAll('[', Support.sequelize.dialect.TICK_CHAR_LEFT) + .replaceAll(']', Support.sequelize.dialect.TICK_CHAR_RIGHT), + 'myCol', + ], + ], + }), + { + default: `SELECT [* FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT}; DELETE FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT};SELECT ${TICK_LEFT}${TICK_LEFT}id${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} AS ${TICK_LEFT}myCol] FROM [User];`, + ibmi: 'SELECT "* FROM ""User""; DELETE FROM ""User"";SELECT ""id""" AS "myCol" FROM "User"', + }, + ); + }); + + it('aliased attributes (2)', () => { + expectsql( + sql.selectQuery('User', { + attributes: [['* FROM User; DELETE FROM User;SELECT id', 'myCol']], + }), + { + default: 'SELECT [* FROM User; DELETE FROM User;SELECT id] AS [myCol] FROM [User];', + ibmi: 'SELECT "* FROM User; DELETE FROM User;SELECT id" AS "myCol" FROM "User"', + }, + ); + }); + + it('aliased attributes (3)', () => { + expectsql( + sql.selectQuery('User', { + attributes: [['id', '* FROM User; DELETE FROM User;SELECT id']], + }), + { + default: 'SELECT [id] AS [* FROM User; DELETE FROM User;SELECT id] FROM [User];', + ibmi: 'SELECT "id" AS "* FROM User; DELETE FROM User;SELECT id" FROM "User"', + }, + ); + }); + + it('attributes from includes', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + // association name is Pascal case to test quoteIdentifier: false + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'Posts' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: [ + // this is not wrapped in `literal()`, so it's a column name. + // [ & ] will be escaped as [[ & ]] + '* FROM [User]; DELETE FROM [User];SELECT [id]' + .replaceAll('[', TICK_LEFT) + .replaceAll(']', TICK_RIGHT), + ], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + // expectsql fails with consecutive TICKS so we add the dialect-specific one ourself + default: `SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT}; DELETE FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT};SELECT ${TICK_LEFT}${TICK_LEFT}id${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} AS ${TICK_LEFT}Posts.* FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT}; DELETE FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT};SELECT ${TICK_LEFT}${TICK_LEFT}id${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} FROM ${TICK_LEFT}User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];`, + ibmi: 'SELECT "User"."name", "User"."age", "Posts"."id" AS "Posts.id", "Posts"."* FROM ""User""; DELETE FROM ""User"";SELECT ""id""" AS "Posts.* FROM ""User""; DELETE FROM ""User"";SELECT ""id""" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "Posts" ON "User"."id" = "Posts"."user_id"', + }, + ); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: [ + // this is not wrapped in `literal()`, so it's a column name. + // [ & ] will be escaped as [[ & ]] + [ + '* FROM [User]; DELETE FROM [User];SELECT [id]' + .replaceAll('[', Support.sequelize.dialect.TICK_CHAR_LEFT) + .replaceAll(']', Support.sequelize.dialect.TICK_CHAR_RIGHT), + 'data', + ], + ], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + // expectsql fails with consecutive TICKS so we add the dialect-specific one ourself + default: `SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT}; DELETE FROM ${TICK_LEFT}${TICK_LEFT}User${TICK_RIGHT}${TICK_RIGHT};SELECT ${TICK_LEFT}${TICK_LEFT}id${TICK_RIGHT}${TICK_RIGHT}${TICK_RIGHT} AS ${TICK_LEFT}Posts.data] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];`, + ibmi: 'SELECT "User"."name", "User"."age", "Posts"."id" AS "Posts.id", "Posts"."* FROM ""User""; DELETE FROM ""User"";SELECT ""id""" AS "Posts.data" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "Posts" ON "User"."id" = "Posts"."user_id"', + }, + ); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: [['* FROM User; DELETE FROM User;SELECT id', 'data']], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + ibmi: 'SELECT "User"."name", "User"."age", "Posts"."id" AS "Posts.id", "Posts"."* FROM User; DELETE FROM User;SELECT id" AS "Posts.data" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "Posts" ON "User"."id" = "Posts"."user_id"', + default: + 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM User; DELETE FROM User;SELECT id] AS [Posts.data] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];', + }, + ); + }); + }); + }); + + describe('quoteIdentifiers: false', () => { + let sql; + + beforeEach(() => { + sql = createSequelizeInstance({ + quoteIdentifiers: false, + }).queryGenerator; + }); + + it('*', () => { + expectsql(sql.selectQuery('User'), { + default: 'SELECT * FROM [User];', + ibmi: 'SELECT * FROM "User"', + postgres: 'SELECT * FROM "User";', + snowflake: 'SELECT * FROM User;', + }); + }); + + it('with attributes', () => { + expectsql( + sql.selectQuery('User', { + attributes: ['name', 'age'], + }), + { + default: 'SELECT [name], [age] FROM [User];', + ibmi: 'SELECT "name", "age" FROM "User"', + postgres: 'SELECT name, age FROM "User";', + snowflake: 'SELECT name, age FROM User;', + }, + ); + }); + + it('include (left outer join)', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + // association name is Pascal case to test quoteIdentifier: false + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'Posts' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + default: + 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];', + ibmi: 'SELECT "User"."name", "User"."age", "Posts"."id" AS "Posts.id", "Posts"."title" AS "Posts.title" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "Posts" ON "User"."id" = "Posts"."user_id"', + postgres: + 'SELECT "User".name, "User".age, Posts.id AS "Posts.id", Posts.title AS "Posts.title" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id;', + snowflake: + 'SELECT User.name, User.age, Posts.id AS "Posts.id", Posts.title AS "Posts.title" FROM User AS User LEFT OUTER JOIN Post AS Posts ON User.id = Posts.user_id;', + }, + ); + }); + + it('nested include (left outer join)', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + const Comment = Support.sequelize.define( + 'Comment', + { + title: DataTypes.STRING, + }, + { + freezeTableName: true, + }, + ); + + // association names are Pascal case to test quoteIdentifier: false + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'Posts' }); + Post.Comments = Post.hasMany(Comment, { foreignKey: 'post_id', as: 'Comments' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age'], + include: _validateIncludedElements({ + include: [ + { + attributes: ['title'], + association: User.Posts, + include: [ + { + model: Comment, + }, + ], + }, + ], + model: User, + }).include, + model: User, + }, + User, + ), + { + default: + 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title], [Posts->Comments].[id] AS [Posts.Comments.id], [Posts->Comments].[title] AS [Posts.Comments.title], [Posts->Comments].[createdAt] AS [Posts.Comments.createdAt], [Posts->Comments].[updatedAt] AS [Posts.Comments.updatedAt], [Posts->Comments].[post_id] AS [Posts.Comments.post_id] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id] LEFT OUTER JOIN [Comment] AS [Posts->Comments] ON [Posts].[id] = [Posts->Comments].[post_id];', + ibmi: 'SELECT "User"."name", "User"."age", "Posts"."id" AS "Posts.id", "Posts"."title" AS "Posts.title", "Posts->Comments"."id" AS "Posts.Comments.id", "Posts->Comments"."title" AS "Posts.Comments.title", "Posts->Comments"."createdAt" AS "Posts.Comments.createdAt", "Posts->Comments"."updatedAt" AS "Posts.Comments.updatedAt", "Posts->Comments"."post_id" AS "Posts.Comments.post_id" FROM "User" AS "User" LEFT OUTER JOIN "Post" AS "Posts" ON "User"."id" = "Posts"."user_id" LEFT OUTER JOIN "Comment" AS "Posts->Comments" ON "Posts"."id" = "Posts->Comments"."post_id"', + postgres: + 'SELECT "User".name, "User".age, Posts.id AS "Posts.id", Posts.title AS "Posts.title", "Posts->Comments".id AS "Posts.Comments.id", "Posts->Comments".title AS "Posts.Comments.title", "Posts->Comments".createdAt AS "Posts.Comments.createdAt", "Posts->Comments".updatedAt AS "Posts.Comments.updatedAt", "Posts->Comments".post_id AS "Posts.Comments.post_id" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id LEFT OUTER JOIN Comment AS "Posts->Comments" ON Posts.id = "Posts->Comments".post_id;', + snowflake: + 'SELECT User.name, User.age, Posts.id AS "Posts.id", Posts.title AS "Posts.title", "Posts->Comments".id AS "Posts.Comments.id", "Posts->Comments".title AS "Posts.Comments.title", "Posts->Comments".createdAt AS "Posts.Comments.createdAt", "Posts->Comments".updatedAt AS "Posts.Comments.updatedAt", "Posts->Comments".post_id AS "Posts.Comments.post_id" FROM User AS User LEFT OUTER JOIN Post AS Posts ON User.id = Posts.user_id LEFT OUTER JOIN Comment AS "Posts->Comments" ON Posts.id = "Posts->Comments".post_id;', + }, + ); + }); + + it('attributes with dot notation', () => { + const User = Support.sequelize.define( + 'User', + { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + statuslabel: { + field: 'status.label', + type: DataTypes.STRING, + }, + }, + { + freezeTableName: true, + }, + ); + const Post = Support.sequelize.define( + 'Post', + { + title: DataTypes.STRING, + statuslabel: { + field: 'status.label', + type: DataTypes.STRING, + }, + }, + { + freezeTableName: true, + }, + ); + + // association name is Pascal case to test quoteIdentifier: false + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'Posts' }); + + expectsql( + sql.selectQuery( + 'User', + { + attributes: ['name', 'age', ['status.label', 'statuslabel']], + include: _validateIncludedElements({ + include: [ + { + attributes: ['title', ['status.label', 'statuslabel']], + association: User.Posts, + }, + ], + model: User, + }).include, + model: User, + dotNotation: true, + }, + User, + ), + { + default: + 'SELECT [User].[name], [User].[age], [User].[status.label] AS [statuslabel], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title], [Posts].[status.label] AS [Posts.statuslabel] 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" AS statuslabel, Posts.id AS "Posts.id", Posts.title AS "Posts.title", Posts."status.label" AS "Posts.statuslabel" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id;', + snowflake: + 'SELECT User.name, User.age, User."status.label" AS statuslabel, Posts.id AS "Posts.id", Posts.title AS "Posts.title", Posts."status.label" AS "Posts.statuslabel" FROM User AS User LEFT OUTER JOIN Post AS Posts ON User.id = Posts.user_id;', + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/sql/update.test.js b/packages/core/test/unit/sql/update.test.js new file mode 100644 index 000000000000..461b11d2551a --- /dev/null +++ b/packages/core/test/unit/sql/update.test.js @@ -0,0 +1,145 @@ +'use strict'; + +const Support = require('../../support'); +const { DataTypes } = require('@sequelize/core'); + +const expectsql = Support.expectsql; +const current = Support.sequelize; +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 + +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.table, + { user_name: 'triggertest' }, + { id: 2 }, + options, + User.getAttributes(), + ), + { + query: { + db2: 'SELECT * FROM FINAL TABLE (UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2);', + ibmi: 'UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2', + default: 'UPDATE [users] SET [user_name]=$sequelize_1 WHERE [id] = $sequelize_2', + }, + bind: { + default: { sequelize_1: 'triggertest', sequelize_2: 2 }, + }, + }, + ); + }); + + it('with temp table for trigger', () => { + const User = Support.sequelize.define( + 'user', + { + username: { + type: DataTypes.STRING, + field: 'user_name', + }, + }, + { + timestamps: false, + hasTrigger: true, + }, + ); + + const options = { + returning: true, + hasTrigger: true, + }; + expectsql( + sql.updateQuery( + User.table, + { user_name: 'triggertest' }, + { id: 2 }, + options, + User.getAttributes(), + ), + { + query: { + ibmi: 'UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2', + mssql: + 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); UPDATE [users] SET [user_name]=$sequelize_1 OUTPUT INSERTED.[id], INSERTED.[user_name] INTO @tmp WHERE [id] = $sequelize_2; SELECT * FROM @tmp', + sqlite3: + 'UPDATE `users` SET `user_name`=$sequelize_1 WHERE `id` = $sequelize_2 RETURNING `id`, `user_name`', + postgres: + 'UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2 RETURNING "id", "user_name"', + db2: 'SELECT * FROM FINAL TABLE (UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2);', + snowflake: 'UPDATE "users" SET "user_name"=$sequelize_1 WHERE "id" = $sequelize_2', + default: 'UPDATE `users` SET `user_name`=$sequelize_1 WHERE `id` = $sequelize_2', + }, + bind: { + default: { sequelize_1: 'triggertest', sequelize_2: 2 }, + }, + }, + ); + }); + + it('works with limit', () => { + const User = Support.sequelize.define( + 'User', + { + username: { + type: DataTypes.STRING, + }, + userId: { + type: DataTypes.INTEGER, + }, + }, + { + timestamps: false, + }, + ); + + expectsql( + sql.updateQuery( + User.table, + { username: 'new.username' }, + { username: 'username' }, + { limit: 1 }, + ), + { + query: { + ibmi: 'UPDATE "Users" SET "username"=$sequelize_1 WHERE "username" = $sequelize_2', + mssql: + 'UPDATE TOP(1) [Users] SET [username]=$sequelize_1 WHERE [username] = $sequelize_2', + mariadb: + 'UPDATE `Users` SET `username`=$sequelize_1 WHERE `username` = $sequelize_2 LIMIT 1', + mysql: + 'UPDATE `Users` SET `username`=$sequelize_1 WHERE `username` = $sequelize_2 LIMIT 1', + sqlite3: + 'UPDATE `Users` SET `username`=$sequelize_1 WHERE rowid IN (SELECT rowid FROM `Users` WHERE `username` = $sequelize_2 LIMIT 1)', + db2: 'SELECT * FROM FINAL TABLE (UPDATE (SELECT * FROM "Users" WHERE "username" = $sequelize_2 FETCH NEXT 1 ROWS ONLY) SET "username"=$sequelize_1);', + snowflake: + 'UPDATE "Users" SET "username"=$sequelize_1 WHERE "username" = $sequelize_2 LIMIT 1', + default: 'UPDATE [Users] SET [username]=$sequelize_1 WHERE [username] = $sequelize_2', + }, + bind: { + default: { sequelize_1: 'new.username', sequelize_2: 'username' }, + }, + }, + ); + }); + }); +}); diff --git a/packages/core/test/unit/sql/where.test.ts b/packages/core/test/unit/sql/where.test.ts new file mode 100644 index 000000000000..456eb4d96251 --- /dev/null +++ b/packages/core/test/unit/sql/where.test.ts @@ -0,0 +1,4010 @@ +import type { + AttributeNames, + Attributes, + Cast, + Col, + Fn, + InferAttributes, + Literal, + Range, + WhereOperators, + WhereOptions, +} from '@sequelize/core'; +import { DataTypes, JSON_NULL, Model, Op, SQL_NULL, and, json, or, sql } from '@sequelize/core'; +import type { FormatWhereOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; +import attempt from 'lodash/attempt'; +import util from 'node:util'; +import { createTester, expectsql, getTestDialectTeaser, sequelize } from '../../support'; + +const { literal, col, where, fn, cast, attribute } = sql; + +const queryGen = sequelize.dialect.queryGenerator; + +// Notice: [] will be replaced by dialect specific tick/quote character +// when there is no dialect specific expectation but only a default expectation + +// TODO: fix and resolve any .skip test + +type Expectations = { + [dialectName: string]: string | Error; +}; + +const dialectSupportsBigInt = () => sequelize.dialect.supports.dataTypes.BIGINT; +const dialectSupportsArray = () => sequelize.dialect.supports.dataTypes.ARRAY; +const dialectSupportsRange = () => sequelize.dialect.supports.dataTypes.RANGE; +const dialectSupportsJsonB = () => sequelize.dialect.supports.dataTypes.JSONB; +const dialectSupportsJson = () => sequelize.dialect.supports.dataTypes.JSON; +const dialectSupportsJsonOperations = () => sequelize.dialect.supports.jsonOperations; +const dialectSupportsJsonQuotedExtraction = () => sequelize.dialect.supports.jsonExtraction.quoted; +const dialectSupportsJsonUnquotedExtraction = () => + sequelize.dialect.supports.jsonExtraction.unquoted; + +interface SomeInterface { + foo: string; +} + +class TestModel extends Model> { + declare intAttr1: number; + declare intAttr2: number; + + declare nullableIntAttr: number | null; + + declare intArrayAttr: number[]; + declare intRangeAttr: Range; + declare dateRangeAttr: Range; + + declare stringAttr: string; + declare binaryAttr: Buffer; + declare dateAttr: Date; + declare booleanAttr: boolean; + declare bigIntAttr: bigint; + + declare jsonAttr: object | null; + declare jsonbAttr: object | null; + + declare aliasedInt: number; + declare aliasedJsonAttr: object; + declare aliasedJsonbAttr: object; + + declare jsonbTypeLiteralAttr: { foo: string }; + declare jsonbInterfaceAttr: SomeInterface; + + declare uuidAttr: string; +} + +type TestModelWhere = WhereOptions>; + +describe(getTestDialectTeaser('SQL'), () => { + before(() => { + TestModel.init( + { + intAttr1: DataTypes.INTEGER, + intAttr2: DataTypes.INTEGER, + nullableIntAttr: DataTypes.INTEGER, + + ...(dialectSupportsArray() && { + intArrayAttr: DataTypes.ARRAY(DataTypes.INTEGER), + intRangeAttr: DataTypes.RANGE(DataTypes.INTEGER), + dateRangeAttr: DataTypes.RANGE(DataTypes.DATE(3)), + }), + + stringAttr: DataTypes.STRING, + binaryAttr: DataTypes.BLOB, + dateAttr: DataTypes.DATE(3), + booleanAttr: DataTypes.BOOLEAN, + ...(dialectSupportsBigInt() && { bigIntAttr: DataTypes.BIGINT }), + + aliasedInt: { type: DataTypes.INTEGER, field: 'aliased_int' }, + + ...(dialectSupportsJson() && { + jsonAttr: { type: DataTypes.JSON }, + aliasedJsonAttr: { type: DataTypes.JSON, field: 'aliased_json' }, + }), + + ...(dialectSupportsJsonB() && { + jsonbAttr: { type: DataTypes.JSONB }, + aliasedJsonbAttr: { type: DataTypes.JSONB, field: 'aliased_jsonb' }, + jsonbTypeLiteralAttr: { type: DataTypes.JSONB }, + jsonbInterfaceAttr: { type: DataTypes.JSONB }, + }), + + uuidAttr: DataTypes.UUID, + }, + { sequelize }, + ); + }); + + describe('whereQuery', () => { + it('prefixes its output with WHERE when it is not empty', () => { + expectsql(queryGen.whereQuery({ firstName: 'abc' }), { + default: `WHERE [firstName] = 'abc'`, + mssql: `WHERE [firstName] = N'abc'`, + }); + }); + + it('returns an empty string if the input results in an empty query', () => { + expectsql(queryGen.whereQuery({ firstName: { [Op.notIn]: [] } }), { + default: '', + }); + }); + }); + + describe('whereItemsQuery', () => { + type IncludesType = Needle extends any + ? Extract extends never + ? false + : true + : never; + + /** + * 'OperatorsSupportingSequelizeValueMethods' lists all operators + * that accept values: `col()`, `literal()`, `fn()`, `cast()`, and { [Op.col] } + */ + type OperatorsSupportingSequelizeValueMethods = keyof { + [Key in keyof WhereOperators as IncludesType< + WhereOperators[Key], + Col | Literal | Fn | Cast | { [Op.col]: string } + > extends true + ? Key + : never]: WhereOperators[Key]; + }; + + /** + * Tests whether an operator is compatible with the 5 sequelize methods that can be used as values: + * - col() + * - literal() + * - fn() + * - cast() + * - legacy Op.col + * + * If there is a typescript error on the operator passed to this function, then + * the typings in {@link WhereOperators} for the provided operator are incorrect. + * + * @param operator + * @param sqlOperator + */ + function testSequelizeValueMethods( + operator: OperatorsSupportingSequelizeValueMethods, + sqlOperator: string, + ): void { + testSql( + { intAttr1: { [operator]: { [Op.col]: 'intAttr2' } } }, + { + default: `[intAttr1] ${sqlOperator} [intAttr2]`, + }, + ); + + testSql( + { intAttr1: { [operator]: col('intAttr2') } }, + { + default: `[intAttr1] ${sqlOperator} [intAttr2]`, + }, + ); + + testSql( + { intAttr1: { [operator]: literal('literal') } }, + { + default: `[intAttr1] ${sqlOperator} literal`, + }, + ); + + testSql( + { intAttr1: { [operator]: fn('NOW') } }, + { + default: `[intAttr1] ${sqlOperator} NOW()`, + }, + ); + + testSql( + { intAttr1: { [operator]: fn('SUM', { [Op.col]: 'intAttr2' }) } }, + { + default: `[intAttr1] ${sqlOperator} SUM([intAttr2])`, + }, + ); + + testSql( + { intAttr1: { [operator]: cast(col('intAttr2'), 'string') } }, + { + default: `[intAttr1] ${sqlOperator} CAST([intAttr2] AS STRING)`, + }, + ); + + testSql( + { intAttr1: { [operator]: cast({ [Op.col]: 'intAttr2' }, 'string') } }, + { + default: `[intAttr1] ${sqlOperator} CAST([intAttr2] AS STRING)`, + }, + ); + + testSql( + { intAttr1: { [operator]: cast(12, 'string') } }, + { + default: `[intAttr1] ${sqlOperator} CAST(12 AS STRING)`, + }, + ); + } + + /** + * 'OperatorsSupportingSequelizeValueMethods' lists all operators + * that accept values: `col()`, `literal()`, `fn()`, `cast()`, and { [Op.col] } + */ + type OperatorsSupportingAnyAll = keyof { + [Key in keyof WhereOperators as IncludesType< + WhereOperators[Key], + | { [Op.all]: any[] | Literal | { [Op.values]: any[] } } + | { [Op.any]: any[] | Literal | { [Op.values]: any[] } } + > extends true + ? Key + : never]: WhereOperators[Key]; + }; + + /** + * Tests whether an operator is compatible with: + * - Op.any (+ Op.values) + * - Op.all (+ Op.values) + * + * If there is a typescript error on the operator passed to this function, then + * the typings in {@link WhereOperators} for the provided operator are incorrect. + * + * @param operator + * @param sqlOperator + * @param testWithValues + * @param attributeName + */ + function testSupportsAnyAll( + operator: OperatorsSupportingAnyAll, + sqlOperator: string, + testWithValues: TestWithValue[], + attributeName: AttributeNames = 'intAttr1', + ) { + if (!dialectSupportsArray()) { + return; + } + + const arrayOperators: Array<[jsOp: symbol, sqlOp: string]> = [ + [Op.any, 'ANY'], + [Op.all, 'ALL'], + ]; + + for (const [arrayOperator, arraySqlOperator] of arrayOperators) { + testSql( + { [attributeName]: { [operator]: { [arrayOperator]: testWithValues } } }, + { + default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}])`, + postgres: `"${attributeName}" ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}]${attributeName === 'stringAttr' ? '::VARCHAR(255)[]' : ''})`, + }, + ); + + testSql( + { [attributeName]: { [operator]: { [arrayOperator]: literal('literal') } } }, + { + default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (literal)`, + }, + ); + + // e.g. "col" LIKE ANY (VALUES ("col2")) + testSql( + { + [attributeName]: { + [operator]: { + [arrayOperator]: { + [Op.values]: [ + literal('literal'), + fn('UPPER', col('col2')), + col('col3'), + cast(col('col'), 'string'), + testWithValues[0], + ], + }, + }, + }, + }, + { + default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (VALUES (literal), (UPPER("col2")), ("col3"), (CAST("col" AS STRING)), (${util.inspect(testWithValues[0])}))`, + }, + ); + } + } + + const testSql = createTester( + (it, whereObj: TestModelWhere, expectations: Expectations, options?: FormatWhereOptions) => { + it( + util.inspect(whereObj, { depth: 10 }) + (options ? `, ${util.inspect(options)}` : ''), + () => { + const sqlOrError = attempt(() => + queryGen.whereItemsQuery(whereObj, { + ...options, + model: TestModel, + }), + ); + + return expectsql(sqlOrError, expectations); + }, + ); + }, + ); + + // "where" is typically optional. If the user sets it to undefined, we treat is as if the option was not set. + testSql(undefined, { + default: '', + }); + + testSql( + {}, + { + default: '', + }, + ); + + testSql([], { + default: '', + }); + + // @ts-expect-error -- not supported, testing that it throws + testSql(null, { + default: + new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts. +Value: null +Caused by: Invalid Query: expected a plain object, an array or a sequelize SQL method but got null`), + }); + + // @ts-expect-error -- not supported, testing that it throws + testSql(10, { + default: + new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts. +Value: 10 +Caused by: Invalid Query: expected a plain object, an array or a sequelize SQL method but got 10`), + }); + + testSql( + { intAttr1: undefined }, + { + default: + new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts. +Value: { intAttr1: undefined } +Caused by: "undefined" cannot be escaped`), + }, + ); + + testSql( + // @ts-expect-error -- user does not exist + { intAttr1: 1, user: undefined }, + { default: new Error('"undefined" cannot be escaped') }, + ); + + testSql( + { intAttr1: 1 }, + { + default: '[User].[intAttr1] = 1', + }, + { mainAlias: 'User' }, + ); + + testSql( + { dateAttr: { $gte: '2022-11-06' } }, + { default: new Error(`{ '$gte': '2022-11-06' } is not a valid date`) }, + ); + + testSql(literal('raw sql'), { + default: 'raw sql', + }); + + describe('value serialization', () => { + // string + testSql( + { stringAttr: '1' }, + { + default: `[stringAttr] = '1'`, + mssql: `[stringAttr] = N'1'`, + }, + ); + + testSql( + { + stringAttr: 'here is a null char: \0', + }, + { + default: "[stringAttr] = 'here is a null char: \\0'", + snowflake: '"stringAttr" = \'here is a null char: \0\'', + mssql: "[stringAttr] = N'here is a null char: \0'", + db2: '"stringAttr" = \'here is a null char: \0\'', + ibmi: '"stringAttr" = \'here is a null char: \0\'', + sqlite3: "`stringAttr` = 'here is a null char: \0'", + }, + ); + + testSql( + { + dateAttr: 1_356_998_400_000, + }, + { + default: `[dateAttr] = '2013-01-01 00:00:00.000 +00:00'`, + 'mariadb mysql': `\`dateAttr\` = '2013-01-01 00:00:00.000'`, + mssql: `[dateAttr] = N'2013-01-01 00:00:00.000 +00:00'`, + 'db2 snowflake ibmi': `"dateAttr" = '2013-01-01 00:00:00.000'`, + }, + ); + + describe('Buffer', () => { + testSql( + { binaryAttr: Buffer.from('Sequelize') }, + { + ibmi: `"binaryAttr" = BLOB(X'53657175656c697a65')`, + postgres: `"binaryAttr" = '\\x53657175656c697a65'`, + 'sqlite3 mariadb mysql': "`binaryAttr` = X'53657175656c697a65'", + db2: `"binaryAttr" = BLOB('Sequelize')`, + snowflake: `"binaryAttr" = X'53657175656c697a65'`, + mssql: '[binaryAttr] = 0x53657175656c697a65', + }, + ); + + // Including a quote (') to ensure dialects that don't convert to hex are safe from SQL injection. + testSql( + { binaryAttr: [Buffer.from(`Seque'lize1`), Buffer.from('Sequelize2')] }, + { + ibmi: `"binaryAttr" IN (BLOB(X'5365717565276c697a6531'), BLOB(X'53657175656c697a6532'))`, + postgres: `"binaryAttr" IN ('\\x5365717565276c697a6531', '\\x53657175656c697a6532')`, + 'sqlite3 mariadb mysql': + "`binaryAttr` IN (X'5365717565276c697a6531', X'53657175656c697a6532')", + db2: `"binaryAttr" IN (BLOB('Seque''lize1'), BLOB('Sequelize2'))`, + snowflake: `"binaryAttr" IN (X'5365717565276c697a6531', X'53657175656c697a6532')`, + mssql: '[binaryAttr] IN (0x5365717565276c697a6531, 0x53657175656c697a6532)', + }, + ); + }); + }); + + describe('implicit operator', () => { + testSql( + { intAttr1: 1 }, + { + default: '[intAttr1] = 1', + }, + ); + + testSql( + { stringAttr: '1' }, + { + default: `[stringAttr] = '1'`, + mssql: `[stringAttr] = N'1'`, + }, + ); + + testSql( + { intAttr1: [1, 2] }, + { + default: '[intAttr1] IN (1, 2)', + }, + ); + + testSql( + { stringAttr: ['1', '2'] }, + { + default: `[stringAttr] IN ('1', '2')`, + mssql: `[stringAttr] IN (N'1', N'2')`, + }, + ); + + testSql( + { intAttr1: ['not-an-int'] }, + { default: new Error(`'not-an-int' is not a valid integer`) }, + ); + + testSql( + { 'stringAttr::integer': 1 }, + { + default: 'CAST([stringAttr] AS INTEGER) = 1', + }, + ); + + testSql( + { $intAttr1$: 1 }, + { + default: '[intAttr1] = 1', + }, + ); + + testSql( + { '$stringAttr$::integer': 1 }, + { + default: 'CAST([stringAttr] AS INTEGER) = 1', + }, + ); + + testSql( + { '$association.attribute$': 1 }, + { + default: '[association].[attribute] = 1', + }, + ); + + testSql( + { '$association.attribute$::integer': 1 }, + { + default: 'CAST([association].[attribute] AS INTEGER) = 1', + }, + ); + + testSql( + { booleanAttr: true }, + { + default: `[booleanAttr] = true`, + mssql: '[booleanAttr] = 1', + sqlite3: '`booleanAttr` = 1', + ibmi: '"booleanAttr" = 1', + }, + ); + + testSql( + { + stringAttr: 'a project', + intAttr1: { + [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }], + }, + }, + { + default: "[stringAttr] = 'a project' AND ([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10)", + mssql: "[stringAttr] = N'a project' AND ([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10)", + }, + ); + + testSql( + { nullableIntAttr: null }, + { + default: '[nullableIntAttr] IS NULL', + }, + ); + + testSql( + { nullableIntAttr: SQL_NULL }, + { + default: '[nullableIntAttr] IS NULL', + }, + ); + + testSql( + { dateAttr: new Date('2021-01-01T00:00:00Z') }, + { + default: `[dateAttr] = '2021-01-01 00:00:00.000 +00:00'`, + mssql: `[dateAttr] = N'2021-01-01 00:00:00.000 +00:00'`, + 'mariadb mysql': `\`dateAttr\` = '2021-01-01 00:00:00.000'`, + 'db2 ibmi snowflake': `"dateAttr" = '2021-01-01 00:00:00.000'`, + }, + ); + + testSql( + { intAttr1: { [Op.col]: 'intAttr2' } }, + { + default: '[intAttr1] = [intAttr2]', + }, + ); + + testSql( + { intAttr1: col('intAttr2') }, + { + default: '[intAttr1] = [intAttr2]', + }, + ); + + testSql( + { intAttr1: literal('literal') }, + { + default: '[intAttr1] = literal', + }, + ); + + testSql( + { stringAttr: fn('UPPER', col('stringAttr')) }, + { + default: '[stringAttr] = UPPER([stringAttr])', + }, + ); + + testSql( + { stringAttr: fn('UPPER', { [Op.col]: 'stringAttr' }) }, + { + default: '[stringAttr] = UPPER([stringAttr])', + }, + ); + + testSql( + { stringAttr: cast(col('intAttr1'), 'string') }, + { + default: '[stringAttr] = CAST([intAttr1] AS STRING)', + }, + ); + + testSql( + { stringAttr: cast({ [Op.col]: 'intAttr1' }, 'string') }, + { + default: '[stringAttr] = CAST([intAttr1] AS STRING)', + }, + ); + + testSql( + { stringAttr: cast('abc', 'string') }, + { + default: `[stringAttr] = CAST('abc' AS STRING)`, + mssql: `[stringAttr] = CAST(N'abc' AS STRING)`, + }, + ); + + if (dialectSupportsArray()) { + testSql( + { intArrayAttr: [1, 2] }, + { + default: `[intArrayAttr] = ARRAY[1,2]`, + }, + ); + + testSql( + { intArrayAttr: [] }, + { + default: `[intArrayAttr] = ARRAY[]::INTEGER[]`, + }, + ); + + // when using arrays, Op.in is never included + testSql( + // @ts-expect-error -- Omitting the operator with an array attribute is always Op.eq, never Op.in + { intArrayAttr: [[1, 2]] }, + { default: new Error('[ 1, 2 ] is not a valid integer') }, + ); + + testSql( + { intAttr1: { [Op.any]: [2, 3, 4] } }, + { + default: '[intAttr1] = ANY (ARRAY[2,3,4])', + }, + ); + + testSql( + { intAttr1: { [Op.any]: literal('literal') } }, + { + default: '[intAttr1] = ANY (literal)', + }, + ); + + testSql( + { intAttr1: { [Op.any]: { [Op.values]: [col('col')] } } }, + { + default: '[intAttr1] = ANY (VALUES ([col]))', + }, + ); + + testSql( + { intAttr1: { [Op.all]: [2, 3, 4] } }, + { + default: '[intAttr1] = ALL (ARRAY[2,3,4])', + }, + ); + + testSql( + { intAttr1: { [Op.all]: literal('literal') } }, + { + default: '[intAttr1] = ALL (literal)', + }, + ); + + testSql( + { intAttr1: { [Op.all]: { [Op.values]: [col('col')] } } }, + { + default: '[intAttr1] = ALL (VALUES ([col]))', + }, + ); + + // e.g. "col" LIKE ANY (VALUES ("col2")) + testSql( + { + intAttr1: { + [Op.any]: { + [Op.values]: [ + literal('literal'), + fn('UPPER', col('col2')), + col('col3'), + cast(col('col'), 'string'), + 1, + ], + }, + }, + }, + { + default: `[intAttr1] = ANY (VALUES (literal), (UPPER([col2])), ([col3]), (CAST([col] AS STRING)), (1))`, + }, + ); + } + }); + + describe('Op.eq', () => { + testSql( + { intAttr1: { [Op.eq]: 1 } }, + { + default: '[intAttr1] = 1', + }, + ); + + testSql( + { 'intAttr1::integer': { [Op.eq]: 1 } }, + { + default: 'CAST([intAttr1] AS INTEGER) = 1', + }, + ); + + testSql( + { $intAttr1$: { [Op.eq]: 1 } }, + { + default: '[intAttr1] = 1', + }, + ); + + testSql( + { '$intAttr1$::integer': { [Op.eq]: 1 } }, + { + default: 'CAST([intAttr1] AS INTEGER) = 1', + }, + ); + + testSql( + { '$association.attribute$': { [Op.eq]: 1 } }, + { + default: '[association].[attribute] = 1', + }, + ); + + testSql( + { '$association.attribute$::integer': { [Op.eq]: 1 } }, + { + default: `CAST([association].[attribute] AS INTEGER) = 1`, + }, + ); + + if (dialectSupportsArray()) { + // @ts-expect-error -- intArrayAttr is not an array + const ignore: TestModelWhere = { intAttr1: { [Op.eq]: [1, 2] } }; + + testSql( + { intArrayAttr: { [Op.eq]: [1, 2] } }, + { + default: '[intArrayAttr] = ARRAY[1,2]', + }, + ); + } + + { + // @ts-expect-error -- intAttr1 is not nullable + const ignore: TestModelWhere = { intAttr1: { [Op.eq]: null } }; + + // this one is + testSql( + { nullableIntAttr: { [Op.eq]: null } }, + { + default: '[nullableIntAttr] IS NULL', + }, + ); + } + + testSql( + { booleanAttr: { [Op.eq]: true } }, + { + default: '[booleanAttr] = true', + 'mssql sqlite3 ibmi': '[booleanAttr] = 1', + }, + ); + + testSequelizeValueMethods(Op.eq, '='); + testSupportsAnyAll(Op.eq, '=', [2, 3, 4]); + }); + + describe('Op.ne', () => { + testSql( + { intAttr1: { [Op.ne]: 1 } }, + { + default: '[intAttr1] != 1', + }, + ); + + if (dialectSupportsArray()) { + testSql( + { intArrayAttr: { [Op.ne]: [1, 2] } }, + { + default: '[intArrayAttr] != ARRAY[1,2]', + }, + ); + } + + testSql( + { nullableIntAttr: { [Op.ne]: null } }, + { + default: '[nullableIntAttr] IS NOT NULL', + }, + ); + + testSql( + { booleanAttr: { [Op.ne]: true } }, + { + default: '[booleanAttr] != true', + 'mssql ibmi sqlite3': '[booleanAttr] != 1', + }, + ); + + testSequelizeValueMethods(Op.ne, '!='); + testSupportsAnyAll(Op.ne, '!=', [2, 3, 4]); + }); + + describe('Op.is', () => { + { + // @ts-expect-error -- intAttr is not nullable + const ignore: TestModelWhere = { intAttr: { [Op.is]: null } }; + } + + { + // @ts-expect-error -- stringAttr is not a boolean + const ignore: TestModelWhere = { stringAttr: { [Op.is]: true } }; + } + + testSql( + { nullableIntAttr: { [Op.is]: null } }, + { + default: '[nullableIntAttr] IS NULL', + }, + ); + + testSql( + { nullableIntAttr: { [Op.is]: SQL_NULL } }, + { + default: '[nullableIntAttr] IS NULL', + }, + ); + + testSql( + { booleanAttr: { [Op.is]: false } }, + { + default: '[booleanAttr] IS false', + 'mssql ibmi sqlite3': '[booleanAttr] IS 0', + }, + ); + + testSql( + { booleanAttr: { [Op.is]: true } }, + { + default: '[booleanAttr] IS true', + 'mssql ibmi sqlite3': '[booleanAttr] IS 1', + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: 1 } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: { [Op.col]: 'intAttr2' } } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: col('intAttr2') } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + testSql( + { intAttr1: { [Op.is]: literal('UNKNOWN') } }, + { + default: '[intAttr1] IS UNKNOWN', + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: fn('UPPER', col('intAttr2')) } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: cast(col('intAttr2'), 'boolean') } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + if (dialectSupportsArray()) { + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: { [Op.any]: [2, 3] } } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + + testSql( + // @ts-expect-error -- not supported, testing that it throws + { intAttr1: { [Op.is]: { [Op.all]: [2, 3, 4] } } }, + { + default: new Error( + 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.', + ), + }, + ); + } + }); + + describe('Op.isNot', () => { + testSql( + { nullableIntAttr: { [Op.isNot]: null } }, + { + default: '[nullableIntAttr] IS NOT NULL', + }, + ); + + testSql( + { booleanAttr: { [Op.isNot]: false } }, + { + default: '[booleanAttr] IS NOT false', + 'mssql ibmi sqlite3': '[booleanAttr] IS NOT 0', + }, + ); + + testSql( + { booleanAttr: { [Op.isNot]: true } }, + { + default: '[booleanAttr] IS NOT true', + 'mssql ibmi sqlite3': '[booleanAttr] IS NOT 1', + }, + ); + }); + + describe('Op.not', () => { + testSql( + { [Op.not]: {} }, + { + default: '', + }, + ); + + testSql( + { + [Op.not]: { + [Op.not]: {}, + }, + }, + { + default: '', + }, + ); + + testSql( + { [Op.not]: [] }, + { + default: '', + }, + ); + + testSql( + { nullableIntAttr: { [Op.not]: {} } }, + { + default: '', + }, + ); + + testSql( + { nullableIntAttr: { [Op.not]: null } }, + { + default: 'NOT ([nullableIntAttr] IS NULL)', + }, + ); + + testSql( + { booleanAttr: { [Op.not]: false } }, + { + default: 'NOT ([booleanAttr] = false)', + mssql: 'NOT ([booleanAttr] = 0)', + ibmi: 'NOT ("booleanAttr" = 0)', + sqlite3: 'NOT (`booleanAttr` = 0)', + }, + ); + + testSql( + { booleanAttr: { [Op.not]: true } }, + { + default: 'NOT ([booleanAttr] = true)', + mssql: 'NOT ([booleanAttr] = 1)', + ibmi: 'NOT ("booleanAttr" = 1)', + sqlite3: 'NOT (`booleanAttr` = 1)', + }, + ); + + testSql( + { intAttr1: { [Op.not]: 1 } }, + { + default: 'NOT ([intAttr1] = 1)', + }, + ); + + testSql( + { intAttr1: { [Op.not]: [1, 2] } }, + { + default: 'NOT ([intAttr1] IN (1, 2))', + }, + ); + + { + // @ts-expect-error -- not a valid query: attribute does not exist. + const ignore: TestModelWhere = { [Op.not]: { doesNotExist: 5 } }; + } + + testSql( + { [Op.not]: { intAttr1: 5 } }, + { + default: 'NOT ([intAttr1] = 5)', + }, + ); + + testSql( + { [Op.not]: { intAttr1: { [Op.gt]: 5 } } }, + { + default: 'NOT ([intAttr1] > 5)', + }, + ); + + testSql( + { [Op.not]: where(col('intAttr1'), Op.eq, '5') }, + { + default: `NOT ([intAttr1] = '5')`, + mssql: `NOT ([intAttr1] = N'5')`, + }, + ); + + if (dialectSupportsJsonOperations() && dialectSupportsJsonQuotedExtraction()) { + testSql( + { [Op.not]: json('data.key', 10) }, + { + postgres: `NOT ("data"->'key' = '10')`, + sqlite3: `NOT (json_extract(\`data\`,'$.key') = '10')`, + mariadb: `NOT (json_compact(json_extract(\`data\`,'$.key')) = '10')`, + mysql: `NOT (json_extract(\`data\`,'$.key') = CAST('10' AS JSON))`, + }, + ); + } + + testSql( + { intAttr1: { [Op.not]: { [Op.gt]: 5 } } }, + { + default: 'NOT ([intAttr1] > 5)', + }, + ); + }); + + function describeComparisonSuite( + operator: typeof Op.gt | typeof Op.gte | typeof Op.lt | typeof Op.lte, + sqlOperator: string, + ) { + // ensure gte, gt, lte, lt support the same typings, so we only have to test their typings once. + // unfortunately, at time of writing (TS 4.5.5), TypeScript + // does not detect an error in `{ [operator]: null }` + // but it does detect an error in { [Op.gt]: null }` + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + describe(`Op.${operator.description}`, () => { + { + const ignore: TestModelWhere = { intAttr1: { [Op.gt]: 1 } }; + testSql( + { intAttr1: { [operator]: 1 } }, + { + default: `[intAttr1] ${sqlOperator} 1`, + }, + ); + } + + { + const ignore: TestModelWhere = { stringAttr: { [Op.gt]: 'abc' } }; + testSql( + { stringAttr: { [operator]: 'abc' } }, + { + default: `[stringAttr] ${sqlOperator} 'abc'`, + mssql: `[stringAttr] ${sqlOperator} N'abc'`, + }, + ); + } + + if (dialectSupportsArray()) { + const ignore: TestModelWhere = { intArrayAttr: { [Op.gt]: [1, 2] } }; + testSql( + { intArrayAttr: { [operator]: [1, 2] } }, + { + default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2]`, + }, + ); + } + + expectTypeOf({ intAttr1: { [Op.gt]: null } }).not.toMatchTypeOf(); + testSql( + { intAttr1: { [operator]: null } }, + { + default: `[intAttr1] ${sqlOperator} NULL`, + }, + ); + + testSequelizeValueMethods(operator, sqlOperator); + testSupportsAnyAll(operator, sqlOperator, [2, 3, 4]); + }); + } + + describeComparisonSuite(Op.gt, '>'); + describeComparisonSuite(Op.gte, '>='); + describeComparisonSuite(Op.lt, '<'); + describeComparisonSuite(Op.lte, '<='); + + function describeBetweenSuite( + operator: typeof Op.between | typeof Op.notBetween, + sqlOperator: string, + ) { + // ensure between and notBetween support the same typings, so we only have to test their typings once. + // unfortunately, at time of writing (TS 4.5.5), TypeScript + // does not detect an error in `{ [operator]: null }` + // but it does detect an error in { [Op.gt]: null }` + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.notBetween] + >(); + + describe(`Op.${operator.description}`, () => { + expectTypeOf({ id: { [Op.between]: [1, 2] } }).toMatchTypeOf(); + expectTypeOf({ + id: { [Op.between]: [new Date(), new Date()] }, + }).toMatchTypeOf(); + expectTypeOf({ id: { [Op.between]: ['a', 'b'] } }).toMatchTypeOf(); + + // expectTypeOf doesn't work with this one: + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [1, 2] }, + }; + + testSql( + { intAttr1: { [operator]: [1, 2] } }, + { + default: `[intAttr1] ${sqlOperator} 1 AND 2`, + }, + ); + + // @ts-expect-error -- must pass exactly 2 items + const ignoreWrong: TestModelWhere = { intAttr1: { [Op.between]: [1, 2, 3] } }; + + // @ts-expect-error -- must pass exactly 2 items + const ignoreWrong2: TestModelWhere = { intAttr1: { [Op.between]: [1] } }; + + testSql( + { intAttr1: { [operator]: [1] } }, + { + default: new Error( + 'Operators Op.between and Op.notBetween must be used with an array of two values, or a literal.', + ), + }, + ); + + // @ts-expect-error -- must pass exactly 2 items + const ignoreWrong3: TestModelWhere = { intAttr1: { [Op.between]: [] } }; + } + + if (dialectSupportsArray()) { + { + const ignoreRight: TestModelWhere = { + intArrayAttr: { + [Op.between]: [ + [1, 2], + [3, 4], + ], + }, + }; + testSql( + { + intArrayAttr: { + [operator]: [ + [1, 2], + [3, 4], + ], + }, + }, + { + default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2] AND ARRAY[3,4]`, + }, + ); + } + + { + // @ts-expect-error -- this is not valid because intAttr1 is not an array and cannot be compared to arrays + const ignore: TestModelWhere = { + intAttr1: { + [Op.between]: [ + [1, 2], + [3, 4], + ], + }, + }; + } + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [col('col1'), col('col2')] }, + }; + testSql( + { intAttr1: { [operator]: [col('col1'), col('col2')] } }, + { + default: `[intAttr1] ${sqlOperator} [col1] AND [col2]`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [literal('literal1'), literal('literal2')] }, + }; + testSql( + { intAttr1: { [operator]: [literal('literal1'), literal('literal2')] } }, + { + default: `[intAttr1] ${sqlOperator} literal1 AND literal2`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [fn('NOW'), fn('NOW')] }, + }; + testSql( + { intAttr1: { [operator]: [fn('NOW'), fn('NOW')] } }, + { + default: `[intAttr1] ${sqlOperator} NOW() AND NOW()`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] }, + }; + testSql( + { intAttr1: { [operator]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] } }, + { + default: `[intAttr1] ${sqlOperator} [col1] AND [col2]`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: [cast(col('col'), 'string'), cast(col('col'), 'string')] }, + }; + testSql( + { intAttr1: { [operator]: [cast(col('col'), 'string'), cast(col('col'), 'string')] } }, + { + default: `[intAttr1] ${sqlOperator} CAST([col] AS STRING) AND CAST([col] AS STRING)`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.between]: literal('literal1 AND literal2') }, + }; + testSql( + { intAttr1: { [operator]: literal('literal1 AND literal2') } }, + { + default: `[intAttr1] ${sqlOperator} literal1 AND literal2`, + }, + ); + } + }); + } + + describeBetweenSuite(Op.between, 'BETWEEN'); + describeBetweenSuite(Op.notBetween, 'NOT BETWEEN'); + + function describeInSuite( + operator: typeof Op.in | typeof Op.notIn, + sqlOperator: string, + extraTests: () => void, + ): void { + // ensure between and notBetween support the same typings, so we only have to test their typings once. + // unfortunately, at time of writing (TS 4.5.5), TypeScript + // does not detect an error in `{ [operator]: null }` + // but it does detect an error in { [Op.gt]: null }` + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.notBetween] + >(); + + describe(`Op.${operator.description}`, () => { + { + const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [1, 2, 3] } }; + testSql( + { intAttr1: { [operator]: [1, 2, 3] } }, + { + default: `[intAttr1] ${sqlOperator} (1, 2, 3)`, + }, + ); + } + + if (dialectSupportsArray()) { + { + // valid + const ignore: TestModelWhere = { + intArrayAttr: { + [Op.in]: [ + [1, 2], + [3, 4], + ], + }, + }; + testSql( + { + intArrayAttr: { + [operator]: [ + [1, 2], + [3, 4], + ], + }, + }, + { + default: `[intArrayAttr] ${sqlOperator} (ARRAY[1,2], ARRAY[3,4])`, + }, + ); + } + + { + // @ts-expect-error -- intAttr1 is not an array + const ignore: TestModelWhere = { + intAttr1: { + [Op.in]: [ + [1, 2], + [3, 4], + ], + }, + }; + testSql( + { + intArrayAttr: { + [operator]: [ + [1, 2], + [3, 4], + ], + }, + }, + { + default: `[intArrayAttr] ${sqlOperator} (ARRAY[1,2], ARRAY[3,4])`, + }, + ); + } + } + + { + // @ts-expect-error -- this is invalid because intAttr1 is not an array and cannot be compared to arrays. + const ignore: TestModelWhere = { + intAttr1: { + [Op.in]: [ + [1, 2], + [3, 4], + ], + }, + }; + } + + { + // @ts-expect-error -- not supported, testing that it throws + const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: 1 } }; + testSql( + { intAttr1: { [operator]: 1 } }, + { + default: new Error( + 'Operators Op.in and Op.notIn must be called with an array of values, or a literal', + ), + }, + ); + } + + { + // @ts-expect-error -- not supported, testing that it throws + const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: col('col2') } }; + testSql( + { intAttr1: { [operator]: col('col1') } }, + { + default: new Error( + 'Operators Op.in and Op.notIn must be called with an array of values, or a literal', + ), + }, + ); + } + + { + const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [col('col1'), col('col2')] } }; + testSql( + { intAttr1: { [operator]: [col('col1'), col('col2')] } }, + { + default: `[intAttr1] ${sqlOperator} ([col1], [col2])`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.in]: [literal('literal1'), literal('literal2')] }, + }; + testSql( + { intAttr1: { [operator]: [literal('literal1'), literal('literal2')] } }, + { + default: `[intAttr1] ${sqlOperator} (literal1, literal2)`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [fn('NOW'), fn('NOW')] } }; + testSql( + { intAttr1: { [operator]: [fn('NOW'), fn('NOW')] } }, + { + default: `[intAttr1] ${sqlOperator} (NOW(), NOW())`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.in]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] }, + }; + testSql( + { intAttr1: { [operator]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] } }, + { + default: `[intAttr1] ${sqlOperator} ([col1], [col2])`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intAttr1: { [Op.in]: [cast(col('col'), 'string'), cast(col('col'), 'string')] }, + }; + testSql( + { intAttr1: { [operator]: [cast(col('col'), 'string'), cast(col('col'), 'string')] } }, + { + default: `[intAttr1] ${sqlOperator} (CAST([col] AS STRING), CAST([col] AS STRING))`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: literal('literal') } }; + testSql( + { intAttr1: { [operator]: literal('literal') } }, + { + default: `[intAttr1] ${sqlOperator} literal`, + }, + ); + } + + { + // @ts-expect-error -- Op.all is not compatible with Op.in + const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: { [Op.all]: [] } } }; + } + + extraTests(); + }); + } + + describeInSuite(Op.in, 'IN', () => { + testSql( + { intAttr1: { [Op.in]: [] } }, + { + default: '[intAttr1] IN (NULL)', + }, + ); + }); + + describeInSuite(Op.notIn, 'NOT IN', () => { + testSql( + { intAttr1: { [Op.notIn]: [] } }, + { + default: '', + }, + ); + }); + + function describeLikeSuite( + operator: typeof Op.like | typeof Op.notLike | typeof Op.iLike | typeof Op.notILike, + sqlOperator: string, + ) { + // ensure like ops support the same typings, so we only have to test their typings once. + // unfortunately, at time of writing (TS 4.5.5), TypeScript + // does not detect an error in `{ [operator]: null }` + // but it does detect an error in { [Op.iLike]: null }` + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.like] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.like] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.like] + >(); + + describe(`Op.${operator.description}`, () => { + expectTypeOf({ stringAttr: { [Op.like]: '%id' } }).toMatchTypeOf(); + testSql( + { stringAttr: { [operator]: '%id' } }, + { + default: `[stringAttr] ${sqlOperator} '%id'`, + mssql: `[stringAttr] ${sqlOperator} N'%id'`, + }, + ); + + // This test checks that the right data type is used to stringify the right operand + testSql( + { 'intAttr1::text': { [operator]: '%id' } }, + { + default: `CAST([intAttr1] AS TEXT) ${sqlOperator} '%id'`, + mssql: `CAST([intAttr1] AS TEXT) ${sqlOperator} N'%id'`, + }, + ); + + testSequelizeValueMethods(operator, sqlOperator); + testSupportsAnyAll(operator, sqlOperator, ['a', 'b', 'c'], 'stringAttr'); + }); + } + + describeLikeSuite(Op.like, 'LIKE'); + describeLikeSuite(Op.notLike, 'NOT LIKE'); + describeLikeSuite(Op.iLike, 'ILIKE'); + describeLikeSuite(Op.notILike, 'NOT ILIKE'); + + function describeOverlapSuite( + operator: typeof Op.overlap | typeof Op.contains | typeof Op.contained, + sqlOperator: string, + ) { + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.overlap] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.overlap] + >(); + + if (dialectSupportsArray()) { + describe(`Op.${operator.description} on ARRAY`, () => { + { + const ignoreRight: TestModelWhere = { intArrayAttr: { [Op.overlap]: [1, 2, 3] } }; + testSql( + { intArrayAttr: { [operator]: [1, 2, 3] } }, + { + default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2,3]`, + }, + ); + } + + testSequelizeValueMethods(operator, sqlOperator); + // ARRAY Overlap ARRAY doesn't support ANY or ALL, except with VALUES + // testSupportsAnyAll(operator, sqlOperator, [[1, 2], [1, 2]]); + + { + const ignore: TestModelWhere = { + // @ts-expect-error -- cannot compare an array with a range! + intArrayAttr: { [Op.overlap]: [1, { value: 2, inclusive: true }] }, + }; + testSql( + { intArrayAttr: { [operator]: [1, { value: 2, inclusive: true }] } }, + { + default: new Error('{ value: 2, inclusive: true } is not a valid integer'), + }, + ); + } + + { + // @ts-expect-error -- not supported, testing that it throws + const ignoreWrong: TestModelWhere = { intArrayAttr: { [Op.overlap]: [col('col')] } }; + testSql( + { intArrayAttr: { [operator]: [col('col')] } }, + { + default: new Error(`Col { identifiers: [ 'col' ] } is not a valid integer`), + }, + ); + } + + { + const ignoreWrong: TestModelWhere = { + // @ts-expect-error -- not supported, testing that it throws + intArrayAttr: { [Op.overlap]: [{ [Op.col]: 'col' }] }, + }; + testSql( + { intArrayAttr: { [operator]: [{ [Op.col]: 'col' }] } }, + { + default: new Error(`{ [Symbol(col)]: 'col' } is not a valid integer`), + }, + ); + } + + { + // @ts-expect-error -- not supported, testing that it throws + const ignoreWrong: TestModelWhere = { + intArrayAttr: { [Op.overlap]: [literal('literal')] }, + }; + testSql( + { intArrayAttr: { [operator]: [literal('literal')] } }, + { + default: new Error(`Literal { val: [ 'literal' ] } is not a valid integer`), + }, + ); + } + }); + } + + if (dialectSupportsRange()) { + describe(`Op.${operator.description} on RANGE`, () => { + { + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2] } }; + testSql( + { intRangeAttr: { [operator]: [1, 2] } }, + { + default: `[intRangeAttr] ${sqlOperator} '[1,2)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { [Op.overlap]: [1, { value: 2, inclusive: true }] }, + }; + testSql( + { intRangeAttr: { [operator]: [1, { value: 2, inclusive: true }] } }, + { + // used 'postgres' because otherwise range is transformed to "1,2" + postgres: `"intRangeAttr" ${sqlOperator} '[1,2]'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { [Op.overlap]: [{ value: 1, inclusive: false }, 2] }, + }; + testSql( + { intRangeAttr: { [operator]: [{ value: 1, inclusive: false }, 2] } }, + { + default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { + [Op.overlap]: [ + { value: 1, inclusive: false }, + { value: 2, inclusive: false }, + ], + }, + }; + testSql( + { + intRangeAttr: { + [operator]: [ + { value: 1, inclusive: false }, + { value: 2, inclusive: false }, + ], + }, + }, + { + default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`, + }, + ); + } + + { + // unbounded range (right) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [10, null] } }; + testSql( + { + intRangeAttr: { [operator]: [10, null] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[10,)'::int4range`, + }, + ); + } + + { + // unbounded range (left) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [null, 10] } }; + testSql( + { + intRangeAttr: { [operator]: [null, 10] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[,10)'::int4range`, + }, + ); + } + + { + // unbounded range (left) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [null, null] } }; + testSql( + { + intRangeAttr: { [operator]: [null, null] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[,)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + dateRangeAttr: { [Op.overlap]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY] }, + }; + + testSql( + { + dateRangeAttr: { + [operator]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + }, + }, + { + postgres: `"dateRangeAttr" ${sqlOperator} '[-infinity,infinity)'::tstzrange`, + }, + ); + } + + { + // empty range + const ignoreRight: TestModelWhere = { dateRangeAttr: { [Op.overlap]: [] } }; + + testSql( + { + dateRangeAttr: { [operator]: [] }, + }, + { + postgres: `"dateRangeAttr" ${sqlOperator} 'empty'::tstzrange`, + }, + ); + } + + { + // @ts-expect-error -- 'intRangeAttr' is a range, but right-hand side is a regular Array + const ignore: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2, 3] } }; + testSql( + { intRangeAttr: { [operator]: [1, 2, 3] } }, + { + default: new Error( + 'A range must either be an array with two elements, or an empty array for the empty range. Got [ 1, 2, 3 ].', + ), + }, + ); + } + + testSequelizeValueMethods(operator, sqlOperator); + testSupportsAnyAll(operator, sqlOperator, [1, 2]); + }); + } + } + + describeOverlapSuite(Op.overlap, '&&'); + describeOverlapSuite(Op.contains, '@>'); + + if (dialectSupportsRange()) { + describe('RANGE Op.contains ELEMENT', () => { + testSql( + { + intRangeAttr: { [Op.contains]: 1 }, + }, + { + postgres: `"intRangeAttr" @> 1`, + }, + ); + + testSql( + // @ts-expect-error -- `ARRAY Op.contains ELEMENT` is not a valid query + { intArrayAttr: { [Op.contains]: 1 } }, + { + default: new Error('1 is not a valid array'), + }, + ); + }); + } + + describeOverlapSuite(Op.contained, '<@'); + + describe('ELEMENT Op.contained RANGE', () => { + if (!dialectSupportsRange()) { + return; + } + + testSql( + { + intAttr1: { [Op.contained]: [1, 2] }, + }, + { + postgres: `"intAttr1" <@ '[1,2)'::int4range`, + }, + ); + + testSql( + { + bigIntAttr: { [Op.contained]: [1, 2] }, + }, + { + postgres: `"bigIntAttr" <@ '[1,2)'::int8range`, + }, + ); + + testSql( + { + dateAttr: { + [Op.contained]: [new Date('2020-01-01T00:00:00Z'), new Date('2021-01-01T00:00:00Z')], + }, + }, + { + postgres: `"dateAttr" <@ '[2020-01-01 00:00:00.000 +00:00,2021-01-01 00:00:00.000 +00:00)'::tstzrange`, + }, + ); + + /* + TODO: + numrange — Range of numeric + tsrange — Range of timestamp without time zone + daterange — Range of date + */ + }); + + describe('Op.startsWith', () => { + // TODO: use implementation not based on "LIKE" + // mysql, mariadb: locate() + // postgres:, ^@ + // snowflake, ibmi, db2: position() + // mssql: CHARINDEX() + // sqlite3: INSTR() + + testSql( + { + stringAttr: { + [Op.startsWith]: 'swagger', + }, + }, + { + default: `[stringAttr] LIKE 'swagger%'`, + mssql: `[stringAttr] LIKE N'swagger%'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: "sql'injection", + }, + }, + { + default: `[stringAttr] LIKE 'sql''injection%'`, + mysql: `\`stringAttr\` LIKE 'sql\\'injection%'`, + mariadb: `\`stringAttr\` LIKE 'sql\\'injection%'`, + mssql: `[stringAttr] LIKE N'sql''injection%'`, + }, + ); + + // startsWith should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.startsWith]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] LIKE 'sql\%injection%' ESCAPE '\'`, + mssql: String.raw`[stringAttr] LIKE N'sql\%injection%' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: literal('$bind'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT($bind, '%')`, + mssql: `[stringAttr] LIKE CONCAT($bind, N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: col('username'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT([username], '%')`, + mssql: `[stringAttr] LIKE CONCAT([username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] LIKE CONCAT([username], '%')`, + mssql: `[stringAttr] LIKE CONCAT([username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: fn('NOW'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT(NOW(), '%')`, + mssql: `[stringAttr] LIKE CONCAT(NOW(), N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.startsWith]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT(CAST(NOW() AS STRING), '%')`, + mssql: `[stringAttr] LIKE CONCAT(CAST(NOW() AS STRING), N'%')`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPe is '\') + + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.any + { stringAttr: { [Op.startsWith]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.all + { stringAttr: { [Op.startsWith]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + describe('Op.endsWith', () => { + testSql( + { + stringAttr: { + [Op.endsWith]: 'swagger', + }, + }, + { + default: `[stringAttr] LIKE '%swagger'`, + mssql: `[stringAttr] LIKE N'%swagger'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: "sql'injection", + }, + }, + { + default: `[stringAttr] LIKE '%sql''injection'`, + mysql: `\`stringAttr\` LIKE '%sql\\'injection'`, + mariadb: `\`stringAttr\` LIKE '%sql\\'injection'`, + mssql: `[stringAttr] LIKE N'%sql''injection'`, + }, + ); + + // endsWith should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.endsWith]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] LIKE '%sql\%injection' ESCAPE '\'`, + mssql: String.raw`[stringAttr] LIKE N'%sql\%injection' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: literal('$bind'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', $bind)`, + mssql: `[stringAttr] LIKE CONCAT(N'%', $bind)`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: col('username'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', [username])`, + mssql: `[stringAttr] LIKE CONCAT(N'%', [username])`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', [username])`, + mssql: `[stringAttr] LIKE CONCAT(N'%', [username])`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: fn('NOW'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', NOW())`, + mssql: `[stringAttr] LIKE CONCAT(N'%', NOW())`, + }, + ); + + testSql( + { + stringAttr: { + [Op.endsWith]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', CAST(NOW() AS STRING))`, + mssql: `[stringAttr] LIKE CONCAT(N'%', CAST(NOW() AS STRING))`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\') + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.any + { stringAttr: { [Op.endsWith]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.all + { stringAttr: { [Op.endsWith]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + describe('Op.substring', () => { + // TODO: use implementation not based on "LIKE" + // mysql, mariadb: locate() + // postgres:, position() + // snowflake, ibmi, db2: position() + // mssql: CHARINDEX() + // sqlite3: INSTR() + + testSql( + { + stringAttr: { + [Op.substring]: 'swagger', + }, + }, + { + default: `[stringAttr] LIKE '%swagger%'`, + mssql: `[stringAttr] LIKE N'%swagger%'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: "sql'injection", + }, + }, + { + default: `[stringAttr] LIKE '%sql''injection%'`, + mysql: `\`stringAttr\` LIKE '%sql\\'injection%'`, + mariadb: `\`stringAttr\` LIKE '%sql\\'injection%'`, + mssql: `[stringAttr] LIKE N'%sql''injection%'`, + }, + ); + + // substring should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.substring]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] LIKE '%sql\%injection%' ESCAPE '\'`, + mssql: String.raw`[stringAttr] LIKE N'%sql\%injection%' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: literal('$bind'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', $bind, '%')`, + mssql: `[stringAttr] LIKE CONCAT(N'%', $bind, N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: col('username'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', [username], '%')`, + mssql: `[stringAttr] LIKE CONCAT(N'%', [username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', [username], '%')`, + mssql: `[stringAttr] LIKE CONCAT(N'%', [username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: fn('NOW'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', NOW(), '%')`, + mssql: `[stringAttr] LIKE CONCAT(N'%', NOW(), N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.substring]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] LIKE CONCAT('%', CAST(NOW() AS STRING), '%')`, + mssql: `[stringAttr] LIKE CONCAT(N'%', CAST(NOW() AS STRING), N'%')`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\') + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.any + { stringAttr: { [Op.substring]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- startsWith is not compatible with Op.all + { stringAttr: { [Op.substring]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + describe('Op.notStartsWith', () => { + testSql( + { + stringAttr: { + [Op.notStartsWith]: 'swagger', + }, + }, + { + default: `[stringAttr] NOT LIKE 'swagger%'`, + mssql: `[stringAttr] NOT LIKE N'swagger%'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: "sql'injection", + }, + }, + { + default: `[stringAttr] NOT LIKE 'sql''injection%'`, + mysql: `\`stringAttr\` NOT LIKE 'sql\\'injection%'`, + mariadb: `\`stringAttr\` NOT LIKE 'sql\\'injection%'`, + mssql: `[stringAttr] NOT LIKE N'sql''injection%'`, + }, + ); + + // startsWith should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.notStartsWith]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] NOT LIKE 'sql\%injection%' ESCAPE '\'`, + mssql: String.raw`[stringAttr] NOT LIKE N'sql\%injection%' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: literal('$bind'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT($bind, '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT($bind, N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: col('username'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT([username], '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT([username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT([username], '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT([username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: fn('NOW'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT(NOW(), '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(NOW(), N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notStartsWith]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT(CAST(NOW() AS STRING), '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(CAST(NOW() AS STRING), N'%')`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPe is '\') + testSql( + // @ts-expect-error -- notStartsWith is not compatible with Op.any + { stringAttr: { [Op.notStartsWith]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- notStartsWith is not compatible with Op.all + { stringAttr: { [Op.notStartsWith]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + describe('Op.notEndsWith', () => { + testSql( + { + stringAttr: { + [Op.notEndsWith]: 'swagger', + }, + }, + { + default: `[stringAttr] NOT LIKE '%swagger'`, + mssql: `[stringAttr] NOT LIKE N'%swagger'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: "sql'injection", + }, + }, + { + default: `[stringAttr] NOT LIKE '%sql''injection'`, + mysql: `\`stringAttr\` NOT LIKE '%sql\\'injection'`, + mariadb: `\`stringAttr\` NOT LIKE '%sql\\'injection'`, + mssql: `[stringAttr] NOT LIKE N'%sql''injection'`, + }, + ); + + // notEndsWith should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.notEndsWith]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] NOT LIKE '%sql\%injection' ESCAPE '\'`, + mssql: String.raw`[stringAttr] NOT LIKE N'%sql\%injection' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: literal('$bind'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', $bind)`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', $bind)`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: col('username'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', [username])`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username])`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', [username])`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username])`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: fn('NOW'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', NOW())`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', NOW())`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notEndsWith]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', CAST(NOW() AS STRING))`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', CAST(NOW() AS STRING))`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\') + testSql( + // @ts-expect-error -- notEndsWith is not compatible with Op.any + { stringAttr: { [Op.notEndsWith]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- notEndsWith is not compatible with Op.all + { stringAttr: { [Op.notEndsWith]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + describe('Op.notSubstring', () => { + testSql( + { + stringAttr: { + [Op.notSubstring]: 'swagger', + }, + }, + { + default: `[stringAttr] NOT LIKE '%swagger%'`, + mssql: `[stringAttr] NOT LIKE N'%swagger%'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: "sql'injection", + }, + }, + { + default: `[stringAttr] NOT LIKE '%sql''injection%'`, + mysql: `\`stringAttr\` NOT LIKE '%sql\\'injection%'`, + mariadb: `\`stringAttr\` NOT LIKE '%sql\\'injection%'`, + mssql: `[stringAttr] NOT LIKE N'%sql''injection%'`, + }, + ); + + // notSubstring should escape anything that has special meaning in LIKE + testSql.skip( + { + stringAttr: { + [Op.notSubstring]: 'like%injection', + }, + }, + { + default: String.raw`[stringAttr] NOT LIKE '%sql\%injection%' ESCAPE '\'`, + mssql: String.raw`[stringAttr] NOT LIKE N'%sql\%injection%' ESCAPE '\'`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: literal('$bind'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', $bind, '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', $bind, N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: col('username'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', [username], '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: { [Op.col]: 'username' }, + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', [username], '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username], N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: fn('NOW'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', NOW(), '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', NOW(), N'%')`, + }, + ); + + testSql( + { + stringAttr: { + [Op.notSubstring]: cast(fn('NOW'), 'string'), + }, + }, + { + default: `[stringAttr] NOT LIKE CONCAT('%', CAST(NOW() AS STRING), '%')`, + mssql: `[stringAttr] NOT LIKE CONCAT(N'%', CAST(NOW() AS STRING), N'%')`, + }, + ); + + // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\') + testSql( + // @ts-expect-error -- notSubstring is not compatible with Op.any + { stringAttr: { [Op.notSubstring]: { [Op.any]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + + testSql( + // @ts-expect-error -- notSubstring is not compatible with Op.all + { stringAttr: { [Op.notSubstring]: { [Op.all]: ['test'] } } }, + { + default: new Error( + `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`, + ), + }, + ); + }); + + function describeRegexpSuite( + operator: typeof Op.regexp | typeof Op.iRegexp | typeof Op.notRegexp | typeof Op.notIRegexp, + sqlOperator: string, + ) { + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.regexp] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.regexp] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.regexp] + >(); + + describe(`Op.${operator.description}`, () => { + { + const ignore: TestModelWhere = { stringAttr: { [Op.regexp]: '^sw.*r$' } }; + } + + testSql( + { stringAttr: { [operator]: '^sw.*r$' } }, + { + default: `[stringAttr] ${sqlOperator} '^sw.*r$'`, + }, + ); + + testSql( + { stringAttr: { [operator]: '^new\nline$' } }, + { + default: `[stringAttr] ${sqlOperator} '^new\nline$'`, + mariadb: `\`stringAttr\` ${sqlOperator} '^new\\nline$'`, + mysql: `\`stringAttr\` ${sqlOperator} '^new\\nline$'`, + }, + ); + + testSequelizeValueMethods(operator, sqlOperator); + testSupportsAnyAll(operator, sqlOperator, ['^a$', '^b$'], 'stringAttr'); + }); + } + + if (sequelize.dialect.supports.REGEXP) { + describeRegexpSuite(Op.regexp, sequelize.dialect.name === 'postgres' ? '~' : 'REGEXP'); + describeRegexpSuite( + Op.notRegexp, + sequelize.dialect.name === 'postgres' ? '!~' : 'NOT REGEXP', + ); + } + + if (sequelize.dialect.supports.IREGEXP) { + describeRegexpSuite(Op.iRegexp, '~*'); + describeRegexpSuite(Op.notIRegexp, '!~*'); + } + + if (sequelize.dialect.supports.dataTypes.TSVECTOR) { + describe('Op.match', () => { + testSql( + { stringAttr: { [Op.match]: fn('to_tsvector', 'swagger') } }, + { + default: `[stringAttr] @@ to_tsvector('swagger')`, + }, + ); + + testSequelizeValueMethods(Op.match, '@@'); + // TODO + // testSupportsAnyAll(Op.match, '@@', [fn('to_tsvector', 'a'), fn('to_tsvector', 'b')]); + }); + } + + function describeAdjacentRangeSuite( + operator: + | typeof Op.adjacent + | typeof Op.strictLeft + | typeof Op.strictRight + | typeof Op.noExtendLeft + | typeof Op.noExtendRight, + sqlOperator: string, + ) { + if (!dialectSupportsRange()) { + return; + } + + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.adjacent] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.adjacent] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.adjacent] + >(); + expectTypeOf().toEqualTypeOf< + WhereOperators[typeof Op.adjacent] + >(); + + describe(`RANGE Op.${operator.description} RANGE`, () => { + { + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [1, 2] } }; + testSql( + { intRangeAttr: { [operator]: [1, 2] } }, + { + default: `[intRangeAttr] ${sqlOperator} '[1,2)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { [Op.adjacent]: [1, { value: 2, inclusive: true }] }, + }; + testSql( + { intRangeAttr: { [operator]: [1, { value: 2, inclusive: true }] } }, + { + // used 'postgres' because otherwise range is transformed to "1,2" + postgres: `"intRangeAttr" ${sqlOperator} '[1,2]'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { [Op.adjacent]: [{ value: 1, inclusive: false }, 2] }, + }; + testSql( + { intRangeAttr: { [operator]: [{ value: 1, inclusive: false }, 2] } }, + { + default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + intRangeAttr: { + [Op.adjacent]: [ + { value: 1, inclusive: false }, + { value: 2, inclusive: false }, + ], + }, + }; + testSql( + { + intRangeAttr: { + [operator]: [ + { value: 1, inclusive: false }, + { value: 2, inclusive: false }, + ], + }, + }, + { + default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`, + }, + ); + } + + { + // unbounded range (right) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [10, null] } }; + testSql( + { + intRangeAttr: { [operator]: [10, null] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[10,)'::int4range`, + }, + ); + } + + { + // unbounded range (left) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [null, 10] } }; + testSql( + { + intRangeAttr: { [operator]: [null, 10] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[,10)'::int4range`, + }, + ); + } + + { + // unbounded range (left) + const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [null, null] } }; + testSql( + { + intRangeAttr: { [operator]: [null, null] }, + }, + { + postgres: `"intRangeAttr" ${sqlOperator} '[,)'::int4range`, + }, + ); + } + + { + const ignoreRight: TestModelWhere = { + dateRangeAttr: { [Op.adjacent]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY] }, + }; + + testSql( + { + dateRangeAttr: { + [operator]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + }, + }, + { + postgres: `"dateRangeAttr" ${sqlOperator} '[-infinity,infinity)'::tstzrange`, + }, + ); + } + + { + // empty range + const ignoreRight: TestModelWhere = { dateRangeAttr: { [Op.adjacent]: [] } }; + + testSql( + { + dateRangeAttr: { [operator]: [] }, + }, + { + postgres: `"dateRangeAttr" ${sqlOperator} 'empty'::tstzrange`, + }, + ); + } + + { + // @ts-expect-error -- 'intRangeAttr' is a range, but right-hand side is a regular Array + const ignore: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2, 3] } }; + testSql( + { intRangeAttr: { [operator]: [1, 2, 3] } }, + { + default: new Error( + 'A range must either be an array with two elements, or an empty array for the empty range. Got [ 1, 2, 3 ].', + ), + }, + ); + } + }); + } + + describeAdjacentRangeSuite(Op.adjacent, '-|-'); + describeAdjacentRangeSuite(Op.strictLeft, '<<'); + describeAdjacentRangeSuite(Op.strictRight, '>>'); + describeAdjacentRangeSuite(Op.noExtendLeft, '&>'); + describeAdjacentRangeSuite(Op.noExtendRight, '&<'); + + if (sequelize.dialect.supports.jsonOperations) { + describe('JSON Operations', () => { + { + // @ts-expect-error -- attribute 'doesNotExist' does not exist. + const ignore: TestModelWhere = { 'doesNotExist.nested': 'value' }; + } + + { + // @ts-expect-error -- attribute 'doesNotExist' does not exist. + const ignore: TestModelWhere = { '$doesNotExist$.nested': 'value' }; + } + + testSql( + { jsonAttr: 'value' }, + { + default: `[jsonAttr] = '"value"'`, + mysql: `\`jsonAttr\` = CAST('"value"' AS JSON)`, + mssql: `[jsonAttr] = N'"value"'`, + }, + ); + + testSql( + { jsonAttr: null }, + { + default: new Error('You must be explicit'), + }, + ); + + testSql( + { jsonAttr: { [Op.eq]: null } }, + { + default: `[jsonAttr] = 'null'`, + mysql: `\`jsonAttr\` = CAST('null' AS JSON)`, + mssql: `[jsonAttr] = N'null'`, + }, + ); + + testSql( + { jsonAttr: { [Op.is]: null } }, + { + default: `[jsonAttr] IS NULL`, + }, + ); + + testSql( + { jsonAttr: JSON_NULL }, + { + default: `[jsonAttr] = 'null'`, + mysql: `\`jsonAttr\` = CAST('null' AS JSON)`, + mssql: `[jsonAttr] = N'null'`, + }, + ); + + testSql( + { jsonAttr: SQL_NULL }, + { + default: `[jsonAttr] IS NULL`, + }, + ); + + if (dialectSupportsJsonQuotedExtraction()) { + testSql( + { 'jsonAttr.nested': 'value' }, + { + postgres: `"jsonAttr"->'nested' = '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { 'jsonAttr.nested': null }, + { + default: new Error('You must be explicit'), + }, + ); + + testSql( + { 'jsonAttr.nested': JSON_NULL }, + { + postgres: `"jsonAttr"->'nested' = 'null'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = 'null'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = 'null'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('null' AS JSON)`, + }, + ); + + testSql( + { 'jsonAttr.nested': SQL_NULL }, + { + postgres: `"jsonAttr"->'nested' IS NULL`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) IS NULL`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`, + }, + ); + + testSql( + { 'jsonAttr.nested': { [Op.eq]: null } }, + { + postgres: `"jsonAttr"->'nested' = 'null'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = 'null'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = 'null'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('null' AS JSON)`, + }, + ); + + testSql( + { 'jsonAttr.nested': { [Op.is]: null } }, + { + postgres: `"jsonAttr"->'nested' IS NULL`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) IS NULL`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`, + }, + ); + + testSql(where('value', Op.eq, attribute('jsonAttr.nested')), { + postgres: `'"value"' = "jsonAttr"->'nested'`, + sqlite3: `'"value"' = json_extract(\`jsonAttr\`,'$.nested')`, + mariadb: `'"value"' = json_compact(json_extract(\`jsonAttr\`,'$.nested'))`, + mysql: `CAST('"value"' AS JSON) = json_extract(\`jsonAttr\`,'$.nested')`, + }); + + testSql( + { 'jsonAttr.nested.twice': 'value' }, + { + postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + jsonAttr: { nested: 'value' }, + }, + { + postgres: `"jsonAttr"->'nested' = '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + 'jsonAttr.nested': { twice: 'value' }, + }, + { + postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + jsonAttr: { [Op.eq]: { key: 'value' } }, + }, + { + default: `[jsonAttr] = '{"key":"value"}'`, + mysql: `\`jsonAttr\` = CAST('{"key":"value"}' AS JSON)`, + }, + ); + + testSql( + { + 'jsonAttr.nested': { [Op.ne]: 'value' }, + }, + { + postgres: `"jsonAttr"->'nested' != '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') != '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) != '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') != CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + '$jsonAttr$.nested': 'value', + }, + { + postgres: `"jsonAttr"->'nested' = '"value"'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + '$association.jsonAttr$.nested': 'value', + }, + { + postgres: `"association"."jsonAttr"->'nested' = '"value"'`, + sqlite3: `json_extract(\`association\`.\`jsonAttr\`,'$.nested') = '"value"'`, + mariadb: `json_compact(json_extract(\`association\`.\`jsonAttr\`,'$.nested')) = '"value"'`, + mysql: `json_extract(\`association\`.\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`, + }, + ); + + testSql( + { + 'jsonAttr.nested::STRING': 'value', + }, + { + // with the left value cast to a string, we serialize the right value as a string, not as a JSON value + postgres: `CAST("jsonAttr"->'nested' AS STRING) = 'value'`, + mariadb: `CAST(json_compact(json_extract(\`jsonAttr\`,'$.nested')) AS STRING) = 'value'`, + 'sqlite3 mysql': `CAST(json_extract(\`jsonAttr\`,'$.nested') AS STRING) = 'value'`, + }, + ); + + testSql( + { + '$association.jsonAttr$.nested::STRING': { + attribute: 'value', + }, + }, + { + default: new Error(`Could not guess type of value { attribute: 'value' }`), + }, + ); + + testSql( + { + '$association.jsonAttr$.nested.deep::STRING': 'value', + }, + { + postgres: `CAST("association"."jsonAttr"#>ARRAY['nested','deep']::VARCHAR(255)[] AS STRING) = 'value'`, + mariadb: `CAST(json_compact(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep')) AS STRING) = 'value'`, + 'sqlite3 mysql': `CAST(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep') AS STRING) = 'value'`, + }, + ); + + testSql( + { + $jsonAttr$: { 'nested::string': 'value' }, + }, + { + postgres: `CAST("jsonAttr"->'nested' AS STRING) = 'value'`, + mariadb: `CAST(json_compact(json_extract(\`jsonAttr\`,'$.nested')) AS STRING) = 'value'`, + 'sqlite3 mysql': `CAST(json_extract(\`jsonAttr\`,'$.nested') AS STRING) = 'value'`, + }, + ); + + testSql( + { 'jsonAttr.nested.attribute': 4 }, + { + postgres: `"jsonAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`, + sqlite3: `json_extract(\`jsonAttr\`,'$.nested.attribute') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.attribute')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$.nested.attribute') = CAST('4' AS JSON)`, + }, + ); + + // 0 is treated as a string key here, not an array index + testSql( + { 'jsonAttr.0': 4 }, + { + postgres: `"jsonAttr"->'0' = '4'`, + sqlite3: `json_extract(\`jsonAttr\`,'$."0"') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."0"')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$."0"') = CAST('4' AS JSON)`, + }, + ); + + // 0 is treated as an index here, not a string key + testSql( + { 'jsonAttr[0]': 4 }, + { + postgres: `"jsonAttr"->0 = '4'`, + + // these tests cannot be deduplicated because [0] will be replaced by `0` by expectsql + sqlite3: `json_extract(\`jsonAttr\`,'$[0]') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$[0]')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$[0]') = CAST('4' AS JSON)`, + }, + ); + + testSql( + { 'jsonAttr.0.attribute': 4 }, + { + postgres: `"jsonAttr"#>ARRAY['0','attribute']::VARCHAR(255)[] = '4'`, + sqlite3: `json_extract(\`jsonAttr\`,'$."0".attribute') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."0".attribute')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$."0".attribute') = CAST('4' AS JSON)`, + }, + ); + + // Regression test: https://github.com/sequelize/sequelize/issues/8718 + testSql( + { jsonAttr: { 'hyphenated-key': 4 } }, + { + postgres: `"jsonAttr"->'hyphenated-key' = '4'`, + sqlite3: `json_extract(\`jsonAttr\`,'$."hyphenated-key"') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."hyphenated-key"')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$."hyphenated-key"') = CAST('4' AS JSON)`, + }, + ); + + // SQL injection test + testSql( + { jsonAttr: { '"a\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "': 1 } }, + { + postgres: `"jsonAttr"->'a'')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ' = '1'`, + mysql: `json_extract(\`jsonAttr\`,'$."a\\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "') = CAST('1' AS JSON)`, + sqlite3: `json_extract(\`jsonAttr\`,'$."a'')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "') = '1'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."a\\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "')) = '1'`, + }, + ); + + testSql( + { 'jsonAttr[0].nested.attribute': 4 }, + { + postgres: `"jsonAttr"#>ARRAY['0','nested','attribute']::VARCHAR(255)[] = '4'`, + + // these tests cannot be deduplicated because [0] will be replaced by `0` by expectsql + sqlite3: `json_extract(\`jsonAttr\`,'$[0].nested.attribute') = '4'`, + mariadb: `json_compact(json_extract(\`jsonAttr\`,'$[0].nested.attribute')) = '4'`, + mysql: `json_extract(\`jsonAttr\`,'$[0].nested.attribute') = CAST('4' AS JSON)`, + }, + ); + + // aliases attribute -> column correctly + testSql( + { 'aliasedJsonAttr.nested.attribute': 4 }, + { + postgres: `"aliased_json"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`, + sqlite3: `json_extract(\`aliased_json\`,'$.nested.attribute') = '4'`, + mariadb: `json_compact(json_extract(\`aliased_json\`,'$.nested.attribute')) = '4'`, + mysql: `json_extract(\`aliased_json\`,'$.nested.attribute') = CAST('4' AS JSON)`, + }, + ); + } + + if (dialectSupportsJsonUnquotedExtraction()) { + testSql( + { 'jsonAttr:unquote': 0 }, + { + postgres: `"jsonAttr"#>>ARRAY[]::TEXT[] = 0`, + mssql: `JSON_VALUE([jsonAttr]) = 0`, + 'sqlite3 mysql mariadb': `json_unquote([jsonAttr]) = 0`, + }, + ); + + testSql( + { 'jsonAttr.key:unquote': 0 }, + { + postgres: `"jsonAttr"->>'key' = 0`, + mssql: `JSON_VALUE([jsonAttr], N'$.key') = 0`, + 'sqlite3 mysql mariadb': `json_unquote(json_extract([jsonAttr],'$.key')) = 0`, + }, + ); + + testSql( + { 'jsonAttr.nested.key:unquote': 0 }, + { + postgres: `"jsonAttr"#>>ARRAY['nested','key']::VARCHAR(255)[] = 0`, + mssql: `JSON_VALUE([jsonAttr], N'$.nested.key') = 0`, + 'sqlite3 mysql mariadb': `json_unquote(json_extract([jsonAttr],'$.nested.key')) = 0`, + }, + ); + + testSql( + { 'jsonAttr[0]:unquote': 0 }, + { + postgres: `"jsonAttr"->>0 = 0`, + + // must be separate because [0] will be replaced by `0` by expectsql + sqlite3: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`, + mysql: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`, + mariadb: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`, + mssql: `JSON_VALUE([jsonAttr], N'$[0]') = 0`, + }, + ); + } + }); + } + + if (dialectSupportsJsonB()) { + describe('JSONB', () => { + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: ['a', 'b'], + }, + }, + { + default: `[jsonbAttr] ?| ARRAY['a','b']`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: ['a', 'b'], + }, + }, + { + default: `[jsonbAttr] ?& ARRAY['a','b']`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: literal( + `ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`, + ), + }, + }, + { + default: `[jsonbAttr] ?| ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: literal( + `ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`, + ), + }, + }, + { + default: `[jsonbAttr] ?& ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: col('label'), + }, + }, + { + default: `[jsonbAttr] ?| "label"`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: col('labels'), + }, + }, + { + default: `[jsonbAttr] ?& "labels"`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: cast(col('labels'), 'STRING[]'), + }, + }, + { + default: `[jsonbAttr] ?| CAST("labels" AS STRING[])`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: cast(col('labels'), 'STRING[]'), + }, + }, + { + default: `[jsonbAttr] ?& CAST("labels" AS STRING[])`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: [], + }, + }, + { + default: `[jsonbAttr] ?| ARRAY[]::TEXT[]`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: [], + }, + }, + { + default: `[jsonbAttr] ?& ARRAY[]::TEXT[]`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.anyKeyExists]: fn('get_label'), + }, + }, + { + default: `[jsonbAttr] ?| get_label()`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.allKeysExist]: fn('get_labels'), + }, + }, + { + default: `[jsonbAttr] ?& get_labels()`, + }, + ); + + testSql( + // @ts-expect-error -- typings for `json` are broken, but `json()` is deprecated + { id: { [Op.eq]: json('profile.id') } }, + { + default: `"id" = "profile"->'id'`, + }, + ); + + testSql( + // @ts-expect-error -- typings for `json` are broken, but `json()` is deprecated + json('profile.id', cast('12346-78912', 'text')), + { + postgres: `"User"."profile"->'id' = CAST('12346-78912' AS TEXT)`, + }, + { + mainAlias: 'User', + }, + ); + + testSql( + json({ profile: { id: '12346-78912', name: 'test' } }), + { + postgres: `"User"."profile"->'id' = '"12346-78912"' AND "User"."profile"->'name' = '"test"'`, + }, + { + mainAlias: 'User', + }, + ); + + testSql( + { + jsonbAttr: { + nested: { + attribute: 'value', + }, + }, + }, + { + postgres: `"User"."jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '"value"'`, + }, + { + mainAlias: 'User', + }, + ); + + testSql( + { + jsonbAttr: { + nested: { + [Op.in]: [1, 2], + }, + }, + }, + { + postgres: `"jsonbAttr"->'nested' IN ('1', '2')`, + }, + ); + + testSql( + { + 'jsonbAttr.nested.attribute': { + [Op.in]: [3, 7], + }, + }, + { + postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] IN ('3', '7')`, + }, + ); + + testSql( + { + jsonbAttr: { + nested: { + [Op.between]: [1, 2], + }, + }, + }, + { + postgres: `"jsonbAttr"->'nested' BETWEEN '1' AND '2'`, + }, + ); + + testSql( + { + jsonbAttr: { + price: 5, + name: 'Product', + }, + }, + { + postgres: `"jsonbAttr"->'price' = '5' AND "jsonbAttr"->'name' = '"Product"'`, + }, + ); + + testSql( + { + jsonbAttr: { + name: { + last: 'Simpson', + }, + employment: { + [Op.ne]: 'None', + }, + }, + }, + { + postgres: `"User"."jsonbAttr"#>ARRAY['name','last']::VARCHAR(255)[] = '"Simpson"' AND "User"."jsonbAttr"->'employment' != '"None"'`, + }, + { + mainAlias: 'User', + }, + ); + + const dt = new Date(); + const jsonDt = JSON.stringify(dt); + testSql( + { + jsonbAttr: { + nested: { + attribute: { + [Op.gt]: dt, + }, + }, + }, + }, + { + postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] > ${queryGen.escape(jsonDt)}`, + }, + ); + + testSql( + { + jsonbAttr: { + nested: { + attribute: true, + }, + }, + }, + { + postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = 'true'`, + }, + ); + + testSql( + { + jsonbAttr: { + [Op.contains]: { company: 'Magnafone' }, + }, + }, + { + default: `[jsonbAttr] @> '{"company":"Magnafone"}'`, + }, + ); + + testSql( + { + jsonbTypeLiteralAttr: { [Op.contains]: { foo: 'bar' } }, + }, + { + postgres: '"jsonbTypeLiteralAttr" @> \'{"foo":"bar"}\'', + }, + ); + + testSql( + { + // @ts-expect-error -- key `bad` isn't known + jsonbTypeLiteralAttr: { [Op.contains]: { bad: 'bad' } }, + }, + { + postgres: '"jsonbTypeLiteralAttr" @> \'{"bad":"bad"}\'', + }, + ); + + testSql( + { + jsonbInterfaceAttr: { [Op.contains]: { foo: 'bar' } }, + }, + { + postgres: '"jsonbInterfaceAttr" @> \'{"foo":"bar"}\'', + }, + ); + + testSql( + { + // @ts-expect-error -- key `bad` isn't known + jsonbInterfaceAttr: { [Op.contains]: { bad: 'bad' } }, + }, + { + postgres: '"jsonbInterfaceAttr" @> \'{"bad":"bad"}\'', + }, + ); + + // aliases correctly + + testSql( + { aliasedJsonbAttr: { key: 'value' } }, + { + postgres: `"aliased_jsonb"->'key' = '"value"'`, + }, + ); + }); + } + + testSql( + { + stringAttr: 'a project', + [Op.or]: [{ intAttr1: [1, 2, 3] }, { intAttr1: { [Op.gt]: 10 } }], + }, + { + default: "([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10) AND [stringAttr] = 'a project'", + mssql: "([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10) AND [stringAttr] = N'a project'", + }, + ); + + describe('Op.and', () => { + it('and() is the same as Op.and', () => { + expect(util.inspect(and('a', 'b'))).to.deep.equal(util.inspect({ [Op.and]: ['a', 'b'] })); + }); + + testSql(and([]), { + default: '', + }); + + testSql(and({}), { + default: '', + }); + + // by default: it already is Op.and + testSql( + { intAttr1: 1, intAttr2: 2 }, + { + default: `[intAttr1] = 1 AND [intAttr2] = 2`, + }, + ); + + // top-level array is Op.and + testSql([{ intAttr1: 1 }, { intAttr1: 2 }], { + default: `[intAttr1] = 1 AND [intAttr1] = 2`, + }); + + // $intAttr1$ doesn't override intAttr1 + testSql( + { intAttr1: 1, $intAttr1$: 2 }, + { + default: `[intAttr1] = 1 AND [intAttr1] = 2`, + }, + ); + + // can pass a simple object + testSql( + { [Op.and]: { intAttr1: 1, intAttr2: 2 } }, + { + default: `[intAttr1] = 1 AND [intAttr2] = 2`, + }, + ); + + // can pass an array + testSql( + { [Op.and]: [{ intAttr1: 1, intAttr2: 2 }, { stringAttr: '' }] }, + { + default: `([intAttr1] = 1 AND [intAttr2] = 2) AND [stringAttr] = ''`, + mssql: `([intAttr1] = 1 AND [intAttr2] = 2) AND [stringAttr] = N''`, + }, + ); + + // can be used on attribute + testSql( + { intAttr1: { [Op.and]: [1, { [Op.gt]: 1 }] } }, + { + default: `[intAttr1] = 1 AND [intAttr1] > 1`, + }, + ); + + testSql( + // @ts-expect-error -- cannot be used after operator + { intAttr1: { [Op.gt]: { [Op.and]: [1, 2] } } }, + { + default: new Error(`{ [Symbol(and)]: [ 1, 2 ] } is not a valid integer`), + }, + ); + }); + + describe('Op.or', () => { + it('or() is the same as Op.or', () => { + expect(util.inspect(or('a', 'b'))).to.deep.equal(util.inspect({ [Op.or]: ['a', 'b'] })); + }); + + testSql(or([]), { + default: '', + }); + + testSql(or({}), { + default: '', + }); + + // can pass a simple object + testSql( + { [Op.or]: { intAttr1: 1, intAttr2: 2 } }, + { + default: `[intAttr1] = 1 OR [intAttr2] = 2`, + }, + ); + + // can pass an array + testSql( + { [Op.or]: [{ intAttr1: 1, intAttr2: 2 }, { stringAttr: '' }] }, + { + default: `([intAttr1] = 1 AND [intAttr2] = 2) OR [stringAttr] = ''`, + mssql: `([intAttr1] = 1 AND [intAttr2] = 2) OR [stringAttr] = N''`, + }, + ); + + // can be used on attribute + testSql( + { intAttr1: { [Op.or]: [1, { [Op.gt]: 1 }] } }, + { + default: `[intAttr1] = 1 OR [intAttr1] > 1`, + }, + ); + + testSql( + // @ts-expect-error -- cannot be used after operator + { intAttr1: { [Op.gt]: { [Op.or]: [1, 2] } } }, + { + default: new Error(`{ [Symbol(or)]: [ 1, 2 ] } is not a valid integer`), + }, + ); + + testSql( + { + [Op.or]: { + intAttr1: [1, 3], + intAttr2: { + [Op.in]: [2, 4], + }, + }, + }, + { + default: '[intAttr1] IN (1, 3) OR [intAttr2] IN (2, 4)', + }, + ); + }); + + describe('Op.{and,or,not} combinations', () => { + // both can be used in the same object + testSql( + { + [Op.and]: { intAttr1: 1, intAttr2: 2 }, + [Op.or]: { intAttr1: 1, intAttr2: 2 }, + }, + { + default: `([intAttr1] = 1 AND [intAttr2] = 2) AND ([intAttr1] = 1 OR [intAttr2] = 2)`, + }, + ); + + // Op.or only applies to its direct Array, the nested array is still Op.and + testSql( + { + [Op.or]: [[{ intAttr1: 1 }, { intAttr1: 2 }], { intAttr1: 3 }], + }, + { + default: '([intAttr1] = 1 AND [intAttr1] = 2) OR [intAttr1] = 3', + }, + ); + + // can be nested *after* attribute + testSql( + { + intAttr1: { + [Op.and]: [1, 2, { [Op.or]: [3, 4] }, { [Op.not]: 5 }, [6, 7]], + }, + }, + { + default: + '[intAttr1] = 1 AND [intAttr1] = 2 AND ([intAttr1] = 3 OR [intAttr1] = 4) AND NOT ([intAttr1] = 5) AND [intAttr1] IN (6, 7)', + }, + ); + + // can be nested + testSql( + { + [Op.not]: { + [Op.and]: { + [Op.or]: { + [Op.and]: { + intAttr1: 1, + intAttr2: 2, + }, + }, + }, + }, + }, + { + default: 'NOT ([intAttr1] = 1 AND [intAttr2] = 2)', + }, + ); + + testSql( + { + [Op.not]: { + [Op.or]: { + [Op.and]: { + intAttr1: 1, + intAttr2: 2, + }, + [Op.or]: { + intAttr1: 1, + intAttr2: 2, + }, + }, + }, + }, + { + default: + 'NOT (([intAttr1] = 1 AND [intAttr2] = 2) OR ([intAttr1] = 1 OR [intAttr2] = 2))', + }, + ); + + // Op.not, Op.and, Op.or can reside on the same object as attributes + testSql( + { + intAttr1: 1, + [Op.not]: { + intAttr1: { [Op.eq]: 2 }, + [Op.and]: { + intAttr1: 3, + [Op.or]: { + intAttr1: 4, + [Op.and]: { + intAttr1: 5, + intAttr2: 6, + }, + }, + }, + }, + }, + { + default: + '(NOT (((([intAttr1] = 5 AND [intAttr2] = 6) OR [intAttr1] = 4) AND [intAttr1] = 3) AND [intAttr1] = 2)) AND [intAttr1] = 1', + }, + ); + }); + + describe('where()', () => { + { + // @ts-expect-error -- 'intAttr1' is not a boolean and cannot be compared to the output of 'where' + const ignore: TestModelWhere = { intAttr1: where(fn('lower', col('name')), null) }; + } + + testSql( + { booleanAttr: where(fn('lower', col('name')), null) }, + { + default: `[booleanAttr] = (lower([name]) IS NULL)`, + }, + ); + + testSql( + { booleanAttr: where(fn('lower', col('name')), null) }, + { + default: `[booleanAttr] = (lower([name]) IS NULL)`, + }, + ); + + describe('where(leftOperand, operator, rightOperand)', () => { + testSql(where(col('name'), Op.eq, fn('NOW')), { + default: '[name] = NOW()', + }); + + // some dialects support having a filter inside aggregate functions: + // https://github.com/sequelize/sequelize/issues/6666 + testSql(where(fn('sum', { id: 1 }), Op.eq, 1), { + default: 'sum([id] = 1) = 1', + }); + + // some dialects support having a filter inside aggregate functions, but require casting: + // https://github.com/sequelize/sequelize/issues/6666 + testSql(where(fn('sum', cast({ id: 1 }, 'int')), Op.eq, 1), { + default: 'sum(CAST(([id] = 1) AS INT)) = 1', + }); + + // comparing the output of `where` to `where` + testSql(where(where(col('col'), Op.eq, '1'), Op.eq, where(col('col'), Op.eq, '2')), { + default: `([col] = '1') = ([col] = '2')`, + mssql: `([col] = N'1') = ([col] = N'2')`, + }); + + testSql(where(1, Op.eq, 2), { + default: '1 = 2', + }); + + testSql(where(1, Op.eq, col('col')), { + default: '1 = [col]', + }); + + testSql(where('string', Op.eq, col('col')), { + default: `'string' = [col]`, + mssql: `N'string' = [col]`, + }); + + testSql(where('a', Op.eq, 'b'), { + default: `'a' = 'b'`, + mssql: `N'a' = N'b'`, + }); + + it('does not allow string operators', () => { + // @ts-expect-error -- testing that this errors + expect(() => where(fn('SUM', col('hours')), '>', 0)).to.throw( + 'where(left, operator, right) does not accept a string as the operator', + ); + }); + + testSql(where(fn('SUM', col('hours')), Op.gt, 0), { + default: 'SUM([hours]) > 0', + }); + + testSql(where(fn('lower', col('name')), Op.ne, null), { + default: 'lower([name]) IS NOT NULL', + }); + + // @ts-expect-error -- While these are supported for backwards compatibility, they are not documented. Users should use isNot + testSql(where(fn('lower', col('name')), Op.not, null), { + default: 'NOT (lower([name]) IS NULL)', + }); + + testSql(where(fn('lower', col('name')), Op.isNot, null), { + default: 'lower([name]) IS NOT NULL', + }); + + testSql(where(col('hours'), Op.between, [0, 5]), { + default: '[hours] BETWEEN 0 AND 5', + }); + + testSql(where(col('hours'), Op.notBetween, [0, 5]), { + default: '[hours] NOT BETWEEN 0 AND 5', + }); + + testSql(where({ [Op.col]: 'hours' }, Op.notBetween, [0, 5]), { + default: '[hours] NOT BETWEEN 0 AND 5', + }); + + testSql(where(cast({ [Op.col]: 'hours' }, 'integer'), Op.notBetween, [0, 5]), { + default: 'CAST([hours] AS INTEGER) NOT BETWEEN 0 AND 5', + }); + + testSql(where(fn('SUM', { [Op.col]: 'hours' }), Op.notBetween, [0, 5]), { + default: 'SUM([hours]) NOT BETWEEN 0 AND 5', + }); + + testSql(where(literal(`'hours'`), Op.eq, 'hours'), { + default: `'hours' = 'hours'`, + mssql: `'hours' = N'hours'`, + }); + + testSql(where(col('col'), Op.eq, { [Op.in]: [1, 2] }), { + default: new Error('Could not guess type of value { [Symbol(in)]: [ 1, 2 ] }'), + }); + }); + + describe('where(leftOperand, whereAttributeHashValue)', () => { + testSql(where(fn('lower', col('name')), null), { + default: 'lower([name]) IS NULL', + }); + + testSql(where(cast(col('name'), 'int'), { [Op.eq]: 10 }), { + default: 'CAST([name] AS INT) = 10', + }); + + testSql(where(literal('abc'), { [Op.eq]: 10 }), { + default: 'abc = 10', + }); + + testSql(where(col('name'), { [Op.eq]: '123', [Op.not]: { [Op.eq]: '456' } }), { + default: `[name] = '123' AND NOT ([name] = '456')`, + mssql: `[name] = N'123' AND NOT ([name] = N'456')`, + }); + + testSql(where(col('name'), or({ [Op.eq]: '123', [Op.not]: { [Op.eq]: '456' } })), { + default: `[name] = '123' OR NOT ([name] = '456')`, + mssql: `[name] = N'123' OR NOT ([name] = N'456')`, + }); + + testSql( + // Note: using `col()`, the following is not treated as a json.path. + // (yes, it's inconsistant with regular attribute notation. attr could be a good replacement) + where(col('attribute.path'), 10), + { + default: '[attribute].[path] = 10', + }, + ); + + testSql( + // Note: using `col()`, the following is not treated as a nested.attribute.path. + // (yes, it's inconsistant with regular attribute notation. attr could be a good replacement) + where(col('$attribute.path$'), 10), + { + default: '[$attribute].[path$] = 10', + }, + ); + + testSql(where(col('col'), { [Op.and]: [1, 2] }), { + default: '[col] = 1 AND [col] = 2', + }); + + if (dialectSupportsJsonOperations() && dialectSupportsJsonQuotedExtraction()) { + testSql(where(col('col'), { jsonPath: 'value' }), { + postgres: `"col"->'jsonPath' = '"value"'`, + sqlite3: `json_extract(\`col\`,'$.jsonPath') = '"value"'`, + mariadb: `json_compact(json_extract(\`col\`,'$.jsonPath')) = '"value"'`, + mysql: `json_extract(\`col\`,'$.jsonPath') = CAST('"value"' AS JSON)`, + }); + } + }); + }); + }); +}); diff --git a/packages/core/test/unit/transaction.test.ts b/packages/core/test/unit/transaction.test.ts new file mode 100644 index 000000000000..f181001af030 --- /dev/null +++ b/packages/core/test/unit/transaction.test.ts @@ -0,0 +1,96 @@ +import { IsolationLevel } from '@sequelize/core'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { beforeAll2, getTestDialect, sequelize } from '../support'; + +const dialectName = getTestDialect(); + +describe('Transaction', () => { + // These dialects do not pass transaction queries to queryRaw. + // Instead, they call connection transaction methods directly. + if (sequelize.dialect.supports.connectionTransactionMethods) { + return; + } + + const vars = beforeAll2(() => { + sequelize.setDatabaseVersion('does not matter, prevents the SHOW SERVER_VERSION query'); + + return { + stub: sinon.stub(sequelize, 'queryRaw').resolves([[], {}]), + stubConnection: sinon.stub(sequelize.dialect.connectionManager, 'connect').resolves({ + uuid: 'ssfdjd-434fd-43dfg23-2d', + close() {}, + }), + stubValidate: sinon.stub(sequelize.dialect.connectionManager, 'validate').returns(true), + stubRelease: sinon.stub(sequelize.dialect.connectionManager, 'disconnect'), + stubTransactionId: sinon + .stub(sequelize.queryGenerator, 'generateTransactionId') + .returns('123'), + }; + }); + + beforeEach(() => { + vars.stub.resetHistory(); + vars.stubConnection.resetHistory(); + vars.stubValidate.resetHistory(); + vars.stubRelease.resetHistory(); + }); + + after(() => { + vars.stub.restore(); + vars.stubConnection.restore(); + vars.stubValidate.restore(); + vars.stubRelease.restore(); + vars.stubTransactionId.restore(); + }); + + it('should run auto commit query only when needed', async () => { + sequelize.setDatabaseVersion('does not matter, prevents the SHOW SERVER_VERSION query'); + + const expectations: Record = { + all: ['START TRANSACTION'], + snowflake: ['START TRANSACTION NAME "123"'], + sqlite3: ['BEGIN DEFERRED TRANSACTION'], + }; + + await sequelize.transaction(async () => { + expect(vars.stub.args.map(arg => arg[0])).to.deep.equal( + expectations[dialectName] || expectations.all, + ); + }); + }); + + it('should set isolation level correctly', async () => { + const expectations: Record = { + all: ['SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', 'START TRANSACTION'], + postgres: ['START TRANSACTION', 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED'], + sqlite3: ['BEGIN DEFERRED TRANSACTION', 'PRAGMA read_uncommitted = 1'], + }; + + try { + await sequelize.transaction({ isolationLevel: IsolationLevel.READ_UNCOMMITTED }, async () => { + expect(vars.stub.args.map(arg => arg[0])).to.deep.equal( + expectations[dialectName] || expectations.all, + ); + }); + } catch (error) { + if (!sequelize.dialect.supports.isolationLevels) { + expect(error).to.be.instanceOf( + Error, + `Isolation levels are not supported by ${dialectName}.`, + ); + } else { + throw error; + } + } + }); +}); + +describe('Sequelize#transaction', () => { + it('throws if the callback is not provided', async () => { + // @ts-expect-error -- this test ensures a helpful error is thrown to ease migration. + await expect(sequelize.transaction()).to.be.rejectedWith( + 'sequelize.transaction requires a callback. If you wish to start an unmanaged transaction, please use sequelize.startUnmanagedTransaction instead', + ); + }); +}); diff --git a/packages/core/test/unit/utils/attribute-syntax.test.ts b/packages/core/test/unit/utils/attribute-syntax.test.ts new file mode 100644 index 000000000000..47dff9a0ddd5 --- /dev/null +++ b/packages/core/test/unit/utils/attribute-syntax.test.ts @@ -0,0 +1,134 @@ +import { AssociationPath, Attribute, sql } from '@sequelize/core'; +import { Unquote } from '@sequelize/core/_non-semver-use-at-your-own-risk_/expression-builders/dialect-aware-fn.js'; +import { + parseAttributeSyntax, + parseNestedJsonKeySyntax, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/attribute-syntax.js'; +import { expect } from 'chai'; + +describe('parseAttributeSyntax', () => { + it('parses simple attributes', () => { + expect(parseAttributeSyntax('foo')).to.deep.eq(new Attribute('foo')); + }); + + it('parses simple associations', () => { + expect(parseAttributeSyntax('$bar$')).to.deep.eq(new Attribute('bar')); + + expect(parseAttributeSyntax('$foo.bar$')).to.deep.eq(new AssociationPath(['foo'], 'bar')); + + expect(parseAttributeSyntax('$foo.zzz.bar$')).to.deep.eq( + new AssociationPath(['foo', 'zzz'], 'bar'), + ); + }); + + it('throws for unbalanced association syntax', () => { + // The error points at the erroneous character each time, but we only test the first one + expect(() => parseAttributeSyntax('foo$')).to + .throwWithCause(`Failed to parse syntax of attribute. Parse error at index 3: +foo$ + ^`); + + expect(() => parseAttributeSyntax('$foo')).to + .throwWithCause(`Failed to parse syntax of attribute. Parse error at index 4: +$foo + ^`); + }); + + it('parses cast syntax', () => { + expect(parseAttributeSyntax('foo::bar')).to.deep.eq(sql.cast(new Attribute('foo'), 'bar')); + }); + + it('parses consecutive casts', () => { + expect(parseAttributeSyntax('foo::bar::baz')).to.deep.eq( + sql.cast(sql.cast(new Attribute('foo'), 'bar'), 'baz'), + ); + }); + + it('parses modifier syntax', () => { + expect(parseAttributeSyntax('foo:unquote')).to.deep.eq(sql.unquote(new Attribute('foo'))); + }); + + it('parses consecutive modifiers', () => { + expect(parseAttributeSyntax('foo:unquote:unquote')).to.deep.eq( + sql.unquote(sql.unquote(new Attribute('foo'))), + ); + }); + + it('parses casts and modifiers', () => { + expect(parseAttributeSyntax('textAttr::json:unquote::integer')).to.deep.eq( + sql.cast(sql.unquote(sql.cast(new Attribute('textAttr'), 'json')), 'integer'), + ); + }); + + it('treats everything after ::/: as a cast/modifier', () => { + // "json.property" is treated as a cast, not a JSON path + // but it's not a valid cast, so it will throw + expect(() => parseAttributeSyntax('textAttr::json.property')).to + .throwWithCause(`Failed to parse syntax of attribute. Parse error at index 14: +textAttr::json.property + ^`); + + // "json.property" is treated as a modifier (which does not exist and will throw), not a JSON path + expect(() => parseAttributeSyntax('textAttr:json.property')).to + .throwWithCause(`Failed to parse syntax of attribute. Parse error at index 13: +textAttr:json.property + ^`); + }); + + it('parses JSON paths', () => { + expect(parseAttributeSyntax('foo.bar')).to.deep.eq(sql.jsonPath(new Attribute('foo'), ['bar'])); + + expect(parseAttributeSyntax('foo."bar"')).to.deep.eq( + sql.jsonPath(new Attribute('foo'), ['bar']), + ); + + expect(parseAttributeSyntax('foo."bar\\""')).to.deep.eq( + sql.jsonPath(new Attribute('foo'), ['bar"']), + ); + + expect(parseAttributeSyntax('foo."bar\\\\"')).to.deep.eq( + sql.jsonPath(new Attribute('foo'), ['bar\\']), + ); + + expect(parseAttributeSyntax('foo[123]')).to.deep.eq(sql.jsonPath(new Attribute('foo'), [123])); + + expect(parseAttributeSyntax('foo."123"')).to.deep.eq( + sql.jsonPath(new Attribute('foo'), ['123']), + ); + + expect(parseAttributeSyntax('foo.abc[0]."def"[1]')).to.deep.eq( + sql.jsonPath(new Attribute('foo'), ['abc', 0, 'def', 1]), + ); + }); +}); + +describe('parseNestedJsonKeySyntax', () => { + it('parses JSON paths', () => { + expect(parseNestedJsonKeySyntax('foo.bar')).to.deep.eq({ + pathSegments: ['foo', 'bar'], + castsAndModifiers: [], + }); + + expect(parseNestedJsonKeySyntax('abc-def.ijk-lmn')).to.deep.eq({ + pathSegments: ['abc-def', 'ijk-lmn'], + castsAndModifiers: [], + }); + + expect(parseNestedJsonKeySyntax('"foo"."bar"')).to.deep.eq({ + pathSegments: ['foo', 'bar'], + castsAndModifiers: [], + }); + + expect(parseNestedJsonKeySyntax('[0]')).to.deep.eq({ + pathSegments: [0], + castsAndModifiers: [], + }); + }); + + it('parses casts and modifiers', () => { + expect(parseNestedJsonKeySyntax('[0]:unquote::text:unquote::text')).to.deep.eq({ + pathSegments: [0], + castsAndModifiers: [Unquote, 'text', Unquote, 'text'], + }); + }); +}); diff --git a/packages/core/test/unit/utils/check.test.ts b/packages/core/test/unit/utils/check.test.ts new file mode 100644 index 000000000000..855b10bb66a0 --- /dev/null +++ b/packages/core/test/unit/utils/check.test.ts @@ -0,0 +1,44 @@ +import { DataTypes } from '@sequelize/core'; +import { + defaultValueSchemable, + isWhereEmpty, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { expect } from 'chai'; +import { allowDeprecationsInSuite, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('utils / check', () => { + describe('defaultValueSchemable', () => { + allowDeprecationsInSuite(['SEQUELIZE0026']); + + it('should return false if the value is a NOW', () => { + expect(defaultValueSchemable(DataTypes.NOW, dialect)).to.equal(false); + expect(defaultValueSchemable(DataTypes.NOW(), dialect)).to.equal(false); + }); + it('should return false if the value is a UUIDV1', () => { + expect(defaultValueSchemable(DataTypes.UUIDV1, dialect)).to.equal(false); + expect(defaultValueSchemable(DataTypes.UUIDV1(), dialect)).to.equal(false); + }); + it('should return false if the value is a UUIDV4', () => { + expect(defaultValueSchemable(DataTypes.UUIDV4, dialect)).to.equal(false); + expect(defaultValueSchemable(DataTypes.UUIDV4(), dialect)).to.equal(false); + }); + it('should return true otherwise', () => { + expect(defaultValueSchemable('hello', dialect)).to.equal(true); + expect(defaultValueSchemable(DataTypes.INTEGER(), dialect)).to.equal(true); + }); + }); + + describe('isWhereEmpty', () => { + it('should return true if the where is empty', () => { + expect(isWhereEmpty({})).to.equal(true); + }); + it('should return false if the where is not empty', () => { + expect(isWhereEmpty({ a: 1 })).to.equal(false); + }); + it('should return false even if value is empty', () => { + expect(isWhereEmpty({ a: undefined })).to.equal(false); + }); + }); +}); diff --git a/packages/core/test/unit/utils/model-utils.test.ts b/packages/core/test/unit/utils/model-utils.test.ts new file mode 100644 index 000000000000..78c31a7b7292 --- /dev/null +++ b/packages/core/test/unit/utils/model-utils.test.ts @@ -0,0 +1,51 @@ +import { Model, isModelStatic, isSameInitialModel } from '@sequelize/core'; +import { expect } from 'chai'; +import { sequelize } from '../../support'; + +describe('isModelStatic', () => { + it('returns true for model subclasses', () => { + const MyModel = sequelize.define('MyModel', {}); + + expect(isModelStatic(MyModel)).to.be.true; + }); + + it('returns false for model instances', () => { + const MyModel = sequelize.define('MyModel', {}); + + expect(isModelStatic(MyModel.build())).to.be.false; + }); + + it('returns false for the Model class', () => { + expect(isModelStatic(Model)).to.be.false; + }); + + it('returns false for the anything else', () => { + expect(isModelStatic(Date)).to.be.false; + }); +}); + +describe('isSameInitialModel', () => { + it('returns true if both models have the same initial model', () => { + const MyModel = sequelize.define( + 'MyModel', + {}, + { + scopes: { + scope1: { + where: { id: 1 }, + }, + }, + }, + ); + + expect(isSameInitialModel(MyModel.withSchema('abc'), MyModel.withScope('scope1'))).to.be.true; + }); + + it('returns false if the models are different', () => { + const MyModel1 = sequelize.define('MyModel1', {}); + + const MyModel2 = sequelize.define('MyModel2', {}); + + expect(isSameInitialModel(MyModel1, MyModel2)).to.be.false; + }); +}); diff --git a/packages/core/test/unit/utils/parse-common-connection-url-options.test.ts b/packages/core/test/unit/utils/parse-common-connection-url-options.test.ts new file mode 100644 index 000000000000..e1638c73fb87 --- /dev/null +++ b/packages/core/test/unit/utils/parse-common-connection-url-options.test.ts @@ -0,0 +1,140 @@ +import { parseCommonConnectionUrlOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/connection-options.js'; +import { expect } from 'chai'; + +describe('parseCommonConnectionUrlOptions', () => { + it('maps url parts to specified options', () => { + const options = parseCommonConnectionUrlOptions({ + stringSearchParams: ['search1', 'search2'], + booleanSearchParams: ['search3'], + numberSearchParams: ['search4'], + allowedProtocols: ['mariadb'], + hostname: 'theHost', + password: 'thePassword', + pathname: 'theDb', + port: 'thePort', + url: 'mariadb://a:b@c:1234/d?search1=1&search2=2&search3=true&search4=4', + username: 'theUser', + }); + + expect(options).to.deep.eq({ + theDb: 'd', + theHost: 'c', + thePassword: 'b', + thePort: 1234, + theUser: 'a', + search1: '1', + search2: '2', + search3: true, + search4: 4, + }); + }); + + it('throws when specifying an unknown search param', () => { + expect(() => + parseCommonConnectionUrlOptions({ + url: 'mariadb://localhost?connectTimeout=1000', + allowedProtocols: ['mariadb'], + hostname: 'host', + port: 'port', + pathname: 'database', + }), + ).to.throw('Option "connectTimeout" cannot be set as a connection URL search parameter.'); + }); + + it('throws when specifying an invalid value for boolean search param', () => { + expect(() => + parseCommonConnectionUrlOptions({ + booleanSearchParams: ['search'], + url: 'mariadb://localhost?search=invalid', + allowedProtocols: ['mariadb'], + hostname: 'host', + port: 'port', + pathname: 'database', + }), + ).to.throwWithCause( + 'Cannot convert "invalid" to a boolean. It must be either "true" or "false".', + ); + }); + + it('throws when specifying an invalid value for number search param', () => { + expect(() => + parseCommonConnectionUrlOptions({ + numberSearchParams: ['search'], + url: 'mariadb://localhost?search=invalid', + allowedProtocols: ['mariadb'], + hostname: 'host', + port: 'port', + pathname: 'database', + }), + ).to.throwWithCause('Cannot convert "invalid" to a finite number.'); + }); + + it('throws an error if the protocol is not supported', () => { + expect(() => + parseCommonConnectionUrlOptions({ + url: 'mysql://localhost', + allowedProtocols: ['mariadb'], + hostname: 'host', + port: 'port', + pathname: 'database', + }), + ).to.throw( + `URL "mysql://localhost" is not a valid connection URL. Expected the protocol to be one of "mariadb", but it's "mysql"`, + ); + }); + + it('supports not providing username, password, port, or database name', () => { + const options = parseCommonConnectionUrlOptions({ + allowedProtocols: ['mariadb'], + hostname: 'host', + pathname: 'database', + url: 'mariadb://localhost', + port: 'port', + }); + + expect(options).to.deep.eq({ + host: 'localhost', + }); + }); + + it('supports URL-encoded username, password, hostname, pathname, and search parameters keys and values', () => { + const options = parseCommonConnectionUrlOptions({ + stringSearchParams: ['search1', 'search2', '1', '2'], + allowedProtocols: ['mariadb'], + hostname: 'theHost', + password: 'thePassword', + pathname: 'theDb', + port: 'thePort', + url: 'mariadb://%61:%62@%63:1234/%64?search1=%31&search2=%32&%31=%31&%32=%32', + username: 'theUser', + }); + + expect(options).to.deep.eq({ + theDb: 'd', + theHost: 'c', + thePassword: 'b', + thePort: 1234, + theUser: 'a', + search1: '1', + search2: '2', + 1: '1', + 2: '2', + }); + }); + + it('supports using a socket path as an encoded hostname', () => { + const options = parseCommonConnectionUrlOptions({ + allowedProtocols: ['postgres'], + hostname: 'host', + pathname: 'database', + url: 'postgres://%2Ftmp%2Fmysocket:9821/dbname', + port: 'port', + }); + + expect(options).to.deep.eq({ + host: '/tmp/mysocket', + port: 9821, + database: 'dbname', + }); + }); +}); diff --git a/packages/core/test/unit/utils/sql.test.ts b/packages/core/test/unit/utils/sql.test.ts new file mode 100644 index 000000000000..bfb1bd45915a --- /dev/null +++ b/packages/core/test/unit/utils/sql.test.ts @@ -0,0 +1,1019 @@ +import type { AbstractDialect } from '@sequelize/core'; +import { sql as sqlTag } from '@sequelize/core'; +import { + injectReplacements, + mapBindParameters, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { expect } from 'chai'; +import { + createSequelizeInstance, + expectPerDialect, + expectsql, + sequelize, + toHaveProperties, + toMatchSql, +} from '../../support'; + +const { list } = sqlTag; + +const dialect = sequelize.dialect; + +const supportsNamedParameters = dialect.name === 'sqlite3' || dialect.name === 'mssql'; + +describe('mapBindParameters', () => { + it('parses named bind parameters', () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT ${dialect.TICK_CHAR_LEFT}$id${dialect.TICK_CHAR_RIGHT} FROM users WHERE id = '$id' OR id = $id OR id = '''$id'''`, + dialect, + ); + + expectsql(sql, { + default: `SELECT [$id] FROM users WHERE id = '$id' OR id = ? OR id = '''$id'''`, + postgres: `SELECT "$id" FROM users WHERE id = '$id' OR id = $1 OR id = '''$id'''`, + sqlite3: `SELECT \`$id\` FROM users WHERE id = '$id' OR id = $id OR id = '''$id'''`, + mssql: `SELECT [$id] FROM users WHERE id = '$id' OR id = @id OR id = '''$id'''`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['id']); + } + }); + + it('parses numeric bind parameters', () => { + const { sql, bindOrder, parameterSet } = mapBindParameters( + `SELECT * FROM users WHERE id = $1`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = ?`, + postgres: `SELECT * FROM users WHERE id = $1`, + sqlite3: `SELECT * FROM users WHERE id = $1`, + mssql: `SELECT * FROM users WHERE id = @1`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['1']); + } + + expect(parameterSet).to.deep.eq(new Set(['1'])); + }); + + it('parses bind parameters followed by cast syntax', () => { + const { sql } = mapBindParameters(`SELECT * FROM users WHERE id = $param::string`, dialect); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = ?::string`, + postgres: `SELECT * FROM users WHERE id = $1::string`, + sqlite3: `SELECT * FROM users WHERE id = $param::string`, + mssql: `SELECT * FROM users WHERE id = @param::string`, + }); + }); + + it('parses bind parameters following JSON extraction', () => { + const { sql } = mapBindParameters(`SELECT * FROM users WHERE json_col->>$key`, dialect); + + expectsql(sql, { + default: `SELECT * FROM users WHERE json_col->>?`, + postgres: `SELECT * FROM users WHERE json_col->>$1`, + sqlite3: `SELECT * FROM users WHERE json_col->>$key`, + mssql: `SELECT * FROM users WHERE json_col->>@key`, + }); + }); + + it('parses bind parameters followed by a semicolon', () => { + const { sql } = mapBindParameters('SELECT * FROM users WHERE id = $id;', dialect); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = ?;`, + postgres: `SELECT * FROM users WHERE id = $1;`, + sqlite3: `SELECT * FROM users WHERE id = $id;`, + mssql: `SELECT * FROM users WHERE id = @id;`, + ibmi: `SELECT * FROM users WHERE id = ?;`, // 'default' removes the ; for ibmi + }); + }); + + if (sequelize.dialect.supports.dataTypes.ARRAY) { + it('parses bind parameters inside ARRAY[]', () => { + const { sql } = mapBindParameters( + 'SELECT * FROM users WHERE id = ARRAY[$id1]::int[];', + dialect, + ); + + expectsql(sql, { + default: 'SELECT * FROM users WHERE id = ARRAY[$1]::int[];', + }); + }); + } + + it('parses single letter bind parameters', () => { + const { sql, bindOrder, parameterSet } = mapBindParameters( + `SELECT * FROM users WHERE id = $a`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = ?`, + postgres: `SELECT * FROM users WHERE id = $1`, + sqlite3: `SELECT * FROM users WHERE id = $a`, + mssql: `SELECT * FROM users WHERE id = @a`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['a']); + } + + expect(parameterSet).to.deep.eq(new Set(['a'])); + }); + + it(`does not consider the token to be a bind parameter if it does not follow '(', ',', '=' or whitespace`, () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT * FROM users WHERE id = fn($id) OR id = fn('a',$id) OR id=$id OR id$id = 1 OR id = $id`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = fn(?) OR id = fn('a',?) OR id=? OR id$id = 1 OR id = ?`, + postgres: `SELECT * FROM users WHERE id = fn($1) OR id = fn('a',$1) OR id=$1 OR id$id = 1 OR id = $1`, + sqlite3: `SELECT * FROM users WHERE id = fn($id) OR id = fn('a',$id) OR id=$id OR id$id = 1 OR id = $id`, + mssql: `SELECT * FROM users WHERE id = fn(@id) OR id = fn('a',@id) OR id=@id OR id$id = 1 OR id = @id`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else if (dialect.name === 'postgres') { + expect(bindOrder).to.deep.eq(['id']); + } else { + expect(bindOrder).to.deep.eq(['id', 'id', 'id', 'id']); + } + }); + + it('does not consider the token to be a bind parameter if it is part of a $ quoted string', () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT * FROM users WHERE id = $tag$ $id $tag$ OR id = $$ $id $$`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag$ $id $tag$ OR id = $$ $id $$`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq([]); + } + }); + + it('does not consider the token to be a bind parameter if it is part of a nested $ quoted string', () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT * FROM users WHERE id = $tag1$ $tag2$ $id $tag2$ $tag1$`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag1$ $tag2$ $id $tag2$ $tag1$`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq([]); + } + }); + + it('does consider the token to be a bind parameter if it is in between two identifiers that look like $ quoted strings', () => { + const { sql, bindOrder } = mapBindParameters(`SELECT z$$ $id x$$ * FROM users`, dialect); + + expectsql(sql, { + default: `SELECT z$$ ? x$$ * FROM users`, + postgres: `SELECT z$$ $1 x$$ * FROM users`, + sqlite3: `SELECT z$$ $id x$$ * FROM users`, + mssql: `SELECT z$$ @id x$$ * FROM users`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['id']); + } + }); + + it('does consider the token to be a bind parameter if it is located after a $ quoted string', () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT $$ abc $$ AS string FROM users WHERE id = $id`, + dialect, + ); + + expectsql(sql, { + default: `SELECT $$ abc $$ AS string FROM users WHERE id = ?`, + postgres: `SELECT $$ abc $$ AS string FROM users WHERE id = $1`, + sqlite3: `SELECT $$ abc $$ AS string FROM users WHERE id = $id`, + mssql: `SELECT $$ abc $$ AS string FROM users WHERE id = @id`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['id']); + } + }); + + it('does not consider the token to be a bind parameter if it is part of a string with a backslash escaped quote, in dialects that support backslash escape', () => { + expectPerDialect( + () => mapBindParameters(`SELECT * FROM users WHERE id = '\\' $id' OR id = $id`, dialect), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' $id' OR id = $id`), + + 'mysql mariadb': toHaveProperties({ + sql: toMatchSql(`SELECT * FROM users WHERE id = '\\' $id' OR id = ?`), + bindOrder: ['id'], + }), + }, + ); + }); + + it('does not consider the token to be a bind parameter if it is part of a string with a backslash escaped quote, in dialects that support standardConformingStrings = false', () => { + if (!supportsNonStandardConformingStrings()) { + return; + } + + expectPerDialect( + () => + mapBindParameters( + `SELECT * FROM users WHERE id = '\\' $id' OR id = $id`, + getNonStandardConfirmingStringDialect(), + ), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' $id' OR id = $id`), + + 'mysql mariadb': toHaveProperties({ + sql: toMatchSql(`SELECT * FROM users WHERE id = '\\' $id' OR id = ?`), + bindOrder: ['id'], + }), + postgres: toHaveProperties({ + sql: `SELECT * FROM users WHERE id = '\\' $id' OR id = $1`, + bindOrder: ['id'], + }), + }, + ); + }); + + it('does not consider the token to be a bind parameter if it is part of an E-prefixed string with a backslash escaped quote, in dialects that support E-prefixed strings', () => { + expectPerDialect( + () => mapBindParameters(`SELECT * FROM users WHERE id = E'\\' $id' OR id = $id`, dialect), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = E'\\' $id' OR id = $id`), + + 'mysql mariadb': toHaveProperties({ + sql: toMatchSql(`SELECT * FROM users WHERE id = E'\\' $id' OR id = ?`), + bindOrder: ['id'], + }), + postgres: toHaveProperties({ + sql: `SELECT * FROM users WHERE id = E'\\' $id' OR id = $1`, + bindOrder: ['id'], + }), + }, + ); + }); + + it('treats strings prefixed with a lowercase e as E-prefixed strings too', () => { + expectPerDialect( + () => mapBindParameters(`SELECT * FROM users WHERE id = e'\\' $id' OR id = $id`, dialect), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = e'\\' $id' OR id = $id`), + + 'mysql mariadb': toHaveProperties({ + sql: toMatchSql(`SELECT * FROM users WHERE id = e'\\' $id' OR id = ?`), + bindOrder: ['id'], + }), + postgres: toHaveProperties({ + sql: `SELECT * FROM users WHERE id = e'\\' $id' OR id = $1`, + bindOrder: ['id'], + }), + }, + ); + }); + + it('considers the token to be a bind parameter if it is outside a string ending with an escaped backslash', () => { + const { sql, bindOrder } = mapBindParameters( + `SELECT * FROM users WHERE id = '\\\\' OR id = $id`, + dialect, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = '\\\\' OR id = ?`, + postgres: `SELECT * FROM users WHERE id = '\\\\' OR id = $1`, + sqlite3: `SELECT * FROM users WHERE id = '\\\\' OR id = $id`, + mssql: `SELECT * FROM users WHERE id = '\\\\' OR id = @id`, + }); + + if (supportsNamedParameters) { + expect(bindOrder).to.be.null; + } else { + expect(bindOrder).to.deep.eq(['id']); + } + }); + + it('does not consider the token to be a bind parameter if it is part of a string with an escaped backslash followed by a backslash escaped quote', () => { + expectPerDialect( + () => mapBindParameters(`SELECT * FROM users WHERE id = '\\\\\\' $id' OR id = $id`, dialect), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\\\\\' $id' OR id = $id`), + + 'mysql mariadb': toHaveProperties({ + sql: toMatchSql(`SELECT * FROM users WHERE id = '\\\\\\' $id' OR id = ?`), + bindOrder: ['id'], + }), + }, + ); + }); + + it('does not consider the token to be a bind parameter if it is in a single line comment', () => { + const { sql } = mapBindParameters( + ` + SELECT * FROM users -- WHERE id = $id + WHERE id = $id + `, + dialect, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- WHERE id = $id + WHERE id = ? + `, + postgres: ` + SELECT * FROM users -- WHERE id = $id + WHERE id = $1 + `, + sqlite3: ` + SELECT * FROM users -- WHERE id = $id + WHERE id = $id + `, + mssql: ` + SELECT * FROM users -- WHERE id = $id + WHERE id = @id + `, + }); + }); + + it('does not consider the token to be a bind parameter if it is in string but a previous comment included a string delimiter', () => { + const { sql } = mapBindParameters( + ` + SELECT * FROM users -- ' + WHERE id = ' $id ' + `, + dialect, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- ' + WHERE id = ' $id ' + `, + }); + }); + + it('does not consider the token to be a bind parameter if it is in a single line comment', () => { + const { sql } = mapBindParameters( + ` + SELECT * FROM users /* + WHERE id = $id + */ + WHERE id = $id + `, + dialect, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users /* + WHERE id = $id + */ + WHERE id = ? + `, + postgres: ` + SELECT * FROM users /* + WHERE id = $id + */ + WHERE id = $1 + `, + sqlite3: ` + SELECT * FROM users /* + WHERE id = $id + */ + WHERE id = $id + `, + mssql: ` + SELECT * FROM users /* + WHERE id = $id + */ + WHERE id = @id + `, + }); + }); +}); + +describe('injectReplacements (named replacements)', () => { + it('parses named replacements', () => { + const sql = injectReplacements( + `SELECT ${dialect.TICK_CHAR_LEFT}:id${dialect.TICK_CHAR_RIGHT} FROM users WHERE id = ':id' OR id = :id OR id = ''':id'''`, + dialect, + { + id: 1, + }, + ); + + expectsql(sql, { + default: `SELECT [:id] FROM users WHERE id = ':id' OR id = 1 OR id = ''':id'''`, + }); + }); + + it('throws if a named replacement is not provided as an own property', () => { + expect(() => { + injectReplacements(`SELECT * FROM users WHERE id = :toString`, dialect, { + id: 1, + }); + }).to.throw('Named replacement ":toString" has no entry in the replacement map.'); + }); + + it('parses named replacements followed by cast syntax', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE id = :id::string`, dialect, { + id: 1, + }); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = 1::string`, + }); + }); + + it('parses named replacements following JSON extraction', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE json_col->>:key`, dialect, { + key: 'name', + }); + + expectsql(sql, { + default: `SELECT * FROM users WHERE json_col->>'name'`, + mssql: `SELECT * FROM users WHERE json_col->>N'name'`, + }); + }); + + it('parses named replacements followed by a semicolon', () => { + const sql = injectReplacements('SELECT * FROM users WHERE id = :id;', dialect, { + id: 1, + }); + + expectsql(sql, { + default: 'SELECT * FROM users WHERE id = 1;', + ibmi: `SELECT * FROM users WHERE id = 1;`, // 'default' removes the ; for ibmi + }); + }); + + // this is an officially supported workaround. + // The right way to support ARRAY in replacement is https://github.com/sequelize/sequelize/issues/14410 + if (sequelize.dialect.supports.dataTypes.ARRAY) { + it('parses named replacements inside ARRAY[]', () => { + const sql = injectReplacements( + 'SELECT * FROM users WHERE id = ARRAY[:id1]::int[] OR id = ARRAY[:id1,:id2]::int[] OR id = ARRAY[:id1, :id2]::int[];', + dialect, + { + id1: 1, + id2: 4, + }, + ); + + expectsql(sql, { + default: + 'SELECT * FROM users WHERE id = ARRAY[1]::int[] OR id = ARRAY[1,4]::int[] OR id = ARRAY[1, 4]::int[];', + }); + }); + } + + it('parses single letter named replacements', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE id = :a`, dialect, { + a: 1, + }); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = 1`, + }); + }); + + it(`does not consider the token to be a replacement if it does not follow '(', ',', '=' or whitespace`, () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = fn(:id) OR id = fn('a',:id) OR id=:id OR id = :id`, + dialect, + { + id: 1, + }, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = fn(1) OR id = fn('a',1) OR id=1 OR id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a $ quoted string', () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = $tag$ :id $tag$ OR id = $$ :id $$`, + dialect, + { + id: 1, + }, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag$ :id $tag$ OR id = $$ :id $$`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a nested $ quoted string', () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = $tag1$ $tag2$ :id $tag2$ $tag1$`, + dialect, + { + id: 1, + }, + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag1$ $tag2$ :id $tag2$ $tag1$`, + }); + }); + + it('does consider the token to be a replacement if it is in between two identifiers that look like $ quoted strings', () => { + const sql = injectReplacements(`SELECT z$$ :id x$$ * FROM users`, dialect, { + id: 1, + }); + + expectsql(sql, { + default: `SELECT z$$ 1 x$$ * FROM users`, + }); + }); + + it('does consider the token to be a bind parameter if it is located after a $ quoted string', () => { + const sql = injectReplacements( + `SELECT $$ abc $$ AS string FROM users WHERE id = :id`, + dialect, + { + id: 1, + }, + ); + + expectsql(sql, { + default: `SELECT $$ abc $$ AS string FROM users WHERE id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with a backslash escaped quote', () => { + const test = () => + injectReplacements(`SELECT * FROM users WHERE id = '\\' :id' OR id = :id`, dialect, { + id: 1, + }); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' :id' OR id = :id`), + + 'mysql mariadb': toMatchSql(`SELECT * FROM users WHERE id = '\\' :id' OR id = 1`), + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with a backslash escaped quote, in dialects that support standardConformingStrings = false', () => { + if (!supportsNonStandardConformingStrings()) { + return; + } + + const test = () => + injectReplacements( + `SELECT * FROM users WHERE id = '\\' :id' OR id = :id`, + getNonStandardConfirmingStringDialect(), + { id: 1 }, + ); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' :id' OR id = :id`), + + 'mysql mariadb postgres': toMatchSql(`SELECT * FROM users WHERE id = '\\' :id' OR id = 1`), + }); + }); + + it('does not consider the token to be a replacement if it is part of an E-prefixed string with a backslash escaped quote, in dialects that support E-prefixed strings', () => { + expectPerDialect( + () => + injectReplacements(`SELECT * FROM users WHERE id = E'\\' :id' OR id = :id`, dialect, { + id: 1, + }), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = E'\\' :id' OR id = :id`), + + 'mysql mariadb postgres': toMatchSql(`SELECT * FROM users WHERE id = E'\\' :id' OR id = 1`), + }, + ); + }); + + it('considers the token to be a replacement if it is outside a string ending with an escaped backslash', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE id = '\\\\' OR id = :id`, dialect, { + id: 1, + }); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = '\\\\' OR id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with an escaped backslash followed by a backslash escaped quote', () => { + const test = () => + injectReplacements(`SELECT * FROM users WHERE id = '\\\\\\' :id' OR id = :id`, dialect, { + id: 1, + }); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\\\\\' :id' OR id = :id`), + + 'mysql mariadb': `SELECT * FROM users WHERE id = '\\\\\\' :id' OR id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is in a single line comment', () => { + const sql = injectReplacements( + ` + SELECT * FROM users -- WHERE id = :id + WHERE id = :id + `, + dialect, + { id: 1 }, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- WHERE id = :id + WHERE id = 1 + `, + }); + }); + + it('does not consider the token to be a replacement if it is in string but a previous comment included a string delimiter', () => { + const sql = injectReplacements( + ` + SELECT * FROM users -- ' + WHERE id = ' :id ' + `, + dialect, + { id: 1 }, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- ' + WHERE id = ' :id ' + `, + }); + }); + + it('does not consider the token to be a replacement if it is in a single line comment', () => { + const sql = injectReplacements( + ` + SELECT * FROM users /* + WHERE id = :id + */ + WHERE id = :id + `, + dialect, + { id: 1 }, + ); + + expectsql(sql, { + default: ` + SELECT * FROM users /* + WHERE id = :id + */ + WHERE id = 1 + `, + }); + }); + + it('does not interpret ::x as a replacement, as it is a cast', () => { + expect(injectReplacements(`('foo')::string`, dialect, [0])).to.equal(`('foo')::string`); + }); +}); + +describe('injectReplacements (positional replacements)', () => { + it('parses positional replacements', () => { + const sql = injectReplacements( + `SELECT ${dialect.TICK_CHAR_LEFT}?${dialect.TICK_CHAR_RIGHT} FROM users WHERE id = '?' OR id = ? OR id = '''?''' OR id2 = ?`, + dialect, + [1, 2], + ); + + expectsql(sql, { + default: `SELECT [?] FROM users WHERE id = '?' OR id = 1 OR id = '''?''' OR id2 = 2`, + }); + }); + + it('parses positional replacements followed by cast syntax', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE id = ?::string`, dialect, [1]); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = 1::string`, + }); + }); + + it('parses named replacements following JSON extraction', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE json_col->>?`, dialect, ['name']); + + expectsql(sql, { + default: `SELECT * FROM users WHERE json_col->>'name'`, + mssql: `SELECT * FROM users WHERE json_col->>N'name'`, + }); + }); + + it('parses positional replacements followed by a semicolon', () => { + const sql = injectReplacements('SELECT * FROM users WHERE id = ?;', dialect, [1]); + + expectsql(sql, { + default: 'SELECT * FROM users WHERE id = 1;', + ibmi: 'SELECT * FROM users WHERE id = 1;', // 'default' removes the ; for ibmi + }); + }); + + // this is an officially supported workaround. + // The right way to support ARRAY in replacement is https://github.com/sequelize/sequelize/issues/14410 + if (sequelize.dialect.supports.dataTypes.ARRAY) { + it('parses positional replacements inside ARRAY[]', () => { + const sql = injectReplacements( + 'SELECT * FROM users WHERE id = ARRAY[?]::int[] OR ARRAY[?,?]::int[] OR ARRAY[?, ?]::int[];', + dialect, + [1, 1, 4, 1, 4], + ); + + expectsql(sql, { + default: + 'SELECT * FROM users WHERE id = ARRAY[1]::int[] OR ARRAY[1,4]::int[] OR ARRAY[1, 4]::int[];', + }); + }); + } + + it(`does not consider the token to be a replacement if it does not follow '(', ',', '=' or whitespace`, () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = fn(?) OR id = fn('a',?) OR id=? OR id = ?`, + dialect, + [2, 1, 3, 4], + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = fn(2) OR id = fn('a',1) OR id=3 OR id = 4`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a $ quoted string', () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = $tag$ ? $tag$ OR id = $$ ? $$`, + dialect, + [1], + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag$ ? $tag$ OR id = $$ ? $$`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a nested $ quoted string', () => { + const sql = injectReplacements( + `SELECT * FROM users WHERE id = $tag1$ $tag2$ ? $tag2$ $tag1$`, + dialect, + [1], + ); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = $tag1$ $tag2$ ? $tag2$ $tag1$`, + }); + }); + + it('does consider the token to be a replacement if it is in between two identifiers that look like $ quoted strings', () => { + const sql = injectReplacements(`SELECT z$$ ? x$$ * FROM users`, dialect, [1]); + + expectsql(sql, { + default: `SELECT z$$ 1 x$$ * FROM users`, + }); + }); + + it('does not consider the token to be a replacement if it is in an unnamed $ quoted string', () => { + const sql = injectReplacements(`SELECT $$ ? $$`, dialect, [1]); + + expectsql(sql, { + default: `SELECT $$ ? $$`, + }); + }); + + it('does consider the token to be a replacement if it is located after a $ quoted string', () => { + const sql = injectReplacements(`SELECT $$ abc $$ AS string FROM users WHERE id = ?`, dialect, [ + 1, + ]); + + expectsql(sql, { + default: `SELECT $$ abc $$ AS string FROM users WHERE id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with a backslash escaped quote', () => { + const test = () => + injectReplacements(`SELECT * FROM users WHERE id = '\\' ?' OR id = ?`, dialect, [1]); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' ?' OR id = ?`), + + 'mysql mariadb': toMatchSql(`SELECT * FROM users WHERE id = '\\' ?' OR id = 1`), + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with a backslash escaped quote, in dialects that support standardConformingStrings = false', () => { + if (!supportsNonStandardConformingStrings()) { + return; + } + + const test = () => + injectReplacements( + `SELECT * FROM users WHERE id = '\\' ?' OR id = ?`, + getNonStandardConfirmingStringDialect(), + [1], + ); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\' ?' OR id = ?`), + + 'mysql mariadb postgres': toMatchSql(`SELECT * FROM users WHERE id = '\\' ?' OR id = 1`), + }); + }); + + it('does not consider the token to be a replacement if it is part of an E-prefixed string with a backslash escaped quote, in dialects that support E-prefixed strings', () => { + expectPerDialect( + () => injectReplacements(`SELECT * FROM users WHERE id = E'\\' ?' OR id = ?`, dialect, [1]), + { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = E'\\' ?' OR id = ?`), + + 'mysql mariadb postgres': toMatchSql(`SELECT * FROM users WHERE id = E'\\' ?' OR id = 1`), + }, + ); + }); + + it('considers the token to be a replacement if it is outside a string ending with an escaped backslash', () => { + const sql = injectReplacements(`SELECT * FROM users WHERE id = '\\\\' OR id = ?`, dialect, [1]); + + expectsql(sql, { + default: `SELECT * FROM users WHERE id = '\\\\' OR id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is part of a string with an escaped backslash followed by a backslash escaped quote', () => { + const test = () => + injectReplacements(`SELECT * FROM users WHERE id = '\\\\\\' ?' OR id = ?`, dialect, [1]); + + expectPerDialect(test, { + default: new Error(`The following SQL query includes an unterminated string literal: +SELECT * FROM users WHERE id = '\\\\\\' ?' OR id = ?`), + + 'mysql mariadb': `SELECT * FROM users WHERE id = '\\\\\\' ?' OR id = 1`, + }); + }); + + it('does not consider the token to be a replacement if it is in a single line comment', () => { + const sql = injectReplacements( + ` + SELECT * FROM users -- WHERE id = ? + WHERE id = ? + `, + dialect, + [1], + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- WHERE id = ? + WHERE id = 1 + `, + }); + }); + + it('does not consider the token to be a replacement if it is in string but a previous comment included a string delimiter', () => { + const sql = injectReplacements( + ` + SELECT * FROM users -- ' + WHERE id = ' ? ' + `, + dialect, + [1], + ); + + expectsql(sql, { + default: ` + SELECT * FROM users -- ' + WHERE id = ' ? ' + `, + }); + }); + + it('does not consider the token to be a replacement if it is in a single line comment', () => { + const sql = injectReplacements( + ` + SELECT * FROM users /* + WHERE id = ? + */ + WHERE id = ? + `, + dialect, + [1], + ); + + expectsql(sql, { + default: ` + SELECT * FROM users /* + WHERE id = ? + */ + WHERE id = 1 + `, + }); + }); + + // https://github.com/sequelize/sequelize/issues/14358 + it('does not parse ?& and ?| operators as replacements (#14358)', async () => { + const sql = injectReplacements( + 'SELECT * FROM products WHERE tags ?& ARRAY[1] AND tags ?| ARRAY[1] AND id = ?;', + dialect, + [1], + ); + + expectsql(sql, { + default: 'SELECT * FROM products WHERE tags ?& ARRAY[1] AND tags ?| ARRAY[1] AND id = 1;', + // 'default' removes the trailing ; for ibmi, but we actually need to test it's there this time, to ensure '?;' is treated as a replacement + ';' + ibmi: 'SELECT * FROM products WHERE tags ?& ARRAY[1] AND tags ?| ARRAY[1] AND id = 1;', + }); + }); + + it('formats where clause correctly when the value is falsy', () => { + expect(injectReplacements('foo = ?', dialect, [0])).to.equal('foo = 0'); + }); + + it('formats arrays as an expression when they are wrapped with list(), instead of an ARRAY data type', async () => { + const sql = injectReplacements( + 'INSERT INTO users (username, email, created_at, updated_at) VALUES ?;', + dialect, + [ + [ + list(['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10']), + list(['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10']), + ], + ], + ); + + expectsql(sql, { + default: ` + INSERT INTO users (username, email, created_at, updated_at) + VALUES + ('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');`, + // 'default' removes the trailing ; for ibmi, but we actually need to test it's there this time, to ensure '?;' is treated as a replacement + ';' + ibmi: ` + INSERT INTO users (username, email, created_at, updated_at) + VALUES + ('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');`, + mssql: ` + INSERT INTO users (username, email, created_at, updated_at) + VALUES + (N'john', N'john@gmail.com', N'2012-01-01 10:10:10', N'2012-01-01 10:10:10'), + (N'michael', N'michael@gmail.com', N'2012-01-01 10:10:10', N'2012-01-01 10:10:10');`, + }); + }); +}); + +function supportsNonStandardConformingStrings() { + return (sequelize.dialect.constructor as typeof AbstractDialect) + .getSupportedOptions() + .includes('standardConformingStrings'); +} + +function getNonStandardConfirmingStringDialect() { + return createSequelizeInstance({ + // @ts-expect-error -- postgres-specific option + standardConformingStrings: false, + }).dialect; +} diff --git a/packages/core/test/unit/utils/utils.test.ts b/packages/core/test/unit/utils/utils.test.ts new file mode 100644 index 000000000000..340e91137cc4 --- /dev/null +++ b/packages/core/test/unit/utils/utils.test.ts @@ -0,0 +1,251 @@ +import { DataTypes, sql } from '@sequelize/core'; +import { toDefaultValue } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dialect.js'; +import { mapFinderOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/format.js'; +import { + cloneDeep, + defaults, + flattenObjectDeep, + merge, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { + pluralize, + singularize, + underscoredIf, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import { expect } from 'chai'; +import { allowDeprecationsInSuite, sequelize } from '../../support'; + +const dialect = sequelize.dialect; + +describe('Utils', () => { + describe('underscore', () => { + describe('underscoredIf', () => { + it('is defined', () => { + expect(underscoredIf).to.be.ok; + }); + + it('underscores if second param is true', () => { + expect(underscoredIf('fooBar', true)).to.equal('foo_bar'); + }); + + it("doesn't underscore if second param is false", () => { + expect(underscoredIf('fooBar', false)).to.equal('fooBar'); + }); + }); + }); + + describe('cloneDeep', () => { + it('should clone objects', () => { + const obj = { foo: 1 }; + + const clone = cloneDeep(obj); + expect(clone).to.deep.equal(obj); + expect(clone).to.not.equal(obj); + }); + + it('should clone nested objects', () => { + const obj = { foo: { bar: 1 } }; + + const clone = cloneDeep(obj); + expect(clone).to.deep.equal(obj); + expect(clone).to.not.equal(obj); + }); + + it('clones sql expression builders', () => { + const obj = [ + sql`literal test`, + sql.where({ foo: 'bar' }), + sql.col('foo'), + sql.unquote('foo'), + sql.cast('foo', 'bar'), + sql.fn('foo', 'bar'), + sql.attribute('foo'), + sql.identifier('foo'), + sql.jsonPath(sql.attribute('foo'), ['foo']), + sql.list(['a', 'b']), + ]; + + const clone = cloneDeep(obj); + expect(clone).to.deep.equal(obj); + expect(clone).to.not.equal(obj); + }); + + it('should not call clone methods on plain objects', () => { + expect(() => { + cloneDeep({ + clone() { + throw new Error('clone method called'); + }, + }); + }).to.not.throw(); + }); + + it('should not call clone methods on arrays', () => { + expect(() => { + const arr: unknown[] = []; + + // @ts-expect-error -- type error normal, you're not supposed to add methods to array instances. + arr.clone = function clone() { + throw new Error('clone method called'); + }; + + cloneDeep(arr); + }).to.not.throw(); + }); + }); + + describe('inflection', () => { + it('should pluralize/singularize words correctly', () => { + expect(pluralize('buy')).to.equal('buys'); + expect(pluralize('holiday')).to.equal('holidays'); + expect(pluralize('days')).to.equal('days'); + expect(pluralize('status')).to.equal('statuses'); + + expect(singularize('status')).to.equal('status'); + }); + }); + + describe('flattenObjectDeep', () => { + it('should return the value if it is not an object', () => { + const value = 'non-object'; + const returnedValue = flattenObjectDeep(value); + expect(returnedValue).to.equal(value); + }); + + it('should return correctly if values are null', () => { + const value = { + name: 'John', + address: { + street: 'Fake St. 123', + city: null, + coordinates: { + longitude: 55.677_962_7, + latitude: 12.596_431_3, + }, + }, + }; + const returnedValue = flattenObjectDeep(value); + expect(returnedValue).to.deep.equal({ + name: 'John', + 'address.street': 'Fake St. 123', + 'address.city': null, + 'address.coordinates.longitude': 55.677_962_7, + 'address.coordinates.latitude': 12.596_431_3, + }); + }); + }); + + describe('merge', () => { + it('does not clone sequelize models', () => { + const User = sequelize.define('user'); + const merged = merge({}, { include: [{ model: User }] }); + const merged2 = merge({}, { user: User }); + + // @ts-expect-error -- TODO: merge's return type is bad, to improve + expect(merged.include[0].model).to.equal(User); + // @ts-expect-error -- see above + expect(merged2.user).to.equal(User); + }); + }); + + describe('toDefaultValue', () => { + allowDeprecationsInSuite(['SEQUELIZE0026']); + + it('return uuid v1', () => { + expect( + /^[\da-z-]{36}$/.test( + toDefaultValue(new DataTypes.UUIDV1().toDialectDataType(dialect)) as string, + ), + ).to.equal(true); + }); + it('return uuid v4', () => { + expect( + /^[\da-z-]{36}/.test( + toDefaultValue(new DataTypes.UUIDV4().toDialectDataType(dialect)) as string, + ), + ).to.equal(true); + }); + it('return now', () => { + expect( + Object.prototype.toString.call( + toDefaultValue(new DataTypes.NOW().toDialectDataType(dialect)), + ), + ).to.equal('[object Date]'); + }); + it('return plain string', () => { + expect(toDefaultValue('Test')).to.equal('Test'); + }); + it('return plain object', () => { + expect(toDefaultValue({})).to.deep.equal({}); + }); + }); + + describe('defaults', () => { + it('defaults normal object', () => { + expect(defaults({ a: 1, c: 3 }, { b: 2 }, { c: 4, d: 4 })).to.eql({ + a: 1, + b: 2, + c: 3, + d: 4, + }); + }); + + it('defaults symbol keys', () => { + expect( + defaults( + { a: 1, [Symbol.for('eq')]: 3 }, + { b: 2 }, + { [Symbol.for('eq')]: 4, [Symbol.for('ne')]: 4 }, + ), + ).to.eql({ + a: 1, + b: 2, + [Symbol.for('eq')]: 3, + [Symbol.for('ne')]: 4, + }); + }); + }); + + describe('mapFinderOptions', () => { + it('virtual attribute dependencies', () => { + const User = sequelize.define('User', { + createdAt: { + type: DataTypes.DATE, + field: 'created_at', + }, + active: { + type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']), + }, + }); + + expect(mapFinderOptions({ attributes: ['active'] }, User).attributes).to.eql([ + ['created_at', 'createdAt'], + ]); + }); + + it('multiple calls', () => { + const User = sequelize.define('User', { + createdAt: { + type: DataTypes.DATE, + field: 'created_at', + }, + active: { + type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']), + }, + }); + + expect( + mapFinderOptions( + // @ts-expect-error -- TODO: improve mapFinderOptions typing + mapFinderOptions( + { + attributes: ['active'], + }, + User, + ), + User, + ).attributes, + ).to.eql([['created_at', 'createdAt']]); + }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/core/typedoc.json b/packages/core/typedoc.json new file mode 100644 index 000000000000..0db09f3a982f --- /dev/null +++ b/packages/core/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/decorators/legacy/index.ts", "src/index.d.ts"], + "excludeExternals": true +} diff --git a/packages/db2/.eslintrc.js b/packages/db2/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/db2/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/db2/CHANGELOG.md b/packages/db2/CHANGELOG.md new file mode 100644 index 000000000000..549ef374d9fb --- /dev/null +++ b/packages/db2/CHANGELOG.md @@ -0,0 +1,78 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +### Bug Fixes + +- **db2:** update the `ibm_db` module to 3.3.0 ([#17737](https://github.com/sequelize/sequelize/issues/17737)) ([7d386bf](https://github.com/sequelize/sequelize/commit/7d386bf22d7d0e99ca7fac11305b6ee956b7535b)) + +### Features + +- add parameter style ([#17560](https://github.com/sequelize/sequelize/issues/17560)) ([1f4bdee](https://github.com/sequelize/sequelize/commit/1f4bdee80bb7ab5a335d11681f0a9ea973277297)) + +### BREAKING CHANGES + +- the `bindParam` option has been replaced with `parameterStyle` which defaults to `ParameterStyle.BIND` + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/db2 + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/db2 + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/db2 + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- **db2:** remove unnecessary override ([#17525](https://github.com/sequelize/sequelize/issues/17525)) ([9a51a05](https://github.com/sequelize/sequelize/commit/9a51a05569cf7b3ff9e532611f1bce07839d7ce2)) +- unify returning queries ([#17157](https://github.com/sequelize/sequelize/issues/17157)) ([0a350c0](https://github.com/sequelize/sequelize/commit/0a350c0f91d0eee9c56b92f47cc23c273c9eb206)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/db2 + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +### Bug Fixes + +- set sequelize dialect type in query generator and interface ([#17285](https://github.com/sequelize/sequelize/issues/17285)) ([0227288](https://github.com/sequelize/sequelize/commit/0227288d1c6fcbf2d4f09e2efa50e4aeb9d435f2)) + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +### Features + +- **db2:** move db2 to the `@sequelize/db2` package ([#17197](https://github.com/sequelize/sequelize/issues/17197)) ([6aa4ced](https://github.com/sequelize/sequelize/commit/6aa4ceda95fb5fb96abaf6e0de3cd116ade664f9)) +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` diff --git a/packages/db2/package.json b/packages/db2/package.json new file mode 100644 index 000000000000..2d191ccedc92 --- /dev/null +++ b/packages/db2/package.json @@ -0,0 +1,45 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "DB2 for Linux, Unix and Windows Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/db2", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs db2", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "dayjs": "^1.11.19", + "ibm_db": "^3.3.4", + "lodash": "^4.17.21" + } +} diff --git a/packages/db2/src/_internal/data-types-overrides.ts b/packages/db2/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..128dcd455a27 --- /dev/null +++ b/packages/db2/src/_internal/data-types-overrides.ts @@ -0,0 +1,242 @@ +import type { AbstractDialect } from '@sequelize/core'; +import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import maxBy from 'lodash/maxBy.js'; + +dayjs.extend(utc); + +function removeUnsupportedIntegerOptions( + dataType: BaseTypes.BaseIntegerDataType, + dialect: AbstractDialect, +) { + if (dataType.options.length != null) { + // this option only makes sense for zerofill + dialect.warnDataTypeIssue( + `${dialect.name} does not support ${dataType.getDataTypeId()} with length specified. This options is ignored.`, + ); + + delete dataType.options.length; + } +} + +export class BLOB extends BaseTypes.BLOB { + toSql() { + if (this.options.length != null) { + if (this.options.length.toLowerCase() === 'tiny') { + // tiny = 255 bytes + return 'BLOB(255)'; + } + + if (this.options.length.toLowerCase() === 'medium') { + // medium = 16M + return 'BLOB(16M)'; + } + + if (this.options.length.toLowerCase() === 'long') { + // long = 2GB + return 'BLOB(2G)'; + } + + return `BLOB(${this.options.length})`; + } + + return 'BLOB(1M)'; + } +} + +export class STRING extends BaseTypes.STRING { + toSql() { + const length = this.options.length ?? 255; + + if (this.options.binary) { + if (length <= 4000) { + return `VARCHAR(${length}) FOR BIT DATA`; + } + + throw new Error( + `${this._getDialect().name} does not support the BINARY option for data types with a length greater than 4000.`, + ); + } + + if (length <= 4000) { + return `VARCHAR(${length})`; + } + + return `CLOB(${length})`; + } +} + +export class CHAR extends BaseTypes.CHAR { + toSql() { + if (this.options.binary) { + return `CHAR(${this.options.length ?? 255}) FOR BIT DATA`; + } + + return super.toSql(); + } +} + +export class TEXT extends BaseTypes.TEXT { + toSql() { + // default value for CLOB + let len = 2_147_483_647; + if (typeof this.options.length === 'string') { + switch (this.options.length.toLowerCase()) { + // 'tiny', 'medium' and 'long' are MySQL values. + // 'max' is dialect-dependant. + case 'tiny': + len = 2 ** 8; + break; + case 'medium': + len = 2 ** 24; + break; + case 'long': + // long would normally be 2 ** 32, but that's above the limit for DB2 + len = 2_147_483_647; + break; + default: + throw new Error( + `LENGTH value ${this.options.length} is not supported. Expected a number of one of the following strings: tiny, medium, long.`, + ); + } + } + + if (len > 32_672) { + return `CLOB(${len})`; + } + + return `VARCHAR(${len})`; + } +} + +export class UUID extends BaseTypes.UUID { + toSql() { + return 'CHAR(36) FOR BIT DATA'; + } +} + +export class NOW extends BaseTypes.NOW { + toSql() { + return 'CURRENT TIME'; + } +} + +export class DATE extends BaseTypes.DATE { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.precision != null && this.options.precision > 6) { + this.options.precision = 6; + } + } + + toSql() { + return `TIMESTAMP${this.options.precision != null ? `(${this.options.precision})` : ''}`; + } + + toBindableValue(date: AcceptedDate) { + date = dayjs(date).utc(false); + + return date.format('YYYY-MM-DD HH:mm:ss.SSS'); + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^8-1 check when the unsigned option is true + // TODO: add >= -2^7 =< 2^7-1 check when the unsigned option is false + + toSql(): string { + return 'SMALLINT'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^16-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'INTEGER'; + } + + return 'SMALLINT'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^24-1 check when the unsigned option is true + // TODO: add >= -2^23 =< 2^23-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^32-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'BIGINT'; + } + + return 'INTEGER'; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } +} + +export class FLOAT extends BaseTypes.FLOAT { + // TODO: add check constraint >= 0 if unsigned is true + + getNumberSqlTypeName() { + return 'REAL'; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + // TODO: add check constraint >= 0 if unsigned is true + + getNumberSqlTypeName() { + return 'DOUBLE'; + } +} + +export class DECIMAL extends BaseTypes.DECIMAL { + // TODO: add check constraint >= 0 if unsigned is true +} + +export class ENUM extends BaseTypes.ENUM { + toSql() { + const minLength = maxBy(this.options.values, value => value.length)?.length ?? 0; + + // db2 does not have an ENUM type, we use VARCHAR instead. + return `VARCHAR(${Math.max(minLength, 255)})`; + } +} diff --git a/packages/db2/src/connection-manager.ts b/packages/db2/src/connection-manager.ts new file mode 100644 index 000000000000..b09658a9dd68 --- /dev/null +++ b/packages/db2/src/connection-manager.ts @@ -0,0 +1,136 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + ConnectionError, + ConnectionRefusedError, +} from '@sequelize/core'; +import { removeUndefined } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { inspect } from '@sequelize/utils'; +import type { ConnStr } from 'ibm_db'; +import * as IbmDb from 'ibm_db'; +import type { Db2Dialect } from './dialect.js'; + +export interface Db2Connection extends AbstractConnection, IbmDb.Database {} + +export interface Db2ConnectionOptions { + /** + * ODBC "DATABASE" parameter + */ + database?: string; + + /** + * ODBC "HOSTNAME" parameter + */ + hostname?: string; + + /** + * Additional ODBC parameters. Used to build the connection string. + */ + odbcOptions?: Record; + + /** + * ODBC "PWD" parameter + */ + password?: string; + + /** + * ODBC "PORT" parameter + */ + port?: number | string; + + /** + * Sets ODBC "Security" parameter to SSL + */ + ssl?: boolean; + + /** + * ODBC "SSLServerCertificate" parameter + */ + sslServerCertificate?: string; + + /** + * ODBC "UID" parameter + */ + username?: string; +} + +export type IbmDbModule = typeof IbmDb; + +/** + * DB2 Connection Manager + * + * Get connections, validate and disconnect them. + * AbstractConnectionManager pooling use it to handle DB2 specific connections + * Use https://github.com/ibmdb/node-ibm_db to connect with DB2 server + */ +export class Db2ConnectionManager extends AbstractConnectionManager { + readonly #lib: IbmDbModule; + + constructor(dialect: Db2Dialect) { + super(dialect); + this.#lib = this.dialect.options.ibmDbModule ?? IbmDb; + } + + /** + * Connects to DB2 databases based on config. + * + * @param config + */ + async connect(config: ConnectionOptions): Promise { + const connectionConfig: Record = removeUndefined({ + DATABASE: config.database, + HOSTNAME: config.hostname, + PORT: config.port ? String(config.port) : '50000', + UID: config.username, + PWD: config.password, + SSLServerCertificate: config.sslServerCertificate, + }); + + if (config.ssl) { + connectionConfig.Security = 'SSL'; + } + + if (config.odbcOptions) { + for (const optionName of Object.keys(config.odbcOptions)) { + if (connectionConfig[optionName]) { + throw new Error( + `Key ${inspect(optionName)} in "odbcOptions" was already set by a built-in option`, + ); + } + + connectionConfig[optionName] = config.odbcOptions[optionName]; + } + } + + // TODO: add relevant Database options to the connection options of this dialect + const connection: Db2Connection = new this.#lib.Database(); + + return new Promise((resolve, reject) => { + // ibm_db's typings for the OBDC connection string are missing many properties + connection.open(connectionConfig as unknown as ConnStr, error => { + if (error) { + if (error.message && error.message.includes('SQL30081N')) { + return void reject(new ConnectionRefusedError(error)); + } + + return void reject(new ConnectionError(error)); + } + + return void resolve(connection); + }); + }); + } + + async disconnect(connection: Db2Connection) { + // Don't disconnect a connection that is already disconnected + if (!connection.connected) { + return; + } + + await connection.close(); + } + + validate(connection: Db2Connection): boolean { + return connection.connected; + } +} diff --git a/packages/db2/src/dialect.ts b/packages/db2/src/dialect.ts new file mode 100644 index 000000000000..606ef483116a --- /dev/null +++ b/packages/db2/src/dialect.ts @@ -0,0 +1,138 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import { createUnspecifiedOrderedBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { Db2ConnectionOptions, IbmDbModule } from './connection-manager.js'; +import { Db2ConnectionManager } from './connection-manager.js'; +import { Db2QueryGenerator } from './query-generator.js'; +import { Db2QueryInterface } from './query-interface.js'; +import { Db2Query } from './query.js'; + +export interface Db2DialectOptions { + /** + * The ibm_db library to use. + * If not provided, the ibm_db npm library will be used. + * Must be compatible with the ibm_db npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + ibmDbModule?: IbmDbModule; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + ibmDbModule: undefined, +}); + +const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + database: undefined, + hostname: undefined, + odbcOptions: undefined, + password: undefined, + port: undefined, + ssl: undefined, + sslServerCertificate: undefined, + username: undefined, +}); + +export class Db2Dialect extends AbstractDialect { + static readonly supports = AbstractDialect.extendSupport({ + migrations: false, + schemas: true, + finalTable: true, + autoIncrement: { + defaultValue: false, + }, + alterColumn: { + unique: false, + }, + index: { + collate: false, + using: false, + where: true, + include: true, + }, + constraints: { + onUpdate: false, + }, + tmpTableTrigger: true, + dataTypes: { + COLLATE_BINARY: true, + TIME: { + precision: false, + }, + }, + removeColumn: { + cascade: true, + }, + renameTable: { + changeSchema: false, + changeSchemaAndTable: false, + }, + createSchema: { + authorization: true, + }, + connectionTransactionMethods: true, + startTransaction: { + useBegin: true, + }, + }); + + readonly connectionManager: Db2ConnectionManager; + readonly queryGenerator: Db2QueryGenerator; + readonly queryInterface: Db2QueryInterface; + readonly Query = Db2Query; + + constructor(sequelize: Sequelize, options: Db2DialectOptions) { + super({ + dataTypesDocumentationUrl: + 'https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0008478.html', + identifierDelimiter: '"', + minimumDatabaseVersion: '1.0.0', + name: 'db2', + options, + sequelize, + dataTypeOverrides: DataTypes, + }); + + this.connectionManager = new Db2ConnectionManager(this); + this.queryGenerator = new Db2QueryGenerator(this); + this.queryInterface = new Db2QueryInterface(this); + + this.registerDataTypeParser(['CHAR () FOR BIT DATA', 'VARCHAR () FOR BIT DATA'], value => { + return value.toString(); + }); + + this.registerDataTypeParser(['TIMESTAMP'], value => { + // values are returned as UTC, but the UTC Offset is left unspecified. + return `${value}+00`; + }); + } + + createBindCollector() { + return createUnspecifiedOrderedBindCollector(); + } + + escapeBuffer(buffer: Buffer): string { + return `BLOB(${this.queryGenerator.escape(buffer.toString())})`; + } + + getDefaultSchema(): string { + return this.sequelize.options.replication.write.username?.toUpperCase() ?? ''; + } + + parseConnectionUrl(): Db2ConnectionOptions { + throw new Error( + 'The "url" option is not supported by the Db2 dialect. Instead, please use the "odbcOptions" option.', + ); + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/db2/src/index.mjs b/packages/db2/src/index.mjs new file mode 100644 index 000000000000..77081db47e34 --- /dev/null +++ b/packages/db2/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const Db2ConnectionManager = Pkg.Db2ConnectionManager; +export const Db2Dialect = Pkg.Db2Dialect; +export const Db2QueryGenerator = Pkg.Db2QueryGenerator; +export const Db2QueryInterface = Pkg.Db2QueryInterface; +export const Db2Query = Pkg.Db2Query; diff --git a/packages/db2/src/index.ts b/packages/db2/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/db2/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/db2/src/query-generator-typescript.internal.ts b/packages/db2/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..6d0f5b8e3b16 --- /dev/null +++ b/packages/db2/src/query-generator-typescript.internal.ts @@ -0,0 +1,258 @@ +import type { + ConstraintType, + DropSchemaQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator, Op } from '@sequelize/core'; +import { + DROP_SCHEMA_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import { randomBytes } from 'node:crypto'; +import type { Db2Dialect } from './dialect.js'; +import { Db2QueryGeneratorInternal } from './query-generator.internal.js'; + +/** + * Temporary class to ease the TypeScript migration + */ +export class Db2QueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: Db2QueryGeneratorInternal; + + constructor( + dialect: Db2Dialect, + internals: Db2QueryGeneratorInternal = new Db2QueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + internals.whereSqlBuilder.setOperatorKeyword(Op.regexp, 'REGEXP_LIKE'); + internals.whereSqlBuilder.setOperatorKeyword(Op.notRegexp, 'NOT REGEXP_LIKE'); + + this.#internals = internals; + } + + dropSchemaQuery(schemaName: string, options?: DropSchemaQueryOptions): string { + if (options) { + rejectInvalidOptions( + 'dropSchemaQuery', + this.dialect, + DROP_SCHEMA_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `DROP SCHEMA ${this.quoteIdentifier(schemaName)} RESTRICT`; + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + let schemasToSkip = this.#internals.getTechnicalSchemaNames(); + if (options && Array.isArray(options?.skip)) { + schemasToSkip = [...schemasToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT SCHEMANAME AS "schema" FROM SYSCAT.SCHEMATA', + `WHERE SCHEMANAME NOT LIKE 'SYS%' AND SCHEMANAME NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT COLNAME AS "Name",', + 'TABNAME AS "Table",', + 'TABSCHEMA AS "Schema",', + 'TYPENAME AS "Type",', + 'LENGTH AS "Length",', + 'SCALE AS "Scale",', + 'NULLS AS "IsNull",', + 'DEFAULT AS "Default",', + 'COLNO AS "Colno",', + 'IDENTITY AS "IsIdentity",', + 'KEYSEQ AS "KeySeq",', + 'REMARKS AS "Comment"', + 'FROM SYSCAT.COLUMNS', + `WHERE TABNAME = ${this.escape(table.tableName)}`, + `AND TABSCHEMA = ${this.escape(table.schema)}`, + ]); + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT TABNAME AS "tableName",', + 'TRIM(TABSCHEMA) AS "schema"', + `FROM SYSCAT.TABLES WHERE TYPE = 'T'`, + options?.schema + ? `AND TABSCHEMA = ${this.escape(options.schema)}` + : `AND TABSCHEMA NOT LIKE 'SYS%' AND TABSCHEMA NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY TABSCHEMA, TABNAME', + ]); + } + + renameTableQuery( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableQueryOptions, + ): string { + if (options) { + rejectInvalidOptions( + 'renameTableQuery', + this.dialect, + RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + const beforeTable = this.extractTableDetails(beforeTableName); + const afterTable = this.extractTableDetails(afterTableName); + + if (beforeTable.schema !== afterTable.schema) { + throw new Error( + `Moving tables between schemas is not supported by ${this.dialect.name} dialect.`, + ); + } + + return `RENAME TABLE ${this.quoteTable(beforeTableName)} TO ${this.quoteIdentifier(afterTable.tableName)}`; + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE TABLE ${this.quoteTable(tableName)} IMMEDIATE`; + } + + #getConstraintType(type: ConstraintType): string { + switch (type) { + case 'CHECK': + return 'K'; + case 'FOREIGN KEY': + return 'F'; + case 'PRIMARY KEY': + return 'P'; + case 'UNIQUE': + return 'U'; + default: + throw new Error(`Constraint type ${type} is not supported`); + } + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT TRIM(c.TABSCHEMA) AS "constraintSchema",', + 'c.CONSTNAME AS "constraintName",', + `CASE c.TYPE WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' WHEN 'K' THEN 'CHECK' WHEN 'U' THEN 'UNIQUE' ELSE NULL END AS "constraintType",`, + 'TRIM(c.TABSCHEMA) AS "tableSchema",', + 'c.TABNAME AS "tableName",', + 'k.COLNAME AS "columnNames",', + 'TRIM(r.REFTABSCHEMA) AS "referencedTableSchema",', + 'r.REFTABNAME AS "referencedTableName",', + 'fk.COLNAME AS "referencedColumnNames",', + `CASE r.DELETERULE WHEN 'A' THEN 'NO ACTION' WHEN 'C' THEN 'CASCADE' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "deleteAction",`, + `CASE r.UPDATERULE WHEN 'A' THEN 'NO ACTION' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS "updateAction",`, + 'ck.TEXT AS "definition"', + 'FROM SYSCAT.TABCONST c', + 'LEFT JOIN SYSCAT.REFERENCES r ON c.CONSTNAME = r.CONSTNAME AND c.TABNAME = r.TABNAME AND c.TABSCHEMA = r.TABSCHEMA', + 'LEFT JOIN SYSCAT.KEYCOLUSE k ON c.CONSTNAME = k.CONSTNAME AND c.TABNAME = k.TABNAME AND c.TABSCHEMA = k.TABSCHEMA', + 'LEFT JOIN SYSCAT.KEYCOLUSE fk ON r.REFKEYNAME = fk.CONSTNAME', + 'LEFT JOIN SYSCAT.CHECKS ck ON c.CONSTNAME = ck.CONSTNAME AND c.TABNAME = ck.TABNAME AND c.TABSCHEMA = ck.TABSCHEMA', + `WHERE c.TABNAME = ${this.escape(table.tableName)}`, + `AND c.TABSCHEMA = ${this.escape(table.schema)}`, + options?.columnName ? `AND k.COLNAME = ${this.escape(options.columnName)}` : '', + options?.constraintName ? `AND c.CONSTNAME = ${this.escape(options.constraintName)}` : '', + options?.constraintType + ? `AND c.TYPE = ${this.escape(this.#getConstraintType(options.constraintType))}` + : '', + 'ORDER BY c.CONSTNAME, k.COLSEQ, fk.COLSEQ', + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT', + 'i.INDNAME AS "name",', + 'i.TABNAME AS "tableName",', + 'i.UNIQUERULE AS "keyType",', + 'i.INDEXTYPE AS "type",', + 'c.COLNAME AS "columnName",', + 'c.COLORDER AS "columnOrder"', + 'FROM SYSCAT.INDEXES i', + 'INNER JOIN SYSCAT.INDEXCOLUSE c ON i.INDNAME = c.INDNAME AND i.INDSCHEMA = c.INDSCHEMA', + `WHERE TABNAME = ${this.escape(table.tableName)}`, + `AND TABSCHEMA = ${this.escape(table.schema)}`, + 'ORDER BY i.INDNAME, c.COLSEQ;', + ]); + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + let indexName: string; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return `DROP INDEX ${this.quoteIdentifier(indexName)}`; + } + + versionQuery() { + return 'select service_level as "version" from TABLE (sysproc.env_get_inst_info()) as A'; + } + + tableExistsQuery(tableName: TableOrModel): string { + const table = this.extractTableDetails(tableName); + + return `SELECT TABNAME FROM SYSCAT.TABLES WHERE TABNAME = ${this.escape(table.tableName)} AND TABSCHEMA = ${this.escape(table.schema)}`; + } + + createSavepointQuery(savepointName: string): string { + return `SAVEPOINT ${this.quoteIdentifier(savepointName)} ON ROLLBACK RETAIN CURSORS`; + } + + generateTransactionId(): string { + return randomBytes(10).toString('hex'); + } +} diff --git a/packages/db2/src/query-generator.d.ts b/packages/db2/src/query-generator.d.ts new file mode 100644 index 000000000000..25e171cf76e3 --- /dev/null +++ b/packages/db2/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { Db2QueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class Db2QueryGenerator extends Db2QueryGeneratorTypeScript {} diff --git a/packages/db2/src/query-generator.internal.ts b/packages/db2/src/query-generator.internal.ts new file mode 100644 index 000000000000..7ec0b105215e --- /dev/null +++ b/packages/db2/src/query-generator.internal.ts @@ -0,0 +1,18 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import { formatDb2StyleLimitOffset } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import type { Db2Dialect } from './dialect.js'; + +const TECHNICAL_SCHEMA_NAMES = Object.freeze(['ERRORSCHEMA', 'NULLID', 'SQLJ']); + +export class Db2QueryGeneratorInternal< + Dialect extends Db2Dialect = Db2Dialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalSchemaNames(): readonly string[] { + return TECHNICAL_SCHEMA_NAMES; + } + + addLimitAndOffset(options: AddLimitOffsetOptions) { + return formatDb2StyleLimitOffset(options, this.queryGenerator); + } +} diff --git a/packages/db2/src/query-generator.js b/packages/db2/src/query-generator.js new file mode 100644 index 000000000000..0ac4a187942c --- /dev/null +++ b/packages/db2/src/query-generator.js @@ -0,0 +1,722 @@ +'use strict'; + +import { DataTypes, Op, ParameterStyle } from '@sequelize/core'; +import { + attributeTypeToSql, + normalizeDataType, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import { + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { removeNullishValuesFromHash } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/format.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { createBindParamGenerator } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { removeTrailingSemicolon } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import defaults from 'lodash/defaults'; +import each from 'lodash/each'; +import forEach from 'lodash/forEach'; +import forOwn from 'lodash/forOwn'; +import includes from 'lodash/includes'; +import isPlainObject from 'lodash/isPlainObject'; +import isString from 'lodash/isString'; +import startsWith from 'lodash/startsWith'; +import template from 'lodash/template'; +import { Db2QueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +const CREATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set(['uniqueKeys']); + +/* istanbul ignore next */ +function throwMethodUndefined(methodName) { + throw new Error(`The method "${methodName}" is not defined! Please add it to your sql dialect.`); +} + +export class Db2QueryGenerator extends Db2QueryGeneratorTypeScript { + constructor(dialect, internals) { + super(dialect, internals); + + this.autoGenValue = 1; + } + + createTableQuery(tableName, attributes, options) { + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + const query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes %>)'; + const primaryKeys = []; + const foreignKeys = {}; + const attrStr = []; + const commentTemplate = ` -- <%= comment %>, TableName = <%= table %>, ColumnName = <%= column %>;`; + + let commentStr = ''; + + for (const attr in attributes) { + if (Object.hasOwn(attributes, attr)) { + let dataType = attributes[attr]; + let match; + + if (dataType.includes('COMMENT ')) { + const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/); + if (commentMatch && commentMatch.length > 2) { + const commentText = commentMatch[2].replace(/COMMENT/, '').trim(); + commentStr += template( + commentTemplate, + this._templateSettings, + )({ + table: this.quoteTable(tableName), + comment: this.escape(commentText), + column: this.quoteIdentifier(attr), + }); + // remove comment related substring from dataType + dataType = commentMatch[1]; + } + } + + if (includes(dataType, 'PRIMARY KEY')) { + primaryKeys.push(attr); + + if (includes(dataType, 'REFERENCES')) { + // Db2 doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace(/PRIMARY KEY/, '')}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace(/PRIMARY KEY/, '')}`); + } + } else if (includes(dataType, 'REFERENCES')) { + // Db2 doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + foreignKeys[attr] = match[2]; + } else { + if (options && options.uniqueKeys) { + for (const ukey in options.uniqueKeys) { + if ( + options.uniqueKeys[ukey].fields.includes(attr) && + !includes(dataType, 'NOT NULL') + ) { + dataType += ' NOT NULL'; + break; + } + } + } + + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + } + + const values = { + table: this.quoteTable(tableName), + attributes: attrStr.join(', '), + }; + const pkString = primaryKeys + .map(pk => { + return this.quoteIdentifier(pk); + }) + .join(', '); + + if (options && options.uniqueKeys) { + each(options.uniqueKeys, (columns, indexName) => { + if (!isString(indexName)) { + indexName = `uniq_${tableName}_${columns.fields.join('_')}`; + } + + values.attributes += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; + }); + } + + if (pkString.length > 0) { + values.attributes += `, PRIMARY KEY (${pkString})`; + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + values.attributes += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + } + } + + return `${template(query, this._templateSettings)(values).trim()};${commentStr}`; + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + dataType = { + ...dataType, + // TODO: attributeToSQL SHOULD be using attributes in addColumnQuery + // but instead we need to pass the key along as the field here + field: key, + type: normalizeDataType(dataType.type, this.dialect), + }; + + const query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'; + const attribute = template( + '<%= key %> <%= definition %>', + this._templateSettings, + )({ + key: this.quoteIdentifier(key), + definition: this.attributeToSQL(dataType, { + context: 'addColumn', + }), + }); + + return template( + query, + this._templateSettings, + )({ + table: this.quoteTable(table), + attribute, + }); + } + + changeColumnQuery(tableName, attributes) { + const query = 'ALTER TABLE <%= tableName %> <%= query %>;'; + const attrString = []; + const constraintString = []; + + for (const attributeName in attributes) { + const attrValue = attributes[attributeName]; + let defs = [attrValue]; + if (Array.isArray(attrValue)) { + defs = attrValue; + } + + for (const definition of defs) { + if (/REFERENCES/.test(definition)) { + constraintString.push( + template( + '<%= fkName %> FOREIGN KEY (<%= attrName %>) <%= definition %>', + this._templateSettings, + )({ + fkName: this.quoteIdentifier(`${attributeName}_foreign_idx`), + attrName: this.quoteIdentifier(attributeName), + definition: definition.replace(/.+?(?=REFERENCES)/, ''), + }), + ); + } else if (startsWith(definition, 'DROP ')) { + attrString.push( + template( + '<%= attrName %> <%= definition %>', + this._templateSettings, + )({ + attrName: this.quoteIdentifier(attributeName), + definition, + }), + ); + } else { + attrString.push( + template( + '<%= attrName %> SET <%= definition %>', + this._templateSettings, + )({ + attrName: this.quoteIdentifier(attributeName), + definition, + }), + ); + } + } + } + + let finalQuery = ''; + if (attrString.length > 0) { + finalQuery += `ALTER COLUMN ${attrString.join(' ALTER COLUMN ')}`; + finalQuery += constraintString.length > 0 ? ' ' : ''; + } + + if (constraintString.length > 0) { + finalQuery += `ADD CONSTRAINT ${constraintString.join(' ADD CONSTRAINT ')}`; + } + + return template( + query, + this._templateSettings, + )({ + tableName: this.quoteTable(tableName), + query: finalQuery, + }); + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const query = 'ALTER TABLE <%= tableName %> RENAME COLUMN <%= before %> TO <%= after %>;'; + const newName = Object.keys(attributes)[0]; + + return template( + query, + this._templateSettings, + )({ + tableName: this.quoteTable(tableName), + before: this.quoteIdentifier(attrBefore), + after: this.quoteIdentifier(newName), + }); + } + + bulkInsertQuery(tableName, attrValueHashes, options, attributes) { + options ||= {}; + attributes ||= {}; + let query = 'INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>;'; + if (options.returning) { + query = + 'SELECT * FROM FINAL TABLE (INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>);'; + } + + const emptyQuery = 'INSERT INTO <%= table %>'; + const tuples = []; + const allAttributes = []; + const allQueries = []; + + let outputFragment; + const valuesForEmptyQuery = []; + + if (options.returning) { + outputFragment = ''; + } + + forEach(attrValueHashes, attrValueHash => { + // special case for empty objects with primary keys + const fields = Object.keys(attrValueHash); + const firstAttr = attributes[fields[0]]; + if ( + fields.length === 1 && + firstAttr && + firstAttr.autoIncrement && + attrValueHash[fields[0]] === null + ) { + valuesForEmptyQuery.push(`(${this.autoGenValue++})`); + + return; + } + + // normal case + forOwn(attrValueHash, (value, key) => { + if (!allAttributes.includes(key)) { + if (value === null && attributes[key] && attributes[key].autoIncrement) { + return; + } + + allAttributes.push(key); + } + }); + }); + if (valuesForEmptyQuery.length > 0) { + allQueries.push(`${emptyQuery} VALUES ${valuesForEmptyQuery.join(',')}`); + } + + if (allAttributes.length > 0) { + forEach(attrValueHashes, attrValueHash => { + tuples.push( + `(${ + // TODO: pass type of attribute & model + allAttributes + .map(key => + this.escape(attrValueHash[key] ?? null, { replacements: options.replacements }), + ) + .join(',') + })`, + ); + }); + allQueries.push(query); + } + + const replacements = { + table: this.quoteTable(tableName), + attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','), + tuples, + output: outputFragment, + }; + + const generatedQuery = template(allQueries.join(';'), this._templateSettings)(replacements); + + return generatedQuery; + } + + updateQuery(tableName, attrValueHash, where, options, attributes) { + const sql = super.updateQuery(tableName, attrValueHash, where, options, attributes); + options ||= {}; + defaults(options, this.options); + if (!options.limit) { + sql.query = `SELECT * FROM FINAL TABLE (${removeTrailingSemicolon(sql.query)});`; + + return sql; + } + + attrValueHash = removeNullishValuesFromHash(attrValueHash, options.omitNull, options); + + let bind; + let bindParam; + const parameterStyle = options?.parameterStyle ?? ParameterStyle.BIND; + const modelAttributeMap = {}; + const values = []; + + if (parameterStyle === ParameterStyle.BIND) { + bind = Object.create(null); + bindParam = createBindParamGenerator(bind); + } + + if (attributes) { + each(attributes, (attribute, key) => { + modelAttributeMap[key] = attribute; + if (attribute.field) { + modelAttributeMap[attribute.field] = attribute; + } + }); + } + + for (const key in attrValueHash) { + const value = attrValueHash[key] ?? null; + const escapedValue = this.escape(value, { + // TODO: pass model + type: modelAttributeMap[key]?.type, + replacements: options.replacements, + bindParam, + }); + + values.push(`${this.quoteIdentifier(key)}=${escapedValue}`); + } + + let query; + const whereOptions = defaults({ bindParam }, options); + + query = `UPDATE (SELECT * FROM ${this.quoteTable(tableName)} ${this.whereQuery(where, whereOptions)} FETCH NEXT ${this.escape(options.limit, undefined, { replacements: options.replacements })} ROWS ONLY) SET ${values.join(',')}`; + query = `SELECT * FROM FINAL TABLE (${query});`; + + const result = { query }; + if (parameterStyle === ParameterStyle.BIND) { + result.bind = bind; + } + + return result; + } + + upsertQuery(tableName, insertValues, updateValues, where, model, options) { + const targetTableAlias = this.quoteTable(`${tableName}_target`); + const sourceTableAlias = this.quoteTable(`${tableName}_source`); + const primaryKeysColumns = []; + const identityColumns = []; + const uniqueAttrs = []; + const tableNameQuoted = this.quoteTable(tableName); + + const modelDefinition = model.modelDefinition; + // Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed + const attributes = modelDefinition.attributes; + for (const attribute of attributes.values()) { + if (attribute.primaryKey) { + primaryKeysColumns.push(attribute.columnName); + } + + if (attribute.autoIncrement) { + identityColumns.push(attribute.columnName); + } + } + + // Add unique indexes defined by indexes option to uniqueAttrs + for (const index of model.getIndexes()) { + if (index.unique && index.fields) { + for (const field of index.fields) { + const fieldName = typeof field === 'string' ? field : field.name || field.attribute; + // TODO: "index.fields" are column names, not an attribute name. This is a bug. + if (!uniqueAttrs.includes(fieldName) && attributes.has(fieldName)) { + uniqueAttrs.push(fieldName); + } + } + } + } + + const updateKeys = Object.keys(updateValues); + const insertKeys = Object.keys(insertValues); + const insertKeysQuoted = insertKeys.map(key => this.quoteIdentifier(key)).join(', '); + const insertValuesEscaped = insertKeys + .map(key => { + return this.escape(insertValues[key], { + // TODO: pass type + // TODO: bind param + replacements: options.replacements, + model, + }); + }) + .join(', '); + const sourceTableQuery = `VALUES(${insertValuesEscaped})`; // Virtual Table + let joinCondition; + + // Filter NULL Clauses + const clauses = where[Op.or].filter(clause => { + let valid = true; + /* + * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row + */ + for (const key of Object.keys(clause)) { + if (clause[key] == null) { + valid = false; + break; + } + } + + return valid; + }); + + /* + * Generate ON condition using PK(s). + * If not, generate using UK(s). Else throw error + */ + const getJoinSnippet = array => { + return array.map(key => { + key = this.quoteIdentifier(key); + + return `${targetTableAlias}.${key} = ${sourceTableAlias}.${key}`; + }); + }; + + if (clauses.length === 0) { + throw new Error('Primary Key or Unique key should be passed to upsert query'); + } else { + // Search for primary key attribute in clauses -- Model can have two separate unique keys + for (const key in clauses) { + const keys = Object.keys(clauses[key]); + const columnName = modelDefinition.getColumnNameLoose(keys[0]); + + if (primaryKeysColumns.includes(columnName)) { + joinCondition = getJoinSnippet(primaryKeysColumns).join(' AND '); + break; + } + } + + if (!joinCondition) { + joinCondition = getJoinSnippet(uniqueAttrs).join(' AND '); + } + } + + // Remove the IDENTITY_INSERT Column from update + const filteredUpdateClauses = updateKeys + .filter(key => { + if (!identityColumns.includes(key)) { + return true; + } + + return false; + }) + .map(key => { + const value = this.escape(updateValues[key], undefined, { + replacements: options.replacements, + }); + key = this.quoteIdentifier(key); + + return `${targetTableAlias}.${key} = ${value}`; + }) + .join(', '); + const updateSnippet = + filteredUpdateClauses.length > 0 + ? `WHEN MATCHED THEN UPDATE SET ${filteredUpdateClauses}` + : ''; + + const insertSnippet = `(${insertKeysQuoted}) VALUES(${insertValuesEscaped})`; + + let query = `MERGE INTO ${tableNameQuoted} AS ${targetTableAlias} USING (${sourceTableQuery}) AS ${sourceTableAlias}(${insertKeysQuoted}) ON ${joinCondition}`; + query += ` ${updateSnippet} WHEN NOT MATCHED THEN INSERT ${insertSnippet};`; + + return query; + } + + addIndexQuery(tableName, attributes, options, rawTablename) { + if ('include' in attributes && !attributes.unique) { + throw new Error('DB2 does not support non-unique indexes with INCLUDE syntax.'); + } + + return super.addIndexQuery(tableName, attributes, options, rawTablename); + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + let template; + let changeNull = 1; + + if (attribute.type instanceof DataTypes.ENUM) { + // enums are a special case + template = attribute.type.toSql({ dialect: this.dialect }); + template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.type.options.values + .map(value => { + return this.escape(value, undefined, { replacements: options?.replacements }); + }) + .join(', ')}))`; + } else { + template = attributeTypeToSql(attribute.type, { dialect: this.dialect }); + } + + if (options && options.context === 'changeColumn' && attribute.type) { + template = `DATA TYPE ${template}`; + } else if (attribute.allowNull === false || attribute.primaryKey === true) { + template += ' NOT NULL'; + changeNull = 0; + } + + if (attribute.autoIncrement) { + let initialValue = 1; + if (attribute.initialAutoIncrement) { + initialValue = attribute.initialAutoIncrement; + } + + template += ` GENERATED BY DEFAULT AS IDENTITY(START WITH ${initialValue}, INCREMENT BY 1)`; + } + + // Blobs/texts cannot have a defaultValue + if ( + attribute.type !== 'TEXT' && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + template += ` DEFAULT ${this.escape(attribute.defaultValue, { replacements: options?.replacements, type: attribute.type })}`; + } + + if ( + attribute.unique === true && + (options?.context !== 'changeColumn' || this.dialect.supports.alterColumn.unique) + ) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + if ((!options || !options.withoutForeignKeyConstraints) && attribute.references) { + if (options && options.context === 'addColumn' && options.foreignKey) { + const attrName = this.quoteIdentifier(options.foreignKey); + const fkName = `${options.tableName}_${attrName}_fidx`; + template += `, CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`; + } + + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate && attribute.onUpdate.toUpperCase() !== 'CASCADE') { + // Db2 do not support CASCADE option for ON UPDATE clause. + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + if ( + options && + options.context === 'changeColumn' && + changeNull === 1 && + attribute.allowNull !== undefined + ) { + template = [template]; + if (attribute.allowNull) { + template.push('DROP NOT NULL'); + } else { + template.push('NOT NULL'); + } + } + + if (attribute.comment && typeof attribute.comment === 'string') { + template += ` COMMENT ${attribute.comment}`; + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = {}; + const existingConstraints = []; + let key; + let attribute; + + for (key in attributes) { + attribute = attributes[key]; + + if (attribute.references) { + if (existingConstraints.includes(this.quoteTable(attribute.references.table))) { + // no cascading constraints to a table more than once + attribute.onDelete = ''; + attribute.onUpdate = ''; + } else if (attribute.unique && attribute.unique === true) { + attribute.onDelete = ''; + attribute.onUpdate = ''; + } else { + existingConstraints.push(this.quoteTable(attribute.references.table)); + } + } + + if (key && !attribute.field && typeof attribute === 'object') { + attribute.field = key; + } + + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } + + createTrigger() { + throwMethodUndefined('createTrigger'); + } + + dropTrigger() { + throwMethodUndefined('dropTrigger'); + } + + renameTrigger() { + throwMethodUndefined('renameTrigger'); + } + + createFunction() { + throwMethodUndefined('createFunction'); + } + + dropFunction() { + throwMethodUndefined('dropFunction'); + } + + renameFunction() { + throwMethodUndefined('renameFunction'); + } + + addUniqueFields(dataValues, rawAttributes, uniqno) { + uniqno = uniqno === undefined ? 1 : uniqno; + for (const key in rawAttributes) { + if (rawAttributes[key].unique && dataValues[key] === undefined) { + if (rawAttributes[key].type instanceof DataTypes.DATE) { + dataValues[key] = new Date(); + } else if (rawAttributes[key].type instanceof DataTypes.STRING) { + dataValues[key] = `unique${uniqno++}`; + } else if (rawAttributes[key].type instanceof DataTypes.INTEGER) { + dataValues[key] = uniqno++; + } else if (rawAttributes[key].type instanceof DataTypes.BOOLEAN) { + dataValues[key] = new DataTypes.BOOLEAN(false); + } + } + } + + return uniqno; + } +} diff --git a/packages/db2/src/query-interface-typescript.internal.ts b/packages/db2/src/query-interface-typescript.internal.ts new file mode 100644 index 000000000000..78e3cf944413 --- /dev/null +++ b/packages/db2/src/query-interface-typescript.internal.ts @@ -0,0 +1,155 @@ +import type { + CommitTransactionOptions, + QiDropAllSchemasOptions, + RollbackTransactionOptions, + SetIsolationLevelOptions, + StartTransactionOptions, +} from '@sequelize/core'; +import { AbstractQueryInterface, QueryTypes, Transaction } from '@sequelize/core'; +import { START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import type { Db2Connection } from './connection-manager.js'; +import type { Db2Dialect } from './dialect.js'; +import { Db2QueryInterfaceInternal } from './query-interface.internal.js'; + +export class Db2QueryInterfaceTypeScript< + Dialect extends Db2Dialect = Db2Dialect, +> extends AbstractQueryInterface { + readonly #internalQueryInterface: Db2QueryInterfaceInternal; + + constructor(dialect: Dialect, internalQueryInterface?: Db2QueryInterfaceInternal) { + internalQueryInterface ??= new Db2QueryInterfaceInternal(dialect); + + super(dialect, internalQueryInterface); + this.#internalQueryInterface = internalQueryInterface; + } + + async dropAllSchemas(options?: QiDropAllSchemasOptions): Promise { + const skip = options?.skip || []; + const allSchemas = await this.listSchemas(options); + const schemaNames = allSchemas.filter(schemaName => !skip.includes(schemaName)); + + // if the dialect does not support "cascade", then drop all tables and routines first in a loop to avoid deadlocks and timeouts + if (options?.cascade === undefined) { + for (const schema of schemaNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropAllTables({ ...options, schema }); + + // In Db2 the routines are scoped to the schema, so we need to drop them separately for each schema + // eslint-disable-next-line no-await-in-loop + const routines = await this.sequelize.queryRaw<{ + ROUTINENAME: string; + ROUTINETYPE: 'F' | 'M' | 'P'; + }>( + `SELECT ROUTINENAME, ROUTINETYPE FROM SYSCAT.ROUTINES WHERE ROUTINESCHEMA = ${this.queryGenerator.escape(schema)}`, + { + ...options, + type: QueryTypes.SELECT, + }, + ); + for (const routine of routines) { + const type = + routine.ROUTINETYPE === 'F' + ? 'FUNCTION' + : routine.ROUTINETYPE === 'P' + ? 'PROCEDURE' + : routine.ROUTINETYPE === 'M' + ? 'METHOD' + : ''; + // eslint-disable-next-line no-await-in-loop + await this.sequelize.queryRaw( + `DROP ${type} ${this.quoteIdentifier(schema)}.${this.quoteIdentifier(routine.ROUTINENAME)}`, + options, + ); + } + + // In Db2 the triggers are scoped to the schema, so we need to drop them separately for each schema + // eslint-disable-next-line no-await-in-loop + const triggers = await this.sequelize.queryRaw<{ TRIGNAME: string }>( + `SELECT TRIGNAME FROM SYSCAT.TRIGGERS WHERE TRIGSCHEMA = ${this.queryGenerator.escape(schema)}`, + { + ...options, + type: QueryTypes.SELECT, + }, + ); + for (const trigger of triggers) { + // eslint-disable-next-line no-await-in-loop + await this.sequelize.queryRaw( + `DROP TRIGGER ${this.quoteIdentifier(schema)}.${this.quoteIdentifier(trigger.TRIGNAME)}`, + options, + ); + } + } + } + + // Drop all the schemas in a loop to avoid deadlocks and timeouts + for (const schema of schemaNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropSchema(schema, options); + } + } + + async _commitTransaction( + transaction: Transaction, + _options: CommitTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to commit a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as Db2Connection; + await connection.commitTransaction(); + } + + async _rollbackTransaction( + transaction: Transaction, + _options: RollbackTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as Db2Connection; + await connection.rollbackTransaction(); + } + + async _setIsolationLevel( + transaction: Transaction, + options: SetIsolationLevelOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error( + 'Unable to set the isolation level for a transaction without the transaction object.', + ); + } + + const level = this.#internalQueryInterface.parseIsolationLevel(options.isolationLevel); + const connection = transaction.getConnection() as Db2Connection; + connection.setIsolationLevel(level); + } + + async _startTransaction( + transaction: Transaction, + options: StartTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to start a transaction without the transaction object.'); + } + + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.sequelize.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.sequelize.dialect.supports.startTransaction, + options, + ); + } + + const connection = transaction.getConnection() as Db2Connection; + await connection.beginTransaction(); + if (options.isolationLevel) { + await transaction.setIsolationLevel(options.isolationLevel); + } + } +} diff --git a/packages/db2/src/query-interface.d.ts b/packages/db2/src/query-interface.d.ts new file mode 100644 index 000000000000..a5708e03fb36 --- /dev/null +++ b/packages/db2/src/query-interface.d.ts @@ -0,0 +1,6 @@ +import type { Db2Dialect } from './dialect.js'; +import { Db2QueryInterfaceTypeScript } from './query-interface-typescript.internal.js'; + +export class Db2QueryInterface< + Dialect extends Db2Dialect = Db2Dialect, +> extends Db2QueryInterfaceTypeScript {} diff --git a/packages/db2/src/query-interface.internal.ts b/packages/db2/src/query-interface.internal.ts new file mode 100644 index 000000000000..c8c837e8b778 --- /dev/null +++ b/packages/db2/src/query-interface.internal.ts @@ -0,0 +1,43 @@ +import { IsolationLevel } from '@sequelize/core'; +import { AbstractQueryInterfaceInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-interface-internal.js'; +import type { Db2Dialect } from './dialect.js'; + +/** + * DB2 isolation level constants used by ibm_db driver. + * + * @see https://www.ibm.com/docs/en/db2/11.5.x?topic=keywords-txnisolation + */ +enum Db2IsolationLevel { + READ_UNCOMMITTED = 1, + READ_COMMITTED = 2, + REPEATABLE_READ = 4, + SERIALIZABLE = 8, +} + +export class Db2QueryInterfaceInternal extends AbstractQueryInterfaceInternal { + constructor(readonly dialect: Db2Dialect) { + super(dialect); + } + + /** + * Parses the isolation level and returns the corresponding value for the ibm_db driver. + * + * @see https://www.ibm.com/docs/en/db2/11.5.x?topic=keywords-txnisolation + * + * @param value The isolation level to parse. + */ + parseIsolationLevel(value: IsolationLevel): Db2IsolationLevel { + switch (value) { + case IsolationLevel.READ_UNCOMMITTED: + return Db2IsolationLevel.READ_UNCOMMITTED; + case IsolationLevel.READ_COMMITTED: + return Db2IsolationLevel.READ_COMMITTED; + case IsolationLevel.REPEATABLE_READ: + return Db2IsolationLevel.REPEATABLE_READ; + case IsolationLevel.SERIALIZABLE: + return Db2IsolationLevel.SERIALIZABLE; + default: + throw new Error(`Unknown isolation level: ${value}`); + } + } +} diff --git a/packages/db2/src/query-interface.js b/packages/db2/src/query-interface.js new file mode 100644 index 000000000000..5c64444fa06d --- /dev/null +++ b/packages/db2/src/query-interface.js @@ -0,0 +1,113 @@ +'use strict'; + +import { Op, QueryTypes } from '@sequelize/core'; +import { isWhereEmpty } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { assertNoReservedBind } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import clone from 'lodash/clone'; +import intersection from 'lodash/intersection'; +import isPlainObject from 'lodash/isPlainObject'; +import { Db2QueryInterfaceTypeScript } from './query-interface-typescript.internal'; + +/** + * The interface that Sequelize uses to talk with Db2 database + */ +export class Db2QueryInterface extends Db2QueryInterfaceTypeScript { + async upsert(tableName, insertValues, updateValues, where, options) { + if (options.bind) { + assertNoReservedBind(options.bind); + } + + options = { ...options }; + + const model = options.model; + const wheres = []; + const attributes = Object.keys(insertValues); + let indexFields; + + options = clone(options); + + if (!isWhereEmpty(where)) { + wheres.push(where); + } + + // Lets combine unique keys and indexes into one + const indexes = []; + + for (const value of model.getIndexes()) { + 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, + ); + + delete options.replacements; + + return this.sequelize.queryRaw(sql, options); + } + + async addConstraint(tableName, options) { + try { + await super.addConstraint(tableName, options); + } catch (error) { + if (!error.cause) { + throw error; + } + + // Operation not allowed for reason code "7" on table "DB2INST1.users". SQLSTATE=57007 + if (error.cause.sqlcode !== -668 || error.cause.state !== '57007') { + throw error; + } + + // https://www.ibm.com/support/pages/how-verify-and-resolve-sql0668n-reason-code-7-when-accessing-table + await this.executeTableReorg(tableName); + await super.addConstraint(tableName, options); + } + } + + /** + * DB2 can put tables in the "reorg pending" state after a structure change (e.g. ALTER) + * Other changes cannot be done to these tables until the reorg has been completed. + * + * This method forces a reorg to happen now. + * + * @param {TableName} tableName - The name of the table to reorg + */ + async executeTableReorg(tableName) { + // https://www.ibm.com/support/pages/sql0668n-operating-not-allowed-reason-code-7-seen-when-querying-or-viewing-table-db2-warehouse-cloud-and-db2-cloud + return await this.sequelize.query( + `CALL SYSPROC.ADMIN_CMD('REORG TABLE ${this.queryGenerator.quoteTable(tableName)}')`, + ); + } +} diff --git a/packages/db2/src/query.d.ts b/packages/db2/src/query.d.ts new file mode 100644 index 000000000000..2cbcaad9db53 --- /dev/null +++ b/packages/db2/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class Db2Query extends AbstractQuery {} diff --git a/packages/db2/src/query.js b/packages/db2/src/query.js new file mode 100644 index 000000000000..2025b198b3b4 --- /dev/null +++ b/packages/db2/src/query.js @@ -0,0 +1,437 @@ +'use strict'; + +import { + AbstractQuery, + DatabaseError, + EmptyResultError, + ForeignKeyConstraintError, + UniqueConstraintError, + UnknownConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import forOwn from 'lodash/forOwn'; +import assert from 'node:assert'; + +const debug = logger.debugContext('sql:db2'); + +export class Db2Query extends AbstractQuery { + getInsertIdField() { + return 'id'; + } + + getSQLTypeFromJsType(value) { + if (Buffer.isBuffer(value)) { + return { ParamType: 'INPUT', DataType: 'BLOB', Data: value }; + } + + if (typeof value === 'bigint') { + // The ibm_db module does not handle bigint, send as a string instead: + return value.toString(); + } + + return value; + } + + async _run(connection, sql, parameters) { + assert(typeof sql === 'string', `sql parameter must be a string`); + + this.sql = sql; + + const complete = this._logQuery(sql, debug, parameters); + + const params = []; + if (parameters) { + forOwn(parameters, (value, key) => { + const param = this.getSQLTypeFromJsType(value, key); + params.push(param); + }); + } + + const SQL = this.sql.toUpperCase(); + let newSql = this.sql; + + // TODO: move this to Db2QueryGenerator + if ((this.isSelectQuery() || SQL.startsWith('SELECT ')) && !SQL.includes(' FROM ', 8)) { + if (this.sql.at(-1) === ';') { + newSql = this.sql.slice(0, -1); + } + + newSql += ' FROM SYSIBM.SYSDUMMY1;'; + } + + let stmt; + try { + stmt = await connection.prepare(newSql); + } catch (error) { + throw this.formatError(error); + } + + let res; + try { + // Warning: the promise version stmt.execute() does not return the same thing as stmt.execute(callback), despite the documentation. + res = await this.#execute(stmt, params); + } catch (error) { + if (error.message) { + // eslint-disable-next-line no-ex-assign -- legacy code. TODO: reformat + error = this.filterSQLError(error, this.sql, connection); + if (error === null) { + stmt.closeSync(); + + return this.formatResults([], 0); + } + } + + error.sql = sql; + stmt.closeSync(); + throw this.formatError(error, connection, parameters); + } + + const { outparams, result } = res; + + complete(); + + // map the INOUT parameters to the name provided by the dev + // this is an internal API, not yet ready for dev consumption, hence the _unsafe_ prefix. + if (outparams && this.options.bindParameterOrder && this.options._unsafe_db2Outparams) { + for (let i = 0; i < this.options.bindParameterOrder.length; i++) { + const paramName = this.options.bindParameterOrder[i]; + const paramValue = outparams[i]; + + this.options._unsafe_db2Outparams.set(paramName, paramValue); + } + } + + let data = []; + let metadata = []; + let affectedRows = 0; + if (typeof result === 'object') { + if (this.sql.startsWith('DELETE FROM ')) { + affectedRows = result.getAffectedRowsSync(); + } else { + data = result.fetchAllSync(); + metadata = result.getColumnMetadataSync(); + } + + result.closeSync(); + } + + stmt.closeSync(); + const datalen = data.length; + if (datalen > 0) { + const coltypes = {}; + for (const metadatum of metadata) { + coltypes[metadatum.SQL_DESC_NAME] = metadatum.SQL_DESC_TYPE_NAME; + } + + for (let i = 0; i < datalen; i++) { + for (const column in data[i]) { + const value = data[i][column]; + if (value === null) { + continue; + } + + const parse = this.sequelize.dialect.getParserForDatabaseDataType(coltypes[column]); + if (parse) { + data[i][column] = parse(value); + } + } + } + + if (outparams && outparams.length > 0) { + data.unshift(outparams); + } + + return this.formatResults(data, datalen, metadata); + } + + return this.formatResults(data, affectedRows); + } + + async run(sql, parameters) { + return await this._run(this.connection, sql, parameters); + } + + #execute(stmt, params) { + return new Promise((resolve, reject) => { + stmt.execute(params, (err, result, outparams) => { + if (err) { + reject(err); + } else { + resolve({ result, outparams }); + } + }); + }); + } + + filterSQLError(err, _sql, _connection) { + // This error is safe to ignore: + // [IBM][CLI Driver][DB2/LINUXX8664] SQL0605W The index was not created because an index "x" with a matching definition already exists. SQLSTATE=01550 + if (err.message.search('SQL0605W') !== -1) { + return null; + } + + return err; + } + + /** + * High level function that handles the results of a query execution. + * + * + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + * + * @param {Array} data - The result of the query execution. + * @param {Integer} rowCount - The number of affected rows. + * @param {Array} metadata - Metadata of the returned result set. + * @private + */ + formatResults(data, rowCount, metadata) { + 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() && !this.isUpsertQuery() && data.length === 0) { + throw new EmptyResultError(); + } + + // Due to Db2 returning values with every insert or update, + // we only want to map the returned values to the instance if the user wants it. + // TODO: This is a hack, and should be fixed in the future. + if (this.options.returning && Array.isArray(data) && data[0]) { + for (const attributeOrColumnName of Object.keys(data[0])) { + const modelDefinition = this.model.modelDefinition; + const attribute = modelDefinition.columns.get(attributeOrColumnName); + const updatedValue = this._parseDatabaseValue( + data[0][attributeOrColumnName], + attribute?.type, + ); + + this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, { + raw: true, + comesFromDatabase: true, + }); + } + } + } + + if (this.isUpsertQuery()) { + return [this.instance, null]; + } + + return [ + this.instance || (data && ((this.options.plain && data[0]) || data)) || undefined, + this.options.returning ? data.length : rowCount, + ]; + } + + if (this.isBulkUpdateQuery()) { + return this.options.returning ? this.handleSelectQuery(data) : rowCount; + } + + let result = this.instance; + if (this.isDescribeQuery()) { + result = {}; + for (const _result of data) { + if (_result.Default) { + _result.Default = _result.Default.replace("('", '').replace("')", '').replaceAll("'", ''); + } + + result[_result.Name] = { + type: _result.Type.toUpperCase(), + allowNull: _result.IsNull === 'Y', + defaultValue: _result.Default, + primaryKey: _result.KeySeq > 0, + autoIncrement: _result.IsIdentity === 'Y', + comment: _result.Comment, + }; + } + } else if (this.isShowIndexesQuery()) { + result = this.handleShowIndexesQuery(data); + } else if (this.isSelectQuery()) { + result = this.handleSelectQuery(data); + } else if (this.isCallQuery()) { + result = data; + } else if (this.isDeleteQuery()) { + result = rowCount; + } else if (this.isShowConstraintsQuery()) { + result = data; + } else if (this.isRawQuery()) { + // Db2 returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta + result = [data, metadata]; + } else { + result = data; + } + + return result; + } + + formatError(err, conn, parameters) { + let match; + + if (!(err && err.message)) { + err.message = 'No error message found.'; + } + + match = err.message.match( + /SQL0803N {2}An error or warning occurred. {2}One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "(\d)+" constrains table "(.*)\.(.*)" from having duplicate values for the index key./, + ); + if (match && match.length > 0) { + let uniqueIndexName = ''; + let uniqueKey = ''; + const fields = {}; + let message = err.message; + const query = `SELECT INDNAME FROM SYSCAT.INDEXES WHERE IID = ${match[1]} AND TABSCHEMA = '${match[2]}' AND TABNAME = '${match[3]}'`; + + if (Boolean(conn) && match.length > 3) { + uniqueIndexName = conn.querySync(query); + uniqueIndexName = uniqueIndexName[0].INDNAME; + } + + if (this.model && Boolean(uniqueIndexName)) { + uniqueKey = this.model + .getIndexes() + .find(index => index.unique && index.name === uniqueIndexName); + } + + if (!uniqueKey && this.options.fields) { + uniqueKey = this.options.fields[match[1] - 1]; + } + + if (uniqueKey) { + // TODO: DB2 uses a custom "column" property, but it should use "fields" instead, so column can be removed + if (this.options.where && this.options.where[uniqueKey.column] !== undefined) { + fields[uniqueKey.column] = this.options.where[uniqueKey.column]; + } else if ( + this.options.instance && + this.options.instance.dataValues && + this.options.instance.dataValues[uniqueKey.column] + ) { + fields[uniqueKey.column] = this.options.instance.dataValues[uniqueKey.column]; + } else if (parameters) { + fields[uniqueKey.column] = parameters['0']; + } + } + + if (uniqueKey && Boolean(uniqueKey.msg)) { + message = uniqueKey.msg; + } + + const errors = []; + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + match = + err.message.match( + /SQL0532N {2}A parent row cannot be deleted because the relationship "(.*)" restricts the deletion/, + ) || + err.message.match(/SQL0530N/) || + err.message.match(/SQL0531N/); + if (match && match.length > 0) { + const data = err.message.match(/(?:"([\w.]+)")/); + const constraintData = data && data.length > 0 ? data[1] : undefined; + const [, table, constraint] = constraintData.split('.'); + + return new ForeignKeyConstraintError({ + fields: null, + index: constraint, + cause: err, + table, + }); + } + + match = err.message.match(/SQL0204N {2}"(.*)" is an undefined name./); + if (match && match.length > 1) { + const constraint = match[1]; + let table = err.sql.match(/table "(.+?)"/i); + table = table ? table[1] : undefined; + + return new UnknownConstraintError({ + message: match[0], + constraint, + table, + cause: err, + }); + } + + return new DatabaseError(err); + } + + isShowOrDescribeQuery() { + let result = false; + + result ||= this.sql + .toLowerCase() + .startsWith( + "select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'", + ); + result ||= this.sql.toLowerCase().startsWith('select tablename = t.name, name = ind.name,'); + result ||= this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname'); + + return result; + } + + handleShowIndexesQuery(data) { + const indexes = data.reduce((acc, curr) => { + if (acc.has(curr.name)) { + const index = acc.get(curr.name); + if (curr.columnOrder === 'I') { + index.includes.push(curr.columnName); + } else { + index.fields.push({ + attribute: curr.columnName, + length: undefined, + order: curr.columnOrder === 'D' ? 'DESC' : curr.columnOrder === 'A' ? 'ASC' : undefined, + collate: undefined, + }); + } + + return acc; + } + + acc.set(curr.name, { + primary: curr.keyType === 'P', + fields: + curr.columnOrder === 'I' + ? [] + : [ + { + attribute: curr.columnName, + length: undefined, + order: curr.columnOrder === 'D' ? 'DESC' : 'ASC', + collate: undefined, + }, + ], + includes: curr.columnOrder === 'I' ? [curr.columnName] : [], + name: curr.name, + tableName: curr.tableName, + unique: curr.keyType === 'U', + type: curr.type, + }); + + return acc; + }, new Map()); + + return Array.from(indexes.values()); + } +} diff --git a/packages/db2/tsconfig.json b/packages/db2/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/db2/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/db2/typedoc.json b/packages/db2/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/db2/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/ibmi/.eslintrc.js b/packages/ibmi/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/ibmi/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/ibmi/CHANGELOG.md b/packages/ibmi/CHANGELOG.md new file mode 100644 index 000000000000..c70965b01689 --- /dev/null +++ b/packages/ibmi/CHANGELOG.md @@ -0,0 +1,73 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +### Features + +- add parameter style ([#17560](https://github.com/sequelize/sequelize/issues/17560)) ([1f4bdee](https://github.com/sequelize/sequelize/commit/1f4bdee80bb7ab5a335d11681f0a9ea973277297)) + +### BREAKING CHANGES + +- the `bindParam` option has been replaced with `parameterStyle` which defaults to `ParameterStyle.BIND` + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/db2-ibmi + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/db2-ibmi + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/db2-ibmi + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- unify returning queries ([#17157](https://github.com/sequelize/sequelize/issues/17157)) ([0a350c0](https://github.com/sequelize/sequelize/commit/0a350c0f91d0eee9c56b92f47cc23c273c9eb206)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/db2-ibmi + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +**Note:** Version bump only for package @sequelize/db2-ibmi + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +- feat(ibmi)!: move ibmi to the `@sequelize/ibmi` package (#17209) ([21772a5](https://github.com/sequelize/sequelize/commit/21772a5b2aa4eec952f91ba747093cb737af4af9)), closes [#17209](https://github.com/sequelize/sequelize/issues/17209) + +### Features + +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` +- Instead of installing the `odbc` package, users need to install `@sequelize/ibmi`. diff --git a/packages/ibmi/package.json b/packages/ibmi/package.json new file mode 100644 index 000000000000..d2e6b5743e58 --- /dev/null +++ b/packages/ibmi/package.json @@ -0,0 +1,45 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "DB2 for IBM i Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/db2-ibmi", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs ibmi", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "odbc": "^2.4.9" + } +} diff --git a/packages/ibmi/src/_internal/data-types-overrides.ts b/packages/ibmi/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..edc92ad7b704 --- /dev/null +++ b/packages/ibmi/src/_internal/data-types-overrides.ts @@ -0,0 +1,250 @@ +import type { AbstractDialect } from '@sequelize/core'; +import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import maxBy from 'lodash/maxBy.js'; + +dayjs.extend(utc); + +function removeUnsupportedIntegerOptions( + dataType: BaseTypes.BaseIntegerDataType, + dialect: AbstractDialect, +) { + if (dataType.options.length != null) { + // this option only makes sense for zerofill + dialect.warnDataTypeIssue( + `${dialect.name} does not support ${dataType.getDataTypeId()} with length specified. This options is ignored.`, + ); + + delete dataType.options.length; + } +} + +export class BLOB extends BaseTypes.BLOB { + toSql() { + if (this.options.length != null) { + if (this.options.length.toLowerCase() === 'tiny') { + // tiny = 255 bytes + return 'BLOB(255)'; + } + + if (this.options.length.toLowerCase() === 'medium') { + // medium = 16M + return 'BLOB(16M)'; + } + + if (this.options.length.toLowerCase() === 'long') { + // long = 2GB + return 'BLOB(2G)'; + } + + return `BLOB(${this.options.length})`; + } + + return 'BLOB(1M)'; + } +} + +export class STRING extends BaseTypes.STRING { + toSql() { + const length = this.options.length ?? 255; + + if (this.options.binary) { + if (length <= 4000) { + return `VARCHAR(${length}) FOR BIT DATA`; + } + + throw new Error( + `${this._getDialect().name} does not support the BINARY option for data types with a length greater than 4000.`, + ); + } + + if (length <= 4000) { + return `VARCHAR(${length})`; + } + + return `CLOB(${length})`; + } +} + +export class CHAR extends BaseTypes.CHAR { + toSql() { + if (this.options.binary) { + return `CHAR(${this.options.length ?? 255}) FOR BIT DATA`; + } + + return super.toSql(); + } +} + +export class TEXT extends BaseTypes.TEXT { + toSql() { + // default value for CLOB + let len = 2_147_483_647; + if (typeof this.options.length === 'string') { + switch (this.options.length.toLowerCase()) { + // 'tiny', 'medium' and 'long' are MySQL values. + // 'max' is dialect-dependant. + case 'tiny': + len = 2 ** 8; + break; + case 'medium': + len = 2 ** 24; + break; + case 'long': + // long would normally be 2 ** 32, but that's above the limit for db2 + len = 2_147_483_647; + break; + default: + throw new Error( + `LENGTH value ${this.options.length} is not supported. Expected a number of one of the following strings: tiny, medium, long.`, + ); + } + } + + if (len > 32_672) { + return `CLOB(${len})`; + } + + return `VARCHAR(${len})`; + } +} + +export class DATE extends BaseTypes.DATE { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.precision != null && this.options.precision > 6) { + this.options.precision = 6; + } + } + + toSql() { + return `TIMESTAMP${this.options.precision != null ? `(${this.options.precision})` : ''}`; + } + + toBindableValue(date: AcceptedDate) { + date = dayjs(date).utc(false); + + return date.format('YYYY-MM-DD HH:mm:ss.SSS'); + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^8-1 check when the unsigned option is true + // TODO: add >= -2^7 =< 2^7-1 check when the unsigned option is false + + toSql(): string { + return 'SMALLINT'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^16-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'INTEGER'; + } + + return 'SMALLINT'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^24-1 check when the unsigned option is true + // TODO: add >= -2^23 =< 2^23-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^32-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'BIGINT'; + } + + return 'INTEGER'; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } +} + +export class FLOAT extends BaseTypes.FLOAT { + // TODO: add check constraint >= 0 if unsigned is true + + getNumberSqlTypeName() { + return 'REAL'; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + // TODO: add check constraint >= 0 if unsigned is true + + getNumberSqlTypeName() { + return 'DOUBLE'; + } +} + +export class DECIMAL extends BaseTypes.DECIMAL { + // TODO: add check constraint >= 0 if unsigned is true +} + +export class ENUM extends BaseTypes.ENUM { + toSql() { + const minLength = maxBy(this.options.values, value => value.length)?.length ?? 0; + + // db2 does not have an ENUM type, we use VARCHAR instead. + return `VARCHAR(${Math.max(minLength, 255)})`; + } +} + +export class UUID extends BaseTypes.UUID { + toSql() { + return 'CHAR(36)'; + } +} + +export class BOOLEAN extends BaseTypes.BOOLEAN { + escape(value: boolean | unknown): string { + return value ? '1' : '0'; + } + + toBindableValue(value: boolean | unknown): unknown { + return value ? 1 : 0; + } + + toSql() { + return 'SMALLINT'; + } +} diff --git a/packages/ibmi/src/connection-manager.ts b/packages/ibmi/src/connection-manager.ts new file mode 100644 index 000000000000..9218dbcb93e7 --- /dev/null +++ b/packages/ibmi/src/connection-manager.ts @@ -0,0 +1,126 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { AbstractConnectionManager, ConnectionRefusedError } from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import type { ConnectionParameters, NodeOdbcError, Connection as OdbcConnection } from 'odbc'; +import * as Odbc from 'odbc'; +import type { IBMiDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:ibmi'); + +export type OdbcModule = typeof Odbc; + +export interface IBMiConnection extends AbstractConnection, OdbcConnection { + // properties of ObdcConnection, but not declared in their typings + connected: boolean; +} + +export interface IBMiConnectionOptions extends Omit { + /** + * Any extra ODBC connection string parts to use. + * + * Will be prepended to the connection string parts produced by the other options. + */ + odbcConnectionString?: string; + + /** + * The ODBC "DSN" part of the connection string. + */ + dataSourceName?: string; + + /** + * The ODBC "UID" part of the connection string. + */ + username?: string; + + /** + * The ODBC "PWD" part of the connection string. + */ + password?: string; + + /** + * The ODBC "SYSTEM" part of the connection string. + */ + system?: string; +} + +export class IBMiConnectionManager extends AbstractConnectionManager { + readonly #lib: OdbcModule; + + constructor(dialect: IBMiDialect) { + super(dialect); + this.#lib = this.dialect.options.odbcModule ?? Odbc; + } + + async connect(config: ConnectionOptions): Promise { + const connectionKeywords = []; + if (config.odbcConnectionString) { + connectionKeywords.push(config.odbcConnectionString); + } + + if (config.dataSourceName) { + connectionKeywords.push(`DSN=${config.dataSourceName}`); + } + + if (config.username) { + connectionKeywords.push(`UID=${config.username}`); + } + + if (config.password) { + connectionKeywords.push(`PWD=${config.password}`); + } + + if (config.system) { + connectionKeywords.push(`SYSTEM=${config.system}`); + } + + if (connectionKeywords.length === 0) { + throw new Error('No connection information provided.'); + } + + let connectionString: string = connectionKeywords.join(';'); + if (!connectionString.endsWith(';')) { + connectionString += ';'; + } + + let connection; + try { + connection = (await this.#lib.connect(connectionString)) as IBMiConnection; + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + + if (error.toString().includes('Error connecting to the database')) { + throw new ConnectionRefusedError(error); + } + + throw error; + } + + return connection; + } + + async disconnect(connection: IBMiConnection): Promise { + if (!this.validate(connection)) { + debug('Tried to disconnect, but connection was already closed.'); + + return; + } + + await new Promise((resolve, reject) => { + connection.close((error: NodeOdbcError) => { + if (error) { + return void reject(error); + } + + resolve(); + + return undefined; + }); + }); + } + + validate(connection: IBMiConnection): boolean { + return connection.connected; + } +} diff --git a/packages/ibmi/src/dialect.ts b/packages/ibmi/src/dialect.ts new file mode 100644 index 000000000000..47a1299b4755 --- /dev/null +++ b/packages/ibmi/src/dialect.ts @@ -0,0 +1,128 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import { createUnspecifiedOrderedBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { IBMiConnectionOptions, OdbcModule } from './connection-manager.js'; +import { IBMiConnectionManager } from './connection-manager.js'; +import { IBMiQueryGenerator } from './query-generator.js'; +import { IBMiQueryInterface } from './query-interface.js'; +import { IBMiQuery } from './query.js'; + +export interface IbmiDialectOptions { + /** + * The odbc library to use. + * If not provided, the odbc npm library will be used. + * Must be compatible with the odbc npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + odbcModule?: OdbcModule; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + odbcModule: undefined, +}); + +const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + connectionTimeout: undefined, + loginTimeout: undefined, + username: undefined, + system: undefined, + odbcConnectionString: undefined, + dataSourceName: undefined, + password: undefined, +}); + +export class IBMiDialect extends AbstractDialect { + static readonly supports = AbstractDialect.extendSupport({ + 'VALUES ()': true, + 'ON DUPLICATE KEY': false, + connectionTransactionMethods: true, + bulkDefault: true, + index: { + using: false, + where: true, + functionBased: true, + collate: false, + include: false, + }, + constraints: { + onUpdate: false, + }, + groupedLimit: false, + upserts: false, + schemas: true, + dataTypes: { + COLLATE_BINARY: true, + }, + removeColumn: { + cascade: true, + }, + renameTable: { + changeSchema: false, + changeSchemaAndTable: false, + }, + createSchema: { + authorization: true, + }, + dropSchema: { + cascade: true, + ifExists: true, + }, + }); + + readonly connectionManager: IBMiConnectionManager; + readonly queryGenerator: IBMiQueryGenerator; + readonly queryInterface: IBMiQueryInterface; + readonly Query = IBMiQuery; + + constructor(sequelize: Sequelize, options: IbmiDialectOptions) { + console.warn( + 'The IBMi dialect is experimental and usage is at your own risk. Its development is exclusively community-driven and not officially supported by the maintainers.', + ); + + super({ + dataTypesDocumentationUrl: + 'https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_73/db2/rbafzch2data.htm', + identifierDelimiter: '"', + minimumDatabaseVersion: '7.3.0', + name: 'ibmi', + options, + sequelize, + dataTypeOverrides: DataTypes, + }); + + this.connectionManager = new IBMiConnectionManager(this); + this.queryGenerator = new IBMiQueryGenerator(this); + this.queryInterface = new IBMiQueryInterface(this); + } + + createBindCollector() { + return createUnspecifiedOrderedBindCollector(); + } + + escapeBuffer(buffer: Buffer): string { + return `BLOB(X'${buffer.toString('hex')}')`; + } + + getDefaultSchema(): string { + // TODO: what is the default schema in IBMi? + return ''; + } + + parseConnectionUrl(): IBMiConnectionOptions { + throw new Error( + 'The "url" option is not supported by the Db2 dialect. Instead, please use the "odbcConnectionString" option.', + ); + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/ibmi/src/index.mjs b/packages/ibmi/src/index.mjs new file mode 100644 index 000000000000..45364f9986d2 --- /dev/null +++ b/packages/ibmi/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const IBMiConnectionManager = Pkg.IBMiConnectionManager; +export const IBMiDialect = Pkg.IBMiDialect; +export const IBMiQueryGenerator = Pkg.IBMiQueryGenerator; +export const IBMiQueryInterface = Pkg.IBMiQueryInterface; +export const IBMiQuery = Pkg.IBMiQuery; diff --git a/packages/ibmi/src/index.ts b/packages/ibmi/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/ibmi/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/ibmi/src/query-generator-typescript.internal.ts b/packages/ibmi/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..976d7bd34046 --- /dev/null +++ b/packages/ibmi/src/query-generator-typescript.internal.ts @@ -0,0 +1,233 @@ +import type { + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator } from '@sequelize/core'; +import { + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import type { IBMiDialect } from './dialect.js'; +import { IBMiQueryGeneratorInternal } from './query-generator.internal.js'; + +const REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS = new Set(['ifExists']); + +/** + * Temporary class to ease the TypeScript migration + */ +export class IBMiQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: IBMiQueryGeneratorInternal; + + constructor( + dialect: IBMiDialect, + internals: IBMiQueryGeneratorInternal = new IBMiQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + this.#internals = internals; + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + return joinSQLFragments([ + `SELECT DISTINCT SCHEMA_NAME AS "schema" FROM QSYS2.SYSSCHEMAAUTH WHERE GRANTEE = CURRENT USER`, + `AND SCHEMA_NAME NOT LIKE 'Q%' AND SCHEMA_NAME NOT LIKE 'SYS%'`, + options?.skip && Array.isArray(options.skip) && options.skip.length > 0 + ? `AND SCHEMA_NAME NOT IN (${options?.skip.map(schema => this.escape(schema)).join(', ')})` + : '', + ]); + } + + describeTableQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT', + 'QSYS2.SYSCOLUMNS.*,', + 'QSYS2.SYSCST.CONSTRAINT_NAME,', + 'QSYS2.SYSCST.CONSTRAINT_TYPE', + 'FROM QSYS2.SYSCOLUMNS', + 'LEFT OUTER JOIN QSYS2.SYSCSTCOL', + 'ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA', + 'AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME', + 'AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME', + 'LEFT JOIN QSYS2.SYSCST', + 'ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME', + 'WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA =', + table.schema ? this.escape(table.schema) : 'CURRENT SCHEMA', + 'AND QSYS2.SYSCOLUMNS.TABLE_NAME =', + this.escape(table.tableName), + ]); + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT TABLE_NAME AS "tableName",', + 'TABLE_SCHEMA AS "schema"', + `FROM QSYS2.SYSTABLES WHERE TABLE_TYPE = 'T'`, + options?.schema + ? `AND TABLE_SCHEMA = ${this.escape(options.schema)}` + : `AND TABLE_SCHEMA NOT LIKE 'Q%' AND TABLE_SCHEMA NOT LIKE 'SYS%'`, + 'ORDER BY TABLE_SCHEMA, TABLE_NAME', + ]); + } + + renameTableQuery( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableQueryOptions, + ): string { + if (options) { + rejectInvalidOptions( + 'renameTableQuery', + this.dialect, + RENAME_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + const beforeTable = this.extractTableDetails(beforeTableName); + const afterTable = this.extractTableDetails(afterTableName); + + if (beforeTable.schema !== afterTable.schema) { + throw new Error( + `Moving tables between schemas is not supported by ${this.dialect.name} dialect.`, + ); + } + + return `RENAME TABLE ${this.quoteTable(beforeTableName)} TO ${this.quoteIdentifier(afterTable.tableName)}`; + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE TABLE ${this.quoteTable(tableName)} IMMEDIATE`; + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT c.CONSTRAINT_SCHEMA AS "constraintSchema",', + 'c.CONSTRAINT_NAME AS "constraintName",', + 'c.CONSTRAINT_TYPE AS "constraintType",', + 'c.TABLE_SCHEMA AS "tableSchema",', + 'c.TABLE_NAME AS "tableName",', + 'k.COLUMN_NAME AS "columnNames",', + 'fk.TABLE_SCHEMA AS "referencedTableSchema",', + 'fk.TABLE_NAME AS "referencedTableName",', + 'fk.COLUMN_NAME AS "referencedColumnNames",', + 'r.DELETE_RULE AS "deleteRule",', + 'r.UPDATE_RULE AS "updateRule",', + 'ch.CHECK_CLAUSE AS "definition",', + 'c.IS_DEFERRABLE AS "isDeferrable",', + 'c.INITIALLY_DEFERRED AS "initiallyDeferred"', + 'FROM QSYS2.SYSCST c', + 'LEFT JOIN QSYS2.SYSREFCST r ON c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA', + 'LEFT JOIN QSYS2.SYSKEYCST k ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA', + 'LEFT JOIN QSYS2.SYSKEYCST fk ON r.UNIQUE_CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.UNIQUE_CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA', + 'LEFT JOIN QSYS2.SYSCHKCST ch ON c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA', + `WHERE c.TABLE_NAME = ${this.escape(table.tableName)}`, + 'AND c.TABLE_SCHEMA =', + table.schema ? this.escape(table.schema) : 'CURRENT SCHEMA', + options?.columnName ? `AND k.COLUMN_NAME = ${this.escape(options.columnName)}` : '', + options?.constraintName + ? `AND c.CONSTRAINT_NAME = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.CONSTRAINT_TYPE = ${this.escape(options.constraintType)}` + : '', + 'ORDER BY c.CONSTRAINT_NAME, k.ORDINAL_POSITION, fk.ORDINAL_POSITION', + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + // TODO [+odbc]: check if the query also works when capitalized (for consistency) + return joinSQLFragments([ + 'select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA,', + 'QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and', + 'QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where', + 'QSYS2.SYSCSTCOL.TABLE_SCHEMA =', + table.schema ? this.escape(table.schema) : 'CURRENT SCHEMA', + `and QSYS2.SYSCSTCOL.TABLE_NAME = ${this.escape(table.tableName)} union select QSYS2.SYSKEYS.INDEX_NAME AS NAME,`, + `QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS`, + 'left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA =', + table.schema ? this.escape(table.schema) : 'CURRENT SCHEMA', + 'and QSYS2.SYSINDEXES.TABLE_NAME =', + this.escape(table.tableName), + ]); + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + let indexName: string; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return joinSQLFragments([ + 'BEGIN', + options?.ifExists + ? `IF EXISTS (SELECT * FROM QSYS2.SYSINDEXES WHERE INDEX_NAME = ${this.quoteIdentifier(indexName)}) THEN` + : '', + `DROP INDEX ${this.quoteIdentifier(indexName)};`, + 'COMMIT;', + options?.ifExists ? 'END IF;' : '', + 'END', + ]); + } + + // Version queries + versionQuery() { + return 'SELECT CONCAT(OS_VERSION, CONCAT(\'.\', OS_RELEASE)) AS "version" FROM SYSIBMADM.ENV_SYS_INFO'; + } + + tableExistsQuery(tableName: TableOrModel): string { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + `SELECT TABLE_NAME FROM QSYS2.SYSTABLES WHERE TABLE_NAME = ${this.escape(table.tableName)} AND TABLE_SCHEMA = `, + table.schema ? this.escape(table.schema) : 'CURRENT SCHEMA', + ]); + } + + createSavepointQuery(savepointName: string): string { + return `SAVEPOINT ${this.quoteIdentifier(savepointName)} ON ROLLBACK RETAIN CURSORS`; + } +} diff --git a/packages/ibmi/src/query-generator.d.ts b/packages/ibmi/src/query-generator.d.ts new file mode 100644 index 000000000000..97334f5b7979 --- /dev/null +++ b/packages/ibmi/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { IBMiQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class IBMiQueryGenerator extends IBMiQueryGeneratorTypeScript {} diff --git a/packages/ibmi/src/query-generator.internal.ts b/packages/ibmi/src/query-generator.internal.ts new file mode 100644 index 000000000000..d550145e0acd --- /dev/null +++ b/packages/ibmi/src/query-generator.internal.ts @@ -0,0 +1,12 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import { formatDb2StyleLimitOffset } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import type { IBMiDialect } from './dialect.js'; + +export class IBMiQueryGeneratorInternal< + Dialect extends IBMiDialect = IBMiDialect, +> extends AbstractQueryGeneratorInternal { + addLimitAndOffset(options: AddLimitOffsetOptions) { + return formatDb2StyleLimitOffset(options, this.queryGenerator); + } +} diff --git a/packages/ibmi/src/query-generator.js b/packages/ibmi/src/query-generator.js new file mode 100644 index 000000000000..534567bab6ac --- /dev/null +++ b/packages/ibmi/src/query-generator.js @@ -0,0 +1,466 @@ +'use strict'; + +import { DataTypes } from '@sequelize/core'; +import { + attributeTypeToSql, + normalizeDataType, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import { + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { BaseSqlExpression } from '@sequelize/core/_non-semver-use-at-your-own-risk_/expression-builders/base-sql-expression.js'; +import { conformIndex } from '@sequelize/core/_non-semver-use-at-your-own-risk_/model-internals.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { + nameIndex, + removeTrailingSemicolon, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import each from 'lodash/each'; +import isPlainObject from 'lodash/isPlainObject'; +import util from 'node:util'; +import { IBMiQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +const typeWithoutDefault = new Set(['BLOB']); + +const CREATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set(['uniqueKeys']); + +export class IBMiQueryGenerator extends IBMiQueryGeneratorTypeScript { + // Table queries + createTableQuery(tableName, attributes, options) { + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + const primaryKeys = []; + const foreignKeys = Object.create(null); + const attrStr = []; + + for (const attr in attributes) { + if (!Object.hasOwn(attributes, attr)) { + continue; + } + + const dataType = attributes[attr]; + + if (dataType.includes('PRIMARY KEY')) { + primaryKeys.push(attr); + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + + let attributesClause = attrStr.join(', '); + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + if (options?.uniqueKeys) { + // only need to sort primary keys once, don't do it in place + const sortedPrimaryKeys = [...primaryKeys]; + sortedPrimaryKeys.sort(); + + each(options.uniqueKeys, (columns, indexName) => { + // sort the columns for each unique key, so they can be easily compared + // with the sorted primary key fields + const sortedColumnFields = [...columns.fields]; + sortedColumnFields.sort(); + // if primary keys === unique keys, then skip adding new constraint + const uniqueIsPrimary = + sortedColumnFields.length === primaryKeys.length && + sortedColumnFields.every((value, index) => { + return value === sortedPrimaryKeys[index]; + }); + if (uniqueIsPrimary) { + return true; + } + + if (typeof indexName !== 'string') { + indexName = `uniq_${tableName}_${columns.fields.join('_')}`; + } + + attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; + }); + } + + if (pkString.length > 0) { + attributesClause += `, PRIMARY KEY (${pkString})`; + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + } + } + + const quotedTable = this.quoteTable(tableName); + + return `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' + BEGIN END; + CREATE TABLE ${quotedTable} (${attributesClause}); + END`; + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + dataType = { + ...dataType, + // TODO: attributeToSQL SHOULD be using attributes in addColumnQuery + // but instead we need to pass the key along as the field here + field: key, + type: normalizeDataType(dataType.type, this.dialect), + }; + + const definition = this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key, + }); + + return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition}`; + } + + changeColumnQuery(tableName, attributes) { + const attrString = []; + const constraintString = []; + + for (const attributeName in attributes) { + let definition = attributes[attributeName]; + if (definition.includes('REFERENCES')) { + const attrName = this.quoteIdentifier(attributeName); + definition = definition.replace(/.+?(?=REFERENCES)/, ''); + const foreignKey = this.quoteIdentifier(`${attributeName}`); + constraintString.push(`${foreignKey} FOREIGN KEY (${attrName}) ${definition}`); + } else { + attrString.push(`"${attributeName}" SET DATA TYPE ${definition}`); + } + } + + let finalQuery = ''; + if (attrString.length) { + finalQuery += `ALTER COLUMN ${attrString.join(', ')}`; + finalQuery += constraintString.length ? ' ' : ''; + } + + if (constraintString.length) { + finalQuery += `ADD CONSTRAINT ${constraintString.join(', ')}`; + } + + return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery}`; + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const attrString = []; + + for (const attrName in attributes) { + const definition = attributes[attrName]; + attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); + } + + return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`; + } + + /* + Returns an add index query. + Parameters: + - tableName -> Name of an existing table, possibly with schema. + - options: + - type: UNIQUE|FULLTEXT|SPATIAL + - name: The name of the index. Default is
__ + - fields: An array of attributes as string or as hash. + If the attribute is a hash, it must have the following content: + - name: The name of the attribute/column + - length: An integer. Optional + - order: 'ASC' or 'DESC'. Optional + - parser + - using + - operator + - concurrently: Pass CONCURRENT so other operations run while the index is created + - rawTablename, the name of the table, without schema. Used to create the name of the index + @private + */ + addIndexQuery(tableName, _attributes, _options, rawTablename) { + let options = _options || Object.create(null); + + if (!Array.isArray(_attributes)) { + options = _attributes; + } else { + options.fields = _attributes; + } + + options.prefix = options.prefix || rawTablename || tableName; + if (options.prefix && typeof options.prefix === 'string') { + options.prefix = options.prefix.replaceAll('.', '_'); + } + + const fieldsSql = options.fields.map(field => { + if (typeof field === 'string') { + return this.quoteIdentifier(field); + } + + if (field instanceof BaseSqlExpression) { + return this.formatSqlExpression(field); + } + + let result = ''; + + if (field.attribute) { + field.name = field.attribute; + } + + if (!field.name) { + throw new Error(`The following index field has no name: ${util.inspect(field)}`); + } + + result += this.quoteIdentifier(field.name); + + if (this.dialect.supports.index.length && field.length) { + result += `(${field.length})`; + } + + if (field.order) { + result += ` ${field.order}`; + } + + return result; + }); + + if (options.include) { + throw new Error( + `The include attribute for indexes is not supported by ${this.dialect.name} dialect`, + ); + } + + if (!options.name) { + // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) + // All calls that go through sequelize should already have a name + options = nameIndex(options, options.prefix); + } + + options = conformIndex(options); + + if (!this.dialect.supports.index.type) { + delete options.type; + } + + if (options.where) { + options.where = this.whereQuery(options.where); + } + + tableName = this.quoteTable(tableName); + + let schema; + // TODO: drop this option in favor of passing the schema through tableName + if (typeof options.schema === 'string') { + schema = this.quoteIdentifiers(options.schema); + } + + // Although the function is 'addIndex', and the values are passed through + // the 'indexes' key of a table, Db2 for i doesn't allow REFERENCES to + // work against a UNIQUE INDEX, only a UNIQUE constraint. + if (options.unique) { + return `BEGIN + DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42891' + BEGIN END; + ALTER TABLE ${tableName} ADD CONSTRAINT ${this.quoteIdentifiers(options.name)} UNIQUE (${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})${options.where ? ` ${options.where}` : ''}; + END`; + } + + return `CREATE${options.unique ? ' UNIQUE' : ''} INDEX ${schema ? ` ${schema}.` : ''}${this.quoteIdentifiers(options.name)} ON ${tableName} (${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})${options.where ? ` ${options.where}` : ''}`; + } + + updateQuery(tableName, attrValueHash, where, options, columnDefinitions) { + const out = super.updateQuery(tableName, attrValueHash, where, options, columnDefinitions); + + out.query = removeTrailingSemicolon(out.query); + + return out; + } + + arithmeticQuery( + operator, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ) { + return removeTrailingSemicolon( + super.arithmeticQuery( + operator, + tableName, + where, + incrementAmountsByField, + extraAttributesToBeUpdated, + options, + ), + ); + } + + insertQuery(table, valueHash, modelAttributes, options) { + // remove the final semi-colon + const query = super.insertQuery(table, valueHash, modelAttributes, options); + if (query.query.at(-1) === ';') { + query.query = query.query.slice(0, -1); + query.query = `SELECT * FROM FINAL TABLE (${query.query})`; + } + + return query; + } + + selectQuery(tableName, options, model) { + // remove the final semi-colon + let query = super.selectQuery(tableName, options, model); + if (query.at(-1) === ';') { + query = query.slice(0, -1); + } + + return query; + } + + bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { + // remove the final semi-colon + let query = super.bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes); + if (query.at(-1) === ';') { + query = query.slice(0, -1); + query = `SELECT * FROM FINAL TABLE (${query})`; + } + + return query; + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + const attributeString = attribute.type.toString({ + escape: this.escape.bind(this), + dialect: this.dialect, + }); + let template = attributeString; + + if (attribute.type instanceof DataTypes.ENUM) { + // enums are a special case + template = attribute.type.toSql({ dialect: this.dialect }); + if (options && options.context) { + template += options.context === 'changeColumn' ? ' ADD' : ''; + } + + template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.type.options.values + .map(value => { + return this.escape(value); + }) + .join(', ')}))`; + } else { + template = attributeTypeToSql(attribute.type, { dialect: this.dialect }); + } + + if (attribute.allowNull === false) { + template += ' NOT NULL'; + } else if (attribute.allowNull === true && options && options.context === 'changeColumn') { + template += ' DROP NOT NULL'; + } + + if (attribute.autoIncrement) { + template += ' GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1)'; + } + + // BLOB cannot have a default value + if ( + !typeWithoutDefault.has(attributeString) && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + if (attribute.defaultValue === true) { + attribute.defaultValue = 1; + } else if (attribute.defaultValue === false) { + attribute.defaultValue = 0; + } + + template += ` DEFAULT ${this.escape(attribute.defaultValue)}`; + } + + if (attribute.unique === true && !attribute.primaryKey) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + // Db2 for i comments are a mess + // if (attribute.comment) { + // template += ` ${options.context === 'changeColumn' ? 'ADD ' : ''}COMMENT ${this.escape(attribute.comment)}`; + // } + + if (attribute.first) { + template += ' FIRST'; + } + + if (attribute.after) { + template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; + } + + 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`); + + template += ` ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`; + } + + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate && attribute.onUpdate.toUpperCase() !== 'CASCADE') { + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = Object.create(null); + + for (const key of Object.keys(attributes)) { + const attribute = { + ...attributes[key], + field: attributes[key].field || key, + }; + + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } +} diff --git a/packages/ibmi/src/query-interface.internal.ts b/packages/ibmi/src/query-interface.internal.ts new file mode 100644 index 000000000000..fab131a93e70 --- /dev/null +++ b/packages/ibmi/src/query-interface.internal.ts @@ -0,0 +1,31 @@ +import { IsolationLevel } from '@sequelize/core'; +import { AbstractQueryInterfaceInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-interface-internal.js'; +import type { IBMiDialect } from './dialect.js'; + +export class IBMiQueryInterfaceInternal extends AbstractQueryInterfaceInternal { + constructor(readonly dialect: IBMiDialect) { + super(dialect); + } + + /** + * Parses the isolation level and returns the corresponding value for odbc. + * + * @see https://github.com/markdirish/node-odbc/#setIsolationLevellevel-callback + * + * @param value The isolation level to parse. + */ + parseIsolationLevel(value: IsolationLevel): number { + switch (value) { + case IsolationLevel.READ_UNCOMMITTED: + return 1; + case IsolationLevel.READ_COMMITTED: + return 2; + case IsolationLevel.REPEATABLE_READ: + return 4; + case IsolationLevel.SERIALIZABLE: + return 8; + default: + throw new Error(`Unknown isolation level: ${value}`); + } + } +} diff --git a/packages/ibmi/src/query-interface.ts b/packages/ibmi/src/query-interface.ts new file mode 100644 index 000000000000..a4ccebff122a --- /dev/null +++ b/packages/ibmi/src/query-interface.ts @@ -0,0 +1,89 @@ +import type { + CommitTransactionOptions, + RollbackTransactionOptions, + SetIsolationLevelOptions, + StartTransactionOptions, +} from '@sequelize/core'; +import { AbstractQueryInterface, Transaction } from '@sequelize/core'; +import { START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import type { IBMiConnection } from './connection-manager'; +import type { IBMiDialect } from './dialect.js'; +import { IBMiQueryInterfaceInternal } from './query-interface.internal.js'; + +export class IBMiQueryInterface< + Dialect extends IBMiDialect = IBMiDialect, +> extends AbstractQueryInterface { + readonly #internalQueryInterface: IBMiQueryInterfaceInternal; + + constructor(dialect: Dialect, internalQueryInterface?: IBMiQueryInterfaceInternal) { + internalQueryInterface ??= new IBMiQueryInterfaceInternal(dialect); + + super(dialect, internalQueryInterface); + this.#internalQueryInterface = internalQueryInterface; + } + + async _startTransaction( + transaction: Transaction, + options: StartTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to start a transaction without the transaction object.'); + } + + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.sequelize.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.sequelize.dialect.supports.startTransaction, + options, + ); + } + + const connection = transaction.getConnection() as IBMiConnection; + await connection.beginTransaction(); + if (options.isolationLevel) { + await transaction.setIsolationLevel(options.isolationLevel); + } + } + + async _commitTransaction( + transaction: Transaction, + _options: CommitTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to commit a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as IBMiConnection; + await connection.commit(); + } + + async _rollbackTransaction( + transaction: Transaction, + _options: RollbackTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as IBMiConnection; + await connection.rollback(); + } + + async _setIsolationLevel( + transaction: Transaction, + options: SetIsolationLevelOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error( + 'Unable to set the isolation level for a transaction without the transaction object.', + ); + } + + const level = this.#internalQueryInterface.parseIsolationLevel(options.isolationLevel); + const connection = transaction.getConnection() as IBMiConnection; + await connection.setIsolationLevel(level); + } +} diff --git a/packages/ibmi/src/query.d.ts b/packages/ibmi/src/query.d.ts new file mode 100644 index 000000000000..816f21877e19 --- /dev/null +++ b/packages/ibmi/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class IBMiQuery extends AbstractQuery {} diff --git a/packages/ibmi/src/query.js b/packages/ibmi/src/query.js new file mode 100644 index 000000000000..de372abc7367 --- /dev/null +++ b/packages/ibmi/src/query.js @@ -0,0 +1,272 @@ +'use strict'; + +import { + AbstractQuery, + ConnectionRefusedError, + DatabaseError, + EmptyResultError, + ForeignKeyConstraintError, + UniqueConstraintError, + UnknownConstraintError, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; + +const debug = logger.debugContext('sql:ibmi'); + +export class IBMiQuery extends AbstractQuery { + getInsertIdField() { + return 'id'; + } + + async run(sql, parameters) { + this.sql = sql.replace(/;$/, ''); + + const complete = this._logQuery(sql, debug, parameters); + + let results; + try { + results = await this.connection.query(this.sql, parameters); + } catch (error) { + throw this.formatError(error); + } + + complete(); + + // parse the results to the format sequelize expects + for (const result of results) { + for (const column of results.columns) { + const value = result[column.name]; + if (value == null) { + continue; + } + + const parse = this.sequelize.dialect.getParserForDatabaseDataType(column.dataType); + if (parse) { + result[column.name] = parse(value); + } + } + } + + return this.formatResults(results); + } + + /** + * High level function that handles the results of a query execution. + * + * + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + * + * @param {Array} data - The result of the query execution. + * @private + */ + formatResults(data) { + if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { + if (this.instance && this.instance.dataValues) { + if (this.isInsertQuery() && !this.isUpsertQuery() && data.length === 0) { + throw new EmptyResultError(); + } + + if (this.options.returning && Array.isArray(data) && data[0]) { + for (const attributeOrColumnName of Object.keys(data[0])) { + const modelDefinition = this.model.modelDefinition; + const attribute = modelDefinition.columns.get(attributeOrColumnName); + const updatedValue = this._parseDatabaseValue( + data[0][attributeOrColumnName], + attribute?.type, + ); + + this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, { + raw: true, + comesFromDatabase: true, + }); + } + } + } + + if (this.isUpsertQuery()) { + return [this.instance, null]; + } + + return [ + this.instance || (data && ((this.options.plain && data[0]) || data)) || undefined, + data.count, + ]; + } + + if (this.isSelectQuery()) { + return this.handleSelectQuery(data); + } + + if (this.isShowIndexesQuery()) { + return this.handleShowIndexesQuery(data); + } + + if (this.isDescribeQuery()) { + const result = {}; + + for (const _result of data) { + const enumRegex = /^enum/i; + result[_result.COLUMN_NAME] = { + type: enumRegex.test(_result.Type) + ? _result.Type.replace(enumRegex, 'ENUM') + : _result.DATA_TYPE.toUpperCase(), + allowNull: _result.IS_NULLABLE === 'Y', + defaultValue: _result.COLUMN_DEFAULT, + primaryKey: _result.CONSTRAINT_TYPE === 'PRIMARY KEY', + autoIncrement: _result.IS_GENERATED !== 'IDENTITY_GENERATION', + }; + } + + return result; + } + + if (this.isCallQuery()) { + return data[0]; + } + + if (this.isDeleteQuery()) { + return data.count; + } + + if (this.isBulkUpdateQuery()) { + return this.options.returning ? this.handleSelectQuery(data) : data.count; + } + + if (this.isShowConstraintsQuery()) { + return data; + } + + if (this.isRawQuery()) { + // MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta + return [data, data]; + } + + return this.instance; + } + + handleInsertQuery(results, metaData) { + if (this.instance) { + // add the inserted row id to the instance + const autoIncrementAttribute = this.model.autoIncrementAttribute.field; + let id = null; + + id ||= results && results[autoIncrementAttribute]; + id ||= metaData && metaData[autoIncrementAttribute]; + + this.instance[this.model.autoIncrementAttribute] = id; + } + } + + handleShowIndexesQuery(data) { + const indexes = Object.create(null); + + data.forEach(item => { + if (Object.hasOwn(indexes, item.NAME)) { + indexes[item.NAME].fields.push({ + attribute: item.COLUMN_NAME, + length: undefined, + order: undefined, + collate: undefined, + }); + } else { + indexes[item.NAME] = { + primary: item.CONSTRAINT_TYPE === 'PRIMARY KEY', + fields: [ + { + attribute: item.COLUMN_NAME, + length: undefined, + order: undefined, + collate: undefined, + }, + ], + name: item.NAME, + tableName: item.TABLE_NAME, + unique: item.CONSTRAINT_TYPE === 'PRIMARY KEY' || item.CONSTRAINT_TYPE === 'UNIQUE', + type: item.CONSTRAINT_TYPE, + }; + } + }); + + return Object.values(indexes); + } + + formatError(err) { + // Db2 for i uses the `odbc` connector. The `odbc` connector returns a list + // of odbc errors, each of which has a code and a state. To determine the + // type of SequelizeError, check the code and create the associated error. + // Error codes can be found at: + // https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzala/rzalaccl.htm + + // some errors occur outside of ODBC (e.g. connection errors) + if (err.toString().includes('Error connecting to the database')) { + return new ConnectionRefusedError(err); + } + + if (Object.hasOwn(err, 'odbcErrors') && err.odbcErrors.length > 0) { + const odbcError = err.odbcErrors[0]; + const foreignKeyConstraintCodes = [ + -530, // The insert or update value of a foreign key is invalid. + -531, // The update or delete of a parent key is prevented by a NO ACTION update or delete rule. + -532, // The update or delete of a parent key is prevented by a NO ACTION update or delete rule. + ]; + const uniqueConstraintCodes = [ + -803, // A violation of the constraint imposed by a unique index or a unique constraint occurred. + ]; + + /** + * Check for ODBC connection errors by looking at the SQL state. This will allow for an IPL + * on the IBM i to be detected and the connection to be re-established. + */ + if (odbcError.state === '08S01') { + return new ConnectionRefusedError(err); + } + + if (foreignKeyConstraintCodes.includes(odbcError.code)) { + return new ForeignKeyConstraintError({ + cause: err, + sql: {}, + fields: {}, + }); + } + + if (uniqueConstraintCodes.includes(odbcError.code)) { + return new UniqueConstraintError({ + errors: err.odbcErrors, + cause: err, + sql: {}, + fields: {}, + }); + } + + if (odbcError.code === -204) { + let constraintName; + let type; + const constraintNameRegex = /"([^)]+?)" in [^]+? type (\*\w+?) not found./; + const constraintNameRegexMatches = odbcError.message.match(constraintNameRegex); + if (constraintNameRegexMatches && constraintNameRegexMatches.length === 3) { + constraintName = constraintNameRegexMatches[1]; + type = constraintNameRegexMatches[2]; + + if (type === '*N') { + return new UnknownConstraintError({ + cause: err, + constraint: constraintName, + }); + } + } + } + + return new DatabaseError(odbcError); + } + + return err; + } +} diff --git a/packages/ibmi/tsconfig.json b/packages/ibmi/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/ibmi/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/ibmi/typedoc.json b/packages/ibmi/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/ibmi/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/mariadb/.eslintrc.js b/packages/mariadb/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/mariadb/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/mariadb/CHANGELOG.md b/packages/mariadb/CHANGELOG.md new file mode 100644 index 000000000000..b5001f2d9f48 --- /dev/null +++ b/packages/mariadb/CHANGELOG.md @@ -0,0 +1,70 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +**Note:** Version bump only for package @sequelize/mariadb + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/mariadb + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/mariadb + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/mariadb + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- **mariadb:** fix inefficient regular expression in error message ([#17508](https://github.com/sequelize/sequelize/issues/17508)) ([3f5250b](https://github.com/sequelize/sequelize/commit/3f5250b9741c265031e5e7307c2fe3e9cef56b48)) +- **mariadb:** update mariadb to v3.3.2 ([#17518](https://github.com/sequelize/sequelize/issues/17518)) ([3819cf5](https://github.com/sequelize/sequelize/commit/3819cf545f2a20a7db28dda649bb4881dd015b16)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/mariadb + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +### Bug Fixes + +- set sequelize dialect type in query generator and interface ([#17285](https://github.com/sequelize/sequelize/issues/17285)) ([0227288](https://github.com/sequelize/sequelize/commit/0227288d1c6fcbf2d4f09e2efa50e4aeb9d435f2)) + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +- feat(mariadb)!: move mariadb to the `@sequelize/mariadb` package (#17198) ([46ea159](https://github.com/sequelize/sequelize/commit/46ea159306c55c7b3c02ac0ba24a2c0dd3dff4d9)), closes [#17198](https://github.com/sequelize/sequelize/issues/17198) + +### Features + +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` +- Instead of installing the `mariadb` package, users need to install `@sequelize/mariadb.` diff --git a/packages/mariadb/package.json b/packages/mariadb/package.json new file mode 100644 index 000000000000..35b0d3df85db --- /dev/null +++ b/packages/mariadb/package.json @@ -0,0 +1,54 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "MariaDB Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/mariadb", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs mariadb", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-unit": "mocha src/**/*.test.ts", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "mariadb": "^3.4.5", + "semver": "^7.7.3", + "wkx": "^0.5.0" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "chai": "4.5.0", + "mocha": "11.7.5" + } +} diff --git a/packages/mariadb/src/_internal/connection-options.ts b/packages/mariadb/src/_internal/connection-options.ts new file mode 100644 index 000000000000..cce990eb89fc --- /dev/null +++ b/packages/mariadb/src/_internal/connection-options.ts @@ -0,0 +1,79 @@ +import { getSynchronizedTypeKeys, type PickByType } from '@sequelize/utils'; +import type { MariaDbConnectionOptions } from '../connection-manager.js'; + +/** Options that are typed as "any" */ +type AnyOptions = 'sessionVariables' | 'connectAttributes'; + +type StringConnectionOptions = PickByType, string>; + +const STRING_CONNECTION_OPTION_MAP = { + cachingRsaPublicKey: undefined, + charset: undefined, + collation: undefined, + database: undefined, + host: undefined, + initSql: undefined, + password: undefined, + rsaPublicKey: undefined, + socketPath: undefined, + user: undefined, +} as const satisfies Record; + +export const STRING_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + STRING_CONNECTION_OPTION_MAP, +); + +type BooleanConnectionOptions = PickByType, boolean>; + +const BOOLEAN_CONNECTION_OPTION_MAP = { + debug: undefined, + debugCompress: undefined, + // TODO: https://github.com/sequelize/sequelize/issues/11832 - replace with a unified "logging" option + logParam: undefined, + trace: undefined, + multipleStatements: undefined, + ssl: undefined, + compress: undefined, + logPackets: undefined, + forceVersionCheck: undefined, + foundRows: undefined, + allowPublicKeyRetrieval: undefined, + metaEnumerable: undefined, + bulk: undefined, + pipelining: undefined, + permitLocalInfile: undefined, + checkDuplicate: undefined, +} as const satisfies Record; + +export const BOOLEAN_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + BOOLEAN_CONNECTION_OPTION_MAP, +); + +type NumberConnectionOptions = PickByType, number>; + +const NUMBER_CONNECTION_OPTION_MAP = { + port: undefined, + connectTimeout: undefined, + socketTimeout: undefined, + debugLen: undefined, + maxAllowedPacket: undefined, + keepAliveDelay: undefined, + prepareCacheLength: undefined, + queryTimeout: undefined, +} as const satisfies Record; + +export const NUMBER_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + NUMBER_CONNECTION_OPTION_MAP, +); + +export const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + ...STRING_CONNECTION_OPTION_MAP, + ...BOOLEAN_CONNECTION_OPTION_MAP, + ...NUMBER_CONNECTION_OPTION_MAP, + connectAttributes: undefined, + infileStreamFactory: undefined, + // TODO: https://github.com/sequelize/sequelize/issues/11832 - replace with a unified "logging" option + logger: undefined, + sessionVariables: undefined, + stream: undefined, +}); diff --git a/packages/mariadb/src/_internal/data-types-db.ts b/packages/mariadb/src/_internal/data-types-db.ts new file mode 100644 index 000000000000..fdb927582e5e --- /dev/null +++ b/packages/mariadb/src/_internal/data-types-db.ts @@ -0,0 +1,58 @@ +import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import dayjs from 'dayjs'; +import type { FieldInfo } from 'mariadb'; +import type { MariaDbDialect } from '../dialect.js'; + +/** + * First pass of DB value parsing: Parses based on the MariaDB Type ID. + * If a Sequelize DataType is specified, the value is then passed to {@link DataTypes.ABSTRACT#parseDatabaseValue}. + * + * @param dialect + */ +export function registerMariaDbDbDataTypeParsers(dialect: MariaDbDialect) { + dialect.registerDataTypeParser(['DATETIME'], (value: FieldInfo) => { + const valueStr: string | null = value.string(); + if (valueStr === null) { + return null; + } + + const timeZone: string = dialect.sequelize.options.timezone; + if (timeZone === '+00:00') { + // default value + // mariadb returns a UTC date string that looks like the following: + // 2022-01-01 00:00:00 + // The above does not specify a time zone offset, so Date.parse will try to parse it as a local time. + // Adding +00 fixes this. + return `${valueStr}+00`; + } + + if (isValidTimeZone(timeZone)) { + return dayjs.tz(valueStr, timeZone).toISOString(); + } + + // offset format, we can just append. + // "2022-09-22 20:03:06" with timeZone "-04:00" + // becomes "2022-09-22 20:03:06-04:00" + return valueStr + timeZone; + }); + + // dateonly + dialect.registerDataTypeParser(['DATE'], (value: FieldInfo) => { + return value.string(); + }); + + // bigint + dialect.registerDataTypeParser(['LONGLONG'], (value: FieldInfo) => { + return value.string(); + }); + + dialect.registerDataTypeParser(['GEOMETRY'], (value: FieldInfo) => { + return value.geometry(); + }); + + // For backwards compatibility, we currently return BIGINTs as strings. We will implement bigint support for all + // dialects in the future: https://github.com/sequelize/sequelize/issues/10468 + dialect.registerDataTypeParser(['BIGINT'], (value: FieldInfo) => { + return value.string(); + }); +} diff --git a/packages/mariadb/src/_internal/data-types-overrides.ts b/packages/mariadb/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..b16bc8cc900f --- /dev/null +++ b/packages/mariadb/src/_internal/data-types-overrides.ts @@ -0,0 +1,100 @@ +import type { BindParamOptions, GeoJson } from '@sequelize/core'; +import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import { isString } from '@sequelize/utils'; +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; +import wkx from 'wkx'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +export class FLOAT extends BaseTypes.FLOAT { + protected getNumberSqlTypeName(): string { + return 'FLOAT'; + } +} + +export class BOOLEAN extends BaseTypes.BOOLEAN { + toSql() { + return 'TINYINT(1)'; + } + + toBindableValue(value: boolean | unknown): unknown { + // when binding, must be an integer + return value ? 1 : 0; + } +} + +export class DATE extends BaseTypes.DATE { + toBindableValue(date: AcceptedDate) { + date = this._applyTimezone(date); + + // MariaDB datetime precision defaults to 0 + const precision = this.options.precision ?? 0; + let format = 'YYYY-MM-DD HH:mm:ss'; + // TODO: We should normally use `S`, `SS` or `SSS` based on the precision, but + // dayjs has a bug which causes `S` and `SS` to be ignored: + // https://github.com/iamkun/dayjs/issues/1734 + if (precision > 0) { + format += `.SSS`; + } + + return date.format(format); + } + + sanitize(value: unknown, options?: { timezone?: string }): unknown { + if (isString(value) && options?.timezone) { + if (isValidTimeZone(options.timezone)) { + return dayjs.tz(value, options.timezone).toDate(); + } + + return new Date(`${value} ${options.timezone}`); + } + + return super.sanitize(value); + } +} + +export class UUID extends BaseTypes.UUID { + // TODO: add check constraint to enforce GUID format + toSql() { + return 'CHAR(36) BINARY'; + } +} + +export class GEOMETRY extends BaseTypes.GEOMETRY { + toBindableValue(value: GeoJson) { + const srid = this.options.srid ? `, ${this.options.srid}` : ''; + + return `ST_GeomFromText(${this._getDialect().escapeString( + wkx.Geometry.parseGeoJSON(value).toWkt(), + )}${srid})`; + } + + getBindParamSql(value: GeoJson, options: BindParamOptions) { + const srid = this.options.srid ? `, ${options.bindParam(this.options.srid)}` : ''; + + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())}${srid})`; + } + + toSql() { + const sql = this.options.type?.toUpperCase() || 'GEOMETRY'; + + if (this.options.srid) { + return `${sql} REF_SYSTEM_ID=${this.options.srid}`; + } + + return sql; + } +} + +export class ENUM extends BaseTypes.ENUM { + toSql() { + const dialect = this._getDialect(); + + return `ENUM(${this.options.values.map(value => dialect.escapeString(value)).join(', ')})`; + } +} diff --git a/packages/mariadb/src/connection-manager.ts b/packages/mariadb/src/connection-manager.ts new file mode 100644 index 000000000000..34878552d2b0 --- /dev/null +++ b/packages/mariadb/src/connection-manager.ts @@ -0,0 +1,175 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + AccessDeniedError, + ConnectionError, + ConnectionRefusedError, + HostNotFoundError, + HostNotReachableError, + InvalidConnectionError, +} from '@sequelize/core'; +import { isErrorWithStringCode } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { timeZoneToOffsetString } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { removeUndefined } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import * as MariaDb from 'mariadb'; +import semver from 'semver'; +import type { MariaDbDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:mariadb'); + +export type MariaDbModule = typeof MariaDb; + +export interface MariaDbConnection extends AbstractConnection, MariaDb.Connection {} + +export interface MariaDbConnectionOptions + extends Omit< + MariaDb.ConnectionConfig, + // Can only be set by Sequelize to prevent users from making it return a format + // that is incompatible with Sequelize + | 'typeCast' + // Replaced by Sequelize's global option + | 'timezone' + // Users cannot use MariaDB's placeholders, they use Sequelize's syntax instead + | 'namedPlaceholders' + | 'arrayParenthesis' + + // The following options will conflict with the format expected by Sequelize + | 'insertIdAsNumber' + | 'metaAsArray' + | 'rowsAsArray' + | 'nestTables' + | 'dateStrings' + | 'decimalAsNumber' + | 'bigIntAsNumber' + | 'supportBigNumbers' + | 'bigNumberStrings' + | 'autoJsonMap' + // This option is not necessary because we do not allow using decimalAsNumber, + // insertIdAsNumber, nor bigIntAsNumber. + // If someone requests to enable this option, do not accept it. + // Instead, the same feature should be added to Sequelize as a cross-dialect feature. + | 'checkNumberRange' + // unsafe compatibility option + | 'permitSetMultiParamEntries' + > {} + +/** + * MariaDB Connection Manager + * + * Get connections, validate and disconnect them. + * AbstractConnectionManager pooling use it to handle MariaDB specific connections + * Use https://github.com/MariaDB/mariadb-connector-nodejs to connect with MariaDB server + */ +export class MariaDbConnectionManager extends AbstractConnectionManager< + MariaDbDialect, + MariaDbConnection +> { + readonly #lib: MariaDbModule; + + constructor(dialect: MariaDbDialect) { + super(dialect); + this.#lib = dialect.options.mariaDbModule ?? MariaDb; + } + + #typeCast(field: MariaDb.FieldInfo, next: MariaDb.TypeCastNextFunction): MariaDb.TypeCastResult { + const parser = this.dialect.getParserForDatabaseDataType(field.type); + + if (parser) { + return parser(field) as MariaDb.TypeCastResult; + } + + return next(); + } + + /** + * Connect with MariaDB database based on config, Handle any errors in connection + * Set the pool handlers on connection.error + * Also set proper timezone once connection is connected. + * + * @param config + */ + async connect(config: ConnectionOptions): Promise { + // Named timezone is not supported in mariadb, convert to offset + let tzOffset = this.sequelize.options.timezone; + tzOffset = tzOffset.includes('/') ? timeZoneToOffsetString(tzOffset) : tzOffset; + + const connectionConfig: MariaDb.ConnectionConfig = removeUndefined({ + foundRows: false, + ...config, + timezone: tzOffset, + typeCast: (field: MariaDb.FieldInfo, next: MariaDb.TypeCastNextFunction) => + this.#typeCast(field, next), + }); + + if (!this.sequelize.options.keepDefaultTimezone) { + // set timezone for this connection + if (connectionConfig.initSql) { + if (!Array.isArray(connectionConfig.initSql)) { + connectionConfig.initSql = [connectionConfig.initSql]; + } + + connectionConfig.initSql.push(`SET time_zone = '${tzOffset}'`); + } else { + connectionConfig.initSql = `SET time_zone = '${tzOffset}'`; + } + } + + try { + const connection = await this.#lib.createConnection(connectionConfig); + this.sequelize.setDatabaseVersion(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': + void this.sequelize.pool.destroy(connection); + break; + default: + } + }); + + return connection; + } catch (error: unknown) { + if (!isErrorWithStringCode(error)) { + throw error; + } + + switch (error.code) { + case 'ECONNREFUSED': + throw new ConnectionRefusedError(error); + case 'ER_ACCESS_DENIED_ERROR': + case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR': + throw new AccessDeniedError(error); + case 'ENOTFOUND': + throw new HostNotFoundError(error); + case 'EHOSTUNREACH': + case 'ENETUNREACH': + case 'EADDRNOTAVAIL': + throw new HostNotReachableError(error); + case 'EINVAL': + throw new InvalidConnectionError(error); + default: + throw new ConnectionError(error); + } + } + } + + async disconnect(connection: MariaDbConnection) { + // Don't disconnect connections with CLOSED state + if (!connection.isValid()) { + debug('connection tried to disconnect but was already at CLOSED state'); + + return; + } + + await connection.end(); + } + + validate(connection: MariaDbConnection): boolean { + return connection && connection.isValid(); + } +} diff --git a/packages/mariadb/src/dialect.test.ts b/packages/mariadb/src/dialect.test.ts new file mode 100644 index 000000000000..69f57fd41342 --- /dev/null +++ b/packages/mariadb/src/dialect.test.ts @@ -0,0 +1,23 @@ +import { Sequelize } from '@sequelize/core'; +import type { MariaDbConnectionOptions } from '@sequelize/mariadb'; +import { MariaDbDialect } from '@sequelize/mariadb'; +import { expect } from 'chai'; + +describe('MariaDbDialect#parseConnectionUrl', () => { + const dialect = new Sequelize({ dialect: MariaDbDialect }).dialect; + + it('parses connection URL', () => { + const options: MariaDbConnectionOptions = dialect.parseConnectionUrl( + 'mariadb://user:password@localhost:1234/dbname?charset=utf8mb4', + ); + + expect(options).to.deep.eq({ + host: 'localhost', + port: 1234, + user: 'user', + password: 'password', + database: 'dbname', + charset: 'utf8mb4', + }); + }); +}); diff --git a/packages/mariadb/src/dialect.ts b/packages/mariadb/src/dialect.ts new file mode 100644 index 000000000000..b3419c14033f --- /dev/null +++ b/packages/mariadb/src/dialect.ts @@ -0,0 +1,178 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import type { SupportableNumericOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/dialect.js'; +import { parseCommonConnectionUrlOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/connection-options.js'; +import { + createUnspecifiedOrderedBindCollector, + escapeMysqlMariaDbString, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import { + BOOLEAN_CONNECTION_OPTION_NAMES, + CONNECTION_OPTION_NAMES, + NUMBER_CONNECTION_OPTION_NAMES, + STRING_CONNECTION_OPTION_NAMES, +} from './_internal/connection-options.js'; +import { registerMariaDbDbDataTypeParsers } from './_internal/data-types-db.js'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { MariaDbConnectionOptions, MariaDbModule } from './connection-manager.js'; +import { MariaDbConnectionManager } from './connection-manager.js'; +import { MariaDbQueryGenerator } from './query-generator.js'; +import { MariaDbQueryInterface } from './query-interface.js'; +import { MariaDbQuery } from './query.js'; + +export interface MariaDbDialectOptions { + /** + * The mariadb library to use. + * If not provided, the mariadb npm library will be used. + * Must be compatible with the mariadb npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + mariaDbModule?: MariaDbModule; + + /** + * Show warnings if there are any when executing a query + */ + showWarnings?: boolean | undefined; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + mariaDbModule: undefined, + showWarnings: undefined, +}); + +const numericOptions: SupportableNumericOptions = { + zerofill: true, + unsigned: true, +}; + +export class MariaDbDialect extends AbstractDialect< + MariaDbDialectOptions, + MariaDbConnectionOptions +> { + static supports = AbstractDialect.extendSupport({ + 'VALUES ()': true, + 'LIMIT ON UPDATE': true, + lock: true, + forShare: 'LOCK IN SHARE MODE', + settingIsolationLevelDuringTransaction: false, + schemas: true, + inserts: { + ignoreDuplicates: ' IGNORE', + updateOnDuplicate: ' ON DUPLICATE KEY UPDATE', + }, + index: { + collate: false, + length: true, + parser: true, + type: true, + using: 1, + }, + constraints: { + foreignKeyChecksDisableable: true, + removeOptions: { ifExists: true }, + }, + indexViaAlter: true, + indexHints: true, + dataTypes: { + COLLATE_BINARY: true, + GEOMETRY: true, + INTS: numericOptions, + FLOAT: { ...numericOptions, scaleAndPrecision: true }, + REAL: { ...numericOptions, scaleAndPrecision: true }, + DOUBLE: { ...numericOptions, scaleAndPrecision: true }, + DECIMAL: numericOptions, + JSON: true, + }, + REGEXP: true, + jsonOperations: true, + jsonExtraction: { + unquoted: true, + quoted: true, + }, + uuidV1Generation: true, + globalTimeZoneConfig: true, + removeColumn: { + ifExists: true, + }, + createSchema: { + charset: true, + collate: true, + // TODO [>=2024-06-19]: uncomment when MariaDB 10.5 is oldest supported version + // comment: true, + ifNotExists: true, + replace: true, + }, + dropSchema: { + ifExists: true, + }, + startTransaction: { + readOnly: true, + }, + }); + + readonly queryGenerator: MariaDbQueryGenerator; + readonly connectionManager: MariaDbConnectionManager; + readonly queryInterface: MariaDbQueryInterface; + + readonly Query = MariaDbQuery; + + constructor(sequelize: Sequelize, options: MariaDbDialectOptions) { + super({ + dataTypesDocumentationUrl: 'https://mariadb.com/kb/en/library/resultset/#field-types', + identifierDelimiter: '`', + minimumDatabaseVersion: '10.4.30', + name: 'mariadb', + options, + sequelize, + dataTypeOverrides: DataTypes, + }); + + this.connectionManager = new MariaDbConnectionManager(this); + this.queryGenerator = new MariaDbQueryGenerator(this); + this.queryInterface = new MariaDbQueryInterface(this); + + registerMariaDbDbDataTypeParsers(this); + } + + createBindCollector() { + return createUnspecifiedOrderedBindCollector(); + } + + escapeString(value: string) { + return escapeMysqlMariaDbString(value); + } + + canBackslashEscape() { + return true; + } + + getDefaultSchema(): string { + return this.sequelize.options.replication.write.database ?? ''; + } + + parseConnectionUrl(url: string): MariaDbConnectionOptions { + return parseCommonConnectionUrlOptions({ + url: new URL(url), + allowedProtocols: ['mariadb'], + hostname: 'host', + port: 'port', + pathname: 'database', + username: 'user', + password: 'password', + stringSearchParams: STRING_CONNECTION_OPTION_NAMES, + booleanSearchParams: BOOLEAN_CONNECTION_OPTION_NAMES, + numberSearchParams: NUMBER_CONNECTION_OPTION_NAMES, + }); + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/mariadb/src/index.mjs b/packages/mariadb/src/index.mjs new file mode 100644 index 000000000000..72bb5ce40545 --- /dev/null +++ b/packages/mariadb/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const MariaDbConnectionManager = Pkg.MariaDbConnectionManager; +export const MariaDbDialect = Pkg.MariaDbDialect; +export const MariaDbQueryGenerator = Pkg.MariaDbQueryGenerator; +export const MariaDbQueryInterface = Pkg.MariaDbQueryInterface; +export const MariaDbQuery = Pkg.MariaDbQuery; diff --git a/packages/mariadb/src/index.ts b/packages/mariadb/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/mariadb/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/mariadb/src/query-generator-typescript.internal.ts b/packages/mariadb/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..195e3fe31cde --- /dev/null +++ b/packages/mariadb/src/query-generator-typescript.internal.ts @@ -0,0 +1,197 @@ +import type { + Expression, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator, Op } from '@sequelize/core'; +import type { EscapeOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { buildJsonPath } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/json.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import type { MariaDbDialect } from './dialect.js'; +import { MariaDbQueryGeneratorInternal } from './query-generator.internal.js'; + +const REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS = new Set(['ifExists']); + +/** + * Temporary class to ease the TypeScript migration + */ +export class MariaDbQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: MariaDbQueryGeneratorInternal; + + constructor( + dialect: MariaDbDialect, + internals: MariaDbQueryGeneratorInternal = new MariaDbQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + internals.whereSqlBuilder.setOperatorKeyword(Op.regexp, 'REGEXP'); + internals.whereSqlBuilder.setOperatorKeyword(Op.notRegexp, 'NOT REGEXP'); + + this.#internals = internals; + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + let schemasToSkip = this.#internals.getTechnicalSchemaNames(); + + if (options && Array.isArray(options?.skip)) { + schemasToSkip = [...schemasToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT SCHEMA_NAME AS `schema`', + 'FROM INFORMATION_SCHEMA.SCHEMATA', + `WHERE SCHEMA_NAME NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + return `SHOW FULL COLUMNS FROM ${this.quoteTable(tableName)};`; + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT TABLE_NAME AS `tableName`,', + 'TABLE_SCHEMA AS `schema`', + `FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'`, + options?.schema + ? `AND TABLE_SCHEMA = ${this.escape(options.schema)}` + : `AND TABLE_SCHEMA NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY TABLE_SCHEMA, TABLE_NAME', + ]); + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE ${this.quoteTable(tableName)}`; + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT c.CONSTRAINT_SCHEMA AS constraintSchema,', + 'c.CONSTRAINT_NAME AS constraintName,', + 'c.CONSTRAINT_TYPE AS constraintType,', + 'c.TABLE_SCHEMA AS tableSchema,', + 'c.TABLE_NAME AS tableName,', + 'kcu.COLUMN_NAME AS columnNames,', + 'kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema,', + 'kcu.REFERENCED_TABLE_NAME AS referencedTableName,', + 'kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames,', + 'r.DELETE_RULE AS deleteAction,', + 'r.UPDATE_RULE AS updateAction,', + 'ch.CHECK_CLAUSE AS definition', + 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c', + 'LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME', + 'LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME', + 'LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME', + `WHERE c.TABLE_NAME = ${this.escape(table.tableName)}`, + `AND c.TABLE_SCHEMA = ${this.escape(table.schema)}`, + options?.columnName ? `AND kcu.COLUMN_NAME = ${this.escape(options.columnName)}` : '', + options?.constraintName + ? `AND c.CONSTRAINT_NAME = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.CONSTRAINT_TYPE = ${this.escape(options.constraintType)}` + : '', + 'ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION', + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + return `SHOW INDEX FROM ${this.quoteTable(tableName)}`; + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + let indexName; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return joinSQLFragments([ + 'DROP INDEX', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(indexName), + 'ON', + this.quoteTable(tableName), + ]); + } + + getToggleForeignKeyChecksQuery(enable: boolean): string { + return `SET FOREIGN_KEY_CHECKS=${enable ? '1' : '0'}`; + } + + jsonPathExtractionQuery( + sqlExpression: string, + path: ReadonlyArray, + unquote: boolean, + ): string { + const extractQuery = `json_extract(${sqlExpression},${this.escape(buildJsonPath(path))})`; + + if (unquote) { + return `json_unquote(${extractQuery})`; + } + + // MariaDB has a very annoying behavior with json_extract: It returns the JSON value as a proper JSON string (e.g. `true` or `null` instead true or null) + // Except if the value is going to be used in a comparison, in which case it unquotes it automatically (even if we did not call JSON_UNQUOTE). + // This is a problem because it makes it impossible to distinguish between a JSON text `true` and a JSON boolean true. + // This useless function call is here to make mariadb not think the value will be used in a comparison, and thus not unquote it. + // We could replace it with a custom function that does nothing, but this would require a custom function to be created on the database ahead of time. + return `json_compact(${extractQuery})`; + } + + formatUnquoteJson(arg: Expression, options?: EscapeOptions) { + return `json_unquote(${this.escape(arg, options)})`; + } + + versionQuery() { + return 'SELECT VERSION() as `version`'; + } + + getUuidV1FunctionCall(): string { + return 'UUID()'; + } +} diff --git a/packages/mariadb/src/query-generator.d.ts b/packages/mariadb/src/query-generator.d.ts new file mode 100644 index 000000000000..d2ef097f8321 --- /dev/null +++ b/packages/mariadb/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { MariaDbQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class MariaDbQueryGenerator extends MariaDbQueryGeneratorTypeScript {} diff --git a/packages/mariadb/src/query-generator.internal.ts b/packages/mariadb/src/query-generator.internal.ts new file mode 100644 index 000000000000..af2fd6273482 --- /dev/null +++ b/packages/mariadb/src/query-generator.internal.ts @@ -0,0 +1,27 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import { formatMySqlStyleLimitOffset } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import type { MariaDbDialect } from './dialect.js'; + +const TECHNICAL_SCHEMAS = Object.freeze([ + 'MYSQL', + 'INFORMATION_SCHEMA', + 'PERFORMANCE_SCHEMA', + 'SYS', + 'mysql', + 'information_schema', + 'performance_schema', + 'sys', +]); + +export class MariaDbQueryGeneratorInternal< + Dialect extends MariaDbDialect = MariaDbDialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalSchemaNames() { + return TECHNICAL_SCHEMAS; + } + + addLimitAndOffset(options: AddLimitOffsetOptions) { + return formatMySqlStyleLimitOffset(options, this.queryGenerator); + } +} diff --git a/packages/mariadb/src/query-generator.js b/packages/mariadb/src/query-generator.js new file mode 100644 index 000000000000..be7d3d1c46bd --- /dev/null +++ b/packages/mariadb/src/query-generator.js @@ -0,0 +1,252 @@ +'use strict'; + +import { + attributeTypeToSql, + normalizeDataType, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import each from 'lodash/each'; +import isPlainObject from 'lodash/isPlainObject'; +import { MariaDbQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); + +export class MariaDbQueryGenerator extends MariaDbQueryGeneratorTypeScript { + createTableQuery(tableName, attributes, options) { + options = { + engine: 'InnoDB', + charset: null, + rowFormat: null, + ...options, + }; + + const primaryKeys = []; + const foreignKeys = {}; + const attrStr = []; + + for (const attr in attributes) { + if (!Object.hasOwn(attributes, attr)) { + continue; + } + + const dataType = attributes[attr]; + let match; + + if (dataType.includes('PRIMARY KEY')) { + primaryKeys.push(attr); + + if (dataType.includes('REFERENCES')) { + // MariaDB doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + } + } else if (dataType.includes('REFERENCES')) { + // MariaDB doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + + const table = this.quoteTable(tableName); + let attributesClause = attrStr.join(', '); + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + if (options.uniqueKeys) { + each(options.uniqueKeys, (columns, indexName) => { + if (typeof indexName !== 'string') { + indexName = `uniq_${tableName}_${columns.fields.join('_')}`; + } + + attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields + .map(field => this.quoteIdentifier(field)) + .join(', ')})`; + }); + } + + if (pkString.length > 0) { + attributesClause += `, PRIMARY KEY (${pkString})`; + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + } + } + + return 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}`, + ';', + ]); + } + + addColumnQuery(table, key, dataType, options = {}) { + const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS' : ''; + + dataType = { + ...dataType, + type: normalizeDataType(dataType.type, this.dialect), + }; + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + ifNotExists, + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key, + }), + ';', + ]); + } + + changeColumnQuery(tableName, attributes) { + const attrString = []; + const constraintString = []; + + for (const attributeName in attributes) { + let definition = attributes[attributeName]; + if (definition.includes('REFERENCES')) { + const attrName = this.quoteIdentifier(attributeName); + definition = definition.replace(/.+?(?=REFERENCES)/, ''); + constraintString.push(`FOREIGN KEY (${attrName}) ${definition}`); + } else { + attrString.push(`\`${attributeName}\` \`${attributeName}\` ${definition}`); + } + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `CHANGE ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';', + ]); + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const attrString = []; + + for (const attrName in attributes) { + const definition = attributes[attrName]; + attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'CHANGE', + attrString.join(', '), + ';', + ]); + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + const attributeString = attributeTypeToSql(attribute.type, { + escape: this.escape.bind(this), + dialect: this.dialect, + }); + let template = attributeString; + + if (attribute.allowNull === false) { + template += ' NOT NULL'; + } + + if (attribute.autoIncrement) { + template += ' auto_increment'; + } + + // BLOB/TEXT/GEOMETRY/JSON cannot have a default value + if ( + !typeWithoutDefault.has(attributeString) && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + template += ` DEFAULT ${this.escape(attribute.defaultValue)}`; + } + + if (attribute.unique === true) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + if (attribute.comment) { + template += ` COMMENT ${this.escape(attribute.comment)}`; + } + + if (attribute.first) { + template += ' FIRST'; + } + + if (attribute.after) { + template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; + } + + if ((!options || !options.withoutForeignKeyConstraints) && attribute.references) { + if (options && options.context === 'addColumn' && options.foreignKey) { + const fkName = this.quoteIdentifier( + `${this.extractTableDetails(options.tableName).tableName}_${options.foreignKey}_foreign_idx`, + ); + + template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${this.quoteIdentifier(options.foreignKey)})`; + } + + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = {}; + + for (const key in attributes) { + const attribute = attributes[key]; + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } +} diff --git a/packages/mariadb/src/query-interface.d.ts b/packages/mariadb/src/query-interface.d.ts new file mode 100644 index 000000000000..f0990d320ba6 --- /dev/null +++ b/packages/mariadb/src/query-interface.d.ts @@ -0,0 +1,6 @@ +import { AbstractQueryInterface } from '@sequelize/core'; +import type { MariaDbDialect } from './dialect.js'; + +export class MariaDbQueryInterface< + Dialect extends MariaDbDialect = MariaDbDialect, +> extends AbstractQueryInterface {} diff --git a/packages/mariadb/src/query-interface.js b/packages/mariadb/src/query-interface.js new file mode 100644 index 000000000000..9ee061d8e1e6 --- /dev/null +++ b/packages/mariadb/src/query-interface.js @@ -0,0 +1,65 @@ +'use strict'; + +import { AbstractQueryInterface, QueryTypes } from '@sequelize/core'; +import { getObjectFromMap } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { + assertNoReservedBind, + combineBinds, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; + +/** + * The interface that Sequelize uses to talk with MariaDB database + */ +export class MariaDbQueryInterface extends AbstractQueryInterface { + /** + * A wrapper that fixes MariaDb's inability to cleanly remove columns from existing tables if they have a foreign key constraint. + * + * @override + */ + async removeColumn(tableName, columnName, options) { + const foreignKeys = await this.showConstraints(tableName, { + ...options, + columnName, + constraintType: 'FOREIGN KEY', + }); + await Promise.all( + foreignKeys.map(constraint => + this.removeConstraint(tableName, constraint.constraintName, options), + ), + ); + + await super.removeColumn(tableName, columnName, options); + } + + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + if (options.bind) { + assertNoReservedBind(options.bind); + } + + const modelDefinition = options.model.modelDefinition; + + options = { ...options }; + + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + options.upsertKeys = Array.from(modelDefinition.primaryKeysAttributeNames, pkAttrName => + modelDefinition.getColumnName(pkAttrName), + ); + + const { bind, query } = this.queryGenerator.insertQuery( + tableName, + insertValues, + getObjectFromMap(modelDefinition.attributes), + options, + ); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + options.bind = combineBinds(options.bind, bind); + + return this.sequelize.queryRaw(query, options); + } +} diff --git a/packages/mariadb/src/query.d.ts b/packages/mariadb/src/query.d.ts new file mode 100644 index 000000000000..68ad3542ea56 --- /dev/null +++ b/packages/mariadb/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class MariaDbQuery extends AbstractQuery {} diff --git a/packages/mariadb/src/query.js b/packages/mariadb/src/query.js new file mode 100644 index 000000000000..3053021857fb --- /dev/null +++ b/packages/mariadb/src/query.js @@ -0,0 +1,333 @@ +'use strict'; + +import { + AbstractQuery, + DataTypes, + DatabaseError, + ForeignKeyConstraintError, + UniqueConstraintError, + UnknownConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { inspect } from '@sequelize/utils'; +import forOwn from 'lodash/forOwn'; +import zipObject from 'lodash/zipObject'; + +const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; +const ER_ROW_IS_REFERENCED = 1451; +const ER_NO_REFERENCED_ROW = 1452; +const ER_CANT_DROP_FIELD_OR_KEY = 1091; + +const debug = logger.debugContext('sql:mariadb'); + +export class MariaDbQuery extends AbstractQuery { + constructor(connection, sequelize, options) { + super(connection, sequelize, { showWarnings: false, ...options }); + } + + async run(sql, parameters) { + this.sql = sql; + const { connection, options } = this; + + const showWarnings = this.sequelize.dialect.options.showWarnings || options.showWarnings; + + const complete = this._logQuery(sql, debug, parameters); + + if (parameters) { + debug('parameters(%j)', parameters); + } + + let results; + + 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 { + // Ignore errors - since MariaDB automatically rolled back, we're + // not that worried about this redundant rollback failing. + } + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } finally { + complete(); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + + return this.formatResults(results); + } + + /** + * High level function that handles the results of a query execution. + * + * + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + * + * @param {Array} data - The result of the query execution. + * @private + */ + formatResults(data) { + let result = this.instance; + + if (this.isBulkUpdateQuery() || this.isDeleteQuery()) { + return data.affectedRows; + } + + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } + + if (this.isInsertQuery(data)) { + this.handleInsertQuery(data); + + if (!this.instance) { + const modelDefinition = this.model?.modelDefinition; + + // handle bulkCreate AI primary key + if ( + modelDefinition?.autoIncrementAttributeName && + modelDefinition?.autoIncrementAttributeName === 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...) + const startId = data[this.getInsertIdField()]; + result = Array.from({ length: data.affectedRows }); + const pkColumnName = modelDefinition.attributes.get( + this.model.primaryKeyAttribute, + ).columnName; + for (let i = 0n; i < data.affectedRows; i++) { + result[i] = { [pkColumnName]: startId + i }; + } + + return [result, data.affectedRows]; + } + + return [data[this.getInsertIdField()], data.affectedRows]; + } + } + + if (this.isSelectQuery()) { + this.handleJsonSelectQuery(data); + + return this.handleSelectQuery(data); + } + + if (this.isInsertQuery() || this.isUpdateQuery()) { + return [result, data.affectedRows]; + } + + if (this.isCallQuery()) { + return data[0]; + } + + if (this.isRawQuery()) { + const meta = data.meta; + + return [data, meta]; + } + + if (this.isShowIndexesQuery()) { + return this.handleShowIndexesQuery(data); + } + + if (this.isShowConstraintsQuery()) { + return data; + } + + if (this.isDescribeQuery()) { + result = {}; + + for (const _result of data) { + result[_result.Field] = { + type: _result.Type.toLowerCase().startsWith('enum') + ? _result.Type.replace(/^enum/i, 'ENUM') + : _result.Type.toUpperCase(), + allowNull: _result.Null === 'YES', + defaultValue: _result.Default, + primaryKey: _result.Key === 'PRI', + autoIncrement: + Object.hasOwn(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment', + comment: _result.Comment ? _result.Comment : null, + }; + } + + return result; + } + + return result; + } + + handleJsonSelectQuery(rows) { + if (!this.model || !this.model.fieldRawAttributesMap) { + return; + } + + const meta = rows.meta; + for (const [i, _field] of Object.keys(this.model.fieldRawAttributesMap).entries()) { + const modelField = this.model.fieldRawAttributesMap[_field]; + if (modelField.type instanceof DataTypes.JSON) { + // Value is returned as String, not JSON + rows = rows.map(row => { + // JSON fields for MariaDB server 10.5.2+ already results in JSON format so we can skip JSON.parse + // In this case the column type field will be MYSQL_TYPE_STRING, but the extended type will indicate 'json' + if ( + row[modelField.fieldName] && + typeof row[modelField.fieldName] === 'string' && + (!meta[i] || meta[i].dataTypeFormat !== 'json') + ) { + row[modelField.fieldName] = JSON.parse(row[modelField.fieldName]); + } + + if (DataTypes.JSON.parse) { + return DataTypes.JSON.parse( + modelField, + this.sequelize.options, + row[modelField.fieldName], + ); + } + + return row; + }); + } + } + } + + formatError(err) { + switch (err.errno) { + 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 fieldVal = match ? match[1] : undefined; + const uniqueKey = + this.model && + this.model.getIndexes().find(index => index.unique && index.name === fieldKey); + + if (uniqueKey) { + if (uniqueKey.msg) { + message = uniqueKey.msg; + } + + fields = zipObject(uniqueKey.fields, values); + } else { + fields[fieldKey] = fieldVal; + } + + const errors = []; + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + case ER_ROW_IS_REFERENCED: + case ER_NO_REFERENCED_ROW: { + // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) + const match = err.message.match( + /CONSTRAINT (["`])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/, + ); + const quoteChar = match ? match[1] : '`'; + const fields = match + ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) + : undefined; + + return new ForeignKeyConstraintError({ + 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, + index: match ? match[2] : undefined, + cause: err, + }); + } + + case ER_CANT_DROP_FIELD_OR_KEY: { + const constraintMatch = err.sql.match(/(?:constraint|index) `(.+?)`/i); + const constraint = constraintMatch ? constraintMatch[1] : undefined; + const tableMatch = err.sql.match(/table `(.+?)`/i); + const table = tableMatch ? tableMatch[1] : undefined; + + return new UnknownConstraintError({ + message: err.text, + constraint, + table, + cause: err, + }); + } + + default: + return new DatabaseError(err); + } + } + + handleShowIndexesQuery(data) { + let currItem; + const result = []; + + for (const item of data) { + if (!currItem || currItem.name !== item.Key_name) { + currItem = { + primary: item.Key_name === 'PRIMARY', + fields: [], + name: item.Key_name, + tableName: item.Table, + unique: item.Non_unique !== '1', + type: item.Index_type, + }; + result.push(currItem); + } + + currItem.fields[item.Seq_in_index - 1] = { + attribute: item.Column_name, + length: item.Sub_part || undefined, + order: + item.Collation === 'A' + ? 'ASC' + : item.Collation === 'D' + ? 'DESC' + : // Not sorted + item.Collation === null + ? null + : (() => { + throw new Error(`Unknown index collation ${inspect(item.Collation)}`); + })(), + }; + } + + return result; + } +} diff --git a/packages/mariadb/tsconfig.json b/packages/mariadb/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/mariadb/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/mariadb/typedoc.json b/packages/mariadb/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/mariadb/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/mssql/.eslintrc.js b/packages/mssql/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/mssql/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/mssql/CHANGELOG.md b/packages/mssql/CHANGELOG.md new file mode 100644 index 000000000000..bdec08a33d0b --- /dev/null +++ b/packages/mssql/CHANGELOG.md @@ -0,0 +1,70 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +**Note:** Version bump only for package @sequelize/mssql + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/mssql + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/mssql + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/mssql + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- **mssql:** update mssql to v18.6.1 ([#17521](https://github.com/sequelize/sequelize/issues/17521)) ([b0ed3eb](https://github.com/sequelize/sequelize/commit/b0ed3eb5757af0b18c47c011ffc42b9c9cb44c46)) +- unify returning queries ([#17157](https://github.com/sequelize/sequelize/issues/17157)) ([0a350c0](https://github.com/sequelize/sequelize/commit/0a350c0f91d0eee9c56b92f47cc23c273c9eb206)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +### Bug Fixes + +- **mssql:** add ability to use instanceName in connection-manager config ([#17432](https://github.com/sequelize/sequelize/issues/17432)) ([b2e0d69](https://github.com/sequelize/sequelize/commit/b2e0d69c3b4071c616f0e6ef8ceea8dfc3cbf284)) + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +**Note:** Version bump only for package @sequelize/mssql + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +- feat(mssql)!: move mssql to the `@sequelize/mssql` package (#17206) ([8631f5a](https://github.com/sequelize/sequelize/commit/8631f5a51cf81e244f3160d753865bdfa0a2f539)), closes [#17206](https://github.com/sequelize/sequelize/issues/17206) + +### Features + +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` +- Instead of installing the `mssql` package, users need to install `@sequelize/mssql`. diff --git a/packages/mssql/package.json b/packages/mssql/package.json new file mode 100644 index 000000000000..166eaeb94c2e --- /dev/null +++ b/packages/mssql/package.json @@ -0,0 +1,54 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "Microsoft SQL Server Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/mssql", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs mssql", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-unit": "mocha src/**/*.test.ts", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "tedious": "^19.1.2" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "@types/sinon": "17.0.4", + "chai": "4.5.0", + "mocha": "11.7.5", + "sinon": "18.0.1" + } +} diff --git a/packages/mssql/src/_internal/async-queue.test.ts b/packages/mssql/src/_internal/async-queue.test.ts new file mode 100644 index 000000000000..ecbbac1a5070 --- /dev/null +++ b/packages/mssql/src/_internal/async-queue.test.ts @@ -0,0 +1,59 @@ +import { ConnectionError } from '@sequelize/core'; +import { assert, expect } from 'chai'; +import sinon from 'sinon'; +import { AsyncQueueError } from '../async-queue-error'; +import { AsyncQueue } from './async-queue'; + +const asyncFunction = async () => 'test'; + +describe('AsyncQueue', () => { + let queue: AsyncQueue; + + beforeEach(() => { + queue = new AsyncQueue(); + }); + + it('should initialize correctly', () => { + assert(queue instanceof AsyncQueue); + expect(queue.closed).to.be.false; + }); + + it('should close correctly', () => { + queue.close(); + expect(queue.closed).to.be.true; + }); + + it('should enqueue and execute function correctly', async () => { + const mockAsyncFunction = sinon.stub().resolves('test'); + const result = await queue.enqueue(mockAsyncFunction); + expect(result).to.equal('test'); + expect(mockAsyncFunction.calledOnce).to.be.true; + }); + + it('should reject if closed before execution', async () => { + queue.close(); + try { + await queue.enqueue(asyncFunction); + } catch (error) { + assert(error instanceof ConnectionError); + expect(error.cause).to.be.instanceOf( + AsyncQueueError, + 'the connection was closed before this query could be executed', + ); + } + }); + + it('should reject if closed during execution', async () => { + const promise = queue.enqueue(asyncFunction); + queue.close(); + try { + await promise; + } catch (error) { + assert(error instanceof ConnectionError); + expect(error.cause).to.be.instanceOf( + AsyncQueueError, + 'the connection was closed before this query could finish executing', + ); + } + }); +}); diff --git a/packages/mssql/src/_internal/async-queue.ts b/packages/mssql/src/_internal/async-queue.ts new file mode 100644 index 000000000000..feae67c65451 --- /dev/null +++ b/packages/mssql/src/_internal/async-queue.ts @@ -0,0 +1,45 @@ +import { ConnectionError } from '@sequelize/core'; +import { AsyncQueueError } from '../async-queue-error'; + +export class AsyncQueue { + previous: Promise; + closed: boolean; + rejectCurrent: (reason?: any) => void; + + constructor() { + this.previous = Promise.resolve(); + this.closed = false; + this.rejectCurrent = () => { + /** do nothing */ + }; + } + + close() { + this.closed = true; + this.rejectCurrent( + new ConnectionError( + new AsyncQueueError('the connection was closed before this query could finish executing'), + ), + ); + } + + async enqueue(asyncFunction: (...args: any[]) => Promise) { + // 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(async () => { + 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); + }); + }); + } +} diff --git a/packages/mssql/src/_internal/connection-options.ts b/packages/mssql/src/_internal/connection-options.ts new file mode 100644 index 000000000000..67617e1806f8 --- /dev/null +++ b/packages/mssql/src/_internal/connection-options.ts @@ -0,0 +1,121 @@ +import { getSynchronizedTypeKeys, type NonUndefined, type PickByType } from '@sequelize/utils'; +import type * as Tedious from 'tedious'; +import type { MsSqlConnectionOptions } from '../connection-manager.js'; + +export type InlinedTediousOptions = Omit< + NonUndefined, + // Default nullability of columns is not needed as Sequelize explicitly sets it, + // and this option will be confusing with Sequelize's option about default nullability. + | 'enableAnsiNullDefault' + | 'camelCaseColumns' + | 'columnNameReplacer' + | 'enableQuotedIdentifier' + | 'useUTC' + | 'useColumnNames' + // Conflicts with our own isolationLevel option, which does the same thing + | 'isolationLevel' +>; + +export const INLINED_OPTION_OBJ = { + abortTransactionOnError: undefined, + appName: undefined, + cancelTimeout: undefined, + connectTimeout: undefined, + connectionIsolationLevel: undefined, + connectionRetryInterval: undefined, + connector: undefined, + cryptoCredentialsDetails: undefined, + database: undefined, + dateFormat: undefined, + datefirst: undefined, + debug: undefined, + enableAnsiNull: undefined, + enableAnsiPadding: undefined, + enableAnsiWarnings: undefined, + enableArithAbort: undefined, + enableConcatNullYieldsNull: undefined, + enableCursorCloseOnCommit: undefined, + enableImplicitTransactions: undefined, + enableNumericRoundabort: undefined, + encrypt: undefined, + fallbackToDefaultDb: undefined, + instanceName: undefined, + language: undefined, + localAddress: undefined, + lowerCaseGuids: undefined, + maxRetriesOnTransientErrors: undefined, + multiSubnetFailover: undefined, + packetSize: undefined, + port: undefined, + readOnlyIntent: undefined, + requestTimeout: undefined, + rowCollectionOnDone: undefined, + rowCollectionOnRequestCompletion: undefined, + serverName: undefined, + tdsVersion: undefined, + textsize: undefined, + trustServerCertificate: undefined, + workstationId: undefined, +} as const satisfies Record; + +export const INLINED_OPTION_NAMES = + getSynchronizedTypeKeys(INLINED_OPTION_OBJ); + +export const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + ...INLINED_OPTION_OBJ, + authentication: undefined, + server: undefined, +}); + +type StringConnectionOptions = PickByType; + +export const STRING_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + appName: undefined, + database: undefined, + dateFormat: undefined, + encrypt: undefined, + instanceName: undefined, + language: undefined, + localAddress: undefined, + server: undefined, + serverName: undefined, + tdsVersion: undefined, + workstationId: undefined, +}); + +type BooleanConnectionOptions = PickByType; + +export const BOOLEAN_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + abortTransactionOnError: undefined, + enableAnsiNull: undefined, + enableAnsiPadding: undefined, + enableAnsiWarnings: undefined, + enableArithAbort: undefined, + enableConcatNullYieldsNull: undefined, + enableCursorCloseOnCommit: undefined, + enableImplicitTransactions: undefined, + enableNumericRoundabort: undefined, + encrypt: undefined, + fallbackToDefaultDb: undefined, + lowerCaseGuids: undefined, + multiSubnetFailover: undefined, + readOnlyIntent: undefined, + rowCollectionOnDone: undefined, + rowCollectionOnRequestCompletion: undefined, + trustServerCertificate: undefined, +}); + +type NumberConnectionOptions = PickByType; + +export const NUMBER_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + cancelTimeout: undefined, + connectionRetryInterval: undefined, + connectTimeout: undefined, + connectionIsolationLevel: undefined, + datefirst: undefined, + maxRetriesOnTransientErrors: undefined, + packetSize: undefined, + port: undefined, + requestTimeout: undefined, + textsize: undefined, +}); diff --git a/packages/mssql/src/_internal/data-types-db.ts b/packages/mssql/src/_internal/data-types-db.ts new file mode 100644 index 000000000000..2e82a59fc36f --- /dev/null +++ b/packages/mssql/src/_internal/data-types-db.ts @@ -0,0 +1,58 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import type { MsSqlDialect } from '../dialect.js'; + +dayjs.extend(utc); + +/** + * First pass of DB value parsing: Parses based on the MSSQL Type ID. + * If a Sequelize DataType is specified, the value is then passed to {@link DataTypes.ABSTRACT#parseDatabaseValue}. + * + * @param dialect + */ +export function registerMsSqlDbDataTypeParsers(dialect: MsSqlDialect) { + dialect.registerDataTypeParser(['GUIDN'], (value: unknown) => { + if (typeof value !== 'string') { + return value; + } + + // unify with other dialects by forcing lowercase on UUID strings. + return value.toLowerCase(); + }); + + dialect.registerDataTypeParser(['TIMEN'], (value: unknown) => { + if (value instanceof Date) { + // We lose precision past the millisecond because Tedious pre-parses the value. + // This could be fixed by https://github.com/tediousjs/tedious/issues/678 + return dayjs.utc(value).format('HH:mm:ss.SSS'); + } + + return value; + }); + + dialect.registerDataTypeParser(['DATETIMEOFFSETN'], (value: unknown) => { + if (value instanceof Date) { + // Tedious pre-parses the value as a Date, but we want + // to provide a string in raw queries and let the user decide on which date library to use. + // As a result, Tedious parses the date, then we serialize it, then our Date data type parses it again. + // This is inefficient but could be fixed by https://github.com/tediousjs/tedious/issues/678 + // We also lose precision past the millisecond because Tedious pre-parses the value. + return dayjs.utc(value).format('YYYY-MM-DD HH:mm:ss.SSS+00'); + } + + return value; + }); + + dialect.registerDataTypeParser(['DATEN'], (value: unknown) => { + if (value instanceof Date) { + return dayjs.utc(value).format('YYYY-MM-DD'); + } + + return value; + }); + + dialect.registerDataTypeParser(['DECIMAL', 'DECIMALN'], (value: unknown) => { + // Tedious returns DECIMAL as a JS number, which is not an appropriate type for a decimal. + return String(value); + }); +} diff --git a/packages/mssql/src/_internal/data-types-overrides.ts b/packages/mssql/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..919fdc6151af --- /dev/null +++ b/packages/mssql/src/_internal/data-types-overrides.ts @@ -0,0 +1,241 @@ +import type { AbstractDialect } from '@sequelize/core'; +import { BaseError } from '@sequelize/core'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import maxBy from 'lodash/maxBy'; +import NodeUtil from 'node:util'; + +function removeUnsupportedIntegerOptions( + dataType: BaseTypes.BaseIntegerDataType, + dialect: AbstractDialect, +) { + if (dataType.options.length != null) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support '${dataType.constructor.name}' with length specified. This options is ignored.`, + ); + + delete dataType.options.length; + } +} + +export class BLOB extends BaseTypes.BLOB { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + // tiny = 2^8 + // regular = 2^16 + // medium = 2^24 + // long = 2^32 + // in mssql, anything above 8000 bytes must be MAX + + if (this.options.length != null && this.options.length.toLowerCase() !== 'tiny') { + dialect.warnDataTypeIssue( + `${dialect.name}: ${this.getDataTypeId()} cannot limit its size beyond length=tiny. This option is ignored, in favor of the highest size possible.`, + ); + } + } + + toSql() { + if (this.options.length && this.options.length.toLowerCase() === 'tiny') { + return 'VARBINARY(256)'; + } + + return 'VARBINARY(MAX)'; + } +} + +export class STRING extends BaseTypes.STRING { + toSql() { + return `NVARCHAR(${this.options.length ?? 255})`; + } +} + +export class TEXT extends BaseTypes.TEXT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + // tiny = 2^8 + // regular = 2^16 + // medium = 2^24 + // long = 2^32 + // in mssql, anything above 8000 bytes must be MAX + + if (this.options.length != null && this.options.length.toLowerCase() !== 'tiny') { + dialect.warnDataTypeIssue( + `${dialect.name}: ${this.getDataTypeId()} cannot limit its size beyond length=tiny. This option is ignored, in favor of the highest size possible.`, + ); + } + } + + toSql() { + if (this.options.length && this.options.length.toLowerCase() === 'tiny') { + return 'NVARCHAR(256)'; + } + + return 'NVARCHAR(MAX)'; + } +} + +export class BOOLEAN extends BaseTypes.BOOLEAN { + escape(value: boolean | unknown): string { + return value ? '1' : '0'; + } + + toBindableValue(value: boolean | unknown): unknown { + return value ? 1 : 0; + } + + toSql() { + return 'BIT'; + } +} + +export class UUID extends BaseTypes.UUID { + toSql() { + return 'UNIQUEIDENTIFIER'; + } +} + +export class NOW extends BaseTypes.NOW { + toSql() { + return 'GETDATE()'; + } +} + +export class DATE extends BaseTypes.DATE { + toSql() { + if (this.options.precision != null) { + return `DATETIMEOFFSET(${this.options.precision})`; + } + + return 'DATETIMEOFFSET'; + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add check constraint between -128 & 127 inclusive when the unsigned option is false + + toSql() { + if (!this.options.unsigned) { + return 'SMALLINT'; + } + + // tinyint is always unsigned in mssql + return 'TINYINT'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add check constraint between 0 & 65535 inclusive when the unsigned option is true + + toSql() { + if (this.options.unsigned) { + return 'INT'; + } + + return 'SMALLINT'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: unsigned: add check constraint between 0 & 16777215 inclusive + // TODO: signed: add check constraint between -8388608 & 8388607 inclusive + + toSql() { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO:add check constraint between 0 & 4294967295 inclusive when the unsigned option is true + + toSql() { + if (this.options.unsigned) { + return 'BIGINT'; + } + + return 'INTEGER'; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } +} + +export class FLOAT extends BaseTypes.FLOAT { + // TODO: add check constraint >= 0 if unsigned is true + + protected getNumberSqlTypeName(): string { + return 'REAL'; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + // TODO: add check constraint >= 0 if unsigned is true +} + +export class DECIMAL extends BaseTypes.DECIMAL { + // TODO: add check constraint >= 0 if unsigned is true +} + +// https://learn.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver16 +export class JSON extends BaseTypes.JSON { + // TODO: add constraint + // https://learn.microsoft.com/en-us/sql/t-sql/functions/isjson-transact-sql?view=sql-server-ver16 + + parseDatabaseValue(value: unknown): unknown { + if (typeof value !== 'string') { + throw new BaseError( + `DataTypes.JSON received a non-string value from the database, which it cannot parse: ${NodeUtil.inspect(value)}.`, + ); + } + + try { + return globalThis.JSON.parse(value); + } catch (error) { + throw new BaseError( + `DataTypes.JSON received a value from the database that it not valid JSON: ${NodeUtil.inspect(value)}.`, + { cause: error }, + ); + } + } + + toSql() { + return 'NVARCHAR(MAX)'; + } +} + +export class ENUM extends BaseTypes.ENUM { + // TODO: add constraint + + toSql() { + const minLength = maxBy(this.options.values, value => value.length)?.length ?? 0; + + // mssql does not have an ENUM type, we use NVARCHAR instead. + // It is not possible to create an index on NVARCHAR(MAX), so we use 255 which should be plenty for everyone + // but just in case, we also increase the length if the longest value is longer than 255 characters + return `NVARCHAR(${Math.max(minLength, 255)})`; + } +} diff --git a/packages/mssql/src/_internal/symbols.ts b/packages/mssql/src/_internal/symbols.ts new file mode 100644 index 000000000000..91a04b32a73a --- /dev/null +++ b/packages/mssql/src/_internal/symbols.ts @@ -0,0 +1 @@ +export const ASYNC_QUEUE = Symbol('async-queue'); diff --git a/packages/mssql/src/async-queue-error.ts b/packages/mssql/src/async-queue-error.ts new file mode 100644 index 000000000000..b15794b7b3f7 --- /dev/null +++ b/packages/mssql/src/async-queue-error.ts @@ -0,0 +1,11 @@ +import { BaseError } from '@sequelize/core'; + +/** + * Thrown when a connection to a database is closed while an operation is in progress + */ +export class AsyncQueueError extends BaseError { + constructor(message: string) { + super(message); + this.name = 'SequelizeAsyncQueueError'; + } +} diff --git a/packages/mssql/src/connection-manager.ts b/packages/mssql/src/connection-manager.ts new file mode 100644 index 000000000000..1d9d9a7583c7 --- /dev/null +++ b/packages/mssql/src/connection-manager.ts @@ -0,0 +1,174 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + AccessDeniedError, + ConnectionError, + ConnectionRefusedError, + HostNotFoundError, + HostNotReachableError, + InvalidConnectionError, +} from '@sequelize/core'; +import { isErrorWithStringCode } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { removeUndefined } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { isError, splitObject } from '@sequelize/utils'; +import * as Tedious from 'tedious'; +import { AsyncQueue } from './_internal/async-queue.js'; +import type { InlinedTediousOptions } from './_internal/connection-options.js'; +import { INLINED_OPTION_NAMES } from './_internal/connection-options.js'; +import { ASYNC_QUEUE } from './_internal/symbols.js'; +import type { MsSqlDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:mssql'); +const debugTedious = logger.debugContext('connection:mssql:tedious'); + +export interface MsSqlConnection extends AbstractConnection, Tedious.Connection { + // custom properties we attach to the connection + [ASYNC_QUEUE]: AsyncQueue; +} + +export type MsSqlConnectionOptions = Partial> & + // We inline "options" with the other options, so we can allowlist them. + InlinedTediousOptions; + +export type TediousModule = typeof Tedious; + +export class MsSqlConnectionManager extends AbstractConnectionManager< + MsSqlDialect, + MsSqlConnection +> { + readonly #lib: TediousModule; + + constructor(dialect: MsSqlDialect) { + super(dialect); + this.#lib = dialect.options.tediousModule ?? Tedious; + } + + async connect(connectionOptions: ConnectionOptions): Promise { + const [inlinedOptions, regularOptions] = splitObject(connectionOptions, INLINED_OPTION_NAMES); + + const tediousConfig: Partial = { + ...regularOptions, + options: removeUndefined(inlinedOptions), + }; + + try { + return await new Promise((resolve, reject) => { + const connection: MsSqlConnection = new this.#lib.Connection( + tediousConfig as Tedious.ConnectionConfiguration, + ) as MsSqlConnection; + if (connection.state === connection.STATE.INITIALIZED) { + connection.connect(); + } + + connection[ASYNC_QUEUE] = new AsyncQueue(); + + const connectHandler = (error: unknown) => { + connection.removeListener('end', endHandler); + connection.removeListener('error', errorHandler); + + if (error) { + return void 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: unknown) => { + 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: unknown) => { + if ( + isErrorWithStringCode(error) && + (error.code === 'ESOCKET' || error.code === 'ECONNRESET') + ) { + void this.sequelize.pool.destroy(connection); + } + }); + + if (tediousConfig.options?.debug) { + connection.on('debug', debugTedious.log.bind(debugTedious)); + } + }); + } catch (error: unknown) { + isError.assert(error); + + if (!isErrorWithStringCode(error)) { + throw new ConnectionError(error); + } + + switch (error.code) { + case 'ESOCKET': + if (error.message.includes('connect EHOSTUNREACH')) { + throw new HostNotReachableError(error); + } + + if (error.message.includes('connect ENETUNREACH')) { + throw new HostNotReachableError(error); + } + + if (error.message.includes('connect EADDRNOTAVAIL')) { + throw new HostNotReachableError(error); + } + + if (error.message.includes('getaddrinfo ENOTFOUND')) { + throw new HostNotFoundError(error); + } + + if (error.message.includes('connect ECONNREFUSED')) { + throw new ConnectionRefusedError(error); + } + + throw new ConnectionError(error); + case 'ER_ACCESS_DENIED_ERROR': + case 'ELOGIN': + throw new AccessDeniedError(error); + case 'EINVAL': + throw new InvalidConnectionError(error); + default: + throw new ConnectionError(error); + } + } + } + + async disconnect(connection: MsSqlConnection): Promise { + // Don't disconnect a connection that is already disconnected + if (connection.closed) { + return; + } + + connection[ASYNC_QUEUE].close(); + + await new Promise(resolve => { + connection.on('end', resolve); + connection.close(); + debug('connection closed'); + }); + } + + validate(connection: MsSqlConnection) { + return connection?.state.name === 'LoggedIn'; + } +} diff --git a/packages/mssql/src/dialect.test.ts b/packages/mssql/src/dialect.test.ts new file mode 100644 index 000000000000..e52871354ba9 --- /dev/null +++ b/packages/mssql/src/dialect.test.ts @@ -0,0 +1,28 @@ +import { Sequelize } from '@sequelize/core'; +import type { MsSqlConnectionOptions } from '@sequelize/mssql'; +import { MsSqlDialect } from '@sequelize/mssql'; +import { expect } from 'chai'; + +describe('MsSqlDialect#parseConnectionUrl', () => { + const dialect = new Sequelize({ dialect: MsSqlDialect }).dialect; + + it('parses connection URL', () => { + const options: MsSqlConnectionOptions = dialect.parseConnectionUrl( + 'sqlserver://user:password@localhost:1234/dbname?language=en', + ); + + expect(options).to.deep.eq({ + server: 'localhost', + port: 1234, + database: 'dbname', + language: 'en', + authentication: { + type: 'default', + options: { + userName: 'user', + password: 'password', + }, + }, + }); + }); +}); diff --git a/packages/mssql/src/dialect.ts b/packages/mssql/src/dialect.ts new file mode 100644 index 000000000000..2005c507a0e6 --- /dev/null +++ b/packages/mssql/src/dialect.ts @@ -0,0 +1,194 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import { parseCommonConnectionUrlOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/connection-options.js'; +import { createNamedParamBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import { + BOOLEAN_CONNECTION_OPTION_NAMES, + CONNECTION_OPTION_NAMES, + NUMBER_CONNECTION_OPTION_NAMES, + STRING_CONNECTION_OPTION_NAMES, +} from './_internal/connection-options.js'; +import { registerMsSqlDbDataTypeParsers } from './_internal/data-types-db.js'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { MsSqlConnectionOptions, TediousModule } from './connection-manager.js'; +import { MsSqlConnectionManager } from './connection-manager.js'; +import { MsSqlQueryGenerator } from './query-generator.js'; +import { MsSqlQueryInterface } from './query-interface.js'; +import { MsSqlQuery } from './query.js'; + +export { TDS_VERSION, ISOLATION_LEVEL as TEDIOUS_ISOLATION_LEVEL } from 'tedious'; + +export interface MsSqlDialectOptions { + /** + * The tedious library to use. + * If not provided, the tedious npm library will be used. + * Must be compatible with the tedious npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + tediousModule?: TediousModule; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + tediousModule: undefined, +}); + +export class MsSqlDialect extends AbstractDialect { + static supports = AbstractDialect.extendSupport({ + 'DEFAULT VALUES': true, + 'LIMIT ON UPDATE': true, + migrations: false, + returnValues: 'output', + schemas: true, + multiDatabases: true, + autoIncrement: { + identityInsert: true, + defaultValue: false, + update: false, + }, + alterColumn: { + unique: false, + }, + constraints: { + restrict: false, + default: true, + removeOptions: { ifExists: true }, + }, + index: { + collate: false, + type: true, + using: false, + where: true, + include: true, + }, + tmpTableTrigger: true, + dataTypes: { + JSON: true, + // TODO: https://learn.microsoft.com/en-us/sql/t-sql/spatial-geography/spatial-types-geography?view=sql-server-ver16 + GEOGRAPHY: false, + // TODO: https://learn.microsoft.com/en-us/sql/t-sql/spatial-geometry/spatial-types-geometry-transact-sql?view=sql-server-ver16 + GEOMETRY: false, + }, + uuidV4Generation: true, + jsonOperations: true, + jsonExtraction: { + unquoted: true, + quoted: false, + }, + tableHints: true, + removeColumn: { + ifExists: true, + }, + renameTable: { + changeSchemaAndTable: false, + }, + createSchema: { + authorization: true, + }, + connectionTransactionMethods: true, + settingIsolationLevelDuringTransaction: false, + startTransaction: { + useBegin: true, + }, + delete: { + limit: false, + }, + }); + + readonly connectionManager: MsSqlConnectionManager; + readonly queryGenerator: MsSqlQueryGenerator; + readonly queryInterface: MsSqlQueryInterface; + readonly Query = MsSqlQuery; + + constructor(sequelize: Sequelize, options: MsSqlDialectOptions) { + super({ + name: 'mssql', + sequelize, + dataTypeOverrides: DataTypes, + identifierDelimiter: { + start: '[', + end: ']', + }, + options, + dataTypesDocumentationUrl: + 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx', + // SQL Server 2017 Express (version 14), minimum supported version, all the way + // up to the most recent version. When increasing this version, remember to + // update also the minimum version in the documentation at + // https://github.com/sequelize/website/blob/main/docs/other-topics/dialect-specific-things.md + // and set the relevant years for the mssql Docker images in the ci.yml file at + // .github/workflows/ci.yml + minimumDatabaseVersion: '14.0.1000', + }); + + this.connectionManager = new MsSqlConnectionManager(this); + this.queryGenerator = new MsSqlQueryGenerator(this); + this.queryInterface = new MsSqlQueryInterface(this); + + registerMsSqlDbDataTypeParsers(this); + } + + createBindCollector() { + return createNamedParamBindCollector('@'); + } + + escapeBuffer(buffer: Buffer): string { + const hex = buffer.toString('hex'); + + return `0x${hex}`; + } + + escapeString(value: string): string { + // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS + // http://stackoverflow.com/q/603572/130598 + value = value.replaceAll("'", "''"); + + return `N'${value}'`; + } + + getDefaultSchema(): string { + return 'dbo'; + } + + parseConnectionUrl(url: string): MsSqlConnectionOptions { + const urlObject = new URL(url); + + const options: MsSqlConnectionOptions = parseCommonConnectionUrlOptions({ + allowedProtocols: ['sqlserver'], + url: urlObject, + hostname: 'server', + port: 'port', + pathname: 'database', + stringSearchParams: STRING_CONNECTION_OPTION_NAMES, + booleanSearchParams: BOOLEAN_CONNECTION_OPTION_NAMES, + numberSearchParams: NUMBER_CONNECTION_OPTION_NAMES, + }); + + if (urlObject.username || urlObject.password) { + options.authentication = { + type: 'default', + options: {}, + }; + + if (urlObject.username) { + options.authentication.options.userName = decodeURIComponent(urlObject.username); + } + + if (urlObject.password) { + options.authentication.options.password = decodeURIComponent(urlObject.password); + } + } + + return options; + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/mssql/src/index.mjs b/packages/mssql/src/index.mjs new file mode 100644 index 000000000000..c599778e9f87 --- /dev/null +++ b/packages/mssql/src/index.mjs @@ -0,0 +1,10 @@ +import Pkg from './index.js'; + +export const AsyncQueueError = Pkg.AsyncQueueError; +export const MsSqlConnectionManager = Pkg.MsSqlConnectionManager; +export const MsSqlDialect = Pkg.MsSqlDialect; +export const MsSqlQueryGenerator = Pkg.MsSqlQueryGenerator; +export const MsSqlQueryInterface = Pkg.MsSqlQueryInterface; +export const MsSqlQuery = Pkg.MsSqlQuery; +export const TDS_VERSION = Pkg.TDS_VERSION; +export const TEDIOUS_ISOLATION_LEVEL = Pkg.TEDIOUS_ISOLATION_LEVEL; diff --git a/packages/mssql/src/index.ts b/packages/mssql/src/index.ts new file mode 100644 index 000000000000..605304f308d3 --- /dev/null +++ b/packages/mssql/src/index.ts @@ -0,0 +1,8 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './async-queue-error.js'; +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/mssql/src/query-generator-typescript.internal.ts b/packages/mssql/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..ab16a464ad3a --- /dev/null +++ b/packages/mssql/src/query-generator-typescript.internal.ts @@ -0,0 +1,348 @@ +import type { + BulkDeleteQueryOptions, + ConstraintType, + CreateDatabaseQueryOptions, + Expression, + ListDatabasesQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator } from '@sequelize/core'; +import type { EscapeOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { + CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { buildJsonPath } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/json.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import { randomBytes } from 'node:crypto'; +import type { MsSqlDialect } from './dialect.js'; +import { MsSqlQueryGeneratorInternal } from './query-generator.internal.js'; + +const CREATE_DATABASE_QUERY_SUPPORTED_OPTIONS = new Set([ + 'collate', +]); +const REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS = new Set(['ifExists']); + +/** + * Temporary class to ease the TypeScript migration + */ +export class MsSqlQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: MsSqlQueryGeneratorInternal; + + constructor( + dialect: MsSqlDialect, + internals: MsSqlQueryGeneratorInternal = new MsSqlQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + this.#internals = internals; + } + + createDatabaseQuery(database: string, options?: CreateDatabaseQueryOptions) { + if (options) { + rejectInvalidOptions( + 'createDatabaseQuery', + this.dialect, + CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_DATABASE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + return joinSQLFragments([ + `IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = ${this.escape(database)})`, + `CREATE DATABASE ${this.quoteIdentifier(database)}`, + options?.collate ? `COLLATE ${this.escape(options.collate)}` : '', + ]); + } + + listDatabasesQuery(options?: ListDatabasesQueryOptions) { + let databasesToSkip = this.#internals.getTechnicalDatabaseNames(); + if (options && Array.isArray(options?.skip)) { + databasesToSkip = [...databasesToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT [name] FROM sys.databases', + `WHERE [name] NOT IN (${databasesToSkip.map(database => this.escape(database)).join(', ')})`, + ]); + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + const schemasToSkip = ['dbo', 'guest', ...this.#internals.getTechnicalSchemaNames()]; + + if (options?.skip) { + schemasToSkip.push(...options.skip); + } + + return joinSQLFragments([ + 'SELECT [name] AS [schema] FROM sys.schemas', + `WHERE [name] NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT', + `c.COLUMN_NAME AS 'Name',`, + `c.DATA_TYPE AS 'Type',`, + `c.CHARACTER_MAXIMUM_LENGTH AS 'Length',`, + `c.IS_NULLABLE as 'IsNull',`, + `COLUMN_DEFAULT AS 'Default',`, + `pk.CONSTRAINT_TYPE AS 'Constraint',`, + `COLUMNPROPERTY(OBJECT_ID('[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',`, + `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', + '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`, + 'ON pk.table_schema=c.table_schema', + 'AND pk.table_name=c.table_name', + 'AND pk.column_name=c.column_name', + 'INNER JOIN sys.columns AS sc', + `ON sc.object_id = object_id('[' + t.table_schema + '].[' + t.table_name + ']') AND sc.name = c.column_name`, + 'LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id', + 'AND prop.minor_id = sc.column_id', + `AND prop.name = 'MS_Description'`, + `WHERE t.TABLE_NAME = ${this.escape(table.tableName)}`, + `AND t.TABLE_SCHEMA = ${this.escape(table.schema)}`, + ]); + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT t.name AS [tableName], s.name AS [schema]', + `FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.type = 'U'`, + options?.schema + ? `AND s.name = ${this.escape(options.schema)}` + : `AND s.name NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY s.name, t.name', + ]); + } + + renameTableQuery( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableQueryOptions, + ): string { + const beforeTable = this.extractTableDetails(beforeTableName); + const afterTable = this.extractTableDetails(afterTableName); + + if (beforeTable.schema !== afterTable.schema) { + if (!options?.changeSchema) { + throw new Error( + 'To move a table between schemas, you must set `options.changeSchema` to true.', + ); + } + + if (beforeTable.tableName !== afterTable.tableName) { + throw new Error( + `Renaming a table and moving it to a different schema is not supported by ${this.dialect.name}.`, + ); + } + + return `ALTER SCHEMA ${this.quoteIdentifier(afterTable.schema)} TRANSFER ${this.quoteTable(beforeTableName)}`; + } + + return `EXEC sp_rename '${this.quoteTable(beforeTableName)}', ${this.escape(afterTable.tableName)}`; + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE TABLE ${this.quoteTable(tableName)}`; + } + + #getConstraintType(type: ConstraintType): string { + switch (type) { + case 'CHECK': + return 'CHECK_CONSTRAINT'; + case 'DEFAULT': + return 'DEFAULT_CONSTRAINT'; + case 'FOREIGN KEY': + return 'FOREIGN_KEY_CONSTRAINT'; + case 'PRIMARY KEY': + return 'PRIMARY_KEY_CONSTRAINT'; + case 'UNIQUE': + return 'UNIQUE_CONSTRAINT'; + default: + throw new Error(`Constraint type ${type} is not supported`); + } + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + `SELECT DB_NAME() AS constraintCatalog`, + `, s.[name] AS constraintSchema`, + `, c.constraintName`, + `, REPLACE(LEFT(c.constraintType, CHARINDEX('_CONSTRAINT', c.constraintType) - 1), '_', ' ') AS constraintType`, + `, DB_NAME() AS tableCatalog`, + `, s.[name] AS tableSchema`, + `, t.[name] AS tableName`, + `, c.columnNames`, + `, c.referencedTableSchema`, + `, c.referencedTableName`, + `, c.referencedColumnNames`, + `, c.deleteAction`, + `, c.updateAction`, + `, c.definition`, + `FROM sys.tables t`, + `INNER JOIN sys.schemas s ON t.schema_id = s.schema_id`, + `INNER JOIN (`, + `SELECT kc.[name] AS constraintName, kc.[type_desc] AS constraintType, kc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema`, + `, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, null AS [definition], null AS column_id FROM sys.key_constraints kc`, + `LEFT JOIN sys.indexes i ON kc.name = i.name LEFT JOIN sys.index_columns ic ON ic.index_id = i.index_id AND ic.object_id = kc.parent_object_id LEFT JOIN sys.columns c ON c.column_id = ic.column_id AND c.object_id = kc.parent_object_id`, + `UNION ALL SELECT [name] AS constraintName, [type_desc] AS constraintType, [parent_object_id] AS constraintTableId, null AS columnNames, null as referencedTableSchema, null AS referencedTableName`, + `, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.check_constraints c UNION ALL`, + `SELECT dc.[name] AS constraintName, dc.[type_desc] AS constraintType, dc.[parent_object_id] AS constraintTableId, c.[name] AS columnNames, null as referencedTableSchema`, + `, null AS referencedTableName, null AS referencedColumnNames, null AS deleteAction, null AS updateAction, [definition], null AS column_id FROM sys.default_constraints dc`, + `INNER JOIN sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id UNION ALL`, + `SELECT k.[name] AS constraintName, k.[type_desc] AS constraintType, k.[parent_object_id] AS constraintTableId, fcol.[name] AS columnNames, OBJECT_SCHEMA_NAME(k.[referenced_object_id]) as referencedTableSchema`, + `, OBJECT_NAME(k.[referenced_object_id]) AS referencedTableName, rcol.[name] AS referencedColumnNames, k.[delete_referential_action_desc] AS deleteAction, k.[update_referential_action_desc] AS updateAction`, + `, null AS [definition], rcol.column_id FROM sys.foreign_keys k INNER JOIN sys.foreign_key_columns c ON k.[object_id] = c.constraint_object_id`, + `INNER JOIN sys.columns fcol ON c.parent_column_id = fcol.column_id AND c.parent_object_id = fcol.object_id INNER JOIN sys.columns rcol ON c.referenced_column_id = rcol.column_id AND c.referenced_object_id = rcol.object_id`, + `) c ON t.object_id = c.constraintTableId`, + `WHERE s.name = ${this.escape(table.schema)} AND t.name = ${this.escape(table.tableName)}`, + options?.columnName ? `AND c.columnNames = ${this.escape(options.columnName)}` : '', + options?.constraintName + ? `AND c.constraintName = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.constraintType = ${this.escape(this.#getConstraintType(options.constraintType))}` + : '', + `ORDER BY c.constraintName, c.column_id`, + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + const objectId = table?.schema ? `${table.schema}.${table.tableName}` : `${table.tableName}`; + + return joinSQLFragments([ + 'SELECT', + 'I.[name] AS [index_name],', + 'I.[type_desc] AS [index_type],', + 'C.[name] AS [column_name],', + 'IC.[is_descending_key],', + 'IC.[is_included_column],', + 'I.[is_unique],', + 'I.[is_primary_key],', + 'I.[is_unique_constraint]', + 'FROM sys.indexes I', + 'INNER JOIN sys.index_columns IC ON IC.index_id = I.index_id AND IC.object_id = I.object_id', + 'INNER JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id', + `WHERE I.[object_id] = OBJECT_ID(${this.escape(objectId)}) ORDER BY I.[name];`, + ]); + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + let indexName: string; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return joinSQLFragments([ + 'DROP INDEX', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(indexName), + 'ON', + this.quoteTable(tableName), + ]); + } + + createSavepointQuery(savepointName: string): string { + return `SAVE TRANSACTION ${this.quoteIdentifier(savepointName)}`; + } + + rollbackSavepointQuery(savepointName: string): string { + return `ROLLBACK TRANSACTION ${this.quoteIdentifier(savepointName)}`; + } + + generateTransactionId(): string { + return randomBytes(10).toString('hex'); + } + + jsonPathExtractionQuery( + sqlExpression: string, + path: ReadonlyArray, + unquote: boolean, + ): string { + if (!unquote) { + throw new Error( + `JSON Paths are not supported in ${this.dialect.name} without unquoting the JSON value.`, + ); + } + + return `JSON_VALUE(${sqlExpression}, ${this.escape(buildJsonPath(path))})`; + } + + formatUnquoteJson(arg: Expression, options?: EscapeOptions) { + return `JSON_VALUE(${this.escape(arg, options)})`; + } + + versionQuery() { + // Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch + return `DECLARE @ms_ver NVARCHAR(20); +SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion'))); +SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'`; + } + + getUuidV4FunctionCall(): string { + return 'NEWID()'; + } + + bulkDeleteQuery(tableOrModel: TableOrModel, options: BulkDeleteQueryOptions) { + const sql = super.bulkDeleteQuery(tableOrModel, options); + + return `${sql}; SELECT @@ROWCOUNT AS AFFECTEDROWS;`; + } +} diff --git a/packages/mssql/src/query-generator.d.ts b/packages/mssql/src/query-generator.d.ts new file mode 100644 index 000000000000..a6e57cbbc34b --- /dev/null +++ b/packages/mssql/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { MsSqlQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class MsSqlQueryGenerator extends MsSqlQueryGeneratorTypeScript {} diff --git a/packages/mssql/src/query-generator.internal.ts b/packages/mssql/src/query-generator.internal.ts new file mode 100644 index 000000000000..67e8a7188158 --- /dev/null +++ b/packages/mssql/src/query-generator.internal.ts @@ -0,0 +1,48 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import type { MsSqlDialect } from './dialect.js'; + +const TECHNICAL_DATABASE_NAMES = Object.freeze(['master', 'model', 'msdb', 'tempdb']); + +const TECHNICAL_SCHEMA_NAMES = Object.freeze([ + 'db_accessadmin', + 'db_backupoperator', + 'db_datareader', + 'db_datawriter', + 'db_ddladmin', + 'db_denydatareader', + 'db_denydatawriter', + 'db_owner', + 'db_securityadmin', + 'INFORMATION_SCHEMA', + 'sys', +]); + +export class MsSqlQueryGeneratorInternal< + Dialect extends MsSqlDialect = MsSqlDialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalDatabaseNames() { + return TECHNICAL_DATABASE_NAMES; + } + + getTechnicalSchemaNames() { + return TECHNICAL_SCHEMA_NAMES; + } + + addLimitAndOffset(options: AddLimitOffsetOptions) { + let fragment = ''; + if (options.offset || options.limit) { + fragment += ` OFFSET ${this.queryGenerator.escape(options.offset || 0, options)} ROWS`; + } + + if (options.limit != null) { + if (options.limit === 0) { + throw new Error(`LIMIT 0 is not supported by ${this.dialect.name} dialect.`); + } + + fragment += ` FETCH NEXT ${this.queryGenerator.escape(options.limit, options)} ROWS ONLY`; + } + + return fragment; + } +} diff --git a/packages/mssql/src/query-generator.js b/packages/mssql/src/query-generator.js new file mode 100644 index 000000000000..0f21b2a07202 --- /dev/null +++ b/packages/mssql/src/query-generator.js @@ -0,0 +1,602 @@ +'use strict'; + +import { DataTypes, Op } from '@sequelize/core'; +import { + attributeTypeToSql, + normalizeDataType, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import { + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import each from 'lodash/each'; +import forOwn from 'lodash/forOwn'; +import isPlainObject from 'lodash/isPlainObject'; +import isString from 'lodash/isString'; +import { MsSqlQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +/* istanbul ignore next */ +function throwMethodUndefined(methodName) { + throw new Error(`The method "${methodName}" is not defined! Please add it to your sql dialect.`); +} + +const CREATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set(['uniqueKeys']); + +export class MsSqlQueryGenerator extends MsSqlQueryGeneratorTypeScript { + createTableQuery(tableName, attributes, options) { + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + const primaryKeys = []; + const foreignKeys = {}; + const attributesClauseParts = []; + + let commentStr = ''; + + for (const attr in attributes) { + if (Object.hasOwn(attributes, attr)) { + let dataType = attributes[attr]; + let match; + + if (dataType.includes('COMMENT ')) { + const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/); + const commentText = commentMatch[2].replace('COMMENT', '').trim(); + commentStr += this.commentTemplate(commentText, tableName, attr); + // remove comment related substring from dataType + dataType = commentMatch[1]; + } + + if (dataType.includes('PRIMARY KEY')) { + primaryKeys.push(attr); + + if (dataType.includes('REFERENCES')) { + // MSSQL doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attributesClauseParts.push( + `${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`, + ); + foreignKeys[attr] = match[2]; + } else { + 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.*)$/); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + foreignKeys[attr] = match[2]; + } else { + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + } + + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + if (options?.uniqueKeys) { + each(options.uniqueKeys, (columns, indexName) => { + if (typeof indexName !== 'string') { + indexName = generateIndexName(tableName, columns); + } + + attributesClauseParts.push( + `CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields + .map(field => this.quoteIdentifier(field)) + .join(', ')})`, + ); + }); + } + + if (pkString.length > 0) { + attributesClauseParts.push(`PRIMARY KEY (${pkString})`); + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + attributesClauseParts.push( + `FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`, + ); + } + } + + const quotedTableName = this.quoteTable(tableName); + + return joinSQLFragments([ + `IF OBJECT_ID(${this.escape(quotedTableName)}, 'U') IS NULL`, + `CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`, + ';', + commentStr, + ]); + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + dataType = { + ...dataType, + // TODO: attributeToSQL SHOULD be using attributes in addColumnQuery + // but instead we need to pass the key along as the field here + field: key, + type: normalizeDataType(dataType.type, this.dialect), + }; + + let commentStr = ''; + + if (dataType.comment && isString(dataType.comment)) { + commentStr = this.commentTemplate(dataType.comment, table, key); + // attributeToSQL will try to include `COMMENT 'Comment Text'` when it returns if the comment key + // is present. This is needed for createTable statement where that part is extracted with regex. + // Here we can intercept the object and remove comment property since we have the original object. + delete dataType.comment; + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { context: 'addColumn' }), + ';', + commentStr, + ]); + } + + commentTemplate(comment, table, column) { + const tableDetails = this.extractTableDetails(table); + const tableName = tableDetails.tableName; + const tableSchema = tableDetails.schema; + + return ` EXEC sp_addextendedproperty @name = N'MS_Description', @value = ${this.escape(comment)}, @level0type = N'Schema', @level0name = ${this.escape(tableSchema)}, @level1type = N'Table', @level1name = ${this.quoteIdentifier(tableName)}, @level2type = N'Column', @level2name = ${this.quoteIdentifier(column)};`; + } + + changeColumnQuery(tableName, attributes) { + const attrString = []; + const constraintString = []; + let commentString = ''; + + for (const attributeName in attributes) { + const quotedAttrName = this.quoteIdentifier(attributeName); + let definition = attributes[attributeName]; + if (definition.includes('COMMENT ')) { + const commentMatch = definition.match(/^(.+) (COMMENT.*)$/); + const commentText = commentMatch[2].replace('COMMENT', '').trim(); + commentString += this.commentTemplate(commentText, tableName, attributeName); + // remove comment related substring from dataType + definition = commentMatch[1]; + } + + if (definition.includes('REFERENCES')) { + constraintString.push( + `FOREIGN KEY (${quotedAttrName}) ${definition.replace(/.+?(?=REFERENCES)/, '')}`, + ); + } else { + attrString.push(`${quotedAttrName} ${definition}`); + } + } + + return 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 joinSQLFragments([ + 'EXEC sp_rename', + `'${this.quoteTable(tableName)}.${attrBefore}',`, + `'${newName}',`, + "'COLUMN'", + ';', + ]); + } + + bulkInsertQuery(tableName, attrValueHashes, options, attributes) { + const quotedTable = this.quoteTable(tableName); + options ||= {}; + attributes ||= {}; + + const tuples = []; + const allAttributes = []; + const allQueries = []; + + let needIdentityInsertWrapper = false; + let outputFragment = ''; + + if (options.returning) { + const returnValues = this.generateReturnValues(attributes, options); + + outputFragment = returnValues.outputFragment; + } + + const emptyQuery = `INSERT INTO ${quotedTable}${outputFragment} DEFAULT VALUES`; + + for (const attrValueHash of attrValueHashes) { + // special case for empty objects with primary keys + const fields = Object.keys(attrValueHash); + const firstAttr = attributes[fields[0]]; + if ( + fields.length === 1 && + firstAttr && + firstAttr.autoIncrement && + attrValueHash[fields[0]] === null + ) { + allQueries.push(emptyQuery); + continue; + } + + // normal case + forOwn(attrValueHash, (value, key) => { + if (value !== null && attributes[key] && attributes[key].autoIncrement) { + needIdentityInsertWrapper = true; + } + + if (!allAttributes.includes(key)) { + if (value === null && attributes[key] && attributes[key].autoIncrement) { + return; + } + + allAttributes.push(key); + } + }); + } + + if (allAttributes.length > 0) { + for (const attrValueHash of attrValueHashes) { + tuples.push( + `(${allAttributes + .map(key => { + // TODO: bindParam + // TODO: pass "model" + return this.escape(attrValueHash[key] ?? null, { + type: attributes[key]?.type, + replacements: options.replacements, + }); + }) + .join(',')})`, + ); + } + + const quotedAttributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(','); + allQueries.push( + tupleStr => + `INSERT INTO ${quotedTable} (${quotedAttributes})${outputFragment} VALUES ${tupleStr}`, + ); + } + + const commands = []; + let offset = 0; + while (offset < Math.max(tuples.length, 1)) { + // SQL Server can insert a maximum of 1000 rows at a time, + // This splits the insert in multiple statements to respect that limit + const tupleStr = tuples.slice(offset, Math.min(tuples.length, offset + 1000)); + let generatedQuery = allQueries.map(v => (typeof v === 'string' ? v : v(tupleStr))).join(';'); + if (needIdentityInsertWrapper) { + generatedQuery = `SET IDENTITY_INSERT ${quotedTable} ON; ${generatedQuery}; SET IDENTITY_INSERT ${quotedTable} OFF`; + } + + commands.push(generatedQuery); + offset += 1000; + } + + return `${commands.join(';')};`; + } + + updateQuery(tableName, attrValueHash, where, options = {}, attributes) { + const sql = super.updateQuery(tableName, attrValueHash, where, options, attributes); + + if (options.limit) { + const updateArgs = `UPDATE TOP(${this.escape(options.limit, undefined, options)})`; + sql.query = sql.query.replace('UPDATE', updateArgs); + } + + return sql; + } + + upsertQuery(tableName, insertValues, updateValues, where, model, options) { + // TODO: support TableNameWithSchema objects + const targetTableAlias = this.quoteTable(`${tableName}_target`); + const sourceTableAlias = this.quoteTable(`${tableName}_source`); + const primaryKeysColumns = []; + const identityColumns = []; + const uniqueColumns = []; + const tableNameQuoted = this.quoteTable(tableName); + let needIdentityInsertWrapper = false; + + const modelDefinition = model.modelDefinition; + // Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed + for (const attribute of modelDefinition.attributes.values()) { + if (attribute.primaryKey) { + primaryKeysColumns.push(attribute.columnName); + } + + if (attribute.autoIncrement) { + identityColumns.push(attribute.columnName); + } + } + + // Add unique indexes defined by indexes option to uniqueAttrs + for (const index of model.getIndexes()) { + if (index.unique && index.fields) { + for (const field of index.fields) { + const columnName = typeof field === 'string' ? field : field.name || field.attribute; + // TODO: columnName can't be used to get an attribute from modelDefinition.attributes, this is a bug + if (!uniqueColumns.includes(columnName) && modelDefinition.attributes.has(columnName)) { + uniqueColumns.push(columnName); + } + } + } + } + + const updateKeys = Object.keys(updateValues); + const insertKeys = Object.keys(insertValues); + const insertKeysQuoted = insertKeys.map(key => this.quoteIdentifier(key)).join(', '); + const insertValuesEscaped = insertKeys + .map(key => { + // TODO: pass "model", "type" and "bindParam" options + return this.escape(insertValues[key], options); + }) + .join(', '); + const sourceTableQuery = `VALUES(${insertValuesEscaped})`; // Virtual Table + let joinCondition; + + // IDENTITY_INSERT Condition + for (const key of identityColumns) { + if (insertValues[key] && insertValues[key] !== null) { + needIdentityInsertWrapper = true; + /* + * IDENTITY_INSERT Column Cannot be updated, only inserted + * http://stackoverflow.com/a/30176254/2254360 + */ + } + } + + // Filter NULL Clauses + const clauses = where[Op.or].filter(clause => { + let valid = true; + /* + * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row + */ + for (const key of Object.keys(clause)) { + if (clause[key] == null) { + valid = false; + break; + } + } + + return valid; + }); + + /* + * Generate ON condition using PK(s). + * If not, generate using UK(s). Else throw error + */ + const getJoinSnippet = array => { + return array.map(key => { + key = this.quoteIdentifier(key); + + return `${targetTableAlias}.${key} = ${sourceTableAlias}.${key}`; + }); + }; + + if (clauses.length === 0) { + throw new Error('Primary Key or Unique key should be passed to upsert query'); + } else { + // Search for primary key attribute in clauses -- Model can have two separate unique keys + for (const key in clauses) { + const keys = Object.keys(clauses[key]); + const columnName = modelDefinition.getColumnNameLoose(keys[0]); + + if (primaryKeysColumns.includes(columnName)) { + joinCondition = getJoinSnippet(primaryKeysColumns).join(' AND '); + break; + } + } + + if (!joinCondition) { + joinCondition = getJoinSnippet(uniqueColumns).join(' AND '); + } + } + + // Remove the IDENTITY_INSERT Column from update + const filteredUpdateClauses = updateKeys + .filter(key => !identityColumns.includes(key)) + .map(key => { + const value = this.escape(updateValues[key], undefined, options); + key = this.quoteIdentifier(key); + + return `${targetTableAlias}.${key} = ${value}`; + }); + const updateSnippet = + filteredUpdateClauses.length > 0 + ? `WHEN MATCHED THEN UPDATE SET ${filteredUpdateClauses.join(', ')}` + : ''; + + const insertSnippet = `(${insertKeysQuoted}) VALUES(${insertValuesEscaped})`; + + let query = `MERGE INTO ${tableNameQuoted} WITH(HOLDLOCK) AS ${targetTableAlias} USING (${sourceTableQuery}) AS ${sourceTableAlias}(${insertKeysQuoted}) ON ${joinCondition}`; + query += ` ${updateSnippet} WHEN NOT MATCHED THEN INSERT ${insertSnippet} OUTPUT $action, INSERTED.*;`; + if (needIdentityInsertWrapper) { + query = `SET IDENTITY_INSERT ${tableNameQuoted} ON; ${query} SET IDENTITY_INSERT ${tableNameQuoted} OFF;`; + } + + return query; + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + // handle self-referential constraints + if ( + attribute.references && + attribute.Model && + this.isSameTable(attribute.Model.tableName, attribute.references.table) + ) { + this.sequelize.log( + 'MSSQL does not support self-referential constraints, ' + + 'we will remove it but we recommend restructuring your query', + ); + attribute.onDelete = ''; + attribute.onUpdate = ''; + } + + let template; + + if (attribute.type instanceof DataTypes.ENUM) { + // enums are a special case + template = attribute.type.toSql({ dialect: this.dialect }); + template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.type.options.values + .map(value => { + return this.escape(value, options); + }) + .join(', ')}))`; + + return template; + } + + template = attributeTypeToSql(attribute.type, { dialect: this.dialect }); + + if (attribute.allowNull === false) { + template += ' NOT NULL'; + } else if ( + !attribute.primaryKey && + !defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + template += ' NULL'; + } + + if (attribute.autoIncrement) { + template += ' IDENTITY(1,1)'; + } + + // Blobs/texts cannot have a defaultValue + if ( + attribute.type !== 'TEXT' && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + template += ` DEFAULT ${this.escape(attribute.defaultValue, { ...options, type: attribute.type })}`; + } + + if ( + attribute.unique === true && + (options?.context !== 'changeColumn' || this.dialect.supports.alterColumn.unique) + ) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + if ((!options || !options.withoutForeignKeyConstraints) && attribute.references) { + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + if (attribute.comment && typeof attribute.comment === 'string') { + template += ` COMMENT ${attribute.comment}`; + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = Object.create(null); + const existingConstraints = []; + + for (const key of Object.keys(attributes)) { + const attribute = { ...attributes[key] }; + + if (attribute.references) { + if (existingConstraints.includes(this.quoteTable(attribute.references.table))) { + // no cascading constraints to a table more than once + attribute.onDelete = ''; + attribute.onUpdate = ''; + } else { + existingConstraints.push(this.quoteTable(attribute.references.table)); + + // NOTE: this really just disables cascading updates for all + // definitions. Can be made more robust to support the + // few cases where MSSQL actually supports them + attribute.onUpdate = ''; + } + } + + if (key && !attribute.field) { + attribute.field = key; + } + + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } + + createTrigger() { + throwMethodUndefined('createTrigger'); + } + + dropTrigger() { + throwMethodUndefined('dropTrigger'); + } + + renameTrigger() { + throwMethodUndefined('renameTrigger'); + } + + createFunction() { + throwMethodUndefined('createFunction'); + } + + dropFunction() { + throwMethodUndefined('dropFunction'); + } + + renameFunction() { + throwMethodUndefined('renameFunction'); + } +} diff --git a/packages/mssql/src/query-interface-typescript.internal.ts b/packages/mssql/src/query-interface-typescript.internal.ts new file mode 100644 index 000000000000..573610afafa6 --- /dev/null +++ b/packages/mssql/src/query-interface-typescript.internal.ts @@ -0,0 +1,129 @@ +import type { + CommitTransactionOptions, + CreateSavepointOptions, + RollbackSavepointOptions, + RollbackTransactionOptions, + StartTransactionOptions, +} from '@sequelize/core'; +import { AbstractQueryInterface, Transaction } from '@sequelize/core'; +import { START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { ASYNC_QUEUE } from './_internal/symbols.js'; +import type { MsSqlConnection } from './connection-manager.js'; +import type { MsSqlDialect } from './dialect.js'; +import { MsSqlQueryInterfaceInternal } from './query-interface.internal.js'; + +export class MsSqlQueryInterfaceTypescript< + Dialect extends MsSqlDialect = MsSqlDialect, +> extends AbstractQueryInterface { + readonly #internalQueryInterface: MsSqlQueryInterfaceInternal; + + constructor(dialect: Dialect, internalQueryInterface?: MsSqlQueryInterfaceInternal) { + internalQueryInterface ??= new MsSqlQueryInterfaceInternal(dialect); + + super(dialect, internalQueryInterface); + this.#internalQueryInterface = internalQueryInterface; + } + + async _commitTransaction( + transaction: Transaction, + _options: CommitTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to commit a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as MsSqlConnection; + await connection[ASYNC_QUEUE].enqueue( + async () => + new Promise((resolve, reject) => { + connection.commitTransaction(error => (error ? reject(error) : resolve())); + }), + ); + } + + async _createSavepoint(transaction: Transaction, options: CreateSavepointOptions): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to create a savepoint without the transaction object.'); + } + + const connection = transaction.getConnection() as MsSqlConnection; + await connection[ASYNC_QUEUE].enqueue( + async () => + new Promise((resolve, reject) => { + connection.saveTransaction( + error => (error ? reject(error) : resolve()), + options.savepointName, + ); + }), + ); + } + + async _rollbackSavepoint( + transaction: Transaction, + options: RollbackSavepointOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a savepoint without the transaction object.'); + } + + const connection = transaction.getConnection() as MsSqlConnection; + await connection[ASYNC_QUEUE].enqueue( + async () => + new Promise((resolve, reject) => { + connection.rollbackTransaction( + error => (error ? reject(error) : resolve()), + options.savepointName, + ); + }), + ); + } + + async _rollbackTransaction( + transaction: Transaction, + _options: RollbackTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a transaction without the transaction object.'); + } + + const connection = transaction.getConnection() as MsSqlConnection; + await connection[ASYNC_QUEUE].enqueue( + async () => + new Promise((resolve, reject) => { + connection.rollbackTransaction(error => (error ? reject(error) : resolve())); + }), + ); + } + + async _startTransaction( + transaction: Transaction, + options: StartTransactionOptions, + ): Promise { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to start a transaction without the transaction object.'); + } + + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.sequelize.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.sequelize.dialect.supports.startTransaction, + options, + ); + } + + const connection = transaction.getConnection() as MsSqlConnection; + await connection[ASYNC_QUEUE].enqueue( + async () => + new Promise((resolve, reject) => { + connection.beginTransaction( + error => (error ? reject(error) : resolve()), + options.transactionName, + this.#internalQueryInterface.parseIsolationLevel(options.isolationLevel), + ); + }), + ); + } +} diff --git a/packages/mssql/src/query-interface.d.ts b/packages/mssql/src/query-interface.d.ts new file mode 100644 index 000000000000..de1d7ed1fc1c --- /dev/null +++ b/packages/mssql/src/query-interface.d.ts @@ -0,0 +1,6 @@ +import type { MsSqlDialect } from './dialect.js'; +import { MsSqlQueryInterfaceTypescript } from './query-interface-typescript.internal.js'; + +export class MsSqlQueryInterface< + Dialect extends MsSqlDialect = MsSqlDialect, +> extends MsSqlQueryInterfaceTypescript {} diff --git a/packages/mssql/src/query-interface.internal.ts b/packages/mssql/src/query-interface.internal.ts new file mode 100644 index 000000000000..8c0580b77ff3 --- /dev/null +++ b/packages/mssql/src/query-interface.internal.ts @@ -0,0 +1,35 @@ +import { IsolationLevel } from '@sequelize/core'; +import { AbstractQueryInterfaceInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-interface-internal.js'; +import type { MsSqlDialect } from './dialect.js'; + +export class MsSqlQueryInterfaceInternal extends AbstractQueryInterfaceInternal { + constructor(readonly dialect: MsSqlDialect) { + super(dialect); + } + + /** + * Parses the isolation level and returns the corresponding value for tedious. + * + * @see https://github.com/tediousjs/tedious/blob/master/src/transaction.ts + * + * @param value The isolation level to parse. + */ + parseIsolationLevel(value?: IsolationLevel | null | undefined): number { + if (value == null) { + return 0; + } + + switch (value) { + case IsolationLevel.READ_UNCOMMITTED: + return 1; + case IsolationLevel.READ_COMMITTED: + return 2; + case IsolationLevel.REPEATABLE_READ: + return 3; + case IsolationLevel.SERIALIZABLE: + return 4; + default: + throw new Error(`Unknown isolation level: ${value}`); + } + } +} diff --git a/packages/mssql/src/query-interface.js b/packages/mssql/src/query-interface.js new file mode 100644 index 000000000000..420f7577d1c8 --- /dev/null +++ b/packages/mssql/src/query-interface.js @@ -0,0 +1,100 @@ +'use strict'; + +import { Op, QueryTypes } from '@sequelize/core'; +import { isWhereEmpty } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { assertNoReservedBind } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import intersection from 'lodash/intersection'; +import { MsSqlQueryInterfaceTypescript } from './query-interface-typescript.internal.js'; + +/** + * The interface that Sequelize uses to talk with MSSQL database + */ +export class MsSqlQueryInterface extends MsSqlQueryInterfaceTypescript { + /** + * A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. + * + * @override + */ + async removeColumn(tableName, columnName, options) { + const allConstraints = await this.showConstraints(tableName, { ...options, columnName }); + const constraints = allConstraints.filter(constraint => + ['DEFAULT', 'FOREIGN KEY', 'PRIMARY KEY'].includes(constraint.constraintType), + ); + await Promise.all( + constraints.map(constraint => + this.removeConstraint(tableName, constraint.constraintName, options), + ), + ); + + await super.removeColumn(tableName, columnName, options); + } + + /** + * @override + */ + async bulkInsert(tableName, records, options, attributes) { + // If more than 1,000 rows are inserted outside of a transaction, we can't guarantee safe rollbacks. + // See https://github.com/sequelize/sequelize/issues/15426 + if (records.length > 1000 && !options.transaction) { + throw new Error( + `MSSQL doesn't allow for inserting more than 1,000 rows at a time, so Sequelize executes the insert as multiple queries. Please run this in a transaction to ensure safe rollbacks`, + ); + } + + return super.bulkInsert(tableName, records, options, attributes); + } + + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + if (options.bind) { + assertNoReservedBind(options.bind); + } + + const model = options.model; + const wheres = []; + + options = { ...options }; + + if (!isWhereEmpty(where)) { + wheres.push(where); + } + + // Lets combine unique keys and indexes into one + const uniqueColumnNames = Object.values(model.getIndexes()) + .filter(c => c.unique && c.fields.length > 0) + .map(c => c.fields); + + const attributes = Object.keys(insertValues); + for (const index of uniqueColumnNames) { + 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, + ); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw, and queryRaw will throw if we use the option + delete options.replacements; + + return await this.sequelize.queryRaw(sql, options); + } +} diff --git a/packages/mssql/src/query.d.ts b/packages/mssql/src/query.d.ts new file mode 100644 index 000000000000..1ffc3a7a5221 --- /dev/null +++ b/packages/mssql/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class MsSqlQuery extends AbstractQuery {} diff --git a/packages/mssql/src/query.js b/packages/mssql/src/query.js new file mode 100644 index 000000000000..0c1f75ff65a8 --- /dev/null +++ b/packages/mssql/src/query.js @@ -0,0 +1,438 @@ +'use strict'; + +import { + AbstractQuery, + AggregateError, + DatabaseError, + EmptyResultError, + ForeignKeyConstraintError, + UniqueConstraintError, + UnknownConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import forOwn from 'lodash/forOwn'; +import zipObject from 'lodash/zipObject'; +import { Request, TYPES } from 'tedious'; +import { ASYNC_QUEUE } from './_internal/symbols.js'; + +const debug = logger.debugContext('sql:mssql'); + +const minSafeIntegerAsBigInt = BigInt(Number.MIN_SAFE_INTEGER); +const maxSafeIntegerAsBigInt = BigInt(Number.MAX_SAFE_INTEGER); + +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); +} + +export class MsSqlQuery extends AbstractQuery { + getInsertIdField() { + return 'id'; + } + + getSQLTypeFromJsType(value, TYPES) { + const paramType = { type: TYPES.NVarChar, typeOptions: {}, value }; + if (typeof value === 'number') { + if (Number.isInteger(value)) { + if (value >= -2_147_483_648 && value <= 2_147_483_647) { + paramType.type = TYPES.Int; + } else { + paramType.type = TYPES.BigInt; + } + } else { + paramType.type = TYPES.Numeric; + // Default to a reasonable numeric precision/scale pending more sophisticated logic + paramType.typeOptions = { precision: 30, scale: getScale(value) }; + } + } else if (typeof value === 'bigint') { + if (value < minSafeIntegerAsBigInt || value > maxSafeIntegerAsBigInt) { + paramType.type = TYPES.VarChar; + paramType.value = value.toString(); + } else { + return this.getSQLTypeFromJsType(Number(value), TYPES); + } + } else if (typeof value === 'boolean') { + paramType.type = TYPES.Bit; + } + + if (Buffer.isBuffer(value)) { + paramType.type = TYPES.VarBinary; + } + + return paramType; + } + + async _run(connection, sql, parameters) { + this.sql = sql; + + const complete = this._logQuery(sql, debug, parameters); + + const query = new Promise((resolve, reject) => { + const rows = []; + const request = new Request(sql, (err, rowCount) => { + err ? reject(err) : resolve([rows, rowCount]); + }); + + if (parameters) { + if (Array.isArray(parameters)) { + // eslint-disable-next-line unicorn/no-for-loop + for (let i = 0; i < parameters.length; i++) { + const paramType = this.getSQLTypeFromJsType(parameters[i], TYPES); + request.addParameter( + String(i + 1), + paramType.type, + paramType.value, + paramType.typeOptions, + ); + } + } else { + forOwn(parameters, (parameter, parameterName) => { + const paramType = this.getSQLTypeFromJsType(parameter, TYPES); + request.addParameter( + parameterName, + paramType.type, + paramType.value, + paramType.typeOptions, + ); + }); + } + } + + request.on('row', columns => { + rows.push(columns); + }); + + connection.execSql(request); + }); + + let rows; + let rowCount; + + try { + [rows, rowCount] = await query; + } catch (error) { + error.sql = sql; + error.parameters = parameters; + + throw this.formatError(error); + } + + complete(); + + if (Array.isArray(rows)) { + const dialect = this.sequelize.dialect; + rows = rows.map(columns => { + const row = {}; + for (const column of columns) { + const parser = dialect.getParserForDatabaseDataType(column.metadata.type.type); + let value = column.value; + + if (value != null && parser) { + value = parser(value); + } + + row[column.metadata.colName] = value; + } + + return row; + }); + } + + return this.formatResults(rows, rowCount); + } + + run(sql, parameters) { + return this.connection[ASYNC_QUEUE].enqueue(() => this._run(this.connection, sql, parameters)); + } + + /** + * High level function that handles the results of a query execution. + * + * @param {Array} data - The result of the query execution. + * @param {number} rowCount + * @private + * @example + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + */ + formatResults(data, rowCount) { + 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() && !this.isUpsertQuery() && data.length === 0) { + throw new EmptyResultError(); + } + + // if this was an upsert and no data came back, that means the record exists, but the update was a noop. + // return the current instance and mark it as an "not an insert". + if (this.isUpsertQuery() && data.length === 0) { + return [this.instance || data, false]; + } + + if (Array.isArray(data) && data[0]) { + for (const attributeOrColumnName of Object.keys(data[0])) { + const modelDefinition = this.model.modelDefinition; + const attribute = modelDefinition.columns.get(attributeOrColumnName); + const updatedValue = this._parseDatabaseValue( + data[0][attributeOrColumnName], + attribute?.type, + ); + + this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, { + raw: true, + comesFromDatabase: true, + }); + } + } + } + + if (this.isUpsertQuery()) { + return [this.instance || data, data[0].$action === 'INSERT']; + } + + return [ + this.instance || (data && ((this.options.plain && data[0]) || data)) || undefined, + rowCount, + ]; + } + + if (this.isDescribeQuery()) { + const result = {}; + for (const _result of data) { + if (_result.Default) { + _result.Default = _result.Default.replace("('", '').replace("')", '').replaceAll("'", ''); + } + + result[_result.Name] = { + type: _result.Type.toUpperCase(), + allowNull: _result.IsNull === 'YES', + defaultValue: _result.Default, + primaryKey: _result.Constraint === 'PRIMARY KEY', + autoIncrement: _result.IsIdentity === 1, + comment: _result.Comment, + }; + + if (result[_result.Name].type.includes('CHAR') && _result.Length) { + if (_result.Length === -1) { + result[_result.Name].type += '(MAX)'; + } else { + result[_result.Name].type += `(${_result.Length})`; + } + } + } + + return result; + } + + if (this.isSelectQuery()) { + return this.handleSelectQuery(data); + } + + if (this.isShowIndexesQuery()) { + return this.handleShowIndexesQuery(data); + } + + if (this.isCallQuery()) { + return data[0]; + } + + if (this.isBulkUpdateQuery()) { + return this.options.returning ? this.handleSelectQuery(data) : rowCount; + } + + if (this.isDeleteQuery()) { + return data[0] ? data[0].AFFECTEDROWS : 0; + } + + if (this.isShowConstraintsQuery()) { + return data; + } + + if (this.isRawQuery()) { + return [data, rowCount]; + } + + return data; + } + + formatError(err) { + let match; + + match = err.message.match( + /Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'\. Cannot insert duplicate key in object '.*'\.(:? The duplicate key value is \((.*)\).)?/s, + ); + match ||= err.message.match( + /Cannot insert duplicate key row in object .* with unique index '(.*)'\.(:? The duplicate key value is \((.*)\).)?/s, + ); + if (match && match.length > 1) { + let fields = {}; + const uniqueKey = + this.model && + this.model.getIndexes().find(index => index.unique && index.name === match[1]); + + let message = 'Validation error'; + + if (uniqueKey && Boolean(uniqueKey.msg)) { + message = uniqueKey.msg; + } + + if (match[3]) { + const values = match[3].split(',').map(part => part.trim()); + if (uniqueKey) { + fields = zipObject(uniqueKey.fields, values); + } else { + fields[match[1]] = match[3]; + } + } + + const errors = []; + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + const uniqueConstraintError = new UniqueConstraintError({ + message, + errors, + cause: err, + fields, + }); + if (err.errors?.length > 0) { + return new AggregateError([...err.errors, uniqueConstraintError]); + } + + return uniqueConstraintError; + } + + match = err.message.match( + /The (?:DELETE|INSERT|MERGE|UPDATE) statement conflicted with the (?:FOREIGN KEY|REFERENCE) constraint "(.*)"\. The conflict occurred in database "(.*)", table "(.*)", column '(.*)'\./, + ); + if (match && match.length > 1) { + const fkConstraintError = new ForeignKeyConstraintError({ + index: match[1], + cause: err, + table: match[3], + fields: [match[4]], + }); + + if (err.errors?.length > 0) { + return new AggregateError([...err.errors, fkConstraintError]); + } + + return fkConstraintError; + } + + if (err.errors?.length > 0) { + let firstError; + for (const [index, error] of err.errors.entries()) { + match = error.message.match( + /Could not (?:create|drop) constraint(?: or index)?\. See previous errors\./, + ); + if (match && match.length > 0) { + let constraint = err.sql.match(/(?:constraint|index) \[(.+?)]/i); + constraint = constraint ? constraint[1] : undefined; + let table = err.sql.match(/table \[(.+?)]/i); + table = table ? table[1] : undefined; + + firstError = new UnknownConstraintError({ + message: err.errors[index - 1].message, + constraint, + table, + cause: err, + }); + } + } + + if (firstError) { + return new AggregateError([...err.errors, firstError]); + } + + return new AggregateError(err.errors); + } + + return new DatabaseError(err); + } + + isShowOrDescribeQuery() { + let result = false; + + result ||= this.sql + .toLowerCase() + .startsWith( + "select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'", + ); + result ||= this.sql.toLowerCase().startsWith('select tablename = t.name, name = ind.name,'); + result ||= this.sql.toLowerCase().startsWith('exec sys.sp_helpindex @objname'); + + return result; + } + + handleShowIndexesQuery(data) { + // Group by index name, and collect all fields + const indexes = data.reduce((acc, curr) => { + if (acc.has(curr.index_name)) { + const index = acc.get(curr.index_name); + if (curr.is_included_column) { + index.includes.push(curr.column_name); + } else { + index.fields.push({ + attribute: curr.column_name, + length: undefined, + order: curr.is_descending_key ? 'DESC' : 'ASC', + collate: undefined, + }); + } + + return acc; + } + + acc.set(curr.index_name, { + primary: curr.is_primary_key, + fields: curr.is_included_column + ? [] + : [ + { + attribute: curr.column_name, + length: undefined, + order: curr.is_descending_key ? 'DESC' : 'ASC', + collate: undefined, + }, + ], + includes: curr.is_included_column ? [curr.column_name] : [], + name: curr.index_name, + tableName: undefined, + unique: curr.is_unique, + type: null, + }); + + return acc; + }, new Map()); + + return Array.from(indexes.values()); + } +} diff --git a/packages/mssql/tsconfig.json b/packages/mssql/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/mssql/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/mssql/typedoc.json b/packages/mssql/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/mssql/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/mysql/.eslintrc.js b/packages/mysql/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/mysql/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/mysql/CHANGELOG.md b/packages/mysql/CHANGELOG.md new file mode 100644 index 000000000000..06f9e5572b46 --- /dev/null +++ b/packages/mysql/CHANGELOG.md @@ -0,0 +1,69 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +**Note:** Version bump only for package @sequelize/mysql + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/mysql + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/mysql + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/mysql + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- **mysql:** update mysql2 to ^3.11.2 ([#17498](https://github.com/sequelize/sequelize/issues/17498)) ([18ce1b0](https://github.com/sequelize/sequelize/commit/18ce1b0c5c0f237c17f91647217d769cb194da78)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/mysql + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +### Bug Fixes + +- set sequelize dialect type in query generator and interface ([#17285](https://github.com/sequelize/sequelize/issues/17285)) ([0227288](https://github.com/sequelize/sequelize/commit/0227288d1c6fcbf2d4f09e2efa50e4aeb9d435f2)) + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +- feat(mysql)!: move mysql to the `@sequelize/mysql` package (#17202) ([5c7830e](https://github.com/sequelize/sequelize/commit/5c7830e976900cca2b8c40535a0e895a66f2d8a6)), closes [#17202](https://github.com/sequelize/sequelize/issues/17202) + +### Features + +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` +- Instead of installing `mysql2`, users need to install `@sequelize/mysql`. diff --git a/packages/mysql/package.json b/packages/mysql/package.json new file mode 100644 index 000000000000..5390dc3297f8 --- /dev/null +++ b/packages/mysql/package.json @@ -0,0 +1,53 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "MySQL Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/mysql", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs mysql", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-unit": "mocha src/**/*.test.ts", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "mysql2": "^3.16.0", + "wkx": "^0.5.0" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "chai": "4.5.0", + "mocha": "11.7.5" + } +} diff --git a/packages/mysql/src/_internal/connection-options.ts b/packages/mysql/src/_internal/connection-options.ts new file mode 100644 index 000000000000..b4ce60c4a6b0 --- /dev/null +++ b/packages/mysql/src/_internal/connection-options.ts @@ -0,0 +1,76 @@ +import type { PickByType } from '@sequelize/utils'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import type { MySqlConnectionOptions } from '../connection-manager.js'; + +type AnyOptions = 'debug' | 'stream'; + +type StringConnectionOptions = PickByType, string>; + +const STRING_CONNECTION_OPTION_MAP: Record = { + charset: undefined, + database: undefined, + host: undefined, + localAddress: undefined, + password: undefined, + password1: undefined, + password2: undefined, + password3: undefined, + passwordSha1: undefined, + socketPath: undefined, + ssl: undefined, + user: undefined, +} as const; + +export const STRING_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + STRING_CONNECTION_OPTION_MAP, +); + +type BooleanConnectionOptions = PickByType, boolean>; + +const BOOLEAN_CONNECTION_OPTION_MAP = { + compress: undefined, + disableEval: undefined, + enableKeepAlive: undefined, + gracefulEnd: undefined, + insecureAuth: undefined, + isServer: undefined, + jsonStrings: undefined, + multipleStatements: undefined, + trace: undefined, + waitForConnections: undefined, +} as const satisfies Record; + +export const BOOLEAN_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + BOOLEAN_CONNECTION_OPTION_MAP, +); + +type NumberConnectionOptions = PickByType, number>; + +const NUMBER_CONNECTION_OPTION_MAP = { + port: undefined, + connectionLimit: undefined, + connectTimeout: undefined, + charsetNumber: undefined, + maxIdle: undefined, + queueLimit: undefined, + idleTimeout: undefined, + maxPreparedStatements: undefined, + keepAliveInitialDelay: undefined, +} as const satisfies Record; + +export const NUMBER_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + NUMBER_CONNECTION_OPTION_MAP, +); + +export const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + ...STRING_CONNECTION_OPTION_MAP, + ...BOOLEAN_CONNECTION_OPTION_MAP, + ...NUMBER_CONNECTION_OPTION_MAP, + infileStreamFactory: undefined, + flags: undefined, + authSwitchHandler: undefined, + connectAttributes: undefined, + authPlugins: undefined, + debug: undefined, + stream: undefined, +}); diff --git a/packages/mysql/src/_internal/data-types-db.ts b/packages/mysql/src/_internal/data-types-db.ts new file mode 100644 index 000000000000..0ff367084b9e --- /dev/null +++ b/packages/mysql/src/_internal/data-types-db.ts @@ -0,0 +1,67 @@ +import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import dayjs from 'dayjs'; +import type { TypeCastField } from 'mysql2'; +import wkx from 'wkx'; +import type { MySqlDialect } from '../dialect.js'; + +/** + * First pass of DB value parsing: Parses based on the MySQL Type ID. + * If a Sequelize DataType is specified, the value is then passed to {@link DataTypes.ABSTRACT#parseDatabaseValue}. + * + * @param dialect + */ +export function registerMySqlDbDataTypeParsers(dialect: MySqlDialect) { + /* + * @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 + */ + dialect.registerDataTypeParser(['DATETIME'], (value: TypeCastField) => { + const valueStr: string | null = value.string(); + if (valueStr === null) { + return null; + } + + const timeZone: string = dialect.sequelize.options.timezone; + if (timeZone === '+00:00') { + // default value + // mysql returns a UTC date string that looks like the following: + // 2022-01-01 00:00:00 + // The above does not specify a time zone offset, so Date.parse will try to parse it as a local time. + // Adding +00 fixes this. + return `${valueStr}+00`; + } + + if (isValidTimeZone(timeZone)) { + return dayjs.tz(valueStr, timeZone).toISOString(); + } + + // offset format, we can just append. + // "2022-09-22 20:03:06" with timeZone "-04:00" + // becomes "2022-09-22 20:03:06-04:00" + return valueStr + timeZone; + }); + + // dateonly + dialect.registerDataTypeParser(['DATE'], (value: TypeCastField) => { + return value.string(); + }); + + // bigint + dialect.registerDataTypeParser(['LONGLONG'], (value: TypeCastField) => { + return value.string(); + }); + + dialect.registerDataTypeParser(['GEOMETRY'], (value: TypeCastField) => { + let buffer = value.buffer(); + // Empty buffer, MySQL doesn't support POINT EMPTY + // check, https://dev.mysql.com/worklog/task/?id=2381 + if (!buffer || buffer.length === 0) { + return null; + } + + // For some reason, discard the first 4 bytes + buffer = buffer.subarray(4); + + return wkx.Geometry.parse(buffer).toGeoJSON({ shortCrs: true }); + }); +} diff --git a/packages/mysql/src/_internal/data-types-overrides.ts b/packages/mysql/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..27939561e156 --- /dev/null +++ b/packages/mysql/src/_internal/data-types-overrides.ts @@ -0,0 +1,178 @@ +import type { BindParamOptions, GeoJson } from '@sequelize/core'; +import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import { isString } from '@sequelize/utils'; +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; +import wkx from 'wkx'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +export class FLOAT extends BaseTypes.FLOAT { + protected getNumberSqlTypeName(): string { + return 'FLOAT'; + } + + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + protected getNumberSqlTypeName(): string { + return 'DOUBLE PRECISION'; + } + + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +/** @deprecated */ +export class REAL extends BaseTypes.REAL { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class DECIMAL extends BaseTypes.DECIMAL { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + protected _supportsNativeUnsigned(): boolean { + return true; + } +} + +export class BOOLEAN extends BaseTypes.BOOLEAN { + toSql() { + return 'TINYINT(1)'; + } + + escape(value: boolean | unknown): string { + // must be 'true' & 'false' when inlining so the values are compatible with the 'IS' operator + return value ? 'true' : 'false'; + } + + toBindableValue(value: boolean | unknown): unknown { + // when binding, must be an integer + return value ? 1 : 0; + } +} + +export class DATE extends BaseTypes.DATE { + toBindableValue(date: AcceptedDate) { + date = this._applyTimezone(date); + + // MySQL datetime precision defaults to 0 + const precision = this.options.precision ?? 0; + let format = 'YYYY-MM-DD HH:mm:ss'; + // TODO: We should normally use `S`, `SS` or `SSS` based on the precision, but + // dayjs has a bug which causes `S` and `SS` to be ignored: + // https://github.com/iamkun/dayjs/issues/1734 + if (precision > 0) { + format += `.SSS`; + } + + return date.format(format); + } + + sanitize(value: unknown, options?: { timezone?: string }): unknown { + if (isString(value) && options?.timezone) { + if (isValidTimeZone(options.timezone)) { + return dayjs.tz(value, options.timezone).toDate(); + } + + return new Date(`${value} ${options.timezone}`); + } + + return super.sanitize(value); + } +} + +export class JSON extends BaseTypes.JSON { + escape(value: any): string { + // In MySQL, JSON cannot be directly compared to a text, we need to cast it to JSON + // This is not necessary for the values of INSERT & UPDATE statements, so we could omit this + // if we add context to the escape & getBindParamSql methods + return `CAST(${super.escape(value)} AS JSON)`; + } + + getBindParamSql(value: any, options: BindParamOptions): string { + return `CAST(${super.getBindParamSql(value, options)} AS JSON)`; + } +} + +export class UUID extends BaseTypes.UUID { + // TODO: add check constraint to enforce GUID format + toSql() { + return 'CHAR(36) BINARY'; + } +} + +export class GEOMETRY extends BaseTypes.GEOMETRY { + toBindableValue(value: GeoJson) { + const srid = this.options.srid ? `, ${this.options.srid}` : ''; + + return `ST_GeomFromText(${this._getDialect().escapeString( + wkx.Geometry.parseGeoJSON(value).toWkt(), + )}${srid})`; + } + + getBindParamSql(value: GeoJson, options: BindParamOptions) { + const srid = this.options.srid ? `, ${options.bindParam(this.options.srid)}` : ''; + + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())}${srid})`; + } + + toSql() { + const sql = this.options.type?.toUpperCase() || 'GEOMETRY'; + + if (this.options.srid) { + // According to the documentation examples the format is: POINT NOT NULL SRID 4326 + // however in practise the order of NOT NULL and the SRID specification doesn't seem to matter. + // Using the /*!80003 ... */ syntax is for backwards compat with MySQL versions before 8.0: MySQL 5.7 doesn't support SRIDs on table columns. + return `${sql} /*!80003 SRID ${this.options.srid} */`; + } + + return sql; + } +} + +export class ENUM extends BaseTypes.ENUM { + toSql() { + const dialect = this._getDialect(); + + return `ENUM(${this.options.values.map(value => dialect.escapeString(value)).join(', ')})`; + } +} diff --git a/packages/mysql/src/connection-manager.ts b/packages/mysql/src/connection-manager.ts new file mode 100644 index 000000000000..dbc30999b72d --- /dev/null +++ b/packages/mysql/src/connection-manager.ts @@ -0,0 +1,212 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + AccessDeniedError, + ConnectionError, + ConnectionRefusedError, + HostNotFoundError, + HostNotReachableError, + InvalidConnectionError, +} from '@sequelize/core'; +import { timeZoneToOffsetString } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { isError } from '@sequelize/utils'; +import { isNodeError } from '@sequelize/utils/node'; +import * as MySql2 from 'mysql2'; +import assert from 'node:assert'; +import { promisify } from 'node:util'; +import type { MySqlDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:mysql'); + +export type MySql2Module = typeof MySql2; + +export interface MySqlConnection extends MySql2.Connection, AbstractConnection {} + +export interface MySqlConnectionOptions + extends Omit< + MySql2.ConnectionOptions, + // The user cannot modify these options: + // This option is currently a global Sequelize option + | 'timezone' + // Conflicts with our own features + | 'nestTables' + // We provide our own placeholders. + // TODO: should we use named placeholders for mysql? + | 'namedPlaceholders' + // We provide our own pool + | 'pool' + // Our code expects specific response formats, setting any of the following option would break Sequelize + | 'typeCast' + | 'bigNumberStrings' + | 'supportBigNumbers' + | 'dateStrings' + | 'decimalNumbers' + | 'rowsAsArray' + | 'stringifyObjects' + | 'queryFormat' + | 'Promise' + // We provide our own "url" implementation + | 'uri' + > {} + +/** + * MySQL Connection Manager + * + * Get connections, validate and disconnect them. + * AbstractConnectionManager pooling use it to handle MySQL specific connections + * Use https://github.com/sidorares/node-mysql2 to connect with MySQL server + */ +export class MySqlConnectionManager extends AbstractConnectionManager< + MySqlDialect, + MySqlConnection +> { + readonly #lib: MySql2Module; + + constructor(dialect: MySqlDialect) { + super(dialect); + this.#lib = this.dialect.options.mysql2Module ?? MySql2; + } + + #typecast(field: MySql2.TypeCastField, next: () => void): unknown { + const dataParser = this.dialect.getParserForDatabaseDataType(field.type); + if (dataParser) { + const value = dataParser(field); + + if (value !== undefined) { + return value; + } + } + + return next(); + } + + /** + * Connect with MySQL database based on config, Handle any errors in connection + * Set the pool handlers on connection.error + * Also set proper timezone once connection is connected. + * + * @param config + */ + async connect(config: ConnectionOptions): Promise { + assert(typeof config.port === 'number', 'port has not been normalized'); + + // TODO: enable dateStrings + const connectionConfig: MySql2.ConnectionOptions = { + flags: ['-FOUND_ROWS'], + port: 3306, + ...config, + ...(!this.sequelize.options.timezone ? null : { timezone: this.sequelize.options.timezone }), + bigNumberStrings: false, + supportBigNumbers: true, + typeCast: (field, next) => this.#typecast(field, next), + }; + + try { + const connection: MySqlConnection = await createConnection(this.#lib, connectionConfig); + + debug('connection acquired'); + + connection.on('error', (error: unknown) => { + if (!isNodeError(error)) { + return; + } + + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + case 'EPIPE': + case 'PROTOCOL_CONNECTION_LOST': + void this.sequelize.pool.destroy(connection); + break; + default: + } + }); + + if (!this.sequelize.options.keepDefaultTimezone && this.sequelize.options.timezone) { + // 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 = tzOffset.includes('/') ? timeZoneToOffsetString(tzOffset) : tzOffset; + await promisify(cb => connection.query(`SET time_zone = '${tzOffset}'`, cb))(); + } + + return connection; + } catch (error) { + if (!isError(error)) { + throw error; + } + + const code = isNodeError(error) ? error.code : null; + + switch (code) { + case 'ECONNREFUSED': + throw new ConnectionRefusedError(error); + case 'ER_ACCESS_DENIED_ERROR': + throw new AccessDeniedError(error); + case 'ENOTFOUND': + throw new HostNotFoundError(error); + case 'EHOSTUNREACH': + throw new HostNotReachableError(error); + case 'EINVAL': + throw new InvalidConnectionError(error); + default: + throw new ConnectionError(error); + } + } + } + + async disconnect(connection: MySqlConnection) { + // @ts-expect-error -- undeclared var + if (connection._closing) { + debug('connection tried to disconnect but was already at CLOSED state'); + + return; + } + + await promisify(callback => connection.end(callback))(); + } + + validate(connection: MySqlConnection) { + return ( + connection && + // @ts-expect-error -- undeclared var + !connection._fatalError && + // @ts-expect-error -- undeclared var + !connection._protocolError && + // @ts-expect-error -- undeclared var + !connection._closing && + // @ts-expect-error -- undeclared var + !connection.stream.destroyed + ); + } +} + +async function createConnection( + lib: typeof MySql2, + config: MySql2.ConnectionOptions, +): Promise { + return new Promise((resolve, reject) => { + const connection: MySqlConnection = lib.createConnection(config) as MySqlConnection; + + const errorHandler = (e: unknown) => { + // 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); + }); +} diff --git a/packages/mysql/src/dialect.test.ts b/packages/mysql/src/dialect.test.ts new file mode 100644 index 000000000000..3a858aad2597 --- /dev/null +++ b/packages/mysql/src/dialect.test.ts @@ -0,0 +1,23 @@ +import { Sequelize } from '@sequelize/core'; +import type { MySqlConnectionOptions } from '@sequelize/mysql'; +import { MySqlDialect } from '@sequelize/mysql'; +import { expect } from 'chai'; + +describe('MariaDbDialect#parseConnectionUrl', () => { + const dialect = new Sequelize({ dialect: MySqlDialect }).dialect; + + it('parses connection URL', () => { + const options: MySqlConnectionOptions = dialect.parseConnectionUrl( + 'mysql://user:password@localhost:1234/dbname?charset=utf8mb4', + ); + + expect(options).to.deep.eq({ + host: 'localhost', + port: 1234, + user: 'user', + password: 'password', + database: 'dbname', + charset: 'utf8mb4', + }); + }); +}); diff --git a/packages/mysql/src/dialect.ts b/packages/mysql/src/dialect.ts new file mode 100644 index 000000000000..6bdbe79c6043 --- /dev/null +++ b/packages/mysql/src/dialect.ts @@ -0,0 +1,174 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import type { SupportableNumericOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/dialect.js'; +import { parseCommonConnectionUrlOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/connection-options.js'; +import { + createUnspecifiedOrderedBindCollector, + escapeMysqlMariaDbString, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import { + BOOLEAN_CONNECTION_OPTION_NAMES, + CONNECTION_OPTION_NAMES, + NUMBER_CONNECTION_OPTION_NAMES, + STRING_CONNECTION_OPTION_NAMES, +} from './_internal/connection-options.js'; +import { registerMySqlDbDataTypeParsers } from './_internal/data-types-db.js'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { MySql2Module, MySqlConnectionOptions } from './connection-manager.js'; +import { MySqlConnectionManager } from './connection-manager.js'; +import { MySqlQueryGenerator } from './query-generator.js'; +import { MySqlQueryInterface } from './query-interface.js'; +import { MySqlQuery } from './query.js'; + +export interface MySqlDialectOptions { + /** + * The mysql2 library to use. + * If not provided, the mysql2 npm library will be used. + * Must be compatible with the mysql2 npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + mysql2Module?: MySql2Module; + + /** + * Show warnings if there are any when executing a query + */ + showWarnings?: boolean | undefined; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + mysql2Module: undefined, + showWarnings: undefined, +}); + +const numericOptions: SupportableNumericOptions = { + zerofill: true, + unsigned: true, +}; + +export class MySqlDialect extends AbstractDialect { + static supports = AbstractDialect.extendSupport({ + 'VALUES ()': true, + 'LIMIT ON UPDATE': true, + lock: true, + forShare: 'LOCK IN SHARE MODE', + settingIsolationLevelDuringTransaction: false, + schemas: true, + inserts: { + ignoreDuplicates: ' IGNORE', + updateOnDuplicate: ' ON DUPLICATE KEY UPDATE', + }, + index: { + collate: false, + length: true, + parser: true, + type: true, + using: 1, + }, + constraints: { + foreignKeyChecksDisableable: true, + }, + indexViaAlter: true, + indexHints: true, + dataTypes: { + COLLATE_BINARY: true, + GEOMETRY: true, + INTS: numericOptions, + FLOAT: { ...numericOptions, scaleAndPrecision: true }, + REAL: { ...numericOptions, scaleAndPrecision: true }, + DOUBLE: { ...numericOptions, scaleAndPrecision: true }, + DECIMAL: numericOptions, + JSON: true, + }, + jsonOperations: true, + jsonExtraction: { + unquoted: true, + quoted: true, + }, + REGEXP: true, + uuidV1Generation: true, + globalTimeZoneConfig: true, + maxExecutionTimeHint: { + select: true, + }, + createSchema: { + charset: true, + collate: true, + ifNotExists: true, + }, + dropSchema: { + ifExists: true, + }, + startTransaction: { + readOnly: true, + }, + }); + + readonly connectionManager: MySqlConnectionManager; + readonly queryGenerator: MySqlQueryGenerator; + readonly queryInterface: MySqlQueryInterface; + readonly Query = MySqlQuery; + + constructor(sequelize: Sequelize, options: MySqlDialectOptions) { + super({ + sequelize, + options, + dataTypeOverrides: DataTypes, + minimumDatabaseVersion: '8.0.19', + identifierDelimiter: '`', + dataTypesDocumentationUrl: 'https://dev.mysql.com/doc/refman/8.0/en/data-types.html', + name: 'mysql', + }); + + this.connectionManager = new MySqlConnectionManager(this); + this.queryGenerator = new MySqlQueryGenerator(this); + this.queryInterface = new MySqlQueryInterface(this); + + registerMySqlDbDataTypeParsers(this); + } + + createBindCollector() { + return createUnspecifiedOrderedBindCollector(); + } + + escapeString(value: string): string { + return escapeMysqlMariaDbString(value); + } + + escapeJson(value: unknown): string { + return `CAST(${super.escapeJson(value)} AS JSON)`; + } + + canBackslashEscape() { + return true; + } + + getDefaultSchema(): string { + return this.sequelize.options.replication.write.database ?? ''; + } + + parseConnectionUrl(url: string): MySqlConnectionOptions { + return parseCommonConnectionUrlOptions({ + url, + allowedProtocols: ['mysql'], + hostname: 'host', + port: 'port', + pathname: 'database', + username: 'user', + password: 'password', + stringSearchParams: STRING_CONNECTION_OPTION_NAMES, + booleanSearchParams: BOOLEAN_CONNECTION_OPTION_NAMES, + numberSearchParams: NUMBER_CONNECTION_OPTION_NAMES, + }); + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/mysql/src/index.mjs b/packages/mysql/src/index.mjs new file mode 100644 index 000000000000..d7319d9932ae --- /dev/null +++ b/packages/mysql/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const MySqlConnectionManager = Pkg.MySqlConnectionManager; +export const MySqlDialect = Pkg.MySqlDialect; +export const MySqlQueryGenerator = Pkg.MySqlQueryGenerator; +export const MySqlQueryInterface = Pkg.MySqlQueryInterface; +export const MySqlQuery = Pkg.MySqlQuery; diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/mysql/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/mysql/src/query-generator-typescript.internal.ts b/packages/mysql/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..ca631ed17d4a --- /dev/null +++ b/packages/mysql/src/query-generator-typescript.internal.ts @@ -0,0 +1,182 @@ +import type { + Expression, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator, Op } from '@sequelize/core'; +import type { EscapeOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { buildJsonPath } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/json.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import type { MySqlDialect } from './dialect.js'; +import { MySqlQueryGeneratorInternal } from './query-generator.internal.js'; + +/** + * Temporary class to ease the TypeScript migration + */ +export class MySqlQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: MySqlQueryGeneratorInternal; + + constructor( + dialect: MySqlDialect, + internals: MySqlQueryGeneratorInternal = new MySqlQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + internals.whereSqlBuilder.setOperatorKeyword(Op.regexp, 'REGEXP'); + internals.whereSqlBuilder.setOperatorKeyword(Op.notRegexp, 'NOT REGEXP'); + + this.#internals = internals; + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + let schemasToSkip = this.#internals.getTechnicalSchemaNames(); + if (options && Array.isArray(options?.skip)) { + schemasToSkip = [...schemasToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT SCHEMA_NAME AS `schema`', + 'FROM INFORMATION_SCHEMA.SCHEMATA', + `WHERE SCHEMA_NAME NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + return `SHOW FULL COLUMNS FROM ${this.quoteTable(tableName)};`; + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT TABLE_NAME AS `tableName`,', + 'TABLE_SCHEMA AS `schema`', + `FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'`, + options?.schema + ? `AND TABLE_SCHEMA = ${this.escape(options.schema)}` + : `AND TABLE_SCHEMA NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY TABLE_SCHEMA, TABLE_NAME', + ]); + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE ${this.quoteTable(tableName)}`; + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT c.CONSTRAINT_SCHEMA AS constraintSchema,', + 'c.CONSTRAINT_NAME AS constraintName,', + 'c.CONSTRAINT_TYPE AS constraintType,', + 'c.TABLE_SCHEMA AS tableSchema,', + 'c.TABLE_NAME AS tableName,', + 'kcu.COLUMN_NAME AS columnNames,', + 'kcu.REFERENCED_TABLE_SCHEMA AS referencedTableSchema,', + 'kcu.REFERENCED_TABLE_NAME AS referencedTableName,', + 'kcu.REFERENCED_COLUMN_NAME AS referencedColumnNames,', + 'r.DELETE_RULE AS deleteAction,', + 'r.UPDATE_RULE AS updateAction,', + 'ch.CHECK_CLAUSE AS definition', + 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c', + 'LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME AND c.TABLE_NAME = r.TABLE_NAME', + 'LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.CONSTRAINT_CATALOG = kcu.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND c.TABLE_NAME = kcu.TABLE_NAME', + 'LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS ch ON c.CONSTRAINT_CATALOG = ch.CONSTRAINT_CATALOG', + 'AND c.CONSTRAINT_SCHEMA = ch.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = ch.CONSTRAINT_NAME', + `WHERE c.TABLE_NAME = ${this.escape(table.tableName)}`, + `AND c.TABLE_SCHEMA = ${this.escape(table.schema)}`, + options?.columnName ? `AND kcu.COLUMN_NAME = ${this.escape(options.columnName)}` : '', + options?.constraintName + ? `AND c.CONSTRAINT_NAME = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.CONSTRAINT_TYPE = ${this.escape(options.constraintType)}` + : '', + 'ORDER BY c.CONSTRAINT_NAME, kcu.ORDINAL_POSITION', + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + return `SHOW INDEX FROM ${this.quoteTable(tableName)}`; + } + + getToggleForeignKeyChecksQuery(enable: boolean): string { + return `SET FOREIGN_KEY_CHECKS=${enable ? '1' : '0'}`; + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + let indexName: string; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`; + } + + jsonPathExtractionQuery( + sqlExpression: string, + path: ReadonlyArray, + unquote: boolean, + ): string { + const extractQuery = `json_extract(${sqlExpression},${this.escape(buildJsonPath(path))})`; + if (unquote) { + return `json_unquote(${extractQuery})`; + } + + return extractQuery; + } + + formatUnquoteJson(arg: Expression, options?: EscapeOptions) { + return `json_unquote(${this.escape(arg, options)})`; + } + + versionQuery() { + return 'SELECT VERSION() as `version`'; + } + + getUuidV1FunctionCall(): string { + return 'UUID()'; + } +} diff --git a/packages/mysql/src/query-generator.d.ts b/packages/mysql/src/query-generator.d.ts new file mode 100644 index 000000000000..454da5787d85 --- /dev/null +++ b/packages/mysql/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { MySqlQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class MySqlQueryGenerator extends MySqlQueryGeneratorTypeScript {} diff --git a/packages/mysql/src/query-generator.internal.ts b/packages/mysql/src/query-generator.internal.ts new file mode 100644 index 000000000000..4d91765b4848 --- /dev/null +++ b/packages/mysql/src/query-generator.internal.ts @@ -0,0 +1,27 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import { formatMySqlStyleLimitOffset } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import type { MySqlDialect } from './dialect.js'; + +const TECHNICAL_SCHEMAS = Object.freeze([ + 'MYSQL', + 'INFORMATION_SCHEMA', + 'PERFORMANCE_SCHEMA', + 'SYS', + 'mysql', + 'information_schema', + 'performance_schema', + 'sys', +]); + +export class MySqlQueryGeneratorInternal< + Dialect extends MySqlDialect = MySqlDialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalSchemaNames(): readonly string[] { + return TECHNICAL_SCHEMAS; + } + + addLimitAndOffset(options: AddLimitOffsetOptions) { + return formatMySqlStyleLimitOffset(options, this.queryGenerator); + } +} diff --git a/packages/mysql/src/query-generator.js b/packages/mysql/src/query-generator.js new file mode 100644 index 000000000000..a5a5653b8486 --- /dev/null +++ b/packages/mysql/src/query-generator.js @@ -0,0 +1,290 @@ +'use strict'; + +import { + attributeTypeToSql, + normalizeDataType, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import { ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { BaseSqlExpression } from '@sequelize/core/_non-semver-use-at-your-own-risk_/expression-builders/base-sql-expression.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { inspect } from '@sequelize/utils'; +import each from 'lodash/each'; +import isPlainObject from 'lodash/isPlainObject'; +import { MySqlQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); + +export class MySqlQueryGenerator extends MySqlQueryGeneratorTypeScript { + createTableQuery(tableName, attributes, options) { + options = { + engine: 'InnoDB', + charset: null, + rowFormat: null, + ...options, + }; + + const primaryKeys = []; + const foreignKeys = {}; + const attrStr = []; + + for (const attr in attributes) { + if (!Object.hasOwn(attributes, attr)) { + continue; + } + + const dataType = attributes[attr]; + let match; + + if (dataType.includes('PRIMARY KEY')) { + primaryKeys.push(attr); + + if (dataType.includes('REFERENCES')) { + // MySQL doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + } + } else if (dataType.includes('REFERENCES')) { + // MySQL doesn't support inline REFERENCES declarations: move to the end + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + + const table = this.quoteTable(tableName); + let attributesClause = attrStr.join(', '); + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + if (options.uniqueKeys) { + each(options.uniqueKeys, (columns, indexName) => { + if (typeof indexName !== 'string') { + indexName = `uniq_${tableName}_${columns.fields.join('_')}`; + } + + attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields + .map(field => this.quoteIdentifier(field)) + .join(', ')})`; + }); + } + + if (pkString.length > 0) { + attributesClause += `, PRIMARY KEY (${pkString})`; + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + } + } + + return 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}`, + ';', + ]); + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + dataType = { + ...dataType, + type: normalizeDataType(dataType.type, this.dialect), + }; + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key, + }), + ';', + ]); + } + + changeColumnQuery(tableName, attributes) { + const attrString = []; + const constraintString = []; + + for (const attributeName in attributes) { + let definition = attributes[attributeName]; + if (definition.includes('REFERENCES')) { + const attrName = this.quoteIdentifier(attributeName); + definition = definition.replace(/.+?(?=REFERENCES)/, ''); + constraintString.push(`FOREIGN KEY (${attrName}) ${definition}`); + } else { + attrString.push(`\`${attributeName}\` \`${attributeName}\` ${definition}`); + } + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `CHANGE ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';', + ]); + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const attrString = []; + + for (const attrName in attributes) { + const definition = attributes[attrName]; + attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'CHANGE', + attrString.join(', '), + ';', + ]); + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + const attributeString = attributeTypeToSql(attribute.type, { + escape: this.escape.bind(this), + dialect: this.dialect, + }); + let template = attributeString; + + if (attribute.allowNull === false) { + template += ' NOT NULL'; + } + + if (attribute.autoIncrement) { + template += ' auto_increment'; + } + + // BLOB/TEXT/GEOMETRY/JSON cannot have a default value + if ( + !typeWithoutDefault.has(attributeString) && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + const { defaultValue } = attribute; + const escaped = this.escape(defaultValue); + // MySQL 8.0.13+ supports expressions as default values if they are wrapped in parentheses + template += ` DEFAULT ${defaultValue instanceof BaseSqlExpression ? `(${escaped})` : escaped}`; + } + + if (attribute.unique === true) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + if (attribute.comment) { + template += ` COMMENT ${this.escape(attribute.comment)}`; + } + + if (attribute.first) { + template += ' FIRST'; + } + + if (attribute.after) { + template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; + } + + if ((!options || !options.withoutForeignKeyConstraints) && attribute.references) { + if (options && options.context === 'addColumn' && options.foreignKey) { + const fkName = this.quoteIdentifier( + `${this.extractTableDetails(options.tableName).tableName}_${options.foreignKey}_foreign_idx`, + ); + + template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${this.quoteIdentifier(options.foreignKey)})`; + } + + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = {}; + + for (const key in attributes) { + const attribute = attributes[key]; + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } + + _getBeforeSelectAttributesFragment(options) { + let fragment = ''; + + const MINIMUM_EXECUTION_TIME_VALUE = 0; + const MAXIMUM_EXECUTION_TIME_VALUE = 4_294_967_295; + + if (options.maxExecutionTimeHintMs != null) { + if ( + Number.isSafeInteger(options.maxExecutionTimeHintMs) && + options.maxExecutionTimeHintMs >= MINIMUM_EXECUTION_TIME_VALUE && + options.maxExecutionTimeHintMs <= MAXIMUM_EXECUTION_TIME_VALUE + ) { + fragment += ` /*+ MAX_EXECUTION_TIME(${options.maxExecutionTimeHintMs}) */`; + } else { + throw new Error( + `maxExecutionTimeMs must be between ${MINIMUM_EXECUTION_TIME_VALUE} and ${MAXIMUM_EXECUTION_TIME_VALUE}, but it is ${inspect(options.maxExecutionTimeHintMs)}`, + ); + } + } + + return fragment; + } +} diff --git a/packages/mysql/src/query-interface.d.ts b/packages/mysql/src/query-interface.d.ts new file mode 100644 index 000000000000..6c72924b6c84 --- /dev/null +++ b/packages/mysql/src/query-interface.d.ts @@ -0,0 +1,6 @@ +import { AbstractQueryInterface } from '@sequelize/core'; +import type { MySqlDialect } from './dialect.js'; + +export class MySqlQueryInterface< + Dialect extends MySqlDialect = MySqlDialect, +> extends AbstractQueryInterface {} diff --git a/packages/mysql/src/query-interface.js b/packages/mysql/src/query-interface.js new file mode 100644 index 000000000000..fc7c6cd32a87 --- /dev/null +++ b/packages/mysql/src/query-interface.js @@ -0,0 +1,65 @@ +'use strict'; + +import { AbstractQueryInterface, QueryTypes } from '@sequelize/core'; +import { getObjectFromMap } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { + assertNoReservedBind, + combineBinds, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; + +/** + * The interface that Sequelize uses to talk with MySQL/MariaDB database + */ +export class MySqlQueryInterface extends AbstractQueryInterface { + /** + * 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) { + const foreignKeys = await this.showConstraints(tableName, { + ...options, + columnName, + constraintType: 'FOREIGN KEY', + }); + await Promise.all( + foreignKeys.map(constraint => + this.removeConstraint(tableName, constraint.constraintName, options), + ), + ); + + await super.removeColumn(tableName, columnName, options); + } + + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + if (options.bind) { + assertNoReservedBind(options.bind); + } + + const modelDefinition = options.model.modelDefinition; + + options = { ...options }; + + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + options.upsertKeys = Array.from(modelDefinition.primaryKeysAttributeNames, pkAttrName => + modelDefinition.getColumnName(pkAttrName), + ); + + const { bind, query } = this.queryGenerator.insertQuery( + tableName, + insertValues, + getObjectFromMap(modelDefinition.attributes), + options, + ); + + // unlike bind, replacements are handled by QueryGenerator, not QueryRaw + delete options.replacements; + options.bind = combineBinds(options.bind, bind); + + return await this.sequelize.queryRaw(query, options); + } +} diff --git a/packages/mysql/src/query.d.ts b/packages/mysql/src/query.d.ts new file mode 100644 index 000000000000..84e25b4ed7d7 --- /dev/null +++ b/packages/mysql/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class MySqlQuery extends AbstractQuery {} diff --git a/packages/mysql/src/query.js b/packages/mysql/src/query.js new file mode 100644 index 000000000000..579ee6037c20 --- /dev/null +++ b/packages/mysql/src/query.js @@ -0,0 +1,314 @@ +'use strict'; + +import { + AbstractQuery, + DatabaseError, + ForeignKeyConstraintError, + UniqueConstraintError, + UnknownConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { inspect } from '@sequelize/utils'; +import forOwn from 'lodash/forOwn'; +import map from 'lodash/map'; +import zipObject from 'lodash/zipObject'; + +const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; +const ER_ROW_IS_REFERENCED = 1451; +const ER_NO_REFERENCED_ROW = 1452; +const ER_CONSTRAINT_NOT_FOUND = 3940; + +const debug = logger.debugContext('sql:mysql'); + +export class MySqlQuery extends AbstractQuery { + constructor(connection, sequelize, options) { + super(connection, sequelize, { showWarnings: false, ...options }); + } + + async run(sql, parameters) { + this.sql = sql; + const { connection, options } = this; + + const showWarnings = this.sequelize.dialect.options.showWarnings || options.showWarnings; + + const complete = this._logQuery(sql, debug, parameters); + + if (parameters) { + debug('parameters(%j)', parameters); + } + + let results; + + try { + if (parameters && parameters.length > 0) { + results = await new Promise((resolve, reject) => { + connection + .execute(sql, parameters, (error, result) => (error ? reject(error) : resolve(result))) + .setMaxListeners(100); + }); + } else { + results = await new Promise((resolve, reject) => { + connection + .query({ sql }, (error, result) => (error ? reject(error) : resolve(result))) + .setMaxListeners(100); + }); + } + } catch (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 { + // Ignore errors - since MySQL automatically rolled back, we're + // not that worried about this redundant rollback failing. + } + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } finally { + complete(); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + + return this.formatResults(results); + } + + /** + * High level function that handles the results of a query execution. + * + * + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + * + * @param {Array} data - The result of the query execution. + * @private + */ + formatResults(data) { + let result = this.instance; + + if (this.isInsertQuery(data)) { + this.handleInsertQuery(data); + + if (!this.instance) { + const modelDefinition = this.model?.modelDefinition; + + // handle bulkCreate AI primary key + if ( + data.constructor.name === 'ResultSetHeader' && + modelDefinition?.autoIncrementAttributeName && + modelDefinition?.autoIncrementAttributeName === this.model.primaryKeyAttribute + ) { + const startId = data[this.getInsertIdField()]; + result = []; + for ( + let i = BigInt(startId); + i < BigInt(startId) + BigInt(data.affectedRows); + i = i + 1n + ) { + result.push({ + [modelDefinition.getColumnName(this.model.primaryKeyAttribute)]: + typeof startId === 'string' ? i.toString() : Number(i), + }); + } + } else { + result = data[this.getInsertIdField()]; + } + } + } + + if (this.isSelectQuery()) { + return this.handleSelectQuery(data); + } + + if (this.isDescribeQuery()) { + result = {}; + + for (const _result of data) { + const enumRegex = /^enum/i; + result[_result.Field] = { + type: enumRegex.test(_result.Type) + ? _result.Type.replace(enumRegex, 'ENUM') + : _result.Type.toUpperCase(), + allowNull: _result.Null === 'YES', + defaultValue: _result.Default, + primaryKey: _result.Key === 'PRI', + autoIncrement: + Object.hasOwn(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment', + comment: _result.Comment ? _result.Comment : null, + }; + } + + return result; + } + + if (this.isShowIndexesQuery()) { + return this.handleShowIndexesQuery(data); + } + + if (this.isCallQuery()) { + return data[0]; + } + + if (this.isBulkUpdateQuery() || this.isDeleteQuery()) { + return data.affectedRows; + } + + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } + + if (this.isInsertQuery() || this.isUpdateQuery()) { + return [result, data.affectedRows]; + } + + if (this.isShowConstraintsQuery()) { + return data; + } + + if (this.isRawQuery()) { + // MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta + return [data, data]; + } + + return result; + } + + formatError(err) { + const errCode = err.errno || err.code; + + switch (errCode) { + 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].split('.').pop() : undefined; + const fieldVal = match ? match[1] : undefined; + const uniqueKey = + this.model && + this.model.getIndexes().find(index => index.unique && index.name === fieldKey); + + if (uniqueKey) { + if (uniqueKey.msg) { + message = uniqueKey.msg; + } + + fields = zipObject(uniqueKey.fields, values); + } else { + fields[fieldKey] = fieldVal; + } + + const errors = []; + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + case ER_ROW_IS_REFERENCED: + case ER_NO_REFERENCED_ROW: { + // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) + const match = err.message.match( + /CONSTRAINT (["`])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/, + ); + const quoteChar = match ? match[1] : '`'; + const fields = match + ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) + : undefined; + + return new ForeignKeyConstraintError({ + 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, + cause: err, + }); + } + + case ER_CONSTRAINT_NOT_FOUND: { + const constraintMatch = err.sql.match(/(?:constraint|index) `(.+?)`/i); + const constraint = constraintMatch ? constraintMatch[1] : undefined; + const tableMatch = err.sql.match(/table `(.+?)`/i); + const table = tableMatch ? tableMatch[1] : undefined; + + return new UnknownConstraintError({ + message: err.text, + constraint, + table, + cause: err, + }); + } + + default: + return new DatabaseError(err); + } + } + + handleShowIndexesQuery(data) { + // Group by index name, and collect all fields + data = data.reduce((acc, item) => { + if (!(item.Key_name in acc)) { + acc[item.Key_name] = item; + item.fields = []; + } + + acc[item.Key_name].fields[item.Seq_in_index - 1] = { + attribute: item.Column_name, + length: item.Sub_part || undefined, + order: + item.Collation === 'A' + ? 'ASC' + : item.Collation === 'D' + ? 'DESC' + : // Not sorted + item.Collation === null + ? null + : (() => { + throw new Error(`Unknown index collation ${inspect(item.Collation)}`); + })(), + }; + delete item.column_name; + + return acc; + }, {}); + + return map(data, item => { + return { + primary: item.Key_name === 'PRIMARY', + fields: item.fields, + name: item.Key_name, + tableName: item.Table, + // MySQL 8 returns this as a number (Integer), MySQL 5 returns it as a string (BigInt) + unique: item.Non_unique !== '1' && item.Non_unique !== 1, + type: item.Index_type, + }; + }); + } +} diff --git a/packages/mysql/tsconfig.json b/packages/mysql/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/mysql/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/mysql/typedoc.json b/packages/mysql/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/mysql/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/postgres/.eslintrc.js b/packages/postgres/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/postgres/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/postgres/CHANGELOG.md b/packages/postgres/CHANGELOG.md new file mode 100644 index 000000000000..eaf781d6e10f --- /dev/null +++ b/packages/postgres/CHANGELOG.md @@ -0,0 +1,74 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +### Bug Fixes + +- **postgres:** order on column name in showConstraintsQuery ([#17760](https://github.com/sequelize/sequelize/issues/17760)) ([5de096a](https://github.com/sequelize/sequelize/commit/5de096aefe86ca8941667ecadd5a91059312a5d9)) + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +### Bug Fixes + +- **postgres:** correct existing enum type matching ([#17576](https://github.com/sequelize/sequelize/issues/17576)) ([425d217](https://github.com/sequelize/sequelize/commit/425d21718af40f86015f6496ea6cf721cc61b981)) +- **postgres:** update to postgres 17 ([#17740](https://github.com/sequelize/sequelize/issues/17740)) ([b5c2b26](https://github.com/sequelize/sequelize/commit/b5c2b2667004b3b27e5634c677507f5593987938)) + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/postgres + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +**Note:** Version bump only for package @sequelize/postgres + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- unify returning queries ([#17157](https://github.com/sequelize/sequelize/issues/17157)) ([0a350c0](https://github.com/sequelize/sequelize/commit/0a350c0f91d0eee9c56b92f47cc23c273c9eb206)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/postgres + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +**Note:** Version bump only for package @sequelize/postgres + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) +- update bulkDeleteQuery supported options ([#17191](https://github.com/sequelize/sequelize/issues/17191)) ([c53fd01](https://github.com/sequelize/sequelize/commit/c53fd0114ab7a796d7b649abd12086e3c6f7d077)) + +- feat(mariadb)!: move mariadb to the `@sequelize/mariadb` package (#17198) ([46ea159](https://github.com/sequelize/sequelize/commit/46ea159306c55c7b3c02ac0ba24a2c0dd3dff4d9)), closes [#17198](https://github.com/sequelize/sequelize/issues/17198) + +### Features + +- move postgres to the `@sequelize/postgres` package ([#17190](https://github.com/sequelize/sequelize/issues/17190)) ([721d560](https://github.com/sequelize/sequelize/commit/721d56061c801015a8ec91d8e0aed30b5da24497)) +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` +- Instead of installing the `mariadb` package, users need to install `@sequelize/mariadb.` diff --git a/packages/postgres/package.json b/packages/postgres/package.json new file mode 100644 index 000000000000..39ad811e54f9 --- /dev/null +++ b/packages/postgres/package.json @@ -0,0 +1,57 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "Postgres Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/postgres", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs postgres", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-unit": "mocha src/**/*.test.ts", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "@types/pg": "^8.11.14", + "lodash": "^4.17.21", + "pg": "^8.15.6", + "pg-hstore": "^2.3.4", + "pg-types": "^4.1.0", + "postgres-array": "^3.0.4", + "semver": "^7.7.3", + "wkx": "^0.5.0" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "chai": "4.5.0", + "mocha": "11.7.5" + } +} diff --git a/packages/postgres/src/_internal/connection-options.ts b/packages/postgres/src/_internal/connection-options.ts new file mode 100644 index 000000000000..24d534e48f10 --- /dev/null +++ b/packages/postgres/src/_internal/connection-options.ts @@ -0,0 +1,55 @@ +import type { PickByType } from '@sequelize/utils'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import type { PostgresConnectionOptions } from '../connection-manager.js'; + +type StringConnectionOptions = PickByType; + +const STRING_CONNECTION_OPTION_MAP = { + application_name: undefined, + client_encoding: undefined, + database: undefined, + host: undefined, + options: undefined, + password: undefined, + user: undefined, +} as const satisfies Record; + +export const STRING_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + STRING_CONNECTION_OPTION_MAP, +); + +type BooleanConnectionOptions = PickByType; + +const BOOLEAN_CONNECTION_OPTION_MAP = { + binary: undefined, + keepAlive: undefined, + ssl: undefined, + statement_timeout: undefined, +} as const satisfies Record; + +export const BOOLEAN_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + BOOLEAN_CONNECTION_OPTION_MAP, +); + +type NumberConnectionOptions = PickByType; + +const NUMBER_CONNECTION_OPTION_MAP = { + port: undefined, + statement_timeout: undefined, + query_timeout: undefined, + keepAliveInitialDelayMillis: undefined, + idle_in_transaction_session_timeout: undefined, + connectionTimeoutMillis: undefined, + lock_timeout: undefined, +} as const satisfies Record; + +export const NUMBER_CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys( + NUMBER_CONNECTION_OPTION_MAP, +); + +export const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + ...STRING_CONNECTION_OPTION_MAP, + ...BOOLEAN_CONNECTION_OPTION_MAP, + ...NUMBER_CONNECTION_OPTION_MAP, + stream: undefined, +}); diff --git a/packages/postgres/src/_internal/data-types-db.ts b/packages/postgres/src/_internal/data-types-db.ts new file mode 100644 index 000000000000..b3073e4e2ea9 --- /dev/null +++ b/packages/postgres/src/_internal/data-types-db.ts @@ -0,0 +1,88 @@ +import { getDataTypeParser } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import identity from 'lodash/identity'; +import assert from 'node:assert'; +import wkx from 'wkx'; +import type { PostgresDialect } from '../dialect.js'; +import { parseHstore } from './hstore.js'; +import { buildRangeParser } from './range.js'; + +/** + * First pass of DB value parsing: Parses based on the Postgres Type ID. + * If a Sequelize DataType is specified, the value is then passed to {@link DataTypes.ABSTRACT#parseDatabaseValue}. + * + * @param dialect + */ +export function registerPostgresDbDataTypeParsers(dialect: PostgresDialect) { + // types & OIDs listed here https://github.com/lib/pq/blob/master/oid/types.go + // range & enum are also supported, but use a special path as they are custom types + + // dateonly + dialect.registerDataTypeParser(['date'], (value: unknown) => { + if (value === 'infinity') { + return Number.POSITIVE_INFINITY; + } + + if (value === '-infinity') { + return Number.NEGATIVE_INFINITY; + } + + return value; + }); + + dialect.registerDataTypeParser(['timestamptz', 'timestamp'], (value: unknown) => { + // override default parser to prevent returning a Date object (which is the default behavior in pg). + // return dates as string, not Date objects. Different implementations could be used instead (such as Temporal, dayjs) + return value; + }); + + dialect.registerDataTypeParser(['numeric', 'decimal'], (value: unknown) => { + if (value === 'NaN') { + return Number.NaN; + } + + return value; + }); + + // dialect.registerDataTypeParser(['bool'], (value: unknown) => { + // return value; + // }); + + dialect.registerDataTypeParser(['geometry'], (value: unknown) => { + assert(typeof value === 'string', 'Expected geometry value to be a string'); + + const b = Buffer.from(value, 'hex'); + + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); + }); + + dialect.registerDataTypeParser(['geography'], (value: unknown) => { + assert(typeof value === 'string', 'Expected geography value to be a string'); + + const b = Buffer.from(value, 'hex'); + + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); + }); + + dialect.registerDataTypeParser(['hstore'], (value: unknown) => { + assert(typeof value === 'string', 'Expected hstore value to be a string'); + + return parseHstore(value); + }); + + const parseInteger = getDataTypeParser(dialect, BaseTypes.INTEGER); + dialect.registerDataTypeParser(['int4range'], buildRangeParser(parseInteger)); + + const parseBigInt = getDataTypeParser(dialect, BaseTypes.BIGINT); + dialect.registerDataTypeParser(['int8range'], buildRangeParser(parseBigInt)); + + const parseDecimal = getDataTypeParser(dialect, BaseTypes.DECIMAL); + dialect.registerDataTypeParser(['numrange'], buildRangeParser(parseDecimal)); + + // the following ranges are returned as an array of strings in raw queries: + // - datetime with time zone + // - datetime without time zone + // - dateonly + // The Sequelize DataType specified by the user will do further parsing of the arrays of strings (like convert values to Date objects). + dialect.registerDataTypeParser(['tstzrange', 'tsrange', 'daterange'], buildRangeParser(identity)); +} diff --git a/packages/postgres/src/_internal/data-types-overrides.ts b/packages/postgres/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..da82c5b5cb97 --- /dev/null +++ b/packages/postgres/src/_internal/data-types-overrides.ts @@ -0,0 +1,439 @@ +import type { AbstractDialect, Rangable } from '@sequelize/core'; +import { attributeTypeToSql } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types-utils.js'; +import type { + AbstractDataType, + AcceptableTypeOf, + AcceptedDate, + BindParamOptions, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import { inspect, isBigInt, isNumber, isString } from '@sequelize/utils'; +import identity from 'lodash/identity.js'; +import assert from 'node:assert'; +import wkx from 'wkx'; +import { PostgresQueryGenerator } from '../query-generator'; +import { stringifyHstore } from './hstore.js'; +import { buildRangeParser, stringifyRange } from './range.js'; + +function removeUnsupportedIntegerOptions( + dataType: BaseTypes.BaseIntegerDataType, + dialect: AbstractDialect, +) { + if (dataType.options.length != null) { + // this option only makes sense for zerofill + dialect.warnDataTypeIssue( + `${dialect.name} does not support ${dataType.getDataTypeId()} with length specified. This options is ignored.`, + ); + + delete dataType.options.length; + } +} + +export class DATEONLY extends BaseTypes.DATEONLY { + toBindableValue(value: AcceptableTypeOf) { + if (value === Number.POSITIVE_INFINITY) { + return 'infinity'; + } + + if (value === Number.NEGATIVE_INFINITY) { + return '-infinity'; + } + + return super.toBindableValue(value); + } + + sanitize(value: unknown): unknown { + if (value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) { + return value; + } + + if (typeof value === 'string') { + const lower = value.toLowerCase(); + if (lower === 'infinity') { + return Number.POSITIVE_INFINITY; + } + + if (lower === '-infinity') { + return Number.NEGATIVE_INFINITY; + } + } + + return super.sanitize(value); + } +} + +export class DECIMAL extends BaseTypes.DECIMAL { + // TODO: add check constraint >= 0 if unsigned is true +} + +export class TEXT extends BaseTypes.TEXT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.length) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support TEXT with options. Plain TEXT will be used instead.`, + ); + + this.options.length = undefined; + } + } +} + +export class DATE extends BaseTypes.DATE { + toSql() { + if (this.options.precision != null) { + return `TIMESTAMP(${this.options.precision}) WITH TIME ZONE`; + } + + return 'TIMESTAMP WITH TIME ZONE'; + } + + validate(value: any) { + if (value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) { + // valid + return; + } + + super.validate(value); + } + + toBindableValue(value: AcceptedDate): string { + if (value === Number.POSITIVE_INFINITY) { + return 'infinity'; + } + + if (value === Number.NEGATIVE_INFINITY) { + return '-infinity'; + } + + return super.toBindableValue(value); + } + + sanitize(value: unknown) { + if (value == null) { + return value; + } + + if (value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) { + return value; + } + + if (value instanceof Date) { + return value; + } + + if (typeof value === 'string') { + const lower = value.toLowerCase(); + if (lower === 'infinity') { + return Number.POSITIVE_INFINITY; + } + + if (lower === '-infinity') { + return Number.NEGATIVE_INFINITY; + } + } + + return super.sanitize(value); + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^8-1 check when the unsigned option is true + // TODO: add >= -2^7 =< 2^7-1 check when the unsigned option is false + + toSql(): string { + return 'SMALLINT'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^16-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'INTEGER'; + } + + return 'SMALLINT'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^24-1 check when the unsigned option is true + // TODO: add >= -2^23 =< 2^23-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^32-1 check when the unsigned option is true + + toSql(): string { + if (this.options.unsigned) { + return 'BIGINT'; + } + + return 'INTEGER'; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + // TODO: add check constraint >= 0 if unsigned is true +} + +export class FLOAT extends BaseTypes.FLOAT { + // TODO: add check constraint >= 0 if unsigned is true + + protected getNumberSqlTypeName(): string { + // REAL is postgres' single precision float. FLOAT(p) is an alias for either REAL of DOUBLE PRECISION based on (p). + return 'REAL'; + } +} + +export class BLOB extends BaseTypes.BLOB { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.length) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support BLOB (BYTEA) with options. Plain BYTEA will be used instead.`, + ); + this.options.length = undefined; + } + } + + toSql() { + return 'BYTEA'; + } +} + +export class GEOMETRY extends BaseTypes.GEOMETRY { + toSql() { + let result = 'GEOMETRY'; + if (this.options.type) { + result += `(${this.options.type.toUpperCase()}`; + if (this.options.srid) { + result += `,${this.options.srid}`; + } + + result += ')'; + } + + return result; + } + + parse(value: string) { + const b = Buffer.from(value, 'hex'); + + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); + } + + toBindableValue(value: AcceptableTypeOf): string { + return `ST_GeomFromGeoJSON(${this._getDialect().escapeString(JSON.stringify(value))})`; + } + + getBindParamSql(value: AcceptableTypeOf, options: BindParamOptions) { + return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; + } +} + +export class GEOGRAPHY extends BaseTypes.GEOGRAPHY { + toSql() { + let result = 'GEOGRAPHY'; + if (this.options.type) { + result += `(${this.options.type}`; + if (this.options.srid) { + result += `,${this.options.srid}`; + } + + result += ')'; + } + + return result; + } + + toBindableValue(value: AcceptableTypeOf) { + return `ST_GeomFromGeoJSON(${this._getDialect().escapeString(JSON.stringify(value))})`; + } + + getBindParamSql(value: AcceptableTypeOf, options: BindParamOptions) { + return `ST_GeomFromGeoJSON(${options.bindParam(value)})`; + } +} + +export class HSTORE extends BaseTypes.HSTORE { + toBindableValue(value: AcceptableTypeOf): string { + if (value == null) { + return value; + } + + return stringifyHstore(value); + } +} + +const defaultRangeParser = buildRangeParser(identity); + +export class RANGE< + T extends BaseTypes.BaseNumberDataType | DATE | DATEONLY = INTEGER, +> extends BaseTypes.RANGE { + toBindableValue(values: Rangable>): string { + if (!Array.isArray(values)) { + throw new TypeError('Range values must be an array'); + } + + return stringifyRange(values, rangePart => { + let out = this.options.subtype.toBindableValue(rangePart); + + if (isNumber(out) || isBigInt(out)) { + out = String(out); + } + + if (!isString(out)) { + throw new Error( + 'DataTypes.RANGE only accepts types that are represented by either strings, numbers or bigints.', + ); + } + + return out; + }); + } + + escape(values: Rangable>): string { + const value = this.toBindableValue(values); + const dialect = this._getDialect(); + + return `${dialect.escapeString(value)}::${this.toSql()}`; + } + + getBindParamSql(values: Rangable>, options: BindParamOptions): string { + const value = this.toBindableValue(values); + + return `${options.bindParam(value)}::${this.toSql()}`; + } + + parseDatabaseValue(value: unknown): unknown { + // node-postgres workaround: The SQL Type-based parser is not called by node-postgres for values returned by Model.findOrCreate. + if (typeof value === 'string') { + value = defaultRangeParser(value); + } + + if (!Array.isArray(value)) { + throw new Error( + `DataTypes.RANGE received a non-range value from the database: ${inspect(value)}`, + ); + } + + return value.map(part => { + return { + ...part, + value: this.options.subtype.parseDatabaseValue(part.value), + }; + }); + } + + toSql() { + const subTypeClass = this.options.subtype.constructor as typeof BaseTypes.AbstractDataType; + + return RANGE.typeMap[subTypeClass.getDataTypeId().toLowerCase()]; + } + + static typeMap: Record = { + integer: 'int4range', + decimal: 'numrange', + date: 'tstzrange', + dateonly: 'daterange', + bigint: 'int8range', + }; +} + +export class ARRAY> extends BaseTypes.ARRAY { + escape(values: Array>) { + const type = this.options.type; + + const mappedValues = isString(type) ? values : values.map(value => type.escape(value)); + + // Types that don't need to specify their cast + const unambiguousType = type instanceof BaseTypes.TEXT || type instanceof BaseTypes.INTEGER; + + const cast = + mappedValues.length === 0 || !unambiguousType ? `::${attributeTypeToSql(type)}[]` : ''; + + return `ARRAY[${mappedValues.join(',')}]${cast}`; + } + + getBindParamSql(values: Array>, options: BindParamOptions) { + if (isString(this.options.type)) { + return options.bindParam(values); + } + + const subType: AbstractDataType = this.options.type; + + return options.bindParam( + values.map((value: any) => { + return subType.toBindableValue(value); + }), + ); + } +} + +export class ENUM extends BaseTypes.ENUM { + override toSql(): string { + const context = this.usageContext; + if (context == null) { + throw new Error( + 'Could not determine the name of this enum because it is not attached to an attribute or a column.', + ); + } + + let tableName; + let columnName; + if ('model' in context) { + tableName = context.model.table; + + const attribute = context.model.getAttributes()[context.attributeName]; + columnName = attribute.field ?? context.attributeName; + } else { + tableName = context.tableName; + columnName = context.columnName; + } + + const queryGenerator = context.sequelize.dialect.queryGenerator; + + assert( + queryGenerator instanceof PostgresQueryGenerator, + 'expected queryGenerator to be PostgresQueryGenerator', + ); + + return queryGenerator.pgEnumName(tableName, columnName); + } +} diff --git a/packages/postgres/src/_internal/hstore.test.ts b/packages/postgres/src/_internal/hstore.test.ts new file mode 100644 index 000000000000..afaf25cd563d --- /dev/null +++ b/packages/postgres/src/_internal/hstore.test.ts @@ -0,0 +1,92 @@ +import { expect } from 'chai'; +import { parseHstore, stringifyHstore } from './hstore'; + +describe('stringifyHstore', () => { + it('should handle empty objects correctly', () => { + expect(stringifyHstore({})).to.equal(''); + }); + + it('should handle null values correctly', () => { + expect(stringifyHstore({ null: null })).to.equal('"null"=>NULL'); + }); + + it('should handle null values correctly', () => { + expect(stringifyHstore({ foo: null })).to.equal('"foo"=>NULL'); + }); + + it('should handle empty string correctly', () => { + expect(stringifyHstore({ foo: '' })).to.equal('"foo"=>""'); + }); + + it('should handle a string with backslashes correctly', () => { + expect(stringifyHstore({ foo: '\\' })).to.equal('"foo"=>"\\\\"'); + }); + + it('should handle a string with double quotes correctly', () => { + expect(stringifyHstore({ foo: '""a"' })).to.equal('"foo"=>"\\"\\"a\\""'); + }); + + it('should handle a string with single quotes correctly', () => { + expect(stringifyHstore({ foo: "''a'" })).to.equal("\"foo\"=>\"''''a''\""); + }); + + it('should handle simple objects correctly', () => { + expect(stringifyHstore({ test: 'value' })).to.equal('"test"=>"value"'); + }); +}); + +describe('parseHstore', () => { + it('should handle empty string correctly', () => { + expect(parseHstore('"foo"=>""')).to.deep.equal({ foo: '' }); + }); + + it('should handle a string with double quotes correctly', () => { + expect(parseHstore('"foo"=>"\\"\\"a\\""')).to.deep.equal({ foo: '""a"' }); + }); + + it('should handle a string with single quotes correctly', () => { + expect(parseHstore("\"foo\"=>\"''''a''\"")).to.deep.equal({ foo: "''a'" }); + }); + + it('should handle a string with backslashes correctly', () => { + expect(parseHstore('"foo"=>"\\\\"')).to.deep.equal({ foo: '\\' }); + }); + + it('should handle empty objects correctly', () => { + expect(parseHstore('')).to.deep.equal({}); + }); + + it('should handle simple objects correctly', () => { + expect(parseHstore('"test"=>"value"')).to.deep.equal({ test: 'value' }); + }); + + // TODO: fork package and fix this + it.skip('is not vulnerable to prototype injection', () => { + const out = parseHstore('__proto__=>1'); + + expect(Object.keys(out)).to.deep.equal(['__proto__']); + // eslint-disable-next-line no-proto -- this is not the getter + expect(out.__proto__).to.equal(1); + }); +}); + +describe('stringify and parse', () => { + it('should stringify then parse back the same structure', () => { + const testObj = { + foo: 'bar', + count: '1', + emptyString: '', + quotyString: '""', + extraQuotyString: '"""a"""""', + backslashes: '\\f023', + moreBackslashes: '\\f\\0\\2\\1', + backslashesAndQuotes: '\\"\\"uhoh"\\"', + nully: null, + }; + + expect(parseHstore(stringifyHstore(testObj))).to.deep.equal(testObj); + expect(parseHstore(stringifyHstore(parseHstore(stringifyHstore(testObj))))).to.deep.equal( + testObj, + ); + }); +}); diff --git a/packages/postgres/src/_internal/hstore.ts b/packages/postgres/src/_internal/hstore.ts new file mode 100644 index 000000000000..c7b08361ef80 --- /dev/null +++ b/packages/postgres/src/_internal/hstore.ts @@ -0,0 +1,13 @@ +import type { HstoreRecord } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +// @ts-expect-error -- TODO: fork pg-hstore and add types +import PgHstore from 'pg-hstore'; + +const hstore = PgHstore({ sanitize: true }); + +export function stringifyHstore(data: HstoreRecord): string { + return hstore.stringify(data); +} + +export function parseHstore(value: string): HstoreRecord { + return hstore.parse(value); +} diff --git a/packages/postgres/src/_internal/range.ts b/packages/postgres/src/_internal/range.ts new file mode 100644 index 000000000000..c0adea75dfa6 --- /dev/null +++ b/packages/postgres/src/_internal/range.ts @@ -0,0 +1,118 @@ +import type { InputRangePart, Rangable, Range, RangePart } from '@sequelize/core'; +import isPlainObject from 'lodash/isPlainObject'; +import NodeUtil from 'node:util'; + +function stringifyRangeBound( + bound: T | number | null, + stringifyBoundary: (val: T) => string, +): string { + if (bound === null) { + return ''; + } + + if (bound === Number.POSITIVE_INFINITY || bound === Number.NEGATIVE_INFINITY) { + return bound.toString().toLowerCase(); + } + + return stringifyBoundary(bound as T); +} + +type ParseValue = (input: string) => T; + +function parseRangeBound(bound: string, parseType: ParseValue): T | number | null { + if (!bound) { + return null; + } + + if (bound === 'infinity') { + return Number.POSITIVE_INFINITY; + } + + if (bound === '-infinity') { + return Number.NEGATIVE_INFINITY; + } + + if (bound.startsWith('"')) { + bound = bound.slice(1); + } + + if (bound.endsWith('"')) { + bound = bound.slice(0, -1); + } + + return parseType(bound); +} + +export function stringifyRange( + range: Rangable, + stringifyBoundary: (val: T) => string, +): string { + if (range.length === 0) { + return 'empty'; + } + + if (range.length !== 2) { + throw new Error('range array length must be 0 (empty) or 2 (lower and upper bounds)'); + } + + const inclusivity = [true, false]; + const bounds = range.map((rangePart, index) => { + if (isInputRangePart(rangePart)) { + if (typeof rangePart.inclusive === 'boolean') { + inclusivity[index] = rangePart.inclusive; + } + + rangePart = rangePart.value; + } + + return stringifyRangeBound(rangePart, stringifyBoundary); + }); + + return `${(inclusivity[0] ? '[' : '(') + bounds[0]},${bounds[1]}${inclusivity[1] ? ']' : ')'}`; +} + +export function parseRange(value: string, parser: ParseValue): Range { + if (typeof value !== 'string') { + throw new TypeError(`Sequelize could not parse range "${value}" as its format is incompatible`); + } + + if (value === 'empty') { + return []; + } + + const result = value.slice(1, -1).split(',', 2); + + if (result.length !== 2) { + throw new TypeError(`Sequelize could not parse range "${value}" as its format is incompatible`); + } + + return result.map((item, index) => { + const part: RangePart = { + value: parseRangeBound(item, parser), + inclusive: index === 0 ? value.startsWith('[') : value.endsWith(']'), + }; + + return part; + }) as Range; +} + +export function isInputRangePart(val: unknown): val is InputRangePart { + return isPlainObject(val) && Object.hasOwn(val as object, 'value'); +} + +export function buildRangeParser( + subTypeParser: (value: unknown) => unknown, +): (value: unknown) => unknown { + return (value: unknown) => { + if (typeof value !== 'string') { + throw new TypeError( + NodeUtil.format( + `Sequelize could not parse range "%O" as its format is incompatible`, + value, + ), + ); + } + + return parseRange(value, subTypeParser); + }; +} diff --git a/packages/postgres/src/connection-manager.ts b/packages/postgres/src/connection-manager.ts new file mode 100644 index 000000000000..08609e908e6e --- /dev/null +++ b/packages/postgres/src/connection-manager.ts @@ -0,0 +1,395 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + ConnectionError, + ConnectionRefusedError, + ConnectionTimedOutError, + HostNotFoundError, + HostNotReachableError, + InvalidConnectionError, + Sequelize, +} from '@sequelize/core'; +import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import type { ClientConfig } from 'pg'; +import * as Pg from 'pg'; +import type { TypeId, TypeParser } from 'pg-types'; +import { parse as parseArray } from 'postgres-array'; +import semver from 'semver'; +import type { PostgresDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:pg'); + +type TypeFormat = 'text' | 'binary'; + +interface TypeOids { + oid: number; + typeName: string; + type: 'base' | 'array' | 'range' | 'range-array'; + /** oid of the base type. Available on array, range & range-array */ + baseOid?: number; + /** oid of the range. Available on range-array */ + rangeOid?: number; +} + +export type PgModule = typeof Pg; + +export interface PostgresConnection extends AbstractConnection, Pg.Client { + // custom property we attach to the client + // TODO: replace with Symbols. + _invalid?: boolean; + standard_conforming_strings?: boolean; + + // Private property of pg-client + // TODO: ask pg to expose a stable, readonly, property we can use + _ending?: boolean; +} + +export interface PostgresConnectionOptions + extends Omit { + /** + * !! DO NOT SET THIS TO TRUE !! + * (unless you know what you're doing) + * see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com] + */ + binary?: boolean; + + /** + * see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME] + * choose the SSL mode with the PGSSLMODE environment variable + * 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] + * In addition to the values accepted by the corresponding server, + * you can use "auto" to determine the right encoding from the + * current locale in the client (LC_CTYPE environment variable on Unix systems) + */ + client_encoding?: string; + + /** + * 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 + * Times out queries after a set time in milliseconds in the database end. Added in pg v7.3 + * Times out queries after a set time in milliseconds in client end, query would be still running in database end. + * Number of milliseconds to wait for connection, default is no 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 + * Maximum wait time for lock requests in milliseconds. Added in pg v8.8.0. + */ + lock_timeout?: number; +} + +export class PostgresConnectionManager extends AbstractConnectionManager< + PostgresDialect, + PostgresConnection +> { + readonly #lib: PgModule; + readonly #oidMap = new Map(); + readonly #oidParserCache = new Map>(); + + constructor(dialect: PostgresDialect) { + super(dialect); + + const pgModule = dialect.options.pgModule ?? Pg; + + if (dialect.options.native && dialect.options.pgModule) { + throw new Error( + 'You cannot specify both the "pgModule" option and the "native" option at the same time, as the "native" option is only used to use "pg-native" as the "pgModule" instead of "pg"', + ); + } + + if (dialect.options.native && !pgModule.native) { + throw new Error( + 'The "native" option was specified, but the "pg-native" module is not installed. You must install it to use the native bindings.', + ); + } + + this.#lib = dialect.options.native ? pgModule.native! : pgModule; + } + + async connect(config: ConnectionOptions): Promise { + const connectionConfig: ClientConfig = { + port: 5432, + ...config, + types: { + getTypeParser: (oid: TypeId, format?: TypeFormat) => this.getTypeParser(oid, format), + }, + }; + + const connection: PostgresConnection = new this.#lib.Client(connectionConfig); + + await new Promise((resolve, reject) => { + let responded = false; + + const parameterHandler = (message: { parameterName: string; parameterValue: string }) => { + switch (message.parameterName) { + case 'server_version': { + const version = semver.coerce(message.parameterValue)?.version; + this.sequelize.setDatabaseVersion( + version && semver.valid(version) ? version : this.dialect.minimumDatabaseVersion, + ); + + break; + } + + case 'standard_conforming_strings': { + connection.standard_conforming_strings = message.parameterValue === 'on'; + break; + } + + default: + } + }; + + const endHandler = () => { + debug('connection timeout'); + if (!responded) { + reject(new ConnectionTimedOutError(new Error('Connection timed out'))); + } + }; + + // If we didn't ever hear from the client.connect() callback the connection timeout + // node-postgres does not treat this as an error since no active query was ever emitted + connection.once('end', endHandler); + + if (!this.dialect.options.native) { + // Receive various server parameters for further configuration + // @ts-expect-error -- undeclared type + connection.connection.on('parameterStatus', parameterHandler); + } + + connection.connect(err => { + responded = true; + + if (!this.dialect.options.native) { + // remove parameter handler + // @ts-expect-error -- undeclared type + connection.connection.removeListener('parameterStatus', parameterHandler); + } + + if (err) { + // @ts-expect-error -- undeclared type + if (err.code) { + // @ts-expect-error -- undeclared type + switch (err.code) { + case 'ECONNREFUSED': + reject(new ConnectionRefusedError(err)); + break; + case 'ENOTFOUND': + reject(new HostNotFoundError(err)); + break; + case 'EHOSTUNREACH': + reject(new HostNotReachableError(err)); + break; + case 'EINVAL': + reject(new InvalidConnectionError(err)); + break; + default: + reject(new ConnectionError(err)); + break; + } + } else { + reject(new ConnectionError(err)); + } + } else { + debug('connection acquired'); + connection.removeListener('end', endHandler); + resolve(connection); + } + }); + }); + + // Don't let a Postgres restart (or error) to take down the whole app + connection.on('error', (error: any) => { + connection._invalid = true; + debug(`connection error ${error.code || error.message}`); + void this.sequelize.pool.destroy(connection); + }); + + let query = ''; + + if ( + this.dialect.options.standardConformingStrings !== false && + connection.standard_conforming_strings + ) { + // 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;'; + } + + // TODO: make this a connection option + const clientMinMessages = this.dialect.options.clientMinMessages ?? 'warning'; + if (clientMinMessages) { + query += `SET client_min_messages TO ${clientMinMessages};`; + } + + if (!this.sequelize.options.keepDefaultTimezone) { + if (this.sequelize.options.timezone && isValidTimeZone(this.sequelize.options.timezone)) { + query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; + } else { + query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; + } + } + + if (query) { + await connection.query(query); + } + + await this.#refreshOidMap(connection); + + return connection; + } + + async disconnect(connection: PostgresConnection): Promise { + if (connection._ending) { + debug('connection tried to disconnect but was already at ENDING state'); + + return; + } + + await connection.end(); + } + + validate(connection: PostgresConnection) { + return !connection._invalid && !connection._ending; + } + + async #refreshOidMap(connection: PostgresConnection | Sequelize): Promise { + const sql = ` + WITH ranges AS (SELECT pg_range.rngtypid, + pg_type.typname AS rngtypname, + pg_type.typarray AS rngtyparray, + pg_range.rngsubtype + FROM pg_range + LEFT OUTER JOIN pg_type + ON pg_type.oid = pg_range.rngtypid) + SELECT pg_type.typname, + pg_type.typtype, + pg_type.oid, + pg_type.typarray, + 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')); + `; + + let results; + if (connection instanceof Sequelize) { + results = (await connection.query(sql)).pop(); + } else { + results = await connection.query(sql); + } + + // 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(results) && results[0].command === 'SET') { + results = results.pop(); + } + + const oidMap = this.#oidMap; + oidMap.clear(); + + for (const row of results.rows) { + // Mapping base types and their arrays + // Array types are declared twice, once as part of the same row as the base type, once as their own row. + if (!oidMap.has(row.oid)) { + oidMap.set(row.oid, { + oid: row.oid, + typeName: row.typname, + type: 'base', + }); + } + + if (row.typarray) { + oidMap.set(row.typarray, { + oid: row.typarray, + typeName: row.typname, + type: 'array', + baseOid: row.oid, + }); + } + + if (row.rngtypid) { + oidMap.set(row.rngtypid, { + oid: row.rngtypid, + typeName: row.rngtypname, + type: 'range', + baseOid: row.oid, + }); + } + + if (row.rngtyparray) { + oidMap.set(row.rngtyparray, { + oid: row.rngtyparray, + typeName: row.rngtypname, + type: 'range-array', + baseOid: row.oid, + rangeOid: row.rngtypid, + }); + } + } + } + + #buildArrayParser(subTypeParser: (value: string) => unknown): (source: string) => unknown[] { + return (source: string) => { + return parseArray(source, subTypeParser); + }; + } + + getTypeParser(oid: TypeId, format?: TypeFormat): TypeParser { + const cachedParser = this.#oidParserCache.get(oid); + + if (cachedParser) { + return cachedParser; + } + + const customParser = this.#getCustomTypeParser(oid, format); + if (customParser) { + this.#oidParserCache.set(oid, customParser); + + return customParser; + } + + // This verbose switch statement is here because `getTypeParser` is missing a signature + // where "format" is a union of 'text' and 'binary' and undefined, so TypeScript can't + // infer the correct return type. + switch (format) { + case 'text': + return this.#lib.types.getTypeParser(oid, format); + case 'binary': + return this.#lib.types.getTypeParser(oid, format); + default: + return this.#lib.types.getTypeParser(oid); + } + } + + #getCustomTypeParser(oid: TypeId, format?: TypeFormat): TypeParser | null { + const typeData = this.#oidMap.get(oid); + + if (!typeData) { + return null; + } + + if (typeData.type === 'range-array') { + return this.#buildArrayParser(this.getTypeParser(typeData.rangeOid!, format)); + } + + if (typeData.type === 'array') { + return this.#buildArrayParser(this.getTypeParser(typeData.baseOid!, format)); + } + + const parser = this.dialect.getParserForDatabaseDataType(typeData.typeName); + + return parser ?? null; + } + + /** + * Refreshes the local registry of Custom Types (e.g. enum) OIDs + */ + async refreshDynamicOids() { + await this.#refreshOidMap(this.sequelize); + } +} diff --git a/packages/postgres/src/dialect.test.ts b/packages/postgres/src/dialect.test.ts new file mode 100644 index 000000000000..da89570599f5 --- /dev/null +++ b/packages/postgres/src/dialect.test.ts @@ -0,0 +1,32 @@ +import { Sequelize } from '@sequelize/core'; +import type { PostgresConnectionOptions } from '@sequelize/postgres'; +import { PostgresDialect } from '@sequelize/postgres'; +import { expect } from 'chai'; + +describe('PostgresDialect#parseConnectionUrl', () => { + const dialect = new Sequelize({ dialect: PostgresDialect }).dialect; + + it('parses connection URL', () => { + const options: PostgresConnectionOptions = dialect.parseConnectionUrl( + 'postgres://user:password@localhost:1234/dbname?client_encoding=utf8mb4', + ); + + expect(options).to.deep.eq({ + host: 'localhost', + port: 1234, + user: 'user', + password: 'password', + database: 'dbname', + client_encoding: 'utf8mb4', + }); + }); + + it('accepts the postgresql:// scheme', () => { + const options: PostgresConnectionOptions = + dialect.parseConnectionUrl('postgresql://@localhost'); + + expect(options).to.deep.eq({ + host: 'localhost', + }); + }); +}); diff --git a/packages/postgres/src/dialect.ts b/packages/postgres/src/dialect.ts new file mode 100644 index 000000000000..912f9a79f421 --- /dev/null +++ b/packages/postgres/src/dialect.ts @@ -0,0 +1,248 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import type { + BindCollector, + DialectSupports, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/dialect.js'; +import { parseCommonConnectionUrlOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/connection-options.js'; +import { createSpecifiedOrderedBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import { + BOOLEAN_CONNECTION_OPTION_NAMES, + CONNECTION_OPTION_NAMES, + NUMBER_CONNECTION_OPTION_NAMES, + STRING_CONNECTION_OPTION_NAMES, +} from './_internal/connection-options.js'; +import { registerPostgresDbDataTypeParsers } from './_internal/data-types-db.js'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { PgModule, PostgresConnectionOptions } from './connection-manager.js'; +import { PostgresConnectionManager } from './connection-manager.js'; +import { PostgresQueryGenerator } from './query-generator.js'; +import { PostgresQueryInterface } from './query-interface.js'; +import { PostgresQuery } from './query.js'; + +export interface PostgresDialectOptions { + /** + * Defines whether the native library shall be used or not. + * If true, you need to have `pg-native` installed. + * + * @default false + */ + native?: boolean; + + /** + * The pg library to use. + * If not provided, the pg npm library will be used. + * Must be compatible with the pg npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + pgModule?: PgModule; + + /** + * 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! + * + * @default true + */ + standardConformingStrings?: boolean; + + /** + * The PostgreSql `client_min_messages` session parameter. + * Set explicitly to `false` to not override the database's default. + * Redshift does not support this parameter, it is important to set this option + * to `false` when connecting to Redshift. + * + * @default 'warning' + */ + clientMinMessages?: string | boolean; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + clientMinMessages: undefined, + native: undefined, + pgModule: undefined, + standardConformingStrings: undefined, +}); + +export class PostgresDialect extends AbstractDialect< + PostgresDialectOptions, + PostgresConnectionOptions +> { + static readonly supports: DialectSupports = AbstractDialect.extendSupport({ + 'DEFAULT VALUES': true, + EXCEPTION: true, + 'ON DUPLICATE KEY': false, + 'ORDER NULLS': true, + returnValues: 'returning', + bulkDefault: true, + schemas: true, + multiDatabases: true, + lock: true, + lockOf: true, + lockKey: true, + lockOuterJoinFailure: true, + skipLocked: true, + forShare: 'FOR SHARE', + constraints: { + deferrable: true, + removeOptions: { cascade: true, ifExists: true }, + }, + index: { + concurrently: true, + using: 2, + where: true, + functionBased: true, + operator: true, + include: true, + }, + inserts: { + onConflictDoNothing: ' ON CONFLICT DO NOTHING', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET', + conflictFields: true, + onConflictWhere: true, + }, + dataTypes: { + ARRAY: true, + RANGE: true, + GEOMETRY: true, + GEOGRAPHY: true, + JSON: true, + JSONB: true, + HSTORE: true, + TSVECTOR: true, + CITEXT: true, + DATETIME: { infinity: true }, + DATEONLY: { infinity: true }, + FLOAT: { NaN: true, infinity: true }, + REAL: { NaN: true, infinity: true }, + DOUBLE: { NaN: true, infinity: true }, + DECIMAL: { unconstrained: true, NaN: true, infinity: true }, + CIDR: true, + MACADDR: true, + MACADDR8: true, + INET: true, + }, + jsonOperations: true, + jsonExtraction: { + unquoted: true, + quoted: true, + }, + REGEXP: true, + IREGEXP: true, + searchPath: true, + escapeStringConstants: true, + globalTimeZoneConfig: true, + uuidV1Generation: true, + uuidV4Generation: true, + dropTable: { + cascade: true, + }, + truncate: { + cascade: true, + restartIdentity: true, + }, + removeColumn: { + cascade: true, + ifExists: true, + }, + renameTable: { + changeSchemaAndTable: false, + }, + createSchema: { + authorization: true, + ifNotExists: true, + }, + dropSchema: { + cascade: true, + ifExists: true, + }, + startTransaction: { + readOnly: true, + }, + delete: { + limit: false, + }, + }); + + readonly connectionManager: PostgresConnectionManager; + readonly queryGenerator: PostgresQueryGenerator; + readonly queryInterface: PostgresQueryInterface; + readonly Query = PostgresQuery; + + constructor(sequelize: Sequelize, options: PostgresDialectOptions) { + super({ + sequelize, + dataTypeOverrides: DataTypes, + options, + name: 'postgres', + minimumDatabaseVersion: '11.0.0', + identifierDelimiter: '"', + dataTypesDocumentationUrl: 'https://www.postgresql.org/docs/current/datatype.html', + }); + + this.connectionManager = new PostgresConnectionManager(this); + this.queryGenerator = new PostgresQueryGenerator(this); + this.queryInterface = new PostgresQueryInterface(this); + + registerPostgresDbDataTypeParsers(this); + } + + createBindCollector(): BindCollector { + return createSpecifiedOrderedBindCollector(); + } + + escapeBuffer(buffer: Buffer): string { + const hex = buffer.toString('hex'); + + // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html + return `'\\x${hex}'`; + } + + escapeString(value: string): string { + // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS + // http://stackoverflow.com/q/603572/130598 + value = value + .replaceAll("'", "''") + // null character is not allowed in Postgres + .replaceAll('\0', '\\0'); + + return `'${value}'`; + } + + canBackslashEscape() { + // postgres can use \ to escape if one of these is true: + // - standard_conforming_strings is off + // - the string is prefixed with E (out of scope for this method) + return this.options.standardConformingStrings === false; + } + + getDefaultSchema() { + return 'public'; + } + + parseConnectionUrl(url: string): PostgresConnectionOptions { + return parseCommonConnectionUrlOptions({ + url, + allowedProtocols: ['postgres', 'postgresql'], + hostname: 'host', + port: 'port', + pathname: 'database', + username: 'user', + password: 'password', + stringSearchParams: STRING_CONNECTION_OPTION_NAMES, + booleanSearchParams: BOOLEAN_CONNECTION_OPTION_NAMES, + numberSearchParams: NUMBER_CONNECTION_OPTION_NAMES, + }); + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions() { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/postgres/src/index.mjs b/packages/postgres/src/index.mjs new file mode 100644 index 000000000000..0405dffcee1b --- /dev/null +++ b/packages/postgres/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const PostgresConnectionManager = Pkg.PostgresConnectionManager; +export const PostgresDialect = Pkg.PostgresDialect; +export const PostgresQueryGenerator = Pkg.PostgresQueryGenerator; +export const PostgresQueryInterface = Pkg.PostgresQueryInterface; +export const PostgresQuery = Pkg.PostgresQuery; diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/postgres/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/postgres/src/query-generator-typescript.internal.ts b/packages/postgres/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..7ae14143408c --- /dev/null +++ b/packages/postgres/src/query-generator-typescript.internal.ts @@ -0,0 +1,290 @@ +import type { + CreateDatabaseQueryOptions, + Expression, + ListDatabasesQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + RemoveIndexQueryOptions, + RenameTableQueryOptions, + ShowConstraintsQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator } from '@sequelize/core'; +import type { EscapeOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import semver from 'semver'; +import type { PostgresDialect } from './dialect.js'; +import { PostgresQueryGeneratorInternal } from './query-generator.internal.js'; + +const CREATE_DATABASE_QUERY_SUPPORTED_OPTIONS = new Set([ + 'collate', + 'ctype', + 'encoding', + 'template', +]); + +/** + * Temporary class to ease the TypeScript migration + */ +export class PostgresQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: PostgresQueryGeneratorInternal; + + constructor( + dialect: PostgresDialect, + internals: PostgresQueryGeneratorInternal = new PostgresQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + this.#internals = internals; + } + + listDatabasesQuery(options?: ListDatabasesQueryOptions) { + let databasesToSkip = this.#internals.getTechnicalDatabaseNames(); + if (options && Array.isArray(options?.skip)) { + databasesToSkip = [...databasesToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT datname AS "name" FROM pg_database', + `WHERE datistemplate = false AND datname NOT IN (${databasesToSkip.map(database => this.escape(database)).join(', ')})`, + ]); + } + + createDatabaseQuery(database: string, options?: CreateDatabaseQueryOptions) { + if (options) { + rejectInvalidOptions( + 'createDatabaseQuery', + this.dialect, + CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_DATABASE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + return joinSQLFragments([ + `CREATE DATABASE ${this.quoteIdentifier(database)}`, + options?.encoding ? `ENCODING = ${this.escape(options.encoding)}` : '', + options?.collate ? `LC_COLLATE = ${this.escape(options.collate)}` : '', + options?.ctype ? `LC_CTYPE = ${this.escape(options.ctype)}` : '', + options?.template ? `TEMPLATE = ${this.escape(options.template)}` : '', + ]); + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + const schemasToSkip = ['public', ...this.#internals.getTechnicalSchemaNames()]; + + if (options && Array.isArray(options?.skip)) { + schemasToSkip.push(...options.skip); + } + + return joinSQLFragments([ + `SELECT schema_name AS "schema" FROM information_schema.schemata`, + `WHERE schema_name !~ E'^pg_' AND schema_name NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT', + 'pk.constraint_type as "Constraint",', + 'c.column_name as "Field",', + 'c.column_default as "Default",', + 'c.is_nullable as "Null",', + `(CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as "Type",`, + '(SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special",', + '(SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment"', + 'FROM information_schema.columns c', + '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', + '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`, + 'ON pk.table_schema=c.table_schema', + 'AND pk.table_name=c.table_name', + 'AND pk.column_name=c.column_name', + `WHERE c.table_name = ${this.escape(table.tableName)}`, + `AND c.table_schema = ${this.escape(table.schema)}`, + ]); + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT table_name AS "tableName", table_schema AS "schema"', + `FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name != 'spatial_ref_sys'`, + options?.schema + ? `AND table_schema = ${this.escape(options.schema)}` + : `AND table_schema !~ E'^pg_' AND table_schema NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY table_schema, table_name', + ]); + } + + renameTableQuery( + beforeTableName: TableOrModel, + afterTableName: TableOrModel, + options?: RenameTableQueryOptions, + ): string { + const beforeTable = this.extractTableDetails(beforeTableName); + const afterTable = this.extractTableDetails(afterTableName); + + if (beforeTable.schema !== afterTable.schema) { + if (!options?.changeSchema) { + throw new Error( + 'To move a table between schemas, you must set `options.changeSchema` to true.', + ); + } + + if (beforeTable.tableName !== afterTable.tableName) { + throw new Error( + `Renaming a table and moving it to a different schema is not supported by ${this.dialect.name}.`, + ); + } + + return `ALTER TABLE ${this.quoteTable(beforeTableName)} SET SCHEMA ${this.quoteIdentifier(afterTable.schema)}`; + } + + return `ALTER TABLE ${this.quoteTable(beforeTableName)} RENAME TO ${this.quoteIdentifier(afterTable.tableName)}`; + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + return joinSQLFragments([ + `TRUNCATE ${this.quoteTable(tableName)}`, + options?.restartIdentity ? 'RESTART IDENTITY' : '', + options?.cascade ? 'CASCADE' : '', + ]); + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + const table = this.extractTableDetails(tableName); + + // Postgres converts camelCased alias to lowercase unless quoted + return joinSQLFragments([ + 'SELECT c.constraint_catalog AS "constraintCatalog",', + 'c.constraint_schema AS "constraintSchema",', + 'c.constraint_name AS "constraintName",', + 'c.constraint_type AS "constraintType",', + 'c.table_catalog AS "tableCatalog",', + 'c.table_schema AS "tableSchema",', + 'c.table_name AS "tableName",', + 'kcu.column_name AS "columnNames",', + 'ccu.table_schema AS "referencedTableSchema",', + 'ccu.table_name AS "referencedTableName",', + 'ccu.column_name AS "referencedColumnNames",', + 'r.delete_rule AS "deleteAction",', + 'r.update_rule AS "updateAction",', + 'pg_get_expr(pgc.conbin, pgc.conrelid) AS "definition",', + 'c.is_deferrable AS "isDeferrable",', + 'c.initially_deferred AS "initiallyDeferred"', + 'FROM INFORMATION_SCHEMA.table_constraints c', + 'LEFT JOIN INFORMATION_SCHEMA.referential_constraints r ON c.constraint_catalog = r.constraint_catalog AND c.constraint_schema = r.constraint_schema AND c.constraint_name = r.constraint_name', + 'LEFT JOIN INFORMATION_SCHEMA.key_column_usage kcu ON c.constraint_catalog = kcu.constraint_catalog AND c.constraint_schema = kcu.constraint_schema AND c.constraint_name = kcu.constraint_name', + 'LEFT JOIN information_schema.constraint_column_usage AS ccu ON r.constraint_catalog = ccu.constraint_catalog AND r.constraint_schema = ccu.constraint_schema AND r.constraint_name = ccu.constraint_name', + 'LEFT JOIN pg_constraint pgc ON c.constraint_name = pgc.conname AND c.table_schema = (SELECT nspname FROM pg_namespace WHERE oid = pgc.connamespace) AND c.table_name = pgc.conrelid::regclass::text', + `WHERE c.table_name = ${this.escape(table.tableName)}`, + `AND c.table_schema = ${this.escape(table.schema)}`, + options?.columnName ? `AND kcu.column_name = ${this.escape(options.columnName)}` : '', + options?.constraintName + ? `AND c.constraint_name = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.constraint_type = ${this.escape(options.constraintType)}` + : '', + 'ORDER BY c.constraint_name, kcu.ordinal_position, ccu.column_name', + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + const table = this.extractTableDetails(tableName); + + // TODO [>=6]: refactor the query to use pg_indexes + return joinSQLFragments([ + 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey[:ix.indnkeyatts-1] AS index_fields,', + 'ix.indkey[ix.indnkeyatts:] AS include_fields, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names,', + 'pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a , pg_namespace s', + 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND', + `t.relkind = 'r' and t.relname = ${this.escape(table.tableName)}`, + `AND s.oid = t.relnamespace AND s.nspname = ${this.escape(table.schema)}`, + 'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey, ix.indnkeyatts ORDER BY i.relname;', + ]); + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options?.cascade && options?.concurrently) { + throw new Error( + `Cannot specify both concurrently and cascade options in removeIndexQuery for ${this.dialect.name} dialect`, + ); + } + + let indexName; + const table = this.extractTableDetails(tableName); + if (Array.isArray(indexNameOrAttributes)) { + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return joinSQLFragments([ + 'DROP INDEX', + options?.concurrently ? 'CONCURRENTLY' : '', + options?.ifExists ? 'IF EXISTS' : '', + `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(indexName)}`, + options?.cascade ? 'CASCADE' : '', + ]); + } + + jsonPathExtractionQuery( + sqlExpression: string, + path: ReadonlyArray, + unquote: boolean, + ): string { + const operator = path.length === 1 ? (unquote ? '->>' : '->') : unquote ? '#>>' : '#>'; + + const pathSql = + path.length === 1 + ? // when accessing an array index with ->, the index must be a number + // when accessing an object key with ->, the key must be a string + this.escape(path[0]) + : // when accessing with #>, the path is always an array of strings + this.escape(path.map(value => String(value))); + + return sqlExpression + operator + pathSql; + } + + formatUnquoteJson(arg: Expression, options?: EscapeOptions) { + return `${this.escape(arg, options)}#>>ARRAY[]::TEXT[]`; + } + + getUuidV1FunctionCall(): string { + return 'uuid_generate_v1()'; + } + + getUuidV4FunctionCall(): string { + const dialectVersion = this.sequelize.getDatabaseVersion(); + + if (semver.lt(dialectVersion, '13.0.0')) { + return 'uuid_generate_v4()'; + } + + // uuid_generate_v4 requires the uuid-ossp extension, which is not installed by default. + // This has broader support, as it is part of the core Postgres distribution, but is only available since Postgres 13. + return 'gen_random_uuid()'; + } + + versionQuery() { + return 'SHOW SERVER_VERSION'; + } +} diff --git a/packages/postgres/src/query-generator.d.ts b/packages/postgres/src/query-generator.d.ts new file mode 100644 index 000000000000..bad298106d16 --- /dev/null +++ b/packages/postgres/src/query-generator.d.ts @@ -0,0 +1,10 @@ +import type { TableName } from '@sequelize/core'; +import { PostgresQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +type PgEnumNameOptions = { + schema?: boolean; +}; + +export class PostgresQueryGenerator extends PostgresQueryGeneratorTypeScript { + pgEnumName(tableName: TableName, columnName: string, options?: PgEnumNameOptions): string; +} diff --git a/packages/postgres/src/query-generator.internal.ts b/packages/postgres/src/query-generator.internal.ts new file mode 100644 index 000000000000..137156049876 --- /dev/null +++ b/packages/postgres/src/query-generator.internal.ts @@ -0,0 +1,36 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import type { PostgresDialect } from './dialect.js'; + +const TECHNICAL_DATABASE_NAMES = Object.freeze(['postgres']); +const TECHNICAL_SCHEMA_NAMES = Object.freeze([ + 'information_schema', + 'tiger', + 'tiger_data', + 'topology', +]); + +export class PostgresQueryGeneratorInternal< + Dialect extends PostgresDialect = PostgresDialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalDatabaseNames() { + return TECHNICAL_DATABASE_NAMES; + } + + getTechnicalSchemaNames() { + return TECHNICAL_SCHEMA_NAMES; + } + + addLimitAndOffset(options: AddLimitOffsetOptions): string { + let fragment = ''; + if (options.limit != null) { + fragment += ` LIMIT ${this.queryGenerator.escape(options.limit, options)}`; + } + + if (options.offset) { + fragment += ` OFFSET ${this.queryGenerator.escape(options.offset, options)}`; + } + + return fragment; + } +} diff --git a/packages/postgres/src/query-generator.js b/packages/postgres/src/query-generator.js new file mode 100644 index 000000000000..ce15878ffb26 --- /dev/null +++ b/packages/postgres/src/query-generator.js @@ -0,0 +1,657 @@ +'use strict'; + +import { DataTypes } from '@sequelize/core'; +import { CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { quoteIdentifier } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dialect.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import each from 'lodash/each'; +import isEmpty from 'lodash/isEmpty'; +import isPlainObject from 'lodash/isPlainObject'; +import map from 'lodash/map'; +import reduce from 'lodash/reduce'; +import { ENUM } from './_internal/data-types-overrides.js'; +import { PostgresQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; +import { PostgresQueryGeneratorInternal } from './query-generator.internal.js'; + +/** + * list of reserved words in PostgreSQL 10 + * source: https://www.postgresql.org/docs/10/static/sql-keywords-appendix.html + * + * @private + */ +const POSTGRES_RESERVED_WORDS = + 'all,analyse,analyze,and,any,array,as,asc,asymmetric,authorization,binary,both,case,cast,check,collate,collation,column,concurrently,constraint,create,cross,current_catalog,current_date,current_role,current_schema,current_time,current_timestamp,current_user,default,deferrable,desc,distinct,do,else,end,except,false,fetch,for,foreign,freeze,from,full,grant,group,having,ilike,in,initially,inner,intersect,into,is,isnull,join,lateral,leading,left,like,limit,localtime,localtimestamp,natural,not,notnull,null,offset,on,only,or,order,outer,overlaps,placing,primary,references,returning,right,select,session_user,similar,some,symmetric,table,tablesample,then,to,trailing,true,union,unique,user,using,variadic,verbose,when,where,window,with'.split( + ',', + ); + +const CREATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set(['comment', 'uniqueKeys']); + +export class PostgresQueryGenerator extends PostgresQueryGeneratorTypeScript { + #internals; + + constructor(dialect, internals = new PostgresQueryGeneratorInternal(dialect)) { + super(dialect, internals); + + this.#internals = internals; + } + + setSearchPath(searchPath) { + return `SET search_path to ${searchPath};`; + } + + createTableQuery(tableName, attributes, options) { + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + options = { ...options }; + + const attrStr = []; + let comments = ''; + let columnComments = ''; + + const quotedTable = this.quoteTable(tableName); + + if (options.comment && typeof options.comment === 'string') { + comments += `; COMMENT ON TABLE ${quotedTable} IS ${this.escape(options.comment)}`; + } + + for (const attr in attributes) { + const quotedAttr = this.quoteIdentifier(attr); + const i = attributes[attr].indexOf('COMMENT '); + if (i !== -1) { + // Move comment to a separate query + const escapedCommentText = this.escape(attributes[attr].slice(Math.max(0, i + 8))); + columnComments += `; COMMENT ON COLUMN ${quotedTable}.${quotedAttr} IS ${escapedCommentText}`; + attributes[attr] = attributes[attr].slice(0, Math.max(0, i)); + } + + const dataType = this.dataTypeMapping(tableName, attr, attributes[attr]); + attrStr.push(`${quotedAttr} ${dataType}`); + } + + let attributesClause = attrStr.join(', '); + + if (options.uniqueKeys) { + each(options.uniqueKeys, (index, indexName) => { + if (typeof indexName !== 'string') { + indexName = generateIndexName(tableName, index); + } + + attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${index.fields + .map(field => this.quoteIdentifier(field)) + .join(', ')})`; + }); + } + + const pks = reduce( + attributes, + (acc, attribute, key) => { + if (attribute.includes('PRIMARY KEY')) { + acc.push(this.quoteIdentifier(key)); + } + + return acc; + }, + [], + ).join(', '); + + if (pks.length > 0) { + attributesClause += `, PRIMARY KEY (${pks})`; + } + + return `CREATE TABLE IF NOT EXISTS ${quotedTable} (${attributesClause})${comments}${columnComments};`; + } + + addColumnQuery(table, key, attribute, options) { + options ||= {}; + + 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(table); + const ifNotExists = options.ifNotExists ? ' IF NOT EXISTS' : ''; + + let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${ifNotExists} ${quotedKey} ${definition};`; + + if (dataType instanceof DataTypes.ENUM) { + query = this.pgEnum(table, key, dataType) + query; + } else if ( + dataType instanceof DataTypes.ARRAY && + dataType.options.type instanceof DataTypes.ENUM + ) { + query = this.pgEnum(table, key, dataType.options.type) + query; + } + + return query; + } + + changeColumnQuery(tableName, attributes) { + const query = subQuery => `ALTER TABLE ${this.quoteTable(tableName)} ALTER COLUMN ${subQuery};`; + const sql = []; + for (const attributeName in attributes) { + let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]); + let attrSql = ''; + + if (definition.includes('NOT NULL')) { + attrSql += query(`${this.quoteIdentifier(attributeName)} SET NOT NULL`); + + definition = definition.replace('NOT NULL', '').trim(); + } else if (!definition.includes('REFERENCES')) { + attrSql += query(`${this.quoteIdentifier(attributeName)} DROP NOT NULL`); + } + + if (definition.includes('DEFAULT')) { + attrSql += query( + `${this.quoteIdentifier(attributeName)} SET DEFAULT ${definition.match(/DEFAULT ([^;]+)/)[1]}`, + ); + + definition = definition.replace(/(DEFAULT[^;]+)/, '').trim(); + } else if (!definition.includes('REFERENCES')) { + attrSql += query(`${this.quoteIdentifier(attributeName)} DROP DEFAULT`); + } + + if (attributes[attributeName].startsWith('ENUM(')) { + attrSql += this.pgEnum(tableName, attributeName, attributes[attributeName]); + definition = definition.replace( + /^ENUM\(.+\)/, + this.pgEnumName(tableName, attributeName, { schema: false }), + ); + definition += ` USING (${this.quoteIdentifier(attributeName)}::${this.pgEnumName(tableName, attributeName)})`; + } + + if (/UNIQUE;*$/.test(definition)) { + definition = definition.replace(/UNIQUE;*$/, ''); + attrSql += query(`ADD UNIQUE (${this.quoteIdentifier(attributeName)})`).replace( + 'ALTER COLUMN', + '', + ); + } + + if (definition.includes('REFERENCES')) { + definition = definition.replace(/.+?(?=REFERENCES)/, ''); + attrSql += query( + `ADD FOREIGN KEY (${this.quoteIdentifier(attributeName)}) ${definition}`, + ).replace('ALTER COLUMN', ''); + } else { + attrSql += query(`${this.quoteIdentifier(attributeName)} TYPE ${definition}`); + } + + sql.push(attrSql); + } + + return sql.join(''); + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const attrString = []; + + for (const attributeName in attributes) { + attrString.push( + `${this.quoteIdentifier(attrBefore)} TO ${this.quoteIdentifier(attributeName)}`, + ); + } + + return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`; + } + + fn(fnName, tableName, parameters, body, returns, language) { + fnName ||= 'testfunc'; + language ||= 'plpgsql'; + returns = returns ? `RETURNS ${returns}` : ''; + parameters ||= ''; + + return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`; + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + let type; + if ( + attribute.type instanceof DataTypes.ENUM || + (attribute.type instanceof DataTypes.ARRAY && attribute.type.type instanceof DataTypes.ENUM) + ) { + const enumType = attribute.type.type || attribute.type; + const values = enumType.options.values; + + if (Array.isArray(values) && values.length > 0) { + type = `ENUM(${values.map(value => this.escape(value)).join(', ')})`; + + if (attribute.type instanceof DataTypes.ARRAY) { + type += '[]'; + } + } else { + throw new Error("Values for ENUM haven't been defined."); + } + } + + if (!type) { + type = attribute.type; + } + + let sql = type.toString(); + + if (attribute.allowNull === false) { + sql += ' NOT NULL'; + } + + if (attribute.autoIncrement) { + if (attribute.autoIncrementIdentity) { + sql += ' GENERATED BY DEFAULT AS IDENTITY'; + } else { + sql += ' SERIAL'; + } + } + + if (defaultValueSchemable(attribute.defaultValue, this.dialect)) { + sql += ` DEFAULT ${this.escape(attribute.defaultValue, { type: attribute.type })}`; + } + + if (attribute.unique === true) { + sql += ' UNIQUE'; + } + + if (attribute.primaryKey) { + sql += ' PRIMARY KEY'; + } + + if (attribute.references) { + let schema; + + if (options.schema) { + schema = options.schema; + } else if ( + (!attribute.references.table || typeof attribute.references.table === 'string') && + options.table && + options.table.schema + ) { + schema = options.table.schema; + } + + const referencesTable = this.extractTableDetails(attribute.references.table, { schema }); + + let referencesKey; + + if (!options.withoutForeignKeyConstraints) { + if (attribute.references.key) { + referencesKey = this.quoteIdentifiers(attribute.references.key); + } else { + referencesKey = this.quoteIdentifier('id'); + } + + sql += ` REFERENCES ${this.quoteTable(referencesTable)} (${referencesKey})`; + + if (attribute.onDelete) { + sql += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + sql += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + + if (attribute.references.deferrable) { + sql += ` ${this.#internals.getDeferrableConstraintSnippet(attribute.references.deferrable)}`; + } + } + } + + if (attribute.comment && typeof attribute.comment === 'string') { + if (options && ['addColumn', 'changeColumn'].includes(options.context)) { + const quotedAttr = this.quoteIdentifier(options.key); + const escapedCommentText = this.escape(attribute.comment); + sql += `; COMMENT ON COLUMN ${this.quoteTable(options.table)}.${quotedAttr} IS ${escapedCommentText}`; + } else { + // for createTable event which does it's own parsing + // TODO: centralize creation of comment statements here + sql += ` COMMENT ${attribute.comment}`; + } + } + + return sql; + } + + attributesToSQL(attributes, options) { + const result = {}; + + for (const key in attributes) { + const attribute = attributes[key]; + result[attribute.field || key] = this.attributeToSQL(attribute, { key, ...options }); + } + + return result; + } + + createTrigger( + tableName, + triggerName, + eventType, + fireOnSpec, + functionName, + functionParams, + optionsArray, + ) { + const decodedEventType = this.decodeTriggerEventType(eventType); + const eventSpec = this.expandTriggerEventSpec(fireOnSpec); + const expandedOptions = this.expandOptions(optionsArray); + 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});`; + } + + dropTrigger(tableName, triggerName) { + return `DROP TRIGGER ${this.quoteIdentifier(triggerName)} ON ${this.quoteTable(tableName)} RESTRICT;`; + } + + renameTrigger(tableName, oldTriggerName, newTriggerName) { + return `ALTER TRIGGER ${this.quoteIdentifier(oldTriggerName)} ON ${this.quoteTable(tableName)} RENAME TO ${this.quoteIdentifier(newTriggerName)};`; + } + + 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 expandedOptionsArray = this.expandOptions(optionsArray); + + const statement = options && options.force ? 'CREATE OR REPLACE FUNCTION' : 'CREATE FUNCTION'; + + return `${statement} ${functionName}(${paramList}) RETURNS ${returnType} AS $func$ ${variableList} BEGIN ${body} END; $func$ language '${language}'${expandedOptionsArray};`; + } + + 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); + + return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`; + } + + renameFunction(oldFunctionName, params, newFunctionName) { + const paramList = this._expandFunctionParamList(params); + + return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`; + } + + _expandFunctionParamList(params) { + if (params === undefined || !Array.isArray(params)) { + throw new Error( + '_expandFunctionParamList: function parameters array required, including an empty one for no arguments', + ); + } + + const paramList = []; + for (const curParam of params) { + const paramDef = []; + if (curParam.type) { + if (curParam.direction) { + paramDef.push(curParam.direction); + } + + if (curParam.name) { + paramDef.push(curParam.name); + } + + paramDef.push(curParam.type); + } else { + throw new Error('function or trigger used with a parameter without any type'); + } + + const joined = paramDef.join(' '); + if (joined) { + paramList.push(joined); + } + } + + return paramList.join(', '); + } + + _expandFunctionVariableList(variables) { + if (!Array.isArray(variables)) { + throw new TypeError('_expandFunctionVariableList: function variables must be an array'); + } + + const variableDefinitions = []; + for (const variable of variables) { + if (!variable.name || !variable.type) { + throw new Error('function variable must have a name and type'); + } + + let variableDefinition = `DECLARE ${variable.name} ${variable.type}`; + if (variable.default) { + variableDefinition += ` := ${variable.default}`; + } + + variableDefinition += ';'; + variableDefinitions.push(variableDefinition); + } + + return variableDefinitions.join(' '); + } + + expandOptions(options) { + return options === undefined || isEmpty(options) ? '' : options.join(' '); + } + + decodeTriggerEventType(eventSpecifier) { + const EVENT_DECODER = { + after: 'AFTER', + before: 'BEFORE', + instead_of: 'INSTEAD OF', + after_constraint: 'AFTER', + }; + + if (!EVENT_DECODER[eventSpecifier]) { + throw new Error(`Invalid trigger event specified: ${eventSpecifier}`); + } + + return EVENT_DECODER[eventSpecifier]; + } + + triggerEventTypeIsConstraint(eventSpecifier) { + return eventSpecifier === 'after_constraint' ? 'CONSTRAINT ' : ''; + } + + expandTriggerEventSpec(fireOnSpec) { + if (isEmpty(fireOnSpec)) { + throw new Error('no table change events specified to trigger on'); + } + + return map(fireOnSpec, (fireValue, fireKey) => { + const EVENT_MAP = { + insert: 'INSERT', + update: 'UPDATE', + delete: 'DELETE', + truncate: 'TRUNCATE', + }; + + if (!EVENT_MAP[fireValue]) { + throw new Error(`parseTriggerEventSpec: undefined trigger event ${fireKey}`); + } + + let eventSpec = EVENT_MAP[fireValue]; + if (eventSpec === 'UPDATE' && Array.isArray(fireValue) && fireValue.length > 0) { + eventSpec += ` OF ${fireValue.join(', ')}`; + } + + return eventSpec; + }).join(' OR '); + } + + pgEnumName(tableName, columnName, options = {}) { + const tableDetails = this.extractTableDetails(tableName, options); + + const enumName = `enum_${tableDetails.tableName}_${columnName}`; + if (options.noEscape) { + return enumName; + } + + const escapedEnumName = this.quoteIdentifier(enumName); + + if (options.schema !== false && tableDetails.schema) { + return this.quoteIdentifier(tableDetails.schema) + tableDetails.delimiter + escapedEnumName; + } + + return escapedEnumName; + } + + pgListEnums(tableName, attrName, options) { + let enumName = ''; + const tableDetails = + tableName != null + ? this.extractTableDetails(tableName, options) + : { schema: this.options.schema || this.dialect.getDefaultSchema() }; + + if (tableDetails.tableName && attrName) { + // pgEnumName escapes as an identifier, we want to escape it as a string + enumName = ` AND t.typname=${this.escape(this.pgEnumName(tableDetails.tableName, attrName, { noEscape: true }))}`; + } + + return ( + 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t ' + + 'JOIN pg_enum e ON t.oid = e.enumtypid ' + + 'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' + + `WHERE n.nspname = ${this.escape(tableDetails.schema)}${enumName} GROUP BY 1` + ); + } + + pgEnum(tableName, attr, dataType, options) { + const enumName = this.pgEnumName(tableName, attr, options); + let values; + + if (dataType instanceof ENUM && dataType.options.values) { + values = `ENUM(${dataType.options.values.map(value => this.escape(value)).join(', ')})`; + } else { + values = dataType.toString().match(/^ENUM\(.+\)/)[0]; + } + + let sql = `DO ${this.escape(`BEGIN CREATE TYPE ${enumName} AS ${values}; EXCEPTION WHEN duplicate_object THEN null; END`)};`; + if (Boolean(options) && options.force === true) { + sql = this.pgEnumDrop(tableName, attr) + sql; + } + + return sql; + } + + pgEnumAdd(tableName, attr, value, options) { + const enumName = this.pgEnumName(tableName, attr); + let sql = `ALTER TYPE ${enumName} ADD VALUE IF NOT EXISTS `; + + sql += this.escape(value); + + if (options.before) { + sql += ` BEFORE ${this.escape(options.before)}`; + } else if (options.after) { + sql += ` AFTER ${this.escape(options.after)}`; + } + + return sql; + } + + pgEnumDrop(tableName, attr, enumName) { + enumName ||= this.pgEnumName(tableName, attr); + + return `DROP TYPE IF EXISTS ${enumName}; `; + } + + fromArray(text) { + if (Array.isArray(text)) { + return text; + } + + text = text.replace(/^{/, '').replace(/}$/, ''); + let matches = text.match(/("(?:\\.|[^"\\])*"|[^,]*)(?:\s*,\s*|\s*$)/gi); + + if (matches.length === 0) { + return []; + } + + matches = matches.map(m => + m + .replace(/",$/, '') + .replace(/,$/, '') + .replaceAll(/(^"|"$)/g, ''), + ); + + return matches.slice(0, -1); + } + + dataTypeMapping(tableName, attr, dataType) { + if (dataType.includes('PRIMARY KEY')) { + dataType = dataType.replace('PRIMARY KEY', ''); + } + + if (dataType.includes('SERIAL')) { + if (dataType.includes('BIGINT')) { + dataType = dataType.replace('SERIAL', 'BIGSERIAL'); + dataType = dataType.replace('BIGINT', ''); + } else if (dataType.includes('SMALLINT')) { + dataType = dataType.replace('SERIAL', 'SMALLSERIAL'); + dataType = dataType.replace('SMALLINT', ''); + } else { + dataType = dataType.replace('INTEGER', ''); + } + + dataType = dataType.replace('NOT NULL', ''); + } + + if (dataType.startsWith('ENUM(')) { + dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEnumName(tableName, attr)); + } + + return dataType; + } + + /** + * Quote identifier in sql clause + * + * @param {string} identifier + * @param {boolean} force + * + * @returns {string} + */ + quoteIdentifier(identifier, force) { + const optForceQuote = force || false; + // TODO [>7]: remove "quoteIdentifiers: false" option + const optQuoteIdentifiers = this.options.quoteIdentifiers !== false; + + if ( + optForceQuote === true || + // TODO [>7]: drop this.options.quoteIdentifiers. Always quote identifiers based on these rules + optQuoteIdentifiers !== false || + identifier.includes('.') || + identifier.includes('->') || + POSTGRES_RESERVED_WORDS.includes(identifier.toLowerCase()) + ) { + // In Postgres if tables or attributes are created double-quoted, + // they are also case sensitive. If they contain any uppercase + // characters, they must always be double-quoted. This makes it + // impossible to write queries in portable SQL if tables are created in + // this way. Hence, we strip quotes if we don't want case sensitivity. + return quoteIdentifier(identifier, this.dialect.TICK_CHAR_LEFT, this.dialect.TICK_CHAR_RIGHT); + } + + return identifier; + } +} diff --git a/packages/postgres/src/query-interface-typescript.internal.ts b/packages/postgres/src/query-interface-typescript.internal.ts new file mode 100644 index 000000000000..a977dea57099 --- /dev/null +++ b/packages/postgres/src/query-interface-typescript.internal.ts @@ -0,0 +1,25 @@ +import type { FetchDatabaseVersionOptions } from '@sequelize/core'; +import { AbstractQueryInterface } from '@sequelize/core'; +import { AbstractQueryInterfaceInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-interface-internal.js'; +import type { PostgresDialect } from './dialect.js'; + +export class PostgresQueryInterfaceTypescript< + Dialect extends PostgresDialect = PostgresDialect, +> extends AbstractQueryInterface { + readonly #internalQueryInterface: AbstractQueryInterfaceInternal; + + constructor(dialect: Dialect, internalQueryInterface?: AbstractQueryInterfaceInternal) { + internalQueryInterface ??= new AbstractQueryInterfaceInternal(dialect); + + super(dialect, internalQueryInterface); + this.#internalQueryInterface = internalQueryInterface; + } + + async fetchDatabaseVersion(options?: FetchDatabaseVersionOptions): Promise { + const payload = await this.#internalQueryInterface.fetchDatabaseVersionRaw<{ + server_version: string; + }>(options); + + return payload.server_version; + } +} diff --git a/packages/postgres/src/query-interface.d.ts b/packages/postgres/src/query-interface.d.ts new file mode 100644 index 000000000000..75eb11dfabde --- /dev/null +++ b/packages/postgres/src/query-interface.d.ts @@ -0,0 +1,6 @@ +import type { PostgresDialect } from './dialect.js'; +import { PostgresQueryInterfaceTypescript } from './query-interface-typescript.internal.js'; + +export class PostgresQueryInterface< + Dialect extends PostgresDialect = PostgresDialect, +> extends PostgresQueryInterfaceTypescript {} diff --git a/packages/postgres/src/query-interface.js b/packages/postgres/src/query-interface.js new file mode 100644 index 000000000000..0e49fd869583 --- /dev/null +++ b/packages/postgres/src/query-interface.js @@ -0,0 +1,275 @@ +'use strict'; + +import { DataTypes, QueryTypes } from '@sequelize/core'; +import { PostgresQueryInterfaceTypescript } from './query-interface-typescript.internal.js'; + +/** + * The interface that Sequelize uses to talk with Postgres database + */ +export class PostgresQueryInterface extends PostgresQueryInterfaceTypescript { + /** + * Ensure enum and their values. + * + * @param {string} tableName Name of table to create + * @param {object} attributes Object representing a list of normalized table attributes + * @param {object} [options] + * @param {Model} [model] + * + * @protected + */ + 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.options.type instanceof DataTypes.ENUM) // ARRAY sub type is ENUM + ) { + sql = this.queryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); + promises.push( + this.sequelize.queryRaw(sql, { + ...options, + plain: true, + raw: true, + type: QueryTypes.SELECT, + }), + ); + } + } + + const results = await Promise.all(promises); + promises = []; + let enumIdx = 0; + + // This little function allows us to re-use the same code that prepends or appends new value to enum array + const addEnumValue = ( + field, + value, + relativeValue, + position = 'before', + spliceStart = promises.length, + ) => { + const valueOptions = { ...options }; + valueOptions.before = null; + valueOptions.after = null; + + switch (position) { + case 'after': + valueOptions.after = relativeValue; + break; + case 'before': + default: + valueOptions.before = relativeValue; + break; + } + + promises.splice(spliceStart, 0, () => { + return this.sequelize.queryRaw( + this.queryGenerator.pgEnumAdd(tableName, field, value, valueOptions), + valueOptions, + ); + }); + }; + + for (i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + const type = attribute.type; + const enumType = type instanceof DataTypes.ARRAY ? type.options.type : type; + const field = attribute.field || keys[i]; + + if ( + type instanceof DataTypes.ENUM || + (type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM) // ARRAY sub type is ENUM + ) { + // If the enum type doesn't exist then create it + if (!results[enumIdx]) { + promises.push(() => { + return this.sequelize.queryRaw( + this.queryGenerator.pgEnum(tableName, field, enumType, options), + { ...options, raw: true }, + ); + }); + } else if (Boolean(results[enumIdx]) && Boolean(model)) { + const enumVals = this.queryGenerator.fromArray(results[enumIdx].enum_value); + const vals = enumType.options.values; + + // Going through already existing values allows us to make queries that depend on those values + // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values + // Then we append the rest of new values AFTER the latest already existing value + // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] + // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] + // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] + let lastOldEnumValue; + let rightestPosition = -1; + for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { + const enumVal = enumVals[oldIndex]; + const newIdx = vals.indexOf(enumVal); + lastOldEnumValue = enumVal; + + if (newIdx === -1) { + continue; + } + + const newValuesBefore = vals.slice(0, newIdx); + const promisesLength = promises.length; + // we go in reverse order so we could stop when we meet old value + for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { + if (enumVals.includes(newValuesBefore[reverseIdx])) { + break; + } + + addEnumValue( + field, + newValuesBefore[reverseIdx], + lastOldEnumValue, + 'before', + promisesLength, + ); + } + + // we detect the most 'right' position of old value in new enum array so we can append new values to it + if (newIdx > rightestPosition) { + rightestPosition = newIdx; + } + } + + if (lastOldEnumValue && rightestPosition < vals.length - 1) { + const remainingEnumValues = vals.slice(rightestPosition + 1); + for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { + addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); + } + } + } + + // Continue to the next enum + enumIdx++; + } + } + + const result = await promises.reduce( + async (promise, asyncFunction) => await asyncFunction(await promise), + Promise.resolve(), + ); + + // If ENUM processed, then refresh OIDs + if (promises.length > 0) { + await this.sequelize.dialect.connectionManager.refreshDynamicOids(); + } + + return result; + } + + /** + * 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 ||= {}; + + return this.sequelize.queryRaw( + this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.quoteIdentifier(enumName)), + { ...options, raw: true }, + ); + } + + /** + * Drop all enums from database (Postgres only) + * + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropAllEnums(options) { + options ||= {}; + + const enums = await this.pgListEnums(null, options); + + return await Promise.all( + enums.map(result => + this.sequelize.queryRaw( + this.queryGenerator.pgEnumDrop( + null, + null, + this.queryGenerator.quoteIdentifier(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 ||= {}; + const sql = this.queryGenerator.pgListEnums(tableName); + + return this.sequelize.queryRaw(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 = []; + // TODO: we support receiving the model class instead of getting it from modelManager. More than one model can use the same table. + const model = this.sequelize.models.find(model => + this.queryGenerator.isSameTable(model.table, tableName), + ); + + if (!model) { + // Do nothing when model is not available + return; + } + + const getTableName = + (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + + tableName; + + const attributes = model.modelDefinition.attributes; + + for (const attribute of attributes.values()) { + if (!(attribute.type instanceof DataTypes.ENUM)) { + continue; + } + + const sql = this.queryGenerator.pgEnumDrop(getTableName, attribute.attributeName); + promises.push( + this.sequelize.queryRaw(sql, { + ...options, + raw: true, + supportsSearchPath: false, + }), + ); + } + + await Promise.all(promises); + } +} diff --git a/packages/postgres/src/query.d.ts b/packages/postgres/src/query.d.ts new file mode 100644 index 000000000000..12251124715d --- /dev/null +++ b/packages/postgres/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class PostgresQuery extends AbstractQuery {} diff --git a/packages/postgres/src/query.js b/packages/postgres/src/query.js new file mode 100644 index 000000000000..2ebfc5e4b070 --- /dev/null +++ b/packages/postgres/src/query.js @@ -0,0 +1,435 @@ +'use strict'; + +import { + AbstractQuery, + DatabaseError, + EmptyResultError, + ExclusionConstraintError, + ForeignKeyConstraintError, + QueryTypes, + UniqueConstraintError, + UnknownConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import escapeRegExp from 'lodash/escapeRegExp'; +import forOwn from 'lodash/forOwn'; +import isEmpty from 'lodash/isEmpty'; +import isEqual from 'lodash/isEqual'; +import mapKeys from 'lodash/mapKeys'; +import toPairs from 'lodash/toPairs'; +import zipObject from 'lodash/zipObject'; + +const debug = logger.debugContext('sql:pg'); + +export class PostgresQuery extends AbstractQuery { + async run(sql, parameters, options) { + const { connection } = this; + + if (!isEmpty(this.options.searchPath)) { + sql = this.sequelize.queryGenerator.setSearchPath(this.options.searchPath) + sql; + } + + if (options?.minifyAliases && this.options.includeAliases) { + for (const [alias, original] of toPairs(this.options.includeAliases) + // Sorting to replace the longest aliases first to prevent alias collision + .sort((a, b) => b[1].length - a[1].length)) { + const reg = new RegExp(escapeRegExp(original), 'g'); + + sql = sql.replace(reg, alias); + } + } + + this.sql = sql; + + const query = new Promise((resolve, reject) => { + if (parameters && parameters.length > 0) { + connection.query(sql, parameters, (error, result) => { + error ? reject(error) : resolve(result); + }); + } else { + connection.query(sql, (error, result) => (error ? reject(error) : resolve(result))); + } + }); + + const complete = this._logQuery(sql, debug, parameters); + + let queryResult; + + try { + queryResult = await query; + } catch (error) { + // set the client so that it will be reaped if the connection resets while executing + if ( + error.code === 'ECONNRESET' || + // https://github.com/sequelize/sequelize/pull/14090 + // pg-native throws custom exception or libpq formatted errors + /Unable to set non-blocking to true/i.test(error) || + /SSL SYSCALL error: EOF detected/i.test(error) || + /Local: Authentication failure/i.test(error) || + // https://github.com/sequelize/sequelize/pull/15144 + error.message === 'Query read timeout' + ) { + connection._invalid = true; + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } + + 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 (options?.minifyAliases && this.options.aliasesMapping) { + rows = rows.map(row => + toPairs(row).reduce((acc, [key, value]) => { + const mapping = this.options.aliasesMapping.get(key); + acc[mapping || key] = value; + + return acc; + }, {}), + ); + } + + const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); + const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); + + if (isRelNameQuery) { + return rows.map(row => ({ + name: row.relname, + tableName: row.relname.split('_')[0], + })); + } + + if (isTableNameQuery) { + return rows.map(row => Object.values(row)); + } + + 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) { + let attributes; + if (/include \(([^]*)\)/gi.test(row.definition)) { + attributes = /on .*? (?:using .*?\s)?\(([^]*)\) include \(([^]*)\)/gi + .exec(row.definition)[1] + .split(','); + } else { + 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.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.index_fields + .map((indKey, index) => { + field = columns[indKey]; + // for functional indices indKey = 0 + if (!field) { + return null; + } + + attribute = attributes[index]; + + return { + attribute: field, + collate: /COLLATE "(.*?)"/.test(attribute) + ? /COLLATE "(.*?)"/.exec(attribute)[1] + : undefined, + order: attribute.includes('DESC') + ? 'DESC' + : attribute.includes('ASC') + ? 'ASC' + : undefined, + length: undefined, + }; + }) + .filter(n => n !== null); + + row.includes = row.include_fields + .map(indKey => { + field = columns[indKey]; + // for functional indices indKey = 0 + if (!field) { + return null; + } + + return field; + }) + .filter(n => n !== null); + delete row.columns; + delete row.definition; + delete row.index_fields; + delete row.include_fields; + } + + return rows; + } + + if (this.isSelectQuery()) { + let result = rows; + // Postgres will treat tables as case-insensitive, so fix the case + // of the returned values to match attributes + // TODO [>7]: remove this.sequelize.options.quoteIdentifiers === false + if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { + const attrsMap = Object.create(null); + + for (const attrName of this.model.modelDefinition.attributes.keys()) { + attrsMap[attrName.toLowerCase()] = attrName; + } + + 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.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.replaceAll("'", ''); + + 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.isShowOrDescribeQuery()) { + return rows; + } + + if (QueryTypes.BULKUPDATE === this.options.type) { + if (!this.options.returning) { + return Number.parseInt(rowCount, 10); + } + + return this.handleSelectQuery(rows); + } + + if (this.isDeleteQuery()) { + return Number.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() && !this.isUpsertQuery() && rowCount === 0) { + throw new EmptyResultError(); + } + + if (Array.isArray(rows) && rows[0]) { + for (const attributeOrColumnName of Object.keys(rows[0])) { + const modelDefinition = this.model.modelDefinition; + const attribute = modelDefinition.columns.get(attributeOrColumnName); + const updatedValue = this._parseDatabaseValue( + rows[0][attributeOrColumnName], + attribute?.type, + ); + + this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, { + raw: true, + comesFromDatabase: true, + }); + } + } + } + + if (this.isUpsertQuery()) { + return [this.instance, null]; + } + + return [ + this.instance || (rows && ((this.options.plain && rows[0]) || rows)) || undefined, + rowCount, + ]; + } + + if (this.isShowConstraintsQuery()) { + return rows; + } + + if (this.isRawQuery()) { + return [rows, queryResult]; + } + + return rows; + } + + formatError(err) { + let match; + let table; + let index; + let fields; + let errors; + let message; + + const code = err.code || err.sqlState; + const errMessage = err.message || err.messagePrimary; + const errDetail = err.detail || err.messageDetail; + + switch (code) { + case '23503': + index = errMessage.match(/violates foreign key constraint "(.+?)"/); + index = index ? index[1] : undefined; + table = errMessage.match(/on table "(.+?)"/); + table = table ? table[1] : undefined; + + return new ForeignKeyConstraintError({ + message: errMessage, + fields: null, + index, + table, + cause: err, + }); + case '23505': + // there are multiple different formats of error messages for this error code + // this regex should check at least two + if (errDetail && (match = errDetail.replaceAll('"', '').match(/Key \((.*?)\)=\((.*?)\)/))) { + fields = zipObject(match[1].split(', '), match[2].split(', ')); + errors = []; + message = 'Validation error'; + + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + if (this.model) { + for (const index of this.model.getIndexes()) { + if (index.unique && isEqual(index.fields, Object.keys(fields)) && index.msg) { + message = index.msg; + break; + } + } + } + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + return new UniqueConstraintError({ + message: errMessage, + cause: err, + }); + + case '23P01': + match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/); + + if (match) { + fields = zipObject(match[1].split(', '), match[2].split(', ')); + } + + message = 'Exclusion constraint error'; + + return new ExclusionConstraintError({ + message, + constraint: err.constraint, + fields, + table: err.table, + cause: err, + }); + + case '42704': + if (err.sql && /(constraint|index)/gi.test(err.sql)) { + message = 'Unknown constraint error'; + index = errMessage.match(/(?:constraint|index) "(.+?)"/i); + index = index ? index[1] : undefined; + table = errMessage.match(/relation "(.+?)"/i); + table = table ? table[1] : undefined; + + throw new UnknownConstraintError({ + message, + constraint: index, + fields, + table, + cause: err, + }); + } + + // falls through + default: + return new DatabaseError(err); + } + } + + getInsertIdField() { + return 'id'; + } +} diff --git a/packages/postgres/tsconfig.json b/packages/postgres/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/postgres/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/postgres/typedoc.json b/packages/postgres/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/postgres/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/snowflake/.eslintrc.js b/packages/snowflake/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/snowflake/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/snowflake/CHANGELOG.md b/packages/snowflake/CHANGELOG.md new file mode 100644 index 000000000000..704b8257a1d7 --- /dev/null +++ b/packages/snowflake/CHANGELOG.md @@ -0,0 +1,67 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +**Note:** Version bump only for package @sequelize/snowflake + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/snowflake + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/snowflake + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +### Bug Fixes + +- **snowflake:** automatically fetch last inserted row ID when using AUTOINCREMENT pk ([#17626](https://github.com/sequelize/sequelize/issues/17626)) ([d2e3b6e](https://github.com/sequelize/sequelize/commit/d2e3b6e44f9ed3d85c67f07e76e02c59dc76177a)) + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- **snowflake:** update snowflake to v1.14.0 ([#17526](https://github.com/sequelize/sequelize/issues/17526)) ([41ae5c3](https://github.com/sequelize/sequelize/commit/41ae5c3c8dbf42d15c351fda428a7cb5e59e151c)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/snowflake + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +### Bug Fixes + +- **snowflake:** add proxy connection options ([#17309](https://github.com/sequelize/sequelize/issues/17309)) ([51b781e](https://github.com/sequelize/sequelize/commit/51b781e4028f4eda5c4221d94cf4c9141055a762)) + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Bug Fixes + +- parse the `url` option based on the dialect ([#17252](https://github.com/sequelize/sequelize/issues/17252)) ([f05281c](https://github.com/sequelize/sequelize/commit/f05281cd406cba7d14c8770d64261ef6b859d143)) + +### Features + +- re-add the ability to override the connector library ([#17219](https://github.com/sequelize/sequelize/issues/17219)) ([b3c3362](https://github.com/sequelize/sequelize/commit/b3c3362aeca7ce50d0bdb657c6db25f2418dc687)) +- type options per dialect, add "url" option, remove alternative Sequelize constructor signatures ([#17222](https://github.com/sequelize/sequelize/issues/17222)) ([b605bb3](https://github.com/sequelize/sequelize/commit/b605bb372b1500a75daa46bb4c4ae6f4912094a1)) + +### BREAKING CHANGES + +- `db2`, `ibmi`, `snowflake` and `sqlite` do not accept the `url` option anymore +- The sequelize constructor only accepts a single parameter: the option bag. All other signatures have been removed. +- Setting the sequelize option to a string representing a URL has been replaced with the `"url"` option. +- The `dialectOptions` option has been removed. All options that were previously in that object can now be set at the root of the option bag, like all other options. +- All dialect-specific options changed. This includes at least some credential options that changed. +- Which dialect-specific option can be used is allow-listed to ensure they do not break Sequelize +- The sequelize pool is not on the connection manager anymore. It is now directly on the sequelize instance and can be accessed via `sequelize.pool` +- The `sequelize.config` field has been removed. Everything related to connecting to the database has been normalized to `sequelize.options.replication.write` (always present) and `sequelize.options.replication.read` (only present if read-replication is enabled) +- `sequelize.options` is now fully frozen. It is no longer possible to modify the Sequelize options after the instance has been created. +- `sequelize.options` is a normalized list of option. If you wish to access the options that were used to create the sequelize instance, use `sequelize.rawOptions` +- The default sqlite database is not `':memory:'` anymore, but `sequelize.sqlite` in your current working directory. +- Setting the sqlite database to a temporary database like `':memory:'` or `''` requires configuring the pool to behave like a singleton, and disallowed read replication +- The `match` option is no longer supported by `sequelize.sync`. If you made use of this feature, let us know so we can design a better alternative. +- The `dialectModulePath` has been fully removed to improve compatibility with bundlers. +- The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule` diff --git a/packages/snowflake/package.json b/packages/snowflake/package.json new file mode 100644 index 000000000000..63c7534ca555 --- /dev/null +++ b/packages/snowflake/package.json @@ -0,0 +1,44 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "Snowflake Connector for Sequelize", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/snowflake", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs snowflake", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "lodash": "^4.17.21", + "snowflake-sdk": "^2.1.0" + } +} diff --git a/packages/snowflake/src/_internal/data-types-overrides.ts b/packages/snowflake/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..19808726cb6f --- /dev/null +++ b/packages/snowflake/src/_internal/data-types-overrides.ts @@ -0,0 +1,97 @@ +import type { AbstractDialect } from '@sequelize/core'; +import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import maxBy from 'lodash/maxBy.js'; + +export class DATE extends BaseTypes.DATE { + toSql() { + return `TIMESTAMP${this.options.precision != null ? `(${this.options.precision})` : ''}`; + } + + toBindableValue(date: AcceptedDate) { + date = this._applyTimezone(date); + + return date.format('YYYY-MM-DD HH:mm:ss.SSS'); + } +} + +export class UUID extends BaseTypes.UUID { + toSql() { + // https://community.snowflake.com/s/question/0D50Z00009LH2fl/what-is-the-best-way-to-store-uuids + return 'VARCHAR(36)'; + } +} + +export class ENUM extends BaseTypes.ENUM { + toSql() { + const minLength = maxBy(this.options.values, value => value.length)?.length ?? 0; + + // db2 does not have an ENUM type, we use VARCHAR instead. + return `VARCHAR(${Math.max(minLength, 255)})`; + } +} + +export class TEXT extends BaseTypes.TEXT { + toSql() { + return 'TEXT'; + } +} + +/** @deprecated */ +export class REAL extends BaseTypes.REAL { + toSql(): string { + return 'REAL'; + } +} + +export class FLOAT extends BaseTypes.FLOAT { + // TODO: warn that FLOAT is not supported in Snowflake, only DOUBLE is + + toSql(): string { + return 'FLOAT'; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + toSql(): string { + // FLOAT is a double-precision floating point in Snowflake + return 'FLOAT'; + } +} + +// Snowflake only has one int type: Integer, which is -99999999999999999999999999999999999999 to 99999999999999999999999999999999999999 +export class TINYINT extends BaseTypes.TINYINT { + toSql() { + return 'INTEGER'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + toSql() { + return 'INTEGER'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + toSql() { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + toSql() { + return 'INTEGER'; + } +} + +export class BIGINT extends BaseTypes.BIGINT { + // not really true, but snowflake allows INT values up to 99999999999999999999999999999999999999, + // which is more than enough to cover a 64-bit unsigned integer (0 - 18446744073709551615) + protected _supportsNativeUnsigned(_dialect: AbstractDialect): boolean { + return true; + } + + toSql() { + return 'INTEGER'; + } +} diff --git a/packages/snowflake/src/connection-manager.ts b/packages/snowflake/src/connection-manager.ts new file mode 100644 index 000000000000..fc8cd36431ab --- /dev/null +++ b/packages/snowflake/src/connection-manager.ts @@ -0,0 +1,154 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { + AbstractConnectionManager, + AccessDeniedError, + ConnectionError, + ConnectionRefusedError, + HostNotFoundError, + HostNotReachableError, + InvalidConnectionError, +} from '@sequelize/core'; +import { isErrorWithStringCode } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { removeUndefined } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import * as SnowflakeSdk from 'snowflake-sdk'; +import type { SnowflakeDialect } from './dialect.js'; + +export type SnowflakeSdkModule = typeof SnowflakeSdk; + +const debug = logger.debugContext('connection:snowflake'); + +export interface SnowflakeConnection extends AbstractConnection, SnowflakeSdk.Connection {} + +export interface SnowflakeConnectionOptions + extends Omit< + SnowflakeSdk.ConnectionOptions, + // "region" is not used by the Snowflake SDK anymore (deprecated option) + | 'region' + // ensures that the dialect produces values that Sequelize expects + | 'fetchAsString' + | 'jsTreatIntegerAsBigInt' + | 'representNullAsStringNull' + | 'rowMode' + // conflicts with Sequelize's schema option. That option will be taken from Sequelize's options instead. + | 'schema' + // sequelize does not support result streaming https://github.com/sequelize/sequelize/issues/10347 + | 'streamResult' + > {} + +export class SnowflakeConnectionManager extends AbstractConnectionManager< + SnowflakeDialect, + SnowflakeConnection +> { + readonly #lib: SnowflakeSdkModule; + + constructor(dialect: SnowflakeDialect) { + super(dialect); + this.#lib = this.dialect.options.snowflakeSdkModule ?? SnowflakeSdk; + } + + /** + * Connect with a snowflake database based on config, Handle any errors in connection + * Set the pool handlers on connection.error + * Also set proper timezone once connection is connected. + * + * @param config + * @returns + * @private + */ + async connect(config: ConnectionOptions): Promise { + try { + const snowflakeConfig: SnowflakeSdk.ConnectionOptions = removeUndefined({ + schema: this.sequelize.options.schema, + ...config, + }); + const connection: SnowflakeConnection = this.#lib.createConnection(snowflakeConfig); + + await new Promise((resolve, reject) => { + connection.connect(err => { + if (err) { + return void reject(err); + } + + resolve(); + }); + }); + + debug('connection acquired'); + + if (!this.sequelize.options.keepDefaultTimezone) { + // TODO: remove default timezone. + // default value is '+00:00', put a quick workaround for it. + const tzOffset = + this.sequelize.options.timezone === '+00:00' + ? 'Etc/UTC' + : this.sequelize.options.timezone; + const isNamedTzOffset = tzOffset.includes('/'); + if (!isNamedTzOffset) { + throw new Error( + 'Snowflake only supports named timezones for the sequelize "timezone" option.', + ); + } + + await new Promise((resolve, reject) => { + connection.execute({ + sqlText: `ALTER SESSION SET timezone = '${tzOffset}'`, + complete(err) { + if (err) { + return void reject(err); + } + + resolve(); + }, + }); + }); + } + + return connection; + } catch (error) { + if (!isErrorWithStringCode(error)) { + throw error; + } + + switch (error.code) { + case 'ECONNREFUSED': + throw new ConnectionRefusedError(error); + case 'ER_ACCESS_DENIED_ERROR': + throw new AccessDeniedError(error); + case 'ENOTFOUND': + throw new HostNotFoundError(error); + case 'EHOSTUNREACH': + throw new HostNotReachableError(error); + case 'EINVAL': + throw new InvalidConnectionError(error); + default: + throw new ConnectionError(error); + } + } + } + + async disconnect(connection: SnowflakeConnection): Promise { + // Don't disconnect connections with CLOSED state + if (!connection.isUp()) { + debug('connection tried to disconnect but was already at CLOSED state'); + + return; + } + + await new Promise((resolve, reject) => { + connection.destroy(err => { + if (err) { + console.error(`Unable to disconnect: ${err.message}`); + reject(err); + } else { + console.error(`Disconnected connection with id: ${connection.getId()}`); + resolve(connection.getId()); + } + }); + }); + } + + validate(connection: SnowflakeConnection) { + return connection.isUp(); + } +} diff --git a/packages/snowflake/src/dialect.ts b/packages/snowflake/src/dialect.ts new file mode 100644 index 000000000000..2a589dc11f7d --- /dev/null +++ b/packages/snowflake/src/dialect.ts @@ -0,0 +1,184 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import { createUnspecifiedOrderedBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { SnowflakeConnectionOptions, SnowflakeSdkModule } from './connection-manager.js'; +import { SnowflakeConnectionManager } from './connection-manager.js'; +import { SnowflakeQueryGenerator } from './query-generator.js'; +import { SnowflakeQueryInterface } from './query-interface.js'; +import { SnowflakeQuery } from './query.js'; + +export interface SnowflakeDialectOptions { + /** + * Show warnings if there are any when executing a query + */ + showWarnings?: boolean | undefined; + + /** + * The snowflake-sdk library to use. + * If not provided, the snowflake-sdk npm library will be used. + * Must be compatible with the snowflake-sdk npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + snowflakeSdkModule?: SnowflakeSdkModule; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + showWarnings: undefined, + snowflakeSdkModule: undefined, +}); + +const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + accessUrl: undefined, + account: undefined, + application: undefined, + arrayBindingThreshold: undefined, + authenticator: undefined, + browserActionTimeout: undefined, + clientConfigFile: undefined, + clientRequestMFAToken: undefined, + clientSessionKeepAlive: undefined, + clientSessionKeepAliveHeartbeatFrequency: undefined, + clientStoreTemporaryCredential: undefined, + credentialCacheDir: undefined, + database: undefined, + disableConsoleLogin: undefined, + disableQueryContextCache: undefined, + disableSamlUrlCheck: undefined, + forceGCPUseDownscopedCredential: undefined, + forceStageBindError: undefined, + gcsUseDownscopedCredential: undefined, + host: undefined, + includeRetryReason: undefined, + noProxy: undefined, + passcode: undefined, + passcodeInPassword: undefined, + password: undefined, + proxyHost: undefined, + proxyPort: undefined, + proxyProtocol: undefined, + proxyUser: undefined, + proxyPassword: undefined, + privateKey: undefined, + privateKeyPass: undefined, + privateKeyPath: undefined, + resultPrefetch: undefined, + retryTimeout: undefined, + role: undefined, + sfRetryMaxLoginRetries: undefined, + serviceName: undefined, + timeout: undefined, + token: undefined, + username: undefined, + validateDefaultParameters: undefined, + warehouse: undefined, +}); + +export class SnowflakeDialect extends AbstractDialect< + SnowflakeDialectOptions, + SnowflakeConnectionOptions +> { + static supports = AbstractDialect.extendSupport({ + 'VALUES ()': true, + 'LIMIT ON UPDATE': true, + lock: true, + forShare: 'LOCK IN SHARE MODE', + savepoints: false, + isolationLevels: false, + settingIsolationLevelDuringTransaction: false, + inserts: { + ignoreDuplicates: ' IGNORE', + // disable for now, but could be enable by approach below + // https://stackoverflow.com/questions/54828745/how-to-migrate-on-conflict-do-nothing-from-postgresql-to-snowflake + // updateOnDuplicate: true + }, + index: { + collate: false, + length: true, + parser: true, + type: true, + using: 1, + }, + constraints: { + deferrable: true, + check: false, + removeOptions: { cascade: true }, + }, + indexViaAlter: true, + indexHints: true, + upserts: false, + schemas: true, + multiDatabases: true, + dataTypes: { + COLLATE_BINARY: true, + }, + REGEXP: true, + globalTimeZoneConfig: true, + dropTable: { + cascade: true, + }, + createSchema: { + comment: true, + ifNotExists: true, + replace: true, + }, + dropSchema: { + cascade: true, + ifExists: true, + }, + delete: { + limit: false, + }, + }); + + readonly Query = SnowflakeQuery; + readonly connectionManager: SnowflakeConnectionManager; + readonly queryGenerator: SnowflakeQueryGenerator; + readonly queryInterface: SnowflakeQueryInterface; + + constructor(sequelize: Sequelize, options: SnowflakeDialectOptions) { + console.warn( + 'The Snowflake dialect is experimental and usage is at your own risk. Its development is exclusively community-driven and not officially supported by the maintainers.', + ); + + super({ + dataTypeOverrides: DataTypes, + dataTypesDocumentationUrl: 'https://docs.snowflake.com/en/sql-reference/data-types.html', + identifierDelimiter: '"', + // TODO: fix the minimum supported version + minimumDatabaseVersion: '5.7.0', + name: 'snowflake', + options, + sequelize, + }); + + this.connectionManager = new SnowflakeConnectionManager(this); + this.queryGenerator = new SnowflakeQueryGenerator(this); + this.queryInterface = new SnowflakeQueryInterface(this); + } + + parseConnectionUrl(): SnowflakeConnectionOptions { + throw new Error( + 'The "url" option is not supported in Snowflake. Please use one of the other available connection options.', + ); + } + + createBindCollector() { + return createUnspecifiedOrderedBindCollector(); + } + + getDefaultSchema(): string { + return 'PUBLIC'; + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions(): readonly string[] { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/snowflake/src/index.mjs b/packages/snowflake/src/index.mjs new file mode 100644 index 000000000000..c73d30974487 --- /dev/null +++ b/packages/snowflake/src/index.mjs @@ -0,0 +1,7 @@ +import Pkg from './index.js'; + +export const SnowflakeConnectionManager = Pkg.SnowflakeConnectionManager; +export const SnowflakeDialect = Pkg.SnowflakeDialect; +export const SnowflakeQueryGenerator = Pkg.SnowflakeQueryGenerator; +export const SnowflakeQueryInterface = Pkg.SnowflakeQueryInterface; +export const SnowflakeQuery = Pkg.SnowflakeQuery; diff --git a/packages/snowflake/src/index.ts b/packages/snowflake/src/index.ts new file mode 100644 index 000000000000..77a8f97c40cc --- /dev/null +++ b/packages/snowflake/src/index.ts @@ -0,0 +1,7 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query.js'; diff --git a/packages/snowflake/src/query-generator-typescript.internal.ts b/packages/snowflake/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..d055d5781e0f --- /dev/null +++ b/packages/snowflake/src/query-generator-typescript.internal.ts @@ -0,0 +1,182 @@ +import type { + CreateDatabaseQueryOptions, + ListDatabasesQueryOptions, + ListSchemasQueryOptions, + ListTablesQueryOptions, + ShowConstraintsQueryOptions, + StartTransactionQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator, Op } from '@sequelize/core'; +import { + CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS, + SHOW_CONSTRAINTS_QUERY_SUPPORTABLE_OPTIONS, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import type { SnowflakeDialect } from './dialect.js'; +import { SnowflakeQueryGeneratorInternal } from './query-generator.internal.js'; + +const SHOW_CONSTRAINTS_QUERY_SUPPORTED_OPTIONS = new Set([ + 'constraintName', + 'constraintType', +]); + +/** + * Temporary class to ease the TypeScript migration + */ +export class SnowflakeQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: SnowflakeQueryGeneratorInternal; + + constructor( + dialect: SnowflakeDialect, + internals: SnowflakeQueryGeneratorInternal = new SnowflakeQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + internals.whereSqlBuilder.setOperatorKeyword(Op.regexp, 'REGEXP'); + internals.whereSqlBuilder.setOperatorKeyword(Op.notRegexp, 'NOT REGEXP'); + + this.#internals = internals; + } + + createDatabaseQuery(database: string, options?: CreateDatabaseQueryOptions) { + if (options) { + rejectInvalidOptions( + 'createDatabaseQuery', + this.dialect, + CREATE_DATABASE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return joinSQLFragments([`CREATE DATABASE IF NOT EXISTS ${this.quoteIdentifier(database)}`]); + } + + listDatabasesQuery(options?: ListDatabasesQueryOptions) { + let sql = `SELECT DATABASE_NAME as "name", * FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES WHERE "name" NOT IN ('SNOWFLAKE', 'SNOWFLAKE$GDS')`; + if (options?.skip) { + sql += ` AND UPPER("name") NOT IN (${options.skip.map(db => `UPPER(${this.escape(db)})`).join(', ')})`; + } + + return sql; + } + + listSchemasQuery(options?: ListSchemasQueryOptions) { + let schemasToSkip = this.#internals.getTechnicalSchemaNames(); + if (options && Array.isArray(options?.skip)) { + schemasToSkip = [...schemasToSkip, ...options.skip]; + } + + return joinSQLFragments([ + 'SELECT SCHEMA_NAME AS "schema"', + 'FROM INFORMATION_SCHEMA.SCHEMATA', + `WHERE SCHEMA_NAME NOT IN (${schemasToSkip.map(schema => this.escape(schema)).join(', ')})`, + ]); + } + + describeTableQuery(tableName: TableOrModel) { + return `SHOW FULL COLUMNS FROM ${this.quoteTable(tableName)};`; + } + + listTablesQuery(options?: ListTablesQueryOptions) { + return joinSQLFragments([ + 'SELECT TABLE_NAME AS "tableName",', + 'TABLE_SCHEMA AS "schema"', + `FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'`, + options?.schema + ? `AND TABLE_SCHEMA = ${this.escape(options.schema)}` + : `AND TABLE_SCHEMA NOT IN (${this.#internals + .getTechnicalSchemaNames() + .map(schema => this.escape(schema)) + .join(', ')})`, + 'ORDER BY TABLE_SCHEMA, TABLE_NAME', + ]); + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return `TRUNCATE ${this.quoteTable(tableName)}`; + } + + showConstraintsQuery(tableName: TableOrModel, options?: ShowConstraintsQueryOptions) { + if (options) { + rejectInvalidOptions( + 'showConstraintsQuery', + this.dialect, + SHOW_CONSTRAINTS_QUERY_SUPPORTABLE_OPTIONS, + SHOW_CONSTRAINTS_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + const table = this.extractTableDetails(tableName); + + return joinSQLFragments([ + 'SELECT c.CONSTRAINT_CATALOG AS "constraintCatalog",', + 'c.CONSTRAINT_SCHEMA AS "constraintSchema",', + 'c.CONSTRAINT_NAME AS "constraintName",', + 'c.CONSTRAINT_TYPE AS "constraintType",', + 'c.TABLE_CATALOG AS "tableCatalog",', + 'c.TABLE_SCHEMA AS "tableSchema",', + 'c.TABLE_NAME AS "tableName",', + 'fk.TABLE_SCHEMA AS "referencedTableSchema",', + 'fk.TABLE_NAME AS "referencedTableName",', + 'r.DELETE_RULE AS "deleteAction",', + 'r.UPDATE_RULE AS "updateAction",', + 'c.IS_DEFERRABLE AS "isDeferrable",', + 'c.INITIALLY_DEFERRED AS "initiallyDeferred"', + 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c', + 'LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r ON c.CONSTRAINT_CATALOG = r.CONSTRAINT_CATALOG AND c.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = r.CONSTRAINT_NAME', + 'LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk ON r.UNIQUE_CONSTRAINT_CATALOG = fk.CONSTRAINT_CATALOG AND r.UNIQUE_CONSTRAINT_SCHEMA = fk.CONSTRAINT_SCHEMA AND r.UNIQUE_CONSTRAINT_NAME = fk.CONSTRAINT_NAME', + `WHERE c.TABLE_NAME = ${this.escape(table.tableName)}`, + `AND c.TABLE_SCHEMA = ${this.escape(table.schema)}`, + options?.constraintName + ? `AND c.CONSTRAINT_NAME = ${this.escape(options.constraintName)}` + : '', + options?.constraintType + ? `AND c.CONSTRAINT_TYPE = ${this.escape(options.constraintType)}` + : '', + 'ORDER BY c.CONSTRAINT_NAME', + ]); + } + + showIndexesQuery() { + // TODO [+snowflake-sdk]: check if this is the correct implementation + return `SELECT '' FROM DUAL`; + } + + versionQuery() { + return 'SELECT CURRENT_VERSION() AS "version"'; + } + + startTransactionQuery(options?: StartTransactionQueryOptions): string { + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.startTransaction, + options, + ); + } + + return options?.transactionName + ? `START TRANSACTION NAME ${this.quoteIdentifier(options.transactionName)}` + : 'START TRANSACTION'; + } +} diff --git a/packages/snowflake/src/query-generator.d.ts b/packages/snowflake/src/query-generator.d.ts new file mode 100644 index 000000000000..992ad46b9781 --- /dev/null +++ b/packages/snowflake/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { SnowflakeQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class SnowflakeQueryGenerator extends SnowflakeQueryGeneratorTypeScript {} diff --git a/packages/snowflake/src/query-generator.internal.ts b/packages/snowflake/src/query-generator.internal.ts new file mode 100644 index 000000000000..6cb037a598fd --- /dev/null +++ b/packages/snowflake/src/query-generator.internal.ts @@ -0,0 +1,35 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import type { SnowflakeDialect } from './dialect.js'; + +const TECHNICAL_SCHEMA_NAMES = Object.freeze([ + 'INFORMATION_SCHEMA', + 'PERFORMANCE_SCHEMA', + 'SYS', + 'information_schema', + 'performance_schema', + 'sys', +]); + +export class SnowflakeQueryGeneratorInternal< + Dialect extends SnowflakeDialect = SnowflakeDialect, +> extends AbstractQueryGeneratorInternal { + getTechnicalSchemaNames() { + return TECHNICAL_SCHEMA_NAMES; + } + + addLimitAndOffset(options: AddLimitOffsetOptions): string { + let fragment = ''; + if (options.limit != null) { + fragment += ` LIMIT ${this.queryGenerator.escape(options.limit, options)}`; + } else if (options.offset) { + fragment += ` LIMIT NULL`; + } + + if (options.offset) { + fragment += ` OFFSET ${this.queryGenerator.escape(options.offset, options)}`; + } + + return fragment; + } +} diff --git a/packages/snowflake/src/query-generator.js b/packages/snowflake/src/query-generator.js new file mode 100644 index 000000000000..fc52b008e3ce --- /dev/null +++ b/packages/snowflake/src/query-generator.js @@ -0,0 +1,367 @@ +'use strict'; + +import { + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { quoteIdentifier } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dialect.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import each from 'lodash/each'; +import isPlainObject from 'lodash/isPlainObject'; +import { SnowflakeQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +/** + * list of reserved words in Snowflake + * source: https://docs.snowflake.com/en/sql-reference/reserved-keywords.html + * + * @private + */ +const SNOWFLAKE_RESERVED_WORDS = + 'account,all,alter,and,any,as,between,by,case,cast,check,column,connect,connections,constraint,create,cross,current,current_date,current_time,current_timestamp,current_user,database,delete,distinct,drop,else,exists,false,following,for,from,full,grant,group,gscluster,having,ilike,in,increment,inner,insert,intersect,into,is,issue,join,lateral,left,like,localtime,localtimestamp,minus,natural,not,null,of,on,or,order,organization,qualify,regexp,revoke,right,rlike,row,rows,sample,schema,select,set,some,start,table,tablesample,then,to,trigger,true,try_cast,union,unique,update,using,values,view,when,whenever,where,with'.split( + ',', + ); + +const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); + +const CREATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set(['comment', 'uniqueKeys']); + +export class SnowflakeQueryGenerator extends SnowflakeQueryGeneratorTypeScript { + createTableQuery(tableName, attributes, options) { + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + options = { + charset: null, + rowFormat: null, + ...options, + }; + + const primaryKeys = []; + const foreignKeys = {}; + const attrStr = []; + + for (const attr in attributes) { + if (!Object.hasOwn(attributes, attr)) { + continue; + } + + let dataType = attributes[attr]; + let match; + + if (dataType.includes('AUTOINCREMENT')) { + // Replace AUTOINCREMENT with DEFAULT .NEXTVAL + const tblPart = tableName.tableName ? tableName.tableName : tableName; + const sequenceName = this.quoteIdentifier(`${tblPart}_${attr}_seq`); + dataType = dataType.replace('AUTOINCREMENT', `DEFAULT ${sequenceName}.NEXTVAL`); + } + + if (dataType.includes('PRIMARY KEY')) { + primaryKeys.push(attr); + + if (dataType.includes('REFERENCES')) { + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + } + } else if (dataType.includes('REFERENCES')) { + match = dataType.match(/^(.+) (REFERENCES.*)$/); + attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + foreignKeys[attr] = match[2]; + } else { + attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + } + } + + const table = this.quoteTable(tableName); + let attributesClause = attrStr.join(', '); + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + if (options.uniqueKeys) { + each(options.uniqueKeys, (columns, indexName) => { + if (typeof indexName !== 'string') { + indexName = `uniq_${tableName}_${columns.fields.join('_')}`; + } + + attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; + }); + } + + if (pkString.length > 0) { + attributesClause += `, PRIMARY KEY (${pkString})`; + } + + for (const fkey in foreignKeys) { + if (Object.hasOwn(foreignKeys, fkey)) { + attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + } + } + + return joinSQLFragments([ + 'CREATE TABLE IF NOT EXISTS', + table, + `(${attributesClause})`, + options.comment && + typeof options.comment === 'string' && + `COMMENT ${this.escape(options.comment)}`, + ';', + ]); + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key, + }), + ';', + ]); + } + + changeColumnQuery(tableName, attributes) { + const query = (...subQuerys) => + joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'ALTER COLUMN', + ...subQuerys, + ';', + ]); + const sql = []; + for (const attributeName in attributes) { + let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]); + const attrSql = []; + + if (definition.includes('NOT NULL')) { + attrSql.push(query(this.quoteIdentifier(attributeName), 'SET NOT NULL')); + + definition = definition.replace('NOT NULL', '').trim(); + } else if (!definition.includes('REFERENCES')) { + attrSql.push(query(this.quoteIdentifier(attributeName), 'DROP NOT NULL')); + } + + if (definition.includes('DEFAULT')) { + attrSql.push( + query( + this.quoteIdentifier(attributeName), + 'SET DEFAULT', + definition.match(/DEFAULT ([^;]+)/)[1], + ), + ); + + definition = definition.replace(/(DEFAULT[^;]+)/, '').trim(); + } else if (!definition.includes('REFERENCES')) { + attrSql.push(query(this.quoteIdentifier(attributeName), 'DROP DEFAULT')); + } + + if (/UNIQUE;*$/.test(definition)) { + definition = definition.replace(/UNIQUE;*$/, ''); + attrSql.push( + query('ADD UNIQUE (', this.quoteIdentifier(attributeName), ')').replace( + 'ALTER COLUMN', + '', + ), + ); + } + + if (definition.includes('REFERENCES')) { + definition = definition.replace(/.+?(?=REFERENCES)/, ''); + attrSql.push( + query('ADD FOREIGN KEY (', this.quoteIdentifier(attributeName), ')', definition).replace( + 'ALTER COLUMN', + '', + ), + ); + } else { + attrSql.push(query(this.quoteIdentifier(attributeName), 'TYPE', definition)); + } + + sql.push(attrSql.join('')); + } + + return sql.join(''); + } + + renameColumnQuery(tableName, attrBefore, attributes) { + const attrString = []; + + for (const attrName in attributes) { + const definition = attributes[attrName]; + attrString.push(`'${attrBefore}' '${attrName}' ${definition}`); + } + + return joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'RENAME COLUMN', + attrString.join(' to '), + ';', + ]); + } + + attributeToSQL(attribute, options) { + if (!isPlainObject(attribute)) { + attribute = { + type: attribute, + }; + } + + const attributeString = attribute.type.toString({ dialect: this.dialect }); + let template = attributeString; + + if (attribute.allowNull === false) { + template += ' NOT NULL'; + } + + if (attribute.autoIncrement) { + template += ' AUTOINCREMENT'; + } + + // BLOB/TEXT/GEOMETRY/JSON cannot have a default value + if ( + !typeWithoutDefault.has(attributeString) && + attribute.type._binary !== true && + defaultValueSchemable(attribute.defaultValue, this.dialect) + ) { + template += ` DEFAULT ${this.escape(attribute.defaultValue, { ...options, type: attribute.type })}`; + } + + if (attribute.unique === true) { + template += ' UNIQUE'; + } + + if (attribute.primaryKey) { + template += ' PRIMARY KEY'; + } + + if (attribute.comment) { + template += ` COMMENT ${this.escape(attribute.comment, options)}`; + } + + if (attribute.first) { + template += ' FIRST'; + } + + if (attribute.after) { + template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; + } + + 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`); + + template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`; + } + + template += ` REFERENCES ${this.quoteTable(attribute.references.table)}`; + + if (attribute.references.key) { + template += ` (${this.quoteIdentifier(attribute.references.key)})`; + } else { + template += ` (${this.quoteIdentifier('id')})`; + } + + if (attribute.onDelete) { + template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + return template; + } + + attributesToSQL(attributes, options) { + const result = {}; + + for (const key in attributes) { + const attribute = attributes[key]; + result[attribute.field || key] = this.attributeToSQL(attribute, options); + } + + return result; + } + + dataTypeMapping(tableName, attr, dataType) { + if (dataType.includes('PRIMARY KEY')) { + dataType = dataType.replace('PRIMARY KEY', ''); + } + + if (dataType.includes('SERIAL')) { + if (dataType.includes('BIGINT')) { + dataType = dataType.replace('SERIAL', 'BIGSERIAL'); + dataType = dataType.replace('BIGINT', ''); + } else if (dataType.includes('SMALLINT')) { + dataType = dataType.replace('SERIAL', 'SMALLSERIAL'); + dataType = dataType.replace('SMALLINT', ''); + } else { + dataType = dataType.replace('INTEGER', ''); + } + + dataType = dataType.replace('NOT NULL', ''); + } + + return dataType; + } + + /** + * Quote identifier in sql clause + * + * @param {string} identifier + * @param {boolean} force + * + * @returns {string} + */ + quoteIdentifier(identifier, force) { + const optForceQuote = force || false; + // TODO [>7]: remove "quoteIdentifiers: false" option + const optQuoteIdentifiers = this.options.quoteIdentifiers !== false; + + if ( + optForceQuote === true || + // TODO [>7]: drop this.options.quoteIdentifiers. Always quote identifiers. + optQuoteIdentifiers !== false || + identifier.includes('.') || + identifier.includes('->') || + SNOWFLAKE_RESERVED_WORDS.includes(identifier.toLowerCase()) + ) { + // In Snowflake if tables or attributes are created double-quoted, + // they are also case sensitive. If they contain any uppercase + // characters, they must always be double-quoted. This makes it + // impossible to write queries in portable SQL if tables are created in + // this way. Hence, we strip quotes if we don't want case sensitivity. + return quoteIdentifier(identifier, this.dialect.TICK_CHAR_LEFT, this.dialect.TICK_CHAR_RIGHT); + } + + return identifier; + } +} diff --git a/packages/snowflake/src/query-interface.ts b/packages/snowflake/src/query-interface.ts new file mode 100644 index 000000000000..30e91bde724f --- /dev/null +++ b/packages/snowflake/src/query-interface.ts @@ -0,0 +1,69 @@ +import type { ColumnsDescription, QueryRawOptions, TableName } from '@sequelize/core'; +import { AbstractQueryInterface, QueryTypes } from '@sequelize/core'; +import type { SnowflakeDialect } from './dialect.js'; + +export class SnowflakeQueryInterface< + Dialect extends SnowflakeDialect = SnowflakeDialect, +> extends AbstractQueryInterface { + /** + * Ensure all required sequences are created for AUTOINCREMENT columns + * + * Snowflake doesn't support returning the last inserted ID for autoincrement columns. + * To overcome this, we create a sequence for each autoincrement column, + * and use it to get the next value. + * + * @param table Table to create + * @param attributes Object representing a list of normalized table attributes + * @param [options] + * + * @protected + */ + async ensureSequences( + table: TableName, + attributes: Record, + options: QueryRawOptions, + ) { + const keys = Object.keys(attributes); + const keyLen = keys.length; + + const promises = []; + for (let i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + if (!attribute.autoIncrement) { + continue; + } + + const tableName = typeof table === 'string' ? table : table.tableName; + const seqName = this.quoteIdentifier(this.getSequenceName(tableName, keys[i])); + const sql = `CREATE SEQUENCE IF NOT EXISTS ${seqName}`; + promises.push( + this.sequelize.queryRaw(sql, { + ...options, + plain: true, + raw: true, + type: QueryTypes.RAW, + }), + ); + } + + await Promise.all(promises); + } + + private async getNextPrimaryKeyValue(tableName: string, fieldName: string) { + const sequenceName = this.getSequenceName(tableName, fieldName); + const sql = `SELECT ${this.quoteIdentifier(sequenceName)}.nextval AS NEXT_VALUE`; + + const row = await this.sequelize.queryRaw(sql, { + plain: true, + raw: true, + type: QueryTypes.SELECT, + }); + + // @ts-expect-error -- NEXT_VALUE is a valid property of a row + return row?.NEXT_VALUE; + } + + private getSequenceName(tableName: string, fieldName: string) { + return `${tableName}_${fieldName}_seq`; + } +} diff --git a/packages/snowflake/src/query.d.ts b/packages/snowflake/src/query.d.ts new file mode 100644 index 000000000000..e2aed0b6c21d --- /dev/null +++ b/packages/snowflake/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class SnowflakeQuery extends AbstractQuery {} diff --git a/packages/snowflake/src/query.js b/packages/snowflake/src/query.js new file mode 100644 index 000000000000..4056ff5224be --- /dev/null +++ b/packages/snowflake/src/query.js @@ -0,0 +1,298 @@ +'use strict'; + +import { + AbstractQuery, + DatabaseError, + ForeignKeyConstraintError, + UniqueConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import forOwn from 'lodash/forOwn'; +import map from 'lodash/map'; +import mapKeys from 'lodash/mapKeys'; +import reduce from 'lodash/reduce'; +import zipObject from 'lodash/zipObject'; + +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:snowflake'); + +export class SnowflakeQuery extends AbstractQuery { + async run(sql, parameters) { + this.sql = sql; + const { connection, options } = this; + + const showWarnings = this.sequelize.dialect.options.showWarnings || options.showWarnings; + + const complete = this._logQuery(sql, debug, parameters); + + if (parameters) { + debug('parameters(%j)', parameters); + } + + let results; + + try { + results = await new Promise((resolve, reject) => { + connection.execute({ + sqlText: sql, + binds: parameters, + complete(err, _stmt, rows) { + if (err) { + reject(err); + } else { + resolve(rows); + } + }, + }); + }); + } catch (error) { + if (options.transaction && error.errno === ER_DEADLOCK) { + try { + await options.transaction.rollback(); + } catch { + // ignore errors + } + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } finally { + complete(); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + + return this.formatResults(results); + } + + /** + * High level function that handles the results of a query execution. + * + * + * Example: + * query.formatResults([ + * { + * id: 1, // this is from the main table + * attr2: 'snafu', // this is from the main table + * Tasks.id: 1, // this is from the associated table + * Tasks.title: 'task' // this is from the associated table + * } + * ]) + * + * @param {Array} data - The result of the query execution. + * @private + */ + formatResults(data) { + let result = this.instance; + + if (this.isInsertQuery(data)) { + this.handleInsertQuery(data); + + if (!this.instance) { + const modelDefinition = this.model?.modelDefinition; + + // handle bulkCreate AI primary key + if ( + data.constructor.name === 'ResultSetHeader' && + modelDefinition?.autoIncrementAttributeName && + modelDefinition?.autoIncrementAttributeName === this.model.primaryKeyAttribute + ) { + const startId = data[this.getInsertIdField()]; + result = []; + for (let i = startId; i < startId + data.affectedRows; i++) { + result.push({ [modelDefinition.getColumnName(this.model.primaryKeyAttribute)]: i }); + } + } else { + result = data[this.getInsertIdField()]; + } + } + } + + if (this.isSelectQuery()) { + // Snowflake will treat tables as case-insensitive, so fix the case + // of the returned values to match attributes + // TODO [>7]: remove this.sequelize.options.quoteIdentifiers === false + if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { + const attrsMap = Object.create(null); + + for (const attrName of this.model.modelDefinition.attributes.keys()) { + attrsMap[attrName.toLowerCase()] = attrName; + } + + data = data.map(data => + reduce( + data, + (prev, value, key) => { + if (value !== undefined && attrsMap[key]) { + prev[attrsMap[key]] = value; + delete prev[key]; + } + + return prev; + }, + data, + ), + ); + } + + this.options.fieldMap = mapKeys(this.options.fieldMap, (v, k) => { + return k.toUpperCase(); + }); + + return this.handleSelectQuery(data); + } + + if (this.isDescribeQuery()) { + result = {}; + + for (const _result of data) { + result[_result.Field] = { + type: _result.Type.toUpperCase(), + allowNull: _result.Null === 'YES', + defaultValue: _result.Default, + primaryKey: _result.Key === 'PRI', + autoIncrement: + Object.hasOwn(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment', + comment: _result.Comment ? _result.Comment : null, + }; + } + + return result; + } + + if (this.isShowIndexesQuery()) { + return this.handleShowIndexesQuery(data); + } + + if (this.isCallQuery()) { + return data[0]; + } + + if (this.isBulkUpdateQuery() || this.isDeleteQuery()) { + return data[0]['number of rows updated']; + } + + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } + + if (this.isInsertQuery() || this.isUpdateQuery()) { + return [result, data.affectedRows]; + } + + if (this.isShowConstraintsQuery()) { + return data; + } + + if (this.isRawQuery()) { + return [data, data]; + } + + return result; + } + + formatError(err) { + const errCode = err.errno || err.code; + + switch (errCode) { + 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 fieldVal = match ? match[1] : undefined; + const uniqueKey = + this.model && + this.model.getIndexes().find(index => index.unique && index.name === fieldKey); + + if (uniqueKey) { + if (uniqueKey.msg) { + message = uniqueKey.msg; + } + + fields = zipObject(uniqueKey.fields, values); + } else { + fields[fieldKey] = fieldVal; + } + + const errors = []; + forOwn(fields, (value, field) => { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + value, + this.instance, + 'not_unique', + ), + ); + }); + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + case ER_ROW_IS_REFERENCED: + case ER_NO_REFERENCED_ROW: { + // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) + const match = err.message.match( + /CONSTRAINT (["`])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/, + ); + const quoteChar = match ? match[1] : '`'; + const fields = match + ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) + : undefined; + + return new ForeignKeyConstraintError({ + 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, + cause: err, + }); + } + + default: + return new DatabaseError(err); + } + } + + handleShowIndexesQuery(data) { + // Group by index name, and collect all fields + data = data.reduce((acc, item) => { + if (!(item.Key_name in acc)) { + acc[item.Key_name] = item; + item.fields = []; + } + + acc[item.Key_name].fields[item.Seq_in_index - 1] = { + attribute: item.Column_name, + length: item.Sub_part || undefined, + order: item.Collation === 'A' ? 'ASC' : undefined, + }; + delete item.column_name; + + return acc; + }, {}); + + return map(data, item => ({ + primary: item.Key_name === 'PRIMARY', + fields: item.fields, + name: item.Key_name, + tableName: item.Table, + unique: item.Non_unique !== 1, + type: item.Index_type, + })); + } +} diff --git a/packages/snowflake/tsconfig.json b/packages/snowflake/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/snowflake/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/snowflake/typedoc.json b/packages/snowflake/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/snowflake/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/sqlite3/.eslintrc.js b/packages/sqlite3/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/sqlite3/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/sqlite3/CHANGELOG.md b/packages/sqlite3/CHANGELOG.md new file mode 100644 index 000000000000..8c4b5dbce744 --- /dev/null +++ b/packages/sqlite3/CHANGELOG.md @@ -0,0 +1,48 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [7.0.0-alpha.47](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.46...v7.0.0-alpha.47) (2025-10-25) + +### Features + +- add parameter style ([#17560](https://github.com/sequelize/sequelize/issues/17560)) ([1f4bdee](https://github.com/sequelize/sequelize/commit/1f4bdee80bb7ab5a335d11681f0a9ea973277297)) + +### BREAKING CHANGES + +- the `bindParam` option has been replaced with `parameterStyle` which defaults to `ParameterStyle.BIND` + +# [7.0.0-alpha.46](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.45...v7.0.0-alpha.46) (2025-03-22) + +**Note:** Version bump only for package @sequelize/sqlite3 + +# [7.0.0-alpha.45](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.44...v7.0.0-alpha.45) (2025-02-17) + +**Note:** Version bump only for package @sequelize/sqlite3 + +# [7.0.0-alpha.44](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.43...v7.0.0-alpha.44) (2025-01-27) + +### Bug Fixes + +- update prettier to v3.3.3 ([#17534](https://github.com/sequelize/sequelize/issues/17534)) ([7d2e72e](https://github.com/sequelize/sequelize/commit/7d2e72e84da08075a631fc43bf69d909649fc297)) + +# [7.0.0-alpha.43](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.42...v7.0.0-alpha.43) (2024-10-04) + +### Bug Fixes + +- unify returning queries ([#17157](https://github.com/sequelize/sequelize/issues/17157)) ([0a350c0](https://github.com/sequelize/sequelize/commit/0a350c0f91d0eee9c56b92f47cc23c273c9eb206)) + +# [7.0.0-alpha.42](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.41...v7.0.0-alpha.42) (2024-09-13) + +**Note:** Version bump only for package @sequelize/sqlite3 + +# [7.0.0-alpha.41](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.40...v7.0.0-alpha.41) (2024-05-17) + +**Note:** Version bump only for package @sequelize/sqlite3 + +# [7.0.0-alpha.40](https://github.com/sequelize/sequelize/compare/v7.0.0-alpha.39...v7.0.0-alpha.40) (2024-04-11) + +### Features + +- rename `@sequelize/sqlite` to `@sequelize/sqlite3`, `@sequelize/ibmi` to `@sequelize/db2-ibmi`, ban conflicting options ([#17269](https://github.com/sequelize/sequelize/issues/17269)) ([1fb48a4](https://github.com/sequelize/sequelize/commit/1fb48a462c96ec64bf8ed19f91662c4d73e1fe3e)) diff --git a/packages/sqlite3/package.json b/packages/sqlite3/package.json new file mode 100644 index 000000000000..5713d349d8b0 --- /dev/null +++ b/packages/sqlite3/package.json @@ -0,0 +1,44 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "SQLite Connector for Sequelize, based on the sqlite3 npm package", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + } + }, + "files": [ + "lib" + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/sqlite3", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "build": "../../build-packages.mjs sqlite3", + "test": "concurrently \"npm:test-*\"", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated", + "sync-exports": "../../dev/sync-exports.mjs ./src" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "@sequelize/utils": "workspace:*", + "lodash": "^4.17.21", + "sqlite3": "^5.1.7" + } +} diff --git a/packages/sqlite3/src/_internal/data-types-overrides.ts b/packages/sqlite3/src/_internal/data-types-overrides.ts new file mode 100644 index 000000000000..bae6d240c51c --- /dev/null +++ b/packages/sqlite3/src/_internal/data-types-overrides.ts @@ -0,0 +1,267 @@ +import type { AbstractDialect } from '@sequelize/core'; +import { BaseError } from '@sequelize/core'; +import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/data-types.js'; +import NodeUtil from 'node:util'; + +function removeUnsupportedIntegerOptions( + dataType: BaseTypes.BaseIntegerDataType, + dialect: AbstractDialect, +) { + if (dataType.options.length != null) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support '${dataType.getDataTypeId()}' with length. This option will be ignored.`, + ); + delete dataType.options.length; + } +} + +function removeUnsupportedDecimalNumberOptions( + dataType: BaseTypes.BaseDecimalNumberDataType, + dialect: AbstractDialect, +) { + if (dataType.options.scale != null || dataType.options.precision != null) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support '${dataType.getDataTypeId()}' with "scale" or "precision" specified. These options will be ignored.`, + ); + dataType.options.scale = undefined; + dataType.options.precision = undefined; + } +} + +export class BOOLEAN extends BaseTypes.BOOLEAN { + // Note: the BOOLEAN type is SQLite maps to NUMERIC, but we still use BOOLEAN because introspecting the table + // still indicates that the column is a BOOLEAN column - which we may be able to exploit in the future to parse the value + // in raw queries where the DataType is not available. + + escape(value: boolean | unknown): string { + return value ? '1' : '0'; + } + + toBindableValue(value: boolean | unknown): unknown { + return value ? 1 : 0; + } + + toSql(): string { + return 'INTEGER'; + } +} + +export class STRING extends BaseTypes.STRING { + // TODO: add length check constraint + // check(length(col) <= 5)) + toSql() { + if (this.options.binary) { + return `TEXT COLLATE BINARY`; + } + + return 'TEXT'; + } +} + +export class TEXT extends BaseTypes.TEXT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.length) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support TEXT with options. Plain 'TEXT' will be used instead.`, + ); + this.options.length = undefined; + } + } +} + +export class CITEXT extends BaseTypes.CITEXT { + toSql() { + return 'TEXT COLLATE NOCASE'; + } +} + +export class TINYINT extends BaseTypes.TINYINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^8-1 check when the unsigned option is true + // TODO: add >= -2^7 =< 2^7-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class SMALLINT extends BaseTypes.SMALLINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^16-1 check when the unsigned option is true + // TODO: add >= -2^15 =< 2^15-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class MEDIUMINT extends BaseTypes.MEDIUMINT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^24-1 check when the unsigned option is true + // TODO: add >= -2^23 =< 2^23-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class INTEGER extends BaseTypes.INTEGER { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedIntegerOptions(this, dialect); + } + + // TODO: add >= 0 =< 2^32-1 check when the unsigned option is true + // TODO: add >= -2^31 =< 2^31-1 check when the unsigned option is false + + toSql(): string { + return 'INTEGER'; + } +} + +export class FLOAT extends BaseTypes.FLOAT { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedDecimalNumberOptions(this, dialect); + dialect.warnDataTypeIssue( + `${dialect.name} does not support single-precision floating point numbers. SQLite's REAL type will be used instead, which in SQLite is a double-precision floating point type.`, + ); + } + + // TODO: add check constraint >= 0 if unsigned is true + + protected getNumberSqlTypeName(): string { + return 'REAL'; + } +} + +export class DOUBLE extends BaseTypes.DOUBLE { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedDecimalNumberOptions(this, dialect); + } + + // TODO: add check constraint >= 0 if unsigned is true + + protected getNumberSqlTypeName(): string { + // in SQLite, REAL is 8 bytes, not 4. + return 'REAL'; + } +} + +/** + * @deprecated use FLOAT. + */ +export class REAL extends BaseTypes.REAL { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + removeUnsupportedDecimalNumberOptions(this, dialect); + } + + protected getNumberSqlTypeName(): string { + // in SQLite, REAL is 8 bytes, not 4. + return 'REAL'; + } +} + +export class TIME extends BaseTypes.TIME { + // TODO: add CHECK constraint + // https://github.com/sequelize/sequelize/pull/14505#issuecomment-1259279743 + + toSql(): string { + return 'TEXT'; + } +} + +export class DATE extends BaseTypes.DATE { + // TODO: add CHECK constraint + // https://github.com/sequelize/sequelize/pull/14505#issuecomment-1259279743 + + toSql(): string { + return 'TEXT'; + } +} + +export class DATEONLY extends BaseTypes.DATEONLY { + // TODO: add CHECK constraint + // https://github.com/sequelize/sequelize/pull/14505#issuecomment-1259279743 + + toSql(): string { + return 'TEXT'; + } +} + +export class BLOB extends BaseTypes.BLOB { + protected _checkOptionSupport(dialect: AbstractDialect) { + super._checkOptionSupport(dialect); + + if (this.options.length) { + dialect.warnDataTypeIssue( + `${dialect.name} does not support '${this.getDataTypeId()}' with length. This option will be ignored.`, + ); + delete this.options.length; + } + } + + toSql() { + return 'BLOB'; + } +} + +export class JSON extends BaseTypes.JSON { + parseDatabaseValue(value: unknown): unknown { + // sqlite3 being sqlite3, JSON numbers are returned as JS numbers, but everything else is returned as a JSON string + if (typeof value === 'number') { + return value; + } + + if (typeof value !== 'string') { + throw new Error( + `DataTypes.JSON received a non-string value from the database, which it cannot parse: ${NodeUtil.inspect(value)}.`, + ); + } + + try { + return globalThis.JSON.parse(value); + } catch (error) { + throw new BaseError( + `DataTypes.JSON received a value from the database that it not valid JSON: ${NodeUtil.inspect(value)}.`, + { cause: error }, + ); + } + } + + // TODO: add check constraint + // https://www.sqlite.org/json1.html#jvalid + toSql(): string { + return 'TEXT'; + } +} + +export class UUID extends BaseTypes.UUID { + // TODO: add check constraint to enforce GUID format + toSql() { + return 'TEXT'; + } +} + +export class ENUM extends BaseTypes.ENUM { + // TODO: add check constraint to enforce list of accepted values + toSql() { + return 'TEXT'; + } +} diff --git a/packages/sqlite3/src/connection-manager.ts b/packages/sqlite3/src/connection-manager.ts new file mode 100644 index 000000000000..576051b78a2c --- /dev/null +++ b/packages/sqlite3/src/connection-manager.ts @@ -0,0 +1,197 @@ +import type { AbstractConnection, ConnectionOptions } from '@sequelize/core'; +import { AbstractConnectionManager, ConnectionError } from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import { checkFileExists } from '@sequelize/utils/node'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import * as Sqlite3 from 'sqlite3'; +import type { SqliteDialect } from './dialect.js'; + +const debug = logger.debugContext('connection:sqlite3'); + +export type Sqlite3Module = typeof Sqlite3; + +const CLOSED_SYMBOL = Symbol('closed'); + +export interface SqliteConnection extends AbstractConnection, Sqlite3.Database { + // Not declared by sqlite3's typings + filename: string; + + // Sequelize extension + [CLOSED_SYMBOL]?: boolean; +} + +export interface SqliteConnectionOptions { + /** + * Path to the SQLite database file + * + * Special values: + * - ':memory:': to use a temporary in-memory database. + * - '': to create a temporary disk-based database. + * + * Note: temporary databases require the pool to have the following configuration: + * - max size: 1 + * - idle timeout: Infinity + * - max uses per resource: Infinity + * + * @default 'sequelize.sqlite' in your current working directory + */ + storage?: string; + + /** + * The mode to open the database connection with. + * + * An integer flag that can be a combination of the following values: + * - OPEN_CREATE + * - OPEN_READONLY + * - OPEN_READWRITE + * - OPEN_SHAREDCACHE + * - OPEN_PRIVATECACHE + * - OPEN_FULLMUTEX + * - OPEN_URI + * + * This package exports each of these values + * + * @example + * ```ts + * import { SqliteDialect, OPEN_CREATE, OPEN_READWRITE } from '@sequelize/sqlite3'; + * + * new Sequelize({ + * dialect: SqliteDialect, + * storage: 'db.sqlite', + * mode: OPEN_CREATE | OPEN_READWRITE, + * }); + * ``` + */ + mode?: number; + + /** + * The "PRAGMA KEY" password to use for the connection. + */ + password?: string; +} + +export { + OPEN_CREATE, + OPEN_FULLMUTEX, + OPEN_PRIVATECACHE, + OPEN_READONLY, + OPEN_READWRITE, + OPEN_SHAREDCACHE, + OPEN_URI, +} from 'sqlite3'; + +export class SqliteConnectionManager extends AbstractConnectionManager< + SqliteDialect, + SqliteConnection +> { + readonly #lib: Sqlite3Module; + + constructor(dialect: SqliteDialect) { + super(dialect); + + this.#lib = this.dialect.options.sqlite3Module ?? Sqlite3; + } + + async connect(options: ConnectionOptions): Promise { + // Using ?? instead of || is important because an empty string signals to SQLite to create a temporary disk-based database. + const storage = options.storage ?? path.join(process.cwd(), 'sequelize.sqlite'); + const inMemory = storage === ':memory:'; + + const isTemporaryStorage = inMemory || storage === ''; + if (isTemporaryStorage) { + const pool = this.sequelize.pool; + const writePool = pool.write; + + // @ts-expect-error -- PR sequelize-pool to expose `idleTimeoutMillis` + if (writePool.idleTimeoutMillis !== Infinity) { + throw new Error(`SQLite is configured to use a temporary database, but the pool is configured to close idle connections, which would lead to data loss while the application is running. +To fix this, set the pool's idleTimeoutMillis to Infinity, or use a non-temporary database.`); + } + + // @ts-expect-error -- PR sequelize-pool to expose `maxUsesPerResource` + if (writePool.maxUsesPerResource !== Infinity) { + // @ts-expect-error -- PR sequelize-pool to expose `maxUsesPerResource` + throw new Error(`SQLite is configured to use a temporary database, but the pool is configured to close connections after ${writePool.maxUsesPerResources}, which would lead to data loss while the application is running. +To fix this, set the pool's maxUsesPerResource to Infinity, or use a non-temporary database.`); + } + + if (writePool.maxSize !== 1) { + throw new Error(`SQLite is configured to use a temporary database, but the pool is configured to allow more than one connection, which would create separate temporary databases. +To fix this, set the pool's maxSize to 1, or use a non-temporary database.`); + } + + if (pool.read) { + throw new Error(`SQLite is configured to use a temporary database, but read-replication is enabled, which would read a different temporary database. +To fix this, disable read replication, or use a non-temporary database.`); + } + } + + const defaultReadWriteMode = this.#lib.OPEN_READWRITE | this.#lib.OPEN_CREATE; + const readWriteMode = options.mode ?? defaultReadWriteMode; + + const storageDir = path.dirname(storage); + + if ( + !isTemporaryStorage && + (readWriteMode & this.#lib.OPEN_CREATE) !== 0 && + !(await checkFileExists(storageDir)) + ) { + // automatic path provision for `options.storage` + await fs.mkdir(storageDir, { recursive: true }); + } + + const connection = await new Promise((resolve, reject) => { + const connectionInstance = new this.#lib.Database( + storage, + readWriteMode, + (err: Error | null) => { + if (err) { + return void reject(new ConnectionError(err)); + } + + debug(`sqlite connection acquired`); + + resolve(connectionInstance); + }, + ) as SqliteConnection; + }); + + if (options.password) { + // Make it possible to define and use password for sqlite encryption plugin like sqlcipher + connection.run(`PRAGMA KEY=${this.sequelize.escape(options.password)}`); + } + + if (this.dialect.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; + } + + validate(connection: SqliteConnection): boolean { + return !connection[CLOSED_SYMBOL]; + } + + async disconnect(connection: SqliteConnection): Promise { + if (connection[CLOSED_SYMBOL]) { + return; + } + + return new Promise((resolve, reject) => { + connection.close(err => { + if (err) { + return reject(err); + } + + debug(`sqlite connection released`); + + connection[CLOSED_SYMBOL] = true; + + resolve(); + }); + }); + } +} diff --git a/packages/sqlite3/src/dialect.ts b/packages/sqlite3/src/dialect.ts new file mode 100644 index 000000000000..33220682ecb4 --- /dev/null +++ b/packages/sqlite3/src/dialect.ts @@ -0,0 +1,138 @@ +import type { Sequelize } from '@sequelize/core'; +import { AbstractDialect } from '@sequelize/core'; +import { createNamedParamBindCollector } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import { getSynchronizedTypeKeys } from '@sequelize/utils'; +import * as DataTypes from './_internal/data-types-overrides.js'; +import type { Sqlite3Module, SqliteConnectionOptions } from './connection-manager.js'; +import { SqliteConnectionManager } from './connection-manager.js'; +import { SqliteQueryGenerator } from './query-generator.js'; +import { SqliteQueryInterface } from './query-interface.js'; +import { SqliteQuery } from './query.js'; + +export interface SqliteDialectOptions { + /** + * If set to false, SQLite will not enforce foreign keys. + * + * @default true + */ + foreignKeys?: boolean; + + /** + * The sqlite3 library to use. + * If not provided, the sqlite3 npm library will be used. + * Must be compatible with the sqlite3 npm library API. + * + * Using this option should only be considered as a last resort, + * as the Sequelize team cannot guarantee its compatibility. + */ + sqlite3Module?: Sqlite3Module; +} + +const DIALECT_OPTION_NAMES = getSynchronizedTypeKeys({ + foreignKeys: undefined, + sqlite3Module: undefined, +}); + +const CONNECTION_OPTION_NAMES = getSynchronizedTypeKeys({ + storage: undefined, + password: undefined, + mode: undefined, +}); + +export class SqliteDialect extends AbstractDialect { + static supports = AbstractDialect.extendSupport({ + DEFAULT: false, + 'DEFAULT VALUES': true, + 'UNION ALL': false, + 'RIGHT JOIN': false, + returnValues: 'returning', + inserts: { + ignoreDuplicates: ' OR IGNORE', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET', + conflictFields: true, + onConflictWhere: true, + }, + index: { + using: false, + where: true, + functionBased: true, + }, + startTransaction: { + useBegin: true, + transactionType: true, + }, + constraints: { + foreignKeyChecksDisableable: true, + add: false, + remove: false, + }, + groupedLimit: false, + dataTypes: { + CHAR: false, + COLLATE_BINARY: true, + CITEXT: true, + DECIMAL: false, + // sqlite3 doesn't give us a way to do sql type-based parsing, *and* returns bigints as js numbers. + // issue: https://github.com/TryGhost/node-sqlite3/issues/922 + BIGINT: false, + JSON: true, + }, + // TODO: add support for JSON operations https://www.sqlite.org/json1.html (bundled in sqlite3) + // be careful: json_extract, ->, and ->> don't have the exact same meanings as mysql & mariadb + jsonOperations: false, + jsonExtraction: { + unquoted: false, + quoted: false, + }, + truncate: { + restartIdentity: false, + }, + delete: { + limit: false, + }, + }); + + readonly Query = SqliteQuery; + readonly connectionManager: SqliteConnectionManager; + readonly queryGenerator: SqliteQueryGenerator; + readonly queryInterface: SqliteQueryInterface; + + constructor(sequelize: Sequelize, options: SqliteDialectOptions) { + super({ + identifierDelimiter: '`', + options, + dataTypeOverrides: DataTypes, + sequelize, + minimumDatabaseVersion: '3.8.0', + dataTypesDocumentationUrl: 'https://www.sqlite.org/datatype3.html', + name: 'sqlite3', + }); + + this.connectionManager = new SqliteConnectionManager(this); + this.queryGenerator = new SqliteQueryGenerator(this); + this.queryInterface = new SqliteQueryInterface(this); + } + + parseConnectionUrl(): SqliteConnectionOptions { + throw new Error( + 'The "url" option is not supported in SQLite. Please use the "storage" option instead.', + ); + } + + createBindCollector() { + return createNamedParamBindCollector('$'); + } + + getDefaultSchema(): string { + // Our SQLite implementation doesn't support schemas + return ''; + } + + static getSupportedOptions() { + return DIALECT_OPTION_NAMES; + } + + static getSupportedConnectionOptions(): readonly string[] { + return CONNECTION_OPTION_NAMES; + } +} diff --git a/packages/sqlite3/src/index.mjs b/packages/sqlite3/src/index.mjs new file mode 100644 index 000000000000..d3aa1d079ef4 --- /dev/null +++ b/packages/sqlite3/src/index.mjs @@ -0,0 +1,14 @@ +import Pkg from './index.js'; + +export const SqliteConnectionManager = Pkg.SqliteConnectionManager; +export const SqliteDialect = Pkg.SqliteDialect; +export const SqliteQueryGenerator = Pkg.SqliteQueryGenerator; +export const SqliteQueryInterface = Pkg.SqliteQueryInterface; +export const SqliteQuery = Pkg.SqliteQuery; +export const OPEN_CREATE = Pkg.OPEN_CREATE; +export const OPEN_FULLMUTEX = Pkg.OPEN_FULLMUTEX; +export const OPEN_PRIVATECACHE = Pkg.OPEN_PRIVATECACHE; +export const OPEN_READONLY = Pkg.OPEN_READONLY; +export const OPEN_READWRITE = Pkg.OPEN_READWRITE; +export const OPEN_SHAREDCACHE = Pkg.OPEN_SHAREDCACHE; +export const OPEN_URI = Pkg.OPEN_URI; diff --git a/packages/sqlite3/src/index.ts b/packages/sqlite3/src/index.ts new file mode 100644 index 000000000000..a1ec0d422ab1 --- /dev/null +++ b/packages/sqlite3/src/index.ts @@ -0,0 +1,8 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './connection-manager.js'; +export * from './dialect.js'; +export * from './query-generator.js'; +export * from './query-interface.js'; +export * from './query-interface.types.js'; +export * from './query.js'; diff --git a/packages/sqlite3/src/query-generator-typescript.internal.ts b/packages/sqlite3/src/query-generator-typescript.internal.ts new file mode 100644 index 000000000000..ea9d3acabd21 --- /dev/null +++ b/packages/sqlite3/src/query-generator-typescript.internal.ts @@ -0,0 +1,321 @@ +import type { + BulkDeleteQueryOptions, + GetConstraintSnippetQueryOptions, + ListTablesQueryOptions, + RemoveColumnQueryOptions, + RemoveIndexQueryOptions, + ShowConstraintsQueryOptions, + StartTransactionQueryOptions, + TableOrModel, + TruncateTableQueryOptions, +} from '@sequelize/core'; +import { AbstractQueryGenerator, IsolationLevel } from '@sequelize/core'; +import { + LIST_TABLES_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { joinSQLFragments } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/join-sql-fragments.js'; +import { extractModelDefinition } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/model-utils.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { generateIndexName } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js'; +import { randomBytes } from 'node:crypto'; +import type { SqliteDialect } from './dialect.js'; +import { SqliteQueryGeneratorInternal } from './query-generator.internal.js'; +import type { SqliteColumnsDescription } from './query-interface.types.js'; + +const REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS = new Set(['ifExists']); +const TRUNCATE_TABLE_QUERY_SUPPORTED_OPTIONS = new Set([ + 'restartIdentity', +]); + +/** + * Temporary class to ease the TypeScript migration + */ +export class SqliteQueryGeneratorTypeScript extends AbstractQueryGenerator { + readonly #internals: SqliteQueryGeneratorInternal; + + constructor( + dialect: SqliteDialect, + internals: SqliteQueryGeneratorInternal = new SqliteQueryGeneratorInternal(dialect), + ) { + super(dialect, internals); + + this.#internals = internals; + } + + describeTableQuery(tableName: TableOrModel) { + return `PRAGMA TABLE_INFO(${this.quoteTable(tableName)})`; + } + + describeCreateTableQuery(tableName: TableOrModel) { + return `SELECT sql FROM sqlite_master WHERE tbl_name = ${this.escapeTable(tableName)};`; + } + + listTablesQuery(options?: ListTablesQueryOptions) { + if (options) { + rejectInvalidOptions( + 'listTablesQuery', + this.dialect, + LIST_TABLES_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + return "SELECT name AS `tableName` FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence'"; + } + + truncateTableQuery(tableName: TableOrModel, options?: TruncateTableQueryOptions) { + if (options) { + rejectInvalidOptions( + 'truncateTableQuery', + this.dialect, + TRUNCATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + TRUNCATE_TABLE_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + const sql = [`DELETE FROM ${this.quoteTable(tableName)}`]; + if (options?.restartIdentity) { + sql.push( + `DELETE FROM ${this.quoteTable('sqlite_sequence')} WHERE ${this.quoteIdentifier('name')} = ${this.escapeTable(tableName)}`, + ); + } + + return sql; + } + + showConstraintsQuery(tableName: TableOrModel, _options?: ShowConstraintsQueryOptions) { + return joinSQLFragments([ + 'SELECT sql FROM sqlite_master', + `WHERE tbl_name = ${this.escapeTable(tableName)}`, + ]); + } + + showIndexesQuery(tableName: TableOrModel) { + return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`; + } + + getToggleForeignKeyChecksQuery(enable: boolean): string { + return `PRAGMA foreign_keys = ${enable ? 'ON' : 'OFF'}`; + } + + renameColumnQuery( + _tableName: TableOrModel, + _attrNameBefore: string, + _attrNameAfter: string, + _attributes: SqliteColumnsDescription, + ): string { + throw new Error(`renameColumnQuery is not supported in ${this.dialect.name}.`); + } + + removeColumnQuery( + _table: TableOrModel, + _columnName: string, + _options?: RemoveColumnQueryOptions, + ): string { + throw new Error(`removeColumnQuery is not supported in ${this.dialect.name}.`); + } + + removeIndexQuery( + tableName: TableOrModel, + indexNameOrAttributes: string | string[], + options?: RemoveIndexQueryOptions, + ) { + if (options) { + rejectInvalidOptions( + 'removeIndexQuery', + this.dialect, + REMOVE_INDEX_QUERY_SUPPORTABLE_OPTIONS, + REMOVE_INDEX_QUERY_SUPPORTED_OPTIONS, + options, + ); + } + + let indexName: string; + if (Array.isArray(indexNameOrAttributes)) { + const table = this.extractTableDetails(tableName); + indexName = generateIndexName(table, { fields: indexNameOrAttributes }); + } else { + indexName = indexNameOrAttributes; + } + + return joinSQLFragments([ + 'DROP INDEX', + options?.ifExists ? 'IF EXISTS' : '', + this.quoteIdentifier(indexName), + ]); + } + + // SQLite does not support renaming columns. The following is a workaround. + _replaceColumnQuery( + tableName: TableOrModel, + attrNameBefore: string, + attrNameAfter: string, + attributes: SqliteColumnsDescription, + ) { + const table = this.extractTableDetails(tableName); + const backupTable = this.extractTableDetails( + `${table.tableName}_${randomBytes(8).toString('hex')}`, + table, + ); + const quotedTableName = this.quoteTable(table); + const quotedBackupTableName = this.quoteTable(backupTable); + + const tableAttributes = this.attributesToSQL(attributes); + const attributeNamesImport = Object.keys(tableAttributes) + .map(attr => { + return attrNameAfter === attr + ? `${this.quoteIdentifier(attrNameBefore)} AS ${this.quoteIdentifier(attr)}` + : this.quoteIdentifier(attr); + }) + .join(', '); + const attributeNamesExport = Object.keys(tableAttributes) + .map(attr => this.quoteIdentifier(attr)) + .join(', '); + + return [ + this.createTableQuery(backupTable, tableAttributes), + `INSERT INTO ${quotedBackupTableName} SELECT ${attributeNamesImport} FROM ${quotedTableName};`, + `DROP TABLE ${quotedTableName};`, + this.createTableQuery(table, tableAttributes), + `INSERT INTO ${quotedTableName} SELECT ${attributeNamesExport} FROM ${quotedBackupTableName};`, + `DROP TABLE ${quotedBackupTableName};`, + ]; + } + + // SQLite has limited ALTER TABLE capapibilites which requires the below workaround involving recreating tables. + // This leads to issues with losing data or losing foreign key references. + _replaceTableQuery( + tableName: TableOrModel, + attributes: SqliteColumnsDescription, + createTableSql?: string, + ) { + const table = this.extractTableDetails(tableName); + const backupTable = this.extractTableDetails( + `${table.tableName}_${randomBytes(8).toString('hex')}`, + table, + ); + const quotedTableName = this.quoteTable(table); + const quotedBackupTableName = this.quoteTable(backupTable); + + const tableAttributes = this.attributesToSQL(attributes); + const attributeNames = Object.keys(tableAttributes) + .map(attr => this.quoteIdentifier(attr)) + .join(', '); + + const backupTableSql = createTableSql + ? `${createTableSql.replace(`CREATE TABLE ${quotedTableName}`, `CREATE TABLE ${quotedBackupTableName}`)};` + : this.createTableQuery(backupTable, tableAttributes); + + return [ + backupTableSql, + `INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`, + `DROP TABLE ${quotedTableName};`, + `ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`, + ]; + } + + private escapeTable(tableName: TableOrModel): string { + const table = this.extractTableDetails(tableName); + + if (table.schema) { + return this.escape(`${table.schema}${table.delimiter}${table.tableName}`); + } + + return this.escape(table.tableName); + } + + versionQuery() { + return 'SELECT sqlite_version() as `version`'; + } + + tableExistsQuery(tableName: TableOrModel): string { + return `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${this.escapeTable(tableName)}`; + } + + /** + * Generates an SQL query to check if there are any foreign key violations in the db schema + * + * @param tableName + */ + foreignKeyCheckQuery(tableName: TableOrModel) { + return `PRAGMA foreign_key_check(${this.quoteTable(tableName)});`; + } + + setIsolationLevelQuery(isolationLevel: IsolationLevel): string { + switch (isolationLevel) { + case IsolationLevel.REPEATABLE_READ: + throw new Error( + `The ${isolationLevel} isolation level is not supported by ${this.dialect.name}.`, + ); + case IsolationLevel.READ_UNCOMMITTED: + return 'PRAGMA read_uncommitted = 1'; + case IsolationLevel.READ_COMMITTED: + throw new Error( + `The ${isolationLevel} isolation level is not supported by ${this.dialect.name}.`, + ); + case IsolationLevel.SERIALIZABLE: + return 'PRAGMA read_uncommitted = 0'; + default: + throw new Error(`Unknown isolation level: ${isolationLevel}`); + } + } + + startTransactionQuery(options?: StartTransactionQueryOptions): string { + if (options) { + rejectInvalidOptions( + 'startTransactionQuery', + this.dialect, + START_TRANSACTION_QUERY_SUPPORTABLE_OPTIONS, + this.dialect.supports.startTransaction, + options, + ); + } + + return joinSQLFragments([ + 'BEGIN', + // Use the transaction type from the options, or the default transaction type from the dialect + options?.transactionType ?? this.sequelize.options.transactionType, + 'TRANSACTION', + ]); + } + + bulkDeleteQuery(tableOrModel: TableOrModel, options: BulkDeleteQueryOptions) { + const table = this.quoteTable(tableOrModel); + const modelDefinition = extractModelDefinition(tableOrModel); + const whereOptions = { ...options, model: modelDefinition }; + const whereFragment = whereOptions.where + ? this.whereQuery(whereOptions.where, whereOptions) + : ''; + + if (whereOptions.limit) { + return joinSQLFragments([ + `DELETE FROM ${table} WHERE rowid IN (`, + `SELECT rowid FROM ${table}`, + whereFragment, + this.#internals.addLimitAndOffset(whereOptions), + ')', + ]); + } + + return joinSQLFragments([`DELETE FROM ${table}`, whereFragment]); + } + + /** + * Temporary function until we have moved the query generation of addConstraint here. + * + * @param tableName + * @param options + */ + _TEMPORARY_getConstraintSnippet( + tableName: TableOrModel, + options: GetConstraintSnippetQueryOptions, + ): string { + return this.#internals.getConstraintSnippet(tableName, options); + } +} diff --git a/packages/sqlite3/src/query-generator.d.ts b/packages/sqlite3/src/query-generator.d.ts new file mode 100644 index 000000000000..37b0aac9e325 --- /dev/null +++ b/packages/sqlite3/src/query-generator.d.ts @@ -0,0 +1,3 @@ +import { SqliteQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class SqliteQueryGenerator extends SqliteQueryGeneratorTypeScript {} diff --git a/packages/sqlite3/src/query-generator.internal.ts b/packages/sqlite3/src/query-generator.internal.ts new file mode 100644 index 000000000000..c4cda22876a1 --- /dev/null +++ b/packages/sqlite3/src/query-generator.internal.ts @@ -0,0 +1,23 @@ +import { AbstractQueryGeneratorInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-internal.js'; +import type { AddLimitOffsetOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.internal-types.js'; +import type { SqliteDialect } from './dialect.js'; + +export class SqliteQueryGeneratorInternal< + Dialect extends SqliteDialect = SqliteDialect, +> extends AbstractQueryGeneratorInternal { + addLimitAndOffset(options: AddLimitOffsetOptions) { + let fragment = ''; + if (options.limit != null) { + fragment += ` LIMIT ${this.queryGenerator.escape(options.limit, options)}`; + } else if (options.offset) { + // limit must be specified if offset is specified. + fragment += ` LIMIT -1`; + } + + if (options.offset) { + fragment += ` OFFSET ${this.queryGenerator.escape(options.offset, options)}`; + } + + return fragment; + } +} diff --git a/packages/sqlite3/src/query-generator.js b/packages/sqlite3/src/query-generator.js new file mode 100644 index 000000000000..d58b6276c2d5 --- /dev/null +++ b/packages/sqlite3/src/query-generator.js @@ -0,0 +1,263 @@ +'use strict'; + +import { ParameterStyle } from '@sequelize/core'; +import { + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator.js'; +import { rejectInvalidOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { removeNullishValuesFromHash } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/format.js'; +import { EMPTY_SET } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js'; +import { defaultValueSchemable } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/query-builder-utils.js'; +import { createBindParamGenerator } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import defaults from 'lodash/defaults'; +import each from 'lodash/each'; +import isObject from 'lodash/isObject'; +import { SqliteQueryGeneratorTypeScript } from './query-generator-typescript.internal.js'; + +export class SqliteQueryGenerator extends SqliteQueryGeneratorTypeScript { + createTableQuery(tableName, attributes, options) { + // TODO: add support for 'uniqueKeys' by improving the createTableQuery implementation so it also generates a CREATE UNIQUE INDEX query + if (options) { + rejectInvalidOptions( + 'createTableQuery', + this.dialect, + CREATE_TABLE_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + options ||= {}; + + const primaryKeys = []; + const needsMultiplePrimaryKeys = + Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; + const attrArray = []; + + for (const attr in attributes) { + if (Object.hasOwn(attributes, attr)) { + const dataType = attributes[attr]; + const containsAutoIncrement = dataType.includes('AUTOINCREMENT'); + + let dataTypeString = dataType; + if (dataType.includes('PRIMARY KEY')) { + if (dataType.includes('INT')) { + // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc) + dataTypeString = containsAutoIncrement + ? 'INTEGER PRIMARY KEY AUTOINCREMENT' + : 'INTEGER PRIMARY KEY'; + + if (dataType.includes(' REFERENCES')) { + dataTypeString += dataType.slice(dataType.indexOf(' REFERENCES')); + } + } + + if (needsMultiplePrimaryKeys) { + primaryKeys.push(attr); + if (dataType.includes('NOT NULL')) { + dataTypeString = dataType.replace(' PRIMARY KEY', ''); + } else { + dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); + } + } + } + + attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`); + } + } + + const table = this.quoteTable(tableName); + let attrStr = attrArray.join(', '); + const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); + + // sqlite has a bug where using CONSTRAINT constraint_name UNIQUE during CREATE TABLE + // does not respect the provided constraint name + // and uses sqlite_autoindex_ as the name of the constraint instead. + // CREATE UNIQUE INDEX does not have this issue, so we're using that instead + // + // if (options.uniqueKeys) { + // each(options.uniqueKeys, (columns, indexName) => { + // if (columns.customIndex) { + // if (typeof indexName !== 'string') { + // indexName = generateIndexName(tableName, columns); + // } + // + // attrStr += `, CONSTRAINT ${ + // this.quoteIdentifier(indexName) + // } UNIQUE (${ + // columns.fields.map(field => this.quoteIdentifier(field)).join(', ') + // })`; + // } + // }); + // } + + if (pkString.length > 0) { + attrStr += `, PRIMARY KEY (${pkString})`; + } + + const sql = `CREATE TABLE IF NOT EXISTS ${table} (${attrStr});`; + + return this.replaceBooleanDefaults(sql); + } + + addColumnQuery(table, key, dataType, options) { + if (options) { + rejectInvalidOptions( + 'addColumnQuery', + this.dialect, + ADD_COLUMN_QUERY_SUPPORTABLE_OPTIONS, + EMPTY_SET, + options, + ); + } + + const attributes = {}; + attributes[key] = dataType; + const fields = this.attributesToSQL(attributes, { context: 'addColumn' }); + const attribute = `${this.quoteIdentifier(key)} ${fields[key]}`; + + const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`; + + return this.replaceBooleanDefaults(sql); + } + + updateQuery(tableName, attrValueHash, where, options, attributes) { + options ||= {}; + defaults(options, this.options); + if ('bindParam' in options) { + throw new Error('The bindParam option has been removed. Use parameterStyle instead.'); + } + + attrValueHash = removeNullishValuesFromHash(attrValueHash, options.omitNull, options); + + const modelAttributeMap = Object.create(null); + const values = []; + let suffix = ''; + let bind; + let bindParam; + const parameterStyle = options?.parameterStyle ?? ParameterStyle.BIND; + + if (parameterStyle === ParameterStyle.BIND) { + bind = Object.create(null); + bindParam = createBindParamGenerator(bind); + } + + if (options.returning) { + const returnValues = this.generateReturnValues(attributes, options); + + suffix += returnValues.returningFragment; + + // ensure that the return output is properly mapped to model fields. + options.mapToModel = true; + } + + if (attributes) { + each(attributes, (attribute, key) => { + modelAttributeMap[key] = attribute; + if (attribute.field) { + modelAttributeMap[attribute.field] = attribute; + } + }); + } + + for (const key in attrValueHash) { + const value = attrValueHash[key] ?? null; + + const escapedValue = this.escape(value, { + replacements: options.replacements, + bindParam, + type: modelAttributeMap[key]?.type, + // TODO: model, + }); + + values.push(`${this.quoteIdentifier(key)}=${escapedValue}`); + } + + let query; + 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, undefined, options)})${suffix}`.trim(); + } else { + query = + `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where, whereOptions)}${suffix}`.trim(); + } + + const result = { query }; + if (parameterStyle === ParameterStyle.BIND) { + result.bind = bind; + } + + return result; + } + + attributesToSQL(attributes, options) { + const result = {}; + for (const name in attributes) { + const attribute = attributes[name]; + const columnName = attribute.field || attribute.columnName || name; + + if (isObject(attribute)) { + let sql = attribute.type.toString(); + + if (attribute.allowNull === false) { + sql += ' NOT NULL'; + } + + if (defaultValueSchemable(attribute.defaultValue, this.dialect)) { + // TODO thoroughly check that DataTypes.NOW will properly + // get populated on all databases as DEFAULT value + // i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP + sql += ` DEFAULT ${this.escape(attribute.defaultValue, { ...options, type: attribute.type })}`; + } + + if (attribute.unique === true) { + sql += ' UNIQUE'; + } + + if (attribute.primaryKey) { + sql += ' PRIMARY KEY'; + + if (attribute.autoIncrement) { + sql += ' AUTOINCREMENT'; + } + } + + if (attribute.references) { + const referencesTable = this.quoteTable(attribute.references.table); + + let referencesKey; + if (attribute.references.key) { + referencesKey = this.quoteIdentifier(attribute.references.key); + } else { + referencesKey = this.quoteIdentifier('id'); + } + + sql += ` REFERENCES ${referencesTable} (${referencesKey})`; + + if (attribute.onDelete) { + sql += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; + } + + if (attribute.onUpdate) { + sql += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; + } + } + + result[columnName] = sql; + } else { + result[columnName] = attribute; + } + } + + return result; + } + + replaceBooleanDefaults(sql) { + return sql + .replaceAll(/DEFAULT '?false'?/g, 'DEFAULT 0') + .replaceAll(/DEFAULT '?true'?/g, 'DEFAULT 1'); + } +} diff --git a/packages/sqlite3/src/query-interface.internal.ts b/packages/sqlite3/src/query-interface.internal.ts new file mode 100644 index 000000000000..b0b71eb0110c --- /dev/null +++ b/packages/sqlite3/src/query-interface.internal.ts @@ -0,0 +1,111 @@ +import type { QueryRawOptions, Sequelize, TableOrModel } from '@sequelize/core'; +import { ForeignKeyConstraintError, QueryTypes, TransactionNestMode } from '@sequelize/core'; +import { AbstractQueryInterfaceInternal } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-interface-internal.js'; +import { withSqliteForeignKeysOff } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import type { SqliteDialect } from './dialect.js'; +import type { SqliteQueryGenerator } from './query-generator.js'; +import type { SqliteQueryInterface } from './query-interface.js'; +import type { SqliteColumnsDescription } from './query-interface.types.js'; + +export class SqliteQueryInterfaceInternal extends AbstractQueryInterfaceInternal { + constructor(readonly dialect: SqliteDialect) { + super(dialect); + } + + get #sequelize(): Sequelize { + return this.dialect.sequelize; + } + + get #queryGenerator(): SqliteQueryGenerator { + return this.dialect.queryGenerator; + } + + get #queryInterface(): SqliteQueryInterface { + return this.dialect.queryInterface; + } + + /** + * Alters a table in sqlite. + * Workaround for sqlite's limited alter table support. + * + * @param tableName + * @param columns + * @param options + */ + async alterTableInternal( + tableName: TableOrModel, + columns: SqliteColumnsDescription, + options?: QueryRawOptions, + ): Promise { + const table = this.#queryGenerator.extractTableDetails(tableName); + + await withSqliteForeignKeysOff(this.#sequelize, options, async () => { + await this.#sequelize.transaction( + { + nestMode: TransactionNestMode.savepoint, + transaction: options?.transaction, + }, + async transaction => { + const indexes = await this.#queryInterface.showIndex(tableName, { + ...options, + transaction, + }); + + for (const index of indexes) { + // This index is reserved by SQLite, we can't add it through addIndex and must use "UNIQUE" on the column definition instead. + if (!index.name.startsWith('sqlite_autoindex_')) { + continue; + } + + if (!index.unique) { + continue; + } + + for (const field of index.fields) { + if (columns[field.attribute]) { + columns[field.attribute].unique = true; + } + } + } + + const sql = this.#queryGenerator._replaceTableQuery(tableName, columns); + await this.executeQueriesSequentially(sql, { ...options, transaction, raw: true }); + + // Run a foreign keys integrity check + const foreignKeyCheckResult = await this.#sequelize.queryRaw( + this.#queryGenerator.foreignKeyCheckQuery(tableName), + { + ...options, + transaction, + type: QueryTypes.SELECT, + }, + ); + + if (foreignKeyCheckResult.length > 0) { + // There are foreign key violations, exit + throw new ForeignKeyConstraintError({ + message: `Foreign key violations detected: ${JSON.stringify(foreignKeyCheckResult, null, 2)}`, + table: table.tableName, + }); + } + + await Promise.all( + indexes.map(async index => { + // This index is reserved by SQLite, we can't add it through addIndex and must use "UNIQUE" on the column definition instead. + if (index.name.startsWith('sqlite_autoindex_')) { + return; + } + + return this.#sequelize.queryInterface.addIndex(tableName, { + ...index, + type: undefined, + transaction, + fields: index.fields.map(field => field.attribute), + }); + }), + ); + }, + ); + }); + } +} diff --git a/packages/sqlite3/src/query-interface.ts b/packages/sqlite3/src/query-interface.ts new file mode 100644 index 000000000000..b89e1b4205fe --- /dev/null +++ b/packages/sqlite3/src/query-interface.ts @@ -0,0 +1,539 @@ +import type { + AddConstraintOptions, + AttributeOptions, + ConstraintDescription, + ConstraintType, + DataType, + DescribeTableOptions, + QiDropAllTablesOptions, + QueryRawOptions, + RemoveColumnOptions, + RemoveConstraintOptions, + ShowConstraintsOptions, + TableOrModel, +} from '@sequelize/core'; +import { + AbstractQueryInterface, + BaseError, + QueryTypes, + UnknownConstraintError, +} from '@sequelize/core'; +import { isErrorWithStringCode } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/check.js'; +import { + noSchemaDelimiterParameter, + noSchemaParameter, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/deprecations.js'; +import { withSqliteForeignKeysOff } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/sql.js'; +import isEmpty from 'lodash/isEmpty'; +import type { SqliteDialect } from './dialect.js'; +import { SqliteQueryInterfaceInternal } from './query-interface.internal.js'; +import type { SqliteColumnsDescription } from './query-interface.types.js'; + +export class SqliteQueryInterface< + Dialect extends SqliteDialect = SqliteDialect, +> extends AbstractQueryInterface { + readonly #internalQueryInterface: SqliteQueryInterfaceInternal; + + constructor(dialect: Dialect, internalQueryInterface?: SqliteQueryInterfaceInternal) { + internalQueryInterface ??= new SqliteQueryInterfaceInternal(dialect); + + super(dialect, internalQueryInterface); + this.#internalQueryInterface = internalQueryInterface; + } + + async dropAllTables(options?: QiDropAllTablesOptions): Promise { + const skip = options?.skip || []; + const allTables = await this.listTables(options); + const tableNames = allTables.filter(tableName => !skip.includes(tableName.tableName)); + + await withSqliteForeignKeysOff(this.sequelize, options, async () => { + for (const table of tableNames) { + // eslint-disable-next-line no-await-in-loop + await this.dropTable(table, options); + } + }); + } + + async describeTable( + tableName: TableOrModel, + options?: DescribeTableOptions, + ): Promise { + const table = this.queryGenerator.extractTableDetails(tableName); + + if (typeof options === 'string') { + noSchemaParameter(); + table.schema = options; + } + + if (typeof options === 'object' && options !== null) { + if (options.schema) { + noSchemaParameter(); + table.schema = options.schema; + } + + if (options.schemaDelimiter) { + noSchemaDelimiterParameter(); + table.delimiter = options.schemaDelimiter; + } + } + + const sql = this.queryGenerator.describeTableQuery(table); + try { + const data = (await this.sequelize.queryRaw(sql, { + ...options, + type: QueryTypes.DESCRIBE, + })) as SqliteColumnsDescription; + /* + * 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 table ${table.tableName}${table.schema ? ` in schema ${table.schema}` : ''}. Check the table name and schema; remember, they _are_ case sensitive.`, + ); + } + + // This is handled by copying indexes over, + // we don't use "unique" because it creates an index with a name + // we can't control + for (const column of Object.values(data)) { + column.unique = false; + } + + const indexes = await this.showIndex(tableName, options); + for (const index of indexes) { + for (const field of index.fields) { + if (index.unique !== undefined) { + data[field.attribute].unique = index.unique; + } + } + } + + // Sqlite requires the foreign keys added to the column definitions + // when describing a table as this is required in the replaceTableQuery + const foreignKeys = await this.showConstraints(tableName, { + ...options, + constraintType: 'FOREIGN KEY', + }); + for (const foreignKey of foreignKeys) { + for (const [index, columnName] of foreignKey.columnNames!.entries()) { + // Add constraints to column definition + Object.assign(data[columnName], { + references: { + table: foreignKey.referencedTableName, + key: foreignKey.referencedColumnNames!.at(index), + }, + onUpdate: foreignKey.updateAction, + onDelete: foreignKey.deleteAction, + }); + } + } + + return data; + } catch (error) { + if ( + error instanceof BaseError && + isErrorWithStringCode(error.cause) && + error.cause.code === 'ER_NO_SUCH_TABLE' + ) { + throw new Error( + `No description found for table ${table.tableName}${table.schema ? ` in schema ${table.schema}` : ''}. Check the table name and schema; remember, they _are_ case sensitive.`, + ); + } + + throw error; + } + } + + async addConstraint(tableName: TableOrModel, options: AddConstraintOptions): Promise { + 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'); + } + + const constraintSnippet = this.queryGenerator._TEMPORARY_getConstraintSnippet( + tableName, + options, + ); + const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName); + const describeCreateTable = await this.sequelize.queryRaw(describeCreateTableSql, { + ...options, + raw: true, + type: QueryTypes.SELECT, + }); + + if (!describeCreateTable.length || !('sql' in describeCreateTable[0])) { + throw new Error('Unable to find constraints for table. Perhaps the table does not exist?'); + } + + let { sql: createTableSql } = describeCreateTable[0] as { sql: string }; + // Replace double quotes with backticks and ending ')' with constraint snippet + createTableSql = createTableSql + .replaceAll('"', '`') + .replace(/\);?$/, `, ${constraintSnippet})`); + + const fields = await this.describeTable(tableName, options); + const sql = this.queryGenerator._replaceTableQuery(tableName, fields, createTableSql); + await this.#internalQueryInterface.executeQueriesSequentially(sql, { ...options, raw: true }); + } + + async removeConstraint( + tableName: TableOrModel, + constraintName: string, + options?: RemoveConstraintOptions, + ): Promise { + const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName); + const describeCreateTable = await this.sequelize.queryRaw(describeCreateTableSql, { + ...options, + raw: true, + type: QueryTypes.SELECT, + }); + + if (!describeCreateTable.length || !('sql' in describeCreateTable[0])) { + throw new Error('Unable to find constraints for table. Perhaps the table does not exist?'); + } + + const { sql: createTableSql } = describeCreateTable[0] as { sql: string }; + const constraints = await this.showConstraints(tableName, options); + const constraint = constraints.find(c => c.constraintName === constraintName); + + if (!constraint) { + const table = this.queryGenerator.extractTableDetails(tableName); + throw new UnknownConstraintError({ + message: `Constraint ${constraintName} on table ${table.tableName} does not exist`, + constraint: constraintName, + table: table.tableName, + }); + } + + constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName); + let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.definition}`; + + if (constraint.constraintType === 'FOREIGN KEY') { + constraintSnippet = `, CONSTRAINT ${constraint.constraintName} FOREIGN KEY`; + const columns = constraint + .columnNames!.map(columnName => this.queryGenerator.quoteIdentifier(columnName)) + .join(', '); + const referenceTableName = this.queryGenerator.quoteTable(constraint.referencedTableName!); + const referenceTableColumns = constraint + .referencedColumnNames!.map(columnName => this.queryGenerator.quoteIdentifier(columnName)) + .join(', '); + constraintSnippet += ` (${columns})`; + constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableColumns})`; + constraintSnippet += constraint.updateAction ? ` ON UPDATE ${constraint.updateAction}` : ''; + constraintSnippet += constraint.deleteAction ? ` ON DELETE ${constraint.deleteAction}` : ''; + } else if (['PRIMARY KEY', 'UNIQUE'].includes(constraint.constraintType)) { + constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType}`; + const columns = constraint + .columnNames!.map(columnName => this.queryGenerator.quoteIdentifier(columnName)) + .join(', '); + constraintSnippet += ` (${columns})`; + } + + const fields = await this.describeTable(tableName, options); + // Replace double quotes with backticks and remove constraint snippet + const sql = this.queryGenerator._replaceTableQuery( + tableName, + fields, + createTableSql.replaceAll('"', '`').replace(constraintSnippet, ''), + ); + await this.#internalQueryInterface.executeQueriesSequentially(sql, { ...options, raw: true }); + } + + async showConstraints( + tableName: TableOrModel, + options?: ShowConstraintsOptions, + ): Promise { + const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName); + const describeCreateTable = await this.sequelize.queryRaw(describeCreateTableSql, { + ...options, + raw: true, + type: QueryTypes.SELECT, + }); + + if (!describeCreateTable.length || !('sql' in describeCreateTable[0])) { + throw new Error('Unable to find constraints for table. Perhaps the table does not exist?'); + } + + const { sql: createTableSql } = describeCreateTable[0] as { sql: string }; + const match = /CREATE TABLE (?:`|'|")(\S+)(?:`|'|") \((.+)\)/.exec(createTableSql); + const data: ConstraintDescription[] = []; + + if (match) { + const [, constraintTableName, attributeSQL] = match; + const keys = []; + const attributes = []; + const constraints = []; + const sqlAttributes = attributeSQL.split(/,(?![^(]*\))/).map(attr => attr.trim()); + for (const attribute of sqlAttributes) { + if (attribute.startsWith('CONSTRAINT')) { + constraints.push(attribute); + } else if (attribute.startsWith('PRIMARY KEY') || attribute.startsWith('FOREIGN KEY')) { + keys.push(attribute); + } else { + attributes.push(attribute); + } + } + + for (const attribute of attributes) { + const [, column, type] = attribute.match(/`(\S+)` (.+)/) || []; + if (/\bPRIMARY KEY\b/.test(type)) { + data.push({ + constraintSchema: '', + constraintName: 'PRIMARY', + constraintType: 'PRIMARY KEY', + tableSchema: '', + tableName: constraintTableName, + columnNames: [column], + }); + } else if (/\bREFERENCES\b/.test(type)) { + const deleteAction = type.match(/ON DELETE (\w+(?: (?!ON UPDATE)\w+)?)/); + const updateAction = type.match(/ON UPDATE (\w+(?: (?!ON DELETE)\w+)?)/); + const [, referencedTableName, referencedColumnNames] = + type.match(/REFERENCES `(\S+)` \(`(\S+)`\)/) || []; + + data.push({ + constraintSchema: '', + constraintName: 'FOREIGN', + constraintType: 'FOREIGN KEY', + tableSchema: '', + tableName: constraintTableName, + columnNames: [column], + referencedTableSchema: '', + referencedTableName: referencedTableName ?? '', + referencedColumnNames: [referencedColumnNames], + deleteAction: deleteAction?.at(1) ?? '', + updateAction: updateAction?.at(1) ?? '', + }); + } else if (/\bUNIQUE\b/.test(type)) { + data.push({ + constraintSchema: '', + constraintName: 'UNIQUE', + constraintType: 'UNIQUE', + tableSchema: '', + tableName: constraintTableName, + columnNames: [column], + }); + } else if (/\bCHECK\b/.test(type)) { + const definition = type.match(/CHECK (.+)/); + + data.push({ + constraintSchema: '', + constraintName: 'CHECK', + constraintType: 'CHECK', + tableSchema: '', + tableName: constraintTableName, + columnNames: [column], + definition: definition ? (definition[1] ?? '') : '', + }); + } + } + + for (const constraint of constraints) { + const [, constraintName, constraintType, definition] = + constraint.match(/CONSTRAINT (?:`|'|")(\S+)(?:`|'|") (\w+(?: \w+)?) (.+)/) || []; + if (/\bPRIMARY KEY\b/.test(constraint)) { + const columnsMatch = [...definition.matchAll(/`(\S+)`/g)]; + + data.push({ + constraintSchema: '', + constraintName, + constraintType: 'PRIMARY KEY', + tableSchema: '', + tableName: constraintTableName, + columnNames: columnsMatch.map(col => col[1]), + }); + } else if (/\bREFERENCES\b/.test(constraint)) { + const deleteAction = definition.match(/ON DELETE (\w+(?: (?!ON UPDATE)\w+)?)/); + const updateAction = definition.match(/ON UPDATE (\w+(?: (?!ON DELETE)\w+)?)/); + const [, rawColumnNames, referencedTableName, rawReferencedColumnNames] = + definition.match( + /\(([^\s,]+(?:,\s?[^\s,]+)*)\) REFERENCES `(\S+)` \(([^\s,]+(?:,\s?[^\s,]+)*)\)/, + ) || []; + const columnsMatch = [...rawColumnNames.matchAll(/`(\S+)`/g)]; + const referencedColumnNames = [...rawReferencedColumnNames.matchAll(/`(\S+)`/g)]; + + data.push({ + constraintSchema: '', + constraintName, + constraintType: 'FOREIGN KEY', + tableSchema: '', + tableName: constraintTableName, + columnNames: columnsMatch.map(col => col[1]), + referencedTableSchema: '', + referencedTableName: referencedTableName ?? '', + referencedColumnNames: referencedColumnNames.map(col => col[1]), + deleteAction: deleteAction?.at(1) ?? '', + updateAction: updateAction?.at(1) ?? '', + }); + } else if (['CHECK', 'DEFAULT', 'UNIQUE'].includes(constraintType)) { + const columnsMatch = [...definition.matchAll(/`(\S+)`/g)]; + + data.push({ + constraintSchema: '', + constraintName, + constraintType: constraintType as ConstraintType, + tableSchema: '', + tableName: constraintTableName, + ...(constraintType !== 'CHECK' && { columnNames: columnsMatch.map(col => col[1]) }), + ...(constraintType !== 'UNIQUE' && { definition }), + }); + } + } + + for (const key of keys) { + const [, constraintType, rawColumnNames] = + key.match(/(\w+(?: \w+)?)\s?\(([^\s,]+(?:,\s?[^\s,]+)*)\)/) || []; + const columnsMatch = [...rawColumnNames.matchAll(/`(\S+)`/g)]; + const columnNames = columnsMatch.map(col => col[1]); + + if (constraintType === 'PRIMARY KEY') { + data.push({ + constraintSchema: '', + constraintName: 'PRIMARY', + constraintType, + tableSchema: '', + tableName: constraintTableName, + columnNames, + }); + } else if (constraintType === 'FOREIGN KEY') { + const deleteAction = key.match(/ON DELETE (\w+(?: (?!ON UPDATE)\w+)?)/); + const updateAction = key.match(/ON UPDATE (\w+(?: (?!ON DELETE)\w+)?)/); + const [, referencedTableName, rawReferencedColumnNames] = + key.match(/REFERENCES `(\S+)` \(([^\s,]+(?:,\s?[^\s,]+)*)\)/) || []; + const referencedColumnNames = [...rawReferencedColumnNames.matchAll(/`(\S+)`/g)]; + + data.push({ + constraintSchema: '', + constraintName: 'FOREIGN', + constraintType, + tableSchema: '', + tableName: constraintTableName, + columnNames, + referencedTableSchema: '', + referencedTableName, + referencedColumnNames: referencedColumnNames.map(col => col[1]), + deleteAction: deleteAction?.at(1) ?? '', + updateAction: updateAction?.at(1) ?? '', + }); + } + } + } else { + throw new Error(`Could not parse constraints from SQL: ${createTableSql}`); + } + + let constraintData = data; + + if (options?.columnName) { + constraintData = constraintData.filter(constraint => + constraint.columnNames?.includes(options.columnName!), + ); + constraintData = constraintData.map(constraint => { + if (constraint.columnNames) { + constraint.columnNames = constraint.columnNames.filter( + column => column === options.columnName, + ); + } + + return constraint; + }); + } + + if (options?.constraintName) { + constraintData = constraintData.filter( + constraint => constraint.constraintName === options.constraintName, + ); + } + + if (options?.constraintType) { + constraintData = constraintData.filter( + constraint => constraint.constraintType === options.constraintType, + ); + } + + return constraintData; + } + + /** + * 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 tableName + * @param removeColumn + * @param options + */ + async removeColumn( + tableName: TableOrModel, + removeColumn: string, + options?: RemoveColumnOptions, + ): Promise { + const fields = await this.describeTable(tableName, options); + delete fields[removeColumn]; + + await this.#internalQueryInterface.alterTableInternal(tableName, fields, 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 tableName + * @param columnName + * @param dataTypeOrOptions + * @param options + */ + async changeColumn( + tableName: TableOrModel, + columnName: string, + dataTypeOrOptions: DataType | AttributeOptions, + options?: QueryRawOptions, + ): Promise { + const columns = await this.describeTable(tableName, options); + for (const column of Object.values(columns)) { + // This is handled by copying indexes over, + // we don't use "unique" because it creates an index with a name + // we can't control + delete column.unique; + } + + Object.assign(columns[columnName], this.sequelize.normalizeAttribute(dataTypeOrOptions)); + + await this.#internalQueryInterface.alterTableInternal(tableName, columns, options); + } + + /** + * A wrapper that fixes SQLite's inability to rename columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but with a renamed version of the respective column. + * + * @param tableName + * @param attrNameBefore + * @param attrNameAfter + * @param options + */ + async renameColumn( + tableName: TableOrModel, + attrNameBefore: string, + attrNameAfter: string, + options?: QueryRawOptions, + ): Promise { + const fields = await this.assertTableHasColumn(tableName, attrNameBefore, options); + + fields[attrNameAfter] = { ...fields[attrNameBefore] }; + delete fields[attrNameBefore]; + + const sql = this.queryGenerator._replaceColumnQuery( + tableName, + attrNameBefore, + attrNameAfter, + fields, + ); + await this.#internalQueryInterface.executeQueriesSequentially(sql, { ...options, raw: true }); + } +} diff --git a/packages/sqlite3/src/query-interface.types.ts b/packages/sqlite3/src/query-interface.types.ts new file mode 100644 index 000000000000..207caabd9737 --- /dev/null +++ b/packages/sqlite3/src/query-interface.types.ts @@ -0,0 +1,12 @@ +import type { ColumnDescription } from '@sequelize/core'; + +// SQLite needs to validate unqiue columns and foreign keys due to limitations in its ALTER TABLE implementation. +export interface SqliteColumnDescription extends ColumnDescription { + unique?: boolean; + references?: { + table: string; + key: string; + }; +} + +export type SqliteColumnsDescription = Record; diff --git a/packages/sqlite3/src/query.d.ts b/packages/sqlite3/src/query.d.ts new file mode 100644 index 000000000000..ba21c0d9148e --- /dev/null +++ b/packages/sqlite3/src/query.d.ts @@ -0,0 +1,3 @@ +import { AbstractQuery } from '@sequelize/core'; + +export class SqliteQuery extends AbstractQuery {} diff --git a/packages/sqlite3/src/query.js b/packages/sqlite3/src/query.js new file mode 100644 index 000000000000..e7c79b61f8a5 --- /dev/null +++ b/packages/sqlite3/src/query.js @@ -0,0 +1,348 @@ +'use strict'; + +import { + AbstractQuery, + DatabaseError, + EmptyResultError, + ForeignKeyConstraintError, + TimeoutError, + UniqueConstraintError, + ValidationErrorItem, +} from '@sequelize/core'; +import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js'; +import isEqual from 'lodash/isEqual'; +import isPlainObject from 'lodash/isPlainObject'; +import merge from 'lodash/merge'; + +const debug = logger.debugContext('sql:sqlite3'); + +// sqlite3 currently ignores bigint values, so we have to translate to string for now +// There's a WIP here: https://github.com/TryGhost/node-sqlite3/pull/1501 +function stringifyIfBigint(value) { + if (typeof value === 'bigint') { + return value.toString(); + } + + return value; +} + +export class SqliteQuery extends AbstractQuery { + getInsertIdField() { + return 'lastID'; + } + + _collectModels(include, prefix) { + const ret = {}; + + if (include) { + for (const _include of include) { + let key; + if (!prefix) { + key = _include.as; + } else { + key = `${prefix}.${_include.as}`; + } + + ret[key] = _include.model; + + if (_include.include) { + merge(ret, this._collectModels(_include.include, key)); + } + } + } + + return ret; + } + + _handleQueryResponse(metaData, results) { + 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() && !this.isUpsertQuery() && results.length === 0) { + throw new EmptyResultError(); + } + + if (Array.isArray(results) && results[0]) { + for (const attributeOrColumnName of Object.keys(results[0])) { + const modelDefinition = this.model.modelDefinition; + const attribute = modelDefinition.columns.get(attributeOrColumnName); + const updatedValue = this._parseDatabaseValue( + results[0][attributeOrColumnName], + attribute?.type, + ); + + this.instance.set(attribute?.attributeName ?? attributeOrColumnName, updatedValue, { + raw: true, + comesFromDatabase: true, + }); + } + } + } + + if (this.isUpsertQuery()) { + return [this.instance, null]; + } + + return [ + this.instance || (results && ((this.options.plain && results[0]) || results)) || undefined, + this.options.returning ? results.length : metaData.changes, + ]; + } + + if (this.isBulkUpdateQuery()) { + return this.options.returning ? this.handleSelectQuery(results) : metaData.changes; + } + + if (this.isDeleteQuery()) { + return metaData.changes; + } + + if (this.isShowConstraintsQuery()) { + return results; + } + + if (this.isSelectQuery()) { + return this.handleSelectQuery(results); + } + + if (this.isShowOrDescribeQuery()) { + return results; + } + + if (this.sql.includes('PRAGMA INDEX_LIST')) { + return this.handleShowIndexesQuery(results); + } + + if (this.sql.includes('PRAGMA INDEX_INFO')) { + return results; + } + + if (this.sql.includes('PRAGMA TABLE_INFO')) { + // this is the sqlite way of getting the metadata of a table + const result = {}; + + let defaultValue; + for (const _result of results) { + if (_result.dflt_value === null) { + // Column schema omits any "DEFAULT ..." + defaultValue = undefined; + } else if (_result.dflt_value === 'NULL') { + // Column schema is a "DEFAULT NULL" + defaultValue = null; + } else { + defaultValue = _result.dflt_value; + } + + result[_result.name] = { + type: _result.type, + allowNull: _result.notnull === 0, + defaultValue, + primaryKey: _result.pk !== 0, + }; + + if (result[_result.name].type === 'TINYINT(1)') { + result[_result.name].defaultValue = { 0: false, 1: true }[ + result[_result.name].defaultValue + ]; + } + + if (typeof result[_result.name].defaultValue === 'string') { + result[_result.name].defaultValue = result[_result.name].defaultValue.replaceAll("'", ''); + } + } + + return result; + } + + if (this.isRawQuery()) { + return [results, metaData]; + } + + return this.instance; + } + + async run(sql, parameters) { + const conn = this.connection; + this.sql = sql; + const method = this.getDatabaseMethod(); + const complete = this._logQuery(sql, debug, parameters); + + const executeSql = async () => { + if (!parameters) { + parameters = []; + } + + if (isPlainObject(parameters)) { + const newParameters = Object.create(null); + + for (const key of Object.keys(parameters)) { + newParameters[`$${key}`] = stringifyIfBigint(parameters[key]); + } + + parameters = newParameters; + } else { + parameters = parameters.map(stringifyIfBigint); + } + + let response; + try { + if (method === 'run') { + response = await this.#runSeries(conn, sql, parameters); + } else { + response = await this.#allSeries(conn, sql, parameters); + } + } catch (error) { + error.sql = this.sql; + throw this.formatError(error); + } + + complete(); + + return this._handleQueryResponse(response.statement, response.results); + }; + + return executeSql(); + } + + #allSeries(connection, query, parameters) { + return new Promise((resolve, reject) => { + connection.serialize(() => { + connection.all(query, parameters, function (err, results) { + if (err) { + reject(err); + + return; + } + + // node-sqlite3 passes the statement object as `this` to the callback + // eslint-disable-next-line no-invalid-this + resolve({ statement: this, results }); + }); + }); + }); + } + + #runSeries(connection, query, parameters) { + return new Promise((resolve, reject) => { + connection.serialize(() => { + connection.run(query, parameters, function (err, results) { + if (err) { + reject(err); + + return; + } + + // node-sqlite3 passes the statement object as `this` to the callback + // eslint-disable-next-line no-invalid-this + resolve({ statement: this, results }); + }); + }); + }); + } + + formatError(err) { + switch (err.code) { + case 'SQLITE_CONSTRAINT_UNIQUE': + case 'SQLITE_CONSTRAINT_PRIMARYKEY': + case 'SQLITE_CONSTRAINT_TRIGGER': + case 'SQLITE_CONSTRAINT_FOREIGNKEY': + case 'SQLITE_CONSTRAINT': { + if (err.message.includes('FOREIGN KEY constraint failed')) { + return new ForeignKeyConstraintError({ + cause: err, + }); + } + + let fields = []; + + // Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique + let match = err.message.match(/columns (.*?) are/); + if (match !== null && match.length >= 2) { + fields = match[1].split(', '); + } else { + // Sqlite post 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y + match = err.message.match(/UNIQUE constraint failed: (.*)/); + if (match !== null && match.length >= 2) { + fields = match[1].split(', ').map(columnWithTable => columnWithTable.split('.')[1]); + } + } + + const errors = []; + let message = 'Validation error'; + + for (const field of fields) { + errors.push( + new ValidationErrorItem( + this.getUniqueConstraintErrorMessage(field), + 'unique violation', // ValidationErrorItem.Origins.DB, + field, + this.instance && this.instance[field], + this.instance, + 'not_unique', + ), + ); + } + + if (this.model) { + for (const index of this.model.getIndexes()) { + if (index.unique && isEqual(index.fields, fields) && index.msg) { + message = index.msg; + break; + } + } + } + + return new UniqueConstraintError({ message, errors, cause: err, fields }); + } + + case 'SQLITE_BUSY': + return new TimeoutError(err); + + default: + return new DatabaseError(err); + } + } + + async handleShowIndexesQuery(data) { + // Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that! + return Promise.all( + data.reverse().map(async item => { + item.fields = []; + item.primary = false; + item.unique = Boolean(item.unique); + item.constraintName = item.name; + 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; + }), + ); + } + + getDatabaseMethod() { + if ( + this.isBulkUpdateQuery() || + this.isInsertQuery() || + this.isUpdateQuery() || + this.isUpsertQuery() + ) { + return this.options.returning ? 'all' : 'run'; + } + + if ( + this.isDeleteQuery() || + this.sql.toLowerCase().includes('CREATE TEMPORARY TABLE'.toLowerCase()) + ) { + return 'run'; + } + + return 'all'; + } +} diff --git a/packages/sqlite3/tsconfig.json b/packages/sqlite3/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/sqlite3/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/sqlite3/typedoc.json b/packages/sqlite3/typedoc.json new file mode 100644 index 000000000000..a6500efb0151 --- /dev/null +++ b/packages/sqlite3/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "excludeExternals": true +} diff --git a/packages/utils/.eslintrc.js b/packages/utils/.eslintrc.js new file mode 100644 index 000000000000..e13dec291282 --- /dev/null +++ b/packages/utils/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, +}; diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 000000000000..946294502cfd --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,67 @@ +{ + "bugs": "https://github.com/sequelize/sequelize/issues", + "description": "Sequelize Utility Library", + "engines": { + "node": ">=18.20.8" + }, + "types": "./lib/common/index.d.ts", + "exports": { + ".": { + "node": { + "require": { + "types": "./lib/common/index.d.ts", + "default": "./lib/common/index.node.js" + }, + "import": { + "types": "./lib/common/index.d.mts", + "default": "./lib/common/index.node.mjs" + } + }, + "types": "./lib/common/index.d.ts", + "default": "./lib/common/index.js" + }, + "./node": { + "require": { + "types": "./lib/node/index.d.ts", + "default": "./lib/node/index.js" + }, + "import": { + "types": "./lib/node/index.d.mts", + "default": "./lib/node/index.mjs" + } + } + }, + "files": [ + "lib" + ], + "sideEffects": false, + "homepage": "https://sequelize.org", + "license": "MIT", + "name": "@sequelize/utils", + "repository": "https://github.com/sequelize/sequelize", + "scripts": { + "test": "concurrently \"npm:test-*\"", + "build": "../../build-packages.mjs utils", + "test-typings": "tsc --noEmit --project tsconfig.json", + "test-unit": "mocha src/**/*.test.ts -r ../../test/register-esbuild.js", + "test-exports": "../../dev/sync-exports.mjs ./src --check-outdated --multi-entry-points", + "sync-exports": "../../dev/sync-exports.mjs ./src --multi-entry-points" + }, + "type": "commonjs", + "version": "7.0.0-alpha.47", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@types/lodash": "^4.17.21", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@types/chai": "4.3.20", + "@types/mocha": "10.0.10", + "chai": "4.5.0", + "concurrently": "9.2.1", + "expect-type": "0.19.0", + "mocha": "11.7.5" + } +} diff --git a/packages/utils/src/common/__tests__/array-from-async.test.ts b/packages/utils/src/common/__tests__/array-from-async.test.ts new file mode 100644 index 000000000000..393d7e51fa20 --- /dev/null +++ b/packages/utils/src/common/__tests__/array-from-async.test.ts @@ -0,0 +1,17 @@ +import { arrayFromAsync } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('arrayFromAsync', () => { + it('returns an array from an async iterable', async () => { + async function* asyncGenerator() { + yield 1; + yield 2; + // eslint-disable-next-line -- redundant but still needs to be tested + yield Promise.resolve(3); + } + + const result = await arrayFromAsync(asyncGenerator()); + + expect(result).to.deep.eq([1, 2, 3]); + }); +}); diff --git a/packages/utils/src/common/__tests__/clone-deep-plain-values.test.ts b/packages/utils/src/common/__tests__/clone-deep-plain-values.test.ts new file mode 100644 index 000000000000..7ae0f30d19b3 --- /dev/null +++ b/packages/utils/src/common/__tests__/clone-deep-plain-values.test.ts @@ -0,0 +1,38 @@ +import { cloneDeepPlainValues } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('cloneDeepPlainValues', () => { + it('should clone plain values', () => { + const value = { a: 1, b: 2 }; + const clonedValue = cloneDeepPlainValues(value); + expect(clonedValue).to.deep.equal(value); + expect(clonedValue).not.to.equal(value); + }); + + it('should clone arrays', () => { + const value = [1, 2, 3]; + const clonedValue = cloneDeepPlainValues(value); + expect(clonedValue).to.deep.equal(value); + expect(clonedValue).not.to.equal(value); + }); + + it('should clone nested structures', () => { + const value = { a: { b: { c: 1 } } }; + const clonedValue = cloneDeepPlainValues(value); + expect(clonedValue).to.deep.equal(value); + expect(clonedValue).not.to.equal(value); + }); + + it('should transfer unclonable values when flag is set', () => { + const value = { a: new Map() }; + const clonedValue = cloneDeepPlainValues(value, true); + expect(clonedValue).to.deep.equal(value); + expect(clonedValue).not.to.equal(value); + expect(clonedValue.a).to.equal(value.a); + }); + + it('should throw an error when encountering unclonable values and the transfer flag is not set', () => { + const value = { a: new Map() }; + expect(() => cloneDeepPlainValues(value)).to.throw(); + }); +}); diff --git a/packages/utils/src/common/__tests__/freeze-deep.test.ts b/packages/utils/src/common/__tests__/freeze-deep.test.ts new file mode 100644 index 000000000000..fdecbbb60f20 --- /dev/null +++ b/packages/utils/src/common/__tests__/freeze-deep.test.ts @@ -0,0 +1,37 @@ +import { freezeDeep, freezeDescendants } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('freezeDeep', () => { + it('should freeze a plain object', () => { + const obj = { a: 1, b: 2 }; + const result = freezeDeep(obj); + expect(Object.isFrozen(result)).to.equal(true); + }); + + it('should freeze nested objects', () => { + const obj = { a: 1, b: { c: 3 } }; + const result = freezeDeep(obj); + expect(Object.isFrozen(result.b)).to.equal(true); + }); + + it('should not freeze non-plain objects', () => { + const obj = { a: 1, b: new Date() }; + const result = freezeDeep(obj); + expect(Object.isFrozen(result.b)).to.equal(false); + }); +}); + +describe('freezeDescendants', () => { + it('should freeze descendants of an object', () => { + const obj = { a: 1, b: { c: 3 } }; + const result = freezeDescendants(obj); + expect(Object.isFrozen(result)).to.equal(false); + expect(Object.isFrozen(result.b)).to.equal(true); + }); + + it('should not freeze non-plain object descendants', () => { + const obj = { a: new Date() }; + const result = freezeDescendants(obj); + expect(Object.isFrozen(result.a)).to.equal(false); + }); +}); diff --git a/packages/utils/src/common/__tests__/inspect.test.ts b/packages/utils/src/common/__tests__/inspect.test.ts new file mode 100644 index 000000000000..2b3dd3d71529 --- /dev/null +++ b/packages/utils/src/common/__tests__/inspect.test.ts @@ -0,0 +1,32 @@ +import { inspect } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('inspect function', () => { + it('supports primitives', () => { + expect(inspect(123)).to.equal('123'); + expect(inspect(123n)).to.equal('123n'); + expect(inspect(null)).to.equal('null'); + expect(inspect(undefined)).to.equal('undefined'); + expect(inspect(true)).to.equal('true'); + expect(inspect(false)).to.equal('false'); + expect(inspect(Symbol('test'))).to.equal('Symbol(test)'); + expect(inspect('test')).to.equal('"test"'); + }); + + it('returns a function representation when the input is a function', () => { + const input = function test() {}; + + const result = inspect(input); + expect(result).to.equal('[function test]'); + }); + + it('supports anonymous functions', () => { + const result = inspect(() => {}); + expect(result).to.equal('[function (anonymous)]'); + }); + + it('returns an object representation when the input is an object', () => { + expect(inspect({ key: 'value' })).to.equal('[object Object]'); + expect(inspect(new Date())).to.equal('[object Date]'); + }); +}); diff --git a/packages/utils/src/common/__tests__/parallel-for-each.test.ts b/packages/utils/src/common/__tests__/parallel-for-each.test.ts new file mode 100644 index 000000000000..0a9aa0ce987b --- /dev/null +++ b/packages/utils/src/common/__tests__/parallel-for-each.test.ts @@ -0,0 +1,38 @@ +import { parallelForEach } from '@sequelize/utils'; +import { expect } from 'chai'; +import { setTimeout } from 'node:timers/promises'; + +describe('parallelForEach', () => { + it('executes the callbacks in parallel', async () => { + const array = [1, 2, 3]; + const order: number[] = []; + + await parallelForEach(array, async (value, index) => { + await setTimeout((3 - index) * 100); + order.push(value); + }); + + expect(order).to.deep.equal([3, 2, 1]); + }); + + it('treats holes as undefined', async () => { + // eslint-disable-next-line no-sparse-arrays -- Testing sparse arrays + const array = [1, , 3]; + const values: Array = []; + await parallelForEach(array, async value => { + values.push(value); + }); + + expect(values).to.deep.equal([1, undefined, 3]); + }); + + it('should pass the correct index to the callback', async () => { + const array = ['a', 'b', 'c']; + const indices: number[] = []; + await parallelForEach(array, async (_, index) => { + indices.push(index); + }); + + expect(indices).to.deep.equal([0, 1, 2]); + }); +}); diff --git a/packages/utils/src/common/__tests__/shallow-clone-pojo.test.ts b/packages/utils/src/common/__tests__/shallow-clone-pojo.test.ts new file mode 100644 index 000000000000..a13aeed80ea0 --- /dev/null +++ b/packages/utils/src/common/__tests__/shallow-clone-pojo.test.ts @@ -0,0 +1,22 @@ +import { shallowClonePojo } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('shallowClonePojo', () => { + it('returns a shallow copy of the provided object', () => { + const obj = { a: 1, b: 2 }; + const clonedObj = shallowClonePojo(obj); + expect(clonedObj).to.deep.equal(obj); + expect(clonedObj).to.not.equal(obj); + }); + + it('does not copy nested objects', () => { + const obj = { a: 1, b: { c: 3 } }; + const clonedObj = shallowClonePojo(obj); + expect(clonedObj.b).to.equal(obj.b); + }); + + it('throws an error when provided a non-plain object', () => { + const nonPlainObject = new Date(); + expect(() => shallowClonePojo(nonPlainObject)).to.throw(); + }); +}); diff --git a/packages/utils/src/common/_internal/build-parser.ts b/packages/utils/src/common/_internal/build-parser.ts new file mode 100644 index 000000000000..81780be9062c --- /dev/null +++ b/packages/utils/src/common/_internal/build-parser.ts @@ -0,0 +1,51 @@ +import type { NonNullish } from '../types.js'; + +export function buildNullBasedParser( + parseValue: (...value: In) => Out | null, + buildError: (...value: In) => string, +): Parser { + const parse: Parser = (...value: In): Out | null => { + return parseValue(...value); + }; + + parse.orThrow = (...value: In): Out => { + const out = parseValue(...value); + if (out === null) { + throw new ParseError(buildError(...value)); + } + + return out; + }; + + return parse; +} + +export function buildThrowBasedParser( + parseValue: (...value: In) => Out, +): Parser { + const parse: Parser = (...value: In): Out | null => { + try { + return parseValue(...value); + } catch (error) { + if (error instanceof ParseError) { + return null; + } + + throw error; + } + }; + + parse.orThrow = (...value: In): Out => { + return parseValue(...value); + }; + + return parse; +} + +export interface Parser { + (...value: In): Out | null; + + orThrow(...value: In): Out; +} + +export class ParseError extends Error {} diff --git a/packages/utils/src/common/_internal/build-predicate-function.ts b/packages/utils/src/common/_internal/build-predicate-function.ts new file mode 100644 index 000000000000..c22628bb1020 --- /dev/null +++ b/packages/utils/src/common/_internal/build-predicate-function.ts @@ -0,0 +1,71 @@ +export function buildAssertionFunction( + isAssertedType: (value: unknown) => value is AssertedType, + buildError: (value: unknown, shouldEqual: boolean) => string, +): [is: AssertionFunction, isNot: NegatedAssertionFunction] { + const isType = (value: unknown): value is AssertedType => { + return isAssertedType(value); + }; + + const isNotType = (value: Value): value is Exclude => { + return !isAssertedType(value); + }; + + isType.assert = (value: unknown, message?: string): asserts value is AssertedType => { + if (!isType(value)) { + throw new Error(message ?? buildError(value, true)); + } + }; + + const assertIsNotType = ( + value: Value, + message?: string, + ): asserts value is Exclude => { + if (isType(value)) { + throw new Error(message ?? buildError(value, false)); + } + }; + + isNotType.assert = assertIsNotType; + + return [isType, isNotType]; +} + +export type AssertionTuple = [ + is: AssertionFunction, + isNot: NegatedAssertionFunction, +]; + +export interface AssertionFunction { + assert(value: unknown, message?: string): asserts value is AssertedType; + + (value: unknown): value is AssertedType; +} + +export interface NegatedAssertionFunction { + assert(value: Value, message?: string): asserts value is Exclude; + + /** + * For use as a predicate callback. Prefer using `!` instead if you are not using it as a predicate. + * + * @example + * // exclude all strings + * [].filter(isNotString) + */ + (value: Value): value is Exclude; +} + +export function toBe(validValueOrType: string) { + return function buildToBeErrorMessage(value: unknown, shouldEqual: boolean): string { + return buildErrorMessage(validValueOrType, value, shouldEqual); + }; +} + +export function buildErrorMessage( + validValueOrType: string, + value: unknown, + shouldEqual: boolean, +): string { + return `expected value ${shouldEqual ? '' : 'not '}to be ${validValueOrType} but got ${JSON.stringify( + value, + )} instead`; +} diff --git a/packages/utils/src/common/_internal/integer-regexp.ts b/packages/utils/src/common/_internal/integer-regexp.ts new file mode 100644 index 000000000000..5151a5831a7c --- /dev/null +++ b/packages/utils/src/common/_internal/integer-regexp.ts @@ -0,0 +1,77 @@ +const numericSymbols = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', +]; + +export const MAX_RADIX_INCLUSIVE = 36; +export const MIN_RADIX_INCLUSIVE = 2; + +const integerRegExps: Array = Array.from({ length: numericSymbols.length }); + +export function getIsIntegerRegExp(radix: number): RegExp { + if (radix < MIN_RADIX_INCLUSIVE || radix > MAX_RADIX_INCLUSIVE) { + throw new RangeError( + `parseSafeInteger() radix argument must be between ${MIN_RADIX_INCLUSIVE} and ${MAX_RADIX_INCLUSIVE}`, + ); + } + + const existingRegExp = integerRegExps[radix]; + if (existingRegExp) { + return existingRegExp; + } + + /** + * Get all characters that are valid digits in this base (radix) + * + * Example: if radix = 16, characterSet will include [0, 1, ..., e, f] + */ + const characterSet = numericSymbols.slice(0, radix); + + /** + * Construct a regex that matches whether the input is a valid integer in this radix + * + * Example, if radix = 2, the regex will be: + * /^-?[01]+$/i + * + * "i" for case insensitivity + */ + const newRegExp = new RegExp(`^-?[${characterSet.join('')}]+$`, 'i'); + + integerRegExps[radix] = newRegExp; + + return newRegExp; +} diff --git a/packages/utils/src/common/array-from-async.ts b/packages/utils/src/common/array-from-async.ts new file mode 100644 index 000000000000..9e60b038e3a8 --- /dev/null +++ b/packages/utils/src/common/array-from-async.ts @@ -0,0 +1,15 @@ +/** + * Implements https://github.com/tc39/proposal-array-from-async + * This function works like Array.from, but accepts async iterators instead of sync iterators. + * + * @param asyncIterable An async iterable + * @returns A promise that resolves to an array that includes all items yielded by the async iterable. + */ +export async function arrayFromAsync(asyncIterable: AsyncIterable): Promise { + const out = []; + for await (const v of asyncIterable) { + out.push(await v); + } + + return out; +} diff --git a/packages/utils/src/common/clone-deep-plain-values.ts b/packages/utils/src/common/clone-deep-plain-values.ts new file mode 100644 index 000000000000..7d8201cffd44 --- /dev/null +++ b/packages/utils/src/common/clone-deep-plain-values.ts @@ -0,0 +1,40 @@ +import { pojo } from './pojo.js'; +import { isAnyObject } from './predicates/is-any-object.js'; + +/** + * This function is used to create a deep clone of plain values. + * It can handle arrays, plain objects, and primitives. + * For non-plain objects, it either transfers them as is or throws an error, based on the `transferUnclonables` flag. + * + * @param value The value to be cloned. + * @param transferUnclonables A flag indicating whether to transfer unclonable values as is. + * If false, the function will throw an error when encountering an unclonable value. + * @returns The cloned value. + * @throws Throws an error if the function encounters a non-plain object and `transferUnclonables` is false. + */ +export function cloneDeepPlainValues(value: T, transferUnclonables?: boolean): T { + if (Array.isArray(value)) { + return value.map(val => cloneDeepPlainValues(val, transferUnclonables)) as T; + } + + if (isAnyObject(value)) { + const prototype = Object.getPrototypeOf(value); + + if (prototype !== null && prototype !== Object.prototype) { + if (transferUnclonables) { + return value; + } + + throw new Error('This function can only clone plain objects, arrays and primitives'); + } + + const out = pojo() as T; + for (const key of Object.keys(value) as Array) { + out[key] = cloneDeepPlainValues(value[key], transferUnclonables); + } + + return out; + } + + return value; +} diff --git a/packages/utils/src/common/comparators/__tests__/comparators.test.ts b/packages/utils/src/common/comparators/__tests__/comparators.test.ts new file mode 100644 index 000000000000..aa3b39113e16 --- /dev/null +++ b/packages/utils/src/common/comparators/__tests__/comparators.test.ts @@ -0,0 +1,62 @@ +import { SortDirection, basicComparator, localizedStringComparator } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('localizedStringComparator', () => { + it('sorts strings', () => { + const items = ['0', '10', '2', '1']; + + items.sort( + localizedStringComparator('en', SortDirection.ASC, { + numeric: true, + }), + ); + + expect(items).to.deep.eq(['0', '1', '2', '10']); + }); + + it('sorts strings (desc)', () => { + const items = ['0', '10', '2', '1']; + + items.sort( + localizedStringComparator('en', SortDirection.DESC, { + numeric: true, + }), + ); + + expect(items).to.deep.eq(['10', '2', '1', '0']); + }); +}); + +describe('basicComparator', () => { + it('sorts numbers using > & <', () => { + const items = [0, 10, 2, 1]; + + items.sort(basicComparator()); + + expect(items).to.deep.eq([0, 1, 2, 10]); + }); + + it('sorts bigints using > & <', () => { + const items = [0n, 10n, 2n, 1n]; + + items.sort(basicComparator()); + + expect(items).to.deep.eq([0n, 1n, 2n, 10n]); + }); + + it('sorts unlocalized strings using > & <', () => { + const items = ['0', '10', '2', '1']; + + items.sort(basicComparator()); + + expect(items).to.deep.eq(['0', '1', '10', '2']); + }); + + it('sorts Date objects using > & <', () => { + const items = [new Date(0), new Date(10), new Date(2), new Date(1)]; + + items.sort(basicComparator()); + + expect(items).to.deep.eq([new Date(0), new Date(1), new Date(2), new Date(10)]); + }); +}); diff --git a/packages/utils/src/common/comparators/basic-comparator.ts b/packages/utils/src/common/comparators/basic-comparator.ts new file mode 100644 index 000000000000..3f60deda9226 --- /dev/null +++ b/packages/utils/src/common/comparators/basic-comparator.ts @@ -0,0 +1,31 @@ +import type { Comparator } from './comparator.js'; +import { SortDirection } from './comparator.js'; + +/** + * A comparator that uses the comparison operators (>, <) to determine the order of any value. + * + * Works well for: + * - numbers + * - bigints + * - unlocalized strings + * - Date objects + * + * This should be used with caution as other comparators can be better suited to this task. + * For instance, you may prefer to sort strings with {@link localizedStringComparator} instead + * of relying on their unicode codepoints. + * + * @param direction The sort direction. + */ +export function basicComparator(direction: SortDirection = SortDirection.ASC): Comparator { + return function naivelyCompare(a: T, b: T) { + if (a > b) { + return direction; + } + + if (a < b) { + return -direction; + } + + return 0; + }; +} diff --git a/packages/utils/src/common/comparators/comparator.ts b/packages/utils/src/common/comparators/comparator.ts new file mode 100644 index 000000000000..72155ec29ef2 --- /dev/null +++ b/packages/utils/src/common/comparators/comparator.ts @@ -0,0 +1,12 @@ +export type Comparator = (a: T, b: T) => number; + +/** + * Used by comparator functions + * + * @example + * [].sort(localizedStringComparator('en', SortDirection.DESC)); + */ +export enum SortDirection { + ASC = 1, + DESC = -1, +} diff --git a/packages/utils/src/common/comparators/localized-string-comparator.ts b/packages/utils/src/common/comparators/localized-string-comparator.ts new file mode 100644 index 000000000000..9364294e2097 --- /dev/null +++ b/packages/utils/src/common/comparators/localized-string-comparator.ts @@ -0,0 +1,20 @@ +import type { Comparator } from './comparator.js'; +import { SortDirection } from './comparator.js'; + +/** + * Returns a comparator that sorts strings alphabetically using `localeCompare`. + * The collator includes many options to sort numerically, to ignore the case, to ignore accents, etc… + * + * @param locale The locale of the strings + * @param direction The sort direction + * @param options collator options + */ +export function localizedStringComparator( + locale: string, + direction: SortDirection = SortDirection.ASC, + options?: Omit, +): Comparator { + return (a, b) => { + return a.localeCompare(b, locale, { usage: 'sort', ...options }) * direction; + }; +} diff --git a/packages/utils/src/common/consts.ts b/packages/utils/src/common/consts.ts new file mode 100644 index 000000000000..2a2054a9abcc --- /dev/null +++ b/packages/utils/src/common/consts.ts @@ -0,0 +1,16 @@ +import { pojo } from './pojo.js'; +import type { ReadOnlyRecord } from './types.js'; + +/** + * An immutable empty array meant to be used as a default value in places where the value won't be mutated, + * as a way to reduce the memory footprint of that value by not instantiating common values. + */ +export const EMPTY_ARRAY: readonly never[] = Object.freeze([]); + +/** + * An immutable empty object meant to be used as a default value in places where the value won't be mutated, + * as a way to reduce the memory footprint of that value by not instantiating common values. + */ +export const EMPTY_OBJECT: ReadOnlyRecord = + // eslint-disable-next-line -- false positive + Object.freeze(pojo() as Record); diff --git a/packages/utils/src/common/freeze-deep.ts b/packages/utils/src/common/freeze-deep.ts new file mode 100644 index 000000000000..2934cebf376a --- /dev/null +++ b/packages/utils/src/common/freeze-deep.ts @@ -0,0 +1,24 @@ +import { isPlainObject } from './predicates/is-plain-object.js'; + +export function freezeDeep(obj: T): T { + Object.freeze(obj); + + freezeDescendants(obj); + + return obj; +} + +/** + * Only freezes the descendants of an object, not the object itself. + * + * @param obj + */ +export function freezeDescendants(obj: T): T { + for (const descendant of Object.values(obj)) { + if (isPlainObject(descendant) || Array.isArray(descendant)) { + freezeDeep(descendant); + } + } + + return obj; +} diff --git a/packages/utils/src/common/get-immutable-pojo.ts b/packages/utils/src/common/get-immutable-pojo.ts new file mode 100644 index 000000000000..48334a92fae7 --- /dev/null +++ b/packages/utils/src/common/get-immutable-pojo.ts @@ -0,0 +1,18 @@ +import { shallowClonePojo } from './shallow-clone-pojo.js'; + +/** + * Returns a prototype-free, shallow-immutable, version of the provided object, + * without modifying the original object. + * + * If the object already matches the criteria, it is returned as-is. + * + * @param obj - The object. + * @returns The immutable version of the object. + */ +export function getImmutablePojo(obj: T): T { + if (Object.isFrozen(obj) && Object.getPrototypeOf(obj) === null) { + return obj; + } + + return Object.freeze(shallowClonePojo(obj)); +} diff --git a/packages/utils/src/common/get-synchronized-type-keys.ts b/packages/utils/src/common/get-synchronized-type-keys.ts new file mode 100644 index 000000000000..91dc55518741 --- /dev/null +++ b/packages/utils/src/common/get-synchronized-type-keys.ts @@ -0,0 +1,24 @@ +/** + * This strange piece of code is used to get a runtime array of keys that is guaranteed to be in sync with the keys of the provided interface. + * + * @param input + * @example + * ```ts + * export interface Db2DialectOptions { + * db2Module?: Db2Module; + * } + * + * const DIALECT_OPTION_NAMES = getSynchronizedKeys({ + * // if a key is missing, TypeScript will throw an error + * db2Module: undefined, + * }); + * + * // Output is ['db2Module'] + * + * @param input + */ +export function getSynchronizedTypeKeys( + input: Record, +): ReadonlyArray { + return Object.freeze(Object.keys(input) as Array); +} diff --git a/packages/utils/src/common/index.node.mjs b/packages/utils/src/common/index.node.mjs new file mode 100644 index 000000000000..856ef15a513d --- /dev/null +++ b/packages/utils/src/common/index.node.mjs @@ -0,0 +1,54 @@ +import Pkg from './index.node.js'; + +export const arrayFromAsync = Pkg.arrayFromAsync; +export const cloneDeepPlainValues = Pkg.cloneDeepPlainValues; +export const EMPTY_ARRAY = Pkg.EMPTY_ARRAY; +export const EMPTY_OBJECT = Pkg.EMPTY_OBJECT; +export const freezeDeep = Pkg.freezeDeep; +export const freezeDescendants = Pkg.freezeDescendants; +export const inspect = Pkg.inspect; +export const combinedIterator = Pkg.combinedIterator; +export const count = Pkg.count; +export const every = Pkg.every; +export const find = Pkg.find; +export const join = Pkg.join; +export const map = Pkg.map; +export const some = Pkg.some; +export const MapView = Pkg.MapView; +export const MultiMap = Pkg.MultiMap; +export const parallelForEach = Pkg.parallelForEach; +export const parseBigInt = Pkg.parseBigInt; +export const parseFiniteNumber = Pkg.parseFiniteNumber; +export const parseSafeInteger = Pkg.parseSafeInteger; +export const pojo = Pkg.pojo; +export const isAnyObject = Pkg.isAnyObject; +export const isNotAnyObject = Pkg.isNotAnyObject; +export const isBigInt = Pkg.isBigInt; +export const isNotBigInt = Pkg.isNotBigInt; +export const isError = Pkg.isError; +export const isNotError = Pkg.isNotError; +export const isFunction = Pkg.isFunction; +export const isNotFunction = Pkg.isNotFunction; +export const isIterable = Pkg.isIterable; +export const isNotIterable = Pkg.isNotIterable; +export const isNotNullish = Pkg.isNotNullish; +export const isNullish = Pkg.isNullish; +export const isNotNumber = Pkg.isNotNumber; +export const isNumber = Pkg.isNumber; +export const isNotPlainObject = Pkg.isNotPlainObject; +export const isPlainObject = Pkg.isPlainObject; +export const isNotString = Pkg.isNotString; +export const isString = Pkg.isString; +export const isValidIntegerSyntax = Pkg.isValidIntegerSyntax; +export const isValidNumberSyntax = Pkg.isValidNumberSyntax; +export const SetView = Pkg.SetView; +export const shallowClonePojo = Pkg.shallowClonePojo; +export const upcast = Pkg.upcast; +export const getImmutablePojo = Pkg.getImmutablePojo; +export const getSynchronizedTypeKeys = Pkg.getSynchronizedTypeKeys; +export const splitObject = Pkg.splitObject; +export const basicComparator = Pkg.basicComparator; +export const SortDirection = Pkg.SortDirection; +export const localizedStringComparator = Pkg.localizedStringComparator; +export const parseBoolean = Pkg.parseBoolean; +export const intersperse = Pkg.intersperse; diff --git a/packages/utils/src/common/index.node.ts b/packages/utils/src/common/index.node.ts new file mode 100644 index 000000000000..a12954bb0fde --- /dev/null +++ b/packages/utils/src/common/index.node.ts @@ -0,0 +1,44 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './array-from-async.js'; +export * from './clone-deep-plain-values.js'; +export * from './comparators/basic-comparator.js'; +export * from './comparators/comparator.js'; +export * from './comparators/localized-string-comparator.js'; +export * from './consts.js'; +export * from './freeze-deep.js'; +export * from './get-immutable-pojo.js'; +export * from './get-synchronized-type-keys.js'; +export * from './inspect.js'; +export * from './intersperse.js'; +export * from './iterator-utils/combined-iterator.js'; +export * from './iterator-utils/count.js'; +export * from './iterator-utils/every.js'; +export * from './iterator-utils/find.js'; +export * from './iterator-utils/join.js'; +export * from './iterator-utils/map.js'; +export * from './iterator-utils/some.js'; +export * from './map/map-view.node.js'; +export * from './map/multi-map.node.js'; +export * from './parallel-for-each.js'; +export * from './parsers/parse-bigint.js'; +export * from './parsers/parse-boolean.js'; +export * from './parsers/parse-finite-number.js'; +export * from './parsers/parse-safe-integer.js'; +export * from './pojo.js'; +export * from './predicates/is-any-object.js'; +export * from './predicates/is-big-int.js'; +export * from './predicates/is-error.js'; +export * from './predicates/is-function.js'; +export * from './predicates/is-iterable.js'; +export * from './predicates/is-nullish.js'; +export * from './predicates/is-number.js'; +export * from './predicates/is-plain-object.js'; +export * from './predicates/is-string.js'; +export * from './predicates/is-valid-integer-syntax.js'; +export * from './predicates/is-valid-number-syntax.js'; +export * from './set/set-view.node.js'; +export * from './shallow-clone-pojo.js'; +export * from './split-object.js'; +export * from './types.js'; +export * from './upcast.js'; diff --git a/packages/utils/src/common/index.ts b/packages/utils/src/common/index.ts new file mode 100644 index 000000000000..cbaf2f6a1242 --- /dev/null +++ b/packages/utils/src/common/index.ts @@ -0,0 +1,44 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './array-from-async.js'; +export * from './clone-deep-plain-values.js'; +export * from './comparators/basic-comparator.js'; +export * from './comparators/comparator.js'; +export * from './comparators/localized-string-comparator.js'; +export * from './consts.js'; +export * from './freeze-deep.js'; +export * from './get-immutable-pojo.js'; +export * from './get-synchronized-type-keys.js'; +export * from './inspect.js'; +export * from './intersperse.js'; +export * from './iterator-utils/combined-iterator.js'; +export * from './iterator-utils/count.js'; +export * from './iterator-utils/every.js'; +export * from './iterator-utils/find.js'; +export * from './iterator-utils/join.js'; +export * from './iterator-utils/map.js'; +export * from './iterator-utils/some.js'; +export * from './map/map-view.js'; +export * from './map/multi-map.js'; +export * from './parallel-for-each.js'; +export * from './parsers/parse-bigint.js'; +export * from './parsers/parse-boolean.js'; +export * from './parsers/parse-finite-number.js'; +export * from './parsers/parse-safe-integer.js'; +export * from './pojo.js'; +export * from './predicates/is-any-object.js'; +export * from './predicates/is-big-int.js'; +export * from './predicates/is-error.js'; +export * from './predicates/is-function.js'; +export * from './predicates/is-iterable.js'; +export * from './predicates/is-nullish.js'; +export * from './predicates/is-number.js'; +export * from './predicates/is-plain-object.js'; +export * from './predicates/is-string.js'; +export * from './predicates/is-valid-integer-syntax.js'; +export * from './predicates/is-valid-number-syntax.js'; +export * from './set/set-view.js'; +export * from './shallow-clone-pojo.js'; +export * from './split-object.js'; +export * from './types.js'; +export * from './upcast.js'; diff --git a/packages/utils/src/common/inspect.ts b/packages/utils/src/common/inspect.ts new file mode 100644 index 000000000000..8c99b0120dad --- /dev/null +++ b/packages/utils/src/common/inspect.ts @@ -0,0 +1,31 @@ +import { isAnyObject } from './predicates/is-any-object.js'; +import { isBigInt } from './predicates/is-big-int.js'; +import { isFunction } from './predicates/is-function.js'; +import { isString } from './predicates/is-string.js'; + +/** + * Stringify a value, for use in debug messages. + * This is a bare-bones implementation of node:util's inspect method, designed to run in any environment. + * + * @param value The value to stringify + * @returns A string representation of the value + */ +export function inspect(value: unknown): string { + if (isString(value)) { + return JSON.stringify(value); + } + + if (isBigInt(value)) { + return `${String(value)}n`; + } + + if (isFunction(value)) { + return `[function ${value.name || '(anonymous)'}]`; + } + + if (isAnyObject(value)) { + return Object.prototype.toString.call(value); + } + + return String(value); +} diff --git a/packages/utils/src/common/intersperse.ts b/packages/utils/src/common/intersperse.ts new file mode 100644 index 000000000000..ad256502c2d8 --- /dev/null +++ b/packages/utils/src/common/intersperse.ts @@ -0,0 +1,30 @@ +import { isFunction } from './predicates/is-function.js'; + +/** + * Inserts a value between each element of a list. + * `separator` can be a function, which is useful to generate JSX elements with different keys. + * + * @param list The list + * @param separator The value to insert between each element of the list, or a function that will produce each element to insert. + */ +export function intersperse( + list: Value[], + separator: Separator | ((index: number) => Separator), +): Array { + const res: Array = []; + + if (list.length === 0) { + return res; + } + + const separatorIsFunction = isFunction(separator); + + let i = 0; + + res.push(list[i++]); + while (i < list.length) { + res.push(separatorIsFunction ? separator(i) : separator, list[i++]); + } + + return res; +} diff --git a/packages/utils/src/common/iterator-utils/__tests__/iterator-utils.test.ts b/packages/utils/src/common/iterator-utils/__tests__/iterator-utils.test.ts new file mode 100644 index 000000000000..fccb20793a98 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/__tests__/iterator-utils.test.ts @@ -0,0 +1,100 @@ +import { + combinedIterator, + count, + every, + find, + isIterable, + join, + map, + some, +} from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('combinedIterator', () => { + it('chains iterables', () => { + const iter1 = [1, 2, 3]; + const iter2 = new Set([4, 5, 6]); + + const combined = combinedIterator(iter1, iter2); + + isIterable.assert(combined); + + const result = [...combined]; + expect(result).to.deep.eq([1, 2, 3, 4, 5, 6]); + }); +}); + +describe('count', () => { + it('returns the number of elements that match the predicate', () => { + const iter = [1, 2, 3, 4, 5, 6]; + + const result = count(iter, x => x % 2 === 0); + + expect(result).to.eq(3); + }); +}); + +describe('every', () => { + it('returns true if all elements match the predicate', () => { + const iter = [1, 2, 3, 4, 5, 6]; + + const areAllEven = every(iter, x => x % 2 === 0); + const areAllPositive = every(iter, x => x > 0); + + expect(areAllEven).to.be.false; + expect(areAllPositive).to.be.true; + }); + + it('always returns true if the iterable is empty', () => { + const result = every([], () => false); + + expect(result).to.be.true; + }); +}); + +describe('find', () => { + it('returns the first element that matches the predicate', () => { + const iter = [1, 2, 3, 4, 5, 6]; + + const result = find(iter, x => x % 2 === 0); + + expect(result).to.eq(2); + }); +}); + +describe('join', () => { + it('joins the strings of an iterable into a string', () => { + expect(join(['a', 'b', 'c'], '-')).to.eq('a-b-c'); + expect(join([], '-')).to.eq(''); + }); +}); + +describe('map', () => { + it('maps the iterable', () => { + const iter = [1, 2, 3, 4, 5, 6]; + + const result = map(iter, x => x * 2); + + isIterable.assert(result); + + expect([...result]).to.deep.eq([2, 4, 6, 8, 10, 12]); + }); +}); + +describe('some', () => { + it('returns true if at least one element matches the predicate', () => { + const iter = [1, 2, 3, 4, 5, 6]; + + const hasAnEven = some(iter, x => x % 2 === 0); + const hasANegative = some(iter, x => x < 0); + + expect(hasAnEven).to.be.true; + expect(hasANegative).to.be.false; + }); + + it('always returns false if the iterable is empty', () => { + const result = some([], () => true); + + expect(result).to.be.false; + }); +}); diff --git a/packages/utils/src/common/iterator-utils/combined-iterator.ts b/packages/utils/src/common/iterator-utils/combined-iterator.ts new file mode 100644 index 000000000000..df79e486c3a5 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/combined-iterator.ts @@ -0,0 +1,12 @@ +/** + * Combines two iterables, they will be iterated in order + * + * @param iterables + */ +export function* combinedIterator( + ...iterables: Array> +): Generator { + for (const iterable of iterables) { + yield* iterable; + } +} diff --git a/packages/utils/src/common/iterator-utils/count.ts b/packages/utils/src/common/iterator-utils/count.ts new file mode 100644 index 000000000000..a5cda70f74c0 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/count.ts @@ -0,0 +1,22 @@ +/** + * Counts how many elements in the iterable match the predicate. + * + * @param iterable + * @param cb + * @returns how many elements in the iterable match the predicate. + */ +export function count( + iterable: Iterable, + cb: (item: In, index: number) => boolean, +): number { + let i = 0; + let currentCount = 0; + + for (const item of iterable) { + if (cb(item, i++)) { + currentCount++; + } + } + + return currentCount; +} diff --git a/packages/utils/src/common/iterator-utils/every.ts b/packages/utils/src/common/iterator-utils/every.ts new file mode 100644 index 000000000000..8fa88147dcd0 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/every.ts @@ -0,0 +1,9 @@ +export function every(iterable: Iterable, cb: (item: In) => boolean): boolean { + for (const item of iterable) { + if (!cb(item)) { + return false; + } + } + + return true; +} diff --git a/packages/utils/src/common/iterator-utils/find.ts b/packages/utils/src/common/iterator-utils/find.ts new file mode 100644 index 000000000000..057d91cf2b42 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/find.ts @@ -0,0 +1,9 @@ +export function find(iterable: Iterable, cb: (item: Val) => boolean): Val | undefined { + for (const item of iterable) { + if (cb(item)) { + return item; + } + } + + return undefined; +} diff --git a/packages/utils/src/common/iterator-utils/join.ts b/packages/utils/src/common/iterator-utils/join.ts new file mode 100644 index 000000000000..c4e2e0fb4a1c --- /dev/null +++ b/packages/utils/src/common/iterator-utils/join.ts @@ -0,0 +1,16 @@ +export function join(iterable: Iterable, glue: string): string { + const iterator = iterable[Symbol.iterator](); + const first = iterator.next(); + if (first.done) { + return ''; + } + + let result = String(first.value); + + let item; + while (((item = iterator.next()), !item.done)) { + result += glue + String(item.value); + } + + return result; +} diff --git a/packages/utils/src/common/iterator-utils/map.ts b/packages/utils/src/common/iterator-utils/map.ts new file mode 100644 index 000000000000..57b9878ac8d8 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/map.ts @@ -0,0 +1,17 @@ +/** + * Like {@link Array#map}, but works with any iterable. + * + * @param iterable + * @param cb + * @returns an iterator. + */ +export function* map( + iterable: Iterable, + cb: (item: In, index: number) => Out, +): Generator { + let i = 0; + + for (const item of iterable) { + yield cb(item, i++); + } +} diff --git a/packages/utils/src/common/iterator-utils/some.ts b/packages/utils/src/common/iterator-utils/some.ts new file mode 100644 index 000000000000..4889ffaab359 --- /dev/null +++ b/packages/utils/src/common/iterator-utils/some.ts @@ -0,0 +1,9 @@ +export function some(iterable: Iterable, cb: (item: In) => boolean): boolean { + for (const item of iterable) { + if (cb(item)) { + return true; + } + } + + return false; +} diff --git a/packages/utils/src/common/map/__tests__/map-view.test.ts b/packages/utils/src/common/map/__tests__/map-view.test.ts new file mode 100644 index 000000000000..1121a949ca57 --- /dev/null +++ b/packages/utils/src/common/map/__tests__/map-view.test.ts @@ -0,0 +1,86 @@ +import { MapView } from '@sequelize/utils'; +import { expect } from 'chai'; +import NodeUtils from 'node:util'; + +describe('MapView', () => { + const view = new MapView(new Map([['key', 'value']])); + + describe('size', () => { + it('returns the number of elements in the Map', () => { + expect(view.size).to.eq(1); + }); + }); + + describe('get', () => { + it('returns the element associated with the specified key', () => { + expect(view.get('key')).to.eq('value'); + }); + + it('returns undefined if no element is associated with the specified key', () => { + expect(view.get('unknown')).to.be.undefined; + }); + }); + + describe('getOrThrow', () => { + it('returns the element associated with the specified key', () => { + expect(view.getOrThrow('key')).to.eq('value'); + }); + + it('throws an error if no element is associated with the specified key', () => { + expect(() => view.getOrThrow('unknown')).to.throw('No value found for key: unknown'); + }); + }); + + describe('has', () => { + it('returns a boolean indicating whether an element with the specified key exists or not', () => { + expect(view.has('key')).to.be.true; + expect(view.has('unknown')).to.be.false; + }); + }); + + describe('Symbol.iterator', () => { + it('returns an iterator', () => { + expect([...view]).to.eql([['key', 'value']]); + }); + }); + + describe('entries', () => { + it('returns an iterator', () => { + expect([...view.entries()]).to.eql([['key', 'value']]); + }); + }); + + describe('keys', () => { + it('returns an iterator', () => { + expect([...view.keys()]).to.eql(['key']); + }); + }); + + describe('values', () => { + it('returns an iterator', () => { + expect([...view.values()]).to.eql(['value']); + }); + }); + + describe('toMutableMap', () => { + it('returns a new Map', () => { + const map = view.toMutableMap(); + expect(map).to.be.an.instanceOf(Map); + + expect([...map.entries()]).to.deep.eq([...view.entries()]); + }); + }); + + it('reflects mutations done to the original map', () => { + const original = new Map([['key', 'value']]); + const newView = new MapView(original); + + original.set('newKey', 'newValue'); + + expect(newView.get('newKey')).to.eq('newValue'); + }); + + it('is inspectable', () => { + expect(NodeUtils.inspect(view)).to.eq("MapView(1) { 'key' => 'value' }"); + }); +}); diff --git a/packages/utils/src/common/map/__tests__/multi-map.test.ts b/packages/utils/src/common/map/__tests__/multi-map.test.ts new file mode 100644 index 000000000000..37a73b4f3ea9 --- /dev/null +++ b/packages/utils/src/common/map/__tests__/multi-map.test.ts @@ -0,0 +1,190 @@ +import { MultiMap } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('MultiMap', () => { + describe('constructor', () => { + it('ignores duplicate values', () => { + const multiMap = new MultiMap([['key', ['value', 'value']]]); + expect(multiMap.get('key')).to.deep.eq(['value']); + }); + + it('does not store empty values', () => { + const multiMap = new MultiMap([['key', []]]); + expect(multiMap.has('key')).to.eq(false); + expect(multiMap.size).to.eq(0); + }); + }); + + describe('size', () => { + it('returns the number of keys in the Map', () => { + const multiMap = new MultiMap(); + expect(multiMap.size).to.eq(0); + + multiMap.append('key', 'value1'); + expect(multiMap.size).to.eq(1); + + multiMap.append('key', 'value2'); + expect(multiMap.size).to.eq(1); + + multiMap.append('key2', 'value'); + expect(multiMap.size).to.eq(2); + }); + }); + + describe('clear', () => { + it('clears all the keys in the Map', () => { + const multiMap = new MultiMap([['key', ['value']]]); + + multiMap.clear(); + + expect(multiMap.size).to.eq(0); + }); + }); + + describe('append', () => { + it('appends a value to the key', () => { + const multiMap = new MultiMap(); + + multiMap.append('key', 'value1'); + expect(multiMap.get('key')).to.deep.eq(['value1']); + + multiMap.append('key', 'value2'); + expect(multiMap.get('key')).to.deep.eq(['value1', 'value2']); + + // ignores duplicate values + multiMap.append('key', 'value1'); + expect(multiMap.get('key')).to.deep.eq(['value1', 'value2']); + }); + }); + + describe('deleteValue', () => { + it('deletes a value from the key', () => { + const multiMap = new MultiMap([['key', ['value']]]); + + multiMap.deleteValue('key', 'value'); + expect(multiMap.get('key')).to.deep.eq([]); + expect(multiMap.has('key')).to.eq(false); + expect(multiMap.size).to.eq(0); + }); + }); + + describe('delete', () => { + it('deletes a key from the Map', () => { + const multiMap = new MultiMap([['key', ['value']]]); + + multiMap.delete('key'); + expect(multiMap.size).to.eq(0); + expect(multiMap.get('key')).to.deep.eq([]); + }); + }); + + describe('keys', () => { + it('returns the keys of the Map', () => { + const multiMap = new MultiMap([['key', ['value']]]); + + expect([...multiMap.keys()]).to.deep.eq(['key']); + }); + }); + + describe('count', () => { + it('returns the number of values for the key', () => { + const multiMap = new MultiMap([['key', ['value1', 'value2']]]); + + expect(multiMap.count('key')).to.eq(2); + }); + + it('returns 0 if the key does not exist', () => { + const multiMap = new MultiMap(); + + expect(multiMap.count('key')).to.eq(0); + }); + }); + + describe('values', () => { + it('returns the values of the Map', () => { + const multiMap = new MultiMap([['key', ['value1', 'value2']]]); + + expect([...multiMap.values()]).to.deep.eq([['value1', 'value2']]); + }); + }); + + describe('entries', () => { + it('returns the entries of the Map', () => { + const multiMap = new MultiMap([['key', ['value1', 'value2']]]); + + expect([...multiMap.entries()]).to.deep.eq([['key', ['value1', 'value2']]]); + }); + }); + + describe('has', () => { + it('returns true if the key exists', () => { + const multiMap = new MultiMap([['key', ['value']]]); + + expect(multiMap.has('key')).to.eq(true); + }); + + it('returns false if the key does not exist', () => { + const multiMap = new MultiMap(); + + expect(multiMap.has('key')).to.eq(false); + }); + }); + + describe('Symbol.iterator', () => { + it('returns the iterator of the Map', () => { + const multiMap = new MultiMap([['key', ['value1', 'value2']]]); + + expect([...multiMap[Symbol.iterator]()]).to.deep.eq([['key', ['value1', 'value2']]]); + }); + }); + + describe('get', () => { + it('returns the values of the key', () => { + const multiMap = new MultiMap([['key', ['value1', 'value2']]]); + + expect(multiMap.get('key')).to.deep.eq(['value1', 'value2']); + }); + + it('returns an empty array if the key does not exist', () => { + const multiMap = new MultiMap(); + + expect(multiMap.get('key')).to.deep.eq([]); + }); + }); + + describe('set', () => { + it('sets the values of the key', () => { + const multiMap = new MultiMap(); + + multiMap.set('key', ['value1', 'value2']); + expect(multiMap.get('key')).to.deep.eq(['value1', 'value2']); + }); + + it('ignores duplicate values', () => { + const multiMap = new MultiMap(); + + multiMap.set('key', ['value', 'value']); + expect(multiMap.get('key')).to.deep.eq(['value']); + }); + + it('deletes empty values', () => { + const multiMap = new MultiMap(); + + multiMap.set('key', ['value']); + multiMap.set('key', []); + + expect(multiMap.has('key')).to.eq(false); + expect(multiMap.size).to.eq(0); + }); + + it('ignores mutations done after setting the value', () => { + const multiMap = new MultiMap(); + const values = ['value1', 'value2']; + + multiMap.set('key', values); + values.push('value3'); + + expect(multiMap.get('key')).to.deep.eq(['value1', 'value2']); + }); + }); +}); diff --git a/packages/utils/src/common/map/map-view.node.ts b/packages/utils/src/common/map/map-view.node.ts new file mode 100644 index 000000000000..ee794c79eab1 --- /dev/null +++ b/packages/utils/src/common/map/map-view.node.ts @@ -0,0 +1,17 @@ +import NodeUtil, { type InspectOptions } from 'node:util'; +import { pojo } from '../pojo.js'; +import { MapView } from './map-view.js'; + +// @ts-expect-error -- this node-specific extension is not declared on base class +MapView.prototype[NodeUtil.inspect.custom] = function inspect( + depth: number, + options: InspectOptions, +): string { + const newOptions = Object.assign(pojo(), options, { + depth: options.depth == null ? null : options.depth - 1, + }); + + return NodeUtil.inspect(this.toMutableMap(), newOptions).replace(/^Map/, 'MapView'); +}; + +export { MapView } from './map-view.js'; diff --git a/packages/utils/src/common/map/map-view.ts b/packages/utils/src/common/map/map-view.ts new file mode 100644 index 000000000000..4902d31e8880 --- /dev/null +++ b/packages/utils/src/common/map/map-view.ts @@ -0,0 +1,62 @@ +import type { ReadonlyMapLike } from '../types.js'; + +export class MapView implements ReadonlyMapLike { + readonly #target: Map; + + /** + * @returns the number of elements in the Map. + */ + get size(): number { + return this.#target.size; + } + + constructor(target: Map) { + this.#target = target; + } + + /** + * Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map. + * + * @param key + * @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned. + */ + get(key: K): V | undefined { + return this.#target.get(key); + } + + getOrThrow(key: K): V { + if (!this.#target.has(key)) { + throw new Error(`No value found for key: ${key}`); + } + + return this.#target.get(key)!; + } + + /** + * @param key + * @returns boolean indicating whether an element with the specified key exists or not. + */ + has(key: K): boolean { + return this.#target.has(key); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.#target[Symbol.iterator](); + } + + entries(): IterableIterator<[K, V]> { + return this.#target.entries(); + } + + keys(): IterableIterator { + return this.#target.keys(); + } + + values(): IterableIterator { + return this.#target.values(); + } + + toMutableMap(): Map { + return new Map(this.#target); + } +} diff --git a/packages/utils/src/common/map/multi-map.node.ts b/packages/utils/src/common/map/multi-map.node.ts new file mode 100644 index 000000000000..af02df57952a --- /dev/null +++ b/packages/utils/src/common/map/multi-map.node.ts @@ -0,0 +1,17 @@ +import NodeUtil, { type InspectOptions } from 'node:util'; +import { pojo } from '../pojo.js'; +import { MultiMap } from './multi-map.js'; + +// @ts-expect-error -- this node-specific extension is not declared on base class +MultiMap.prototype[NodeUtil.inspect.custom] = function inspect( + depth: number, + options: InspectOptions, +): string { + const newOptions = Object.assign(pojo(), options, { + depth: options.depth == null ? null : options.depth - 1, + }); + + return NodeUtil.inspect(new Map(this), newOptions).replace(/^Map/, 'MultiMap'); +}; + +export { MultiMap } from './multi-map.js'; diff --git a/packages/utils/src/common/map/multi-map.ts b/packages/utils/src/common/map/multi-map.ts new file mode 100644 index 000000000000..e36e362cf283 --- /dev/null +++ b/packages/utils/src/common/map/multi-map.ts @@ -0,0 +1,108 @@ +import uniq from 'lodash/uniq.js'; +import { EMPTY_ARRAY } from '../consts.js'; +import type { Entry, MapLike } from '../types.js'; + +export class MultiMap implements MapLike { + readonly #internalMap = new Map(); + + constructor(entries?: Iterable) { + if (entries) { + for (const [key, values] of entries) { + this.set(key, values); + } + } + } + + get size() { + return this.#internalMap.size; + } + + clear() { + this.#internalMap.clear(); + } + + append(key: K, value: V): this { + const valueSet = this.#internalMap.get(key); + if (valueSet?.includes(value)) { + return this; + } + + const newValue = valueSet ? [...valueSet, value] : [value]; + Object.freeze(newValue); + + this.#internalMap.set(key, newValue); + + return this; + } + + deleteValue(key: K, value: V): boolean { + const valueSet = this.#internalMap.get(key); + if (valueSet == null) { + return false; + } + + const newValueSet = valueSet.filter(val => val !== value); + if (newValueSet.length === valueSet.length) { + return false; + } + + if (newValueSet.length === 0) { + this.#internalMap.delete(key); + + return true; + } + + Object.freeze(newValueSet); + this.#internalMap.set(key, newValueSet); + + return true; + } + + delete(key: K): boolean { + return this.#internalMap.delete(key); + } + + keys(): IterableIterator { + return this.#internalMap.keys(); + } + + count(key: K): number { + const values = this.#internalMap.get(key); + + return values?.length ?? 0; + } + + [Symbol.iterator](): IterableIterator> { + return this.#internalMap[Symbol.iterator](); + } + + entries(): IterableIterator> { + return this.#internalMap.entries(); + } + + get(key: K): readonly V[] { + return this.#internalMap.get(key) ?? EMPTY_ARRAY; + } + + has(key: K): boolean { + return this.#internalMap.has(key); + } + + set(key: K, values: readonly V[]): this { + if (values.length === 0) { + this.#internalMap.delete(key); + + return this; + } + + const uniqueValues = Object.freeze(uniq(values)); + + this.#internalMap.set(key, uniqueValues); + + return this; + } + + values(): IterableIterator { + return this.#internalMap.values(); + } +} diff --git a/packages/utils/src/common/parallel-for-each.ts b/packages/utils/src/common/parallel-for-each.ts new file mode 100644 index 000000000000..2df0dfe54155 --- /dev/null +++ b/packages/utils/src/common/parallel-for-each.ts @@ -0,0 +1,16 @@ +import { map } from './iterator-utils/map.js'; +import type { AllowPromise } from './types.js'; + +/** + * Executes async code in parallel for each entry of an array. + * + * @param iterable The value to iterate + * @param callback The function to call with each entry of the array + * @returns A promise that resolves once each callback is done executing (and their promise resolved) + */ +export async function parallelForEach( + iterable: Iterable, + callback: (value: T, index: number) => AllowPromise, +): Promise { + await Promise.all(map(iterable, callback)); +} diff --git a/packages/utils/src/common/parsers/__tests__/parse-bigint.test.ts b/packages/utils/src/common/parsers/__tests__/parse-bigint.test.ts new file mode 100644 index 000000000000..abb1c57a5600 --- /dev/null +++ b/packages/utils/src/common/parsers/__tests__/parse-bigint.test.ts @@ -0,0 +1,53 @@ +import { parseBigInt } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('parseBigInt', () => { + it('should return null when input is not a valid number syntax', () => { + expect(parseBigInt('not a number')).to.be.null; + // "BigInt" would have returned 0 instead + expect(parseBigInt('')).to.be.null; + expect(parseBigInt('-')).to.be.null; + expect(parseBigInt(' -1')).to.be.null; + }); + + it('should return null when input is an unsafe integer', () => { + expect(parseBigInt(Number.MAX_SAFE_INTEGER + 1)).to.be.null; + expect(parseBigInt(Number.MIN_SAFE_INTEGER - 1)).to.be.null; + }); + + it('should return bigint when input is a safe integer', () => { + expect(parseBigInt(10)).to.deep.equal(10n); + expect(parseBigInt(-10)).to.deep.equal(-10n); + expect(parseBigInt('9007199254740992')).to.deep.equal(9_007_199_254_740_992n); + expect(parseBigInt('-9007199254740992')).to.deep.equal(-9_007_199_254_740_992n); + }); + + it('should return null when input is a non-integer number', () => { + expect(parseBigInt(10.5)).to.be.null; + expect(parseBigInt(Infinity)).to.be.null; + expect(parseBigInt(-Infinity)).to.be.null; + expect(parseBigInt(NaN)).to.be.null; + }); + + it('should return null when input is a non-integer string', () => { + expect(parseBigInt('10.5')).to.be.null; + }); + + it('should return bigint when input is a string representation of an integer', () => { + expect(parseBigInt('10')).to.deep.equal(BigInt(10)); + }); + + it('should return bigint when input is a string representation of a negative integer', () => { + expect(parseBigInt('-10')).to.deep.equal(BigInt(-10)); + }); +}); + +describe('parseBigInt.orThrow', () => { + it('should throw an error if the input cannot be a bigint', () => { + expect(() => parseBigInt.orThrow(Number.MAX_SAFE_INTEGER + 1)).to.throw(); + }); + + it('should return bigint if the input can be a bigint', () => { + expect(parseBigInt.orThrow(10)).to.deep.equal(10n); + }); +}); diff --git a/packages/utils/src/common/parsers/__tests__/parse-finite-number.test.ts b/packages/utils/src/common/parsers/__tests__/parse-finite-number.test.ts new file mode 100644 index 000000000000..4207697528f5 --- /dev/null +++ b/packages/utils/src/common/parsers/__tests__/parse-finite-number.test.ts @@ -0,0 +1,50 @@ +import { parseFiniteNumber } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('parseFiniteNumber', () => { + it('should return null when input is not a valid number syntax', () => { + expect(parseFiniteNumber('not a number')).to.be.null; + // "Number" would have returned 0 instead + expect(parseFiniteNumber('')).to.be.null; + expect(parseFiniteNumber('-')).to.be.null; + expect(parseFiniteNumber(' -1')).to.be.null; + }); + + it('should return null when input is an infinite number', () => { + expect(parseFiniteNumber('Infinity')).to.be.null; + }); + + it('should return a number when input is a valid number string', () => { + expect(parseFiniteNumber('123')).to.equal(123); + expect(parseFiniteNumber('-123')).to.equal(-123); + }); + + it('should return a number when input is a valid number in scientific notation', () => { + expect(parseFiniteNumber('5e1')).to.equal(50); + expect(parseFiniteNumber('-5e1')).to.equal(-50); + }); + + it('should return a number when input is a valid decimal number', () => { + expect(parseFiniteNumber('123.456')).to.equal(123.456); + expect(parseFiniteNumber('-123.456')).to.equal(-123.456); + }); + + it('should return null when input is a BigInt outside of the Safe Integer range', () => { + expect(parseFiniteNumber(BigInt(Number.MAX_SAFE_INTEGER) + 1n)).to.be.null; + }); + + it('should return a number when input is a valid BigInt within the Safe Integer range', () => { + expect(parseFiniteNumber(123n)).to.equal(123); + expect(parseFiniteNumber(-123n)).to.equal(-123); + }); +}); + +describe('parseFiniteNumber.orThrow', () => { + it('throws an error when the input is not parseable as a finite number', () => { + expect(() => parseFiniteNumber.orThrow('not a number')).to.throw(); + }); + + it('returns the parsed number when the input is parseable as a finite number', () => { + expect(parseFiniteNumber.orThrow('123')).to.equal(123); + }); +}); diff --git a/packages/utils/src/common/parsers/__tests__/parse-safe-integer.test.ts b/packages/utils/src/common/parsers/__tests__/parse-safe-integer.test.ts new file mode 100644 index 000000000000..9db42afcc794 --- /dev/null +++ b/packages/utils/src/common/parsers/__tests__/parse-safe-integer.test.ts @@ -0,0 +1,106 @@ +import { parseSafeInteger } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('parseSafeInteger', () => { + it('returns null when input is not a valid integer syntax', () => { + expect(parseSafeInteger('not an integer')).to.be.null; + expect(parseSafeInteger('')).to.be.null; + expect(parseSafeInteger('-')).to.be.null; + expect(parseSafeInteger(' -1')).to.be.null; + }); + + it('returns null when input is an unsafe integer', () => { + expect(parseSafeInteger('9007199254740992')).to.be.null; + expect(parseSafeInteger('-9007199254740992')).to.be.null; + + expect(parseSafeInteger(9_007_199_254_740_992n)).to.be.null; + expect(parseSafeInteger(-9_007_199_254_740_992n)).to.be.null; + }); + + it('returns a number when input is a valid integer string', () => { + expect(parseSafeInteger('123')).to.equal(123); + expect(parseSafeInteger('-123')).to.equal(-123); + }); + + it('returns a number when input is a safe bigint', () => { + expect(parseSafeInteger(123n)).to.equal(123); + expect(parseSafeInteger(-123n)).to.equal(-123); + }); + + it('returns null when input is a non-integer number', () => { + expect(parseSafeInteger('123.456')).to.be.null; + }); + + it('returns a number if the input contains the scientific notation in base 10', () => { + expect(parseSafeInteger('1e3')).to.equal(1e3); + expect(parseSafeInteger('1e3', 10)).to.equal(1e3); + }); + + it('returns null if the input contains the scientific notation in base other than 10', () => { + // note: for radix 15 and above, the letter "e" is a valid digit so this would be a valid number, + // but not one written in the scientific notation. + expect(parseSafeInteger('1e3', 8)).to.equal(null); + }); + + it('returns null if the input contains a numeric separator', () => { + // opt-in support could be added in the future, as well as localized separators + expect(parseSafeInteger('1_000')).to.be.null; + }); + + it('returns a number when input is a valid base 2 integer', () => { + expect(parseSafeInteger('1010', 2)).to.equal(0b1010); + }); + + it('returns null when input is a valid base 2 integer with invalid characters', () => { + expect(parseSafeInteger('10102', 2)).to.be.null; + }); + + it('returns a number when input is a valid base 8 integer', () => { + expect(parseSafeInteger('0755', 8)).to.equal(0o0755); + }); + + it('returns null when input is a valid base 8 integer with invalid characters', () => { + expect(parseSafeInteger('0758', 8)).to.be.null; + }); + + it('returns a number when input is a valid base 16 integer', () => { + expect(parseSafeInteger('ffffff', 16)).to.equal(0xff_ff_ff); + }); + + it('returns null if the number includes a prefix', () => { + // could be supported one day using the "auto" radix (which would support 0x, 0b, 0o prefixes, and base 10 without prefix) + expect(parseSafeInteger('0xffffff', 16)).to.be.null; + expect(parseSafeInteger('0xffffff')).to.be.null; + }); + + it('returns null when input is a valid base 16 integer with invalid characters', () => { + expect(parseSafeInteger('fffg', 16)).to.be.null; + }); + + it('is case insensitive', () => { + expect(parseSafeInteger('Ff', 16)).to.equal(0xff); + }); + + it('returns a number when input is a valid base 36 integer', () => { + expect(parseSafeInteger('z', 36)).to.equal(35); + }); + + it('returns null when input is a valid base 36 integer with invalid characters', () => { + expect(parseSafeInteger('z(', 36)).to.be.null; + }); + + it('throws when radix is less than 2 or more than 36', () => { + expect(() => parseSafeInteger('123', 1)).to.throw(); + expect(() => parseSafeInteger('123', 37)).to.throw(); + }); +}); + +describe('parseSafeInteger.orThrow', () => { + it('throws an error when the input is not parseable as a safe integer', () => { + expect(() => parseSafeInteger.orThrow('not an integer')).to.throw(); + }); + + it('returns the parsed number when the input is parseable as a safe integer', () => { + expect(parseSafeInteger.orThrow('123')).to.equal(123); + }); +}); diff --git a/packages/utils/src/common/parsers/parse-bigint.ts b/packages/utils/src/common/parsers/parse-bigint.ts new file mode 100644 index 000000000000..da67a71cb3d3 --- /dev/null +++ b/packages/utils/src/common/parsers/parse-bigint.ts @@ -0,0 +1,48 @@ +import { buildNullBasedParser } from '../_internal/build-parser.js'; +import { inspect } from '../inspect.js'; +import { isNumber } from '../predicates/is-number.js'; + +/** + * The base 10 regex is special because it accepts the scientific notation + */ +const BASE10_INTEGER_REGEX = + /^(?-?[0-9]*)(\.(?[0-9]+))?([eE](?[-+]?[0-9]+))?$/; + +function parseBigIntInternal(value: string | number): bigint | null { + if (isNumber(value)) { + if (!Number.isSafeInteger(value)) { + return null; + } + + return BigInt(value); + } + + if (value === '') { + return null; + } + + if (!BASE10_INTEGER_REGEX.test(value)) { + return null; + } + + try { + return BigInt(value); + } catch { + return null; + } +} + +/** + * Parses a string as a bigint in base 10. + * + * Unlike {@link parseSafeInteger}, this function does not support specifying the radix; it is always base 10. + * This method supports the scientific notation (e.g. 5e1 produces 50n). + * The Scientific notation is only allowed in base 10. + * + * @param value The string to parse as a safe integer + * @returns The corresponding bigint value + */ +export const parseBigInt = buildNullBasedParser( + parseBigIntInternal, + value => `Cannot convert ${inspect(value)} to a BigInt.`, +); diff --git a/packages/utils/src/common/parsers/parse-boolean.ts b/packages/utils/src/common/parsers/parse-boolean.ts new file mode 100644 index 000000000000..2bb1868df594 --- /dev/null +++ b/packages/utils/src/common/parsers/parse-boolean.ts @@ -0,0 +1,18 @@ +import { buildNullBasedParser } from '../_internal/build-parser.js'; +import { inspect } from '../inspect.js'; + +export const parseBoolean = buildNullBasedParser( + (value: string): boolean | null => { + value = value.toLowerCase(); + + switch (value) { + case 'true': + return true; + case 'false': + return false; + default: + return null; + } + }, + value => `Cannot convert ${inspect(value)} to a boolean. It must be either "true" or "false".`, +); diff --git a/packages/utils/src/common/parsers/parse-finite-number.ts b/packages/utils/src/common/parsers/parse-finite-number.ts new file mode 100644 index 000000000000..89f10d42d8fc --- /dev/null +++ b/packages/utils/src/common/parsers/parse-finite-number.ts @@ -0,0 +1,50 @@ +import type { Parser } from '../_internal/build-parser.js'; +import { buildNullBasedParser } from '../_internal/build-parser.js'; +import { inspect } from '../inspect.js'; +import { isBigInt } from '../predicates/is-big-int.js'; +import { isValidNumberSyntax } from '../predicates/is-valid-number-syntax.js'; + +function parseFiniteNumberInternal(value: string | bigint): number | null { + if (isBigInt(value)) { + if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) { + return null; + } + + return Number(value); + } + + /** + * radix has not been implemented because there is no built-in method we can rely on, + * if it turns out we need it, feel free to implement one. + */ + + if (!isValidNumberSyntax(value)) { + return null; + } + + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + return null; + } + + return parsed; +} + +/** + * Parses a string as a number in base 10. + * + * Unlike {@link parseSafeInteger}, this function does not support specifying the radix, it is always base 10. + * This parser can produce numbers that are not safely representable with the JS number type + * This method will never produce infinite numbers. + * + * This method supports the scientific notation (e.g., 5e1 produces 50) + * + * If you are parsing integers, prefer {@link parseSafeInteger} or {@link parseBigInt} instead. + * + * @param value The string to parse as a floating point number + * @returns null if the input is not a base 10 number + */ +export const parseFiniteNumber: Parser<[value: string | bigint], number> = buildNullBasedParser( + parseFiniteNumberInternal, + value => `Cannot convert ${inspect(value)} to a finite number.`, +); diff --git a/packages/utils/src/common/parsers/parse-safe-integer.ts b/packages/utils/src/common/parsers/parse-safe-integer.ts new file mode 100644 index 000000000000..2f9b1fc4c2dd --- /dev/null +++ b/packages/utils/src/common/parsers/parse-safe-integer.ts @@ -0,0 +1,44 @@ +import { buildNullBasedParser } from '../_internal/build-parser.js'; +import { inspect } from '../inspect.js'; +import { isBigInt } from '../predicates/is-big-int.js'; +import { isNumber } from '../predicates/is-number.js'; +import { isValidIntegerSyntax } from '../predicates/is-valid-integer-syntax.js'; +import { parseFiniteNumber } from './parse-finite-number.js'; + +function parseSafeIntegerInternal( + value: string | bigint | number, + radix: number = 10, +): number | null { + let result: number | null; + if (isNumber(value)) { + result = value; + } else if (isBigInt(value) || radix === 10) { + // delegating to parseNumber as it supports scientific notation & only base 10 is allowed + result = parseFiniteNumber(value); + } else { + if (!isValidIntegerSyntax(value, radix)) { + return null; + } + + result = Number.parseInt(value, radix); + } + + if (!Number.isSafeInteger(result)) { + return null; + } + + return result; +} + +/** + * Parses a string as a safe integer in the specified radix. + * This method supports the scientific notation (e.g. 5e1 produces 50). + * The Scientific notation is only allowed in base 10. + * + * @param value The string to parse as a safe integer + * @returns null if the input is not an integer or is not safely representable by the JS number type (use parseBigInt for that) + */ +export const parseSafeInteger = buildNullBasedParser( + parseSafeIntegerInternal, + (value, radix = 10) => `Value ${inspect(value)} is not a valid base ${inspect(radix)} integer`, +); diff --git a/packages/utils/src/common/pojo.ts b/packages/utils/src/common/pojo.ts new file mode 100644 index 000000000000..b750cd09d8ff --- /dev/null +++ b/packages/utils/src/common/pojo.ts @@ -0,0 +1,26 @@ +/** + * Creates a new object with a null prototype. + * If an object is provided, it sets the prototype of the provided object to null. + * This mutates the provided object. + * + * @example + * ```ts + * const obj = pojo({ a: 1 }); + * console.log(Object.getPrototypeOf(obj)); // null + * + * const obj2 = pojo(); + * console.log(Object.getPrototypeOf(obj2)); // null + * ``` + * + * @param obj The object whose prototype is to be set to null. + * @returns The new object with a null prototype, or the provided object with its prototype set to null. + */ +export function pojo(obj?: T): T { + if (!obj) { + return Object.create(null); + } + + Object.setPrototypeOf(obj, null); + + return obj; +} diff --git a/packages/utils/src/common/predicates/__tests__/is-any-object.test.ts b/packages/utils/src/common/predicates/__tests__/is-any-object.test.ts new file mode 100644 index 000000000000..cc93a5004ba9 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-any-object.test.ts @@ -0,0 +1,37 @@ +import { isAnyObject, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isAnyObject', () => { + it('returns true for plain objects', () => { + expect(isAnyObject({})).to.be.true; + }); + + it('returns true for functions', () => { + expect(isAnyObject(() => {})).to.be.true; + }); + + it('returns true for non-plain objects', () => { + expect(isAnyObject(new Date())).to.be.true; + expect(isAnyObject([1, 2, 3])).to.be.true; + }); + + it('returns false for primitives', () => { + expect(isAnyObject(null)).to.be.false; + expect(isAnyObject(42)).to.be.false; + expect(isAnyObject('string')).to.be.false; + expect(isAnyObject(true)).to.be.false; + expect(isAnyObject(undefined)).to.be.false; + expect(isAnyObject(Symbol('symbol'))).to.be.false; + expect(isAnyObject(123n)).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isAnyObject(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-big-int.test.ts b/packages/utils/src/common/predicates/__tests__/is-big-int.test.ts new file mode 100644 index 000000000000..381320b15562 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-big-int.test.ts @@ -0,0 +1,30 @@ +import { isBigInt, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isBigInt', () => { + it('returns true for bigint', () => { + expect(isBigInt(123n)).to.be.true; + }); + + it('returns false for non-bigint', () => { + expect(isBigInt(42)).to.be.false; + expect(isBigInt('42')).to.be.false; + expect( + isBigInt({ + [Symbol.toPrimitive]() { + return 42n; + }, + }), + ).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isBigInt(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-error.test.ts b/packages/utils/src/common/predicates/__tests__/is-error.test.ts new file mode 100644 index 000000000000..d6ce0ebbd75d --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-error.test.ts @@ -0,0 +1,26 @@ +import { isError, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isError', () => { + it('returns true for Error', () => { + expect(isError(new Error('test'))).to.be.true; + }); + + it('returns true for Error subclasses', () => { + expect(isError(new TypeError('test'))).to.be.true; + }); + + it('returns false for non-Error', () => { + expect(isError({})).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isError(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-function.test.ts b/packages/utils/src/common/predicates/__tests__/is-function.test.ts new file mode 100644 index 000000000000..eef58434299c --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-function.test.ts @@ -0,0 +1,28 @@ +import { isFunction, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isFunction', () => { + it('returns true for function', () => { + expect(isFunction(() => {})).to.be.true; + }); + + it('returns true for class', () => { + class Test {} + + expect(isFunction(Test)).to.be.true; + }); + + it('returns false for non-function', () => { + expect(isFunction({})).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isFunction(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-iterable.test.ts b/packages/utils/src/common/predicates/__tests__/is-iterable.test.ts new file mode 100644 index 000000000000..55433a1f387f --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-iterable.test.ts @@ -0,0 +1,25 @@ +import { isIterable, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isIterable', () => { + it('returns true for iterables', () => { + expect(isIterable([])).to.be.true; + expect(isIterable('string')).to.be.true; + expect(isIterable(new Map())).to.be.true; + expect(isIterable(new Set())).to.be.true; + }); + + it('returns false for non-iterables', () => { + expect(isIterable(42)).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast | null>(null); + if (isIterable(value)) { + expectTypeOf(value).toEqualTypeOf>(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-nullish.test.ts b/packages/utils/src/common/predicates/__tests__/is-nullish.test.ts new file mode 100644 index 000000000000..898a0e20b4df --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-nullish.test.ts @@ -0,0 +1,26 @@ +import { isNullish, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isNullish', () => { + it('returns true for null and undefined', () => { + expect(isNullish(null)).to.be.true; + expect(isNullish(undefined)).to.be.true; + }); + + it('returns false for non-nullish', () => { + expect(isNullish(0)).to.be.false; + expect(isNullish('')).to.be.false; + expect(isNullish(false)).to.be.false; + expect(isNullish(NaN)).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(42); + if (isNullish(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-number.test.ts b/packages/utils/src/common/predicates/__tests__/is-number.test.ts new file mode 100644 index 000000000000..b4d5a918b740 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-number.test.ts @@ -0,0 +1,23 @@ +import { isNumber, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isNumber', () => { + it('returns true for number', () => { + expect(isNumber(42)).to.be.true; + }); + + it('returns false for non-number', () => { + expect(isNumber('42')).to.be.false; + expect(isNumber(42n)).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isNumber(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-plain-object.test.ts b/packages/utils/src/common/predicates/__tests__/is-plain-object.test.ts new file mode 100644 index 000000000000..8c3ecd17482f --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-plain-object.test.ts @@ -0,0 +1,28 @@ +import type { AnyRecord } from '@sequelize/utils'; +import { isPlainObject, pojo, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isPlainObject', () => { + it('returns true for plain object (Object prototype or null prototype)', () => { + expect(isPlainObject({})).to.be.true; + expect(isPlainObject(pojo())).to.be.true; + }); + + it('returns false for non-plain object', () => { + expect(isPlainObject(42)).to.be.false; + expect(isPlainObject('42')).to.be.false; + expect(isPlainObject(() => {})).to.be.false; + expect(isPlainObject(new Date())).to.be.false; + expect(isPlainObject([])).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isPlainObject(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-string.test.ts b/packages/utils/src/common/predicates/__tests__/is-string.test.ts new file mode 100644 index 000000000000..d6777b0408f5 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-string.test.ts @@ -0,0 +1,29 @@ +import { isString, upcast } from '@sequelize/utils'; +import { expect } from 'chai'; +import { expectTypeOf } from 'expect-type'; + +describe('isString', () => { + it('returns true for string', () => { + expect(isString('string')).to.be.true; + }); + + it('returns false for non-string', () => { + expect(isString(42)).to.be.false; + expect( + isString({ + [Symbol.toPrimitive]() { + return 'test'; + }, + }), + ).to.be.false; + }); + + it('narrows the TypeScript type', () => { + const value = upcast(null); + if (isString(value)) { + expectTypeOf(value).toEqualTypeOf(); + } else { + expectTypeOf(value).toEqualTypeOf(); + } + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-valid-integer-syntax.test.ts b/packages/utils/src/common/predicates/__tests__/is-valid-integer-syntax.test.ts new file mode 100644 index 000000000000..a9d15b784c80 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-valid-integer-syntax.test.ts @@ -0,0 +1,40 @@ +import { isValidIntegerSyntax } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('isValidIntegerSyntax', () => { + it('returns true when input is a valid integer syntax', () => { + expect(isValidIntegerSyntax('123')).to.be.true; + }); + + it('returns false when input is not a valid integer syntax', () => { + expect(isValidIntegerSyntax('not an integer')).to.be.false; + expect(isValidIntegerSyntax('')).to.be.false; + expect(isValidIntegerSyntax('-')).to.be.false; + expect(isValidIntegerSyntax(' 1')).to.be.false; + }); + + it('returns false when input is a valid integer syntax with scientific notation', () => { + expect(isValidIntegerSyntax('1e2')).to.be.false; + }); + + it('returns false when input is a valid integer syntax with numeric separators', () => { + expect(isValidIntegerSyntax('1_000')).to.be.false; + }); + + it('supports radixes', () => { + expect(isValidIntegerSyntax('1010', 2)).to.be.true; + expect(isValidIntegerSyntax('10102', 2)).to.be.false; + + expect(isValidIntegerSyntax('z', 36)).to.be.true; + expect(isValidIntegerSyntax('z(', 36)).to.be.false; + }); + + it('is case insensitive', () => { + expect(isValidIntegerSyntax('Ff', 16)).to.be.true; + }); + + it('throws if the radix is below 2 or above 36', () => { + expect(() => isValidIntegerSyntax('123', 1)).to.throw(); + expect(() => isValidIntegerSyntax('123', 37)).to.throw(); + }); +}); diff --git a/packages/utils/src/common/predicates/__tests__/is-valid-number-syntax.test.ts b/packages/utils/src/common/predicates/__tests__/is-valid-number-syntax.test.ts new file mode 100644 index 000000000000..f7a4fefcbda7 --- /dev/null +++ b/packages/utils/src/common/predicates/__tests__/is-valid-number-syntax.test.ts @@ -0,0 +1,29 @@ +import { isValidNumberSyntax } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('isValidNumberSyntax', () => { + it('returns true for valid base 10 numbers', () => { + expect(isValidNumberSyntax('10')).to.equal(true); + expect(isValidNumberSyntax('0.1')).to.equal(true); + expect(isValidNumberSyntax('-10')).to.equal(true); + expect(isValidNumberSyntax('1e5')).to.equal(true); + expect(isValidNumberSyntax('-1e5')).to.equal(true); + }); + + it('returns false for invalid base 10 numbers', () => { + expect(isValidNumberSyntax('abc')).to.equal(false); + expect(isValidNumberSyntax('10a')).to.equal(false); + expect(isValidNumberSyntax('1.1.1')).to.equal(false); + expect(isValidNumberSyntax('1e1e1')).to.equal(false); + expect(isValidNumberSyntax('')).to.equal(false); + expect(isValidNumberSyntax('-')).to.equal(false); + expect(isValidNumberSyntax(' 12')).to.equal(false); + expect(isValidNumberSyntax('1_2')).to.equal(false); + }); + + it('does not support non-finite numbers', () => { + expect(isValidNumberSyntax('Infinity')).to.equal(false); + expect(isValidNumberSyntax('-Infinity')).to.equal(false); + expect(isValidNumberSyntax('NaN')).to.equal(false); + }); +}); diff --git a/packages/utils/src/common/predicates/is-any-object.ts b/packages/utils/src/common/predicates/is-any-object.ts new file mode 100644 index 000000000000..fc425daebfd9 --- /dev/null +++ b/packages/utils/src/common/predicates/is-any-object.ts @@ -0,0 +1,23 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is object => { + if (value === null) { + return false; + } + + const type = typeof value; + + return type === 'object' || type === 'function'; +}, toBe('any object')); + +/** + * Returns whether the provided value is a JavaScript Object (i.e. anything but a primitive). + * + * You will typically want to use more specific functions such as {@link isPlainObject} instead + */ +export const isAnyObject: AssertionFunction = tuple[0]; +export const isNotAnyObject: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-big-int.ts b/packages/utils/src/common/predicates/is-big-int.ts new file mode 100644 index 000000000000..455c1e143cac --- /dev/null +++ b/packages/utils/src/common/predicates/is-big-int.ts @@ -0,0 +1,17 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is bigint => { + return typeof value === 'bigint'; +}, toBe('a bigint')); + +/** + * Returns true if the value is a JS bigint. + * + * @param value The value to compare. + */ +export const isBigInt: AssertionFunction = tuple[0]; +export const isNotBigInt: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-error.ts b/packages/utils/src/common/predicates/is-error.ts new file mode 100644 index 000000000000..bd48281c8177 --- /dev/null +++ b/packages/utils/src/common/predicates/is-error.ts @@ -0,0 +1,17 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is Error => { + return value instanceof Error; +}, toBe('an instance of Error')); + +/** + * Returns true if the value is a JS built-in Error instance, or one of its child classes. + * + * @param value The value to compare. + */ +export const isError: AssertionFunction = tuple[0]; +export const isNotError: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-function.ts b/packages/utils/src/common/predicates/is-function.ts new file mode 100644 index 000000000000..5dd91b133a53 --- /dev/null +++ b/packages/utils/src/common/predicates/is-function.ts @@ -0,0 +1,19 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is Function => { + return typeof value === 'function'; +}, toBe('a function')); + +/** + * Returns true if a value is a function: + * + * isFunction(() => {}); // true + * isFunction(class A {}); // true + * isFunction((class {}).bind()); // true + */ +export const isFunction: AssertionFunction = tuple[0]; +export const isNotFunction: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-iterable.ts b/packages/utils/src/common/predicates/is-iterable.ts new file mode 100644 index 000000000000..16a6c5241db4 --- /dev/null +++ b/packages/utils/src/common/predicates/is-iterable.ts @@ -0,0 +1,19 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; +import { isFunction } from './is-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is Iterable => { + // @ts-expect-error -- TS does not allow accessing Symbol.iterator like this. + return value != null && isFunction(value[Symbol.iterator]); +}, toBe('an iterable')); + +/** + * Returns true if the value is null or undefined. + * + * @param value The value to compare. + */ +export const isIterable: AssertionFunction> = tuple[0]; +export const isNotIterable: NegatedAssertionFunction> = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-nullish.ts b/packages/utils/src/common/predicates/is-nullish.ts new file mode 100644 index 000000000000..798948acd45c --- /dev/null +++ b/packages/utils/src/common/predicates/is-nullish.ts @@ -0,0 +1,18 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; +import type { Nullish } from '../types.js'; + +const tuple = buildAssertionFunction((value: unknown): value is null | undefined => { + return value === null || value === undefined; +}, toBe('null or undefined')); + +/** + * Returns true if the value is null or undefined. + * + * @param value The value to compare. + */ +export const isNullish: AssertionFunction = tuple[0]; +export const isNotNullish: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-number.ts b/packages/utils/src/common/predicates/is-number.ts new file mode 100644 index 000000000000..2204b0c45c70 --- /dev/null +++ b/packages/utils/src/common/predicates/is-number.ts @@ -0,0 +1,17 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is number => { + return typeof value === 'number'; +}, toBe('an IEEE 754 number')); + +/** + * Returns true if the value is a JS IEEE 754 number (not bigint). + * + * @param value The value to compare. + */ +export const isNumber: AssertionFunction = tuple[0]; +export const isNotNumber: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-plain-object.ts b/packages/utils/src/common/predicates/is-plain-object.ts new file mode 100644 index 000000000000..e872f4e32179 --- /dev/null +++ b/packages/utils/src/common/predicates/is-plain-object.ts @@ -0,0 +1,30 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; +import type { AnyRecord } from '../types.js'; + +const tuple = buildAssertionFunction((value: unknown): value is AnyRecord => { + if (value === null || typeof value !== 'object') { + return false; + } + + const prototype = Object.getPrototypeOf(value); + + return prototype === null || prototype === Object.prototype; +}, toBe('a plain object (an object built using the literal syntax, or pojo())')); + +/** + * Returns whether something is a plain object + * A plain object is an object that either has no prototype at all (no inherited methods) or only inherits from Object.prototype + * + * @example + * isPlainObject({ a: 1 }); // true + * isPlainObject(pojo()); // true + * isPlainObject(new Date()); // false + * + * @param value The value to compare. + */ +export const isPlainObject: AssertionFunction = tuple[0]; +export const isNotPlainObject: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-string.ts b/packages/utils/src/common/predicates/is-string.ts new file mode 100644 index 000000000000..eb51961534cc --- /dev/null +++ b/packages/utils/src/common/predicates/is-string.ts @@ -0,0 +1,17 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is string => { + return typeof value === 'string'; +}, toBe('a string')); + +/** + * Returns true if the value is a string. + * + * @param value The value to compare. + */ +export const isString: AssertionFunction = tuple[0]; +export const isNotString: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/common/predicates/is-valid-integer-syntax.ts b/packages/utils/src/common/predicates/is-valid-integer-syntax.ts new file mode 100644 index 000000000000..1faea97aae1d --- /dev/null +++ b/packages/utils/src/common/predicates/is-valid-integer-syntax.ts @@ -0,0 +1,16 @@ +import { getIsIntegerRegExp } from '../_internal/integer-regexp.js'; + +/** + * Determines if the string follows the "integer" syntax. + * + * Does not determine whether the value could be represented using the Number or BigInt type. + * Use {@link parseSafeInteger}, {@link parseBigInt} for that. + * + * @param value + * @param radix + */ +export function isValidIntegerSyntax(value: string, radix: number = 10): boolean { + const regex = getIsIntegerRegExp(radix); + + return regex.test(value); +} diff --git a/packages/utils/src/common/predicates/is-valid-number-syntax.ts b/packages/utils/src/common/predicates/is-valid-number-syntax.ts new file mode 100644 index 000000000000..ffe01708d527 --- /dev/null +++ b/packages/utils/src/common/predicates/is-valid-number-syntax.ts @@ -0,0 +1,13 @@ +const BASE10_NUMBER_REGEX = /^-?[0-9]*(\.[0-9]+)?([eE][-+]?[0-9]+)?$/; + +/** + * Determines if the string follows the "number" syntax: Base 10 numbers that can have a decimal part + * and be written using the scientific notation. + * + * Does not determine whether the value could be represented using the Number type. Use {@link parseFiniteNumber} for that. + * + * @param value + */ +export function isValidNumberSyntax(value: string): boolean { + return value !== '' && value !== '-' && BASE10_NUMBER_REGEX.test(value); +} diff --git a/packages/utils/src/common/set/__tests__/set-view.test.ts b/packages/utils/src/common/set/__tests__/set-view.test.ts new file mode 100644 index 000000000000..5e77a9ee5291 --- /dev/null +++ b/packages/utils/src/common/set/__tests__/set-view.test.ts @@ -0,0 +1,50 @@ +import { SetView } from '@sequelize/utils'; +import { expect } from 'chai'; + +describe('SetView', () => { + const view = new SetView(new Set(['value'])); + + describe('size', () => { + it('returns the number of unique elements in the Set', () => { + expect(view.size).to.eq(1); + }); + }); + + describe('has', () => { + it('returns a boolean indicating whether an element with the specified value exists in the Set or not', () => { + expect(view.has('value')).to.be.true; + expect(view.has('unknown')).to.be.false; + }); + }); + + describe('find', () => { + it('returns the element if the callback function returns true', () => { + expect(view.find(value => value === 'value')).to.eq('value'); + }); + + it('returns undefined if the callback function does not return true for any element', () => { + expect(view.find(value => value === 'unknown')).to.be.undefined; + }); + }); + + describe('Symbol.iterator', () => { + it('returns an iterator', () => { + expect([...view]).to.eql(['value']); + }); + }); + + describe('values', () => { + it('returns an iterator', () => { + expect([...view.values()]).to.eql(['value']); + }); + }); + + describe('toMutableSet', () => { + it('returns a new Set', () => { + const set = view.toMutableSet(); + expect(set).to.be.an.instanceOf(Set); + + expect([...set.values()]).to.deep.eq([...view.values()]); + }); + }); +}); diff --git a/packages/utils/src/common/set/set-view.node.ts b/packages/utils/src/common/set/set-view.node.ts new file mode 100644 index 000000000000..886580f37a07 --- /dev/null +++ b/packages/utils/src/common/set/set-view.node.ts @@ -0,0 +1,17 @@ +import NodeUtil, { type InspectOptions } from 'node:util'; +import { pojo } from '../pojo.js'; +import { SetView } from './set-view.js'; + +// @ts-expect-error -- This node-specific extension is not declared on the base class. +SetView.prototype[NodeUtil.inspect.custom] = function inspect( + depth: number, + options: InspectOptions, +): string { + const newOptions = Object.assign(pojo(), options, { + depth: options.depth == null ? null : options.depth - 1, + }); + + return NodeUtil.inspect(this.toMutableSet(), newOptions).replace(/^Set/, 'SetView'); +}; + +export { SetView } from './set-view.js'; diff --git a/packages/utils/src/common/set/set-view.ts b/packages/utils/src/common/set/set-view.ts new file mode 100644 index 000000000000..981b381778bc --- /dev/null +++ b/packages/utils/src/common/set/set-view.ts @@ -0,0 +1,45 @@ +import { find } from '../iterator-utils/find.js'; +import type { ReadonlySetLike } from '../types.js'; + +export class SetView implements ReadonlySetLike { + readonly #target: Set; + + /** + * @returns the number of (unique) elements in Set. + */ + get size() { + return this.#target.size; + } + + constructor(target: Set) { + this.#target = target; + } + + /** + * @param value + * @returns a boolean indicating whether an element with the specified value exists in the Set or not. + */ + has(value: V): boolean { + return this.#target.has(value); + } + + find(callback: (model: V) => boolean): V | undefined { + return find(this, callback); + } + + [Symbol.iterator](): IterableIterator { + return this.#target[Symbol.iterator](); + } + + values(): IterableIterator { + return this.#target.values(); + } + + toMutableSet(): Set { + return new Set(this.#target); + } + + firstValue(): V | undefined { + return this.#target.values().next().value; + } +} diff --git a/packages/utils/src/common/shallow-clone-pojo.ts b/packages/utils/src/common/shallow-clone-pojo.ts new file mode 100644 index 000000000000..8ba85d1b0ba6 --- /dev/null +++ b/packages/utils/src/common/shallow-clone-pojo.ts @@ -0,0 +1,8 @@ +import { pojo } from './pojo.js'; +import { isPlainObject } from './predicates/is-plain-object.js'; + +export function shallowClonePojo(obj: T): T { + isPlainObject.assert(obj); + + return Object.assign(pojo(), obj); +} diff --git a/packages/utils/src/common/split-object.ts b/packages/utils/src/common/split-object.ts new file mode 100644 index 000000000000..b8bb0eb70907 --- /dev/null +++ b/packages/utils/src/common/split-object.ts @@ -0,0 +1,26 @@ +import { pojo } from './pojo.js'; + +/** + * Splits an object into two objects, one containing the keys provided and the other containing the rest. + * + * @param object The object to split + * @param keys The keys to pick from the object + * @returns A tuple where the first element is an object containing the picked keys and the second element is an object containing the rest + */ +export function splitObject>( + object: Obj, + keys: Keys, +): [Pick, Omit] { + const picked: any = pojo(); + const omitted: any = pojo(); + + for (const key of Object.keys(object) as Array) { + if (keys.includes(key)) { + picked[key] = object[key]; + } else { + omitted[key] = object[key]; + } + } + + return [picked, omitted]; +} diff --git a/packages/utils/src/common/types.ts b/packages/utils/src/common/types.ts new file mode 100644 index 000000000000..551671f50ca4 --- /dev/null +++ b/packages/utils/src/common/types.ts @@ -0,0 +1,114 @@ +/** + * Represents any plain function (no `this`) + */ +export type AnyFunction = (...args: any[]) => any; + +export type UnknownFunction = (...args: unknown[]) => unknown; + +/** + * Represents any plain object (or Record, as TypeScript calls it). + * + * Prefer {@link UnknownRecord} unless you're encountering issues with it. + */ +export type AnyRecord = Record; + +/** + * Represents any plain object (or Record, as TypeScript calls it) + * + * Stricter than {@link AnyRecord}. Not all records can be assigned to this value due to how TypeScript works + * but its usage is recommended because the value won't be typed as any. + */ +export type UnknownRecord = Record; + +export type Nullish = null | undefined; + +export type NonNullish = {}; + +/** + * Makes the type accept null & undefined + */ +export type MakeNullish = T | Nullish; + +export type MakeNonNullish = NonNullable; + +export type NonUndefined = T extends undefined ? never : T; + +export type NonNull = T extends null ? never : T; + +export type NonUndefinedKeys = { + [P in keyof T]: P extends K ? NonUndefined : T[P]; +}; + +export type AllowArray = T | T[]; + +export type AllowIterable = T | Iterable; + +export type AllowReadonlyArray = T | readonly T[]; + +export type AllowPromise = T | Promise; + +/** + * Like {@link Partial}, but also allows undefined. + * Useful when "exactOptionalPropertyTypes" is enabled. + */ +export type PartialOrUndefined = { + [P in keyof T]?: T[P] | undefined; +}; + +/** + * Type helper for making certain fields of an object optional. + */ +export type PartialBy = Omit & Partial>; + +export type RequiredBy = Omit & Required>; + +export type StrictRequiredBy = NonUndefinedKeys< + Omit & Required>, + K +>; + +export type ReadOnlyRecord = Readonly>; + +export type PartialRecord = { + [P in K]?: V; +}; + +export type PartialReadonlyRecord = Readonly>; + +export type Entry = [key: Key, value: Value]; + +export type JsonArray = JsonValue[]; +export type JsonObject = { [key: string]: JsonValue }; +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = JsonPrimitive | JsonObject | JsonArray; + +export type PickByType = { [K in keyof T as U extends T[K] ? K : never]: T[K] }; + +export interface ReadonlyMapLike { + entries(): IterableIterator>; + get(key: K): V | undefined; + has(key: K): boolean; + keys(): IterableIterator; + readonly size: number; + [Symbol.iterator](): IterableIterator>; + values(): IterableIterator; +} + +export interface MapLike extends ReadonlyMapLike { + clear(): void; + delete(key: K): boolean; + set(key: K, value: V): this; +} + +export interface ReadonlySetLike { + has(value: V): boolean; + readonly size: number; + [Symbol.iterator](): IterableIterator; + values(): IterableIterator; +} + +export interface SetLike extends ReadonlySetLike { + add(value: V): this; + clear(): void; + delete(value: V): boolean; +} diff --git a/packages/utils/src/common/upcast.ts b/packages/utils/src/common/upcast.ts new file mode 100644 index 000000000000..5e05316616d2 --- /dev/null +++ b/packages/utils/src/common/upcast.ts @@ -0,0 +1,8 @@ +/** + * This empty function serves as a safe upcast mechanism. + * + * See https://github.com/microsoft/TypeScript/issues/47920 to learn more. + * + * @param val The value to upcast. + */ +export const upcast = (val: T): T => val; diff --git a/packages/utils/src/node/check-file-exists.ts b/packages/utils/src/node/check-file-exists.ts new file mode 100644 index 000000000000..9f56f705381f --- /dev/null +++ b/packages/utils/src/node/check-file-exists.ts @@ -0,0 +1,16 @@ +import fs from 'node:fs/promises'; +import { isNodeError } from './is-node-error.js'; + +export async function checkFileExists(path: string): Promise { + try { + await fs.access(path); + + return true; + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') { + return false; + } + + throw error; + } +} diff --git a/packages/utils/src/node/file-url-to-dirname.ts b/packages/utils/src/node/file-url-to-dirname.ts new file mode 100644 index 000000000000..2a8665592a09 --- /dev/null +++ b/packages/utils/src/node/file-url-to-dirname.ts @@ -0,0 +1,6 @@ +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +export function fileUrlToDirname(url: string | URL): string { + return dirname(fileURLToPath(url)); +} diff --git a/packages/utils/src/node/index.mjs b/packages/utils/src/node/index.mjs new file mode 100644 index 000000000000..0c812b74f0fb --- /dev/null +++ b/packages/utils/src/node/index.mjs @@ -0,0 +1,9 @@ +import Pkg from './index.js'; + +export const checkFileExists = Pkg.checkFileExists; +export const fileUrlToDirname = Pkg.fileUrlToDirname; +export const isNodeError = Pkg.isNodeError; +export const isNotNodeError = Pkg.isNotNodeError; +export const listDirectories = Pkg.listDirectories; +export const listFilesRecursive = Pkg.listFilesRecursive; +export const readFileIfExists = Pkg.readFileIfExists; diff --git a/packages/utils/src/node/index.ts b/packages/utils/src/node/index.ts new file mode 100644 index 000000000000..d54d394f44cf --- /dev/null +++ b/packages/utils/src/node/index.ts @@ -0,0 +1,8 @@ +/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */ + +export * from './check-file-exists.js'; +export * from './file-url-to-dirname.js'; +export * from './is-node-error.js'; +export * from './list-directories.js'; +export * from './list-files-recursive.js'; +export * from './read-file-if-exists.js'; diff --git a/packages/utils/src/node/is-node-error.ts b/packages/utils/src/node/is-node-error.ts new file mode 100644 index 000000000000..835dcb039cd1 --- /dev/null +++ b/packages/utils/src/node/is-node-error.ts @@ -0,0 +1,17 @@ +import type { + AssertionFunction, + NegatedAssertionFunction, +} from '../common/_internal/build-predicate-function.js'; +import { buildAssertionFunction, toBe } from '../common/_internal/build-predicate-function.js'; + +const tuple = buildAssertionFunction((value: unknown): value is NodeJS.ErrnoException => { + return value instanceof Error && 'code' in value; +}, toBe('a NodeJS.ErrnoException')); + +/** + * Returns true if the value is a node error. + * + * @param value The value to compare. + */ +export const isNodeError: AssertionFunction = tuple[0]; +export const isNotNodeError: NegatedAssertionFunction = tuple[1]; diff --git a/packages/utils/src/node/list-directories.ts b/packages/utils/src/node/list-directories.ts new file mode 100644 index 000000000000..7b5ce34558c5 --- /dev/null +++ b/packages/utils/src/node/list-directories.ts @@ -0,0 +1,8 @@ +import type { PathLike } from 'node:fs'; +import fs from 'node:fs/promises'; + +export async function listDirectories(directory: PathLike): Promise { + const entries = await fs.readdir(directory, { withFileTypes: true }); + + return entries.filter(entry => entry.isDirectory()).map(entry => entry.name); +} diff --git a/packages/utils/src/node/list-files-recursive.ts b/packages/utils/src/node/list-files-recursive.ts new file mode 100644 index 000000000000..d299fb5c88b0 --- /dev/null +++ b/packages/utils/src/node/list-files-recursive.ts @@ -0,0 +1,20 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +/** + * Async generator that yields every file present in the specified folder, and is sub-folders + * + * @param dir + */ +export async function* listFilesRecursive(dir: string): AsyncGenerator { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.resolve(dir, entry.name); + + if (entry.isDirectory()) { + yield* listFilesRecursive(entryPath); + } else { + yield entryPath; + } + } +} diff --git a/packages/utils/src/node/read-file-if-exists.ts b/packages/utils/src/node/read-file-if-exists.ts new file mode 100644 index 000000000000..5a8eac919f50 --- /dev/null +++ b/packages/utils/src/node/read-file-if-exists.ts @@ -0,0 +1,24 @@ +import type { Abortable } from 'node:events'; +import type { ObjectEncodingOptions, OpenMode, PathLike } from 'node:fs'; +import fs from 'node:fs/promises'; +import { isNodeError } from './is-node-error.js'; + +export interface ReadFileOptions extends Abortable, ObjectEncodingOptions { + flag?: OpenMode | undefined; +} + +export async function readFileIfExists( + filePath: PathLike, + options?: ReadFileOptions, +): Promise { + try { + return await fs.readFile(filePath, options); + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') { + // file not found + return null; + } + + throw error; + } +} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/utils/typedoc.json b/packages/utils/typedoc.json new file mode 100644 index 000000000000..3c36858e703d --- /dev/null +++ b/packages/utils/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/common/index.ts", "src/node/index.ts"], + "excludeExternals": true +} diff --git a/packages/validator-js/.eslintrc.js b/packages/validator-js/.eslintrc.js new file mode 100644 index 000000000000..cfc430a97352 --- /dev/null +++ b/packages/validator-js/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + parserOptions: { + project: [`${__dirname}/tsconfig.json`], + }, + overrides: [ + { + files: ['test/**/*'], + parserOptions: { + project: [`${__dirname}/test/tsconfig.json`], + }, + }, + ], +}; diff --git a/packages/validator-js/README.md b/packages/validator-js/README.md new file mode 100644 index 000000000000..201d37112226 --- /dev/null +++ b/packages/validator-js/README.md @@ -0,0 +1,534 @@ +

Sequelize logo

+

Sequelize

+ +This library contains model attribute validators built on top of [validator.js](https://www.npmjs.com/package/validator). +Read more about model validation in the [Sequelize documentation](https://sequelize.org/docs/v7/core-concepts/validations-and-constraints/). + +## Installation + +Using npm: + +```sh +npm install @sequelize/validator.js +``` + +Or using yarn: + +```sh +yarn add @sequelize/validator.js +``` + +## Usage + +**⚠️ As indicated in the validator.js documentation, the library validates and sanitizes strings only.** + +To add validation to your model, decorate your model attributes with the decorators exported by this library. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsEmail } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsEmail + declare email: string; +} +``` + +## List of validators + +This package exports the following validators: + +### `Contains` + +Checks if the string attribute contains the seed. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Contains } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Contains('foo') + declare username: string; +} +``` + +### `NotContains` + +Checks if the string attribute does not contain the seed. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { NotContains } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @NotContains('foo') + declare username: string; +} +``` + +### `Equals` + +Checks if the string attribute is exactly a value. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Equals } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Equals('foo') + declare username: string; +} +``` + +### `Is` + +Checks if the string attribute matches a regex. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Is } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Is(/foo/) + declare username: string; +} +``` + +### `Not` + +Checks if the string attribute does not match a regex. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Not } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Not(/foo/) + declare username: string; +} +``` + +### `IsAfter` + +Checks if the string attribute is a date that's after the specified date. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsAfter } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsAfter('2021-01-01') + declare createdAt: string; +} +``` + +### `IsBefore` + +Checks if the string attribute is a date that's before the specified date. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsBefore } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsBefore('2021-01-01') + declare createdAt: string; +} +``` + +### `IsAlpha` + +Checks if the string attribute contains only letters (a-zA-Z). + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsAlpha } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsAlpha + declare username: string; +} +``` + +### `IsAlphanumeric` + +Checks if the string attribute contains only letters and numbers. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsAlphanumeric } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsAlphanumeric + declare username: string; +} +``` + +### `IsCreditCard` + +Checks if the string attribute is a credit card number. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsCreditCard } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsCreditCard + declare creditCard: string; +} +``` + +### `IsDate` + +Checks if the string attribute is a date. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsDate + declare createdAt: string; +} +``` + +### `IsDecimal` + +Checks if the string attribute is a decimal number. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsDecimal } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsDecimal + declare price: string; +} +``` + +### `IsEmail` + +Checks if the string attribute is an email. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsEmail } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsEmail + declare email: string; +} +``` + +### `IsFloat` + +Checks if the string attribute is a float number. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsFloat } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsFloat + declare price: string; +} +``` + +### `IsIP` + +Checks if the string attribute is an IP address (4 or 6). + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsIP } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsIP(4) // optionally pass 4 or 6 to check for a specific IP version + declare ip: string; +} +``` + +### `IsIPv4` + +Checks if the string attribute is an IPv4 address. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsIPv4 } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsIPv4 + declare ipV4: string; +} +``` + +### `IsIPv6` + +Checks if the string attribute is an IPv6 address. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsIPv6 } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsIPv6 + declare ipV6: string; +} +``` + +### `IsIn` + +Checks if the string attribute is in a array of allowed values. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsIn } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsIn(['admin', 'user']) + declare role: string; +} +``` + +### `NotIn` + +Checks if the string attribute is not in a array of disallowed values. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { NotIn } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @NotIn(['admin', 'user']) + declare role: string; +} +``` + +### `IsInt` + +Checks if the string attribute is an integer. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsInt } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsInt + declare age: string; +} +``` + +### `IsLowercase` + +Checks if the string attribute is lowercase. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsLowercase } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsLowercase + declare username: string; +} +``` + +### `IsNumeric` + +Checks if the string attribute is numeric. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsNumeric } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsNumeric + declare age: string; +} +``` + +### `IsUUID` + +Checks if the string attribute is a UUID. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsUUID } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsUUID(4) // UUID version is optional + declare uuid: string; +} +``` + +### `IsUppercase` + +Checks if the string attribute is uppercase. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsUppercase } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsUppercase + declare username: string; +} +``` + +### `IsUrl` + +Checks if the string attribute is a URL. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { IsUrl } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @IsUrl + declare website: string; +} +``` + +### `Length` + +Checks if the string attribute has a length between min and max. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Length } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Length([3, 10]) + declare username: string; +} +``` + +### `Max` + +Checks if the string attribute is not longer than the specified number of characters. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Max } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Max(10) + declare username: string; +} +``` + +### `Min` + +Checks if the string attribute is not shorter than the specified number of characters. + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { Min } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @Min(3) + declare username: string; +} +``` + +### `NotEmpty` + +Checks if the string attribute is not an empty string (after trimming). + +```ts +import { Model, DataTypes } from '@sequelize/core'; +import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'; +import { NotEmpty } from '@sequelize/validator.js'; + +class User extends Model { + @Attribute(DataTypes.STRING) + @NotNull + @NotEmpty + declare username: string; +} +``` diff --git a/packages/validator-js/package.json b/packages/validator-js/package.json new file mode 100644 index 000000000000..fbc75fb0ca07 --- /dev/null +++ b/packages/validator-js/package.json @@ -0,0 +1,59 @@ +{ + "name": "@sequelize/validator.js", + "version": "7.0.0-alpha.47", + "description": "Integration between validator.js and Sequelize", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "repository": { + "type": "git", + "url": "git@github.com:sequelize/sequelize.git" + }, + "bugs": { + "url": "https://github.com/sequelize/sequelize/issues" + }, + "scripts": { + "test-typings": "tsc --noEmit", + "test-unit": "yarn mocha \"test/**/*.test.[tj]s\"", + "mocha": "mocha -r ../../test/register-esbuild.js", + "build": "../../build-packages.mjs validator-js" + }, + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./lib/index.d.mts", + "default": "./lib/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "lib" + ], + "license": "MIT", + "type": "commonjs", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sequelize/core": "workspace:*", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@sequelize/sqlite3": "workspace:*", + "@types/chai": "4.3.20", + "@types/chai-as-promised": "7.1.8", + "chai": "4.5.0", + "chai-as-promised": "7.1.2", + "mocha": "11.7.5" + } +} diff --git a/packages/validator-js/src/index.mjs b/packages/validator-js/src/index.mjs new file mode 100644 index 000000000000..78cf0505dd5a --- /dev/null +++ b/packages/validator-js/src/index.mjs @@ -0,0 +1,32 @@ +import Pkg from './index.js'; + +export const Contains = Pkg.Contains; +export const Equals = Pkg.Equals; +export const Is = Pkg.Is; +export const IsAfter = Pkg.IsAfter; +export const IsAlpha = Pkg.IsAlpha; +export const IsAlphanumeric = Pkg.IsAlphanumeric; +export const IsArray = Pkg.IsArray; +export const IsBefore = Pkg.IsBefore; +export const IsCreditCard = Pkg.IsCreditCard; +export const IsDate = Pkg.IsDate; +export const IsDecimal = Pkg.IsDecimal; +export const IsEmail = Pkg.IsEmail; +export const IsFloat = Pkg.IsFloat; +export const IsIP = Pkg.IsIP; +export const IsIPv4 = Pkg.IsIPv4; +export const IsIPv6 = Pkg.IsIPv6; +export const IsIn = Pkg.IsIn; +export const IsInt = Pkg.IsInt; +export const IsLowercase = Pkg.IsLowercase; +export const IsNumeric = Pkg.IsNumeric; +export const IsUUID = Pkg.IsUUID; +export const IsUppercase = Pkg.IsUppercase; +export const IsUrl = Pkg.IsUrl; +export const Len = Pkg.Len; +export const Max = Pkg.Max; +export const Min = Pkg.Min; +export const Not = Pkg.Not; +export const NotContains = Pkg.NotContains; +export const NotEmpty = Pkg.NotEmpty; +export const NotIn = Pkg.NotIn; diff --git a/packages/validator-js/src/index.ts b/packages/validator-js/src/index.ts new file mode 100644 index 000000000000..f0070757671d --- /dev/null +++ b/packages/validator-js/src/index.ts @@ -0,0 +1,101 @@ +import type { ColumnValidateOptions } from '@sequelize/core'; +import { + createOptionalAttributeOptionsDecorator, + createRequiredAttributeOptionsDecorator, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/decorators/legacy/attribute-utils.js'; +import type { + OptionalParameterizedPropertyDecorator, + RequiredParameterizedPropertyDecorator, +} from '@sequelize/core/_non-semver-use-at-your-own-risk_/decorators/legacy/decorator-utils.js'; +import upperFirst from 'lodash/upperFirst.js'; + +type ValidateKeys = Extract; + +function createRequiredAttributeValidationDecorator( + decoratorName: Key, +): RequiredParameterizedPropertyDecorator { + return createRequiredAttributeOptionsDecorator( + upperFirst(decoratorName), + (decoratorOption: ColumnValidateOptions[Key]) => { + return { validate: { [decoratorName]: decoratorOption } }; + }, + ); +} + +function createOptionalAttributeValidationDecorator( + decoratorName: Key, + defaultValue: ColumnValidateOptions[Key], +): OptionalParameterizedPropertyDecorator { + return createOptionalAttributeOptionsDecorator( + upperFirst(decoratorName), + defaultValue, + (decoratorOption: ColumnValidateOptions[Key]) => { + return { validate: { [decoratorName]: decoratorOption } }; + }, + ); +} + +// TODO: rename to Matches +export const Is = createRequiredAttributeValidationDecorator('is'); + +// TODO: rename to NotMatches +export const Not = createRequiredAttributeValidationDecorator('not'); + +export const IsEmail = createOptionalAttributeValidationDecorator('isEmail', true); + +export const IsUrl = createOptionalAttributeValidationDecorator('isUrl', true); + +export const IsIP = createOptionalAttributeValidationDecorator('isIP', true); + +export const IsIPv4 = createOptionalAttributeValidationDecorator('isIPv4', true); + +export const IsIPv6 = createOptionalAttributeValidationDecorator('isIPv6', true); + +export const IsAlpha = createOptionalAttributeValidationDecorator('isAlpha', true); + +export const IsAlphanumeric = createOptionalAttributeValidationDecorator('isAlphanumeric', true); + +export const IsNumeric = createOptionalAttributeValidationDecorator('isNumeric', true); + +export const IsInt = createOptionalAttributeValidationDecorator('isInt', true); + +export const IsFloat = createOptionalAttributeValidationDecorator('isFloat', true); + +export const IsDecimal = createOptionalAttributeValidationDecorator('isDecimal', true); + +export const IsLowercase = createOptionalAttributeValidationDecorator('isLowercase', true); + +export const IsUppercase = createOptionalAttributeValidationDecorator('isUppercase', true); + +export const NotEmpty = createOptionalAttributeValidationDecorator('notEmpty', true); + +export const Equals = createRequiredAttributeValidationDecorator('equals'); + +export const Contains = createRequiredAttributeValidationDecorator('contains'); + +export const NotIn = createRequiredAttributeValidationDecorator('notIn'); + +export const IsIn = createRequiredAttributeValidationDecorator('isIn'); + +export const NotContains = createRequiredAttributeValidationDecorator('notContains'); + +// TODO: rename IsLength +export const Len = createRequiredAttributeValidationDecorator('len'); + +export const IsUUID = createRequiredAttributeValidationDecorator('isUUID'); + +export const IsDate = createOptionalAttributeValidationDecorator('isDate', true); + +export const IsAfter = createRequiredAttributeValidationDecorator('isAfter'); + +export const IsBefore = createRequiredAttributeValidationDecorator('isBefore'); + +// TODO: rename to MaxLength +export const Max = createRequiredAttributeValidationDecorator('max'); + +// TODO: rename to MinLength +export const Min = createRequiredAttributeValidationDecorator('min'); + +export const IsArray = createOptionalAttributeValidationDecorator('isArray', true); + +export const IsCreditCard = createOptionalAttributeValidationDecorator('isCreditCard', true); diff --git a/packages/validator-js/test/tsconfig.json b/packages/validator-js/test/tsconfig.json new file mode 100644 index 000000000000..505106b60353 --- /dev/null +++ b/packages/validator-js/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["node", "mocha", "sinon", "chai", "sinon-chai", "chai-as-promised"], + "noEmit": true, + "experimentalDecorators": true + }, + "include": ["./types/**/*", "./**/**/*.ts"] +} diff --git a/packages/validator-js/test/validator-js.test.ts b/packages/validator-js/test/validator-js.test.ts new file mode 100644 index 000000000000..bac9c0d42eb1 --- /dev/null +++ b/packages/validator-js/test/validator-js.test.ts @@ -0,0 +1,28 @@ +import { DataTypes, Model, Sequelize } from '@sequelize/core'; +import { Attribute } from '@sequelize/core/decorators-legacy'; +import { SqliteDialect } from '@sequelize/sqlite3'; +import { IsLowercase } from '@sequelize/validator.js'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +chai.use(chaiAsPromised); + +describe('@IsLowercase legacy decorator', () => { + it('validates that the attribute is lowercase', async () => { + class User extends Model { + @Attribute(DataTypes.STRING) + @IsLowercase + declare name: string; + } + + new Sequelize({ + dialect: SqliteDialect, + storage: ':memory:', + models: [User], + }); + + const user = User.build({ name: 'ABC' }); + + await expect(user.validate()).to.be.rejectedWith('Validation isLowercase on name failed'); + }); +}); diff --git a/packages/validator-js/tsconfig.json b/packages/validator-js/tsconfig.json new file mode 100644 index 000000000000..cfbb24587f8d --- /dev/null +++ b/packages/validator-js/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-preset.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/validator-js/typedoc.json b/packages/validator-js/typedoc.json new file mode 100644 index 000000000000..f593f276c273 --- /dev/null +++ b/packages/validator-js/typedoc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"] +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000000..5bad1313ad17 --- /dev/null +++ b/renovate.json @@ -0,0 +1,23 @@ +{ + "extends": [ + "config:recommended", + ":maintainLockFilesWeekly", + ":semanticCommitTypeAll(meta)", + ":semanticCommitScopeDisabled" + ], + "automergeStrategy": "squash", + "automergeType": "branch", + "branchConcurrentLimit": 2, + "semanticCommitType": "meta", + "ignorePaths": ["dev/**/oldest/docker-compose.yml"], + "platformAutomerge": true, + "packageRules": [ + { + "matchUpdateTypes": ["minor", "patch", "pin", "digest", "lockFileMaintenance"], + "automerge": true + } + ], + "rangeStrategy": "bump", + "labels": ["no-autoupdate"], + "postUpdateOptions": ["yarnDedupeHighest"] +} diff --git a/sscce.js b/sscce.js deleted file mode 100644 index 0c68f7d88102..000000000000 --- a/sscce.js +++ /dev/null @@ -1,31 +0,0 @@ -'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.ts b/sscce.ts new file mode 100644 index 000000000000..256fe863341d --- /dev/null +++ b/sscce.ts @@ -0,0 +1,40 @@ +import { DataTypes, Model } from '@sequelize/core'; +import { Attribute } from '@sequelize/core/decorators-legacy'; +import { SqliteDialect } from '@sequelize/sqlite3'; +import { expect } from 'chai'; +import { createSequelizeInstance } from './dev/sscce-helpers'; + +class User extends Model { + @Attribute({ + type: DataTypes.STRING, + allowNull: false, + }) + declare username: string; + + @Attribute({ + type: DataTypes.DATE, + allowNull: false, + }) + declare birthday: Date; +} + +const sequelize = createSequelizeInstance({ + dialect: SqliteDialect, + benchmark: true, + models: [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/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index c9dfaaf55a68..000000000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": { - "no-invalid-this": 0, - "no-unused-expressions": 0, - "camelcase": 0, - "no-console": 0, - "no-prototype-builtins": 0, - "no-multi-spaces": 0 - } -} diff --git a/test/config/config.js b/test/config/config.js deleted file mode 100644 index b15453ab3db6..000000000000 --- a/test/config/config.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const { env } = process; - -module.exports = { - 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', - dialectOptions: { - options: { - encrypt: false, - requestTimeout: 25000 - } - }, - pool: { - max: env.SEQ_MSSQL_POOL_MAX || env.SEQ_POOL_MAX || 5, - idle: env.SEQ_MSSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 - } - }, - - mysql: { - database: env.SEQ_MYSQL_DB || env.SEQ_DB || 'sequelize_test', - 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 || 20057, - pool: { - max: env.SEQ_MYSQL_POOL_MAX || env.SEQ_POOL_MAX || 5, - idle: env.SEQ_MYSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 - } - }, - - mariadb: { - database: env.SEQ_MARIADB_DB || env.SEQ_DB || 'sequelize_test', - 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 || 21103, - pool: { - max: env.SEQ_MARIADB_POOL_MAX || env.SEQ_POOL_MAX || 5, - idle: env.SEQ_MARIADB_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 - } - }, - - sqlite: {}, - - postgres: { - database: env.SEQ_PG_DB || env.SEQ_DB || 'sequelize_test', - 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 || 23010, - pool: { - max: env.SEQ_PG_POOL_MAX || env.SEQ_POOL_MAX || 5, - idle: env.SEQ_PG_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 - }, - minifyAliases: env.SEQ_PG_MINIFY_ALIASES - } -}; diff --git a/test/esm-named-exports.test.js b/test/esm-named-exports.test.js new file mode 100644 index 000000000000..f9aa4f3533e2 --- /dev/null +++ b/test/esm-named-exports.test.js @@ -0,0 +1,139 @@ +const chai = require('chai'); + +const expect = chai.expect; + +/** + * Tests whether the ESM named exports & the CJS exports are the same. + * Context: https://github.com/sequelize/sequelize/pull/13689 + */ + +// require('@sequelize/core') returns the Sequelize class +// The typings do not reflect this as some properties of the Sequelize class are not declared as exported in types/index.d.ts. +// This array lists the properties that are present on the class, but should not be exported in the esm export file nor in types/index.d.ts. +const ignoredCjsKeysMap = { + '@sequelize/core': [ + // make no sense to export + 'length', + 'prototype', + 'name', + 'version', + + // importing the data type directly has been removed, and accessing them on the Sequelize constructor is deprecated. + // Use DataTypes.x exclusively. + 'ABSTRACT', + 'ARRAY', + 'BIGINT', + 'BLOB', + 'BOOLEAN', + 'CHAR', + 'CIDR', + 'CITEXT', + 'DATE', + 'DATEONLY', + 'DECIMAL', + 'DOUBLE', + 'ENUM', + 'FLOAT', + 'GEOGRAPHY', + 'GEOMETRY', + 'HSTORE', + 'INET', + 'INTEGER', + 'JSON', + 'JSONB', + 'MACADDR', + 'MACADDR8', + 'MEDIUMINT', + 'NOW', + 'RANGE', + 'REAL', + 'SMALLINT', + 'STRING', + 'TEXT', + 'TIME', + 'TINYINT', + 'TSVECTOR', + 'UUID', + 'UUIDV1', + 'UUIDV4', + 'VIRTUAL', + ], + '@sequelize/core/decorators-legacy': ['__esModule'], + '@sequelize/db2': ['__esModule'], + '@sequelize/db2-ibmi': ['__esModule'], + '@sequelize/mariadb': ['__esModule'], + '@sequelize/mssql': ['__esModule'], + '@sequelize/mysql': ['__esModule'], + '@sequelize/postgres': ['__esModule'], + '@sequelize/snowflake': ['__esModule'], + '@sequelize/sqlite3': ['__esModule'], + '@sequelize/utils': ['__esModule'], + '@sequelize/utils/node': ['__esModule'], + '@sequelize/validator.js': ['__esModule'], +}; + +const exportPaths = Object.keys(ignoredCjsKeysMap); + +for (const exportPath of exportPaths) { + describe(`module "${exportPath}"`, () => { + it('exposes the same named exports as the CJS module', async () => { + // important: if you transpile this file, it's important + // that we still use both the native import() and the native require(). + // don't transpile this import() to a require(). + const sequelizeEsm = await import(exportPath); + const sequelizeCjs = require(exportPath); + + const esmKeys = Object.keys(sequelizeEsm); + + // include non-enumerables as "Sequelize.{and, or, ...}" are non-enumerable + const cjsKeys = Object.getOwnPropertyNames(sequelizeCjs); + + const ignoredCjsKeys = ignoredCjsKeysMap[exportPath]; + for (const key of ignoredCjsKeys) { + expect(cjsKeys).to.include( + key, + `Sequelize static property ${JSON.stringify(key)} is marked as ignored for ESM export but does not exist. Remove it from ignore list.`, + ); + } + + const missingEsmKeys = []; + for (const key of cjsKeys) { + if (ignoredCjsKeys.includes(key)) { + continue; + } + + if (!esmKeys.includes(key)) { + missingEsmKeys.push(key); + } + } + + expect(missingEsmKeys.length).to.eq( + 0, + `ESM entry point is missing exports: ${missingEsmKeys + .map(v => JSON.stringify(v)) + .join(', ')}. +Either add these exports the corresponding .mjs file (and .d.ts if applicable), or mark them as ignored in "esm-named-exports.test.js: + +${missingEsmKeys.map(key => `export const ${key} = Pkg.${key};\n`).join('')}" + `, + ); + + for (const key of esmKeys) { + expect(sequelizeEsm[key]).not.to.eq( + undefined, + `ESM is exporting undefined under key ${JSON.stringify(key)}`, + ); + + expect(cjsKeys).to.include( + key, + `ESM entry point is declaring export ${JSON.stringify(key)} that is missing from CJS`, + ); + + // exported values need to be the same instances + // if we want to avoid major bugs: + // https://github.com/sequelize/sequelize/pull/13689#issuecomment-987412233 + expect(sequelizeEsm[key]).to.eq(sequelizeCjs[key]); + } + }); + }); +} diff --git a/test/integration/assets/es6project.js b/test/integration/assets/es6project.js deleted file mode 100644 index a93e5b6f9532..000000000000 --- a/test/integration/assets/es6project.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; -exports.default = function(sequelize, DataTypes) { - return sequelize.define(`Project${parseInt(Math.random() * 9999999999999999, 10)}`, { - name: DataTypes.STRING - }); -}; diff --git a/test/integration/assets/project.js b/test/integration/assets/project.js deleted file mode 100644 index bb24beb8fc67..000000000000 --- a/test/integration/assets/project.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = function(sequelize, DataTypes) { - return sequelize.define(`Project${parseInt(Math.random() * 9999999999999999, 10)}`, { - name: DataTypes.STRING - }); -}; diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js deleted file mode 100644 index 03725fea1528..000000000000 --- a/test/integration/associations/belongs-to-many.test.js +++ /dev/null @@ -1,3376 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - _ = require('lodash'), - sinon = require('sinon'), - Op = Sequelize.Op, - current = Support.sequelize, - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('BelongsToMany'), () => { - describe('getAssociations', () => { - 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' }); - - 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', 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', 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', 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', 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', async function() { - const john = await this.User.findOne({ - where: { - username: 'John' - } - }); - - const tasks = await john.getTasks({ - where: { - title: { - [Op.not]: ['Get rich'] - } - } - }); - - expect(tasks).to.have.length(1); - }); - - it('supports a where not in on the primary key', async function() { - const john = await this.User.findOne({ - where: { - username: 'John' - } - }); - - const tasks = await john.getTasks({ - where: { - id: { - [Op.not]: [this.tasks[0].get('id')] - } - } - }); - - 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', 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', async function() { - const Label = this.sequelize.define('Label', { 'title': DataTypes.STRING, 'isActive': DataTypes.BOOLEAN }), - Task = this.Task, - User = this.User; - - Task.hasMany(Label); - Label.belongsTo(Task); - - 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', async function() { - const AcmeUser = this.sequelize.define('User', { - username: DataTypes.STRING - }).schema('acme', '_'), - AcmeProject = this.sequelize.define('Project', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }).schema('acme', '_'), - AcmeProjectUsers = this.sequelize.define('ProjectUsers', { - status: DataTypes.STRING, - data: DataTypes.INTEGER - }).schema('acme', '_'); - - AcmeUser.belongsToMany(AcmeProject, { through: AcmeProjectUsers }); - AcmeProject.belongsToMany(AcmeUser, { through: AcmeProjectUsers }); - - 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, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - allowNull: false - } - }, { - tableName: 'tbl_user' - }); - - const Group = this.sequelize.define('Group', { - 'id_group': { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4 - } - }, { - tableName: 'tbl_group' - }); - - const User_has_Group = this.sequelize.define('User_has_Group', { - - }, { - tableName: 'tbl_user_has_group' - }); - - User.belongsToMany(Group, { as: 'groups', through: User_has_Group, foreignKey: 'id_user' }); - Group.belongsToMany(User, { as: 'users', through: User_has_Group, foreignKey: 'id_group' }); - - 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', async function() { - const User = this.sequelize.define('User', { - userSecondId: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'user_id' - } - }, { - tableName: 'tbl_user' - }); - - const Group = this.sequelize.define('Group', { - groupSecondId: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'group_id' - } - }, { - tableName: 'tbl_group' - }); - - const User_has_Group = this.sequelize.define('User_has_Group', { - - }, { - tableName: 'tbl_user_has_group' - }); - - User.belongsToMany(Group, { through: User_has_Group }); - Group.belongsToMany(User, { through: User_has_Group }); - - 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)', async function() { - 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' }); - - 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, - 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' - } - }, { - tableName: 'tbl_group', - indexes: [ - { - unique: true, - fields: ['group_id'] - } - ] - }); - - User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); - Group.belongsToMany(User, { through: 'usergroups', 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 [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, - 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', 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 [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, - 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'] - } - ] - }); - - const User_has_Group = this.sequelize.define('User_has_Group', { - }, { - tableName: 'tbl_user_has_group', - indexes: [ - { - unique: true, - fields: ['UserUserSecondId', 'GroupGroupSecondId'] - } - ] - }); - - User.belongsToMany(Group, { through: User_has_Group, sourceKey: 'userSecondId' }); - Group.belongsToMany(User, { through: User_has_Group, sourceKey: 'groupSecondId' }); - - 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)', async function() { - 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', foreignKey: 'userId2', sourceKey: 'userSecondId' }); - Group.belongsToMany(User, { through: 'usergroups', foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - - 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, - 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'] - } - ] - }); - - const User_has_Group = this.sequelize.define('User_has_Group', { - userId2: { - type: DataTypes.UUID, - allowNull: false, - field: 'user_id2' - }, - groupId2: { - type: DataTypes.UUID, - allowNull: false, - field: 'group_id2' - } - }, { - tableName: 'tbl_user_has_group', - indexes: [ - { - unique: true, - fields: ['user_id2', 'group_id2'] - } - ] - }); - - User.belongsToMany(Group, { through: User_has_Group, foreignKey: 'userId2', sourceKey: 'userSecondId' }); - Group.belongsToMany(User, { through: User_has_Group, foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - - 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, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'user_id' - } - }, { - tableName: 'tbl_user' - }); - - const Company = this.sequelize.define('Company', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'company_id' - } - }, { - tableName: 'tbl_company' - }); - - const Group = this.sequelize.define('Group', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'group_id' - } - }, { - tableName: 'tbl_group' - }); - - const Company_has_Group = this.sequelize.define('Company_has_Group', { - - }, { - tableName: 'tbl_company_has_group' - }); - - User.belongsTo(Company); - Company.hasMany(User); - Company.belongsToMany(Group, { through: Company_has_Group }); - Group.belongsToMany(Company, { through: Company_has_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] } - ] - })]); - }); - }); - - describe('hasAssociations', () => { - beforeEach(function() { - this.Article = this.sequelize.define('Article', { - pk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - title: DataTypes.STRING - }); - this.Label = this.sequelize.define('Label', { - sk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - this.ArticleLabel = this.sequelize.define('ArticleLabel'); - - this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); - this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); - - return this.sequelize.sync({ force: true }); - }); - - if (current.dialect.supports.transactions) { - 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 - }); - - const Label = sequelize.define('Label', { - sk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - - const ArticleLabel = sequelize.define('ArticleLabel'); - - Article.belongsToMany(Label, { through: ArticleLabel }); - Label.belongsToMany(Article, { through: ArticleLabel }); - - await sequelize.sync({ force: true }); - - const [article, label] = await Promise.all([ - Article.create({ title: 'foo' }), - Label.create({ text: 'bar' }) - ]); - - const t = await sequelize.transaction(); - await article.setLabels([label], { transaction: t }); - const articles = await Article.findAll({ transaction: t }); - - const [hasLabel1, hasLabel2] = await Promise.all([ - articles[0].hasLabels([label]), - articles[0].hasLabels([label], { transaction: t }) - ]); - - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.true; - - await t.rollback(); - }); - } - - it('answers false if only some labels have been assigned', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - this.ArticleLabel = this.sequelize.define('ArticleLabel', { - id: { - primaryKey: true, - type: DataTypes.INTEGER, - autoIncrement: true - }, - relevance: { - type: DataTypes.DECIMAL, - validate: { - min: 0, - max: 1 - } - } - }); - this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); - this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - - 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', async function() { - this.ArticleLabel = this.sequelize.define('ArticleLabel', { - id: { - primaryKey: true, - type: DataTypes.INTEGER, - autoIncrement: true - }, - relevance: { - type: DataTypes.DECIMAL, - validate: { - min: 0, - max: 1 - } - } - }); - this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); - this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - - await this.sequelize.sync({ force: true }); - - const [article0, label10, label20] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - const [article, label1, label2] = await Promise.all([ - article0, - label10, - label20, - article0.addLabel(label10, { - through: { relevance: 1 } - }), - article0.addLabel(label20, { - through: { relevance: .54 } - }), - article0.addLabel(label20, { - through: { relevance: .99 } - }) - ]); - - const result = await article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]); - - await expect(result).to.be.true; - }); - }); - - describe('hasAssociations with binary key', () => { - beforeEach(function() { - const keyDataType = dialect === 'mysql' || dialect === 'mariadb' ? 'BINARY(255)' : DataTypes.BLOB('tiny'); - this.Article = this.sequelize.define('Article', { - id: { - type: keyDataType, - primaryKey: true - } - }); - this.Label = this.sequelize.define('Label', { - id: { - type: keyDataType, - primaryKey: true - } - }); - this.ArticleLabel = this.sequelize.define('ArticleLabel'); - - this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); - this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); - - return this.sequelize.sync({ force: true }); - }); - - it('answers true for labels that have been assigned', async function() { - const [article0, label0] = await Promise.all([ - this.Article.create({ - id: Buffer.alloc(255) - }), - this.Label.create({ - id: Buffer.alloc(255) - }) - ]); - - const [article, label] = await Promise.all([ - article0, - label0, - article0.addLabel(label0, { - through: 'ArticleLabel' - }) - ]); - - const result = await article.hasLabels([label]); - await expect(result).to.be.true; - }); - - it('answer false for labels that have not been assigned', async function() { - const [article, label] = await Promise.all([ - this.Article.create({ - id: Buffer.alloc(255) - }), - this.Label.create({ - id: Buffer.alloc(255) - }) - ]); - - const result = await article.hasLabels([label]); - await expect(result).to.be.false; - }); - }); - - describe('countAssociations', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - this.Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }); - this.UserTask = this.sequelize.define('UserTask', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - started: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - this.User.belongsToMany(this.Task, { through: this.UserTask }); - this.Task.belongsToMany(this.User, { through: this.UserTask }); - - await this.sequelize.sync({ force: true }); - - const [john, task1, task2] = await Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - - this.tasks = [task1, task2]; - this.user = john; - - return john.setTasks([task1, task2]); - }); - - it('should count all associations', async function() { - expect(await this.user.countTasks({})).to.equal(2); - }); - - it('should count filtered associations', async function() { - expect(await this.user.countTasks({ where: { active: true } })).to.equal(1); - }); - - it('should count scoped associations', async function() { - this.User.belongsToMany(this.Task, { - as: 'activeTasks', - through: this.UserTask, - scope: { - active: true - } - }); - - expect(await this.user.countActiveTasks({})).to.equal(1); - }); - - it('should count scoped through associations', async function() { - this.User.belongsToMany(this.Task, { - as: 'startedTasks', - through: { - model: this.UserTask, - scope: { - started: true - } - } - }); - - 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', 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' }); - - 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('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' }); - - 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) - }), - Member = this.sequelize.define('member', { - member_id: { type: DataTypes.INTEGER, primaryKey: true }, - email: DataTypes.STRING(64) - }); - - Group.belongsToMany(Member, { through: 'group_members', foreignKey: 'group_id', otherKey: 'member_id' }); - Member.belongsToMany(Group, { through: 'group_members', foreignKey: 'member_id', otherKey: 'group_id' }); - - 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', 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' }); - - 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', async function() { - const ItemTag = this.sequelize.define('ItemTag', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - tag_id: { type: DataTypes.INTEGER, unique: false }, - taggable: { type: DataTypes.STRING }, - taggable_id: { type: DataTypes.INTEGER, unique: false } - }), - Tag = this.sequelize.define('Tag', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }), - Comment = this.sequelize.define('Comment', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }), - Post = this.sequelize.define('Post', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }); - - Post.belongsToMany(Tag, { - through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, - foreignKey: 'taggable_id' - }); - - Comment.belongsToMany(Tag, { - through: { model: ItemTag, unique: false, scope: { taggable: 'comment' } }, - foreignKey: 'taggable_id' - }); - - 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', async function() { - const ItemTag = this.sequelize.define('ItemTag', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - tag_id: { type: DataTypes.INTEGER, unique: false }, - taggable: { type: DataTypes.STRING }, - taggable_id: { type: DataTypes.INTEGER, unique: false } - }), - Tag = this.sequelize.define('Tag', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }), - Comment = this.sequelize.define('Comment', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }), - Post = this.sequelize.define('Post', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, - name: DataTypes.STRING - }); - - Post.belongsToMany(Tag, { - through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, - foreignKey: 'taggable_id' - }); - - Comment.belongsToMany(Tag, { - through: { model: ItemTag, unique: false, scope: { taggable: 'comment' } }, - foreignKey: 'taggable_id' - }); - - await this.sequelize.sync({ force: true }); - - const [post, comment, tag, secondTag] = await Promise.all([ - Post.create({ name: 'post1' }), - Comment.create({ name: 'comment1' }), - Tag.create({ name: 'tag1' }), - Tag.create({ name: 'tag2' }) - ]); - - this.post = post; - this.comment = comment; - this.tag = tag; - this.secondTag = secondTag; - await this.post.setTags([this.tag, this.secondTag]); - await this.comment.setTags([this.tag, this.secondTag]); - await this.post.setTags([this.tag]); - - const [postTags, commentTags] = await Promise.all([ - this.post.getTags(), - this.comment.getTags() - ]); - - expect(postTags).to.have.length(1); - expect(commentTags).to.have.length(2); - }); - - it('should catch EmptyResultError when rejectOnEmpty is set', async function() { - const User = this.sequelize.define( - 'User', - { username: DataTypes.STRING }, - { rejectOnEmpty: true } - ); - const Task = this.sequelize.define( - 'Task', - { title: DataTypes.STRING } - ); - - User.belongsToMany(Task, { through: 'UserTasks' }); - Task.belongsToMany(User, { through: 'UserTasks' }); - - 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', 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' }); - - 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', 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', async function() { - const User = this.sequelize.define('user', {}), - Group = this.sequelize.define('group', {}), - UserGroups = this.sequelize.define('user_groups', { - isAdmin: Sequelize.BOOLEAN - }); - - User.belongsToMany(Group, { through: UserGroups }); - Group.belongsToMany(User, { through: UserGroups }); - - 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', 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' }); - - 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', 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' }); - - 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', 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 [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 }); - - 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', 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' }); - - 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', async function() { - const User = this.sequelize.define( - 'User', - { username: DataTypes.STRING }, - { - indexes: [ - { - name: 'username_unique', - unique: true, - method: 'BTREE', - fields: ['username'] - } - ] - }); - const Task = this.sequelize.define( - 'Task', - { title: DataTypes.STRING }, - { - indexes: [ - { - name: 'title_index', - method: 'BTREE', - fields: ['title'] - } - ] - }); - //create associations - User.belongsToMany(Task, { through: 'UserTasks' }); - Task.belongsToMany(User, { through: 'UserTasks' }); - await this.sequelize.sync({ force: true }); - }); - - it('should catch EmptyResultError when rejectOnEmpty is set', async function() { - const User = this.sequelize.define( - 'User', - { username: DataTypes.STRING }, - { rejectOnEmpty: true } - ); - const Task = this.sequelize.define( - 'Task', - { title: DataTypes.STRING } - ); - - User.belongsToMany(Task, { through: 'UserTasks' }); - Task.belongsToMany(User, { through: 'UserTasks' }); - - 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', async function() { - const User = this.sequelize.define('User'); - const Task = this.sequelize.define('Task'); - const UserTask = this.sequelize.define('UserTask'); - - User.belongsToMany(Task, { through: UserTask }); - Task.belongsToMany(User, { through: 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', 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' }); - - 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', 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' }); - - 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(async function() { - const Project = this.sequelize.define('Project', { - name: Sequelize.STRING - }); - - const Employee = this.sequelize.define('Employee', { - name: Sequelize.STRING - }); - - const Participation = this.sequelize.define('Participation', { - role: { - type: Sequelize.STRING, - allowNull: false, - validate: { - len: { - args: [2, 50], - msg: 'too bad' - } - } - } - }); - - Project.belongsToMany(Employee, { as: 'Participants', through: Participation }); - Employee.belongsToMany(Project, { as: 'Participations', through: Participation }); - - 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', async function() { - await expect(this.project.addParticipant(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', async function() { - await expect(this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } })).to.be.rejected; - }); - }); - - describe('optimizations using bulk create, destroy and update', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', { username: DataTypes.STRING }, { timestamps: false }); - this.Task = this.sequelize.define('Task', { title: DataTypes.STRING }, { timestamps: false }); - - this.User.belongsToMany(this.Task, { through: 'UserTasks' }); - this.Task.belongsToMany(this.User, { through: 'UserTasks' }); - - return this.sequelize.sync({ force: true }); - }); - - it('uses one insert into statement', async function() { - const spy = sinon.spy(); - - 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' }) - ]); - - await user.setTasks([task1, task2], { - logging: spy - }); - - expect(spy.calledTwice).to.be.ok; - }); - - it('uses one delete from statement', async function() { - const spy = sinon.spy(); - - const [user0, task1, task2] = await Promise.all([ - this.User.create({ username: 'foo' }), - this.Task.create({ title: 'task1' }), - this.Task.create({ title: 'task2' }) - ]); - - 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 - - describe('join table creation', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', - { username: DataTypes.STRING }, - { tableName: 'users' } - ); - this.Task = this.sequelize.define('Task', - { title: DataTypes.STRING }, - { tableName: 'tasks' } - ); - - this.User.belongsToMany(this.Task, { through: 'user_has_tasks' }); - this.Task.belongsToMany(this.User, { through: 'user_has_tasks' }); - - return this.sequelize.sync({ force: true }); - }); - - it('should work with non integer primary keys', async function() { - const Beacons = this.sequelize.define('Beacon', { - id: { - primaryKey: true, - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4 - }, - name: { - type: DataTypes.STRING - } - }); - - // User not to clash with the beforeEach definition - const Users = this.sequelize.define('Usar', { - name: { - type: DataTypes.STRING - } - }); - - Beacons.belongsToMany(Users, { through: 'UserBeacons' }); - Users.belongsToMany(Beacons, { through: 'UserBeacons' }); - - await this.sequelize.sync({ force: true }); - }); - - it('makes join table non-paranoid by default', () => { - const paranoidSequelize = Support.createSequelizeInstance({ - define: { - paranoid: true - } - }), - ParanoidUser = paranoidSequelize.define('ParanoidUser', {}), - ParanoidTask = paranoidSequelize.define('ParanoidTask', {}); - - ParanoidUser.belongsToMany(ParanoidTask, { through: 'UserTasks' }); - ParanoidTask.belongsToMany(ParanoidUser, { through: 'UserTasks' }); - - expect(ParanoidUser.options.paranoid).to.be.ok; - expect(ParanoidTask.options.paranoid).to.be.ok; - - _.forEach(ParanoidUser.associations, association => { - 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', () => { - it('should correctly generate underscored keys', function() { - const User = this.sequelize.define('User', { - - }, { - tableName: 'users', - underscored: true, - timestamps: false - }); - - const Place = this.sequelize.define('Place', { - //fields - }, { - tableName: 'places', - underscored: true, - timestamps: false - }); - - User.belongsToMany(Place, { through: 'user_places' }); - Place.belongsToMany(User, { through: 'user_places' }); - - const attributes = this.sequelize.model('user_places').rawAttributes; - - expect(attributes.PlaceId.field).to.equal('place_id'); - expect(attributes.UserId.field).to.equal('user_id'); - }); - - it('should infer otherKey from paired BTM relationship with a through string defined', function() { - const User = this.sequelize.define('User', {}); - const Place = this.sequelize.define('Place', {}); - - const Places = User.belongsToMany(Place, { through: 'user_places', foreignKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: 'user_places', foreignKey: 'place_id' }); - - expect(Places.foreignKey).to.equal('user_id'); - expect(Users.foreignKey).to.equal('place_id'); - - expect(Places.otherKey).to.equal('place_id'); - expect(Users.otherKey).to.equal('user_id'); - }); - - it('should infer otherKey from paired BTM relationship with a through model defined', function() { - const User = this.sequelize.define('User', {}); - const Place = this.sequelize.define('Place', {}); - const UserPlace = this.sequelize.define('UserPlace', { id: { primaryKey: true, type: DataTypes.INTEGER, autoIncrement: true } }, { timestamps: false }); - - const Places = User.belongsToMany(Place, { through: UserPlace, foreignKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: UserPlace, foreignKey: 'place_id' }); - - expect(Places.foreignKey).to.equal('user_id'); - expect(Users.foreignKey).to.equal('place_id'); - - expect(Places.otherKey).to.equal('place_id'); - expect(Users.otherKey).to.equal('user_id'); - - expect(Object.keys(UserPlace.rawAttributes).length).to.equal(3); // Defined primary key and two foreign keys - }); - }); - - describe('foreign key with fields specified', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', { name: DataTypes.STRING }); - this.Project = this.sequelize.define('Project', { name: DataTypes.STRING }); - this.Puppy = this.sequelize.define('Puppy', { breed: DataTypes.STRING }); - - // doubly linked has many - this.User.belongsToMany(this.Project, { - through: 'user_projects', - as: 'Projects', - foreignKey: { - field: 'user_id', - name: 'userId' - }, - otherKey: { - field: 'project_id', - name: 'projectId' - } - }); - this.Project.belongsToMany(this.User, { - through: 'user_projects', - as: 'Users', - foreignKey: { - field: 'project_id', - name: 'projectId' - }, - otherKey: { - field: 'user_id', - name: 'userId' - } - }); - }); - - it('should correctly get associations even after a child instance is deleted', async function() { - const spy = sinon.spy(); - - 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(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', async function() { - const spy = sinon.spy(); - await this.sequelize.sync({ force: true }); - - 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', async function() { - this.Group = this.sequelize.define('Group', { groupName: DataTypes.STRING }); - - this.Group.belongsToMany(this.User, { - through: 'group_users', - as: 'Users', - foreignKey: { - field: 'group_id', - name: 'groupId' - }, - otherKey: { - field: 'user_id', - name: 'userId' - } - }); - this.User.belongsToMany(this.Group, { - through: 'group_users', - as: 'Groups', - foreignKey: { - field: 'user_id', - name: 'userId' - }, - otherKey: { - field: 'group_id', - name: 'groupId' - } - }); - - 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' }) - ]); - - await user0.addProject(project0); - await group1.addUser(user0); - const group0 = group1; - - // get the group and include both the users in the group and their project's - const groups = await this.Group.findAll({ - where: { id: group0.id }, - include: [ - { - model: this.User, - as: 'Users', - include: [ - { model: this.Project, as: 'Projects' } - ] - } - ] - }); - - const group = groups[0]; - expect(group).to.be.ok; - - const user = group.Users[0]; - expect(user).to.be.ok; - - const project = user.Projects[0]; - expect(project).to.be.ok; - expect(project.name).to.equal('Good Will Hunting'); - }); - }); - - describe('primary key handling for join table', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', - { username: DataTypes.STRING }, - { tableName: 'users' } - ); - this.Task = this.sequelize.define('Task', - { title: DataTypes.STRING }, - { tableName: 'tasks' } - ); - }); - - it('removes the primary key if it was added by sequelize', function() { - this.UserTasks = this.sequelize.define('usertasks', {}); - - this.User.belongsToMany(this.Task, { through: this.UserTasks }); - this.Task.belongsToMany(this.User, { through: this.UserTasks }); - - expect(Object.keys(this.UserTasks.primaryKeys).sort()).to.deep.equal(['TaskId', 'UserId']); - }); - - it('keeps the primary key if it was added by the user', function() { - let fk; - - this.UserTasks = this.sequelize.define('usertasks', { - id: { - type: Sequelize.INTEGER, - autoincrement: true, - primaryKey: true - } - }); - this.UserTasks2 = this.sequelize.define('usertasks2', { - userTasksId: { - type: Sequelize.INTEGER, - autoincrement: true, - primaryKey: true - } - }); - - this.User.belongsToMany(this.Task, { through: this.UserTasks }); - this.Task.belongsToMany(this.User, { through: this.UserTasks }); - - this.User.belongsToMany(this.Task, { through: this.UserTasks2 }); - this.Task.belongsToMany(this.User, { through: this.UserTasks2 }); - - expect(Object.keys(this.UserTasks.primaryKeys)).to.deep.equal(['id']); - expect(Object.keys(this.UserTasks2.primaryKeys)).to.deep.equal(['userTasksId']); - - [this.UserTasks, this.UserTasks2].forEach(model => { - fk = Object.keys(model.uniqueKeys)[0]; - expect(model.uniqueKeys[fk].fields.sort()).to.deep.equal(['TaskId', 'UserId']); - }); - }); - - describe('without sync', () => { - 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', async function() { - this.UsersTasks = this.sequelize.define('UsersTasks', {}, { tableName: 'users_tasks' }); - - this.User.belongsToMany(this.Task, { through: this.UsersTasks }); - this.Task.belongsToMany(this.User, { through: this.UsersTasks }); - - expect(Object.keys(this.UsersTasks.primaryKeys).sort()).to.deep.equal(['TaskId', 'UserId']); - - const [user0, task] = await Promise.all([ - this.User.create({ username: 'foo' }), - this.Task.create({ title: 'foo' }) - ]); - - await user0.addTask(task); - const user = user0; - const result = await user.setTasks(null); - expect(result).to.be.ok; - }); - }); - }); - - describe('through', () => { - describe('paranoid', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', {}, { - paranoid: true - }); - - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); - - await this.sequelize.sync(); - - this.users = await Promise.all([ - this.User.create(), - this.User.create(), - this.User.create() - ]); - - this.projects = await Promise.all([ - this.Project.create(), - this.Project.create(), - this.Project.create() - ]); - }); - - it('gets only non-deleted records by default', async function() { - await this.users[0].addProjects(this.projects); - await this.UserProjects.destroy({ - where: { - ProjectId: this.projects[0].id - } - }); - - const result = await this.users[0].getProjects(); - - expect(result.length).to.equal(2); - }); - - it('returns both deleted and non-deleted records with paranoid=false', async function() { - await this.users[0].addProjects(this.projects); - await this.UserProjects.destroy({ - where: { - ProjectId: this.projects[0].id - } - }); - - const result = await this.users[0].getProjects({ through: { paranoid: false } }); - - expect(result.length).to.equal(3); - }); - - it('hasAssociation also respects paranoid option', async function() { - await this.users[0].addProjects(this.projects); - await this.UserProjects.destroy({ - where: { - ProjectId: this.projects[0].id - } - }); - - expect( - await this.users[0].hasProjects(this.projects[0], { through: { paranoid: false } }) - ).to.equal(true); - - expect( - await this.users[0].hasProjects(this.projects[0]) - ).to.equal(false); - - expect( - await this.users[0].hasProjects(this.projects[1]) - ).to.equal(true); - - expect( - await this.users[0].hasProjects(this.projects) - ).to.equal(false); - }); - }); - - describe('fetching from join table', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', { - status: DataTypes.STRING, - data: DataTypes.INTEGER - }); - - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); - - return this.sequelize.sync(); - }); - - it('should contain the data from the join table on .UserProjects a DAO', async function() { - const [user0, project0] = await Promise.all([ - this.User.create(), - this.Project.create() - ]); - - 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', async function() { - const [user, project0] = await Promise.all([ - this.User.create(), - this.Project.create() - ]); - - await user.addProject(project0, { through: { status: 'active', data: 42 } }); - - const users = await this.User.findAll({ - include: [{ - model: this.Project, - through: { - as: 'myProject' - } - }] - }); - - const project = users[0].Projects[0]; - - expect(project.UserProjects).not.to.exist; - expect(project.status).not.to.exist; - expect(project.myProject).to.be.ok; - expect(project.myProject.status).to.equal('active'); - expect(project.myProject.data).to.equal(42); - }); - - it('should be able to limit the join table attributes returned', async function() { - const [user0, project0] = await Promise.all([ - this.User.create(), - this.Project.create() - ]); - - await user0.addProject(project0, { through: { status: 'active', data: 42 } }); - const user = user0; - const projects = await user.getProjects({ joinTableAttributes: ['status'] }); - const project = projects[0]; - - expect(project.UserProjects).to.be.ok; - expect(project.status).not.to.exist; - expect(project.UserProjects.status).to.equal('active'); - expect(project.UserProjects.data).not.to.exist; - }); - }); - - describe('inserting in join table', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', { - status: DataTypes.STRING, - data: DataTypes.INTEGER - }); - - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); - - return this.sequelize.sync(); - }); - - describe('add', () => { - it('should insert data provided on the object into the join table', async function() { - const [u, p] = await Promise.all([ - this.User.create(), - this.Project.create() - ]); - - 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', async function() { - const [u, p] = await Promise.all([ - this.User.create(), - this.Project.create() - ]); - - 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', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), - Task = this.sequelize.define('Task', {}, { timestamps: false }), - WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); - - Worker.belongsToMany(Task, { through: WorkerTasks }); - Task.belongsToMany(Worker, { through: WorkerTasks }); - - 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', async function() { - const Worker = this.sequelize.define('Worker', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - } - }, { timestamps: false }), - Task = this.sequelize.define('Task', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - } - }, { timestamps: false }), - WorkerTasks = this.sequelize.define('WorkerTasks', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - } - }, { timestamps: false }); - - Worker.belongsToMany(Task, { through: WorkerTasks }); - Task.belongsToMany(Worker, { through: WorkerTasks }); - - 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', 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 }); - - await this.sequelize.sync({ force: true }); - - const foo0 = await Foo.create({ - name: 'foo...', - bars: [ - { - name: 'bar...', - foobar: { - baz: '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', async function() { - await this.Project.bulkCreate([{}, {}]); - - const [user, projects] = await Promise.all([ - this.User.create(), - 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', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), - Task = this.sequelize.define('Task', {}, { timestamps: false }), - WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); - - Worker.belongsToMany(Task, { through: WorkerTasks }); - Task.belongsToMany(Worker, { through: WorkerTasks }); - - 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', 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', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), - Task = this.sequelize.define('Task', {}, { timestamps: false }), - WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); - - Worker.belongsToMany(Task, { through: WorkerTasks }); - Task.belongsToMany(Worker, { through: WorkerTasks }); - - // Test setup - 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', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), - Task = this.sequelize.define('Task', {}, { timestamps: false }), - WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); - - Worker.belongsToMany(Task, { through: WorkerTasks }); - Task.belongsToMany(Worker, { through: WorkerTasks }); - - // Test setup - 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); - }); - }); - }); - - describe('belongsTo and hasMany at once', () => { - beforeEach(function() { - this.A = this.sequelize.define('a', { name: Sequelize.STRING }); - this.B = this.sequelize.define('b', { name: Sequelize.STRING }); - }); - - describe('source belongs to target', () => { - beforeEach(function() { - this.A.belongsTo(this.B, { as: 'relation1' }); - this.A.belongsToMany(this.B, { as: 'relation2', through: 'AB' }); - this.B.belongsToMany(this.A, { as: 'relation2', through: 'AB' }); - - return this.sequelize.sync({ force: true }); - }); - - it('correctly uses bId in A', async function() { - const a1 = this.A.build({ name: 'a1' }), - b1 = this.B.build({ name: 'b1' }); - - 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); - }); - }); - - describe('target belongs to source', () => { - beforeEach(function() { - this.B.belongsTo(this.A, { as: 'relation1' }); - this.A.belongsToMany(this.B, { as: 'relation2', through: 'AB' }); - this.B.belongsToMany(this.A, { as: 'relation2', through: 'AB' }); - - return this.sequelize.sync({ force: true }); - }); - - it('correctly uses bId in A', async function() { - const a1 = this.A.build({ name: 'a1' }), - b1 = this.B.build({ name: 'b1' }); - - 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', 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' }); - - 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; - }); - - 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' }); - - User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup }); - Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup }); - - 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'); - }); - - it('correctly identifies its counterpart when through is a string', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - User.belongsToMany(Group, { as: 'MyGroups', through: 'group_user' }); - Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user' }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist; - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist; - }); - - it('correctly identifies its counterpart when through is a model', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}), - UserGroup = this.sequelize.define('GroupUser', {}, { tableName: 'user_groups' }); - - User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup }); - Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId).to.exist; - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId).to.exist; - }); - }); - - describe('multiple hasMany', () => { - beforeEach(function() { - this.User = this.sequelize.define('user', { name: Sequelize.STRING }); - this.Project = this.sequelize.define('project', { projectName: Sequelize.STRING }); - }); - - describe('project has owners and users and owners and users have projects', () => { - beforeEach(function() { - this.Project.belongsToMany(this.User, { as: 'owners', through: 'projectOwners' }); - this.Project.belongsToMany(this.User, { as: 'users', through: 'projectUsers' }); - - this.User.belongsToMany(this.Project, { as: 'ownedProjects', through: 'projectOwners' }); - this.User.belongsToMany(this.Project, { as: 'memberProjects', through: 'projectUsers' }); - - return this.sequelize.sync({ force: true }); - }); - - it('correctly sets user and owner', async function() { - const p1 = this.Project.build({ projectName: 'p1' }), - u1 = this.User.build({ name: 'u1' }), - u2 = this.User.build({ name: 'u2' }); - - await p1 - .save(); - - await u1.save(); - await u2.save(); - await p1.setUsers([u1]); - - await p1.setOwners([u2]); - }); - }); - }); - - describe('Foreign key constraints', () => { - beforeEach(function() { - this.Task = this.sequelize.define('task', { title: DataTypes.STRING }); - this.User = this.sequelize.define('user', { username: DataTypes.STRING }); - this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); - }); - - it('can cascade deletes both ways by default', async function() { - this.User.belongsToMany(this.Task, { through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { 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 [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' }) - ]); - - await Promise.all([ - user1.setTasks([task1]), - task2.setUsers([user2]) - ]); - - 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', async function() { - this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { onDelete: 'CASCADE', 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([ - expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint - task2.destroy() - ]); - - 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', async function() { - this.UserTasksLong = this.sequelize.define('table_user_task_with_very_long_name', { - id_user_very_long_field: { - type: DataTypes.INTEGER(1) - }, - id_task_very_long_field: { - type: DataTypes.INTEGER(1) - } - }, - { tableName: 'table_user_task_with_very_long_name' } - ); - this.User.belongsToMany(this.Task, { - as: 'MyTasks', - through: this.UserTasksLong, - foreignKey: 'id_user_very_long_field' - }); - this.Task.belongsToMany(this.User, { - as: 'MyUsers', - through: this.UserTasksLong, - foreignKey: 'id_task_very_long_field', - uniqueKey: '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'); - }); - }); - - describe('Association options', () => { - describe('allows the user to provide an attribute definition object as foreignKey', () => { - it('works when taking a column directly from the object', function() { - const Project = this.sequelize.define('project', {}), - User = this.sequelize.define('user', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true - } - }); - - const UserProjects = User.belongsToMany(Project, { foreignKey: { name: 'user_id', defaultValue: 42 }, through: 'UserProjects' }); - expect(UserProjects.through.model.rawAttributes.user_id).to.be.ok; - expect(UserProjects.through.model.rawAttributes.user_id.references.model).to.equal(User.getTableName()); - expect(UserProjects.through.model.rawAttributes.user_id.references.key).to.equal('uid'); - expect(UserProjects.through.model.rawAttributes.user_id.defaultValue).to.equal(42); - }); - }); - - it('should throw an error if foreignKey and as result in a name clash', function() { - const User = this.sequelize.define('user', { - user: Sequelize.INTEGER - }); - - expect(User.belongsToMany.bind(User, User, { as: 'user', through: 'UserUser' })).to - .throw('Naming collision between attribute \'user\' and association \'user\' on model user. To remedy this, change either foreignKey or as in your association definition'); - }); - }); - - describe('thisAssociations', () => { - it('should work with this reference', async function() { - const User = this.sequelize.define('User', { - name: Sequelize.STRING(100) - }), - Follow = this.sequelize.define('Follow'); - - User.belongsToMany(User, { through: Follow, as: 'User' }); - User.belongsToMany(User, { through: Follow, as: 'Fan' }); - - 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', async function() { - const User = this.sequelize.define('User', { - name: Sequelize.STRING(100) - }), - UserFollowers = this.sequelize.define('UserFollower'); - - User.belongsToMany(User, { - as: { - singular: 'Follower', - plural: 'Followers' - }, - through: UserFollowers - }); - - User.belongsToMany(User, { - as: { - singular: 'Invitee', - plural: 'Invitees' - }, - foreignKey: 'InviteeId', - through: 'Invites' - }); - - 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() { - /* camelCase */ - let Person = this.sequelize.define('Person'), - PersonChildren = this.sequelize.define('PersonChildren'), - Children; - - Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren }); - - expect(Children.foreignKey).to.equal('PersonId'); - expect(Children.otherKey).to.equal('ChildId'); - expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok; - expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok; - - /* underscored */ - Person = this.sequelize.define('Person', {}, { underscored: true }); - PersonChildren = this.sequelize.define('PersonChildren', {}, { underscored: true }); - Children = Person.belongsToMany(Person, { as: 'Children', through: PersonChildren }); - - expect(Children.foreignKey).to.equal('PersonId'); - expect(Children.otherKey).to.equal('ChildId'); - expect(PersonChildren.rawAttributes[Children.foreignKey]).to.be.ok; - expect(PersonChildren.rawAttributes[Children.otherKey]).to.be.ok; - expect(PersonChildren.rawAttributes[Children.foreignKey].field).to.equal('person_id'); - expect(PersonChildren.rawAttributes[Children.otherKey].field).to.equal('child_id'); - }); - }); - - describe('Eager loading', () => { - beforeEach(function() { - this.Individual = this.sequelize.define('individual', { - name: Sequelize.STRING - }); - this.Hat = this.sequelize.define('hat', { - name: Sequelize.STRING - }); - this.Event = this.sequelize.define('event', {}); - this.Individual.belongsToMany(this.Hat, { - through: this.Event, - as: { - singular: 'personwearinghat', - plural: 'personwearinghats' - } - }); - this.Hat.belongsToMany(this.Individual, { - through: this.Event, - as: { - singular: 'hatwornby', - plural: 'hatwornbys' - } - }); - }); - - 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 deleted file mode 100644 index e9cbaeaacb31..000000000000 --- a/test/integration/associations/belongs-to.test.js +++ /dev/null @@ -1,947 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - current = Support.sequelize, - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('BelongsTo'), () => { - describe('Model.associations', () => { - it('should store all associations when associating to the same table multiple times', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - Group.belongsTo(User); - Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }); - Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }); - - expect( - Object.keys(Group.associations) - ).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers']); - }); - }); - - describe('get', () => { - describe('multiple', () => { - 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' }); - - 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', 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.', 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); - - 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', 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); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - allowNull: false - } - }).schema('archive'), - Task = this.sequelize.define('TaskXYZ', { - user_id: { - type: Sequelize.INTEGER, - references: { model: User, key: 'uid' } - } - }).schema('archive'); - - Task.belongsTo(User, { foreignKey: 'user_id' }); - - 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', 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...', 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' }); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); - - Task.belongsTo(User); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); - - Task.belongsTo(User); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); - - Task.belongsTo(User); - - 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', 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); - - 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', async function() { - const Comment = this.sequelize.define('comment', { - text: DataTypes.STRING - }); - - const Post = this.sequelize.define('post', { - title: DataTypes.STRING - }); - - Post.hasOne(Comment); - Comment.belongsTo(Post); - - 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', async function() { - const Comment = this.sequelize.define('comment', { - text: DataTypes.STRING - }); - - const Post = this.sequelize.define('post', { - title: DataTypes.STRING - }); - - Post.hasMany(Comment, { foreignKey: 'post_id' }); - Comment.belongsTo(Post, { foreignKey: 'post_id' }); - - await this.sequelize.sync({ force: true }); - const [post, comment] = await Promise.all([Post.create(), Comment.create()]); - expect(comment.get('post_id')).not.to.be.ok; - - const setter = await comment.setPost(post, { save: false }); - - expect(setter).to.be.undefined; - expect(comment.get('post_id')).to.equal(post.get('id')); - expect(comment.changed('post_id')).to.be.true; - }); - - it('supports setting same association twice', async function() { - const Home = this.sequelize.define('home', {}); - const User = this.sequelize.define('user'); - - Home.belongsTo(User); - - 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - Task.belongsTo(User); - - 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', 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(); - }); - } - }); - - describe('foreign key', () => { - it('should setup underscored field with foreign keys when using underscored', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: true }), - Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: true }); - - User.belongsTo(Account); - - expect(User.rawAttributes.AccountId).to.exist; - expect(User.rawAttributes.AccountId.field).to.equal('account_id'); - }); - - it('should use model name when using camelcase', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }), - Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: false }); - - User.belongsTo(Account); - - expect(User.rawAttributes.AccountId).to.exist; - expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); - }); - - 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 }); - - User.belongsTo(Account, { - foreignKey: { - name: 'AccountId', - field: 'account_id' - } - }); - - expect(User.rawAttributes.AccountId).to.exist; - expect(User.rawAttributes.AccountId.field).to.equal('account_id'); - - 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', 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 }); - - Entry.belongsTo(User, { - as: 'owner', - foreignKey: { - name: 'ownerId', - allowNull: false - } - }); - Entry.belongsTo(Mail, { - as: 'mail', - foreignKey: { - name: 'mailId', - allowNull: false - } - }); - Mail.belongsToMany(User, { - as: 'recipients', - through: 'MailRecipients', - otherKey: { - name: 'recipientId', - allowNull: false - }, - foreignKey: { - name: 'mailId', - allowNull: false - }, - timestamps: false - }); - Mail.hasMany(Entry, { - as: 'entries', - foreignKey: { - name: 'mailId', - allowNull: false - } - }); - User.hasMany(Entry, { - as: 'entries', - foreignKey: { - name: 'ownerId', - allowNull: false - } - }); - - 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', 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 - - 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', 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 - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - Task.belongsTo(User, { constraints: false }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - Task.belongsTo(User, { onDelete: 'cascade' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - Task.belongsTo(User, { onDelete: 'restrict' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - Task.belongsTo(User, { onUpdate: 'restrict' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - Task.belongsTo(User, { onUpdate: 'cascade' }); - - 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', async function() { - const User = this.sequelize.define('UserPKBT', { - username: { - type: DataTypes.STRING - } - }); - - const Group = this.sequelize.define('GroupPKBT', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }); - - User.belongsTo(Group); - - 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', 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' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - field: 'user_name', - unique: true - } - }); - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - field: 'the_user_name_field', - unique: true - } - }); - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - User.removeAttribute('id'); - Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - field: 'the_user_name_field', - unique: true - }, - age: { - type: DataTypes.INTEGER, - field: 'the_user_age_field', - primaryKey: true - }, - weight: { - type: DataTypes.INTEGER, - field: 'the_user_weight_field', - primaryKey: true - } - }); - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), - dataTypes = [DataTypes.INTEGER, DataTypes.BIGINT, DataTypes.STRING], - Tasks = {}; - - dataTypes.forEach(dataType => { - const tableName = `TaskXYZ_${dataType.key}`; - Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING }); - Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType, constraints: false }); - }); - - await this.sequelize.sync({ force: true }); - dataTypes.forEach(dataType => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); - }); - - describe('allows the user to provide an attribute definition object as foreignKey', () => { - it('works with a column that hasnt been defined before', function() { - const Task = this.sequelize.define('task', {}), - User = this.sequelize.define('user', {}); - - Task.belongsTo(User, { - foreignKey: { - allowNull: false, - name: 'uid' - } - }); - - expect(Task.rawAttributes.uid).to.be.ok; - expect(Task.rawAttributes.uid.allowNull).to.be.false; - expect(Task.rawAttributes.uid.references.model).to.equal(User.getTableName()); - expect(Task.rawAttributes.uid.references.key).to.equal('id'); - }); - - it('works when taking a column directly from the object', function() { - const User = this.sequelize.define('user', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true - } - }), - Profile = this.sequelize.define('project', { - user_id: { - type: Sequelize.INTEGER, - allowNull: false - } - }); - - Profile.belongsTo(User, { foreignKey: Profile.rawAttributes.user_id }); - - expect(Profile.rawAttributes.user_id).to.be.ok; - expect(Profile.rawAttributes.user_id.references.model).to.equal(User.getTableName()); - expect(Profile.rawAttributes.user_id.references.key).to.equal('uid'); - expect(Profile.rawAttributes.user_id.allowNull).to.be.false; - }); - - it('works when merging with an existing definition', function() { - const Task = this.sequelize.define('task', { - projectId: { - defaultValue: 42, - type: Sequelize.INTEGER - } - }), - Project = this.sequelize.define('project', {}); - - Task.belongsTo(Project, { foreignKey: { allowNull: true } }); - - expect(Task.rawAttributes.projectId).to.be.ok; - expect(Task.rawAttributes.projectId.defaultValue).to.equal(42); - expect(Task.rawAttributes.projectId.allowNull).to.be.ok; - }); - }); - - it('should throw an error if foreignKey and as result in a name clash', function() { - const Person = this.sequelize.define('person', {}), - Car = this.sequelize.define('car', {}); - - expect(Car.belongsTo.bind(Car, Person, { foreignKey: 'person' })).to - .throw('Naming collision between attribute \'person\' and association \'person\' on model car. To remedy this, change either foreignKey or as in your association definition'); - }); - - it('should throw an error if an association clashes with the name of an already define attribute', function() { - const Person = this.sequelize.define('person', {}), - Car = this.sequelize.define('car', { - person: Sequelize.INTEGER - }); - - expect(Car.belongsTo.bind(Car, Person, { as: 'person' })).to - .throw('Naming collision between attribute \'person\' and association \'person\' on model car. To remedy this, change either foreignKey or as in your association definition'); - }); - }); - - describe('Eager loading', () => { - beforeEach(function() { - this.Individual = this.sequelize.define('individual', { - name: Sequelize.STRING - }); - this.Hat = this.sequelize.define('hat', { - name: Sequelize.STRING - }); - this.Individual.belongsTo(this.Hat, { - as: 'personwearinghat' - }); - }); - - 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', 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 deleted file mode 100644 index c470a1183a9a..000000000000 --- a/test/integration/associations/has-many.test.js +++ /dev/null @@ -1,1594 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - moment = require('moment'), - sinon = require('sinon'), - Op = Sequelize.Op, - current = Support.sequelize, - _ = require('lodash'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('HasMany'), () => { - describe('Model.associations', () => { - it('should store all assocations when associting to the same table multiple times', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - Group.hasMany(User); - Group.hasMany(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }); - Group.hasMany(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }); - - expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers']); - }); - }); - - describe('count', () => { - 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' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Task = this.sequelize.define('Task', {}); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }), - SubTask = this.sequelize.define('SubTask', { - title: DataTypes.STRING - }); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - - 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' - } - ] - }], - 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', async function() { - const User = this.sequelize.define('User', {}), - Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - categoryId: { - type: DataTypes.INTEGER, - field: 'category_id' - } - }), - Category = this.sequelize.define('Category', {}); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - Task.Category = Task.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' }); - - 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', async function() { - const User = this.sequelize.define('User', {}).schema('work'), - Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }).schema('work'), - SubTask = this.sequelize.define('SubTask', { - title: DataTypes.STRING - }).schema('work'); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - - 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' - } - ] - }], - 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; - } - }); - }); - } - }); - - describe('(1:N)', () => { - describe('hasAssociation', () => { - beforeEach(function() { - this.Article = this.sequelize.define('Article', { - pk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - title: DataTypes.STRING - }); - - this.Label = this.sequelize.define('Label', { - key: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - - this.Article.hasMany(this.Label); - - return this.sequelize.sync({ force: true }); - }); - - it('should only generate one set of foreignKeys', function() { - this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }, { timestamps: false }); - this.Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }, { timestamps: false }); - - this.Label.belongsTo(this.Article); - this.Article.hasMany(this.Label); - - expect(Object.keys(this.Label.rawAttributes)).to.deep.equal(['id', 'text', 'ArticleId']); - expect(Object.keys(this.Label.rawAttributes).length).to.equal(3); - }); - - if (current.dialect.supports.transactions) { - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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; - }); - }); - - describe('hasAssociations', () => { - beforeEach(function() { - this.Article = this.sequelize.define('Article', { - pk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - title: DataTypes.STRING - }); - - this.Label = this.sequelize.define('Label', { - key: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - - this.Article.hasMany(this.Label); - - return this.sequelize.sync({ force: true }); - }); - - if (current.dialect.supports.transactions) { - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', async function() { - const [article, label1, label2] = await Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ]); - - 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', 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - Task.hasMany(User); - - 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', async function() { - const Article = this.sequelize.define('Article', { title: DataTypes.STRING }), - Label = this.sequelize.define('Label', { text: DataTypes.STRING }); - - Article.hasMany(Label); - - 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', 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', async function() { - const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), - Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }); - - Article.hasMany(Label); - - 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - Task.hasMany(User); - - 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', 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); - - 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', async function() { - this.sequelize.options.omitNull = true; - - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - Task = this.sequelize.define('Task', { title: DataTypes.STRING }); - - Task.hasMany(User); - - 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 { - this.sequelize.options.omitNull = false; - } - }); - - describe('createAssociations', () => { - 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); - - 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', async function() { - const spy = sinon.spy(); - - const Article = this.sequelize.define('Article', { - 'title': DataTypes.STRING - - }), - Label = this.sequelize.define('Label', { - 'text': DataTypes.STRING - }); - - Article.hasMany(Label); - - 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', 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', async function() { - const Article = this.sequelize.define('Article', { - 'title': DataTypes.STRING - }), - Label = this.sequelize.define('Label', { - 'text': DataTypes.STRING - }); - - Article.hasMany(Label); - - 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(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); - - 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', async function() { - this.Article = this.sequelize.define('Article', { - 'title': DataTypes.STRING - }); - this.Label = this.sequelize.define('Label', { - 'text': DataTypes.STRING, - 'until': DataTypes.DATE - }); - - this.Article.hasMany(this.Label); - - 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', 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', 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(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, { - foreignKey: 'userId' - }); - - 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', async function() { - await expect(this.user.countTasks({})).to.eventually.equal(2); - }); - - it('should count filtered associations', async function() { - await expect(this.user.countTasks({ - where: { - active: true - } - })).to.eventually.equal(1); - }); - - it('should count scoped associations', async function() { - this.User.hasMany(this.Task, { - foreignKey: 'userId', - as: 'activeTasks', - scope: { - active: true - } - }); - - await expect(this.user.countActiveTasks({})).to.eventually.equal(1); - }); - }); - - describe('thisAssociations', () => { - it('should work with alias', async function() { - const Person = this.sequelize.define('Group', {}); - - Person.hasMany(Person, { as: 'Children' }); - - await this.sequelize.sync(); - }); - }); - }); - - describe('foreign key constraints', () => { - describe('1:m', () => { - 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); - - 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', 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 - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - User.hasMany(Task, { constraints: false }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - User.hasMany(Task, { onDelete: 'cascade' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - User.hasMany(Task, { onUpdate: 'cascade' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - User.hasMany(Task, { onDelete: 'restrict' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), - User = this.sequelize.define('User', { username: DataTypes.STRING }); - - User.hasMany(Task, { onUpdate: 'restrict' }); - - 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); - }); - } - }); - }); - - describe('Association options', () => { - it('should setup underscored field with foreign keys when using underscored', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: true }); - const Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: true }); - - User.hasMany(Account); - - expect(Account.rawAttributes.UserId).to.exist; - expect(Account.rawAttributes.UserId.field).to.equal('user_id'); - }); - - it('should use model name when using camelcase', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }); - const Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: false }); - - User.hasMany(Account); - - expect(Account.rawAttributes.UserId).to.exist; - expect(Account.rawAttributes.UserId.field).to.equal('UserId'); - }); - - 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 = {}; - - 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 }); - - await Tasks[dataType].sync({ force: true }); - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - } - }); - - it('infers the keyType if none provided', async function() { - const User = this.sequelize.define('User', { - id: { type: DataTypes.STRING, primaryKey: true }, - username: DataTypes.STRING - }), - Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - User.hasMany(Task); - - 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', () => { - it('works with a column that hasnt been defined before', function() { - const Task = this.sequelize.define('task', {}), - User = this.sequelize.define('user', {}); - - User.hasMany(Task, { - foreignKey: { - name: 'uid', - allowNull: false - } - }); - - expect(Task.rawAttributes.uid).to.be.ok; - expect(Task.rawAttributes.uid.allowNull).to.be.false; - expect(Task.rawAttributes.uid.references.model).to.equal(User.getTableName()); - expect(Task.rawAttributes.uid.references.key).to.equal('id'); - }); - - it('works when taking a column directly from the object', function() { - const Project = this.sequelize.define('project', { - user_id: { - type: Sequelize.INTEGER, - defaultValue: 42 - } - }), - User = this.sequelize.define('user', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true - } - }); - - User.hasMany(Project, { foreignKey: Project.rawAttributes.user_id }); - - expect(Project.rawAttributes.user_id).to.be.ok; - expect(Project.rawAttributes.user_id.references.model).to.equal(User.getTableName()); - expect(Project.rawAttributes.user_id.references.key).to.equal('uid'); - expect(Project.rawAttributes.user_id.defaultValue).to.equal(42); - }); - - it('works when merging with an existing definition', function() { - const Task = this.sequelize.define('task', { - userId: { - defaultValue: 42, - type: Sequelize.INTEGER - } - }), - User = this.sequelize.define('user', {}); - - User.hasMany(Task, { foreignKey: { allowNull: true } }); - - expect(Task.rawAttributes.userId).to.be.ok; - expect(Task.rawAttributes.userId.defaultValue).to.equal(42); - expect(Task.rawAttributes.userId.allowNull).to.be.ok; - }); - }); - - it('should throw an error if foreignKey and as result in a name clash', function() { - const User = this.sequelize.define('user', { - user: Sequelize.INTEGER - }); - - expect(User.hasMany.bind(User, User, { as: 'user' })).to - .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', async function() { - const User = this.sequelize.define('user', { - userId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - username: Sequelize.STRING - }); - const Task = this.sequelize.define('task', { - taskId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - title: Sequelize.STRING - }); - const Job = this.sequelize.define('job', { - jobId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - title: Sequelize.STRING - }); - - Task.hasMany(Job, { foreignKey: 'taskId' }); - User.hasMany(Task, { foreignKey: 'userId' }); - - await this.sequelize.sync({ force: true }); - - await User.create({ - username: 'John Doe', - tasks: [ - { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] }, - { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] } - ] - }, { include: [{ model: Task, include: [Job] }] }); - - const { count, rows } = await User.findAndCountAll({ - attributes: ['userId'], - include: [ - { model: Task, separate: true, include: [{ model: Job, separate: true }] } - ], - group: [['userId']] - }); - - expect(count.length).to.equal(1); - expect(rows[0].tasks[0].jobs.length).to.equal(2); - }); - }); - - describe('sourceKey', () => { - beforeEach(function() { - const User = this.sequelize.define('UserXYZ', - { username: Sequelize.STRING, email: Sequelize.STRING }, - { indexes: [{ fields: ['email'], unique: true }] } - ); - const Task = this.sequelize.define('TaskXYZ', - { title: Sequelize.STRING, userEmail: { type: Sequelize.STRING, field: 'user_email_xyz' } }); - - User.hasMany(Task, { foreignKey: 'userEmail', sourceKey: 'email', as: 'tasks' }); - - this.User = User; - this.Task = Task; - - return this.sequelize.sync({ force: true }); - }); - - it('should use sourceKey', async function() { - const User = this.User, - Task = this.Task; - - 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', async function() { - const User = this.User, - Task = this.Task; - - 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', async function() { - const User = this.User, - Task = this.Task; - - 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', async function() { - const User = this.User, - values = { - username: 'John', - email: 'john@example.com', - tasks: [{ title: 'Fix new PR' }] - }; - - 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', async function() { - // Dummy getter/setter to test they are symmetric - function toCustomFormat(string) { - return string && `FORMAT-${string}`; - } - function fromCustomFormat(string) { - return string && string.slice(7); - } - - const Parent = this.sequelize.define('Parent', { - id: { - type: DataTypes.STRING, - primaryKey: true, - get() { - return fromCustomFormat(this.getDataValue('id')); - }, - set(value) { - this.setDataValue('id', toCustomFormat(value)); - } - } - }); - - const Child = this.sequelize.define('Child', { - id: { - type: DataTypes.STRING, - primaryKey: true - }, - parent: { - type: DataTypes.STRING, - get() { - return fromCustomFormat(this.getDataValue('parent')); - }, - set(value) { - this.setDataValue('parent', toCustomFormat(value)); - } - } - }); - Child.belongsTo(Parent, { - foreignKey: 'parent', - targetKey: 'id' - }); - Parent.hasMany(Child, { - foreignKey: 'parent', - targetKey: 'id', - as: 'children' - }); - - const values = { - id: 'sJn369d8Em', - children: [{ id: 'dgeQAQaW7A' }] - }; - 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'); - }); - }); - - describe('sourceKey with where clause in include', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', - { username: Sequelize.STRING, email: { type: Sequelize.STRING, field: 'mail' } }, - { indexes: [{ fields: ['mail'], unique: true }] } - ); - this.Task = this.sequelize.define('Task', - { title: Sequelize.STRING, userEmail: Sequelize.STRING, taskStatus: Sequelize.STRING }); - - this.User.hasMany(this.Task, { - foreignKey: 'userEmail', - sourceKey: 'email' - }); - - return this.sequelize.sync({ force: true }); - }); - - 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'); - }); - }); - - describe('Eager loading', () => { - beforeEach(function() { - this.Individual = this.sequelize.define('individual', { - name: Sequelize.STRING - }); - this.Hat = this.sequelize.define('hat', { - name: Sequelize.STRING - }); - this.Individual.hasMany(this.Hat, { - as: { - singular: 'personwearinghat', - plural: 'personwearinghats' - } - }); - }); - - 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', 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 deleted file mode 100644 index 81f7b97f5665..000000000000 --- a/test/integration/associations/has-one.test.js +++ /dev/null @@ -1,796 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'), - current = Support.sequelize, - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('HasOne'), () => { - describe('Model.associations', () => { - it('should store all associations when associating to the same table multiple times', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - Group.hasOne(User); - Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' }); - Group.hasOne(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' }); - - expect( - Object.keys(Group.associations) - ).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers']); - }); - }); - - describe('get', () => { - describe('multiple', () => { - 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' }); - - 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', 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.', 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); - - 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', 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); - - 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', 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', 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' }); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); - - User.hasOne(Task); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); - - User.hasOne(Task); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); - - User.hasOne(Task); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), - Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); - - User.hasOne(Task); - - 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', async function() { - const Home = this.sequelize.define('home', {}), - User = this.sequelize.define('user'); - - User.hasOne(Home); - - 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', async function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }), - Task = this.sequelize.define('Task', { title: Sequelize.STRING }); - - User.hasOne(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', 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(); - }); - } - - }); - - describe('foreign key', () => { - it('should setup underscored field with foreign keys when using underscored', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: true }), - Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: true }); - - Account.hasOne(User); - - expect(User.rawAttributes.AccountId).to.exist; - expect(User.rawAttributes.AccountId.field).to.equal('account_id'); - }); - - it('should use model name when using camelcase', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }), - Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: false }); - - Account.hasOne(User); - - expect(User.rawAttributes.AccountId).to.exist; - expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); - }); - - 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 }); - - Task.hasOne(User, { - foreignKey: { - name: 'taskId', - field: 'task_id' - } - }); - - expect(User.rawAttributes.taskId).to.exist; - expect(User.rawAttributes.taskId.field).to.equal('task_id'); - await Task.sync({ force: true }); - await User.sync({ force: true }); - - const [user0, task0] = await Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - - await task0.setUserXYZ(user0); - const user = await task0.getUserXYZ(); - // the sql query should correctly look at task_id instead of taskId - expect(user).to.not.be.null; - - const task = await Task.findOne({ - where: { title: 'task' }, - include: [User] - }); - - expect(task.UserXYZ).to.exist; - }); - }); - - describe('foreign key constraints', () => { - it('are enabled by default', 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 - - 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', 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 - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - User.hasOne(Task, { constraints: false }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - User.hasOne(Task, { onDelete: 'cascade' }); - - 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")', 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 }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - User.hasOne(Task, { onUpdate: 'cascade' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - User.hasOne(Task, { onDelete: 'restrict' }); - - 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', async function() { - const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), - User = this.sequelize.define('User', { username: Sequelize.STRING }); - - User.hasOne(Task, { onUpdate: 'restrict' }); - - 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); - }); - - } - - }); - - describe('association column', () => { - 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 - } - }); - - const Group = this.sequelize.define('GroupPKBT', { - name: { - type: Sequelize.STRING, - primaryKey: true - } - }); - - Group.hasOne(User); - - 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', async function() { - const User = this.sequelize.define('User', { - user_name: { - unique: true, - type: Sequelize.STRING - } - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING, - username: Sequelize.STRING - }); - - User.hasOne(Task, { foreignKey: 'username', sourceKey: 'user_name' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - unique: true - } - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING, - username: Sequelize.STRING - }); - - User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - unique: true, - field: 'the_user_name_field' - } - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING, - username: Sequelize.STRING - }); - - User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), - dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], - Tasks = {}; - - 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 }); - - 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', () => { - it('works with a column that hasnt been defined before', function() { - const User = this.sequelize.define('user', {}); - let Profile = this.sequelize.define('project', {}); - - User.hasOne(Profile, { - foreignKey: { - allowNull: false, - name: 'uid' - } - }); - - expect(Profile.rawAttributes.uid).to.be.ok; - expect(Profile.rawAttributes.uid.references.model).to.equal(User.getTableName()); - expect(Profile.rawAttributes.uid.references.key).to.equal('id'); - expect(Profile.rawAttributes.uid.allowNull).to.be.false; - - // Let's clear it - Profile = this.sequelize.define('project', {}); - User.hasOne(Profile, { - foreignKey: { - allowNull: false, - name: 'uid' - } - }); - - expect(Profile.rawAttributes.uid).to.be.ok; - expect(Profile.rawAttributes.uid.references.model).to.equal(User.getTableName()); - expect(Profile.rawAttributes.uid.references.key).to.equal('id'); - expect(Profile.rawAttributes.uid.allowNull).to.be.false; - }); - - it('works when taking a column directly from the object', function() { - const User = this.sequelize.define('user', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true - } - }), - Profile = this.sequelize.define('project', { - user_id: { - type: Sequelize.INTEGER, - allowNull: false - } - }); - - User.hasOne(Profile, { foreignKey: Profile.rawAttributes.user_id }); - - expect(Profile.rawAttributes.user_id).to.be.ok; - expect(Profile.rawAttributes.user_id.references.model).to.equal(User.getTableName()); - expect(Profile.rawAttributes.user_id.references.key).to.equal('uid'); - expect(Profile.rawAttributes.user_id.allowNull).to.be.false; - }); - - it('works when merging with an existing definition', function() { - const User = this.sequelize.define('user', { - uid: { - type: Sequelize.INTEGER, - primaryKey: true - } - }), - Project = this.sequelize.define('project', { - userUid: { - type: Sequelize.INTEGER, - defaultValue: 42 - } - }); - - User.hasOne(Project, { foreignKey: { allowNull: false } }); - - expect(Project.rawAttributes.userUid).to.be.ok; - expect(Project.rawAttributes.userUid.allowNull).to.be.false; - expect(Project.rawAttributes.userUid.references.model).to.equal(User.getTableName()); - expect(Project.rawAttributes.userUid.references.key).to.equal('uid'); - expect(Project.rawAttributes.userUid.defaultValue).to.equal(42); - }); - }); - - it('should throw an error if an association clashes with the name of an already define attribute', function() { - const User = this.sequelize.define('user', { - attribute: Sequelize.STRING - }), - Attribute = this.sequelize.define('attribute', {}); - - expect(User.hasOne.bind(User, Attribute)).to - .throw('Naming collision between attribute \'attribute\' and association \'attribute\' on model user. To remedy this, change either foreignKey or as in your association definition'); - }); - }); - - describe('Counter part', () => { - describe('BelongsTo', () => { - it('should only generate one foreign key', function() { - const Orders = this.sequelize.define('Orders', {}, { timestamps: false }), - InternetOrders = this.sequelize.define('InternetOrders', {}, { timestamps: false }); - - InternetOrders.belongsTo(Orders, { - foreignKeyConstraint: true - }); - Orders.hasOne(InternetOrders, { - foreignKeyConstraint: true - }); - - expect(Object.keys(InternetOrders.rawAttributes).length).to.equal(2); - expect(InternetOrders.rawAttributes.OrderId).to.be.ok; - expect(InternetOrders.rawAttributes.OrdersId).not.to.be.ok; - }); - }); - }); - - describe('Eager loading', () => { - beforeEach(function() { - this.Individual = this.sequelize.define('individual', { - name: Sequelize.STRING - }); - this.Hat = this.sequelize.define('hat', { - name: Sequelize.STRING - }); - this.Individual.hasOne(this.Hat, { - as: 'personwearinghat' - }); - }); - - 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', 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 deleted file mode 100644 index 267cbbe6249a..000000000000 --- a/test/integration/associations/multiple-level-filters.test.js +++ /dev/null @@ -1,222 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { - 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 }); - - Project.belongsTo(User); - User.hasMany(Project); - - Task.belongsTo(Project); - Project.hasMany(Task); - - 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', 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 }); - - Project.belongsTo(User); - User.hasMany(Project); - - Task.belongsTo(Project); - Project.hasMany(Task); - - 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', 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 }); - - Project.belongsTo(User); - User.hasMany(Project); - - Task.belongsTo(Project); - Project.hasMany(Task); - - 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', 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' }); - - 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 deleted file mode 100644 index bbf2a178ef00..000000000000 --- a/test/integration/associations/scope.test.js +++ /dev/null @@ -1,583 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - Op = Sequelize.Op; - -describe(Support.getTestDialectTeaser('associations'), () => { - describe('scope', () => { - beforeEach(function() { - this.Post = this.sequelize.define('post', {}); - this.Image = this.sequelize.define('image', {}); - this.Question = this.sequelize.define('question', {}); - this.Comment = this.sequelize.define('comment', { - title: Sequelize.STRING, - type: Sequelize.STRING, - commentable: Sequelize.STRING, - commentable_id: Sequelize.INTEGER, - isMain: { - field: 'is_main', - type: Sequelize.BOOLEAN, - defaultValue: false - } - }); - - this.Comment.prototype.getItem = function() { - return this[`get${this.get('commentable').substr(0, 1).toUpperCase()}${this.get('commentable').substr(1)}`](); - }; - - this.Post.addScope('withComments', { - include: [this.Comment] - }); - this.Post.addScope('withMainComment', { - include: [{ - model: this.Comment, - as: 'mainComment' - }] - }); - this.Post.hasMany(this.Comment, { - foreignKey: 'commentable_id', - scope: { - commentable: 'post' - }, - constraints: false - }); - this.Post.hasMany(this.Comment, { - foreignKey: 'commentable_id', - as: 'coloredComments', - scope: { - commentable: 'post', - type: { [Op.in]: ['blue', 'green'] } - }, - constraints: false - }); - this.Post.hasOne(this.Comment, { - foreignKey: 'commentable_id', - as: 'mainComment', - scope: { - commentable: 'post', - isMain: true - }, - constraints: false - }); - this.Comment.belongsTo(this.Post, { - foreignKey: 'commentable_id', - as: 'post', - constraints: false - }); - - this.Image.hasMany(this.Comment, { - foreignKey: 'commentable_id', - scope: { - commentable: 'image' - }, - constraints: false - }); - this.Comment.belongsTo(this.Image, { - foreignKey: 'commentable_id', - as: 'image', - constraints: false - }); - - this.Question.hasMany(this.Comment, { - foreignKey: 'commentable_id', - scope: { - commentable: 'question' - }, - constraints: false - }); - this.Comment.belongsTo(this.Question, { - foreignKey: 'commentable_id', - as: 'question', - constraints: false - }); - }); - - describe('1:1', () => { - 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', 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', 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)', async function() { - const logs = []; - const logging = function(log) { - //removing 'executing( || 'default'}) :' from logs - logs.push(log.substring(log.indexOf(':') + 1)); - }; - - await this.sequelize.sync({ force: true }); - const post = await this.Post.create(); - - await post.createComment({ - title: 'I am a post comment' - }); - - await this.Post.scope('withComments').findAll({ - logging - }); - - await this.Post.scope('withComments').findAll({ - logging - }); - - expect(logs[0]).to.equal(logs[1]); - }); - it('should created included association with scope values', async function() { - await this.sequelize.sync({ force: true }); - let post = await this.Post.create({ - comments: [{ - title: 'I am a comment created with a post' - }, { - 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'); - } - 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', 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); - }); - }); - - if (Support.getTestDialect() !== 'sqlite') { - describe('N:M', () => { - describe('on the target', () => { - beforeEach(function() { - this.Post = this.sequelize.define('post', {}); - this.Tag = this.sequelize.define('tag', { - type: DataTypes.STRING - }); - this.PostTag = this.sequelize.define('post_tag'); - - this.Tag.belongsToMany(this.Post, { through: this.PostTag }); - this.Post.belongsToMany(this.Tag, { as: 'categories', through: this.PostTag, scope: { type: 'category' } }); - this.Post.belongsToMany(this.Tag, { as: 'tags', through: this.PostTag, scope: { type: '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'); - }); - }); - - describe('on the through model', () => { - beforeEach(function() { - this.Post = this.sequelize.define('post', {}); - this.Image = this.sequelize.define('image', {}); - this.Question = this.sequelize.define('question', {}); - - this.ItemTag = this.sequelize.define('item_tag', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - tag_id: { - type: DataTypes.INTEGER, - unique: 'item_tag_taggable' - }, - taggable: { - type: DataTypes.STRING, - unique: 'item_tag_taggable' - }, - taggable_id: { - type: DataTypes.INTEGER, - unique: 'item_tag_taggable', - references: null - } - }); - this.Tag = this.sequelize.define('tag', { - name: DataTypes.STRING - }); - - this.Post.belongsToMany(this.Tag, { - through: { - model: this.ItemTag, - unique: false, - scope: { - taggable: 'post' - } - }, - foreignKey: 'taggable_id', - constraints: false - }); - this.Tag.belongsToMany(this.Post, { - through: { - model: this.ItemTag, - unique: false - }, - foreignKey: 'tag_id' - }); - - this.Image.belongsToMany(this.Tag, { - through: { - model: this.ItemTag, - unique: false, - scope: { - taggable: 'image' - } - }, - foreignKey: 'taggable_id', - constraints: false - }); - this.Tag.belongsToMany(this.Image, { - through: { - model: this.ItemTag, - unique: false - }, - foreignKey: 'tag_id' - }); - - this.Question.belongsToMany(this.Tag, { - through: { - model: this.ItemTag, - unique: false, - scope: { - taggable: 'question' - } - }, - foreignKey: 'taggable_id', - constraints: false - }); - this.Tag.belongsToMany(this.Question, { - through: { - model: this.ItemTag, - unique: false - }, - foreignKey: 'tag_id' - }); - }); - - 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 }) - ]); - - 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 deleted file mode 100644 index dc6a094039d3..000000000000 --- a/test/integration/associations/self.test.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Self'), () => { - it('supports freezeTableName', async function() { - const Group = this.sequelize.define('Group', {}, { - tableName: 'user_group', - timestamps: false, - underscored: true, - freezeTableName: true - }); - - Group.belongsTo(Group, { as: 'Parent', foreignKey: 'parent_id' }); - await Group.sync({ force: true }); - - await Group.findAll({ - include: [{ - model: Group, - as: 'Parent' - }] - }); - }); - - 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; - - 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', 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 = Object.values(Person.associations).map(v => v.foreignIdentifier); - const rawAttributes = Object.keys(this.sequelize.models.Family.rawAttributes); - - expect(foreignIdentifiers.length).to.equal(2); - expect(rawAttributes.length).to.equal(4); - - expect(foreignIdentifiers).to.have.members(['PersonId', 'ChildId']); - expect(rawAttributes).to.have.members(['createdAt', 'updatedAt', 'PersonId', 'ChildId']); - - 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', async function() { - const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); - const Family = this.sequelize.define('Family', { - preexisting_child: { - type: DataTypes.INTEGER, - primaryKey: true - }, - preexisting_parent: { - type: DataTypes.INTEGER, - primaryKey: true - } - }, { timestamps: false }); - - 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 = Object.values(Person.associations).map(v => v.foreignIdentifier); - const rawAttributes = Object.keys(Family.rawAttributes); - - expect(foreignIdentifiers.length).to.equal(2); - expect(rawAttributes.length).to.equal(2); - - expect(foreignIdentifiers).to.have.members(['preexisting_parent', 'preexisting_child']); - expect(rawAttributes).to.have.members(['preexisting_parent', 'preexisting_child']); - - let count = 0; - 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'); - } - } - }); - - await 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'); - } - } - }); - - 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 deleted file mode 100644 index 746d43996842..000000000000 --- a/test/integration/cls.test.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize, - cls = require('cls-hooked'), - current = Support.sequelize, - delay = require('delay'), - sinon = require('sinon'); - -if (current.dialect.supports.transactions) { - describe(Support.getTestDialectTeaser('CLS (Async hooks)'), () => { - before(() => { - current.constructor.useCLS(cls.createNamespace('sequelize')); - }); - - after(() => { - cls.destroyNamespace('sequelize'); - delete Sequelize._cls; - }); - - beforeEach(async function() { - this.sequelize = await Support.prepareTransactionTest(this.sequelize); - this.ns = cls.getNamespace('sequelize'); - this.User = this.sequelize.define('user', { - name: Sequelize.STRING - }); - await this.sequelize.sync({ force: true }); - }); - - describe('context', () => { - it('does not use continuation storage on manually managed transactions', async function() { - await Sequelize._clsRun(async () => { - const transaction = await this.sequelize.transaction(); - expect(this.ns.get('transaction')).not.to.be.ok; - await transaction.rollback(); - }); - }); - - it('supports several concurrent transactions', async function() { - let t1id, t2id; - await Promise.all([ - this.sequelize.transaction(async () => { - t1id = this.ns.get('transaction').id; - }), - this.sequelize.transaction(async () => { - t2id = this.ns.get('transaction').id; - }) - ]); - expect(t1id).to.be.ok; - expect(t2id).to.be.ok; - expect(t1id).not.to.equal(t2id); - }); - - it('supports nested promise chains', async function() { - await this.sequelize.transaction(async () => { - const tid = this.ns.get('transaction').id; - - await this.User.findAll(); - expect(this.ns.get('transaction').id).to.be.ok; - expect(this.ns.get('transaction').id).to.equal(tid); - }); - }); - - it('does not leak variables to the outer scope', async function() { - // This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted. - // We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent; - - let transactionSetup = false, - transactionEnded = false; - - const clsTask = this.sequelize.transaction(async () => { - transactionSetup = true; - await delay(500); - expect(this.ns.get('transaction')).to.be.ok; - transactionEnded = true; - }); - - await new Promise(resolve => { - // Wait for the transaction to be setup - const interval = setInterval(() => { - if (transactionSetup) { - clearInterval(interval); - resolve(); - } - }, 200); - }); - expect(transactionEnded).not.to.be.ok; - - expect(this.ns.get('transaction')).not.to.be.ok; - - // Just to make sure it didn't change between our last check and the assertion - expect(transactionEnded).not.to.be.ok; - await clsTask; // ensure we don't leak the promise - }); - - it('does not leak variables to the following promise chain', async function() { - await this.sequelize.transaction(() => {}); - expect(this.ns.get('transaction')).not.to.be.ok; - }); - - it('does not leak outside findOrCreate', async function() { - await this.User.findOrCreate({ - where: { - name: 'Kafka' - }, - logging(sql) { - if (/default/.test(sql)) { - throw new Error('The transaction was not properly assigned'); - } - } - }); - - await this.User.findAll(); - }); - }); - - describe('sequelize.query integration', () => { - it('automagically uses the transaction in all calls', async function() { - await this.sequelize.transaction(async () => { - await this.User.create({ name: 'bob' }); - return Promise.all([ - expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0), - expect(this.User.findAll({})).to.eventually.have.length(1) - ]); - }); - }); - - it('automagically uses the transaction in all calls with async/await', async function() { - await this.sequelize.transaction(async () => { - await this.User.create({ name: 'bob' }); - expect(await this.User.findAll({ transaction: null })).to.have.length(0); - expect(await this.User.findAll({})).to.have.length(1); - }); - }); - }); - - it('CLS namespace is stored in Sequelize._cls', function() { - expect(Sequelize._cls).to.equal(this.ns); - }); - - it('promises returned by sequelize.query are correctly patched', async function() { - await this.sequelize.transaction(async t => { - await this.sequelize.query('select 1', { type: Sequelize.QueryTypes.SELECT }); - return expect(this.ns.get('transaction')).to.equal(t); - } - ); - }); - - it('custom logging with benchmarking has correct CLS context', async function() { - const logger = sinon.spy(() => { - return this.ns.get('value'); - }); - const sequelize = Support.createSequelizeInstance({ - logging: logger, - benchmark: true - }); - - const result = this.ns.runPromise(async () => { - this.ns.set('value', 1); - await delay(500); - return sequelize.query('select 1;'); - }); - - await this.ns.runPromise(() => { - this.ns.set('value', 2); - return sequelize.query('select 2;'); - }); - - await result; - - expect(logger.calledTwice).to.be.true; - expect(logger.firstCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 2/); - expect(logger.firstCall.returnValue).to.be.equal(2); - expect(logger.secondCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 1/); - expect(logger.secondCall.returnValue).to.be.equal(1); - - }); - }); -} diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js deleted file mode 100644 index 96f8bafd66da..000000000000 --- a/test/integration/configuration.test.js +++ /dev/null @@ -1,142 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - config = require('../config/config'), - Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = Support.Sequelize, - fs = require('fs'), - path = require('path'), - { promisify } = require('util'); - -let sqlite3; -if (dialect === 'sqlite') { - sqlite3 = require('sqlite3'); // eslint-disable-line -} - -describe(Support.getTestDialectTeaser('Configuration'), () => { - describe('Connections problems should fail with a nice message', () => { - 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. - willBeRejectedWithArgs = [Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file']; - } - - 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', async () => { - if (dialect === 'mssql') { - // TODO: GitHub Actions seems to be having trouble with this test. Works perfectly fine on a local setup. - expect(true).to.be.true; - return; - } - - 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. - 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'); - } - }); - - it('when we don\'t have a valid dialect.', () => { - expect(() => { - new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { host: '0.0.0.1', port: config[dialect].port, dialect: 'some-fancy-dialect' }); - }).to.throw(Error, 'The dialect some-fancy-dialect is not supported. Supported dialects: mssql, mariadb, mysql, postgres, and sqlite.'); - }); - }); - - describe('Instantiation with arguments', () => { - if (dialect === 'sqlite') { - 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 = () => { - return promisify(fs.access)(p, fs.R_OK | fs.W_OK); - }; - - try { - try { - await promisify(fs.unlink)(p); - } catch (err) { - expect(err.code).to.equal('ENOENT'); - } - - const sequelizeReadOnly0 = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite0 = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - expect(sequelizeReadOnly0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); - expect(sequelizeReadWrite0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); - - await Promise.all([ - sequelizeReadOnly0.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), - sequelizeReadWrite0.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') - ]); - - // By default, sqlite creates a connection that's READWRITE | CREATE - const sequelize = new Sequelize('sqlite://foo', { - storage: p - }); - await testAccess(await sequelize.query(createTableFoo)); - const sequelizeReadOnly = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - await Promise.all([ - sequelizeReadOnly.query(createTableBar) - .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), - sequelizeReadWrite.query(createTableBar) - ]); - } finally { - await promisify(fs.unlink)(p); - } - }); - } - }); - -}); diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js deleted file mode 100644 index ddd8e3a5b52d..000000000000 --- a/test/integration/data-types.test.js +++ /dev/null @@ -1,756 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../index'), - expect = chai.expect, - Support = require('./support'), - sinon = require('sinon'), - _ = require('lodash'), - moment = require('moment'), - current = Support.sequelize, - Op = Sequelize.Op, - uuid = require('uuid'), - DataTypes = require('../../lib/data-types'), - dialect = Support.getTestDialect(), - semver = require('semver'); - -describe(Support.getTestDialectTeaser('DataTypes'), () => { - afterEach(function() { - // Restore some sanity by resetting all parsers - this.sequelize.connectionManager._clearTypeParser(); - this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers - }); - - 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'); - }); - - const stringify = Sequelize.DATE.prototype.stringify = sinon.spy(function(value, options) { - if (!moment.isMoment(value)) { - value = this._applyTimezone(value, options); - } - return value.format('YYYY-MM-DD HH:mm:ss'); - }); - - current.refreshTypes(); - - const User = current.define('user', { - dateField: Sequelize.DATE - }, { - timestamps: false - }); - - await current.sync({ force: true }); - - 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 = async function(Type, value, options) { - const parse = Type.constructor.parse = sinon.spy(value => { - return value; - }); - - const stringify = Type.constructor.prototype.stringify = sinon.spy(function() { - return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments); - }); - let bindParam; - if (options && options.useBindParam) { - bindParam = Type.constructor.prototype.bindParam = sinon.spy(function() { - return Sequelize.ABSTRACT.prototype.bindParam.apply(this, arguments); - }); - } - - const User = current.define('user', { - field: Type - }, { - timestamps: false - }); - - await current.sync({ force: true }); - - current.refreshTypes(); - - 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) { - Type.constructor.parse = _.noop(); - - expect(() => { - current.refreshTypes(); - }).to.throw(`Parse function not supported for type ${Type.key} in dialect ${dialect}`); - - delete Type.constructor.parse; - }; - - if (current.dialect.supports.JSON) { - it('calls parse and stringify for JSON', async () => { - const Type = new Sequelize.JSON(); - - await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); - }); - } - - if (current.dialect.supports.JSONB) { - it('calls parse and stringify for JSONB', async () => { - const Type = new Sequelize.JSONB(); - - await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); - }); - } - - if (current.dialect.supports.HSTORE) { - it('calls parse and bindParam for HSTORE', async () => { - const Type = new Sequelize.HSTORE(); - - await testSuccess(Type, { test: 42, nested: false }, { useBindParam: true }); - }); - } - - if (current.dialect.supports.RANGE) { - it('calls parse and bindParam for RANGE', async () => { - const Type = new Sequelize.RANGE(new Sequelize.INTEGER()); - - await testSuccess(Type, [1, 2], { useBindParam: true }); - }); - } - - it('calls parse and stringify for DATE', async () => { - const Type = new Sequelize.DATE(); - - await testSuccess(Type, new Date()); - }); - - it('calls parse and stringify for DATEONLY', async () => { - const Type = new Sequelize.DATEONLY(); - - await testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); - }); - - it('calls parse and stringify for TIME', async () => { - const Type = new Sequelize.TIME(); - - await testSuccess(Type, moment(new Date()).format('HH:mm:ss')); - }); - - it('calls parse and stringify for BLOB', async () => { - const Type = new Sequelize.BLOB(); - - await testSuccess(Type, 'foobar', { useBindParam: true }); - }); - - it('calls parse and stringify for CHAR', async () => { - const Type = new Sequelize.CHAR(); - - await testSuccess(Type, 'foobar'); - }); - - 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') { - await testSuccess(Type, 'foobar', { useBindParam: true }); - } else { - await testSuccess(Type, 'foobar'); - } - }); - - 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 { - await testSuccess(Type, 'foobar'); - } - }); - - it('calls parse and stringify for BOOLEAN', async () => { - const Type = new Sequelize.BOOLEAN(); - - await testSuccess(Type, true); - }); - - it('calls parse and stringify for INTEGER', async () => { - const Type = new Sequelize.INTEGER(); - - await testSuccess(Type, 1); - }); - - it('calls parse and stringify for DECIMAL', async () => { - const Type = new Sequelize.DECIMAL(); - - await testSuccess(Type, 1.5); - }); - - it('calls parse and stringify for BIGINT', async () => { - const Type = new Sequelize.BIGINT(); - - if (dialect === 'mssql') { - // Same type as integer - testFailure(Type); - } else { - await testSuccess(Type, 1); - } - }); - - it('should handle JS BigInt type', async function() { - const User = this.sequelize.define('user', { - age: Sequelize.BIGINT - }); - - const age = BigInt(Number.MAX_SAFE_INTEGER) * 2n; - - 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', async function() { - const User = this.sequelize.define('user', { - id: { type: Sequelize.TINYINT, primaryKey: true }, - isRegistered: Sequelize.TINYINT - }); - - 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', async () => { - const Type = new Sequelize.DOUBLE(); - - await testSuccess(Type, 1.5, { useBindParam: true }); - }); - - 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 { - await testSuccess(Type, 1.5, { useBindParam: true }); - } - }); - - it('calls parse and bindParam for REAL', async () => { - const Type = new Sequelize.REAL(); - - await testSuccess(Type, 1.5, { useBindParam: true }); - }); - - 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)) { - await testSuccess(Type, uuid.v4()); - } else { - // No native uuid type - testFailure(Type); - } - }); - - it('calls parse and stringify for CIDR', async () => { - const Type = new Sequelize.CIDR(); - - if (['postgres'].includes(dialect)) { - await testSuccess(Type, '10.1.2.3/32'); - } else { - testFailure(Type); - } - }); - - it('calls parse and stringify for INET', async () => { - const Type = new Sequelize.INET(); - - if (['postgres'].includes(dialect)) { - await testSuccess(Type, '127.0.0.1'); - } else { - testFailure(Type); - } - }); - - it('calls parse and stringify for CITEXT', async () => { - const Type = new Sequelize.CITEXT(); - - if (dialect === 'sqlite') { - // The "field type" sqlite returns is TEXT so is covered under TEXT test above - return; - } - - if (dialect === 'postgres') { - await testSuccess(Type, 'foobar'); - } else { - testFailure(Type); - } - }); - - it('calls parse and stringify for MACADDR', async () => { - const Type = new Sequelize.MACADDR(); - - if (['postgres'].includes(dialect)) { - await testSuccess(Type, '01:23:45:67:89:ab'); - } else { - testFailure(Type); - } - - }); - - 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)) { - await testSuccess(Type, 'hat'); - } else { - testFailure(Type); - } - }); - - if (current.dialect.supports.GEOMETRY) { - it('calls parse and bindParam for GEOMETRY', async () => { - const Type = new Sequelize.GEOMETRY(); - - await testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true }); - }); - - it('should parse an empty GEOMETRY field', async () => { - const Type = new Sequelize.GEOMETRY(); - - // MySQL 5.7 or above doesn't support POINT EMPTY - if (dialect === 'mysql' && semver.gte(current.options.databaseVersion, '5.7.0')) { - return; - } - - const runTests = await new Promise((resolve, reject) => { - if (/^postgres/.test(dialect)) { - current.query('SELECT PostGIS_Lib_Version();') - .then(result => { - if (result[0][0] && semver.lte(result[0][0].postgis_lib_version, '2.1.7')) { - resolve(true); - } else { - resolve(); - } - }).catch(reject); - } else { - resolve(true); - } - }); - - 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', async () => { - const Type = new Sequelize.GEOMETRY(); - - current.refreshTypes(); - - const User = current.define('user', { field: Type }, { timestamps: false }); - const point = 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)', async function() { - const Model = this.sequelize.define('model', { - float: Sequelize.FLOAT, - double: Sequelize.DOUBLE, - real: Sequelize.REAL - }); - - 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', async function() { - const Model = this.sequelize.define('model', { - decimal: Sequelize.DECIMAL, - decimalPre: Sequelize.DECIMAL(10, 4), - decimalWithParser: Sequelize.DECIMAL(32, 15), - decimalWithIntParser: Sequelize.DECIMAL(10, 4), - decimalWithFloatParser: Sequelize.DECIMAL(10, 8) - }); - - const sampleData = { - id: 1, - decimal: 12345678.12345678, - decimalPre: 123456.1234, - decimalWithParser: '12345678123456781.123456781234567', - decimalWithIntParser: 1.234, - decimalWithFloatParser: 0.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'); - }); - } - - if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { - it('should parse BIGINT as string', async function() { - const Model = this.sequelize.define('model', { - jewelPurity: Sequelize.BIGINT - }); - - const sampleData = { - id: 1, - jewelPurity: '9223372036854775807' - }; - - 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', async function() { - const Model = this.sequelize.define('M', { - interval: { - type: Sequelize.RANGE(Sequelize.INTEGER), - allowNull: false, - unique: true - } - }); - - 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', async function() { - const Model = this.sequelize.define('M', { - interval: { - type: Sequelize.RANGE(Sequelize.DATE), - allowNull: false, - unique: true - } - }); - const testDate1 = new Date(); - const testDate2 = new Date(testDate1.getTime() + 10000); - const testDateRange = [testDate1, testDate2]; - - 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', async function() { - const Model = this.sequelize.define('M', { - interval: { - type: Sequelize.RANGE(Sequelize.DATE), - allowNull: false, - unique: true - } - }); - const testDate1 = new Date(); - const testDate2 = new Date(testDate1.getTime() + 10000); - const testDateRange = [{ value: testDate1, inclusive: false }, { value: testDate2, inclusive: 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', async function() { - const Model = this.sequelize.define('M', { - interval: { - type: Sequelize.RANGE(Sequelize.DATE), - allowNull: false, - unique: true - } - }); - const testDate1 = new Date(); - const testDate2 = new Date(testDate1.getTime() + 10000); - const testDateRange = [testDate1, { value: testDate2, inclusive: 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', async function() { - const Model = this.sequelize.define('M', { - interval: { - type: Sequelize.RANGE(Sequelize.DATE), - allowNull: false, - unique: true - } - }); - const testDate1 = new Date(); - const testDate2 = new Date(testDate1.getTime() + 10000); - const testDateRange = [testDate1, testDate2]; - const dateRangePredicate = [{ value: testDate1, inclusive: true }, { value: testDate1, inclusive: true }]; - - 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', async function() { - const Model = this.sequelize.define('user', { - name: Sequelize.STRING, - type: Sequelize.ENUM(['action', 'mecha', 'canon', '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', async function() { - const Model = this.sequelize.define('user', { - stamp: Sequelize.DATEONLY - }); - const testDate = moment().format('YYYY-MM-DD'); - const newDate = new Date(); - - 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); - - const record3 = await Model.findByPk(record4.id); - expect(typeof record3.stamp).to.be.eql('string'); - expect(record3.stamp).to.be.eql(testDate); - - 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', async function() { - const Model = this.sequelize.define('user', { - stamp: Sequelize.DATEONLY - }); - const testDate = moment().format('YYYY-MM-DD'); - - 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); - - const record1 = await Model.findByPk(record2.id); - expect(typeof record1.stamp).to.be.eql('string'); - expect(record1.stamp).to.be.eql(testDate); - - 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', async function() { - const ByteModel = this.sequelize.define('Model', { - byteToBool: this.sequelize.Sequelize.BLOB - }, { - timestamps: false - }); - - const BoolModel = this.sequelize.define('Model', { - byteToBool: this.sequelize.Sequelize.BOOLEAN - }, { - timestamps: false - }); - - await ByteModel.sync({ - force: 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 deleted file mode 100644 index 05214e9d8608..000000000000 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ /dev/null @@ -1,182 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - deprecations = require('../../../../lib/utils/deprecations'), - Support = require('../../support'), - sinon = require('sinon'), - Config = require('../../../config/config'), - ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'), - Pool = require('sequelize-pool').Pool; - -const baseConf = Config[Support.getTestDialect()]; -const poolEntry = { - host: baseConf.host, - port: baseConf.port, - pool: {} -}; - -describe(Support.getTestDialectTeaser('Connection Manager'), () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should initialize a single pool without replication', () => { - const options = { - replication: null - }; - const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); - - connectionManager.initPools(); - expect(connectionManager.pool).to.be.instanceOf(Pool); - expect(connectionManager.pool.read).to.be.undefined; - expect(connectionManager.pool.write).to.be.undefined; - }); - - it('should initialize a multiple pools with replication', () => { - const options = { - replication: { - write: { ...poolEntry }, - read: [{ ...poolEntry }, { ...poolEntry }] - } - }; - const sequelize = Support.createSequelizeInstance(options); - 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', async () => { - if (Support.getTestDialect() === 'sqlite') { - return; - } - - const slave1 = { ...poolEntry }; - const slave2 = { ...poolEntry }; - slave1.host = 'slave1'; - slave2.host = 'slave2'; - - const options = { - replication: { - write: { ...poolEntry }, - read: [slave1, slave2] - } - }; - const sequelize = Support.createSequelizeInstance(options); - 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(sequelize.dialect.defaultVersion); - connectionManager.initPools(); - - const queryOptions = { - priority: 0, - type: 'SELECT', - useMaster: false - }; - - const _getConnection = connectionManager.getConnection.bind(connectionManager, queryOptions); - - await _getConnection(); - await _getConnection(); - await _getConnection(); - chai.expect(connectStub.callCount).to.equal(4); - - // First call is the get connection for DB versions - ignore - const calls = connectStub.getCalls(); - chai.expect(calls[1].args[0].host).to.eql('slave1'); - chai.expect(calls[2].args[0].host).to.eql('slave2'); - chai.expect(calls[3].args[0].host).to.eql('slave1'); - }); - - it('should trigger deprecation for non supported engine version', 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: main, - read: [{ ...poolEntry }] - } - }; - const sequelize = Support.createSequelizeInstance(options); - 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(sequelize.dialect.defaultVersion); - connectionManager.initPools(); - - const queryOptions = { - priority: 0, - type: 'SELECT', - useMaster: true - }; - - 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', async () => { - const options = { - replication: null - }; - const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); - - connectionManager.initPools(); - - const poolDrainSpy = sandbox.spy(connectionManager.pool, 'drain'); - const poolClearSpy = sandbox.spy(connectionManager.pool, 'destroyAllNow'); - - 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 deleted file mode 100644 index 0256e4387f83..000000000000 --- a/test/integration/dialects/mariadb/associations.test.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -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', 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' }); - 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(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' }); - await Table1.sync({ force: true }); - await Table2.sync({ force: true }); - }); - - it('should not use only a specified name', function() { - expect(this.sequelize.modelManager.getModel( - 'ms_table1sms_table2s')).not.to.exist; - expect( - this.sequelize.modelManager.getModel('table1_to_table2')).to.exist; - }); - }); - }); - - describe('HasMany', () => { - 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 }); - this.users = null; - this.tasks = null; - - this.User.belongsToMany(this.Task, { as: 'Tasks', through: 'UserTasks' }); - this.Task.belongsToMany(this.User, { as: 'Users', through: 'UserTasks' }); - - const users = []; - const tasks = []; - - for (let i = 0; i < 5; ++i) { - users[i] = { name: `User${Math.random()}` }; - tasks[i] = { name: `Task${Math.random()}` }; - } - - 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 deleted file mode 100644 index 391f4573cb16..000000000000 --- a/test/integration/dialects/mariadb/connector-manager.test.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - env = process.env, - Sequelize = Support.Sequelize; - -if (dialect !== 'mariadb') { - return; -} - -describe('[MARIADB Specific] Connection Manager', () => { - - it('has existing init SQL', async () => { - const sequelize = Support.createSequelizeInstance( - { dialectOptions: { initSql: 'SET @myUserVariable=\'myValue\'' } }); - const res = await sequelize.query('SELECT @myUserVariable'); - expect(res[0]).to.deep.equal([{ '@myUserVariable': 'myValue' }]); - sequelize.close(); - }); - - it('has existing init SQL array', async () => { - const sequelize = Support.createSequelizeInstance( - { - dialectOptions: { - initSql: ['SET @myUserVariable1=\'myValue\'', - 'SET @myUserVariable2=\'myValue\''] - } - }); - const res = await sequelize.query('SELECT @myUserVariable1, @myUserVariable2'); - expect(res[0]).to.deep.equal( - [{ '@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue' }]); - sequelize.close(); - }); - - - - describe('Errors', () => { - const testHost = env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1'; - - it('Connection timeout', async () => { - const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535, dialectOptions: { connectTimeout: 500 } }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError); - }); - - it('ECONNREFUSED', async () => { - const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535 }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); - }); - - it('ENOTFOUND', async () => { - const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); - }); - - it('EHOSTUNREACH', async () => { - const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); - }); - - it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { - const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - 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 deleted file mode 100644 index e2fd3f589630..000000000000 --- a/test/integration/dialects/mariadb/dao-factory.test.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - 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${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' - }); - }); - - it('handles extended attributes (default)', function() { - 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' - }); - }); - - it('handles extended attributes (null)', function() { - 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' - }); - }); - - it('handles extended attributes (primaryKey)', function() { - 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' }); - }); - - it('adds timestamps', function() { - 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' - }); - }); - - it('adds deletedAt if paranoid', function() { - 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${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${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${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${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' }); - }); - }); -}); - diff --git a/test/integration/dialects/mariadb/dao.test.js b/test/integration/dialects/mariadb/dao.test.js deleted file mode 100644 index 1bfc543aab2f..000000000000 --- a/test/integration/dialects/mariadb/dao.test.js +++ /dev/null @@ -1,123 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect !== 'mariadb') return; -describe('[MariaDB Specific] DAO', () => { - beforeEach(async function() { - this.sequelize.options.quoteIdentifiers = true; - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - email: DataTypes.STRING, - location: DataTypes.GEOMETRY() - }); - await this.User.sync({ force: true }); - }); - - afterEach(function() { - this.sequelize.options.quoteIdentifiers = true; - }); - - describe('integers', () => { - describe('integer', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - aNumber: DataTypes.INTEGER - }); - - await this.User.sync({ force: true }); - }); - - it('positive', async function() { - const User = this.User; - - 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', async function() { - const User = this.User; - - 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(async function() { - this.User = this.sequelize.define('User', { - aNumber: DataTypes.BIGINT - }); - - await this.User.sync({ force: true }); - }); - - it('positive', async function() { - const User = this.User; - - 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', async function() { - const User = this.User; - - 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', async function() { - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - 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', async function() { - const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; - const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - - 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', async function() { - const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - 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 deleted file mode 100644 index 33b2b62042da..000000000000 --- a/test/integration/dialects/mariadb/errors.test.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect !== 'mariadb') return; -describe('[MariaDB Specific] Errors', () => { - - const validateError = async (promise, errClass, errValues) => { - const wanted = { ...errValues }; - - 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', () => { - beforeEach(function() { - this.Task = this.sequelize.define('task', { title: DataTypes.STRING }); - this.User = this.sequelize.define('user', { username: DataTypes.STRING }); - this.UserTasks = this.sequelize.define('tasksusers', - { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); - - this.User.belongsToMany(this.Task, - { onDelete: 'RESTRICT', through: 'tasksusers' }); - this.Task.belongsToMany(this.User, - { onDelete: 'RESTRICT', through: 'tasksusers' }); - - this.Task.belongsTo(this.User, - { foreignKey: 'primaryUserId', as: 'primaryUsers' }); - }); - - it('in context of DELETE restriction', async function() { - const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - 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', async function() { - const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - - 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 deleted file mode 100644 index 7386047cb281..000000000000 --- a/test/integration/dialects/mariadb/query-interface.test.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(); - -if (dialect.match(/^mariadb/)) { - describe('QueryInterface', () => { - - describe('databases', () => { - 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 deleted file mode 100644 index 7410fef0cfc9..000000000000 --- a/test/integration/dialects/mssql/connection-manager.test.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const Sequelize = Support.Sequelize; -const dialect = Support.getTestDialect(); - -if (dialect.match(/^mssql/)) { - describe('[MSSQL Specific] Connection Manager', () => { - describe('Errors', () => { - it('ECONNREFUSED', async () => { - const sequelize = Support.createSequelizeInstance({ host: '127.0.0.1', port: 34237 }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); - }); - - it('ENOTFOUND', async () => { - const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); - }); - - it('EHOSTUNREACH', async () => { - const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); - }); - - it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { - const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - 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 deleted file mode 100644 index d757dedfe201..000000000000 --- a/test/integration/dialects/mssql/query-queue.test.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - 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(async function() { - const User = this.User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - - await this.sequelize.sync({ force: true }); - - await User.create({ username: 'John' }); - }); - - it('should queue concurrent requests to a connection', async function() { - const User = this.User; - - await expect(this.sequelize.transaction(async t => { - return Promise.all([ - User.findOne({ - transaction: t - }), - User.findOne({ - transaction: t - }) - ]); - })).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 deleted file mode 100644 index 9ff1b4a07691..000000000000 --- a/test/integration/dialects/mssql/regressions.test.js +++ /dev/null @@ -1,251 +0,0 @@ -'use strict'; - -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(Support.getTestDialectTeaser('Regressions'), () => { - it('does not duplicate columns in ORDER BY statement, #9008', async function() { - const LoginLog = this.sequelize.define('LoginLog', { - ID: { - field: 'id', - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - UserID: { - field: 'userid', - type: Sequelize.UUID, - allowNull: false - } - }); - - const User = this.sequelize.define('User', { - UserID: { - field: 'userid', - type: Sequelize.UUID, - defaultValue: Sequelize.UUIDV4, - primaryKey: true - }, - UserName: { - field: 'username', - type: Sequelize.STRING(50), - allowNull: false - } - }); - - LoginLog.belongsTo(User, { - foreignKey: 'UserID' - }); - User.hasMany(LoginLog, { - foreignKey: 'UserID' - }); - - 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 - }); - - expect(logs).to.have.length(2); - expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); - expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); - - // #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('allow referencing FK to different tables in a schema with onDelete, #10125', async function() { - const Child = this.sequelize.define( - 'Child', - {}, - { - timestamps: false, - freezeTableName: true, - schema: 'a' - } - ); - const Toys = this.sequelize.define( - 'Toys', - {}, - { - timestamps: false, - freezeTableName: true, - schema: 'a' - } - ); - const Parent = this.sequelize.define( - 'Parent', - {}, - { - timestamps: false, - freezeTableName: true, - schema: 'a' - } - ); - - Child.hasOne(Toys, { - onDelete: 'CASCADE' - }); - - Parent.hasOne(Toys, { - onDelete: 'CASCADE' - }); - - const spy = sinon.spy(); - - await this.sequelize.queryInterface.createSchema('a'); - await this.sequelize.sync({ - force: true, - logging: spy - }); - - expect(spy).to.have.been.called; - const log = spy.args.find(arg => arg[0].includes('IF OBJECT_ID(\'[a].[Toys]\', \'U\') IS NULL CREATE TABLE'))[0]; - - expect(log.match(/ON DELETE CASCADE/g).length).to.equal(2); - }); - - it('sets the varchar(max) length correctly on describeTable', async function() { - const Users = this.sequelize.define('_Users', { - username: Sequelize.STRING('MAX') - }, { 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)'); - }); - - 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 deleted file mode 100644 index 0c16f6219fa5..000000000000 --- a/test/integration/dialects/mysql/associations.test.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -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', 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' }); - 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(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' }); - await Table1.sync({ force: true }); - await Table2.sync({ force: true }); - }); - - it('should not use only a specified name', function() { - expect(this.sequelize.modelManager.getModel('ms_table1sms_table2s')).not.to.exist; - expect(this.sequelize.modelManager.getModel('table1_to_table2')).to.exist; - }); - }); - }); - - describe('HasMany', () => { - 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 }); - this.users = null; - this.tasks = null; - - this.User.belongsToMany(this.Task, { as: 'Tasks', through: 'UserTasks' }); - this.Task.belongsToMany(this.User, { as: 'Users', through: 'UserTasks' }); - - const users = [], - tasks = []; - - for (let i = 0; i < 5; ++i) { - users[i] = { name: `User${Math.random()}` }; - tasks[i] = { name: `Task${Math.random()}` }; - } - - await this.sequelize.sync({ force: true }); - await this.User.bulkCreate(users); - await this.Task.bulkCreate(tasks); - }); - - describe('addDAO / getModel', () => { - beforeEach(async function() { - this.user = null; - this.task = null; - - 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', 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(async function() { - this.user = null; - this.tasks = null; - - 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', 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 deleted file mode 100644 index ec0d1549ca24..000000000000 --- a/test/integration/dialects/mysql/connector-manager.test.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const dialect = Support.getTestDialect(); -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', async () => { - const sequelize = Support.createSequelizeInstance({ dialectOptions: { flags: '' } }); - const User = sequelize.define('User', { username: DataTypes.STRING }); - - 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', async () => { - const sequelize = Support.createSequelizeInstance({ keepDefaultTimezone: true, pool: { min: 1, max: 1, handleDisconnects: true, idle: 5000 } }); - const cm = sequelize.connectionManager; - - 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 deleted file mode 100644 index e6215c00da7b..000000000000 --- a/test/integration/dialects/mysql/dao-factory.test.js +++ /dev/null @@ -1,80 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - 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${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' }); - }); - - it('handles extended attributes (default)', function() { - 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' }); - }); - - it('handles extended attributes (null)', function() { - 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' }); - }); - - it('handles extended attributes (primaryKey)', function() { - 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' }); - }); - - it('adds timestamps', function() { - 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' }); - }); - - it('adds deletedAt if paranoid', function() { - 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${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${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${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${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' }); - }); - }); - }); -} diff --git a/test/integration/dialects/mysql/errors.test.js b/test/integration/dialects/mysql/errors.test.js deleted file mode 100644 index 13b7ff376488..000000000000 --- a/test/integration/dialects/mysql/errors.test.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const dialect = Support.getTestDialect(); -const Sequelize = require('../../../../index'); -const DataTypes = require('../../../../lib/data-types'); - -if (dialect === 'mysql') { - describe('[MYSQL Specific] Errors', () => { - - const validateError = async (promise, errClass, errValues) => { - const wanted = { ...errValues }; - - 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', () => { - beforeEach(function() { - this.Task = this.sequelize.define('task', { title: DataTypes.STRING }); - this.User = this.sequelize.define('user', { username: DataTypes.STRING }); - this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); - - this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); - - this.Task.belongsTo(this.User, { foreignKey: 'primaryUserId', as: 'primaryUsers' }); - }); - - 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', 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 deleted file mode 100644 index 9b1a63a97da8..000000000000 --- a/test/integration/dialects/mysql/warning.test.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../../support'); -const Sequelize = Support.Sequelize; -const dialect = Support.getTestDialect(); -const sinon = require('sinon'); - -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', async () => { - const logger = sinon.spy(console, 'log'); - const sequelize = Support.createSequelizeInstance({ - logging: logger, - benchmark: false, - showWarnings: true - }); - - const Model = sequelize.define('model', { - name: Sequelize.DataTypes.STRING(1, true) - }); - - 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 deleted file mode 100644 index d2fdd1ea5651..000000000000 --- a/test/integration/dialects/postgres/associations.test.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] associations', () => { - describe('many-to-many', () => { - describe('where tables have the same prefix', () => { - it('should create a table wp_table1wp_table2s', 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' }); - - expect(this.sequelize.modelManager.getModel('wp_table1swp_table2s')).to.exist; - }); - }); - - describe('when join table name is specified', () => { - beforeEach(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' }); - }); - - it('should not use a combined name', function() { - expect(this.sequelize.modelManager.getModel('ms_table1sms_table2s')).not.to.exist; - }); - - it('should use the specified name', function() { - expect(this.sequelize.modelManager.getModel('table1_to_table2')).to.exist; - }); - }); - }); - - describe('HasMany', () => { - describe('addDAO / getModel', () => { - 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${Support.rand()}`, { name: DataTypes.STRING }); - this.Task = this.sequelize.define(`Task${Support.rand()}`, { name: DataTypes.STRING }); - this.users = null; - this.tasks = null; - - this.User.belongsToMany(this.Task, { as: 'Tasks', through: 'usertasks' }); - this.Task.belongsToMany(this.User, { as: 'Users', through: 'usertasks' }); - - const users = [], - tasks = []; - - for (let i = 0; i < 5; ++i) { - users[i] = { name: `User${Math.random()}` }; - tasks[i] = { name: `Task${Math.random()}` }; - } - - 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', 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', 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${Support.rand()}`, { name: DataTypes.STRING }); - this.Task = this.sequelize.define(`Task${Support.rand()}`, { name: DataTypes.STRING }); - this.users = null; - this.tasks = null; - - this.User.belongsToMany(this.Task, { as: 'Tasks', through: 'usertasks' }); - this.Task.belongsToMany(this.User, { as: 'Users', through: 'usertasks' }); - - for (let i = 0; i < 5; ++i) { - users[i] = { id: i + 1, name: `User${Math.random()}` }; - tasks[i] = { id: i + 1, name: `Task${Math.random()}` }; - } - - 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 deleted file mode 100644 index 0dbd9641980a..000000000000 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES] Sequelize', () => { - 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 }); - 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', async function() { - await checkTimezoneParsing(this.sequelize.options); - }); - - 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', async () => { - const sequelize = Support.createSequelizeInstance({ clientMinMessages: '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', async () => { - const sequelize = Support.createSequelizeInstance({ clientMinMessages: false }); - const result = await sequelize.query('SHOW client_min_messages'); - // `notice` is Postgres's default - expect(result[0].client_min_messages).to.equal('notice'); - }); - }); - - describe('Dynamic OIDs', () => { - const dynamicTypesToCheck = [ - DataTypes.GEOMETRY, - DataTypes.HSTORE, - DataTypes.GEOGRAPHY, - DataTypes.CITEXT - ]; - - // Expect at least these - const expCastTypes = { - integer: 'int4', - decimal: 'numeric', - date: 'timestamptz', - dateonly: 'date', - bigint: 'int8' - }; - - function reloadDynamicOIDs(sequelize) { - // Reset oids so we need to refetch them - sequelize.connectionManager._clearDynamicOIDs(); - sequelize.connectionManager._clearTypeParser(); - - // Force start of connection manager to reload dynamic OIDs - const User = sequelize.define('User', { - perms: DataTypes.ENUM(['foo', 'bar']) - }); - - return User.sync({ force: true }); - } - - it('should fetch regular dynamic oids and create parsers', async () => { - const sequelize = Support.sequelize; - 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; - - 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'); - } - - }); - }); - - it('should fetch enum dynamic oids and create parsers', async () => { - const sequelize = Support.sequelize; - 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', async () => { - const sequelize = Support.sequelize; - await reloadDynamicOIDs(sequelize); - 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'); - } - - 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 deleted file mode 100644 index 39eb80ad50ee..000000000000 --- a/test/integration/dialects/postgres/dao.test.js +++ /dev/null @@ -1,1132 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - Sequelize = Support.Sequelize, - Op = Sequelize.Op, - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'), - sequelize = require('../../../../lib/sequelize'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] DAO', () => { - beforeEach(async function() { - this.sequelize.options.quoteIdentifiers = true; - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - email: { type: DataTypes.ARRAY(DataTypes.TEXT) }, - settings: DataTypes.HSTORE, - document: { type: DataTypes.HSTORE, defaultValue: { default: "'value'" } }, - phones: DataTypes.ARRAY(DataTypes.HSTORE), - emergency_contact: DataTypes.JSON, - emergencyContact: DataTypes.JSON, - friends: { - type: DataTypes.ARRAY(DataTypes.JSON), - defaultValue: [] - }, - magic_numbers: { - type: DataTypes.ARRAY(DataTypes.INTEGER), - defaultValue: [] - }, - course_period: DataTypes.RANGE(DataTypes.DATE), - acceptable_marks: { type: DataTypes.RANGE(DataTypes.DECIMAL), defaultValue: [0.65, 1] }, - available_amount: DataTypes.RANGE, - holidays: DataTypes.ARRAY(DataTypes.RANGE(DataTypes.DATE)), - location: DataTypes.GEOMETRY() - }); - await this.User.sync({ force: true }); - }); - - afterEach(function() { - this.sequelize.options.quoteIdentifiers = true; - }); - - it('should be able to search within an array', async function() { - await this.User.findAll({ - where: { - email: ['hello', 'world'] - }, - attributes: ['id', 'username', 'email', 'settings', 'document', 'phones', 'emergency_contact', 'friends'], - logging(sql) { - expect(sql).to.equal('Executing (default): SELECT "id", "username", "email", "settings", "document", "phones", "emergency_contact", "friends" FROM "Users" AS "User" WHERE "User"."email" = ARRAY[\'hello\',\'world\']::TEXT[];'); - } - }); - }); - - 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' - }] - }); - - 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', async function() { - await this.User.bulkCreate([ - { username: 'bob', email: ['myemail@email.com'] }, - { username: 'tony', email: ['wrongemail@email.com'] } - ]); - - 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: '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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: '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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), - 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', async function() { - const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - - 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', async function() { - const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - - 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', 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', async function() { - await this.User.create({ - username: 'bob', - email: ['myemail@email.com'], - settings: { mailing: false, push: 'facebook', frequency: 3 } - }, { - logging(sql) { - const unexpected = '\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\''; - expect(sql).not.to.include(unexpected); - } - }); - }); - - it('should not rename hstore fields', async function() { - const Equipment = this.sequelize.define('Equipment', { - grapplingHook: { - type: DataTypes.STRING, - field: 'grappling_hook' - }, - utilityBelt: { - type: DataTypes.HSTORE - } - }); - - 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', async function() { - const Equipment = this.sequelize.define('Equipment', { - grapplingHook: { - type: DataTypes.STRING, - field: 'grappling_hook' - }, - utilityBelt: { - type: DataTypes.JSON - } - }); - - 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', 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', async function() { - const User = this.sequelize.define('UserEnums', { - mood: DataTypes.ENUM('happy', 'sad', '1970\'s') - }); - - await User.sync({ force: true }); - }); - - 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') - }); - - await User.sync({ force: true }); - - await User.sync(); - }); - - it('should be able to create/drop enums multiple times', async function() { - const User = this.sequelize.define('UserEnums', { - mood: DataTypes.ENUM('happy', 'sad', 'meh') - }); - - await User.sync({ force: true }); - - await User.sync({ force: true }); - }); - - it('should be able to create/drop multiple enums multiple times', async function() { - const DummyModel = this.sequelize.define('Dummy-pg', { - username: DataTypes.STRING, - theEnumOne: { - type: DataTypes.ENUM, - values: [ - 'one', - 'two', - 'three' - ] - }, - theEnumTwo: { - type: DataTypes.ENUM, - values: [ - 'four', - 'five', - 'six' - ] - } - }); - - 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)', async function() { - const DummyModel = this.sequelize.define('Dummy-pg', { - username: DataTypes.STRING, - theEnumOne: { - field: 'oh_my_this_enum_one', - type: DataTypes.ENUM, - values: [ - 'one', - 'two', - 'three' - ] - }, - theEnumTwo: { - field: 'oh_my_this_enum_two', - type: DataTypes.ENUM, - values: [ - 'four', - 'five', - 'six' - ] - } - }); - - 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', async function() { - let User = this.sequelize.define('UserEnums', { - mood: DataTypes.ENUM('happy', 'sad', 'meh') - }); - - 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', async function() { - let User = this.sequelize.define('UserEnums', { - priority: DataTypes.ENUM('1', '2', '6') - }); - - 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', async function() { - const User = this.sequelize.define('UserEnums', { - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - 'write', - 'check', - 'delete' - ])) - }); - - await User.sync({ force: true }); - - await User.sync(); - }); - - it('should be able to create/drop enums multiple times', async function() { - const User = this.sequelize.define('UserEnums', { - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - 'write', - 'check', - 'delete' - ])) - }); - - await User.sync({ force: true }); - - await User.sync({ force: true }); - }); - - it('should be able to add values to enum types', async function() { - let User = this.sequelize.define('UserEnums', { - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - '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', async function() { - const User = this.sequelize.define('UserEnums', { - name: DataTypes.STRING, - type: DataTypes.ENUM('A', 'B', 'C'), - owners: DataTypes.ARRAY(DataTypes.STRING), - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - 'write', - 'check', - 'delete' - ])) - }); - - 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 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'), - owners: DataTypes.ARRAY(DataTypes.STRING), - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - 'write', - 'check', - 'delete' - ])) - }); - - await expect(User.sync({ force: true }).then(() => { - return User.create({ - name: 'file.exe', - type: 'C', - owners: ['userA', 'userB'], - permissions: ['cosmic_ray_disk_access'] - }); - })).to.be.rejectedWith(/invalid input value for enum "enum_UserEnums_permissions": "cosmic_ray_disk_access"/); - }); - - it('should be able to find records', async function() { - const User = this.sequelize.define('UserEnums', { - name: DataTypes.STRING, - type: DataTypes.ENUM('A', 'B', 'C'), - permissions: DataTypes.ARRAY(DataTypes.ENUM([ - 'access', - 'write', - 'check', - 'delete' - ])) - }); - - 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(async function() { - this.User = this.sequelize.define('User', { - aNumber: DataTypes.INTEGER - }); - - await this.User.sync({ force: true }); - }); - - it('positive', async function() { - const User = this.User; - - 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', async function() { - const User = this.User; - - 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(async function() { - this.User = this.sequelize.define('User', { - aNumber: DataTypes.BIGINT - }); - - await this.User.sync({ force: true }); - }); - - it('positive', async function() { - const User = this.User; - - 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', async function() { - const User = this.User; - - 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(async function() { - this.User = this.sequelize.define('User', { - dates: DataTypes.ARRAY(DataTypes.DATE) - }); - await this.User.sync({ force: true }); - }); - - it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', async function() { - await this.User.create({ - dates: [] - }, { - logging(sql) { - expect(sql).not.to.contain('TIMESTAMP WITH TIME ZONE'); - expect(sql).not.to.contain('DATETIME'); - } - }); - }); - }); - - describe('model', () => { - 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', 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 - 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', async function() { - const User = this.User; - - 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"' }] - }); - - 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', async function() { - const User = this.User; - - await this.User.bulkCreate([{ - username: 'bob', - email: ['myemail@email.com'], - settings: { mailing: true, push: 'facebook', frequency: 3 } - }]); - - const user = await User.findByPk(1); - expect(user.settings.mailing).to.equal('true'); - }); - - 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', 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', async function() { - const data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }; - - 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', async function() { - const data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] }; - - 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', 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', async function() { - const HstoreSubmodel = this.sequelize.define('hstoreSubmodel', { - someValue: DataTypes.HSTORE - }); - const submodelValue = { testing: '"hstore"' }; - - this.User.hasMany(HstoreSubmodel); - - 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', async function() { - const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - 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', 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)] - ]; - - await User.create({ - username: 'bob', - email: ['myemail@email.com'], - holidays - }); - - 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', async function() { - const User = this.User; - const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - - await User.bulkCreate([{ - username: 'bob', - email: ['myemail@email.com'], - course_period: period - }]); - - 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', async function() { - const User = this.User; - const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - - 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', async function() { - const User = this.User; - const period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; - - const oldUser = await User.create({ - username: 'user', - email: ['foo@bar.com'], - course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)] - }); - - // 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', 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 }; - - 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', 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 }], - [{ value: new Date(2015, 8, 1), inclusive: true }, { value: new Date(2015, 9, 15), inclusive: true }] - ]; - const data = { username: 'user', email: ['foo@bar.com'], 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', 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)] - ]; - - 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', 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) - }); - - this.User.hasMany(HolidayDate); - - 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', async function() { - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - 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', async function() { - const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; - const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - 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', async function() { - const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - 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', async function() { - this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; - - this.User = this.sequelize.define('Userxs', { - username: DataTypes.STRING, - fullName: DataTypes.STRING // Note mixed case - }, { - quoteIdentifiers: false - }); - - 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', async function() { - this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; - this.Professor = this.sequelize.define('Professor', { - fullName: DataTypes.STRING - }, { - quoteIdentifiers: false - }); - this.Class = this.sequelize.define('Class', { - name: DataTypes.STRING - }, { - quoteIdentifiers: false - }); - this.Student = this.sequelize.define('Student', { - fullName: DataTypes.STRING - }, { - quoteIdentifiers: false - }); - this.ClassStudent = this.sequelize.define('ClassStudent', { - }, { - quoteIdentifiers: false, - tableName: 'class_student' - }); - this.Professor.hasMany(this.Class); - this.Class.belongsTo(this.Professor); - this.Class.belongsToMany(this.Student, { through: this.ClassStudent }); - this.Student.belongsToMany(this.Class, { through: this.ClassStudent }); - - 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: [ - { - model: this.Class, - include: [ - { - model: this.Student - } - ] - } - ], - 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 deleted file mode 100644 index 17de0200af52..000000000000 --- a/test/integration/dialects/postgres/data-types.test.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -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', () => { - const now = new Date(); - const nowString = now.toISOString(); - - it('DATE should validate a Date as normal', () => { - expect(DataTypes[dialect].DATE().validate(now)).to.equal(true); - expect(DataTypes[dialect].DATE().validate(nowString)).to.equal(true); - }); - - it('DATE should validate Infinity/-Infinity as true', () => { - expect(DataTypes[dialect].DATE().validate(Infinity)).to.equal(true); - expect(DataTypes[dialect].DATE().validate(-Infinity)).to.equal(true); - }); - - it('DATE should stringify Infinity/-Infinity to infinity/-infinity', () => { - expect(DataTypes[dialect].DATE().stringify(Infinity)).to.equal('Infinity'); - expect(DataTypes[dialect].DATE().stringify(-Infinity)).to.equal('-Infinity'); - }); - - it('DATEONLY should stringify Infinity/-Infinity to infinity/-infinity', () => { - expect(DataTypes[dialect].DATEONLY().stringify(Infinity)).to.equal('Infinity'); - expect(DataTypes[dialect].DATEONLY().stringify(-Infinity)).to.equal('-Infinity'); - }); - }); - - describe('DATE/DATEONLY Sanitize', () => { - const now = new Date(); - const nowString = now.toISOString(); - const nowDateOnly = nowString.substr(0, 10); - - it('DATE should sanitize a Date as normal', () => { - expect(DataTypes[dialect].DATE()._sanitize(now)).to.equalTime(now); - expect(DataTypes[dialect].DATE()._sanitize(nowString)).to.equalTime(now); - }); - - it('DATE should sanitize Infinity/-Infinity as Infinity/-Infinity', () => { - expect(DataTypes[dialect].DATE()._sanitize(Infinity)).to.equal(Infinity); - expect(DataTypes[dialect].DATE()._sanitize(-Infinity)).to.equal(-Infinity); - }); - - it('DATE should sanitize "Infinity"/"-Infinity" as Infinity/-Infinity', () => { - expect(DataTypes[dialect].DATE()._sanitize('Infinity')).to.equal(Infinity); - expect(DataTypes[dialect].DATE()._sanitize('-Infinity')).to.equal(-Infinity); - }); - - it('DATEONLY should sanitize a Date as normal', () => { - expect(DataTypes[dialect].DATEONLY()._sanitize(now)).to.equal(nowDateOnly); - expect(DataTypes[dialect].DATEONLY()._sanitize(nowString)).to.equal(nowDateOnly); - }); - - it('DATEONLY should sanitize Infinity/-Infinity as Infinity/-Infinity', () => { - expect(DataTypes[dialect].DATEONLY()._sanitize(Infinity)).to.equal(Infinity); - expect(DataTypes[dialect].DATEONLY()._sanitize(-Infinity)).to.equal(-Infinity); - }); - - it('DATEONLY should sanitize "Infinity"/"-Infinity" as Infinity/-Infinity', () => { - expect(DataTypes[dialect].DATEONLY()._sanitize('Infinity')).to.equal(Infinity); - expect(DataTypes[dialect].DATEONLY()._sanitize('-Infinity')).to.equal(-Infinity); - }); - }); - - describe('DATE SQL', () => { - // create dummy user - it('should be able to create and update records with Infinity/-Infinity', async function() { - this.sequelize.options.typeValidation = true; - - const date = new Date(); - const User = this.sequelize.define('User', { - username: this.sequelize.Sequelize.STRING, - beforeTime: { - type: this.sequelize.Sequelize.DATE, - defaultValue: -Infinity - }, - sometime: { - type: this.sequelize.Sequelize.DATE, - defaultValue: this.sequelize.fn('NOW') - }, - anotherTime: { - type: this.sequelize.Sequelize.DATE - }, - afterTime: { - type: this.sequelize.Sequelize.DATE, - defaultValue: Infinity - } - }, { - timestamps: true - }); - - await User.sync({ - force: true - }); - - 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', async function() { - this.sequelize.options.typeValidation = true; - - const date = new Date(); - const User = this.sequelize.define('User', { - username: this.sequelize.Sequelize.STRING, - beforeTime: { - type: this.sequelize.Sequelize.DATEONLY, - defaultValue: -Infinity - }, - sometime: { - type: this.sequelize.Sequelize.DATEONLY, - defaultValue: this.sequelize.fn('NOW') - }, - anotherTime: { - type: this.sequelize.Sequelize.DATEONLY - }, - afterTime: { - type: this.sequelize.Sequelize.DATEONLY, - defaultValue: Infinity - } - }, { - timestamps: true - }); - - await User.sync({ - force: true - }); - - 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 deleted file mode 100644 index 425a599cbb2d..000000000000 --- a/test/integration/dialects/postgres/error.test.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - DataTypes = require('../../../../lib/data-types'), - Support = require('../../support'), - Sequelize = Support.Sequelize, - dialect = Support.getTestDialect(), - _ = require('lodash'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] ExclusionConstraintError', () => { - const constraintName = 'overlap_period'; - beforeEach(async function() { - this.Booking = this.sequelize.define('Booking', { - roomNo: DataTypes.INTEGER, - period: DataTypes.RANGE(DataTypes.DATE) - }); - - 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', () => { - const errDetails = { - message: 'Exclusion constraint error', - constraint: 'constraint_name', - fields: { 'field1': 1, 'field2': [123, 321] }, - table: 'table_name', - parent: new Error('Test error') - }; - const err = new Sequelize.ExclusionConstraintError(errDetails); - - _.each(errDetails, (value, key) => { - expect(value).to.be.deep.equal(err[key]); - }); - }); - - it('should throw ExclusionConstraintError when "period" value overlaps existing', async function() { - const Booking = this.Booking; - - await Booking - .create({ - roomNo: 1, - guestName: 'Incognito Visitor', - period: [new Date(2015, 0, 1), new Date(2015, 0, 3)] - }); - - 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/hstore.test.js b/test/integration/dialects/postgres/hstore.test.js deleted file mode 100644 index ae81d459438d..000000000000 --- a/test/integration/dialects/postgres/hstore.test.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - hstore = require('../../../../lib/dialects/postgres/hstore'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] hstore', () => { - describe('stringify', () => { - it('should handle empty objects correctly', () => { - expect(hstore.stringify({ })).to.equal(''); - }); - - it('should handle null values correctly', () => { - expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL'); - }); - - it('should handle null values correctly', () => { - expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL'); - }); - - it('should handle empty string correctly', () => { - expect(hstore.stringify({ foo: '' })).to.equal('"foo"=>""'); - }); - - it('should handle a string with backslashes correctly', () => { - expect(hstore.stringify({ foo: '\\' })).to.equal('"foo"=>"\\\\"'); - }); - - it('should handle a string with double quotes correctly', () => { - expect(hstore.stringify({ foo: '""a"' })).to.equal('"foo"=>"\\"\\"a\\""'); - }); - - it('should handle a string with single quotes correctly', () => { - expect(hstore.stringify({ foo: "''a'" })).to.equal('"foo"=>"\'\'\'\'a\'\'"'); - }); - - it('should handle simple objects correctly', () => { - expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"'); - }); - - }); - - describe('parse', () => { - it('should handle a null object correctly', () => { - expect(hstore.parse(null)).to.deep.equal(null); - }); - - it('should handle empty string correctly', () => { - expect(hstore.parse('"foo"=>""')).to.deep.equal({ foo: '' }); - }); - - it('should handle a string with double quotes correctly', () => { - expect(hstore.parse('"foo"=>"\\"\\"a\\""')).to.deep.equal({ foo: '""a"' }); - }); - - it('should handle a string with single quotes correctly', () => { - expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({ foo: "''a'" }); - }); - - it('should handle a string with backslashes correctly', () => { - expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({ foo: '\\' }); - }); - - it('should handle empty objects correctly', () => { - expect(hstore.parse('')).to.deep.equal({ }); - }); - - it('should handle simple objects correctly', () => { - expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }); - }); - - }); - describe('stringify and parse', () => { - it('should stringify then parse back the same structure', () => { - const testObj = { foo: 'bar', count: '1', emptyString: '', quotyString: '""', extraQuotyString: '"""a"""""', backslashes: '\\f023', moreBackslashes: '\\f\\0\\2\\1', backslashesAndQuotes: '\\"\\"uhoh"\\"', nully: null }; - expect(hstore.parse(hstore.stringify(testObj))).to.deep.equal(testObj); - expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj); - }); - }); - }); -} diff --git a/test/integration/dialects/postgres/query-interface.test.js b/test/integration/dialects/postgres/query-interface.test.js deleted file mode 100644 index 066f684f03d8..000000000000 --- a/test/integration/dialects/postgres/query-interface.test.js +++ /dev/null @@ -1,275 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const dialect = Support.getTestDialect(); -const DataTypes = require('../../../../lib/data-types'); -const _ = require('lodash'); - - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] QueryInterface', () => { - beforeEach(function() { - this.sequelize.options.quoteIdenifiers = true; - this.queryInterface = this.sequelize.getQueryInterface(); - }); - - describe('createSchema', () => { - beforeEach(async function() { - // make sure we don't have a pre-existing schema called testSchema. - await this.queryInterface.dropSchema('testschema').catch(() => {}); - }); - - 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 }); - - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); - - 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 }); - - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); - }); - - describe('databaseVersion', () => { - 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(async function() { - // ensure the function names we'll use don't exist before we start. - // then setup our function to rename - 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', 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(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. - await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) - // suppress errors here. if create_job doesn't exist thats ok. - .catch(() => {}); - }); - - after(async function() { - // cleanup - await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) - // suppress errors here. if create_job doesn't exist thats ok. - .catch(() => {}); - }); - - it('creates a stored procedure', async function() { - const body = 'return test;'; - const options = {}; - - // make our call to create a function - 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', async function() { - const body = 'return test;'; - - // run with null options parameter - 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', async function() { - const body = 'return 1;'; - const options = {}; - - await Promise.all([ - // requires functionName - expect(this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options)) - .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), - - // requires Parameters array - expect(this.queryInterface.createFunction('create_job', null, 'integer', 'plpgsql', body, options)) - .to.be.rejectedWith(/function parameters array required/), - - // requires returnType - 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(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(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(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', async function() { - const first_body = 'return \'first\';'; - const second_body = 'return \'second\';'; - - // create function - 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(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', async function() { - const body = 'RETURN myVar + 1;'; - const options = { variables: [{ type: 'integer', name: 'myVar', default: 100 }] }; - 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(async function() { - const body = 'return test;'; - const options = {}; - - // make sure we have a droptest function in place. - await this.queryInterface.createFunction( - 'droptest', - [{ type: 'varchar', name: 'test' }], - 'varchar', - 'plpgsql', - body, - options - ) - // suppress errors.. this could fail if the function is already there.. thats ok. - .catch(() => {}); - }); - - it('can drop a function', 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', async function() { - await Promise.all([ - expect(this.queryInterface.dropFunction()) - .to.be.rejectedWith(/.*requires functionName/), - - expect(this.queryInterface.dropFunction('droptest')) - .to.be.rejectedWith(/.*function parameters array required/), - - expect(this.queryInterface.dropFunction('droptest', [{ name: 'test' }])) - .to.be.rejectedWith(/.*function or trigger used with a parameter without any type/) - ]); - }); - }); - - describe('indexes', () => { - 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', async function() { - await this.queryInterface.addIndex('Group', [this.sequelize.literal(`( - CASE "username" - WHEN 'foo' THEN 'bar' - ELSE 'baz' - END - )`)], { 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'); - }); - - 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' - }); - - 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 deleted file mode 100644 index 5f4265653a6e..000000000000 --- a/test/integration/dialects/postgres/query.test.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES] Query', () => { - - const taskAlias = 'AnActualVeryLongAliasThatShouldBreakthePostgresLimitOfSixtyFourCharacters'; - const teamAlias = 'Toto'; - - const executeTest = async (options, test) => { - const sequelize = Support.createSequelizeInstance(options); - - const User = sequelize.define('User', { name: DataTypes.STRING, updatedAt: DataTypes.DATE }, { underscored: true }); - const Team = sequelize.define('Team', { name: DataTypes.STRING }); - const Task = sequelize.define('Task', { title: DataTypes.STRING }); - - User.belongsTo(Task, { as: taskAlias, foreignKey: 'task_id' }); - User.belongsToMany(Team, { as: teamAlias, foreignKey: 'teamId', through: 'UserTeam' }); - Team.belongsToMany(User, { foreignKey: 'userId', through: 'UserTeam' }); - - 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', async function() { - const options = { ...this.sequelize.options, minifyAliases: false }; - - await executeTest(options, res => { - expect(res[taskAlias]).to.not.exist; - }); - }); - - it('should be able to retrieve include due to alias minifying', async function() { - const options = { ...this.sequelize.options, minifyAliases: true }; - - 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 deleted file mode 100644 index c7e7268f1ec2..000000000000 --- a/test/integration/dialects/postgres/range.test.js +++ /dev/null @@ -1,212 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'), - dialect = Support.getTestDialect(), - range = require('../../../../lib/dialects/postgres/range'); - -if (dialect.match(/^postgres/)) { - // Don't try to load pg until we know we're running on postgres. - const pg = require('pg'); - - describe('[POSTGRES Specific] range datatype', () => { - describe('stringify', () => { - it('should handle empty objects correctly', () => { - expect(range.stringify([])).to.equal('empty'); - }); - - it('should handle null as empty bound', () => { - expect(range.stringify([null, 1])).to.equal('[,1)'); - expect(range.stringify([1, null])).to.equal('[1,)'); - expect(range.stringify([null, null])).to.equal('[,)'); - }); - - it('should handle Infinity/-Infinity as infinity/-infinity bounds', () => { - expect(range.stringify([Infinity, 1])).to.equal('[infinity,1)'); - expect(range.stringify([1, Infinity])).to.equal('[1,infinity)'); - expect(range.stringify([-Infinity, 1])).to.equal('[-infinity,1)'); - expect(range.stringify([1, -Infinity])).to.equal('[1,-infinity)'); - expect(range.stringify([-Infinity, Infinity])).to.equal('[-infinity,infinity)'); - }); - - it('should throw error when array length is no 0 or 2', () => { - expect(() => { range.stringify([1]); }).to.throw(); - expect(() => { range.stringify([1, 2, 3]); }).to.throw(); - }); - - it('should throw error when non-array parameter is passed', () => { - expect(() => { range.stringify({}); }).to.throw(); - expect(() => { range.stringify('test'); }).to.throw(); - expect(() => { range.stringify(undefined); }).to.throw(); - }); - - it('should handle array of objects with `inclusive` and `value` properties', () => { - expect(range.stringify([{ inclusive: true, value: 0 }, { value: 1 }])).to.equal('[0,1)'); - expect(range.stringify([{ inclusive: true, value: 0 }, { inclusive: true, value: 1 }])).to.equal('[0,1]'); - expect(range.stringify([{ inclusive: false, value: 0 }, 1])).to.equal('(0,1)'); - expect(range.stringify([0, { inclusive: true, value: 1 }])).to.equal('[0,1]'); - }); - - it('should handle date values', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATE); - expect(Range.stringify([new Date(Date.UTC(2000, 1, 1)), - new Date(Date.UTC(2000, 1, 2))], { timezone: '+02:00' })).to.equal('\'["2000-02-01 02:00:00.000 +02:00","2000-02-02 02:00:00.000 +02:00")\''); - }); - }); - - describe('stringify value', () => { - - it('should stringify integer values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER); - expect(Range.stringify(1)).to.equal('\'1\'::int4'); - }); - - it('should stringify bigint values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.BIGINT); - expect(Range.stringify(1)).to.equal('\'1\'::int8'); - }); - - it('should stringify numeric values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DECIMAL); - expect(Range.stringify(1.1)).to.equal('\'1.1\'::numeric'); - }); - - it('should stringify dateonly values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATEONLY); - expect(Range.stringify(new Date(Date.UTC(2000, 1, 1)))).to.include('::date'); - }); - - it('should stringify date values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATE); - expect(Range.stringify(new Date(Date.UTC(2000, 1, 1)), { timezone: '+02:00' })).to.equal('\'2000-02-01 02:00:00.000 +02:00\'::timestamptz'); - }); - - describe('with null range bounds', () => { - const infiniteRange = [null, null]; - const infiniteRangeSQL = "'[,)'"; - - it('should stringify integer range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify bigint range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.BIGINT); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify numeric range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DECIMAL); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify dateonly ranges appropriately', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATEONLY); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should be stringified to appropriate unbounded postgres range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATEONLY); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify date values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATE); - expect(Range.stringify(infiniteRange, { timezone: '+02:00' })).to.equal(infiniteRangeSQL); - }); - - }); - - describe('with infinite range bounds', () => { - const infiniteRange = [-Infinity, Infinity]; - const infiniteRangeSQL = "'[-infinity,infinity)'"; - - it('should stringify integer range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify bigint range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.BIGINT); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify numeric range to infinite range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DECIMAL); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify dateonly ranges appropriately', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATEONLY); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should be stringified to appropriate unbounded postgres range', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATEONLY); - expect(Range.stringify(infiniteRange)).to.equal(infiniteRangeSQL); - }); - - it('should stringify date values with appropriate casting', () => { - const Range = new DataTypes.postgres.RANGE(DataTypes.DATE); - expect(Range.stringify(infiniteRange, { timezone: '+02:00' })).to.equal(infiniteRangeSQL); - }); - - }); - - }); - - describe('parse', () => { - it('should handle a null object correctly', () => { - expect(range.parse(null)).to.equal(null); - }); - - it('should handle empty range string correctly', () => { - expect(range.parse('empty')).to.deep.equal([]); - }); - - it('should handle empty bounds correctly', () => { - expect(range.parse('(1,)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: 1, inclusive: false }, { value: null, inclusive: false }]); - expect(range.parse('(,1)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: null, inclusive: false }, { value: 1, inclusive: false }]); - expect(range.parse('(,)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: null, inclusive: false }, { value: null, inclusive: false }]); - }); - - it('should handle infinity/-infinity bounds correctly', () => { - expect(range.parse('(infinity,1)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: Infinity, inclusive: false }, { value: 1, inclusive: false }]); - expect(range.parse('(1,infinity)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: 1, inclusive: false }, { value: Infinity, inclusive: false }]); - expect(range.parse('(-infinity,1)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: -Infinity, inclusive: false }, { value: 1, inclusive: false }]); - expect(range.parse('(1,-infinity)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: 1, inclusive: false }, { value: -Infinity, inclusive: false }]); - expect(range.parse('(-infinity,infinity)', DataTypes.postgres.INTEGER.parse)).to.deep.equal([{ value: -Infinity, inclusive: false }, { value: Infinity, inclusive: false }]); - }); - - it('should return raw value if not range is returned', () => { - expect(range.parse('some_non_array')).to.deep.equal('some_non_array'); - }); - - it('should handle native postgres timestamp format', async () => { - // Make sure nameOidMap is loaded - const connection = await Support.sequelize.connectionManager.getConnection(); - - 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'); - }); - - }); - describe('stringify and parse', () => { - it('should stringify then parse back the same structure', () => { - const testRange = [{ value: 5, inclusive: true }, { value: 10, inclusive: true }]; - const Range = new DataTypes.postgres.RANGE(DataTypes.INTEGER); - - let stringified = Range.stringify(testRange, {}); - stringified = stringified.substr(1, stringified.length - 2); // Remove the escaping ticks - - expect(DataTypes.postgres.RANGE.parse(stringified, { parser: DataTypes.postgres.INTEGER.parse })).to.deep.equal(testRange); - }); - }); - }); -} diff --git a/test/integration/dialects/postgres/regressions.test.js b/test/integration/dialects/postgres/regressions.test.js deleted file mode 100644 index 963e9110fe75..000000000000 --- a/test/integration/dialects/postgres/regressions.test.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - Sequelize = Support.Sequelize, - dialect = Support.getTestDialect(); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] Regressions', () => { - it('properly fetch OIDs after sync, #8749', async function() { - const User = this.sequelize.define('User', { - active: Sequelize.BOOLEAN - }); - - /** - * This Model is important, sync will try to fetch OIDs after each ENUM model sync - * Having ENUM in this model will force OIDs re-fetch - * We are testing that OID refresh keep base type intact - */ - const Media = this.sequelize.define('Media', { - type: Sequelize.ENUM([ - 'image', 'video', 'audio' - ]) - }); - - User.hasMany(Media); - Media.belongsTo(User); - - 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 deleted file mode 100644 index 064fa12a3921..000000000000 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -const chai = require('chai'); -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(() => { - jetpack.remove(fileName); - jetpack.remove(directoryName); - }); - - it('close connection and remove journal and wal files', async function() { - const sequelize = Support.createSequelizeInstance({ - storage: jetpack.path(fileName) - }); - const User = sequelize.define('User', { username: DataTypes.STRING }); - - 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 deleted file mode 100644 index c73030137f0d..000000000000 --- a/test/integration/dialects/sqlite/dao-factory.test.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'), - dialect = Support.getTestDialect(), - dbFile = 'test.sqlite', - storages = [dbFile]; - -if (dialect === 'sqlite') { - describe('[SQLITE Specific] DAOFactory', () => { - after(function() { - this.sequelize.options.storage = ':memory:'; - }); - - beforeEach(async function() { - this.sequelize.options.storage = dbFile; - this.User = this.sequelize.define('User', { - age: DataTypes.INTEGER, - name: DataTypes.STRING, - bio: DataTypes.TEXT - }); - await this.User.sync({ force: true }); - }); - - storages.forEach(storage => { - describe(`with storage "${storage}"`, () => { - after(() => { - if (storage === dbFile) { - require('fs').writeFileSync(dbFile, ''); - } - }); - - describe('create', () => { - 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', async function() { - const Person = this.sequelize.define('Person', { - name: DataTypes.STRING, - options: DataTypes.TEXT - }); - - await Person.sync({ force: true }); - const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - - 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', async function() { - const Person = this.sequelize.define('Person', { - name: DataTypes.STRING, - has_swag: DataTypes.BOOLEAN - }); - - 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', async function() { - const Person = this.sequelize.define('Person', { - name: DataTypes.STRING, - has_swag: DataTypes.BOOLEAN - }); - - 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(async function() { - await this.User.create({ name: 'user', bio: 'footbar' }); - }); - - 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', async function() { // eslint-disable-line mocha/no-skipped-tests - const user = await this.User.findOne({ - where: { name: 'user' }, - attributes: ['id', ['name', 'username']] - }); - - expect(user.username).to.equal('user'); - }); - }); - - describe('.all', () => { - beforeEach(async function() { - await this.User.bulkCreate([ - { name: 'user', bio: 'foobar' }, - { name: 'user', bio: 'foobar' } - ]); - }); - - 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', async function() { - const users = []; - - for (let i = 2; i < 5; i++) { - users[users.length] = { age: i }; - } - - 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', async function() { - const users = []; - - for (let i = 2; i <= 5; i++) { - users[users.length] = { age: i }; - } - - 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 deleted file mode 100644 index 47eb3d286ead..000000000000 --- a/test/integration/dialects/sqlite/dao.test.js +++ /dev/null @@ -1,118 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - Sequelize = Support.Sequelize, - Op = Sequelize.Op, - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect === 'sqlite') { - describe('[SQLITE Specific] DAO', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - emergency_contact: DataTypes.JSON, - emergencyContact: DataTypes.JSON, - dateField: { - type: DataTypes.DATE, - field: 'date_field' - } - }); - this.Project = this.sequelize.define('project', { - dateField: { - type: DataTypes.DATE, - field: 'date_field' - } - }); - - this.User.hasMany(this.Project); - await this.sequelize.sync({ force: true }); - }); - - describe('findAll', () => { - it('handles dates correctly', async function() { - const user = this.User.build({ username: 'user' }); - - user.dataValues.createdAt = new Date(2011, 4, 4); - - 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', async function() { - await this.User.create({ - dateField: 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', async function() { - await this.User.create({ - projects: [ - { dateField: 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]); - - 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: ['kate', 'joe'] }) - ]); - - 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', async function() { - const Payments = this.sequelize.define('payments', {}); - - 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 deleted file mode 100644 index ced16581138a..000000000000 --- a/test/integration/dialects/sqlite/sqlite-master.test.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'); - -if (dialect === 'sqlite') { - describe('[SQLITE Specific] sqlite_master raw queries', () => { - beforeEach(async function() { - this.sequelize.define('SomeTable', { - someColumn: DataTypes.INTEGER - }, { - freezeTableName: true, - timestamps: false - }); - - await this.sequelize.sync({ force: true }); - }); - - 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 *', 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', 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/dialects/sqlite/test.sqlite b/test/integration/dialects/sqlite/test.sqlite deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/integration/error.test.js b/test/integration/error.test.js deleted file mode 100644 index 0f0304f80917..000000000000 --- a/test/integration/error.test.js +++ /dev/null @@ -1,396 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize; - -describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { - describe('API Surface', () => { - it('Should have the Error constructors exposed', () => { - expect(Sequelize).to.have.property('Error'); - expect(Sequelize).to.have.property('ValidationError'); - expect(Sequelize).to.have.property('OptimisticLockError'); - }); - - it('Sequelize Errors instances should be instances of Error', () => { - const error = new Sequelize.Error(); - const errorMessage = 'error message'; - const validationError = new Sequelize.ValidationError(errorMessage, [ - new Sequelize.ValidationErrorItem(' cannot be null', 'notNull Violation', '', null), - new Sequelize.ValidationErrorItem(' cannot be an array or an object', 'string violation', '', null) - ]); - const optimisticLockError = new Sequelize.OptimisticLockError(); - - expect(error).to.be.instanceOf(Sequelize.Error); - expect(error).to.be.instanceOf(Error); - expect(error).to.have.property('name', 'SequelizeBaseError'); - - expect(validationError).to.be.instanceOf(Sequelize.ValidationError); - expect(validationError).to.be.instanceOf(Error); - expect(validationError).to.have.property('name', 'SequelizeValidationError'); - expect(validationError.message).to.equal(errorMessage); - - expect(optimisticLockError).to.be.instanceOf(Sequelize.OptimisticLockError); - expect(optimisticLockError).to.be.instanceOf(Error); - expect(optimisticLockError).to.have.property('name', 'SequelizeOptimisticLockError'); - }); - - it('SequelizeValidationError should find errors by path', () => { - const errorItems = [ - new Sequelize.ValidationErrorItem('invalid', 'type', 'first_name', null), - new Sequelize.ValidationErrorItem('invalid', 'type', 'last_name', null) - ]; - const validationError = new Sequelize.ValidationError('Validation error', errorItems); - expect(validationError).to.have.property('get'); - expect(validationError.get).to.be.a('function'); - - const matches = validationError.get('first_name'); - expect(matches).to.be.instanceOf(Array); - expect(matches).to.have.lengthOf(1); - expect(matches[0]).to.have.property('message', 'invalid'); - }); - - it('SequelizeValidationError should override message property when message parameter is specified', () => { - const errorItems = [ - new Sequelize.ValidationErrorItem('invalid', 'type', 'first_name', null), - new Sequelize.ValidationErrorItem('invalid', 'type', 'last_name', null) - ], - customErrorMessage = 'Custom validation error message', - validationError = new Sequelize.ValidationError(customErrorMessage, errorItems); - - expect(validationError).to.have.property('name', 'SequelizeValidationError'); - expect(validationError.message).to.equal(customErrorMessage); - }); - - it('SequelizeValidationError should concatenate an error messages from given errors if no explicit message is defined', () => { - const errorItems = [ - new Sequelize.ValidationErrorItem(' cannot be null', 'notNull Violation', '', null), - new Sequelize.ValidationErrorItem(' cannot be an array or an object', 'string violation', '', null) - ], - validationError = new Sequelize.ValidationError(null, errorItems); - - expect(validationError).to.have.property('name', 'SequelizeValidationError'); - expect(validationError.message).to.match(/notNull Violation: cannot be null,\nstring violation: cannot be an array or an object/); - }); - - it('SequelizeValidationErrorItem does not require instance & validator constructor parameters', () => { - const error = new Sequelize.ValidationErrorItem('error!', null, 'myfield'); - - expect(error).to.be.instanceOf(Sequelize.ValidationErrorItem); - }); - - it('SequelizeValidationErrorItem should have instance, key & validator properties when given to constructor', () => { - const inst = { foo: 'bar' }; - const vargs = [4]; - - const error = new Sequelize.ValidationErrorItem('error!', 'FUNCTION', 'foo', 'bar', inst, 'klen', 'len', vargs); - - expect(error).to.have.property('instance'); - expect(error.instance).to.equal(inst); - - expect(error).to.have.property('validatorKey', 'klen'); - expect(error).to.have.property('validatorName', 'len'); - expect(error).to.have.property('validatorArgs', vargs); - }); - - it('SequelizeValidationErrorItem.getValidatorKey() should return a string', () => { - const error = new Sequelize.ValidationErrorItem('error!', 'FUNCTION', 'foo', 'bar', null, 'klen', 'len', [4]); - - expect(error).to.have.property('getValidatorKey'); - expect(error.getValidatorKey).to.be.a('function'); - - expect(error.getValidatorKey()).to.equal('function.klen'); - expect(error.getValidatorKey(false)).to.equal('klen'); - expect(error.getValidatorKey(0)).to.equal('klen'); - expect(error.getValidatorKey(1, ':')).to.equal('function:klen'); - expect(error.getValidatorKey(true, '-:-')).to.equal('function-:-klen'); - - const empty = new Sequelize.ValidationErrorItem('error!', 'FUNCTION', 'foo', 'bar'); - - expect(empty.getValidatorKey()).to.equal(''); - expect(empty.getValidatorKey(false)).to.equal(''); - expect(empty.getValidatorKey(0)).to.equal(''); - expect(empty.getValidatorKey(1, ':')).to.equal(''); - expect(empty.getValidatorKey(true, '-:-')).to.equal(''); - }); - - it('SequelizeValidationErrorItem.getValidatorKey() should throw if namespace separator is invalid (only if NS is used & available)', () => { - const error = new Sequelize.ValidationErrorItem('error!', 'FUNCTION', 'foo', 'bar', null, 'klen', 'len', [4]); - - expect(() => error.getValidatorKey(false, {})).to.not.throw(); - expect(() => error.getValidatorKey(false, [])).to.not.throw(); - expect(() => error.getValidatorKey(false, null)).to.not.throw(); - expect(() => error.getValidatorKey(false, '')).to.not.throw(); - expect(() => error.getValidatorKey(false, false)).to.not.throw(); - expect(() => error.getValidatorKey(false, true)).to.not.throw(); - expect(() => error.getValidatorKey(false, undefined)).to.not.throw(); - expect(() => error.getValidatorKey(true, undefined)).to.not.throw(); // undefined will trigger use of function parameter default - - expect(() => error.getValidatorKey(true, {})).to.throw(Error); - expect(() => error.getValidatorKey(true, [])).to.throw(Error); - expect(() => error.getValidatorKey(true, null)).to.throw(Error); - expect(() => error.getValidatorKey(true, '')).to.throw(Error); - expect(() => error.getValidatorKey(true, false)).to.throw(Error); - expect(() => error.getValidatorKey(true, true)).to.throw(Error); - }); - - it('SequelizeValidationErrorItem should map deprecated "type" values to new "origin" values', () => { - const data = { - 'notNull Violation': 'CORE', - 'string violation': 'CORE', - 'unique violation': 'DB', - 'Validation error': 'FUNCTION' - }; - - Object.keys(data).forEach(k => { - const error = new Sequelize.ValidationErrorItem('error!', k, 'foo', null); - - expect(error).to.have.property('origin', data[k]); - expect(error).to.have.property('type', k); - }); - }); - - it('SequelizeValidationErrorItem.Origins is valid', () => { - const ORIGINS = Sequelize.ValidationErrorItem.Origins; - - expect(ORIGINS).to.have.property('CORE', 'CORE'); - expect(ORIGINS).to.have.property('DB', 'DB'); - expect(ORIGINS).to.have.property('FUNCTION', 'FUNCTION'); - - }); - - it('SequelizeDatabaseError should keep original message', () => { - const orig = new Error('original database error message'); - const databaseError = new Sequelize.DatabaseError(orig); - - expect(databaseError).to.have.property('parent'); - expect(databaseError).to.have.property('original'); - expect(databaseError.name).to.equal('SequelizeDatabaseError'); - expect(databaseError.message).to.equal('original database error message'); - }); - - it('SequelizeDatabaseError should keep the original sql and the parameters', () => { - const orig = new Error(); - orig.sql = 'SELECT * FROM table WHERE id = $1'; - orig.parameters = ['1']; - const databaseError = new Sequelize.DatabaseError(orig); - - expect(databaseError).to.have.property('sql'); - expect(databaseError).to.have.property('parameters'); - expect(databaseError.sql).to.equal(orig.sql); - expect(databaseError.parameters).to.equal(orig.parameters); - }); - - it('ConnectionError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.ConnectionError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeConnectionError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('ConnectionRefusedError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.ConnectionRefusedError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeConnectionRefusedError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('AccessDeniedError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.AccessDeniedError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeAccessDeniedError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('HostNotFoundError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.HostNotFoundError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeHostNotFoundError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('HostNotReachableError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.HostNotReachableError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeHostNotReachableError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('InvalidConnectionError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.InvalidConnectionError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeInvalidConnectionError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - - it('ConnectionTimedOutError should keep original message', () => { - const orig = new Error('original connection error message'); - const connectionError = new Sequelize.ConnectionTimedOutError(orig); - - expect(connectionError).to.have.property('parent'); - expect(connectionError).to.have.property('original'); - expect(connectionError.name).to.equal('SequelizeConnectionTimedOutError'); - expect(connectionError.message).to.equal('original connection error message'); - }); - }); - - describe('OptimisticLockError', () => { - it('got correct error type and message', async function() { - const Account = this.sequelize.define('Account', { - number: { - type: Sequelize.INTEGER - } - }, { - version: true - }); - - 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') - ]); - }); - }); - - describe('ConstraintError', () => { - [ - { - type: 'UniqueConstraintError', - exception: Sequelize.UniqueConstraintError - }, - { - type: 'ValidationError', - exception: Sequelize.ValidationError - } - ].forEach(constraintTest => { - - it(`Can be intercepted as ${constraintTest.type} using .catch`, async function() { - const spy = sinon.spy(), - User = this.sequelize.define('user', { - first_name: { - type: Sequelize.STRING, - unique: 'unique_name' - }, - last_name: { - type: Sequelize.STRING, - unique: 'unique_name' - } - }); - - const record = { first_name: 'jan', last_name: 'meier' }; - 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', async function() { - const spy = sinon.spy(), - User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - unique: 'unique \n unique' - } - }); - - 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', async function() { - let User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - unique: 'unique \n unique' - } - }, { timestamps: false }); - - 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', async function() { - const User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - unique: 'unique' - } - }, { timestamps: false }); - - 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 deleted file mode 100644 index 9140abb762d4..000000000000 --- a/test/integration/hooks/associations.test.js +++ /dev/null @@ -1,864 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }, { - paranoid: true - }); - - await this.sequelize.sync({ force: true }); - }); - - - describe('associations', () => { - describe('1:1', () => { - describe('cascade onUpdate', () => { - beforeEach(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasOne(this.Tasks, { onUpdate: 'cascade', hooks: true }); - this.Tasks.belongsTo(this.Projects); - - await this.Projects.sync({ force: true }); - - await this.Tasks.sync({ force: true }); - }); - - it('on success', async function() { - let beforeHook = false, - afterHook = false; - - this.Tasks.beforeUpdate(async () => { - beforeHook = true; - }); - - this.Tasks.afterUpdate(async () => { - afterHook = 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', async function() { - this.Tasks.afterUpdate(async () => { - throw new Error('Whoops!'); - }); - - 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(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasOne(this.Tasks, { onDelete: 'CASCADE', hooks: true }); - this.Tasks.belongsTo(this.Projects); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.setTask(task); - await project.destroy(); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - - it('with errors', async function() { - const CustomErrorText = 'Whoops!'; - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - throw new Error(CustomErrorText); - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - 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(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasOne(this.Tasks); - this.Tasks.belongsTo(this.Projects); - - await this.Projects.sync({ force: true }); - - await this.Tasks.sync({ force: true }); - }); - - it('on success', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.Tasks.beforeUpdate(beforeHook); - this.Tasks.afterUpdate(afterHook); - - 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', async function() { - this.Tasks.afterUpdate(() => { - throw new Error('Whoops!'); - }); - - 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(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasMany(this.Tasks); - this.Tasks.belongsTo(this.Projects); - - await this.Projects.sync({ force: true }); - - await this.Tasks.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.addTask(task); - await project.removeTask(task); - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - - it('with errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(() => { - beforeTask(); - throw new Error('Whoops!'); - }); - this.Tasks.afterUpdate(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - - try { - await project.addTask(task); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).not.to.have.been.called; - } - }); - }); - }); - }); - - describe('1:M', () => { - describe('cascade', () => { - beforeEach(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasMany(this.Tasks, { onDelete: 'cascade', hooks: true }); - this.Tasks.belongsTo(this.Projects, { hooks: true }); - - await this.Projects.sync({ force: true }); - - await this.Tasks.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.addTask(task); - await project.destroy(); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - - it('with errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - throw new Error('Whoops!'); - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - 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(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.hasMany(this.Tasks); - this.Tasks.belongsTo(this.Projects); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.addTask(task); - await project.removeTask(task); - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - - it('with errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeUpdate(async () => { - beforeTask = true; - throw new Error('Whoops!'); - }); - - this.Tasks.afterUpdate(async () => { - afterTask = true; - }); - - 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; - } - }); - }); - }); - }); - - describe('M:M', () => { - describe('cascade', () => { - beforeEach(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - 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 }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.addTask(task); - await project.destroy(); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - // Since Sequelize does not cascade M:M, these should be false - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - - it('with errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - throw new Error('Whoops!'); - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - 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(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.Projects.belongsToMany(this.Tasks, { hooks: true, through: 'project_tasks' }); - this.Tasks.belongsToMany(this.Projects, { hooks: true, through: 'project_tasks' }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - const beforeProject = sinon.spy(), - afterProject = sinon.spy(), - beforeTask = sinon.spy(), - afterTask = sinon.spy(); - - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); - - const project = await this.Projects.create({ title: 'New Project' }); - const task = await this.Tasks.create({ title: 'New Task' }); - await project.addTask(task); - await project.removeTask(task); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - - it('with errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeUpdate(async () => { - beforeTask = true; - throw new Error('Whoops!'); - }); - - this.Tasks.afterUpdate(async () => { - afterTask = true; - }); - - 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; - }); - }); - }); - }); - - // NOTE: Reenable when FK constraints create table query is fixed when using hooks - if (dialect !== 'mssql') { - describe('multiple 1:M', () => { - - describe('cascade', () => { - beforeEach(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.MiniTasks = this.sequelize.define('MiniTask', { - mini_title: DataTypes.STRING - }); - - this.Projects.hasMany(this.Tasks, { onDelete: 'cascade', hooks: true }); - this.Projects.hasMany(this.MiniTasks, { onDelete: 'cascade', hooks: true }); - - this.Tasks.belongsTo(this.Projects, { hooks: true }); - this.Tasks.hasMany(this.MiniTasks, { onDelete: 'cascade', hooks: true }); - - this.MiniTasks.belongsTo(this.Projects, { hooks: true }); - this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false, - beforeMiniTask = false, - afterMiniTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - this.MiniTasks.beforeDestroy(async () => { - beforeMiniTask = true; - }); - - this.MiniTasks.afterDestroy(async () => { - afterMiniTask = true; - }); - - 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(); - 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', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false, - beforeMiniTask = false, - afterMiniTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - this.MiniTasks.beforeDestroy(async () => { - beforeMiniTask = true; - throw new Error('Whoops!'); - }); - - this.MiniTasks.afterDestroy(async () => { - afterMiniTask = true; - }); - - 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; - } - }); - }); - }); - }); - - describe('multiple 1:M sequential hooks', () => { - describe('cascade', () => { - beforeEach(async function() { - this.Projects = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - this.Tasks = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - - this.MiniTasks = this.sequelize.define('MiniTask', { - mini_title: DataTypes.STRING - }); - - this.Projects.hasMany(this.Tasks, { onDelete: 'cascade', hooks: true }); - this.Projects.hasMany(this.MiniTasks, { onDelete: 'cascade', hooks: true }); - - this.Tasks.belongsTo(this.Projects, { hooks: true }); - this.Tasks.hasMany(this.MiniTasks, { onDelete: 'cascade', hooks: true }); - - this.MiniTasks.belongsTo(this.Projects, { hooks: true }); - this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#remove', () => { - it('with no errors', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false, - beforeMiniTask = false, - afterMiniTask = false; - - this.Projects.beforeCreate(async () => { - beforeProject = true; - }); - - this.Projects.afterCreate(async () => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(async () => { - beforeTask = true; - }); - - this.Tasks.afterDestroy(async () => { - afterTask = true; - }); - - this.MiniTasks.beforeDestroy(async () => { - beforeMiniTask = true; - }); - - this.MiniTasks.afterDestroy(async () => { - afterMiniTask = true; - }); - - 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' }) - ]); - - 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', async function() { - let beforeProject = false, - afterProject = false, - beforeTask = false, - afterTask = false, - beforeMiniTask = false, - afterMiniTask = false; - const CustomErrorText = 'Whoops!'; - - this.Projects.beforeCreate(() => { - beforeProject = true; - }); - - this.Projects.afterCreate(() => { - afterProject = true; - }); - - this.Tasks.beforeDestroy(() => { - beforeTask = true; - throw new Error(CustomErrorText); - }); - - this.Tasks.afterDestroy(() => { - afterTask = true; - }); - - this.MiniTasks.beforeDestroy(() => { - beforeMiniTask = true; - }); - - this.MiniTasks.afterDestroy(() => { - afterMiniTask = true; - }); - - 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' }) - ]); - - 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 deleted file mode 100644 index 040955024d80..000000000000 --- a/test/integration/hooks/bulkOperation.test.js +++ /dev/null @@ -1,537 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }, { - paranoid: true - }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#bulkCreate', () => { - describe('on success', () => { - it('should run hooks', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.User.beforeBulkCreate(beforeBulk); - - this.User.afterBulkCreate(afterBulk); - - await this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ]); - - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); - }); - - describe('on error', () => { - it('should return an error from before', async function() { - this.User.beforeBulkCreate(() => { - throw new Error('Whoops!'); - }); - - await expect(this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ])).to.be.rejected; - }); - - it('should return an error from after', async function() { - this.User.afterBulkCreate(() => { - throw new Error('Whoops!'); - }); - - await expect(this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ])).to.be.rejected; - }); - }); - - describe('with the {individualHooks: true} option', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - defaultValue: '' - }, - beforeHookTest: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - aNumber: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - await this.User.sync({ force: true }); - }); - - it('should run the afterCreate/beforeCreate functions for each item created successfully', async function() { - let beforeBulkCreate = false, - afterBulkCreate = false; - - this.User.beforeBulkCreate(async () => { - beforeBulkCreate = true; - }); - - this.User.afterBulkCreate(async () => { - afterBulkCreate = true; - }); - - this.User.beforeCreate(async user => { - user.beforeHookTest = true; - }); - - this.User.afterCreate(async user => { - user.username = `User${user.id}`; - }); - - 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', async function() { - let beforeBulkCreate = false, - afterBulkCreate = false; - - this.User.beforeBulkCreate(async () => { - beforeBulkCreate = true; - }); - - this.User.afterBulkCreate(async () => { - afterBulkCreate = true; - }); - - this.User.beforeCreate(async () => { - throw new Error('You shall not pass!'); - }); - - this.User.afterCreate(async user => { - user.username = `User${user.id}`; - }); - - 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', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.User.beforeBulkUpdate(beforeBulk); - this.User.afterBulkUpdate(afterBulk); - - await this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ]); - - 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', async function() { - this.User.beforeBulkUpdate(() => { - throw new Error('Whoops!'); - }); - - await this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ]); - - await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); - - it('should return an error from after', async function() { - this.User.afterBulkUpdate(() => { - throw new Error('Whoops!'); - }); - - await this.User.bulkCreate([ - { username: 'Cheech', mood: 'sad' }, - { username: 'Chong', mood: 'sad' } - ]); - - await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); - }); - - describe('with the {individualHooks: true} option', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - defaultValue: '' - }, - beforeHookTest: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - aNumber: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - await this.User.sync({ force: true }); - }); - - it('should run the after/before functions for each item created successfully', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.User.beforeBulkUpdate(beforeBulk); - - this.User.afterBulkUpdate(afterBulk); - - this.User.beforeUpdate(user => { - expect(user.changed()).to.not.be.empty; - user.beforeHookTest = true; - }); - - this.User.afterUpdate(user => { - user.username = `User${user.id}`; - }); - - await this.User.bulkCreate([ - { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]); - - 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', async function() { - this.User.beforeUpdate(user => { - expect(user.changed()).to.not.be.empty; - if (user.get('id') === 1) { - user.set('aNumber', user.get('aNumber') + 3); - } - }); - - await this.User.bulkCreate([ - { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]); - - 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', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.User.beforeBulkUpdate(beforeBulk); - - this.User.afterBulkUpdate(afterBulk); - - this.User.beforeUpdate(() => { - throw new Error('You shall not pass!'); - }); - - this.User.afterUpdate(user => { - user.username = `User${user.id}`; - }); - - 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', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.User.beforeBulkDestroy(beforeBulk); - this.User.afterBulkDestroy(afterBulk); - - 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', async function() { - this.User.beforeBulkDestroy(() => { - throw new Error('Whoops!'); - }); - - await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; - }); - - it('should return an error from after', async function() { - this.User.afterBulkDestroy(() => { - throw new Error('Whoops!'); - }); - - await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; - }); - }); - - describe('with the {individualHooks: true} option', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - defaultValue: '' - }, - beforeHookTest: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - aNumber: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - await this.User.sync({ force: true }); - }); - - 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(async () => { - beforeBulk = true; - }); - - this.User.afterBulkDestroy(async () => { - afterBulk = true; - }); - - this.User.beforeDestroy(async () => { - beforeHook = true; - }); - - this.User.afterDestroy(async () => { - afterHook = true; - }); - - await this.User.bulkCreate([ - { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]); - - 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', async function() { - let beforeBulk = false, - afterBulk = false, - beforeHook = false, - afterHook = false; - - this.User.beforeBulkDestroy(async () => { - beforeBulk = true; - }); - - this.User.afterBulkDestroy(async () => { - afterBulk = true; - }); - - this.User.beforeDestroy(async () => { - beforeHook = true; - throw new Error('You shall not pass!'); - }); - - this.User.afterDestroy(async () => { - afterHook = true; - }); - - 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(async function() { - await this.ParanoidUser.bulkCreate([ - { username: 'adam', mood: 'happy' }, - { username: 'joe', mood: 'sad' } - ]); - - await this.ParanoidUser.destroy({ truncate: true }); - }); - - describe('on success', () => { - it('should run hooks', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(); - - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); - - 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', async function() { - this.ParanoidUser.beforeBulkRestore(() => { - throw new Error('Whoops!'); - }); - - await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; - }); - - it('should return an error from after', async function() { - this.ParanoidUser.afterBulkRestore(() => { - throw new Error('Whoops!'); - }); - - await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; - }); - }); - - describe('with the {individualHooks: true} option', () => { - beforeEach(async function() { - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - aNumber: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }, { - paranoid: true - }); - - await this.ParanoidUser.sync({ force: true }); - }); - - it('should run the after/before functions for each item restored successfully', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(), - beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(afterHook); - - await this.ParanoidUser.bulkCreate([ - { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]); - - 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', async function() { - const beforeBulk = sinon.spy(), - afterBulk = sinon.spy(), - beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); - this.ParanoidUser.beforeRestore(async () => { - beforeHook(); - throw new Error('You shall not pass!'); - }); - - this.ParanoidUser.afterRestore(afterHook); - - 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 deleted file mode 100644 index a97a2084c0fc..000000000000 --- a/test/integration/hooks/count.test.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - await this.sequelize.sync({ force: true }); - }); - - describe('#count', () => { - beforeEach(async function() { - await this.User.bulkCreate([ - { username: 'adam', mood: 'happy' }, - { username: 'joe', mood: 'sad' }, - { username: 'joe', mood: 'happy' } - ]); - }); - - describe('on success', () => { - it('hook runs', async function() { - let beforeHook = false; - - this.User.beforeCount(() => { - beforeHook = true; - }); - - const count = await this.User.count(); - expect(count).to.equal(3); - expect(beforeHook).to.be.true; - }); - - it('beforeCount hook can change options', async function() { - this.User.beforeCount(options => { - options.where.username = 'adam'; - }); - - await expect(this.User.count({ where: { username: 'joe' } })).to.eventually.equal(1); - }); - }); - - describe('on error', () => { - it('in beforeCount hook returns error', async function() { - this.User.beforeCount(() => { - throw new Error('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 deleted file mode 100644 index da684deff9f6..000000000000 --- a/test/integration/hooks/create.test.js +++ /dev/null @@ -1,196 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - await this.sequelize.sync({ force: true }); - }); - - describe('#create', () => { - describe('on success', () => { - it('should run hooks', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(), - beforeSave = sinon.spy(), - afterSave = sinon.spy(); - - this.User.beforeCreate(beforeHook); - this.User.afterCreate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); - - 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', async function() { - const beforeHook = sinon.spy(), - beforeSave = sinon.spy(), - afterHook = sinon.spy(), - afterSave = sinon.spy(); - - this.User.beforeCreate(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.User.afterCreate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); - - await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - expect(beforeSave).not.to.have.been.called; - expect(afterSave).not.to.have.been.called; - }); - - it('should return an error from after', async function() { - const beforeHook = sinon.spy(), - beforeSave = sinon.spy(), - afterHook = sinon.spy(), - afterSave = sinon.spy(); - - - this.User.beforeCreate(beforeHook); - this.User.afterCreate(() => { - afterHook(); - throw new Error('Whoops!'); - }); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); - - await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterSave).not.to.have.been.called; - }); - }); - - it('should not trigger hooks on parent when using N:M association setters', async function() { - const A = this.sequelize.define('A', { - name: Sequelize.STRING - }); - const B = this.sequelize.define('B', { - name: Sequelize.STRING - }); - - let hookCalled = 0; - - A.addHook('afterCreate', async () => { - hookCalled++; - }); - - B.belongsToMany(A, { through: 'a_b' }); - A.belongsToMany(B, { through: 'a_b' }); - - 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', async function() { - let hookCalled = 0; - - this.User.beforeValidate(user => { - user.mood = 'happy'; - hookCalled++; - }); - - 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', async function() { - let hookCalled = 0; - - this.User.afterValidate(user => { - user.mood = 'neutral'; - hookCalled++; - }); - - 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', async function() { - let hookCalled = 0; - - this.User.beforeCreate(user => { - user.mood = 'happy'; - hookCalled++; - }); - - 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', async function() { - let hookCalled = 0; - - this.User.beforeSave(user => { - user.mood = 'happy'; - hookCalled++; - }); - - 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', async function() { - let hookCalled = 0; - - this.User.beforeCreate(user => { - user.mood = 'sad'; - hookCalled++; - }); - - this.User.beforeSave(user => { - user.mood = 'happy'; - hookCalled++; - }); - - 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 deleted file mode 100644 index 6dfa3e7f61c3..000000000000 --- a/test/integration/hooks/destroy.test.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - await this.sequelize.sync({ force: true }); - }); - - describe('#destroy', () => { - describe('on success', () => { - it('should run hooks', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeDestroy(beforeHook); - this.User.afterDestroy(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeDestroy(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.User.afterDestroy(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeDestroy(beforeHook); - this.User.afterDestroy(() => { - afterHook(); - throw new Error('Whoops!'); - }); - - 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; - }); - }); - - describe('with paranoid mode enabled', () => { - beforeEach(function() { - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - updatedBy: DataTypes.INTEGER, - virtualField: { - type: DataTypes.VIRTUAL(DataTypes.INTEGER, ['updatedBy']), - get() { - return this.updatedBy - 1; - } - } - }, { - paranoid: true, - hooks: { - beforeDestroy: instance => instance.updatedBy = 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', async function() { - this.ParanoidUser.beforeDestroy(instance => instance.virtualField = 2); - - 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 deleted file mode 100644 index e0e87a68dfc3..000000000000 --- a/test/integration/hooks/find.test.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#find', () => { - beforeEach(async function() { - await this.User.bulkCreate([ - { username: 'adam', mood: 'happy' }, - { username: 'joe', mood: 'sad' } - ]); - }); - - it('allow changing attributes via beforeFind #5675', async function() { - this.User.beforeFind(options => { - options.attributes = { - include: [['id', 'my_id']] - }; - }); - await this.User.findAll({}); - }); - - describe('on success', () => { - it('all hooks run', async function() { - let beforeHook = false, - beforeHook2 = false, - beforeHook3 = false, - afterHook = false; - - this.User.beforeFind(() => { - beforeHook = true; - }); - - this.User.beforeFindAfterExpandIncludeAll(() => { - beforeHook2 = true; - }); - - this.User.beforeFindAfterOptions(() => { - beforeHook3 = true; - }); - - this.User.afterFind(() => { - afterHook = 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', async function() { - this.User.beforeFind(options => { - options.where.username = 'joe'; - }); - - const user = await this.User.findOne({ where: { username: 'adam' } }); - expect(user.mood).to.equal('sad'); - }); - - it('beforeFindAfterExpandIncludeAll hook can change options', async function() { - this.User.beforeFindAfterExpandIncludeAll(options => { - options.where.username = 'joe'; - }); - - const user = await this.User.findOne({ where: { username: 'adam' } }); - expect(user.mood).to.equal('sad'); - }); - - it('beforeFindAfterOptions hook can change options', async function() { - this.User.beforeFindAfterOptions(options => { - options.where.username = 'joe'; - }); - - const user = await this.User.findOne({ where: { username: 'adam' } }); - expect(user.mood).to.equal('sad'); - }); - - it('afterFind hook can change results', async function() { - this.User.afterFind(user => { - user.mood = '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', async function() { - this.User.beforeFind(() => { - throw new Error('Oops!'); - }); - - try { - await this.User.findOne({ where: { username: 'adam' } }); - } catch (err) { - expect(err.message).to.equal('Oops!'); - } - }); - - it('in beforeFindAfterExpandIncludeAll hook returns error', async function() { - this.User.beforeFindAfterExpandIncludeAll(() => { - throw new Error('Oops!'); - }); - - try { - await this.User.findOne({ where: { username: 'adam' } }); - } catch (err) { - expect(err.message).to.equal('Oops!'); - } - }); - - it('in beforeFindAfterOptions hook returns error', async function() { - this.User.beforeFindAfterOptions(() => { - throw new Error('Oops!'); - }); - - try { - await this.User.findOne({ where: { username: 'adam' } }); - } catch (err) { - expect(err.message).to.equal('Oops!'); - } - }); - - it('in afterFind hook returns error', async function() { - this.User.afterFind(() => { - throw new Error('Oops!'); - }); - - 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 deleted file mode 100644 index 5314bcc5687a..000000000000 --- a/test/integration/hooks/hooks.test.js +++ /dev/null @@ -1,406 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = Support.Sequelize, - dialect = Support.getTestDialect(), - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }, { - paranoid: true - }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#define', () => { - before(function() { - this.sequelize.addHook('beforeDefine', (attributes, options) => { - options.modelName = 'bar'; - options.name.plural = 'barrs'; - attributes.type = DataTypes.STRING; - }); - - this.sequelize.addHook('afterDefine', factory => { - factory.options.name.singular = 'barr'; - }); - - this.model = this.sequelize.define('foo', { name: DataTypes.STRING }); - }); - - it('beforeDefine hook can change model name', function() { - expect(this.model.name).to.equal('bar'); - }); - - it('beforeDefine hook can alter options', function() { - expect(this.model.options.name.plural).to.equal('barrs'); - }); - - it('beforeDefine hook can alter attributes', function() { - expect(this.model.rawAttributes.type).to.be.ok; - }); - - it('afterDefine hook can alter options', function() { - expect(this.model.options.name.singular).to.equal('barr'); - }); - - after(function() { - this.sequelize.options.hooks = {}; - this.sequelize.modelManager.removeModel(this.model); - }); - }); - - describe('#init', () => { - before(function() { - Sequelize.addHook('beforeInit', (config, options) => { - config.database = 'db2'; - options.host = 'server9'; - }); - - Sequelize.addHook('afterInit', sequelize => { - sequelize.options.protocol = 'udp'; - }); - - this.seq = new Sequelize('db', 'user', 'pass', { dialect }); - }); - - it('beforeInit hook can alter config', function() { - expect(this.seq.config.database).to.equal('db2'); - }); - - it('beforeInit hook can alter options', function() { - expect(this.seq.options.host).to.equal('server9'); - }); - - it('afterInit hook can alter options', function() { - expect(this.seq.options.protocol).to.equal('udp'); - }); - - after(() => { - Sequelize.options.hooks = {}; - }); - }); - - describe('passing DAO instances', () => { - describe('beforeValidate / afterValidate', () => { - 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: { - async beforeValidate(user) { - expect(user).to.be.instanceof(User); - beforeHooked = true; - }, - async afterValidate(user) { - expect(user).to.be.instanceof(User); - afterHooked = 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', async function() { - let beforeHooked = false; - let afterHooked = false; - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - hooks: { - async beforeCreate(user) { - expect(user).to.be.instanceof(User); - beforeHooked = true; - }, - async afterCreate(user) { - expect(user).to.be.instanceof(User); - afterHooked = 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', async function() { - let beforeHooked = false; - let afterHooked = false; - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - hooks: { - async beforeDestroy(user) { - expect(user).to.be.instanceof(User); - beforeHooked = true; - }, - async afterDestroy(user) { - expect(user).to.be.instanceof(User); - afterHooked = 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', async function() { - let beforeHooked = false; - let afterHooked = false; - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - hooks: { - async beforeUpdate(user) { - expect(user).to.be.instanceof(User); - beforeHooked = true; - }, - async afterUpdate(user) { - expect(user).to.be.instanceof(User); - afterHooked = 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeSync(beforeHook); - this.User.afterSync(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeSync(beforeHook); - this.User.afterSync(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeSync(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.User.afterSync(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeSync(beforeHook); - this.User.afterSync(() => { - afterHook(); - throw new Error('Whoops!'); - }); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(), - modelBeforeHook = sinon.spy(), - modelAfterHook = sinon.spy(); - - this.sequelize.beforeBulkSync(beforeHook); - this.User.beforeSync(modelBeforeHook); - this.User.afterSync(modelAfterHook); - this.sequelize.afterBulkSync(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(), - modelBeforeHook = sinon.spy(), - modelAfterHook = sinon.spy(); - - this.sequelize.beforeBulkSync(beforeHook); - this.User.beforeSync(modelBeforeHook); - this.User.afterSync(modelAfterHook); - this.sequelize.afterBulkSync(afterHook); - - await this.sequelize.sync({ hooks: false }); - expect(beforeHook).to.not.have.been.called; - expect(modelBeforeHook).to.not.have.been.called; - expect(modelAfterHook).to.not.have.been.called; - expect(afterHook).to.not.have.been.called; - }); - - afterEach(function() { - this.sequelize.options.hooks = {}; - }); - - }); - - describe('on error', () => { - - it('should return an error from before', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - this.sequelize.beforeBulkSync(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.sequelize.afterBulkSync(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.sequelize.beforeBulkSync(beforeHook); - this.sequelize.afterBulkSync(() => { - afterHook(); - throw new Error('Whoops!'); - }); - - await expect(this.sequelize.sync()).to.be.rejected; - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - - afterEach(function() { - this.sequelize.options.hooks = {}; - }); - - }); - }); - - describe('#removal', () => { - 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); - - 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', async function() { - const sasukeHook = sinon.spy(), - narutoHook = sinon.spy(); - - this.User.addHook('beforeCreate', sasukeHook); - this.User.addHook('beforeCreate', narutoHook); - - await this.User.create({ username: 'makunouchi' }); - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeCreate', sasukeHook); - await this.User.create({ username: 'sendo' }); - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); - - it('should be able to remove proxies', async function() { - const sasukeHook = sinon.spy(), - narutoHook = sinon.spy(); - - this.User.addHook('beforeSave', sasukeHook); - this.User.addHook('beforeSave', narutoHook); - - const user = await this.User.create({ username: 'makunouchi' }); - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeSave', sasukeHook); - await user.update({ username: 'sendo' }); - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); - }); -}); diff --git a/test/integration/hooks/restore.test.js b/test/integration/hooks/restore.test.js deleted file mode 100644 index c1665d774f59..000000000000 --- a/test/integration/hooks/restore.test.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }, { - paranoid: true - }); - - await this.sequelize.sync({ force: true }); - }); - - describe('#restore', () => { - describe('on success', () => { - it('should run hooks', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.ParanoidUser.beforeRestore(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.ParanoidUser.afterRestore(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(() => { - afterHook(); - throw new Error('Whoops!'); - }); - - 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/upsert.test.js b/test/integration/hooks/upsert.test.js deleted file mode 100644 index dd7b2cb0f51c..000000000000 --- a/test/integration/hooks/upsert.test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'); - -if (Support.sequelize.dialect.supports.upserts) { - describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - allowNull: false, - unique: true //Either Primary Key/Unique Keys should be passed to upsert - }, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - await this.sequelize.sync({ force: true }); - }); - - describe('#upsert', () => { - describe('on success', () => { - it('should run hooks', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeUpsert(beforeHook); - this.User.afterUpsert(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeUpsert(() => { - beforeHook(); - throw new Error('Whoops!'); - }); - this.User.afterUpsert(afterHook); - - 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', async function() { - const beforeHook = sinon.spy(), - afterHook = sinon.spy(); - - this.User.beforeUpsert(beforeHook); - this.User.afterUpsert(() => { - afterHook(); - throw new Error('Whoops!'); - }); - - 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', async function() { - let hookCalled = 0; - const valuesOriginal = { mood: 'sad', username: 'leafninja' }; - - this.User.beforeUpsert(values => { - values.mood = 'happy'; - hookCalled++; - }); - - await this.User.upsert(valuesOriginal); - expect(valuesOriginal.mood).to.equal('happy'); - expect(hookCalled).to.equal(1); - }); - }); - }); - }); -} diff --git a/test/integration/include.test.js b/test/integration/include.test.js deleted file mode 100644 index 7b049f9e2124..000000000000 --- a/test/integration/include.test.js +++ /dev/null @@ -1,1082 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../index'), - expect = chai.expect, - Support = require('./support'), - DataTypes = require('../../lib/data-types'), - _ = require('lodash'), - dialect = Support.getTestDialect(), - current = Support.sequelize, - promiseProps = require('p-props'); - -const sortById = function(a, b) { - return a.id < b.id ? -1 : 1; -}; - -describe(Support.getTestDialectTeaser('Include'), () => { - describe('find', () => { - 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' }); - - 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', async function() { - const Company = this.sequelize.define('Company', {}), - User = this.sequelize.define('User', {}), - Employer = User.belongsTo(Company, { as: 'Employer' }); - - 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', 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' }); - - 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', async function() { - const Company = this.sequelize.define('Company', { name: DataTypes.STRING }), - User = this.sequelize.define('User', {}), - Employer = User.belongsTo(Company, { as: 'Employer', foreignKey: 'employerId' }); - - 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', async function() { - const Company = this.sequelize.define('Company', {}), - Person = this.sequelize.define('Person', {}); - - Company.hasOne(Person, { as: 'CEO' }); - - 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', async function() { - const Company = this.sequelize.define('Company', {}), - Person = this.sequelize.define('Person', {}), - CEO = Company.hasOne(Person, { as: 'CEO' }); - - 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', async function() { - const Company = this.sequelize.define('Company', {}), - Person = this.sequelize.define('Person', {}); - - Person.relation = { - Employer: Person.belongsTo(Company, { as: 'employer' }) - }; - - 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', async function() { - const User = this.sequelize.define('user', {}), - Task = this.sequelize.define('task', {}), - Tasks = User.hasMany(Task); - - Task.belongsTo(User); - - 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', async function() { - const User = this.sequelize.define('user', {}), - Task = this.sequelize.define('task', { title: DataTypes.STRING }), - Tasks = User.hasMany(Task); - - Task.belongsTo(User); - - 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', async function() { - const User = this.sequelize.define('user', {}), - Group = this.sequelize.define('group', {}), - Groups = User.belongsToMany(Group, { through: 'UserGroup' }); - - Group.belongsToMany(User, { through: 'UserGroup' }); - - 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', async function() { - const Task = this.sequelize.define('Task', {}), - User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - Task.belongsTo(User); - User.belongsTo(Group); - - 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', async function() { - const Task = this.sequelize.define('Task', {}), - User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - Task.belongsTo(User); - Task.belongsTo(Group); - - 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', async function() { - const Task = this.sequelize.define('Task', {}), - User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - User.hasOne(Task); - Group.hasOne(User); - User.belongsTo(Group); - - 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', async function() { - const Task = this.sequelize.define('Task', {}), - User = this.sequelize.define('User', {}), - Project = this.sequelize.define('Project', {}); - - User.hasMany(Task); - Task.belongsTo(Project); - - await this.sequelize.sync({ force: true }); - await Project.bulkCreate([{ id: 1 }, { id: 2 }]); - - 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', async function() { - const Task = this.sequelize.define('Task', {}), - Worker = this.sequelize.define('Worker', {}), - Project = this.sequelize.define('Project', {}); - - Worker.belongsTo(Project); - Project.hasMany(Worker); - Project.hasMany(Task); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }); - - User.hasMany(Product); - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - - 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' } - ] - }, { - 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', async function() { - const User = this.sequelize.define('User', {}), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - GroupMember = this.sequelize.define('GroupMember', { - - }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - User.hasMany(Product); - Product.belongsTo(User); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - User.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(User); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - 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 } } - ] - }, { - 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', async function() { - const Project = this.sequelize.define('Project', { - title: Sequelize.STRING - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING, - description: Sequelize.TEXT - }); - - Project.hasMany(Task); - Task.belongsTo(Project); - - await this.sequelize.sync({ force: true }); - - await Task.create({ - title: 'FooBar', - Project: { title: 'BarFoo' } - }, { - include: [Project] - }); - - 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', 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 - comment_title: Sequelize.STRING - }); - - Post.hasMany(PostComment); - - 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', async function() { - const Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }); - - Group.belongsToMany(Group, { through: 'groups_outsourcing_companies', as: 'OutsourcingCompanies' }); - - 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', async function() { - const User = this.sequelize.define('user', { - dateField: Sequelize.DATE - }, { timestamps: false }), - Group = this.sequelize.define('group', { - dateField: Sequelize.DATE - }, { timestamps: false }); - - User.belongsToMany(Group, { through: 'group_user' }); - Group.belongsToMany(User, { through: 'group_user' }); - - 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', async function() { - const User = this.sequelize.define('user', { - name: DataTypes.STRING - }), - Group = this.sequelize.define('group', { - name: DataTypes.STRING - }), - UserGroup = this.sequelize.define('user_group', { - vip: DataTypes.INTEGER - }); - - User.hasMany(Group); - Group.belongsTo(User); - User.belongsToMany(Group, { - through: UserGroup, - as: 'Clubs' - }); - Group.belongsToMany(User, { - through: UserGroup, - as: 'Members' - }); - - 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 = async function() { - const User = this.sequelize.define('User', {}), - Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }); - - User.hasOne(Item); - Item.belongsTo(User); - - this.User = User; - this.Item = Item; - - 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(async function() { - await createUsersAndItems.bind(this)(); - }); - - it('should support Sequelize.and()', async function() { - const result = await this.User.findAll({ - include: [ - { model: this.Item, where: Sequelize.and({ test: 'def' }) } - ] - }); - - expect(result.length).to.eql(1); - expect(result[0].Item.test).to.eql('def'); - }); - - it('should support Sequelize.or()', async function() { - await expect(this.User.findAll({ - include: [ - { model: this.Item, where: Sequelize.or({ - test: 'def' - }, { - test: 'abc' - }) } - ] - })).to.eventually.have.length(2); - }); - }); - - describe('findAndCountAll', () => { - it('should include associations to findAndCountAll', async function() { - await createUsersAndItems.bind(this)(); - - 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', async function() { - const Question = this.sequelize.define('Question', {}), - Answer = this.sequelize.define('Answer', {}), - Questionnaire = this.sequelize.define('Questionnaire', {}); - - Question.belongsToMany(Answer, { through: 'question_answer' }); - Answer.belongsToMany(Question, { through: 'question_answer' }); - - Questionnaire.hasMany(Question); - Question.belongsTo(Questionnaire); - - await this.sequelize.sync({ force: true }); - const questionnaire = await Questionnaire.create(); - - await questionnaire.getQuestions({ - include: Answer - }); - }); - }); - - describe('right join', () => { - it('should support getting an include with a right join', async function() { - const User = this.sequelize.define('user', { - name: DataTypes.STRING - }), - Group = this.sequelize.define('group', { - name: DataTypes.STRING - }); - - User.hasMany(Group); - Group.belongsTo(User); - - await this.sequelize.sync({ force: true }); - - await Promise.all([ - User.create({ name: 'User 1' }), - User.create({ name: 'User 2' }), - User.create({ name: 'User 3' }), - Group.create({ name: 'A Group' }) - ]); - - const groups = await Group.findAll({ - include: [{ - model: User, - right: true - }] - }); - - if (current.dialect.supports['RIGHT JOIN']) { - expect(groups.length).to.equal(3); - } else { - expect(groups.length).to.equal(1); - } - }); - - it('should support getting an include through with a right join', async function() { - const User = this.sequelize.define('user', { - name: DataTypes.STRING - }), - Group = this.sequelize.define('group', { - name: DataTypes.STRING - }), - UserGroup = this.sequelize.define('user_group', { - vip: DataTypes.INTEGER - }); - - User.hasMany(Group); - Group.belongsTo(User); - User.belongsToMany(Group, { - through: UserGroup, - as: 'Clubs', - constraints: false - }); - Group.belongsToMany(User, { - through: UserGroup, - as: 'Members', - constraints: false - }); - - await this.sequelize.sync({ force: true }); - - const [member1, member2, group1, group2] = await Promise.all([ - User.create({ name: 'Member 1' }), - User.create({ name: 'Member 2' }), - Group.create({ name: 'Group 1' }), - Group.create({ name: 'Group 2' }) - ]); - - await Promise.all([ - group1.addMember(member1), - group1.addMember(member2), - group2.addMember(member1) - ]); - - await group2.destroy(); - - const groups = await Group.findAll({ - include: [{ - model: User, - as: 'Members', - right: true - }] - }); - - if (current.dialect.supports['RIGHT JOIN']) { - expect(groups.length).to.equal(2); - } else { - expect(groups.length).to.equal(1); - } - }); - }); - - describe('nested includes', () => { - beforeEach(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 }); - - Team.Members = Team.hasMany(Employee, { as: 'members' }); - Employee.Clearence = Employee.hasOne(Clearence, { as: 'clearence' }); - Clearence.Employee = Clearence.belongsTo(Employee, { as: 'employee' }); - - this.Employee = Employee; - this.Team = Team; - this.Clearence = Clearence; - - 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', async function() { - const teams = await this.Team.findAll({ - include: [ - { - association: this.Team.Members, - required: false, - include: [ - { - association: this.Employee.Clearence, - required: true - } - ] - } - ] - }); - - expect(teams).to.have.length(2); - }); - - it('should support eager loading associations using the name of the relation (string)', async function() { - const team = await this.Team.findOne({ - where: { - name: 'TeamA' - }, - include: [ - { - association: 'members', - required: true - } - ] - }); - - 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)', async function() { - const teams = await this.Team.findAll({ - include: [ - { - association: this.Team.Members, - include: [ - { - association: this.Employee.Clearence, - required: true - } - ] - } - ] - }); - - 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', async function() { - const teams = await this.Team.findAll({ - include: [ - { - association: this.Team.Members, - required: true, - include: [ - { - association: this.Employee.Clearence, - required: true - } - ] - } - ] - }); - - expect(teams).to.have.length(1); - }); - - }); -}); diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js deleted file mode 100644 index bdbc58178f21..000000000000 --- a/test/integration/include/findAll.test.js +++ /dev/null @@ -1,2087 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - _ = require('lodash'), - promiseProps = require('p-props'); - -const sortById = function(a, b) { - return a.id < b.id ? -1 : 1; -}; - -describe(Support.getTestDialectTeaser('Include'), () => { - describe('findAll', () => { - beforeEach(function() { - this.fixtureA = async function() { - const User = this.sequelize.define('User', {}), - Company = this.sequelize.define('Company', { - name: DataTypes.STRING - }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }), - Customer = this.sequelize.define('Customer', { - name: DataTypes.STRING - }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - GroupMember = this.sequelize.define('GroupMember', { - - }), - 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 - } - }); - - this.models = { - User, - Company, - Product, - Tag, - Price, - Customer, - Group, - GroupMember, - Rank - }; - - User.hasMany(Product); - Product.belongsTo(User); - - 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); - - User.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(User); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - SubscriptionForm = this.sequelize.define('SubscriptionForm', {}), - Collection = this.sequelize.define('Collection', {}), - Category = this.sequelize.define('Category', {}), - SubCategory = this.sequelize.define('SubCategory', {}), - Capital = this.sequelize.define('Capital', {}); - - User.hasOne(SubscriptionForm, { foreignKey: 'boundUser' }); - SubscriptionForm.belongsTo(User, { foreignKey: 'boundUser' }); - - SubscriptionForm.hasOne(Collection, { foreignKey: 'boundDesigner' }); - Collection.belongsTo(SubscriptionForm, { foreignKey: 'boundDesigner' }); - - SubscriptionForm.belongsTo(Category, { foreignKey: 'boundCategory' }); - Category.hasMany(SubscriptionForm, { foreignKey: 'boundCategory' }); - - Capital.hasMany(Category, { foreignKey: 'boundCapital' }); - Category.belongsTo(Capital, { foreignKey: 'boundCapital' }); - - Category.hasMany(SubCategory, { foreignKey: 'boundCategory' }); - SubCategory.belongsTo(Category, { foreignKey: 'boundCategory' }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - ProductTag = this.sequelize.define('ProductTag', { - priority: DataTypes.INTEGER - }), - Set = this.sequelize.define('Set', { - title: DataTypes.STRING - }); - - Set.hasMany(Product); - Product.belongsTo(Set); - Product.belongsToMany(Tag, { through: ProductTag }); - Tag.belongsToMany(Product, { through: ProductTag }); - - 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 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', async function() { - const User = this.sequelize.define('User', {}), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - GroupMember = this.sequelize.define('GroupMember', { - - }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - User.hasMany(Product); - Product.belongsTo(User); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - User.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(User); - 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([ - 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', async function() { - const A = this.sequelize.define('a', {}), - B = this.sequelize.define('b', {}), - C = this.sequelize.define('c', {}), - D = this.sequelize.define('d', {}), - E = this.sequelize.define('e', {}), - F = this.sequelize.define('f', {}), - G = this.sequelize.define('g', {}), - H = this.sequelize.define('h', {}); - - A.belongsTo(B); - B.belongsTo(C); - C.belongsTo(D); - D.belongsTo(E); - E.belongsTo(F); - F.belongsTo(G); - G.belongsTo(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 } - ] } - ] } - ] } - ] } - ] } - ] } - ] - }); - - 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)', async function() { - const A = this.sequelize.define('a', {}), - B = this.sequelize.define('b', {}), - C = this.sequelize.define('c', {}), - D = this.sequelize.define('d', {}), - E = this.sequelize.define('e', {}), - F = this.sequelize.define('f', {}), - G = this.sequelize.define('g', { - name: DataTypes.STRING - }), - H = this.sequelize.define('h', { - name: DataTypes.STRING - }); - - A.belongsTo(B); - B.belongsTo(C); - C.belongsTo(D); - D.belongsTo(E); - E.belongsTo(F); - F.belongsTo(G); - G.belongsTo(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 => { - const values = {}; - - if (model.name === 'g') { - values.name = 'yolo'; - } - - promise = (async () => { - await promise; - const instance = await model.create(values); - if (previousInstance) { - await previousInstance[`set${_.upperFirst(model.name)}`](instance); - previousInstance = instance; - return; - } - previousInstance = b = instance; - })(); - }); - - promise = promise.then(() => { - return 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, where: { - name: 'yolo' - }, include: [ - { model: H } - ] } - ] } - ] } - ] } - ] } - ] } - ] - }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }), - Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }); - - User.belongsTo(Item, { 'as': 'itemA', foreignKey: 'itemA_id' }); - User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); - User.belongsTo(Order); - - await this.sequelize.sync(); - - const results = await promiseProps({ - users: User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }), - items: Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' }, - { 'test': 'jkl' } - ]).then(() => { - return Item.findAll({ order: ['id'] }); - }), - orders: Order.bulkCreate([ - { 'position': 2 }, - { 'position': 3 }, - { 'position': 1 } - ]).then(() => { - return Order.findAll({ order: ['id'] }); - }) - }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - ProductTag = this.sequelize.define('ProductTag', { - priority: DataTypes.INTEGER - }); - - Product.belongsToMany(Tag, { through: ProductTag }); - Tag.belongsToMany(Product, { through: ProductTag }); - - 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], { 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - User.belongsTo(Group); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }); - - User.belongsTo(Group); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }); - - User.belongsTo(Group); - - 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', async function() { - const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), - Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), - User = this.sequelize.define('User', { 'username': DataTypes.STRING }); - - // Associate - User.belongsTo(Address, { foreignKey: 'addressId' }); - Address.hasMany(User, { foreignKey: 'addressId' }); - - Address.belongsTo(Street, { foreignKey: 'streetId' }); - Street.hasMany(Address, { foreignKey: 'streetId' }); - - // Sync - 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }); - - User.belongsTo(Group); - Group.hasMany(Category); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }); - - User.belongsTo(Group, { as: 'Team' }); - Group.hasMany(Category, { as: 'Tags' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }); - - User.belongsTo(Group); - Group.hasMany(Category); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Project = this.sequelize.define('Project', { - title: DataTypes.STRING - }); - - User.hasOne(Project, { as: 'LeaderOf' }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - ProductTag = this.sequelize.define('ProductTag', { - priority: DataTypes.INTEGER - }); - - Product.belongsToMany(Tag, { through: ProductTag }); - Tag.belongsToMany(Product, { through: ProductTag }); - - await this.sequelize.sync({ force: true }); - - const results = await promiseProps({ - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]).then(() => { - return Product.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - }); - - await Promise.all([ - results.products[0].addTag(results.tags[0], { priority: 1 }), - results.products[0].addTag(results.tags[1], { priority: 2 }), - results.products[1].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[0], { priority: 3 }), - results.products[2].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[2], { priority: 2 }) - ]); - - const products = await Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }); - - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - - it('should be possible to extend the on clause with a where option on nested includes', async function() { - const User = this.sequelize.define('User', { - name: DataTypes.STRING - }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }), - GroupMember = this.sequelize.define('GroupMember', { - - }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }); - - User.hasMany(Product); - Product.belongsTo(User); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - User.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(User); - 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 = 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', async function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }); - - User.belongsTo(Group); - - 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', 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'] - ] - }); - - 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', async function() { - const Parent = this.sequelize.define('Parent', {}); - const Child1 = this.sequelize.define('Child1', {}); - - Parent.hasMany(Child1); - Child1.belongsTo(Parent); - - 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', 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', 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', 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', async function() { - const Member = this.sequelize.define('Member', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - }, - email: { - type: Sequelize.STRING, - unique: true, - allowNull: false, - validate: { - isEmail: true, - notNull: true, - notEmpty: true - } - }, - password: Sequelize.STRING - }); - const Album = this.sequelize.define('Album', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - }, - title: { - type: Sequelize.STRING(25), - allowNull: false - } - }); - - Album.belongsTo(Member); - Member.hasMany(Album); - - 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 - }); - } - - 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', async function() { - await this.fixtureA(); - - const products = await this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }); - - expect(products.length).to.equal(6); - - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - - 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', 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'] - ] - }); - - expect(products.length).to.equal(10); - - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); - }); - - it('should support including date fields, with the correct timeszone', async function() { - const User = this.sequelize.define('user', { - dateField: Sequelize.DATE - }, { timestamps: false }), - Group = this.sequelize.define('group', { - dateField: Sequelize.DATE - }, { timestamps: false }); - - User.belongsToMany(Group, { through: 'group_user' }); - Group.belongsToMany(User, { through: 'group_user' }); - - 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', 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' }); - - 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', async function() { - const Post = this.sequelize.define('post', { - date: DataTypes.DATE, - 'public': DataTypes.BOOLEAN - }, { - paranoid: true - }); - const Category = this.sequelize.define('category', { - slug: DataTypes.STRING - }); - - Post.hasMany(Category); - Category.belongsTo(Post); - - 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: { - slug: 'food' - } - } - ] - }); - - expect(posts.length).to.equal(2); - }); - - it('should work on a nested set of required 1:1 relations', async function() { - const Person = this.sequelize.define('Person', { - name: { - type: Sequelize.STRING, - allowNull: false - } - }); - - const UserPerson = this.sequelize.define('UserPerson', { - PersonId: { - type: Sequelize.INTEGER, - primaryKey: true - }, - - rank: { - type: Sequelize.STRING - } - }); - - const User = this.sequelize.define('User', { - UserPersonId: { - type: Sequelize.INTEGER, - primaryKey: true - }, - - login: { - type: Sequelize.STRING, - unique: true, - allowNull: false - } - }); - - UserPerson.belongsTo(Person, { - foreignKey: { - allowNull: false - }, - onDelete: 'CASCADE' - }); - Person.hasOne(UserPerson, { - foreignKey: { - allowNull: false - }, - onDelete: 'CASCADE' - }); - - User.belongsTo(UserPerson, { - foreignKey: { - name: 'UserPersonId', - allowNull: false - }, - onDelete: 'CASCADE' - }); - UserPerson.hasOne(User, { - foreignKey: { - name: 'UserPersonId', - allowNull: false - }, - onDelete: 'CASCADE' - }); - - 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: User, - required: true, - attributes: ['login'] - }] - }] - }); - }); - - 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', {}); - - User.belongsTo(Company); - User.belongsToMany(Group, { through: 'UsersGroups' }); - Group.belongsToMany(User, { through: 'UsersGroups' }); - - 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 ', async function() { - const User = this.sequelize.define('User', { - lastName: DataTypes.STRING - }, { tableName: 'dem_users' }); - const Company = this.sequelize.define('Company', { - rank: DataTypes.INTEGER - }, { tableName: 'dem_companies' }); - - User.belongsTo(Company); - Company.hasMany(User); - - 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)', async function() { - const Post = this.sequelize.define('Post', { - title: DataTypes.STRING - }), - Comment = this.sequelize.define('Comment', { - content: DataTypes.TEXT - }); - - Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - - await this.sequelize.sync({ force: true }); - - await Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - - 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 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 - }), - Comment = this.sequelize.define('Comment', { - content: DataTypes.TEXT - }); - - Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - - await this.sequelize.sync({ force: true }); - - await Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - - 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, - primaryKey: true - } - }); - - const Entity = this.sequelize.define('Entity', { - entity_id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - creator: { - type: DataTypes.STRING, - allowNull: false - }, - votes: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0 - } - }); - - const Post = this.sequelize.define('Post', { - post_id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true - } - }); - - const TaggableSentient = this.sequelize.define('TaggableSentient', { - nametag: { - type: DataTypes.STRING, - primaryKey: true - } - }); - - Entity.belongsTo(User, { foreignKey: 'creator', targetKey: 'username' }); - Post.belongsTo(Entity, { foreignKey: 'post_id', targetKey: 'entity_id' }); - - Entity.belongsToMany(TaggableSentient, { - as: 'tags', - through: { model: 'EntityTag', unique: false }, - foreignKey: 'entity_id', - otherKey: 'tag_name' - }); - - TaggableSentient.belongsToMany(Entity, { - as: 'tags', - through: { model: 'EntityTag', unique: false }, - foreignKey: 'tag_name', - otherKey: 'entity_id' - }); - - 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: User, - required: true - }, { - model: TaggableSentient, - as: 'tags', - required: true, - through: { - where: { - tag_name: ['bob'] - } - } - }] - }], - 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', async function() { - const Customer = this.sequelize.define('customer', { - name: DataTypes.STRING - }); - - const ShippingAddress = this.sequelize.define('shippingAddress', { - address: DataTypes.STRING, - verified: DataTypes.BOOLEAN - }); - - const Order = this.sequelize.define('purchaseOrder', { - description: DataTypes.TEXT - }); - - const Shipment = this.sequelize.define('shipment', { - trackingNumber: DataTypes.STRING - }); - - Customer.hasMany(ShippingAddress); - ShippingAddress.belongsTo(Customer); - - Customer.hasMany(Order); - Order.belongsTo(Customer); - - Shipment.belongsTo(Order); - Order.hasOne(Shipment); - - await this.sequelize.sync({ force: true }); - - await Shipment.findOne({ - include: [{ - model: Order, - required: true, - include: [{ - model: Customer, - include: [{ - 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', async function() { - await this.fixtureA(); - - const products = await this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.User }, - { model: this.models.Price } - ], - limit: 10 - }); - - expect(products).to.be.an('array'); - expect(products).to.be.lengthOf(10); - for (const product of products) { - expect(product.title).to.be.a('string'); - // checking that internally added fields used to handle 'BelongsTo' associations are not leaked to result - expect(product.UserId).to.be.equal(undefined); - // checking that included models are on their places - expect(product.User).to.satisfy( User => User === null || User instanceof this.models.User ); - expect(product.Prices).to.be.an('array'); - } - }); - }); -}); diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js deleted file mode 100644 index 22597038719e..000000000000 --- a/test/integration/include/findAndCountAll.test.js +++ /dev/null @@ -1,387 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - Support = require('../support'), - Op = Support.Sequelize.Op, - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Include'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - describe('findAndCountAll', () => { - 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 }); - - Project.hasMany(Task, { foreignKey: 'fk', constraints: false }); - Project.hasMany(Employee, { foreignKey: 'fk', constraints: false }); - - Task.belongsTo(Project, { foreignKey: 'fk', constraints: false }); - Employee.belongsTo(Project, { foreignKey: 'fk', constraints: false }); - - // Sync them - 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', async function() { - const User = this.sequelize.define('User', { name: DataTypes.STRING(40) }, { paranoid: true }), - SomeConnection = this.sequelize.define('SomeConnection', { - m: DataTypes.STRING(40), - fk: DataTypes.INTEGER, - u: DataTypes.INTEGER - }, { paranoid: true }), - 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 }); - - // Associate them - User.hasMany(SomeConnection, { foreignKey: 'u', constraints: false }); - - SomeConnection.belongsTo(User, { foreignKey: 'u', constraints: false }); - SomeConnection.belongsTo(A, { foreignKey: 'fk', constraints: false }); - SomeConnection.belongsTo(B, { foreignKey: 'fk', constraints: false }); - SomeConnection.belongsTo(C, { foreignKey: 'fk', constraints: false }); - - A.hasMany(SomeConnection, { foreignKey: 'fk', constraints: false }); - B.hasMany(SomeConnection, { foreignKey: 'fk', constraints: false }); - C.hasMany(SomeConnection, { foreignKey: 'fk', constraints: false }); - - // Sync them - 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', async function() { - const Project = this.sequelize.define('Project', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - project_name: { type: DataTypes.STRING } - }); - - const User = this.sequelize.define('User', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - user_name: { type: DataTypes.STRING } - }); - - User.hasMany(Project); - - let userId = null; - - 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', async function() { - const s = this.sequelize, - Foo = s.define('Foo', {}), - Bar = s.define('Bar', {}); - - Foo.hasMany(Bar); - Bar.belongsTo(Foo); - - 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', async function() { - const Foo = this.sequelize.define('Foo', {}), - Bar = this.sequelize.define('Bar', { m: DataTypes.STRING(40) }); - - Foo.hasMany(Bar); - Bar.belongsTo(Foo); - - 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.', async function() { - const TaskTag = this.sequelize.define('TaskTag', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - name: { type: DataTypes.STRING } - }); - - const Tag = this.sequelize.define('Tag', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - name: { type: DataTypes.STRING } - }); - - const Task = this.sequelize.define('Task', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - name: { type: DataTypes.STRING } - }); - const Project = this.sequelize.define('Project', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - m: { type: DataTypes.STRING } - }); - - const User = this.sequelize.define('User', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - name: { type: DataTypes.STRING } - }); - - Project.belongsTo(User); - Task.belongsTo(Project); - Task.belongsToMany(Tag, { through: TaskTag }); - - // Sync them - 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', async function() { - const sequelize = this.sequelize; - const User = this.sequelize.define('User', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - first_name: { type: DataTypes.STRING }, - last_name: { type: DataTypes.STRING } - }); - - const Project = this.sequelize.define('Project', { - id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, - name: { type: DataTypes.STRING } - }); - - User.hasMany(Project); - - 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 deleted file mode 100644 index 7ea0dcb18cdd..000000000000 --- a/test/integration/include/findOne.test.js +++ /dev/null @@ -1,361 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'), - 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', 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 - A.hasMany(B); - - B.belongsTo(D); - B.belongsToMany(C, { - through: 'BC' - }); - - C.belongsToMany(B, { - through: 'BC' - }); - - D.hasMany(B); - - 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', async function() { - const Model = this.sequelize.define('Model', {}); - const Model2 = this.sequelize.define('Model2', {}); - const Model4 = this.sequelize.define('Model4', { something: { type: DataTypes.INTEGER } }); - - Model.belongsTo(Model2); - Model2.hasMany(Model); - - Model2.hasMany(Model4); - Model4.belongsTo(Model2); - - 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', async function() { - const User = this.sequelize.define('User', {}, { paranoid: false }), - Task = this.sequelize.define('Task', { - deletedAt: { - type: DataTypes.DATE - } - }, { paranoid: false }); - - User.hasMany(Task, { foreignKey: 'userId' }); - Task.belongsTo(User, { foreignKey: 'userId' }); - - await this.sequelize.sync({ - force: true - }); - - 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', async function() { - const User = this.sequelize.define('User', { - id: { - type: DataTypes.UUID, - defaultValue: Sequelize.UUIDV4, - field: 'main_id', - primaryKey: true - } - }), - Task = this.sequelize.define('Task', { - searchString: { type: DataTypes.STRING } - }); - - User.hasMany(Task, { foreignKey: 'userId' }); - Task.belongsTo(User, { foreignKey: 'userId' }); - - await this.sequelize.sync({ - force: true - }); - - 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', async function() { - const A = this.sequelize.define('a', {}), - B = this.sequelize.define('b', {}), - AB = this.sequelize.define('a_b', { - name: { - type: DataTypes.STRING(40), - field: 'name_id', - primaryKey: true - } - }); - - A.belongsToMany(B, { through: AB }); - B.belongsToMany(A, { through: AB }); - - 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', 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' }); - - 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)', async function() { - const A = this.sequelize.define('A', { - name: DataTypes.STRING - }); - - const B = this.sequelize.define('B', { - flag: DataTypes.BOOLEAN - }); - - const C = this.sequelize.define('C', { - name: DataTypes.STRING - }); - - A.hasOne(B); - B.belongsTo(A); - - B.hasMany(C); - C.belongsTo(B); - - await this.sequelize - .sync({ force: true }); - - const a = await A.findOne({ - include: [ - { - model: B, - where: { flag: true }, - include: [ - { - model: C - } - ] - } - ] - }); - - expect(a).to.not.exist; - }); - - 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' }); - - 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)', async function() { - const A = this.sequelize.define('a', {}), - B = this.sequelize.define('b', {}), - C = this.sequelize.define('c', {}), - D = this.sequelize.define('d', {}), - E = this.sequelize.define('e', {}), - F = this.sequelize.define('f', {}), - G = this.sequelize.define('g', { - name: DataTypes.STRING - }), - H = this.sequelize.define('h', { - name: DataTypes.STRING - }); - - A.belongsTo(B); - B.belongsTo(C); - C.belongsTo(D); - D.belongsTo(E); - E.belongsTo(F); - F.belongsTo(G); - G.belongsTo(H); - - await this.sequelize.sync({ force: true }); - - const [a0, b] = await Promise.all([A.create({}), (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - const values = {}; - - if (model.name === 'g') { - values.name = 'yolo'; - } - - promise = (async () => { - await promise; - const instance = await model.create(values); - if (previousInstance) { - await previousInstance[`set${_.upperFirst(model.name)}`](instance); - previousInstance = instance; - return; - } - previousInstance = b = instance; - })(); - }); - - promise = promise.then(() => { - return 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 } - ] } - ] } - ] } - ] } - ] } - ] } - ] - }); - - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - - 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 - }, { underscored: true }); - - const Post = this.sequelize.define('Post', { - id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true }, - owner_id: { type: DataTypes.INTEGER, unique: 'combiIndex' }, - owner_type: { type: DataTypes.ENUM, values: ['user', 'org'], defaultValue: 'user', unique: 'combiIndex' }, - 'private': { type: DataTypes.BOOLEAN, defaultValue: false } - }, { underscored: true }); - - User.hasMany(Post, { foreignKey: 'owner_id', scope: { owner_type: 'user' }, as: 'UserPosts', constraints: false }); - Post.belongsTo(User, { foreignKey: 'owner_id', as: 'Owner', constraints: false }); - - 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 deleted file mode 100644 index 9ff45e10009a..000000000000 --- a/test/integration/include/limit.test.js +++ /dev/null @@ -1,745 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Op = Sequelize.Op; - -describe(Support.getTestDialectTeaser('Include'), () => { - - describe('LIMIT', () => { - /* - * shortcut for building simple {name: 'foo'} seed data - */ - function build(...args) { - return args.map(arg => ({ name: arg })); - } - - /* - * association overview - * [Task]N---N[Project]N---N[User]N---N[Hobby] - * 1 - * | - * | - * | - * N - * [Comment]N---1[Post]N---N[Tag]N---1[Color] - * 1 - * | - * | - * | - * N - * [Footnote] - */ - beforeEach(function() { - this.Project = this.sequelize.define('Project', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.User = this.sequelize.define('User', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Task = this.sequelize.define('Task', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Hobby = this.sequelize.define('Hobby', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.User.belongsToMany(this.Project, { through: 'user_project' }); - this.Project.belongsToMany(this.User, { through: 'user_project' }); - - this.Project.belongsToMany(this.Task, { through: 'task_project' }); - this.Task.belongsToMany(this.Project, { through: 'task_project' }); - - this.User.belongsToMany(this.Hobby, { through: 'user_hobby' }); - this.Hobby.belongsToMany(this.User, { through: 'user_hobby' }); - - this.Post = this.sequelize.define('Post', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Comment = this.sequelize.define('Comment', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Tag = this.sequelize.define('Tag', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Color = this.sequelize.define('Color', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Footnote = this.sequelize.define('Footnote', { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, { timestamps: false }); - - this.Post.hasMany(this.Comment); - this.Comment.belongsTo(this.Post); - - this.Post.belongsToMany(this.Tag, { through: 'post_tag' }); - this.Tag.belongsToMany(this.Post, { through: 'post_tag' }); - - this.Post.hasMany(this.Footnote); - this.Footnote.belongsTo(this.Post); - - this.User.hasMany(this.Post); - this.Post.belongsTo(this.User); - - this.Tag.belongsTo(this.Color); - this.Color.hasMany(this.Tag); - }); - - /* - * many-to-many - */ - 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.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 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.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 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.Hobby, - required: true, - through: { - where: { - HobbyName: 'archery' - } - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - }); - - 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', 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, - where: { - name: 'archery' - } - }] - }] - }], - 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', 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', 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', 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', 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)', 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', 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.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', 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.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', 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.Post, - required: true, - 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', 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.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', 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.Hobby, - required: true, - through: { - where: { - HobbyName: '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 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.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', 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.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 deleted file mode 100644 index 9f88e8baf1f6..000000000000 --- a/test/integration/include/paranoid.test.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Paranoid'), () => { - - beforeEach(async function() { - const S = this.sequelize, - DT = DataTypes, - - A = this.A = S.define('A', { name: DT.STRING }, { paranoid: true }), - B = this.B = S.define('B', { name: DT.STRING }, { paranoid: true }), - C = this.C = S.define('C', { name: DT.STRING }, { paranoid: true }), - D = this.D = S.define('D', { name: DT.STRING }, { paranoid: true }); - - A.belongsTo(B); - A.belongsToMany(D, { through: 'a_d' }); - A.hasMany(C); - - B.hasMany(A); - B.hasMany(C); - - C.belongsTo(A); - C.belongsTo(B); - - D.belongsToMany(A, { through: 'a_d' }); - - await S.sync({ force: true }); - }); - - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - it('paranoid with timestamps: false should be ignored / not crash', async function() { - const S = this.sequelize, - Test = S.define('Test', { - name: DataTypes.STRING - }, { - timestamps: false, - paranoid: true - }); - - await S.sync({ force: true }); - - await Test.findByPk(1); - }); - - it('test if non required is marked as false', async function() { - const A = this.A, - B = this.B, - options = { - include: [ - { - model: B, - required: false - } - ] - }; - - await A.findOne(options); - expect(options.include[0].required).to.be.equal(false); - }); - - it('test if required is marked as true', async function() { - const A = this.A, - B = this.B, - options = { - include: [ - { - model: B, - required: 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', async function() { - const X = this.sequelize.define('x', { - name: DataTypes.STRING - }, { - paranoid: false - }); - - const Y = this.sequelize.define('y', { - name: DataTypes.STRING - }, { - timestamps: true, - paranoid: true - }); - - X.hasMany(Y); - - 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 deleted file mode 100644 index bfffa78f80f0..000000000000 --- a/test/integration/include/schema.test.js +++ /dev/null @@ -1,1224 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - dialect = Support.getTestDialect(), - _ = require('lodash'), - promiseProps = require('p-props'); - -const sortById = function(a, b) { - return a.id < b.id ? -1 : 1; -}; - -describe(Support.getTestDialectTeaser('Includes with schemas'), () => { - describe('findAll', () => { - afterEach(async function() { - await this.sequelize.dropSchema('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 }); - } - - 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 } - ]) - ]); - } - }; - await this.sequelize.createSchema('account'); - }); - - 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', async function() { - const A = this.sequelize.define('a', {}, { schema: 'account' }), - B = this.sequelize.define('b', {}, { schema: 'account' }), - C = this.sequelize.define('c', {}, { schema: 'account' }), - D = this.sequelize.define('d', {}, { schema: 'account' }), - E = this.sequelize.define('e', {}, { schema: 'account' }), - F = this.sequelize.define('f', {}, { schema: 'account' }), - G = this.sequelize.define('g', {}, { schema: 'account' }), - H = this.sequelize.define('h', {}, { schema: 'account' }); - - A.belongsTo(B); - B.belongsTo(C); - C.belongsTo(D); - D.belongsTo(E); - E.belongsTo(F); - F.belongsTo(G); - G.belongsTo(H); - - let b; - const singles = [ - B, - C, - D, - E, - F, - G, - 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 } - ] } - ] } - ] } - ] } - ] } - ] } - ] - }); - 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', 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' }); - - User.belongsTo(Item, { 'as': 'itemA', foreignKey: 'itemA_id' }); - User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); - User.belongsTo(Order); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - ProductTag = this.sequelize.define('ProductTag', { - priority: DataTypes.INTEGER - }, { schema: 'account' }); - - Product.belongsToMany(Tag, { through: ProductTag }); - Tag.belongsToMany(Product, { through: ProductTag }); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', {}, { schema: 'account' }); - - User.belongsTo(Group); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group); - Group.hasMany(Category); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group, { as: 'Team' }); - Group.hasMany(Category, { as: 'Tags' }); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - Category = this.sequelize.define('Category', { - category: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group); - Group.hasMany(Category); - - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Project = this.sequelize.define('Project', { - title: DataTypes.STRING - }, { schema: 'account' }); - - User.hasOne(Project, { as: 'LeaderOf' }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - ProductTag = this.sequelize.define('ProductTag', { - priority: DataTypes.INTEGER - }, { schema: 'account' }); - - Product.belongsToMany(Tag, { through: ProductTag }); - Tag.belongsToMany(Product, { through: ProductTag }); - - await this.sequelize.sync({ force: true }); - - await Promise.all([ - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ]); - - const [products0, tags] = await Promise.all([ - Product.findAll(), - Tag.findAll() - ]); - - await Promise.all([ - products0[0].addTag(tags[0], { priority: 1 }), - products0[0].addTag(tags[1], { priority: 2 }), - products0[1].addTag(tags[1], { priority: 1 }), - products0[2].addTag(tags[0], { priority: 3 }), - products0[2].addTag(tags[1], { priority: 1 }), - products0[2].addTag(tags[2], { priority: 2 }) - ]); - - const products = await Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }); - - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - - it('should be possible to extend the on clause with a where option on nested includes', async function() { - const User = this.sequelize.define('User', { - 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' }), - 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' }); - - User.hasMany(Product); - Product.belongsTo(User); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - User.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(User); - 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([ - 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 } - ]), - 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'] - ] - }); - 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', async function() { - const User = this.sequelize.define('User', {}, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }); - - User.belongsTo(Group); - - 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', 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', async function() { - await this.fixtureA(); - - const products = await this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }); - - expect(products.length).to.equal(6); - - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - - 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', 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'] - ] - }); - - expect(products.length).to.equal(10); - - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); - }); - - it('should support including date fields, with the correct timezone', async function() { - const User = this.sequelize.define('user', { - dateField: Sequelize.DATE - }, { timestamps: false, schema: 'account' }), - Group = this.sequelize.define('group', { - dateField: Sequelize.DATE - }, { timestamps: false, schema: 'account' }); - - User.belongsToMany(Group, { through: 'group_user' }); - Group.belongsToMany(User, { through: 'group_user' }); - - 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', async function() { - const UserModel = this.sequelize.define('User', { - Id: { - type: DataTypes.INTEGER, - primaryKey: true - }, - Name: DataTypes.STRING, - UserType: DataTypes.INTEGER, - Email: DataTypes.STRING, - PasswordHash: DataTypes.STRING, - Enabled: { - type: DataTypes.BOOLEAN - }, - CreatedDatetime: DataTypes.DATE, - UpdatedDatetime: DataTypes.DATE - }, { - schema: 'hero', - tableName: 'User', - timestamps: false - }); - - const UserIdColumn = { type: Sequelize.INTEGER, references: { model: UserModel, key: 'Id' } }; - - const ResumeModel = this.sequelize.define('Resume', { - Id: { - type: Sequelize.INTEGER, - primaryKey: true - }, - UserId: UserIdColumn, - Name: Sequelize.STRING, - Contact: Sequelize.STRING, - School: Sequelize.STRING, - WorkingAge: Sequelize.STRING, - Description: Sequelize.STRING, - PostType: Sequelize.INTEGER, - RefreshDatetime: Sequelize.DATE, - CreatedDatetime: Sequelize.DATE - }, { - schema: 'hero', - tableName: 'resume', - timestamps: false - }); - - UserModel.hasOne(ResumeModel, { - foreignKey: 'UserId', - as: 'Resume' - }); - - ResumeModel.belongsTo(UserModel, { - foreignKey: 'UserId' - }); - - 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 deleted file mode 100644 index aea667455c47..000000000000 --- a/test/integration/include/separate.test.js +++ /dev/null @@ -1,515 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize, - dialect = Support.getTestDialect(); - -if (current.dialect.supports.groupedLimit) { - describe(Support.getTestDialectTeaser('Include'), () => { - describe('separate', () => { - 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' }); - - 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', async function() { - const User = this.sequelize.define('User', { - name: DataTypes.STRING - }), - Task = this.sequelize.define('Task', {}), - sqlSpy = sinon.spy(); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - - 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', async function() { - const User = this.sequelize.define('User', { - name: DataTypes.STRING, - userExtraId: { - type: DataTypes.INTEGER, - unique: true - } - }); - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING - }); - const sqlSpy = sinon.spy(); - - User.Tasks = User.hasMany(Task, { - as: 'tasks', - foreignKey: 'userId', - sourceKey: 'userExtraId' - }); - - 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' - ], - 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', async function() { - const User = this.sequelize.define('User', {}), - Team = this.sequelize.define('Team', {}), - Company = this.sequelize.define('Company', {}); - - User.Team = User.belongsTo(Team); - Team.Company = Team.belongsTo(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', async function() { - const User = this.sequelize.define('User', {}), - Task = this.sequelize.define('Task', { - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - } - }), - sqlSpy = sinon.spy(); - - User.Tasks = User.hasMany(Task, { as: 'tasks', foreignKey: 'userId' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Company = this.sequelize.define('Company'), - Task = this.sequelize.define('Task', {}), - sqlSpy = sinon.spy(); - - User.Company = User.belongsTo(Company, { as: 'company' }); - Company.Tasks = Company.hasMany(Task, { as: 'tasks' }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Project = this.sequelize.define('Project'), - Company = this.sequelize.define('Company'), - Task = this.sequelize.define('Task', {}), - sqlSpy = sinon.spy(); - - Company.Users = Company.hasMany(User, { as: 'users' }); - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - Task.Project = Task.belongsTo(Project, { as: 'project' }); - - await this.sequelize.sync({ force: true }); - - 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', async function() { - const User = this.sequelize.define('User', {}), - Project = this.sequelize.define('Project', {}), - Task = this.sequelize.define('Task', {}), - sqlSpy = sinon.spy(); - - User.Projects = User.hasMany(Project, { as: 'projects' }); - Project.Tasks = Project.hasMany(Task, { as: 'tasks' }); - - await this.sequelize.sync({ force: true }); - - await Promise.all([User.create({ - id: 1, - projects: [ - { - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, - { - id: 2, - tasks: [ - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - }), User.create({ - id: 2, - projects: [ - { - id: 3, - tasks: [ - {}, - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - })]); - - const users = await User.findAll({ - include: [ - { association: User.Projects, separate: true, include: [ - { association: Project.Tasks, separate: true } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - - const u1projects = users[0].get('projects'); - - expect(u1projects).to.be.ok; - expect(u1projects[0].get('tasks')).to.be.ok; - expect(u1projects[1].get('tasks')).to.be.ok; - expect(u1projects.length).to.equal(2); - - // WTB ES2015 syntax ... - expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3); - expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1); - - expect(users[1].get('projects')).to.be.ok; - expect(users[1].get('projects')[0].get('tasks')).to.be.ok; - expect(users[1].get('projects').length).to.equal(1); - expect(users[1].get('projects')[0].get('tasks').length).to.equal(2); - - expect(sqlSpy).to.have.been.calledThrice; - }); - - it('should work with two schema models in a hasMany association', async function() { - const User = this.sequelize.define('User', {}, { schema: 'archive' }), - Task = this.sequelize.define('Task', { - id: { type: DataTypes.INTEGER, primaryKey: true }, - title: DataTypes.STRING - }, { schema: 'archive' }); - - User.Tasks = User.hasMany(Task, { as: 'tasks' }); - - 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 deleted file mode 100644 index d7d16c7dba4b..000000000000 --- a/test/integration/instance.test.js +++ /dev/null @@ -1,692 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - DataTypes = require('../../lib/data-types'), - dialect = Support.getTestDialect(), - sinon = require('sinon'), - isUUID = require('validator').isUUID; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - }, - - isSuperUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - await this.User.sync({ force: true }); - }); - - describe('Escaping', () => { - 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 - - const u1 = await this.User.create({ username: bio }); - const u2 = await this.User.findByPk(u1.id); - expect(u2.username).to.equal(bio); - }); - }); - - describe('isNewRecord', () => { - it('returns true for non-saved objects', function() { - const user = this.User.build({ username: 'user' }); - expect(user.id).to.be.null; - expect(user.isNewRecord).to.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', async function() { - const user = await this.User.create({ username: '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', async function() { - const users = []; - - for (let i = 0; i < 10; i++) { - users[i] = { username: 'user' }; - } - - await this.User.bulkCreate(users); - const users0 = await this.User.findAll(); - users0.forEach(u => { - expect(u.isNewRecord).to.not.be.ok; - }); - }); - }); - - describe('default values', () => { - describe('uuid', () => { - it('should store a string in uuidv1 and uuidv4', function() { - const user = this.User.build({ username: 'a user' }); - expect(user.uuidv1).to.be.a('string'); - expect(user.uuidv4).to.be.a('string'); - }); - - it('should store a string of length 36 in uuidv1 and uuidv4', function() { - const user = this.User.build({ username: 'a user' }); - expect(user.uuidv1).to.have.length(36); - expect(user.uuidv4).to.have.length(36); - }); - - it('should store a valid uuid in uuidv1 and uuidv4 that conforms to the UUID v1 and v4 specifications', function() { - const user = this.User.build({ username: 'a user' }); - expect(isUUID(user.uuidv1)).to.be.true; - expect(isUUID(user.uuidv4, 4)).to.be.true; - }); - - it('should store a valid uuid if the multiple primary key fields used', function() { - const Person = this.sequelize.define('Person', { - id1: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV1, - primaryKey: true - }, - id2: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV1, - primaryKey: true - } - }); - - const person = Person.build({}); - expect(person.id1).to.be.ok; - expect(person.id1).to.have.length(36); - - expect(person.id2).to.be.ok; - expect(person.id2).to.have.length(36); - }); - }); - describe('current date', () => { - it('should store a date in touchedAt', function() { - const user = this.User.build({ username: 'a user' }); - expect(user.touchedAt).to.be.instanceof(Date); - }); - - it('should store the current date in touchedAt', function() { - const clock = sinon.useFakeTimers(); - clock.tick(5000); - const user = this.User.build({ username: 'a user' }); - clock.restore(); - expect(+user.touchedAt).to.be.equal(5000); - }); - }); - - describe('allowNull date', () => { - it('should be just "null" and not Date with Invalid Date', 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', async function() { - const date = new Date(); - 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', async function() { - await this.User.build({ - username: 'a user' - }) - .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', async function() { - await this.User.build({ - username: 'a user', - isSuperUser: 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")', async function() { - await this.User.build({ - username: 'a user', - isSuperUser: '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)', async function() { - await this.User.build({ - username: 'a user', - isSuperUser: 1 - }) - .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', async function() { - let callCount = 0; - - try { - await this.User.build({ - username: 'a user', - isSuperUser: 'INCORRECT_VALUE_TYPE' - }) - .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', 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', async function() { - const result = await this.User.count(); - expect(result).to.exist; - }); - }); - - describe('findAll', () => { - beforeEach(async function() { - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: { type: DataTypes.STRING } - }, { paranoid: true }); - - this.ParanoidUser.hasOne(this.ParanoidUser); - await this.ParanoidUser.sync({ force: true }); - }); - - 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', async function() { - await this.ParanoidUser.create({ username: 'cuss' }); - - const users0 = await 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' - }) - }); - - expect(users).to.have.length(0); - }); - - 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' - }) - }); - - expect(users0).to.have.length(1); - await users0[0].destroy(); - - const users = await this.ParanoidUser.findAll({ - where: this.sequelize.or({ - username: 'cuss' - }) - }); - - expect(users).to.have.length(0); - }); - - it('escapes a single 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('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('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('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, - primaryKey: true - }, - email: DataTypes.STRING - }); - - 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', 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; - - 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', 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', 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', 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', 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 }, - bool_value2: { type: DataTypes.BOOLEAN, allowNull: true }, - bool_value3: { type: DataTypes.BOOLEAN, allowNull: true } - }, { timestamps: false, logging: false }); - - 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', 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', async function() { - this.UserAssociationEqual = this.sequelize.define('UserAssociationEquals', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }, { timestamps: false }); - - this.ProjectAssociationEqual = this.sequelize.define('ProjectAssocationEquals', { - title: DataTypes.STRING, - overdue_days: DataTypes.INTEGER - }, { timestamps: false }); - - this.UserAssociationEqual.hasMany(this.ProjectAssociationEqual, { as: 'Projects', foreignKey: 'userId' }); - this.ProjectAssociationEqual.belongsTo(this.UserAssociationEqual, { as: 'Users', foreignKey: 'userId' }); - - 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', async function() { - const User = this.sequelize.define('UserHelper', { - username: DataTypes.STRING - }, { timestamps: false, logging: false }); - - await User.sync(); - const user = User.build({ username: 'foo' }); - expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); - }); - }); - - describe('isSoftDeleted', () => { - beforeEach(async function() { - this.ParanoidUser = this.sequelize.define('ParanoidUser', { - username: { type: DataTypes.STRING } - }, { paranoid: true }); - - await this.ParanoidUser.sync({ force: true }); - }); - - 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', 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', 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; - - const user = await users[0].reload({ paranoid: false }); - expect(user.isSoftDeleted()).to.be.true; - }); - - it('works with custom `deletedAt` field name', async function() { - this.ParanoidUserWithCustomDeletedAt = this.sequelize.define('ParanoidUserWithCustomDeletedAt', { - username: { type: DataTypes.STRING } - }, { - deletedAt: 'deletedAtThisTime', - paranoid: true - }); - - this.ParanoidUserWithCustomDeletedAt.hasOne(this.ParanoidUser); - - 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; - - await users[0].destroy(); - expect(users[0].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', 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: { type: DataTypes.INTEGER, defaultValue: 1 } - }, { - paranoid: true - }), - data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '43' }, - { username: 'Bob', secretValue: '44' }]; - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - destroyTime: DataTypes.DATE - }, { paranoid: true, deletedAt: 'destroyTime' }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - deletedAt: { type: DataTypes.DATE, field: 'deleted_at' } - }, { paranoid: true }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - destroyTime: { type: DataTypes.DATE, field: 'destroy_time' } - }, { paranoid: true, deletedAt: 'destroyTime' }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING, - deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) } - }, { paranoid: true }); - - 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 deleted file mode 100644 index 69cf060739eb..000000000000 --- a/test/integration/instance.validations.test.js +++ /dev/null @@ -1,720 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../index'), - Support = require('./support'); - -describe(Support.getTestDialectTeaser('InstanceValidator'), () => { - describe('#update', () => { - it('should allow us to update specific columns without tripping the validations', async function() { - const User = this.sequelize.define('model', { - username: Sequelize.STRING, - email: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isEmail: { - msg: 'You must enter a valid email address' - } - } - } - }); - - 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', async function() { - const Model = this.sequelize.define('model', { - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - notEmpty: true // don't allow empty strings - } - } - }); - - 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', async function() { - const Model = this.sequelize.define('model', { - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - notEmpty: true // don't allow empty strings - } - } - }); - - 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', async function() { - const Model = this.sequelize.define('model', { - uniqueName: { type: Sequelize.STRING, unique: 'uniqueName' } - }); - const records = [ - { uniqueName: 'unique name one' }, - { uniqueName: 'unique name two' } - ]; - 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, - unique: { msg: 'custom unique error message' } - } - }); - const records = [ - { uniqueName: 'unique name one' }, - { uniqueName: 'unique name two' } - ]; - 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, - unique: { msg: 'custom unique error message 1' } - }, - uniqueName2: { - type: Sequelize.STRING, - unique: { msg: 'custom unique error message 2' } - } - }); - const records = [ - { uniqueName1: 'unique name one', uniqueName2: 'unique name one' }, - { uniqueName1: 'unique name one', uniqueName2: 'this is ok' }, - { uniqueName1: 'this is ok', uniqueName2: 'unique name one' } - ]; - 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(async function() { - const Project = this.sequelize.define('Project', { - name: { - type: Sequelize.STRING, - allowNull: false, - defaultValue: 'unknown', - validate: { - isIn: [['unknown', 'hello', 'test']] - } - } - }); - - const Task = this.sequelize.define('Task', { - something: Sequelize.INTEGER - }); - - Project.hasOne(Task); - Task.belongsTo(Project); - - await this.sequelize.sync({ force: true }); - this.Project = Project; - this.Task = Task; - }); - - 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 ', 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', async function() { - const User = this.sequelize.define('UserId', { - id: { - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true, - validate: { - isInt: true - } - } - }); - - 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)', async function() { - const User = this.sequelize.define('UserId', { - username: { - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true, - validate: { - isInt: { args: true, msg: '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(async function() { - this.User = this.sequelize.define('UserId', { - id: { - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true, - validate: { - isInt: { args: true, msg: 'ID must be an integer!' } - } - } - }); - - 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', 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()', async function() { - const user = this.User.build({ id: 'helloworld' }); - - 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()', async function() { - const user = this.User.build({ id: 'helloworld' }); - - try { - await user.save(); - } catch (err) { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - } - }); - }); - }); - - describe('pass all paths when validating', () => { - beforeEach(async function() { - const Project = this.sequelize.define('Project', { - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isIn: [['unknown', 'hello', 'test']] - } - }, - creatorName: { - type: Sequelize.STRING, - allowNull: false - }, - cost: { - type: Sequelize.INTEGER, - allowNull: false - } - - }); - - const Task = this.sequelize.define('Task', { - something: Sequelize.INTEGER - }); - - Project.hasOne(Task); - Task.belongsTo(Project); - - await Project.sync({ force: true }); - await Task.sync({ force: true }); - this.Project = Project; - this.Task = Task; - }); - - 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(async function() { - const Project = this.sequelize.define('Project', { - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isIn: [['unknown', 'hello', 'test']] // important to be - } - } - }); - - await this.sequelize.sync({ force: true }); - this.Project = Project; - }); - - it('correctly throws an error using create method ', async function() { - await this.Project.create({}) - .then(() => { - throw new Error('Validation must be failed'); - }, () => { - // fail is ok - }); - }); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: { - type: Sequelize.STRING, - validate: { - customFn(val, next) { - if (val !== '2') { - next("name should equal '2'"); - } else { - next(); - } - } - } - } - }); - - const failingUser = User.build({ name: '3' }); - - 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' }); - - await expect(successfulUser.validate()).not.to.be.rejected; - }); - - it('supports promises with custom validation methods', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: { - type: Sequelize.STRING, - validate: { - async customFn(val) { - await User.findAll(); - if (val === 'error') { - throw new Error('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'); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - age: { - type: Sequelize.INTEGER, - allowNull: true, - validate: { - min: { args: 0, msg: 'must be positive' } - } - } - }); - - const error = await expect(User - .build({ age: -1 }) - .validate()) - .to.be.rejected; - - expect(error.get('age')[0].message).to.equal('must be positive'); - }); - - 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 - }, - field2: { - type: Sequelize.INTEGER, - allowNull: true - } - }, { - validate: { - xnor() { - if (this.field1 === null === (this.field2 === null)) { - throw new Error('xnor failed'); - } - } - } - }); - - const error = await expect(Foo - .build({ field1: null, field2: null }) - .validate()) - .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', async function() { - const Foo = this.sequelize.define(`Foo${Support.rand()}`, { - bar: { - type: Sequelize.STRING, - validate: { - isIn: [['a', 'b']] - } - } - }), - foo = Foo.build({ bar: 'a' }); - await expect(foo.validate()).not.to.be.rejected; - await expect(foo.validate()).not.to.be.rejected; - }); - - it('validates enums', async function() { - const values = ['value1', 'value2']; - - const Bar = this.sequelize.define(`Bar${Support.rand()}`, { - field: { - type: Sequelize.ENUM, - values, - validate: { - isIn: [values] - } - } - }); - - const failingBar = Bar.build({ field: 'value3' }); - - 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', async function() { - const values = ['value1', 'value2']; - - const Bar = this.sequelize.define(`Bar${Support.rand()}`, { - field: { - type: Sequelize.ENUM, - values, - validate: { - isIn: [values] - } - } - }); - - const failingBar = Bar.build({ field: 'value3' }); - - await expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; - }); - - it('skips validations for fields with value that is SequelizeMethod', async function() { - const values = ['value1', 'value2']; - - const Bar = this.sequelize.define(`Bar${Support.rand()}`, { - field: { - type: Sequelize.ENUM, - values, - validate: { - isIn: [values] - } - } - }); - - const failingBar = Bar.build({ field: this.sequelize.literal('5 + 1') }); - - await expect(failingBar.validate()).not.to.be.rejected; - }); - - 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, - validate: { - isImmutable: true - } - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - name: { - type: Sequelize.STRING, - validate: { - isImmutable: true - } - } - }); - - const user = User.build({ name: 'RedCat' }); - expect(user.getDataValue('name')).to.equal('RedCat'); - - user.setDataValue('name', 'YellowCat'); - await expect(user.validate()).not.to.be.rejected; - }); - - it('raises an error for array on a STRING', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.STRING - } - }); - - await expect(User.build({ - email: ['iama', 'dummy.com'] - }).validate()).to.be.rejectedWith(Sequelize.ValidationError); - }); - - it('raises an error for array on a STRING(20)', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.STRING(20) - } - }); - - await expect(User.build({ - email: ['iama', 'dummy.com'] - }).validate()).to.be.rejectedWith(Sequelize.ValidationError); - }); - - it('raises an error for array on a TEXT', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.TEXT - } - }); - - await expect(User.build({ - email: ['iama', 'dummy.com'] - }).validate()).to.be.rejectedWith(Sequelize.ValidationError); - }); - - it('raises an error for {} on a STRING', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.STRING - } - }); - - await expect(User.build({ - email: { lol: true } - }).validate()).to.be.rejectedWith(Sequelize.ValidationError); - }); - - it('raises an error for {} on a STRING(20)', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.STRING(20) - } - }); - - await expect(User.build({ - email: { lol: true } - }).validate()).to.be.rejectedWith(Sequelize.ValidationError); - }); - - it('raises an error for {} on a TEXT', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.TEXT - } - }); - - 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)', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: Sequelize.STRING - } - }); - - await expect(User.build({ - email: null - }).validate()).not.to.be.rejected; - }); - - it('validates VIRTUAL fields', async function() { - const User = this.sequelize.define('user', { - password_hash: Sequelize.STRING, - salt: Sequelize.STRING, - password: { - type: Sequelize.VIRTUAL, - set(val) { - this.setDataValue('password', val); - this.setDataValue('password_hash', this.salt + val); - }, - validate: { - isLongEnough(val) { - if (val.length < 7) { - throw new Error('Please choose a longer password'); - } - } - } - } - }); - - await Promise.all([ - expect(User.build({ - password: 'short', - salt: '42' - }).validate()).to.be.rejected.then(errors => { - expect(errors.get('password')[0].message).to.equal('Please choose a longer password'); - }), - expect(User.build({ - password: 'loooooooong', - salt: '42' - }).validate()).not.to.be.rejected - ]); - }); - - it('allows me to add custom validation functions to validator.js', async function() { - this.sequelize.Validator.extend('isExactly7Characters', val => { - return val.length === 7; - }); - - const User = this.sequelize.define('User', { - name: { - type: Sequelize.STRING, - validate: { - isExactly7Characters: true - } - } - }); - - await expect(User.build({ - name: 'abcdefg' - }).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 deleted file mode 100644 index a38397066c35..000000000000 --- a/test/integration/instance/decrement.test.js +++ /dev/null @@ -1,188 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - }, - - isSuperUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - await this.User.sync({ force: true }); - }); - - describe('decrement', () => { - beforeEach(async function() { - await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); - }); - - if (current.dialect.supports.transactions) { - 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', 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', 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', 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', 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', 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', 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', 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', 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', async function() { - const User = this.sequelize.define('IncrementUser', { - aNumber: DataTypes.INTEGER - }, { timestamps: true }); - - 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', async function() { - const User = this.sequelize.define('IncrementUser', { - aNumber: DataTypes.INTEGER - }, { timestamps: true }); - - 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 deleted file mode 100644 index c912fb24aeae..000000000000 --- a/test/integration/instance/destroy.test.js +++ /dev/null @@ -1,379 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - moment = require('moment'), - Support = require('../support'), - dialect = Support.getTestDialect(), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('destroy', () => { - if (current.dialect.supports.transactions) { - 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', async function() { - const UserDestroy = this.sequelize.define('UserDestroy', { - name: Support.Sequelize.STRING, - bio: Support.Sequelize.TEXT - }, { paranoid: true }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING, - deletedAt: { type: Support.Sequelize.DATE, defaultValue: new Date(0) } - }, { paranoid: true }); - - 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', async function() { - const UserDestroy = this.sequelize.define('UserDestroy', { - name: Support.Sequelize.STRING, - bio: Support.Sequelize.TEXT - }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING - }, { paranoid: true }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING, - destroyTime: Support.Sequelize.DATE - }, { paranoid: true, deletedAt: 'destroyTime' }); - - 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' - } - }); - - expect(user).to.be.ok; - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - }); - - 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 }); - - 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' - } - }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING, - destroyTime: { type: Support.Sequelize.DATE, field: 'destroy_time' } - }, { paranoid: true, deletedAt: 'destroyTime' }); - - 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' - } - }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING - }, { paranoid: true }); - - 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', async function() { - const UserDelete = this.sequelize.define('UserDelete', { - name: Support.Sequelize.STRING, - bio: Support.Sequelize.TEXT - }); - - const logging = sinon.spy(); - - 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', async function() { - const UserDelete = this.sequelize.define('UserDelete', { - name: Support.Sequelize.STRING, - bio: Support.Sequelize.TEXT - }, { paranoid: true }); - - const logging = sinon.spy(); - - 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', async function() { - const beforeSave = sinon.spy(); - const afterSave = sinon.spy(); - - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Support.Sequelize.STRING - }, { - paranoid: true, - hooks: { - beforeSave, - afterSave - } - }); - - 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', async function() { - const MultiPrimary = this.sequelize.define('MultiPrimary', { - bilibili: { - type: Support.Sequelize.CHAR(2), - primaryKey: true - }, - - guruguru: { - type: Support.Sequelize.CHAR(2), - primaryKey: true - } - }); - - 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', async function() { - const Date = this.sequelize.define('Date', - { - date: { - type: Support.Sequelize.DATE, - primaryKey: true - }, - deletedAt: { - type: Support.Sequelize.DATE, - defaultValue: Infinity - } - }, - { paranoid: true }); - - 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 deleted file mode 100644 index a50aba8fe9f6..000000000000 --- a/test/integration/instance/increment.test.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - }, - - isSuperUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - await this.User.sync({ force: true }); - }); - - describe('increment', () => { - beforeEach(async function() { - await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); - }); - - if (current.dialect.supports.transactions) { - 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', 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', 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', 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', 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', 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', 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', 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', 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', async function() { - const User = this.sequelize.define('IncrementUser', { - aNumber: DataTypes.INTEGER - }, { timestamps: true }); - - await User.sync({ force: true }); - const user1 = await User.create({ aNumber: 1 }); - const oldDate = user1.get('updatedAt'); - - this.clock.tick(1000); - const user0 = await user1.increment('aNumber', { by: 1 }); - const user = await user0.reload(); - - await expect(user).to.have.property('updatedAt').afterTime(oldDate); - }); - - 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 }); - - 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 deleted file mode 100644 index 8b9566d82140..000000000000 --- a/test/integration/instance/reload.test.js +++ /dev/null @@ -1,336 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../../index'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - }, - - isSuperUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - await this.User.sync({ force: true }); - }); - - describe('reload', () => { - if (current.dialect.supports.transactions) { - 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', async function() { - const originalUser = await this.User.create({ username: 'John Doe' }); - await originalUser.update({ username: 'Doe John' }); - const updatedUser = await originalUser.reload(); - expect(originalUser === updatedUser).to.be.true; - }); - - it('should use default internal where', async function() { - 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', async function() { - const user1 = await this.User.create({ - aNumber: 1, - bNumber: 1 - }); - - 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', 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); - - 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', 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); - - 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 } - }); - - 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', 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); - - 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', 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); - - 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', 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); - - - 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 deleted file mode 100644 index 0ab42dda82aa..000000000000 --- a/test/integration/instance/save.test.js +++ /dev/null @@ -1,639 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../../index'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - }, - - isSuperUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }); - - await this.User.sync({ force: true }); - }); - - describe('save', () => { - if (current.dialect.supports.transactions) { - 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', async function() { - const date = new Date(1990, 1, 1); - - const user = await this.User.create({ - username: 'foo', - touchedAt: new 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', async function() { - const Box = this.sequelize.define('box', { - length: DataTypes.INTEGER, - width: DataTypes.INTEGER, - height: DataTypes.INTEGER - }); - - 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', async function() { - await this.User.build({ - validateTest: 'cake', // invalid, but not saved - validateCustom: '1' - }).save({ - fields: ['validateCustom'] - }); - }); - - describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - User.beforeUpdate(instance => { - instance.set('email', '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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - User.beforeUpdate(instance => { - instance.set('email', '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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: { - type: DataTypes.STRING, - validate: { - isEmail: true - } - } - }); - - User.beforeUpdate(instance => { - instance.set('email', 'B'); - }); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: { - type: DataTypes.STRING, - validate: { - isEmail: true - } - } - }); - - User.beforeUpdate(instance => { - instance.set('email', 'B'); - }); - - 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', async function() { - const username = 'user', - User = this.User, - user = this.User.build({ - username, - touchedAt: 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', async function() { - const username = 'user', - newUsername = 'newUser', - User2 = this.sequelize.define('User2', - { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: false, - primaryKey: true - }, - username: { type: DataTypes.STRING } - }); - - 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', async function() { - const now = new Date(); - now.setMilliseconds(0); - - const user = this.User.build({ username: 'user' }); - this.clock.tick(1000); - - const savedUser = await user.save(); - expect(savedUser).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', async function() { - const user = await this.User.create({ username: 'user' }); - const updatedAt = user.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', async function() { - const data = [ - { username: 'Paul' }, - { username: 'Peter' } - ]; - - 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', 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: { - type: DataTypes.VIRTUAL, - get: () => 'swag' - } - }, { - timestamps: false - }); - 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', async function() { - const user = await this.User.create({ - aNumber: 42 - }); - - user.bNumber = this.sequelize.col('aNumber'); - user.username = this.sequelize.fn('upper', 'sequelize'); - await user.save(); - const user2 = await this.User.findByPk(user.id); - expect(user2.username).to.equal('SEQUELIZE'); - expect(user2.bNumber).to.equal(42); - }); - - it('updates with function that contains escaped dollar symbol', async function() { - const user = await this.User.create({}); - user.username = this.sequelize.fn('upper', '$sequelize'); - await user.save(); - const userAfterUpdate = await this.User.findByPk(user.id); - expect(userAfterUpdate.username).to.equal('$SEQUELIZE'); - }); - - describe('without timestamps option', () => { - it("doesn't update the updatedAt column", async function() { - const User2 = this.sequelize.define('User2', { - username: DataTypes.STRING, - updatedAt: DataTypes.DATE - }, { timestamps: false }); - 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', async function() { - const now = new Date(); - this.clock.tick(1000); - - const User2 = this.sequelize.define('User2', { - username: DataTypes.STRING - }, { updatedAt: false }); - - 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', async function() { - const now = new Date(); - this.clock.tick(1000); - - const User2 = this.sequelize.define('User2', { - username: DataTypes.STRING - }, { createdAt: false }); - - 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', async function() { - const User2 = this.sequelize.define('User2', { - username: DataTypes.STRING, - createdAt: { - type: DataTypes.DATE, - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - allowNull: false - } - }, { timestamps: true }); - - 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', 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', 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', 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', 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', async function() { - const user = await this.User.build({ aNumber: 0 }).save({ - fields: ['aNumber'] - }); - - expect(user.aNumber).to.equal(0); - }); - - 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 } - }); - 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(async function() { - this.UserEager = this.sequelize.define('UserEagerLoadingSaves', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }, { timestamps: false }); - - this.ProjectEager = this.sequelize.define('ProjectEagerLoadingSaves', { - title: DataTypes.STRING, - overdue_days: DataTypes.INTEGER - }, { timestamps: false }); - - this.UserEager.hasMany(this.ProjectEager, { as: 'Projects', foreignKey: 'PoobahId' }); - this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah', foreignKey: 'PoobahId' }); - - await this.UserEager.sync({ force: true }); - - await this.ProjectEager.sync({ force: true }); - }); - - 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', 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)', 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 deleted file mode 100644 index cd904747facd..000000000000 --- a/test/integration/instance/to-json.test.js +++ /dev/null @@ -1,222 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('toJSON', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - age: DataTypes.INTEGER, - level: { type: DataTypes.INTEGER }, - isUser: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - isAdmin: { type: DataTypes.BOOLEAN } - }, { - timestamps: false - }); - - this.Project = this.sequelize.define('NiceProject', { title: DataTypes.STRING }, { timestamps: false }); - - this.User.hasMany(this.Project, { as: 'Projects', foreignKey: 'lovelyUserId' }); - this.Project.belongsTo(this.User, { as: 'LovelyUser', foreignKey: 'lovelyUserId' }); - - await this.User.sync({ force: true }); - - await this.Project.sync({ force: true }); - }); - - 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", 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', () => { - it('returns an object containing all values', function() { - const user = this.User.build({ - username: 'Adam', - age: 22, - level: -1, - isUser: false, - isAdmin: true - }); - - expect(user.toJSON()).to.deep.equal({ - id: null, - username: 'Adam', - age: 22, - level: -1, - isUser: false, - isAdmin: true - }); - }); - - it('returns a response that can be stringified', function() { - const user = this.User.build({ - username: 'test.user', - age: 99, - isAdmin: true, - isUser: false - }); - expect(JSON.stringify(user)).to.deep.equal('{"id":null,"username":"test.user","age":99,"isAdmin":true,"isUser":false}'); - }); - - it('returns a response that can be stringified and then parsed', function() { - const user = this.User.build({ username: 'test.user', age: 99, isAdmin: true }); - expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ username: 'test.user', age: 99, isAdmin: true, isUser: false, id: null }); - }); - }); - - describe('create', () => { - it('returns an object containing all values', async function() { - const user = await this.User.create({ - username: 'Adam', - age: 22, - level: -1, - isUser: false, - isAdmin: true - }); - - 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', async function() { - const user = await this.User.create({ - 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', async function() { - const user = await this.User.create({ - username: 'test.user', - age: 99, - isAdmin: true, - level: null - }); - - 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', 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 - }); - }); - - 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 - }); - - 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', async function() { - const user0 = await this.User.create({ - username: 'test.user', - age: 99, - isAdmin: true - }); - - 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', 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; - - 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; - }); - }); -}); diff --git a/test/integration/instance/update.test.js b/test/integration/instance/update.test.js deleted file mode 100644 index c3f17aefeac8..000000000000 --- a/test/integration/instance/update.test.js +++ /dev/null @@ -1,434 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - after(function() { - this.clock.restore(); - }); - - describe('update', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { type: DataTypes.STRING }, - uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, - uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 }, - touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - aDate: { type: DataTypes.DATE }, - - validateTest: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - validateCustom: { - type: DataTypes.STRING, - allowNull: true, - validate: { len: { msg: 'Length failed.', args: [1, 20] } } - }, - validateSideEffect: { - type: DataTypes.VIRTUAL, - allowNull: true, - validate: { isInt: true }, - set(val) { - this.setDataValue('validateSideEffect', val); - this.setDataValue('validateSideAffected', val * 2); - } - }, - validateSideAffected: { - type: DataTypes.INTEGER, - allowNull: true, - validate: { isInt: true } - }, - - dateAllowNullTrue: { - type: DataTypes.DATE, - allowNull: true - } - }); - await this.User.sync({ force: true }); - }); - - if (current.dialect.supports.transactions) { - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - 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)', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }, { - timestamps: false - }); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING, - createdAt: { type: DataTypes.DATE(6), allowNull: false }, - updatedAt: { type: DataTypes.DATE(6), allowNull: false } - }, { - timestamps: true - }); - - this.clock.tick(2100); //move the clock forward 2100 ms. - - 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', async function() { - const user = this.User.build(); - await user.save(); - user.set('validateTest', 5); - expect(user.changed('validateTest')).to.be.ok; - - await user.update({ - validateCustom: '1' - }); - - expect(user.changed('validateTest')).to.be.ok; - expect(user.validateTest).to.be.equal(5); - await user.reload(); - expect(user.validateTest).to.not.be.equal(5); - }); - - it('should save attributes affected by setters', async function() { - const user = this.User.build(); - await user.update({ validateSideEffect: 5 }); - expect(user.validateSideEffect).to.be.equal(5); - await user.reload(); - expect(user.validateSideAffected).to.be.equal(10); - expect(user.validateSideEffect).not.to.be.ok; - }); - - describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - User.beforeUpdate(instance => { - instance.set('email', '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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - User.beforeUpdate(instance => { - instance.set('email', '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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: { - type: DataTypes.STRING, - validate: { - isEmail: true - } - } - }); - - User.beforeUpdate(instance => { - instance.set('email', 'B'); - }); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: { - type: DataTypes.STRING, - validate: { - isEmail: true - } - } - }); - - User.beforeUpdate(instance => { - instance.set('email', 'B'); - }); - - 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - email: DataTypes.STRING - }); - - await User.sync({ force: true }); - - const user0 = await User.create({ - name: 'snafu', - email: 'email' - }); - - 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('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 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - name: DataTypes.STRING, - bio: DataTypes.TEXT, - identifier: { type: DataTypes.STRING, primaryKey: true } - }); - - 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', async function() { - const Download = this.sequelize.define('download', { - startedAt: DataTypes.DATE, - canceledAt: DataTypes.DATE, - finishedAt: DataTypes.DATE - }); - - 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', async function() { - const spy = sinon.spy(); - - 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 deleted file mode 100644 index 6995eff1cebd..000000000000 --- a/test/integration/instance/values.test.js +++ /dev/null @@ -1,535 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('DAO'), () => { - describe('Values', () => { - describe('set', () => { - it('doesn\'t overwrite generated primary keys', function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - - const user = User.build({ id: 1, name: 'Mick' }); - - expect(user.get('id')).to.equal(1); - expect(user.get('name')).to.equal('Mick'); - user.set({ - id: 2, - name: 'Jan' - }); - expect(user.get('id')).to.equal(1); - expect(user.get('name')).to.equal('Jan'); - }); - - it('doesn\'t overwrite defined primary keys', function() { - const User = this.sequelize.define('User', { - identifier: { type: DataTypes.STRING, primaryKey: true } - }); - - const user = User.build({ identifier: 'identifier' }); - - expect(user.get('identifier')).to.equal('identifier'); - user.set('identifier', 'another identifier'); - expect(user.get('identifier')).to.equal('identifier'); - }); - - it('doesn\'t set timestamps', function() { - const User = this.sequelize.define('User', { - identifier: { type: DataTypes.STRING, primaryKey: true } - }); - - const user = User.build({}, { - isNewRecord: false - }); - - user.set({ - createdAt: new Date(2000, 1, 1), - updatedAt: new Date(2000, 1, 1) - }); - - expect(user.get('createdAt')).not.to.be.ok; - expect(user.get('updatedAt')).not.to.be.ok; - }); - - it('doesn\'t set underscored timestamps', function() { - const User = this.sequelize.define('User', { - identifier: { type: DataTypes.STRING, primaryKey: true } - }, { - underscored: true - }); - - const user = User.build({}, { - isNewRecord: false - }); - - user.set({ - created_at: new Date(2000, 1, 1), - updated_at: new Date(2000, 1, 1) - }); - - expect(user.get('created_at')).not.to.be.ok; - expect(user.get('updated_at')).not.to.be.ok; - }); - - it('doesn\'t set value if not a dynamic setter or a model attribute', function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING }, - email_hidden: { type: DataTypes.STRING } - }, { - setterMethods: { - email_secret(value) { - this.set('email_hidden', value); - } - } - }); - - const user = User.build(); - - user.set({ - name: 'antonio banderaz', - email: 'antonio@banderaz.com', - email_secret: 'foo@bar.com' - }); - - user.set('email', 'antonio@banderaz.com'); - - expect(user.get('name')).to.equal('antonio banderaz'); - expect(user.get('email_hidden')).to.equal('foo@bar.com'); - expect(user.get('email')).not.to.be.ok; - expect(user.dataValues.email).not.to.be.ok; - }); - - 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, - always_false: { - type: DataTypes.BOOLEAN, - defaultValue: false - } - }, { timestamps: 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', () => { - it('should support basic includes', function() { - const Product = this.sequelize.define('product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('tag', { - name: Sequelize.STRING - }); - const User = this.sequelize.define('user', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - Product.hasMany(Tag); - Product.belongsTo(User); - - const product = Product.build({}, { - include: [ - User, - Tag - ] - }); - - product.set({ - id: 1, - title: 'Chair', - tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ], - user: { - id: 1, - first_name: 'Mick', - last_name: 'Hansen' - } - }); - - expect(product.tags).to.be.ok; - expect(product.tags.length).to.equal(2); - expect(product.tags[0]).to.be.instanceof(Tag); - expect(product.user).to.be.ok; - expect(product.user).to.be.instanceof(User); - }); - - it('should support basic includes (with raw: true)', function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('tag', { - name: Sequelize.STRING - }); - const User = this.sequelize.define('user', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - Product.hasMany(Tag); - Product.belongsTo(User); - - const product = Product.build({}, { - include: [ - User, - Tag - ] - }); - - product.set({ - id: 1, - title: 'Chair', - tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ], - user: { - id: 1, - first_name: 'Mick', - last_name: 'Hansen' - } - }, { raw: true }); - - expect(product.tags).to.be.ok; - expect(product.tags.length).to.equal(2); - expect(product.tags[0]).to.be.instanceof(Tag); - expect(product.user).to.be.ok; - expect(product.user).to.be.instanceof(User); - }); - }); - }); - - describe('get', () => { - it('should use custom attribute getters in get(key)', function() { - const Product = this.sequelize.define('Product', { - price: { - type: Sequelize.FLOAT, - get() { - return this.dataValues.price * 100; - } - } - }); - - const product = Product.build({ - price: 10 - }); - expect(product.get('price')).to.equal(1000); - }); - - it('should custom virtual getters in get(key)', function() { - const Product = this.sequelize.define('Product', { - priceInCents: { - type: Sequelize.FLOAT - } - }, { - getterMethods: { - price() { - return this.dataValues.priceInCents / 100; - } - } - }); - - const product = Product.build({ - priceInCents: 1000 - }); - expect(product.get('price')).to.equal(10); - }); - - it('should use custom getters in toJSON', function() { - const Product = this.sequelize.define('Product', { - price: { - type: Sequelize.STRING, - get() { - return this.dataValues.price * 100; - } - } - }, { - getterMethods: { - withTaxes() { - return this.get('price') * 1.25; - } - } - }); - - const product = Product.build({ - price: 10 - }); - expect(product.toJSON()).to.deep.equal({ withTaxes: 1250, price: 1000, id: null }); - }); - - it('should work with save', async function() { - const Contact = this.sequelize.define('Contact', { - first: { type: Sequelize.STRING }, - last: { type: Sequelize.STRING }, - tags: { - type: Sequelize.STRING, - get(field) { - const val = this.getDataValue(field); - return JSON.parse(val); - }, - set(val, field) { - this.setDataValue(field, JSON.stringify(val)); - } - } - }); - - 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', () => { - it('should return plain values when true', function() { - const Product = this.sequelize.define('product', { - title: Sequelize.STRING - }); - const User = this.sequelize.define('user', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - Product.belongsTo(User); - - const product = Product.build({}, { - include: [ - User - ] - }); - - product.set({ - id: 1, - title: 'Chair', - user: { - id: 1, - first_name: 'Mick', - last_name: 'Hansen' - } - }, { raw: true }); - - expect(product.get('user', { plain: true })).not.to.be.instanceof(User); - expect(product.get({ plain: true }).user).not.to.be.instanceof(User); - }); - }); - - describe('clone', () => { - it('should copy the values', function() { - const Product = this.sequelize.define('product', { - title: Sequelize.STRING - }); - - const product = Product.build({ - id: 1, - title: 'Chair' - }, { raw: true }); - - const values = product.get({ clone: true }); - delete values.title; - - expect(product.get({ clone: true }).title).to.be.ok; - }); - }); - - it('can pass parameters to getters', function() { - const Product = this.sequelize.define('product', { - title: Sequelize.STRING - }, { - getterMethods: { - rating(key, options) { - if (options.apiVersion > 1) { - return 100; - } - - return 5; - } - } - }); - - const User = this.sequelize.define('user', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }, { - getterMethods: { - height(key, options) { - if (options.apiVersion > 1) { - return 185; // cm - } - - return 6.06; // ft - } - } - }); - - Product.belongsTo(User); - - const product = Product.build({}, { - include: [ - User - ] - }); - - product.set({ - id: 1, - title: 'Chair', - user: { - id: 1, - first_name: 'Jozef', - last_name: 'Hartinger' - } - }); - - expect(product.get('rating')).to.equal(5); - expect(product.get('rating', { apiVersion: 2 })).to.equal(100); - - expect(product.get({ plain: true })).to.have.property('rating', 5); - expect(product.get({ plain: true }).user).to.have.property('height', 6.06); - expect(product.get({ plain: true, apiVersion: 1 })).to.have.property('rating', 5); - expect(product.get({ plain: true, apiVersion: 1 }).user).to.have.property('height', 6.06); - expect(product.get({ plain: true, apiVersion: 2 })).to.have.property('rating', 100); - expect(product.get({ plain: true, apiVersion: 2 }).user).to.have.property('height', 185); - - expect(product.get('user').get('height', { apiVersion: 2 })).to.equal(185); - }); - }); - - describe('changed', () => { - it('should return false if object was built from database', async function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - - 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() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - - 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; - }); - - it('should return false immediately after saving', async function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - let changed; - - User.afterUpdate(instance => { - changed = instance.changed(); - return; - }); - - 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; - }); - }); - - describe('previous', () => { - it('should return an object with the previous values', function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING }, - title: { type: DataTypes.STRING } - }); - - const user = User.build({ - name: 'Jan Meier', - title: 'Mr' - }); - - user.set('name', 'Mick Hansen'); - user.set('title', 'Dr'); - - expect(user.previous()).to.eql({ name: 'Jan Meier', title: 'Mr' }); - }); - - it('should return the previous value', function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING } - }); - - const user = User.build({ - name: 'Jan Meier' - }); - user.set('name', 'Mick Hansen'); - - expect(user.previous('name')).to.equal('Jan Meier'); - expect(user.get('name')).to.equal('Mick Hansen'); - }); - }); - }); -}); diff --git a/test/integration/json.test.js b/test/integration/json.test.js deleted file mode 100644 index 37a511e5d4db..000000000000 --- a/test/integration/json.test.js +++ /dev/null @@ -1,303 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = Support.Sequelize, - current = Support.sequelize, - DataTypes = Sequelize.DataTypes; - -describe('model', () => { - if (current.dialect.supports.JSON) { - describe('json', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - emergency_contact: DataTypes.JSON, - emergencyContact: DataTypes.JSON - }); - this.Order = this.sequelize.define('Order'); - this.Order.belongsTo(this.User); - - await this.sequelize.sync({ force: true }); - }); - - 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', async function() { - await this.User.create({ - username: 'bob', - emergency_contact: { name: 'joe', phones: [1337, 42] } - }, { - fields: ['id', 'username', 'document', 'emergency_contact'], - logging: sql => { - if (dialect.match(/^mysql|mariadb/)) { - expect(sql).to.include('?'); - } else { - expect(sql).to.include('$1'); - } - } - }); - }); - - it('should insert json using a custom field name', async function() { - this.UserFields = this.sequelize.define('UserFields', { - emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } - }); - 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', async function() { - this.UserFields = this.sequelize.define('UserFields', { - emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } - }); - 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', async function() { - const emergencyContact = { name: 'kate', phone: 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: ['emergency_contact'] }); - expect(user.emergency_contact).to.eql(emergencyContact); - }); - - it('should be able to retrieve element of array by index', async function() { - const emergencyContact = { name: 'kate', phones: [1337, 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', async function() { - const emergencyContact = { kate: 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', async function() { - const emergencyContact = { kate: { email: 'kate@kate.com', phones: [1337, 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: '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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: '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 notation', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - 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 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' } }) - ]); - - 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', async function() { - await Promise.all([ - this.User.create({ username: 'swen', emergency_contact: ['kate', 'joe'] }), - this.User.create({ username: 'anna', emergency_contact: [{ name: 'joe' }] }) - ]); - - 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', async function() { - const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - - 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', async function() { - const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - - 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'); - }); - - // JSONB Supports this, but not JSON in postgres/mysql - if (current.dialect.name === 'sqlite') { - it('should be able to find with just string', async function() { - await this.User.create({ - username: 'swen123', - emergency_contact: 'Unknown' - }); - - 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', async function() { - const user = await this.User.create({ - emergency_contact: { - name: '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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - emergency_contact: DataTypes.JSONB - }); - this.Order = this.sequelize.define('Order'); - this.Order.belongsTo(this.User); - - await this.sequelize.sync({ force: true }); - }); - - it('should be able retrieve json value with nested include', async function() { - const user = await this.User.create({ - emergency_contact: { - name: '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 deleted file mode 100644 index 477a3c7dbc66..000000000000 --- a/test/integration/model.test.js +++ /dev/null @@ -1,2642 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../index'), - expect = chai.expect, - Support = require('./support'), - DataTypes = require('../../lib/data-types'), - dialect = Support.getTestDialect(), - errors = require('../../lib/errors'), - sinon = require('sinon'), - _ = require('lodash'), - moment = require('moment'), - current = Support.sequelize, - Op = Sequelize.Op, - semver = require('semver'), - pMap = require('p-map'); - -describe(Support.getTestDialectTeaser('Model'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN - }); - - await this.User.sync({ force: true }); - }); - - describe('constructor', () => { - it('uses the passed dao name as tablename if freezeTableName', function() { - const User = this.sequelize.define('FrozenUser', {}, { freezeTableName: true }); - expect(User.tableName).to.equal('FrozenUser'); - }); - - it('uses the pluralized dao name as tablename unless freezeTableName', function() { - const User = this.sequelize.define('SuperUser', {}, { freezeTableName: false }); - expect(User.tableName).to.equal('SuperUsers'); - }); - - it('uses checks to make sure dao factory is not leaking on multiple define', function() { - this.sequelize.define('SuperUser', {}, { freezeTableName: false }); - const factorySize = this.sequelize.modelManager.all.length; - - this.sequelize.define('SuperUser', {}, { freezeTableName: false }); - const factorySize2 = this.sequelize.modelManager.all.length; - - expect(factorySize).to.equal(factorySize2); - }); - - it('allows us to predefine the ID column with our own specs', async function() { - const User = this.sequelize.define('UserCol', { - id: { - type: Sequelize.STRING, - defaultValue: 'User', - primaryKey: true - } - }); - - 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() { - expect(() => { - this.sequelize.define('UserWithTwoAutoIncrements', { - userid: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - userscore: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true } - }); - }).to.throw(Error, 'Invalid Instance definition. Only one autoincrement field allowed.'); - }); - - it('throws an error if a custom model-wide validation is not a function', function() { - expect(() => { - this.sequelize.define('Foo', { - field: Sequelize.INTEGER - }, { - validate: { - notFunction: 33 - } - }); - }).to.throw(Error, 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction'); - }); - - it('throws an error if a custom model-wide validation has the same name as a field', function() { - expect(() => { - this.sequelize.define('Foo', { - field: Sequelize.INTEGER - }, { - validate: { - field() {} - } - }); - }).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', async function() { - const UserTable = this.sequelize.define('UserCol', { - aNumber: Sequelize.INTEGER, - createdAt: { - type: Sequelize.DATE, - defaultValue: moment('2012-01-01').toDate() - }, - updatedAt: { - type: Sequelize.DATE, - defaultValue: moment('2012-01-02').toDate() - } - }, { timestamps: true }); - - 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', async function() { - const defaultFunction = sinon.stub().returns(5); - const UserTable = this.sequelize.define('UserCol', { - aNumber: { - type: Sequelize.INTEGER, - defaultValue: defaultFunction - } - }, { timestamps: true }); - - 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', async function() { - const UserTable = this.sequelize.define('UserCol', { - aNumber: Sequelize.INTEGER - }, { - timestamps: true, - updatedAt: 'updatedOn', - createdAt: 'dateCreated', - deletedAt: 'deletedAtThisTime', - paranoid: true - }); - - 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', async function() { - const UpdatingUser = this.sequelize.define('UpdatingUser', { - name: DataTypes.STRING - }, { - timestamps: true, - updatedAt: false, - createdAt: false, - deletedAt: 'deletedAtThisTime', - paranoid: true - }); - - 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', async function() { - const titleSetter = sinon.spy(), - Task = this.sequelize.define('TaskBuild', { - title: { - type: Sequelize.STRING(50), - allowNull: false, - defaultValue: '' - } - }, { - setterMethods: { - title: titleSetter - } - }); - - 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', async function() { - const UserTable = this.sequelize.define('UserCol', { - aNumber: Sequelize.INTEGER - }, { - paranoid: true, - underscored: true - }); - - 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', async function() { - const User = this.sequelize.define('UserWithUniqueUsername', { - username: { type: Sequelize.STRING, unique: 'user_and_email' }, - email: { type: Sequelize.STRING, unique: 'user_and_email' }, - aCol: { type: Sequelize.STRING, unique: 'a_and_b' }, - bCol: { type: Sequelize.STRING, unique: 'a_and_b' } - }); - - 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[`"\]]?\)/); - } else { - expect(sql).to.match(/UNIQUE\s*([`"]?user_and_email[`"]?)?\s*\([`"]?username[`"]?, [`"]?email[`"]?\)/); - expect(sql).to.match(/UNIQUE\s*([`"]?a_and_b[`"]?)?\s*\([`"]?aCol[`"]?, [`"]?bCol[`"]?\)/); - } - })) }); - }); - - 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' } - }); - 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', 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' } - }); - - 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 (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', async function() { - // Fake migration style index creation with explicit index definition - let User = this.sequelize.define('UserWithUniqueUsername', { - user_id: { type: Sequelize.INTEGER }, - email: { type: Sequelize.STRING } - }, { - indexes: [ - { - name: 'user_and_email_index', - msg: 'User and email must be unique', - unique: true, - method: 'BTREE', - fields: ['user_id', { attribute: 'email', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', order: 'DESC', length: 5 }] - }] - }); - - 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 (err) { - if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; - expect(err.message).to.equal('User and email must be unique'); - } - }); - - it('should allow the user to specify indexes in options', async function() { - const indices = [{ - name: 'a_b_uniq', - unique: true, - method: 'BTREE', - fields: [ - 'fieldB', - { - attribute: 'fieldA', - collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', - order: 'DESC', - length: 5 - } - ] - }]; - - if (dialect !== 'mssql') { - indices.push({ - type: 'FULLTEXT', - fields: ['fieldC'], - concurrently: true - }); - - indices.push({ - type: 'FULLTEXT', - fields: ['fieldD'] - }); - } - - const Model = this.sequelize.define('model', { - fieldA: Sequelize.STRING, - fieldB: Sequelize.INTEGER, - fieldC: Sequelize.STRING, - fieldD: Sequelize.STRING - }, { - indexes: indices, - engine: 'MyISAM' - }); - - 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]; - - 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(primary.primary).to.be.ok; - - expect(idx1.type).to.equal('BTREE'); - expect(idx2.type).to.equal('FULLTEXT'); - - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: 'ASC' }, - { attribute: 'fieldA', length: 5, order: 'ASC' } - ]); - - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined } - ]); - } - - expect(idx1.name).to.equal('a_b_uniq'); - expect(idx1.unique).to.be.ok; - - if (dialect !== 'mssql') { - expect(idx2.name).to.equal('models_field_c'); - expect(idx2.unique).not.to.be.ok; - } - }); - }); - - describe('build', () => { - it("doesn't create database entries", async function() { - this.User.build({ username: 'John Wayne' }); - expect(await this.User.findAll()).to.have.length(0); - }); - - it('fills the objects with default values', function() { - const Task = this.sequelize.define('TaskBuild', { - title: { type: Sequelize.STRING, defaultValue: 'a task!' }, - foo: { type: Sequelize.INTEGER, defaultValue: 2 }, - bar: { type: Sequelize.DATE }, - foobar: { type: Sequelize.TEXT, defaultValue: 'asd' }, - flag: { type: Sequelize.BOOLEAN, defaultValue: false } - }); - - expect(Task.build().title).to.equal('a task!'); - expect(Task.build().foo).to.equal(2); - expect(Task.build().bar).to.not.be.ok; - expect(Task.build().foobar).to.equal('asd'); - expect(Task.build().flag).to.be.false; - }); - - it('fills the objects with default values', function() { - const Task = this.sequelize.define('TaskBuild', { - title: { type: Sequelize.STRING, defaultValue: 'a task!' }, - foo: { type: Sequelize.INTEGER, defaultValue: 2 }, - bar: { type: Sequelize.DATE }, - foobar: { type: Sequelize.TEXT, defaultValue: 'asd' }, - flag: { type: Sequelize.BOOLEAN, defaultValue: false } - }, { timestamps: false }); - expect(Task.build().title).to.equal('a task!'); - expect(Task.build().foo).to.equal(2); - expect(Task.build().bar).to.not.be.ok; - expect(Task.build().foobar).to.equal('asd'); - expect(Task.build().flag).to.be.false; - }); - - it('attaches getter and setter methods from attribute definition', function() { - const Product = this.sequelize.define('ProductWithSettersAndGetters1', { - price: { - type: Sequelize.INTEGER, - get() { - return `answer = ${this.getDataValue('price')}`; - }, - set(v) { - return this.setDataValue('price', v + 42); - } - } - }); - - expect(Product.build({ price: 42 }).price).to.equal('answer = 84'); - - const p = Product.build({ price: 1 }); - expect(p.price).to.equal('answer = 43'); - - p.price = 0; - expect(p.price).to.equal('answer = 42'); - }); - - it('attaches getter and setter methods from options', function() { - const Product = this.sequelize.define('ProductWithSettersAndGetters2', { - priceInCents: Sequelize.INTEGER - }, { - setterMethods: { - price(value) { - this.dataValues.priceInCents = value * 100; - } - }, - getterMethods: { - price() { - return `$${this.getDataValue('priceInCents') / 100}`; - }, - - priceInCents() { - return this.dataValues.priceInCents; - } - } - }); - - expect(Product.build({ price: 20 }).priceInCents).to.equal(20 * 100); - expect(Product.build({ priceInCents: 30 * 100 }).price).to.equal(`$${30}`); - }); - - it('attaches getter and setter methods from options only if not defined in attribute', function() { - const Product = this.sequelize.define('ProductWithSettersAndGetters3', { - price1: { - type: Sequelize.INTEGER, - set(v) { this.setDataValue('price1', v * 10); } - }, - price2: { - type: Sequelize.INTEGER, - get() { return this.getDataValue('price2') * 10; } - } - }, { - setterMethods: { - price1(v) { this.setDataValue('price1', v * 100); } - }, - getterMethods: { - price2() { return `$${this.getDataValue('price2')}`;} - } - }); - - const p = Product.build({ price1: 1, price2: 2 }); - - expect(p.price1).to.equal(10); - expect(p.price2).to.equal(20); - }); - - describe('include', () => { - it('should support basic includes', function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - Product.hasMany(Tag); - Product.belongsTo(User); - - const product = Product.build({ - id: 1, - title: 'Chair', - Tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ], - User: { - id: 1, - first_name: 'Mick', - last_name: 'Hansen' - } - }, { - include: [ - User, - Tag - ] - }); - - expect(product.Tags).to.be.ok; - expect(product.Tags.length).to.equal(2); - expect(product.Tags[0]).to.be.instanceof(Tag); - expect(product.User).to.be.ok; - expect(product.User).to.be.instanceof(User); - }); - - it('should support includes with aliases', function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - Product.hasMany(Tag, { as: 'categories' }); - Product.belongsToMany(User, { as: 'followers', through: 'product_followers' }); - User.belongsToMany(Product, { as: 'following', through: 'product_followers' }); - - const product = Product.build({ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' }, - { id: 3, name: 'Charlie' }, - { id: 4, name: 'Delta' } - ], - followers: [ - { - id: 1, - first_name: 'Mick', - last_name: 'Hansen' - }, - { - id: 2, - first_name: 'Jan', - last_name: 'Meier' - } - ] - }, { - include: [ - { model: User, as: 'followers' }, - { model: Tag, as: 'categories' } - ] - }); - - expect(product.categories).to.be.ok; - expect(product.categories.length).to.equal(4); - expect(product.categories[0]).to.be.instanceof(Tag); - expect(product.followers).to.be.ok; - expect(product.followers.length).to.equal(2); - expect(product.followers[0]).to.be.instanceof(User); - }); - }); - }); - - describe('findOne', () => { - if (current.dialect.supports.transactions) { - 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', async function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); - - await User.sync({ force: true }); - await User.create({ username: 'A fancy name' }); - expect((await User.findOne({ where: [] })).username).to.equal('A fancy name'); - }); - - 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', 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', 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', 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 - } - }); - 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.', 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 map the correct fields when saving instance (#10589)', async function() { - const User = this.sequelize.define('User', { - id3: { - field: 'id', - type: Sequelize.INTEGER, - primaryKey: true - }, - id: { - field: 'id2', - type: Sequelize.INTEGER, - allowNull: false - }, - id2: { - field: 'id3', - type: Sequelize.INTEGER, - allowNull: false - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }); - - await this.sequelize.sync({ force: true }); - try { - await User.update(); - throw new Error('Update should throw an error if no where clause is given.'); - } catch (err) { - expect(err).to.be.an.instanceof(Error); - expect(err.message).to.equal('Missing where attribute in the options parameter'); - } - }); - - it('should map the correct fields when updating instance (#10589)', async function() { - const User = this.sequelize.define('User', { - id3: { - field: 'id', - type: Sequelize.INTEGER, - primaryKey: true - }, - id: { - field: 'id2', - type: Sequelize.INTEGER, - allowNull: false - }, - id2: { - field: 'id3', - type: Sequelize.INTEGER, - allowNull: false - } - }); - - 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', 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', async function() { - const User = this.sequelize.define('User1', { - username: Sequelize.STRING, - secretValue: Sequelize.STRING - }, { - paranoid: true - }); - - let test = false; - 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', async function() { - const User = this.sequelize.define('User', { - name: Sequelize.STRING, - bio: Sequelize.TEXT - }, { - paranoid: true - }); - let test = false; - 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' } - ]; - - await this.User.bulkCreate(data); - await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - const users = await this.User.findAll({ order: ['id'] }); - expect(users).to.have.lengthOf(3); - - for (const user of users) { - if (user.secretValue === '42') { - expect(user.username).to.equal('Bill'); - } else { - expect(user.username).to.equal('Bob'); - } - } - }); - - it('throws an error if where has a key with undefined value', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { 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', async function() { - const data = [{ username: 'Peter', secretValue: '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', 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', 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING, - virtual: Sequelize.VIRTUAL - }); - - 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', async function() { - const User = this.sequelize.define('UserWithVirtualSetters', { - username: Sequelize.STRING, - illness_name: Sequelize.STRING, - illness_pain: Sequelize.INTEGER, - illness: { - type: Sequelize.VIRTUAL, - set(value) { - this.set('illness_name', value.name); - this.set('illness_pain', value.pain); - } - } - }); - - 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', async function() { - const User = this.sequelize.define('UserWithVirtualSetters', { - username: Sequelize.STRING, - illness_name: Sequelize.STRING, - illness_pain: Sequelize.INTEGER, - illness: { - type: Sequelize.VIRTUAL, - set(value) { - this.set('illness_name', value.name); - this.set('illness_pain', value.pain); - } - } - }); - - 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', async function() { - this.User.beforeUpdate(instance => { - instance.set('intVal', 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' } - ]; - - 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 - - // Pass the time so we can actually see a change - this.clock.tick(1000); - await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - - 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING - }, { paranoid: true }); - - 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', async function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: DataTypes.STRING - }, { paranoid: true }); - - 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', async function() { - const hookSpy = sinon.spy(); - const User = this.sequelize.define('User', - { username: DataTypes.STRING }, - { paranoid: true, hooks: { beforeUpdate: hookSpy } } - ); - - 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Peter', secretValue: '42' }, - { username: 'Peter', secretValue: '42' } - ]; - - 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('`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` 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('`truncate` option returns a number', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }); - await this.sequelize.sync({ force: true }); - await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); - const affectedRows = await User.destroy({ truncate: true }); - expect(await User.findAll()).to.have.lengthOf(0); - expect(affectedRows).to.be.a('number'); - }); - - it('throws an error if no where clause is given', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }); - await this.sequelize.sync({ force: true }); - try { - await User.destroy(); - throw new Error('Destroy should throw an error if no where clause is given.'); - } 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', 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }); - - 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'); - } 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', 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', async function() { - const data = [ - { username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { 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', async function() { - const Log = this.sequelize.define('Log', { - client_id: DataTypes.INTEGER, - content: DataTypes.TEXT, - timestamp: DataTypes.DATE - }); - Log.removeAttribute('id'); - - 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', async function() { - const UserProject = this.sequelize.define('UserProject', { - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - } - }); - - 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', 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' } - ]; - - await ParanoidUser.sync({ force: true }); - await ParanoidUser.bulkCreate(data); - - // 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' } }); - - 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', async function() { - const User = this.sequelize.define('UserCol', { - secretValue: Sequelize.STRING, - username: Sequelize.STRING - }, { paranoid: true }); - - 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', async function() { - const User = this.sequelize.define('UserCol', { - username: Sequelize.STRING - }, { paranoid: true }); - - 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', async function() { - const User = this.sequelize.define('UserCol', { - username: Sequelize.STRING - }, { paranoid: true }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING - }, { paranoid: true }); - const Pet = this.sequelize.define('Pet', { - name: Sequelize.STRING, - UserId: Sequelize.INTEGER - }, { paranoid: true }); - - User.hasMany(Pet); - Pet.belongsTo(User); - - 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 }); - - 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' } - ]; - - 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', 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 }); - - 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('rejects with an error if the model is not paranoid', async function() { - await expect(this.User.restore({ where: { secretValue: '42' } })).to.be.rejectedWith(Error, 'Model is not paranoid'); - }); - - it('restores a previously deleted model', async function() { - 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: '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', 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', async function() { - const userKeys = this.sequelize.define('userkeys', { - foo: { type: Sequelize.STRING, primaryKey: true }, - bar: { type: Sequelize.STRING, primaryKey: true }, - name: Sequelize.STRING, - bio: Sequelize.TEXT - }); - - 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; - }); - } - }); - - // 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 }, - name: Sequelize.STRING, - bio: Sequelize.TEXT - }); - - await this.userKey.sync({ force: true }); - }); - - 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", 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', 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', 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', async function() { - await this.User.bulkCreate([ - { username: 'user1', data: 'A' }, - { username: 'user2', data: 'A' }, - { username: 'user3', data: 'B' } - ]); - const count = await this.User.count({ - attributes: ['data'], - group: ['data'] - }); - expect(count).to.have.lengthOf(2); - }); - - 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; - - beforeEach(function() { - options = { where: { username: 'user1' } }; - - aggregateSpy = sinon.spy(this.User, 'aggregate'); - }); - - afterEach(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('where', { username: 'user1' })) - ); - - aggregateSpy.restore(); - }); - - it('modifies option "limit" by setting it to null', async function() { - options.limit = 5; - - 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', async function() { - options.offset = 10; - - 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', async function() { - options.order = 'username'; - - 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', async function() { - let test = false; - await this.User.count({ - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }); - expect(test).to.be.true; - }); - - 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', async function() { - const Post = this.sequelize.define('Post', {}); - const PostComment = this.sequelize.define('PostComment', {}); - Post.hasMany(PostComment); - 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); - }); - - }); - - for (const methodName of ['min', 'max']) { - describe(methodName, () => { - beforeEach(async function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER, - order: Sequelize.INTEGER - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - - await this.UserWithAge.sync({ force: true }); - await this.UserWithDec.sync({ force: true }); - }); - - 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 }); - - 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('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('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', 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 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 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('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(async function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER, - order: Sequelize.INTEGER, - gender: Sequelize.ENUM('male', 'female') - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - - this.UserWithFields = this.sequelize.define('UserWithFields', { - age: { - type: Sequelize.INTEGER, - field: 'user_age' - }, - order: Sequelize.INTEGER, - gender: { - type: Sequelize.ENUM('male', 'female'), - field: 'male_female' - } - }); - - await Promise.all([ - this.UserWithAge.sync({ force: true }), - this.UserWithDec.sync({ force: true }), - this.UserWithFields.sync({ force: true }) - ]); - }); - - 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 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', 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', 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', async function() { - const options = { where: { gender: 'male' } }; - await this.UserWithFields.bulkCreate([ - { age: 2, gender: 'male' }, - { age: 3, gender: 'female' } - ]); - expect(await this.UserWithFields.sum('age', options)).to.equal(2); - }); - - it('allows sql logging', async function() { - let test = false; - await this.UserWithAge.sum('age', { - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }); - expect(test).to.true; - }); - }); - - describe('schematic support', () => { - beforeEach(async function() { - this.UserPublic = this.sequelize.define('UserPublic', { - age: Sequelize.INTEGER - }); - - this.UserSpecial = this.sequelize.define('UserSpecial', { - age: Sequelize.INTEGER - }); - - 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(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', async function() { - await this.UserSpecial.drop(); - }); - - 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 (['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 - }); - 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', async function() { - const UserPublic = this.sequelize.define('Public', { - username: Sequelize.STRING - }); - - 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', async function() { - const UserPub = this.sequelize.define('UserPub', { - username: Sequelize.STRING - }, { schema: 'prefix' }); - - const ItemPub = this.sequelize.define('ItemPub', { - name: Sequelize.STRING - }, { schema: 'prefix' }); - - UserPub.hasMany(ItemPub, { foreignKeyConstraint: true }); - - if (['postgres', 'mssql', 'mariadb'].includes(dialect)) { - await Support.dropTestSchemas(this.sequelize); - await this.sequelize.queryInterface.createSchema('prefix'); - } - - 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', async function() { - let logged = 0; - 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`'); - } - } - }); - - 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(async function() { - this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING }); - - 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', async function() { - const authorIdColumn = { type: Sequelize.INTEGER, references: { model: this.Author, key: 'id' } }; - - const Post = this.sequelize.define('post', { - title: Sequelize.STRING, - authorId: authorIdColumn - }); - - this.Author.hasMany(Post); - Post.belongsTo(this.Author); - - // The posts table gets dropped in the before filter. - await Post.sync({ logging: _.once(sql => { - if (dialect === 'postgres') { - expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); - } else if (dialect === 'mysql' || dialect === 'mariadb') { - expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/); - } else if (dialect === 'mssql') { - expect(sql).to.match(/FOREIGN KEY \(\[authorId\]\) REFERENCES \[authors\] \(\[id\]\)/); - } else if (dialect === 'sqlite') { - expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/); - } else { - throw new Error('Undefined dialect!'); - } - }) }); - }); - - 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 }); - - this.Author.hasMany(Post); - Post.belongsTo(this.Author); - - // The posts table gets dropped in the before filter. - await Post.sync({ logging: _.once(sql => { - if (dialect === 'postgres') { - expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); - } else if (dialect === 'mysql' || dialect === 'mariadb') { - expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/); - } else if (dialect === 'sqlite') { - expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/); - } else if (dialect === 'mssql') { - expect(sql).to.match(/FOREIGN KEY \(\[authorId\]\) REFERENCES \[authors\] \(\[id\]\)/); - } else { - throw new Error('Undefined dialect!'); - } - }) }); - }); - - 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 }); - - this.Author.hasMany(Post); - Post.belongsTo(this.Author); - - 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); - } else { - // the parser should not end up here ... - expect(2).to.equal(1); - } - } catch (err) { - if (dialect === 'mysql') { - // MySQL 5.7 or above doesn't support POINT EMPTY - 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/); - } - } else if (dialect === 'sqlite') { - // the parser should not end up here ... see above - expect(1).to.equal(2); - } else if (dialect === 'mariadb') { - expect(err.message).to.match(/Foreign key constraint is incorrectly formed/); - } else if (dialect === 'postgres') { - expect(err.message).to.match(/relation "4uth0r5" does not exist/); - } else if (dialect === 'mssql') { - expect(err.message).to.match(/Could not create constraint/); - } else { - throw new Error('Undefined dialect!'); - } - } - }); - - 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 = { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: false, - comment: 'asdf' - }; - - idColumn.references = { model: Member, key: 'id' }; - - this.sequelize.define('Profile', { id: idColumn }); - - await this.sequelize.sync({ force: true }); - }); - }); - - describe('blob', () => { - beforeEach(async function() { - this.BlobUser = this.sequelize.define('blobUser', { - data: Sequelize.BLOB - }); - - await this.BlobUser.sync({ force: true }); - }); - - describe('buffers', () => { - 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') - }); - - expect(user).to.be.ok; - }); - - it('should return a buffer when fetching a blob', async function() { - const user = await this.BlobUser.create({ - data: Buffer.from('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', async function() { - const user = await this.BlobUser.create({ - // create a null column - }); - - const user0 = await this.BlobUser.findByPk(user.id); - expect(user0.data).to.be.null; - }); - }); - - if (dialect !== 'mssql') { - // NOTE: someone remember to inform me about the intent of these tests. Are - // you saying that data passed in as a string is automatically converted - // to binary? i.e. "Sequelize" is CAST as binary, OR that actual binary - // 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', async function() { - const user = await this.BlobUser.create({ - data: 'Sequelize' - }); - - expect(user).to.be.ok; - }); - - 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' - }); - - const user0 = await this.BlobUser.findByPk(user.id); - expect(user0.data).to.be.an.instanceOf(Buffer); - expect(user0.data.toString()).to.have.string('Sequelize'); - }); - }); - } - - }); - - describe('paranoid is true and where is an array', () => { - - 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' }); - - 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', async function() { - const res = await this.User.findAll({ - where: [ - this.sequelize.or({ username: 'vader' }, { username: 'luke' }), - this.sequelize.and({ id: [1, 2, 3] }) - ] - }); - - expect(res).to.have.length(2); - }); - - 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', 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 } - ] - }); - - 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', async function() { - let tableName = ''; - if (this.User.name) { - tableName = `${this.sequelize.queryInterface.queryGenerator.quoteIdentifier(this.User.name)}.`; - } - - const users = await this.User.findAll({ - paranoid: false, - where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.queryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), - include: [ - { model: this.Project } - ] - }); - - 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', async function() { - const res = await this.User.findAll({ - paranoid: false, - where: [ - this.sequelize.or({ username: 'leia' }, { username: 'luke' }), - this.sequelize.and( - { id: [1, 2, 3] }, - this.sequelize.or({ deletedAt: null }, { deletedAt: { [Op.gt]: new Date(0) } }) - ) - ] - }); - - expect(res).to.have.length(2); - }); - - }); - - if (dialect !== 'sqlite' && current.dialect.supports.transactions) { - it('supports multiple async transactions', async function() { - this.timeout(90000); - 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' - } - }); - - 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', async function() { - const uniqueTrue = this.sequelize.define('uniqueTrue', { - str: { type: Sequelize.STRING, unique: true } - }); - - await uniqueTrue.sync({ force: true, logging: _.after(2, _.once(s => { - expect(s).to.match(/UNIQUE/); - })) }); - }); - - it('should not set unique when unique is false', async function() { - const uniqueFalse = this.sequelize.define('uniqueFalse', { - str: { type: Sequelize.STRING, unique: false } - }); - - 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', async function() { - const uniqueUnset = this.sequelize.define('uniqueUnset', { - str: { type: Sequelize.STRING } - }); - - 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', async function() { - this.sequelize.define('project', { - UserId: { - type: Sequelize.STRING, - references: { - model: 'Users', - key: 'UUID' - } - } - }); - - this.sequelize.define('Users', { - UUID: { - type: Sequelize.STRING, - primaryKey: true, - unique: true, - allowNull: false, - validate: { - notNull: true, - notEmpty: true - } - } - }); - - await this.sequelize.sync({ force: true }); - }); - - describe('bulkCreate', () => { - it('errors - should return array of errors if validate and individualHooks are true', async function() { - const data = [{ username: null }, - { username: null }, - { username: null }]; - - const user = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - allowNull: false, - validate: { - notNull: true, - notEmpty: true - } - } - }); - - 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', async function() { - const user = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - allowNull: false, - field: 'data', - get() { - const val = this.getDataValue('username'); - return val.substring(0, val.length - 1); - }, - set(val) { - if (val.includes('!')) { - throw new Error('val should not include a "!"'); - } - this.setDataValue('username', `${val}!`); - } - } - }); - - const data = [{ username: '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 deleted file mode 100644 index 99dd66e07ebc..000000000000 --- a/test/integration/model/attributes.test.js +++ /dev/null @@ -1,142 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - 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', async function() { - let callCount = 0; - - this.Student = this.sequelize.define('student', { - no: { type: Sequelize.INTEGER, primaryKey: true }, - name: Sequelize.STRING - }, { - tableName: 'student', - timestamps: false - }); - - this.Course = this.sequelize.define('course', { - no: { type: Sequelize.INTEGER, primaryKey: true }, - name: Sequelize.STRING - }, { - tableName: 'course', - timestamps: false - }); - - this.Score = this.sequelize.define('score', { - score: Sequelize.INTEGER, - test_value: { - type: Sequelize.INTEGER, - set(v) { - callCount++; - this.setDataValue('test_value', v + 1); - } - } - }, { - tableName: 'score', - timestamps: false - }); - - this.Student.belongsToMany(this.Course, { through: this.Score, foreignKey: 'StudentId' }); - this.Course.belongsToMany(this.Student, { through: this.Score, foreignKey: 'CourseId' }); - - 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"', async function() { - const Person = this.sequelize.define('person', { - name: Sequelize.STRING, - nick: Sequelize.STRING - }, { - timestamps: false - }); - - 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', async function() { - const Person = this.sequelize.define('person', { - name: Sequelize.STRING, - nick: Sequelize.STRING - }); - - const Computer = this.sequelize.define('computer', { - hostname: Sequelize.STRING - }); - - Person.hasMany(Computer); - - 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', async function() { - const User = this.sequelize.define('user', { - 'foo.bar.baz': Sequelize.TEXT - }); - - 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 deleted file mode 100644 index e03aebfe4ee2..000000000000 --- a/test/integration/model/attributes/field.test.js +++ /dev/null @@ -1,587 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Model'), () => { - - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - describe('attributes', () => { - describe('field', () => { - beforeEach(async function() { - const queryInterface = this.sequelize.getQueryInterface(); - - this.User = this.sequelize.define('user', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - field: 'userId' - }, - name: { - type: DataTypes.STRING, - field: 'full_name' - }, - taskCount: { - type: DataTypes.INTEGER, - field: 'task_count', - defaultValue: 0, - allowNull: false - } - }, { - tableName: 'users', - timestamps: false - }); - - this.Task = this.sequelize.define('task', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - field: 'taskId' - }, - title: { - type: DataTypes.STRING, - field: 'name' - } - }, { - tableName: 'tasks', - timestamps: false - }); - - this.Comment = this.sequelize.define('comment', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - field: 'commentId' - }, - text: { type: DataTypes.STRING, field: 'comment_text' }, - notes: { type: DataTypes.STRING, field: 'notes' }, - likes: { type: DataTypes.INTEGER, field: 'like_count' }, - createdAt: { type: DataTypes.DATE, field: 'created_at', allowNull: false }, - updatedAt: { type: DataTypes.DATE, field: 'updated_at', allowNull: false } - }, { - tableName: 'comments', - timestamps: true - }); - - this.User.hasMany(this.Task, { - foreignKey: 'user_id' - }); - this.Task.belongsTo(this.User, { - foreignKey: 'user_id' - }); - this.Task.hasMany(this.Comment, { - foreignKey: 'task_id' - }); - this.Comment.belongsTo(this.Task, { - foreignKey: 'task_id' - }); - - this.User.belongsToMany(this.Comment, { - foreignKey: 'userId', - otherKey: 'commentId', - through: 'userComments' - }); - - await Promise.all([ - queryInterface.createTable('users', { - userId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - }, - full_name: { - type: DataTypes.STRING - }, - task_count: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0 - } - }), - queryInterface.createTable('tasks', { - taskId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - }, - user_id: { - type: DataTypes.INTEGER - }, - name: { - type: DataTypes.STRING - } - }), - queryInterface.createTable('comments', { - commentId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - }, - task_id: { - type: DataTypes.INTEGER - }, - comment_text: { - type: DataTypes.STRING - }, - notes: { - type: DataTypes.STRING - }, - like_count: { - type: DataTypes.INTEGER - }, - created_at: { - type: DataTypes.DATE, - allowNull: false - }, - updated_at: { - type: DataTypes.DATE - } - }), - queryInterface.createTable('userComments', { - commentId: { - type: DataTypes.INTEGER - }, - userId: { - type: DataTypes.INTEGER - } - }) - ]); - }); - - describe('primaryKey', () => { - describe('in combination with allowNull', () => { - beforeEach(async function() { - this.ModelUnderTest = this.sequelize.define('ModelUnderTest', { - identifier: { - primaryKey: true, - type: Sequelize.STRING, - allowNull: false - } - }); - - await this.ModelUnderTest.sync({ force: true }); - }); - - it('sets the column to not allow null', async function() { - const fields = await this - .ModelUnderTest - .describe(); - - expect(fields.identifier).to.include({ allowNull: false }); - }); - }); - - it('should support instance.destroy()', async function() { - const user = await this.User.create(); - await user.destroy(); - }); - - 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(async function() { - await this.Comment.bulkCreate([ - { notes: 'Number one' }, - { notes: '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', 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', async function() { - const comment = await this.Comment.findByPk(1); - await comment.reload(); - }); - - 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', 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', 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', 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', async function() { - await this.User.create({ - name: 'Foobar' - }); - - 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', async function() { - const Entity = this.sequelize.define('Entity', { - strField: { type: Sequelize.STRING, field: 'str_field' } - }); - - 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', async function() { - const Model = this.sequelize.define('test', { - id: { - type: Sequelize.INTEGER, - field: 'test_id', - autoIncrement: true, - primaryKey: true, - validate: { - min: 1 - } - }, - title: { - allowNull: false, - type: Sequelize.STRING(255), - field: 'test_title' - } - }, { - timestamps: true, - underscored: true, - freezeTableName: true - }); - - 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', async function() { - const user = await this.User.create({ - name: 'Barfoo' - }); - - expect(user.get('id')).to.be.ok; - }); - - it('should work with where on includes for find', async function() { - const user = await this.User.create({ - name: 'Barfoo' - }); - - 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', async function() { - const user = await this.User.create({ - name: 'Foobar' - }); - - 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 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' - }); - - const user = await this.User.findOne({ - where: { - name: 'Foobar' - } - }); - - expect(user).to.be.ok; - }); - - it('should work with a where or', async function() { - await this.User.create({ - name: 'Foobar' - }); - - 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', async function() { - await this.User.bulkCreate([{ - name: 'Abc' - }, { - name: 'Bcd' - }, { - name: 'Cde' - }]); - - 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', 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 - }); - - 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'] - ]; - } - - 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', 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', 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', async function() { - await this.Comment.create({ - notes: 'Foobar' - }); - - 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', async function() { - const userId = Math.floor(Math.random() * 100000); - - 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()', async function() { - const User = this.sequelize.define('User', { - deletedAt: { - type: DataTypes.DATE, - field: 'deleted_at' - } - }, { - timestamps: true, - paranoid: true - }); - - 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()', async function() { - const User = this.sequelize.define('User', { - deletedAt: { - type: DataTypes.DATE, - field: 'deleted_at' - } - }, { - timestamps: true, - paranoid: true - }); - - 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`', async function() { - const user = await this.User.create({ - name: 'John' - }); - - const commentCount = await user.countComments(); - await expect(commentCount).to.equal(0); - }); - - it('should work with `hasMany` association `count`', async function() { - const user = await this.User.create({ - name: 'John' - }); - - 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 deleted file mode 100644 index 8da3d1235ddb..000000000000 --- a/test/integration/model/attributes/types.test.js +++ /dev/null @@ -1,191 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('attributes', () => { - describe('types', () => { - describe('VIRTUAL', () => { - beforeEach(async function() { - this.User = this.sequelize.define('user', { - storage: Sequelize.STRING, - field1: { - type: Sequelize.VIRTUAL, - set(val) { - this.setDataValue('storage', val); - this.setDataValue('field1', val); - }, - get() { - return this.getDataValue('field1'); - } - }, - field2: { - type: Sequelize.VIRTUAL, - get() { - return 42; - } - }, - virtualWithDefault: { - type: Sequelize.VIRTUAL, - defaultValue: 'cake' - } - }, { timestamps: false }); - - this.Task = this.sequelize.define('task', {}); - this.Project = this.sequelize.define('project', {}); - - this.Task.belongsTo(this.User); - this.User.hasMany(this.Task); - this.Project.belongsToMany(this.User, { through: 'project_user' }); - this.User.belongsToMany(this.Project, { through: 'project_user' }); - - this.sqlAssert = function(sql) { - expect(sql).to.not.include('field1'); - expect(sql).to.not.include('field2'); - }; - - await this.sequelize.sync({ force: true }); - }); - - it('should not be ignored in dataValues get', function() { - const user = this.User.build({ - field1: 'field1_value', - field2: 'field2_value' - }); - - 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', 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', async function() { - await Promise.all([ - this.User.findOne({ - logging: this.sqlAssert - }), - this.User.findAll({ - logging: this.sqlAssert - }), - this.Task.findAll({ - include: [ - this.User - ], - logging: this.sqlAssert - }), - this.Project.findAll({ - include: [ - this.User - ], - logging: this.sqlAssert - }) - ]); - }); - - it('should allow me to store selected values', async function() { - const Post = this.sequelize.define('Post', { - text: Sequelize.TEXT, - someBoolean: { - type: Sequelize.VIRTUAL - } - }); - - 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"'; - } - - 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', async function() { - const user0 = await this.User.create({ - field1: 'something' - }); - - // 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', async function() { - await this.User.bulkCreate([{ - field1: 'something' - }], { - logging: this.sqlAssert - }); - - const users = await this.User.findAll(); - expect(users[0].storage).to.equal('something'); - }); - - it('should be able to exclude with attributes', async function() { - await this.User.bulkCreate([{ - field1: 'something' - }], { - logging: this.sqlAssert - }); - - const users0 = await this.User.findAll({ - logging: this.sqlAssert - }); - - const user0 = users0[0].get(); - - 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', 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 deleted file mode 100644 index 4696d29e586a..000000000000 --- a/test/integration/model/bulk-create.test.js +++ /dev/null @@ -1,999 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - AggregateError = require('../../../lib/errors/aggregate-error'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - dialect = Support.getTestDialect(), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - 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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - await User.sync({ force: true }); - const transaction = await this.sequelize.transaction(); - await User.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction }); - const count1 = await User.count(); - const count2 = await User.count({ transaction }); - expect(count1).to.equal(0); - expect(count2).to.equal(2); - await transaction.rollback(); - }); - } - - it('should be able to set createdAt and updatedAt if using silent: true', async function() { - const User = this.sequelize.define('user', { - name: DataTypes.STRING - }, { - timestamps: true - }); - - const createdAt = new Date(2012, 10, 10, 10, 10, 10); - const updatedAt = new Date(2011, 11, 11, 11, 11, 11); - const values = new Array(10).fill({ - createdAt, - updatedAt - }); - - 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', async function() { - const User = this.sequelize.define('user', { - name: Sequelize.STRING - }); - - 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', async function() { - const User = this.sequelize.define('user', { - name: Sequelize.STRING, - type: { - type: Sequelize.STRING, - allowNull: false, - field: 'user_type' - }, - createdAt: { - type: Sequelize.DATE, - allowNull: false, - field: 'created_at' - }, - updatedAt: { - type: Sequelize.DATE, - field: 'modified_at' - } - }); - - 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', async function() { - const Beer = this.sequelize.define('Beer', { - style: Sequelize.STRING, - size: Sequelize.INTEGER - }); - - 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', async function() { - const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, - { username: 'Paul', uniqueName: '2' }, - { username: 'Steve', uniqueName: '3' }]; - - 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', async function() { - const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, - { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - - 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', async function() { - const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, - { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - - 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', async function() { - const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, - { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - - 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', async function() { - const quote = "Single'Quote", - data = [{ username: 'Peter', data: quote, uniqueName: '1' }, - { username: 'Paul', data: quote, uniqueName: '2' }]; - - 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', async function() { - const quote = 'Double"Quote', - data = [{ username: 'Peter', data: quote, uniqueName: '1' }, - { username: 'Paul', data: quote, uniqueName: '2' }]; - - 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', async function() { - const json = JSON.stringify({ key: 'value' }), - data = [{ username: 'Peter', data: json, uniqueName: '1' }, - { username: 'Paul', data: json, uniqueName: '2' }]; - - 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', async function() { - const UserWithLength = this.sequelize.define('UserWithLength', { - length: Sequelize.INTEGER - }); - - await UserWithLength.sync({ force: true }); - - await UserWithLength.bulkCreate([{ length: 42 }, { length: 11 }]); - }); - - it('stores the current date in createdAt', async function() { - const data = [{ username: 'Peter', uniqueName: '1' }, - { username: 'Paul', uniqueName: '2' }]; - - 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', async function() { - const Tasks = this.sequelize.define('Task', { - name: { - type: Sequelize.STRING, - allowNull: false - }, - code: { - type: Sequelize.STRING, - validate: { - len: [3, 10] - } - } - }); - - await Tasks.sync({ force: true }); - - try { - await Tasks.bulkCreate([ - { name: 'foo', code: '123' }, - { code: '1234' }, - { name: 'bar', code: '1' } - ], { validate: true }); - } catch (error) { - const expectedValidationError = 'Validation len on code failed'; - const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; - - expect(error).to.be.instanceof(AggregateError); - expect(error.toString()).to.include(expectedValidationError) - .and.to.include(expectedNotNullError); - const { errors } = error; - expect(errors).to.have.length(2); - - const e0name0 = errors[0].errors.get('name')[0]; - - expect(errors[0].record.code).to.equal('1234'); - expect(e0name0.type || e0name0.origin).to.equal('notNull Violation'); - - expect(errors[1].record.name).to.equal('bar'); - expect(errors[1].record.code).to.equal('1'); - expect(errors[1].errors.get('code')[0].message).to.equal(expectedValidationError); - } - }); - - 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, - validate: { - notEmpty: true - } - }, - code: { - type: Sequelize.STRING, - validate: { - len: [3, 10] - } - } - }); - - 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)', async function() { - const Worker = this.sequelize.define('Worker', {}); - 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)', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - await Worker.sync(); - const workers = await Worker.bulkCreate([{}, {}]); - expect(workers).to.be.ok; - }); - - it('should allow autoincremented attributes to be set', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - 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', async function() { - const Dummy = this.sequelize.define('Dummy', { - foo: DataTypes.STRING, - bar: DataTypes.STRING - }, { - schema: 'space1', - tableName: 'Dummy' - }); - - 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', async function() { - const data = [ - { uniqueName: 'Peter', secretValue: '42' }, - { uniqueName: 'Paul', secretValue: '23' } - ]; - - 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', async function() { - const data = [ - { uniqueName: 'Peter', secretValue: '42' }, - { uniqueName: 'Paul', secretValue: '23' } - ]; - - await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }); - data.push({ uniqueName: 'Michael', secretValue: '26' }); - - 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', async function() { - const data = [ - { uniqueName: 'Peter', secretValue: '42' }, - { uniqueName: 'Paul', secretValue: '23' } - ]; - - 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', async function() { - const data = [ - { no: 1, name: 'Peter' }, - { no: 2, name: 'Paul' } - ]; - - 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', async function() { - const data = [ - { plateNumber: 'abc', color: 'Grey' }, - { plateNumber: 'def', color: 'White' } - ]; - - await this.Car.bulkCreate(data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); - const new_data = [ - { plateNumber: 'abc', color: 'Red' }, - { plateNumber: 'def', color: 'Green' }, - { plateNumber: 'ghi', color: 'Blue' } - ]; - await this.Car.bulkCreate(new_data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); - const cars = await this.Car.findAll({ order: ['plateNumber'] }); - expect(cars.length).to.equal(3); - expect(cars[0].plateNumber).to.equal('abc'); - expect(cars[0].color).to.equal('Red'); - expect(cars[1].plateNumber).to.equal('def'); - expect(cars[1].color).to.equal('Green'); - expect(cars[2].plateNumber).to.equal('ghi'); - expect(cars[2].color).to.equal('Blue'); - }); - - it('when the primary key column names and model field names are different and have unique constraints', async function() { - const Person = this.sequelize.define('Person', { - emailAddress: { - type: DataTypes.STRING, - allowNull: false, - primaryKey: true, - unique: true, - field: 'email_address' - }, - name: { - type: DataTypes.STRING, - allowNull: false, - field: 'name' - } - }, {}); - - await Person.sync({ force: true }); - const inserts = [ - { emailAddress: 'a@example.com', name: 'Alice' } - ]; - const people0 = await Person.bulkCreate(inserts); - expect(people0.length).to.equal(1); - expect(people0[0].emailAddress).to.equal('a@example.com'); - expect(people0[0].name).to.equal('Alice'); - - const updates = [ - { emailAddress: 'a@example.com', name: 'CHANGED NAME' }, - { emailAddress: 'b@example.com', name: 'Bob' } - ]; - - const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['emailAddress', 'name'] }); - expect(people.length).to.equal(2); - expect(people[0].emailAddress).to.equal('a@example.com'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].emailAddress).to.equal('b@example.com'); - expect(people[1].name).to.equal('Bob'); - }); - - it('when the composite primary key column names and model field names are different', async function() { - const Person = this.sequelize.define('Person', { - systemId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - field: 'system_id' - }, - system: { - type: DataTypes.STRING, - allowNull: false, - primaryKey: true, - field: 'system' - }, - name: { - type: DataTypes.STRING, - allowNull: false, - field: 'name' - } - }, {}); - - await Person.sync({ force: true }); - const inserts = [ - { systemId: 1, system: 'system1', name: 'Alice' } - ]; - const people0 = await Person.bulkCreate(inserts); - expect(people0.length).to.equal(1); - expect(people0[0].systemId).to.equal(1); - expect(people0[0].system).to.equal('system1'); - expect(people0[0].name).to.equal('Alice'); - - const updates = [ - { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, - { systemId: 1, system: 'system2', name: 'Bob' } - ]; - - const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); - expect(people.length).to.equal(2); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].systemId).to.equal(1); - expect(people[1].system).to.equal('system2'); - expect(people[1].name).to.equal('Bob'); - }); - - it('when the primary key column names and model field names are different and have composite unique constraints', async function() { - const Person = this.sequelize.define('Person', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - field: 'id' - }, - systemId: { - type: DataTypes.INTEGER, - allowNull: false, - unique: 'system_id_system_unique', - field: 'system_id' - }, - system: { - type: DataTypes.STRING, - allowNull: false, - unique: 'system_id_system_unique', - field: 'system' - }, - name: { - type: DataTypes.STRING, - allowNull: false, - field: 'name' - } - }, {}); - - await Person.sync({ force: true }); - const inserts = [ - { id: 1, systemId: 1, system: 'system1', name: 'Alice' } - ]; - const people0 = await Person.bulkCreate(inserts); - expect(people0.length).to.equal(1); - expect(people0[0].systemId).to.equal(1); - expect(people0[0].system).to.equal('system1'); - expect(people0[0].name).to.equal('Alice'); - - const updates = [ - { id: 1, systemId: 1, system: 'system1', name: 'CHANGED NAME' }, - { id: 2, systemId: 1, system: 'system2', name: 'Bob' } - ]; - - const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); - expect(people.length).to.equal(2); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].systemId).to.equal(1); - expect(people[1].system).to.equal('system2'); - expect(people[1].name).to.equal('Bob'); - }); - - it('[#12516] when the primary key column names and model field names are different and have composite unique index constraints', async function() { - const Person = this.sequelize.define( - 'Person', - { - id: { - type: DataTypes.INTEGER, - allowNull: false, - autoIncrement: true, - primaryKey: true, - field: 'id' - }, - systemId: { - type: DataTypes.INTEGER, - allowNull: false, - field: 'system_id' - }, - system: { - type: DataTypes.STRING, - allowNull: false, - field: 'system' - }, - name: { - type: DataTypes.STRING, - allowNull: false, - field: 'name' - } - }, - { - indexes: [ - { - unique: true, - fields: ['system_id', 'system'] - } - ] - } - ); - - await Person.sync({ force: true }); - const inserts = [{ systemId: 1, system: 'system1', name: 'Alice' }]; - const people0 = await Person.bulkCreate(inserts); - expect(people0.length).to.equal(1); - expect(people0[0].systemId).to.equal(1); - expect(people0[0].system).to.equal('system1'); - expect(people0[0].name).to.equal('Alice'); - - const updates = [ - { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, - { systemId: 1, system: 'system2', name: 'Bob' } - ]; - - const people = await Person.bulkCreate(updates, { - updateOnDuplicate: ['systemId', 'system', 'name'] - }); - expect(people.length).to.equal(2); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].systemId).to.equal(1); - expect(people[1].system).to.equal('system2'); - expect(people[1].name).to.equal('Bob'); - }); - }); - - - it('should reject for non array updateOnDuplicate option', async function() { - const data = [ - { uniqueName: 'Peter', secretValue: '42' }, - { uniqueName: 'Paul', secretValue: '23' } - ]; - - 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', async function() { - const data = [ - { uniqueName: 'Peter', secretValue: '42' }, - { uniqueName: 'Paul', secretValue: '23' } - ]; - - await expect( - this.User.bulkCreate(data, { updateOnDuplicate: [] }) - ).to.be.rejectedWith('updateOnDuplicate option only supports non-empty array.'); - }); - }); - } - - if (current.dialect.supports.returnValues) { - describe('return values', () => { - it('should make the auto incremented values available on the returned instances', async function() { - const User = this.sequelize.define('user', {}); - - 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', async function() { - const User = this.sequelize.define('user', { - maId: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'yo_id' - } - }); - - await User - .sync({ force: true }); - - const users0 = await User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - }); - - const actualUsers0 = await User.findAll({ order: ['maId'] }); - const [users, actualUsers] = [users0, actualUsers0]; - expect(users.length).to.eql(actualUsers.length); - users.forEach((user, i) => { - expect(user.get('maId')).to.be.ok; - expect(user.get('maId')).to.equal(actualUsers[i].get('maId')) - .and.to.equal(i + 1); - }); - }); - - it('should only return fields that are not defined in the model (with returning: true)', async function() { - const User = this.sequelize.define('user'); - - await User - .sync({ force: true }); - - await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); - - const users0 = await User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - }); - - const actualUsers0 = await User.findAll(); - const [users, actualUsers] = [users0, actualUsers0]; - expect(users.length).to.eql(actualUsers.length); - users.forEach(user => { - expect(user.get()).not.to.have.property('not_on_model'); - }); - }); - - it('should return fields that are not defined in the model (with returning: ["*"])', async function() { - const User = this.sequelize.define('user'); - - await User - .sync({ force: true }); - - await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); - - const users0 = await User.bulkCreate([ - {}, - {}, - {} - ], { - returning: ['*'] - }); - - const actualUsers0 = await User.findAll(); - const [users, actualUsers] = [users0, actualUsers0]; - expect(users.length).to.eql(actualUsers.length); - users.forEach(user => { - expect(user.get()).to.have.property('not_on_model'); - }); - }); - }); - } - - describe('enums', () => { - it('correctly restores enum values', async function() { - const Item = this.sequelize.define('Item', { - state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }, - name: Sequelize.STRING - }); - - 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', async function() { - const Maya = this.sequelize.define('Maya', { - name: Sequelize.STRING, - secret: { - field: 'secret_given', - type: Sequelize.STRING - }, - createdAt: { - field: 'created_at', - type: Sequelize.DATE - }, - updatedAt: { - field: 'updated_at', - type: Sequelize.DATE - } - }); - - 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' }; - - 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', async function() { - const Maya = this.sequelize.define('Maya', {}); - - const M1 = {}; - const M2 = {}; - - 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', async function() { - const User = this.sequelize.define('user', {}); - - 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', async function() { - const User = this.sequelize.define('user', {}); - - 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); - }); - }); - - describe('virtual attribute', () => { - beforeEach(function() { - this.User = this.sequelize.define('user', { - password: { - type: Sequelize.VIRTUAL, - validate: { - customValidator: () => { - throw new Error('always invalid'); - } - } - } - }); - }); - - it('should validate', async function() { - try { - await this.User - .sync({ force: true }); - - await this.User.bulkCreate([ - { password: 'password' } - ], { 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', 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 deleted file mode 100644 index 34d24fe12fdd..000000000000 --- a/test/integration/model/bulk-create/include.test.js +++ /dev/null @@ -1,633 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('bulkCreate', () => { - describe('include', () => { - it('should bulkCreate data for BelongsTo relations', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }, { - hooks: { - afterBulkCreate(products) { - products.forEach(product => { - product.isIncludeCreatedOnAfterCreate = !!(product.User && product.User.id); - }); - } - } - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }, { - hooks: { - beforeBulkCreate(users, options) { - users.forEach(user => { - user.createOptions = options; - }); - } - } - }); - - Product.belongsTo(User); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING - }); - - Product.belongsTo(User, { - foreignKey: { - allowNull: false - } - }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - const Creator = Product.belongsTo(User, { as: 'creator' }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }, { - hooks: { - afterBulkCreate(products) { - products.forEach(product => { - product.areIncludesCreatedOnAfterCreate = product.Tags && - product.Tags.every(tag => { - return !!tag.id; - }); - }); - } - } - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }, { - hooks: { - afterBulkCreate(tags, options) { - tags.forEach(tag => tag.createOptions = options); - } - } - }); - - Product.hasMany(Tag); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }); - - const Categories = Product.hasMany(Tag, { as: 'categories' }); - - 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] - }), - 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING - }); - - User.hasOne(Task); - - 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING - }); - - const Job = User.hasOne(Task, { as: 'job' }); - - - 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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - hooks: { - afterBulkCreate(users) { - users.forEach(user => { - user.areIncludesCreatedOnAfterCreate = user.Tasks && - user.Tasks.every(task => { - return !!task.id; - }); - }); - } - } - }); - - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }, { - hooks: { - afterBulkCreate(tasks, options) { - tasks.forEach(task => { - task.createOptions = options; - }); - } - } - }); - - User.belongsToMany(Task, { through: 'user_task' }); - Task.belongsToMany(User, { through: 'user_task' }); - - 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', async function() { - const Post = this.sequelize.define('Post', { - title: DataTypes.STRING - }, { - tableName: 'posts', - underscored: true - }); - - const Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { - tableName: 'tags', - underscored: true - }); - - const ItemTag = this.sequelize.define('ItemTag', { - tag_id: { - type: DataTypes.INTEGER, - references: { - model: 'tags', - key: 'id' - } - }, - taggable_id: { - type: DataTypes.INTEGER, - references: null - }, - taggable: { - type: DataTypes.STRING - } - }, { - tableName: 'item_tag', - underscored: true - }); - - Post.belongsToMany(Tag, { - as: 'tags', - foreignKey: 'taggable_id', - constraints: false, - through: { - model: ItemTag, - scope: { - taggable: 'post' - } - } - }); - - Tag.belongsToMany(Post, { - as: 'posts', - foreignKey: 'tag_id', - constraints: false, - through: { - model: ItemTag, - scope: { - taggable: '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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }); - - const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); - Task.belongsToMany(User, { through: 'user_job' }); - - 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] - }), - 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 deleted file mode 100644 index f10257e956e2..000000000000 --- a/test/integration/model/count.test.js +++ /dev/null @@ -1,202 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('count', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }); - this.Project = this.sequelize.define('Project', { - name: DataTypes.STRING - }); - - this.User.hasMany(this.Project); - this.Project.belongsTo(this.User); - - await this.sequelize.sync({ force: true }); - }); - - it('should count rows', async function() { - await this.User.bulkCreate([ - { username: 'foo' }, - { username: 'bar' } - ]); - - await expect(this.User.count()).to.eventually.equal(2); - }); - - it('should support include', async function() { - await this.User.bulkCreate([ - { username: 'foo' }, - { username: 'bar' } - ]); - - 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', async function() { - await this.User.bulkCreate([ - { username: 'foo' }, - { username: 'bar' }, - { - username: 'valak', - createdAt: new Date().setFullYear(2015) - } - ]); - - const users = await this.User.count({ - attributes: ['createdAt'], - group: ['createdAt'] - }); - - expect(users.length).to.be.eql(2); - expect(users[0].createdAt).to.exist; - expect(users[1].createdAt).to.exist; - }); - - it('should not return NaN', async function() { - await this.User.bulkCreate([ - { username: 'valak', age: 10 }, - { username: 'conjuring', age: 20 }, - { username: 'scary', age: 10 } - ]); - - const result = await this.User.count({ - where: { age: 10 }, - group: ['age'], - order: ['age'] - }); - - // 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()', async function() { - await this.User.bulkCreate([ - { username: 'ember', age: 10 }, - { username: 'angular', age: 20 }, - { username: 'mithril', age: 10 } - ]); - - const count0 = await this.User.count({ col: 'username' }); - expect(count0).to.be.eql(3); - - const count = await this.User.count({ - col: 'age', - distinct: true - }); - - expect(count).to.be.eql(2); - }); - - it('should be able to specify NO column for COUNT() with DISTINCT', 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], - where: { - '$Projects.name$': 'project1' - } - }; - - await this.User.bulkCreate([ - { username: 'foo' }, - { username: 'bar' } - ]); - - 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', async function() { - await this.User.bulkCreate([ - { username: 'ember', age: 10 }, - { username: 'angular', age: 20 }, - { username: 'mithril', age: 10 } - ]); - - const count0 = await this.User.count({ - col: 'username', - distinct: true, - include: [this.Project] - }); - - 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', async function() { - const Post = this.sequelize.define('Post', {}); - this.User.hasMany(Post); - 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 deleted file mode 100644 index d495ceb7a933..000000000000 --- a/test/integration/model/create.test.js +++ /dev/null @@ -1,1482 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../index'), - 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, - pTimeout = require('p-timeout'); - -describe(Support.getTestDialectTeaser('Model'), () => { - 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', 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', 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.STRING, - unique: true - }, - username: { - type: DataTypes.STRING, - unique: true - } - }); - - 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.STRING, - unique: true - }, - username: { - type: DataTypes.STRING, - unique: true - } - }); - - 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.STRING, - unique: true - }, - username: { - type: DataTypes.STRING, - unique: true - } - }); - - await User.sync({ force: true }); - await User.create({ username: 'gottlieb' }); - - try { - await User.findOrCreate({ - where: { - [Op.or]: [{ - objectId: 'asdasdasd1' - }, { - objectId: 'asdasdasd2' - }] - }, - defaults: { - username: 'gottlieb' - } - }); - } 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', async function() { - const User = this.sequelize.define('User', { - id: { - type: DataTypes.UUID, - primaryKey: true, - allowNull: false, - defaultValue: DataTypes.UUIDV4 - }, - name: { - type: DataTypes.STRING - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - email: { - type: DataTypes.STRING, - unique: 'company_user_email' - }, - companyId: { - type: DataTypes.INTEGER, - unique: 'company_user_email' - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - email: { - type: DataTypes.STRING, - unique: 'company_user_email' - }, - companyId: { - type: DataTypes.INTEGER, - unique: 'company_user_email' - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - email: { - type: DataTypes.STRING, - unique: 'company_user_email' - }, - companyId: { - type: DataTypes.INTEGER, - unique: 'company_user_email' - } - }); - - 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.INTEGER, - unique: true - }, - description: { - type: DataTypes.TEXT - } - }); - - 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.INTEGER, - unique: true - }, - bool: DataTypes.BOOLEAN - }); - - await User.sync({ force: true }); - - await User.findOrCreate({ - where: { - objectId: 1 - }, - defaults: { - bool: false - } - }); - }); - - it('returns instance if already existent. Single find field.', async function() { - const data = { - username: 'Username' - }; - - 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.', async function() { - const data = { - username: 'Username', - data: 'ThisIsData' - }; - - 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', async function() { - const data = { - username: 'Username', - data: 'ThisIsData' - }; - - const [user0] = await this.User.findOrCreate({ - where: data, - defaults: {} - }); - - expect(user0.dataValues.sequelize_caught_exception).to.be.undefined; - - const [user] = await this.User.findOrCreate({ - where: data, - defaults: {} - }); - - expect(user.dataValues.sequelize_caught_exception).to.be.undefined; - }); - - it('creates new instance with default value.', async function() { - const data = { - username: 'Username' - }, - default_values = { - data: 'ThisIsData' - }; - - 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)', async function() { - const [user, created] = await this.User.findOrCreate({ - where: Sequelize.or({ username: 'Fooobzz' }, { secretValue: 'Yolo' }), - defaults: { username: 'Fooobzz', secretValue: 'Yolo' } - }); - - expect(user.username).to.equal('Fooobzz'); - expect(user.secretValue).to.equal('Yolo'); - expect(created).to.be.true; - }); - - it('should ignore option returning', async function() { - const [user, created] = await this.User.findOrCreate({ - where: { username: 'Username' }, - defaults: { data: 'ThisIsData' }, - returning: false - }); - - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(created).to.be.true; - }); - - if (current.dialect.supports.transactions) { - it('should release transaction when meeting errors', async function() { - const test = async times => { - if (times > 10) { - return true; - } - - 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; - } - }; - - await test(0); - }); - } - - describe('several concurrent calls', () => { - if (current.dialect.supports.transactions) { - 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', async function() { - const User = this.sequelize.define('user', { - username: { - type: DataTypes.STRING, - unique: true, - field: 'user_name' - } - }); - - User.beforeCreate(instance => { - instance.set('username', instance.get('username').trim()); - }); - - const spy = sinon.spy(); - - const names = [ - 'mick ', - 'mick ', - 'mick ', - 'mick ', - 'mick ', - 'mick ', - 'mick ' - ]; - - 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', async function() { - const User = this.sequelize.define('user', { - objectId: { - type: DataTypes.STRING, - unique: true - }, - username: { - type: DataTypes.STRING, - unique: true - } - }); - - await User.sync({ force: true }); - - await User.create({ - username: 'gottlieb' - }); - - return Promise.all([(async () => { - try { - await User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - }); - - throw new Error('I should have ben rejected'); - } catch (err) { - 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' - } - }); - - 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', async function() { - const [first, second] = await Promise.all([ - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }) - ]); - - 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); - }); - }); - }); - - describe('findCreateFind', () => { - (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', async function() { - const [first, second, third] = await Promise.all([ - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }) - ]); - - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1], - thirdInstance = third[0], - thirdCreated = third[1]; - - expect([firstCreated, secondCreated, thirdCreated].filter(value => { - return value; - }).length).to.equal(1); - - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - expect(thirdInstance).to.be.ok; - - expect(firstInstance.id).to.equal(secondInstance.id); - expect(secondInstance.id).to.equal(thirdInstance.id); - }); - }); - - describe('create', () => { - it('works with multiple non-integer primary keys with a default value', async function() { - const User = this.sequelize.define('User', { - 'id1': { - primaryKey: true, - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4 - }, - 'id2': { - primaryKey: true, - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4 - }, - 'email': { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4 - } - }); - - 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', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: DataTypes.STRING, - unique: { name: 'email', msg: 'Email is already registered.' }, - validate: { - notEmpty: true, - isEmail: true - } - } - }); - - 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', async function() { - const Log = this.sequelize.define('log', { - level: DataTypes.STRING - }); - - Log.removeAttribute('id'); - - 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', async function() { - const User = this.sequelize.define('user', { - name: DataTypes.STRING - }, { - timestamps: true - }); - - const createdAt = new Date(2012, 10, 10, 10, 10, 10); - const updatedAt = new Date(2011, 11, 11, 11, 11, 11); - - 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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING, - date_of_birth: DataTypes.DATE, - email: DataTypes.STRING, - password: DataTypes.STRING, - created_time: { - type: DataTypes.DATE, - allowNull: true, - defaultValue: DataTypes.NOW - }, - updated_time: { - type: DataTypes.DATE, - allowNull: true, - defaultValue: DataTypes.NOW - } - }, { - createdAt: 'created_time', - updatedAt: 'updated_time', - tableName: 'users', - underscored: true, - freezeTableName: true, - force: false - }); - - 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', async function() { - const User = this.sequelize.define('User', { - - }, { - createdAt: 'createdAt', - updatedAt: 'updatedAt', - underscored: true - }); - - 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; - }); - - if (current.dialect.supports.transactions) { - 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', async function() { - const User = this.sequelize.define('user', {}); - - 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', async function() { - const User = this.sequelize.define('user', { - maId: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'yo_id' - } - }); - - 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', async function() { - const type = dialect === 'mysql' || dialect === 'mariadb' ? 'signed' : 'integer'; - let match = false; - - 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; - } - }); - - 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', async function() { - let type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer'), - match = false; - - if (dialect === 'mysql' || dialect === 'mariadb') { - type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed'); - } - - const user = await this.User.create({ - intVal: type - }, { - logging(sql) { - if (dialect === 'mysql' || dialect === 'mariadb') { - expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)'); - } else { - expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)'); - } - match = 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', async function() { - const user = await this.User.create({ - intVal: this.sequelize.literal(`CAST(1-2 AS ${dialect === 'mysql' ? 'SIGNED' : 'INTEGER'})`) - }); - - const user0 = await this.User.findByPk(user.id); - expect(user0.intVal).to.equal(-1); - }); - - it('is possible to use funtions when creating an instance', async function() { - const user = await this.User.create({ - secretValue: this.sequelize.fn('upper', 'sequelize') - }); - - const user0 = await this.User.findByPk(user.id); - expect(user0.secretValue).to.equal('SEQUELIZE'); - }); - - it('should escape $ in sequelize functions arguments', async function() { - const user = await this.User.create({ - secretValue: this.sequelize.fn('upper', '$sequelize') - }); - - const user0 = await this.User.findByPk(user.id); - expect(user0.secretValue).to.equal('$SEQUELIZE'); - }); - - it('should work with a non-id named uuid primary key columns', async function() { - const Monkey = this.sequelize.define('Monkey', { - monkeyId: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, allowNull: false } - }); - - 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', async function() { - let userWithDefaults; - - if (dialect.startsWith('postgres')) { - 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 - // to enclose the date function in (). http://www.sqlite.org/syntaxdiagrams.html#column-constraint - userWithDefaults = this.sequelize.define('userWithDefaults', { - year: { - type: Sequelize.STRING, - defaultValue: this.sequelize.fn('', this.sequelize.fn('date', 'now')) - } - }); - - 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 - }); - - if (dialect === 'postgres') { - 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; - 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', async function() { - const User = this.sequelize.define('UserWithArray', { - myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) }, - mystr: { type: Sequelize.ARRAY(Sequelize.STRING) } - }); - let test = false; - - 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", async function() { - const User = this.sequelize.define('UserWithUniqueUsername', { - username: { type: Sequelize.STRING, unique: true } - }); - - 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", async function() { - const User = this.sequelize.define('UserWithUniqueCITEXT', { - username: { type: Sequelize.CITEXT, unique: true } - }); - - 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", async function() { - const User = this.sequelize.define('UserWithUniqueUsernameFunctionIndex', { - username: Sequelize.STRING, - email: { type: Sequelize.STRING, unique: true } - }); - - try { - await User.sync({ force: true }); - const tableName = User.getTableName(); - 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', async function() { - const UserNull = this.sequelize.define('UserWithNonNullSmth', { - username: { type: Sequelize.STRING, unique: true }, - smth: { type: Sequelize.STRING, allowNull: false } - }); - - this.sequelize.options.omitNull = false; - - await UserNull.sync({ force: true }); - - try { - await UserNull.create({ username: 'foo2', smth: null }); - } catch (err) { - expect(err).to.exist; - - 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', async function() { - const UserNull = this.sequelize.define('UserWithNonNullSmth', { - username: { type: Sequelize.STRING, unique: true }, - smth: { type: Sequelize.STRING, allowNull: false } - }); - - this.sequelize.options.omitNull = false; - - 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', async function() { - const StringIsNullOrUrl = this.sequelize.define('StringIsNullOrUrl', { - str: { type: Sequelize.STRING, allowNull: true, validate: { isURL: true } } - }); - - this.sequelize.options.omitNull = false; - - 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() { - expect(() => { - this.sequelize.define('UserBadDataType', { - activity_date: Sequelize.DATe - }); - }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"'); - - expect(() => { - this.sequelize.define('UserBadDataType', { - activity_date: { type: Sequelize.DATe } - }); - }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"'); - }); - - it('sets a 64 bit int in bigint', async function() { - const User = this.sequelize.define('UserWithBigIntFields', { - big: Sequelize.BIGINT - }); - - await User.sync({ force: true }); - const user = await User.create({ big: '9223372036854775807' }); - expect(user.big).to.be.equal('9223372036854775807'); - }); - - it('sets auto increment fields', async function() { - const User = this.sequelize.define('UserWithAutoIncrementField', { - userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false } - }); - - 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', async function() { - const User = this.sequelize.define('UserWithNameAndOptions', { - name: Sequelize.STRING, - options: Sequelize.TEXT - }); - - const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - - await User.sync({ force: true }); - - const user = await User - .create({ name: 'John Doe', options }); - - expect(user.options).to.equal(options); - }); - - 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; - 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', async function() { - const data = { username: 'Peter', secretValue: '42' }; - - 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', async function() { - const data = { username: 'Peter', secretValue: '42' }; - - 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', async function() { - const data = { title: 'Iliad' }, - dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT], - sync = [], - promises = [], - books = []; - - dataTypes.forEach((dataType, index) => { - books[index] = this.sequelize.define(`Book${index}`, { - id: { type: dataType, primaryKey: true, autoIncrement: true }, - title: Sequelize.TEXT - }); - }); - - books.forEach(b => { - sync.push(b.sync({ force: true })); - }); - - 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', async function() { - const quote = "single'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', async function() { - const quote = 'double"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', async function() { - const json = JSON.stringify({ key: 'value' }); - - 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', 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', 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)', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - await Worker.sync(); - const worker = await Worker.create({}, { fields: [] }); - expect(worker).to.be.ok; - }); - - it('should allow truly blank creates', async function() { - const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - await Worker.sync(); - const worker = await Worker.create({}, { fields: [] }); - expect(worker).to.be.ok; - }); - - it('should only set passed fields', async function() { - const User = this.sequelize.define('User', { - 'email': { - type: DataTypes.STRING - }, - 'name': { - type: DataTypes.STRING - } - }); - - 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', async function() { - const Task = this.sequelize.define('task', { - title: DataTypes.STRING - }); - 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', async function() { - const Item = this.sequelize.define('Item', { - state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] } - }); - - 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', async function() { - const Enum = this.sequelize.define('Enum', { - state: { - type: Sequelize.ENUM, - values: ['happy', 'sad'], - allowNull: true - } - }); - - 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', async function() { - const Enum = this.sequelize.define('Enum', { - state: Sequelize.ENUM('happy', 'sad') - }); - - await Enum.sync({ force: true }); - - await Enum.create({ state: 'happy' }); - }); - - it('allows values passed as an array', async function() { - const Enum = this.sequelize.define('Enum', { - state: Sequelize.ENUM(['happy', 'sad']) - }); - - await Enum.sync({ force: true }); - - await Enum.create({ state: 'happy' }); - }); - }); - - describe('when defined via { field: { type: Sequelize.ENUM } }', () => { - it('allows values passed as parameters', async function() { - const Enum = this.sequelize.define('Enum', { - state: { - type: Sequelize.ENUM('happy', 'sad') - } - }); - - await Enum.sync({ force: true }); - - await Enum.create({ state: 'happy' }); - }); - - it('allows values passed as an array', async function() { - const Enum = this.sequelize.define('Enum', { - state: { - type: Sequelize.ENUM(['happy', 'sad']) - } - }); - - await Enum.sync({ force: true }); - - await Enum.create({ state: 'happy' }); - }); - }); - - describe('can safely sync multiple times', () => { - it('through the factory', async function() { - const Enum = this.sequelize.define('Enum', { - state: { - type: Sequelize.ENUM, - values: ['happy', 'sad'], - allowNull: true - } - }); - - await Enum.sync({ force: true }); - await Enum.sync(); - - await Enum.sync({ force: true }); - }); - - it('through sequelize', async function() { - this.sequelize.define('Enum', { - state: { - type: Sequelize.ENUM, - values: ['happy', 'sad'], - allowNull: true - } - }); - - await this.sequelize.sync({ force: true }); - await this.sequelize.sync(); - - await this.sequelize.sync({ force: true }); - }); - }); - }); - }); - - it('should return autoIncrement primary key (create)', async function() { - const Maya = this.sequelize.define('Maya', {}); - - const M1 = {}; - - await Maya.sync({ force: true }); - const m = await Maya.create(M1, { returning: true }); - expect(m.id).to.be.eql(1); - }); - - it('should support logging', async function() { - const spy = sinon.spy(); - - await this.User.create({}, { - logging: spy - }); - - 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 deleted file mode 100644 index 01b651523d6d..000000000000 --- a/test/integration/model/create/include.test.js +++ /dev/null @@ -1,469 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('create', () => { - describe('include', () => { - it('should create data for BelongsTo relations', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }, { - hooks: { - afterCreate(product) { - product.isIncludeCreatedOnAfterCreate = !!(product.User && product.User.id); - } - } - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }, { - hooks: { - beforeCreate(user, options) { - user.createOptions = options; - } - } - }); - - Product.belongsTo(User); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING - }); - - Product.belongsTo(User, { - foreignKey: { - allowNull: false - } - }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const User = this.sequelize.define('User', { - first_name: Sequelize.STRING, - last_name: Sequelize.STRING - }); - - const Creator = Product.belongsTo(User, { as: 'creator' }); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }, { - hooks: { - afterCreate(product) { - product.areIncludesCreatedOnAfterCreate = product.Tags && - product.Tags.every(tag => { - return !!tag.id; - }); - } - } - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }, { - hooks: { - afterCreate(tag, options) { - tag.createOptions = options; - } - } - }); - - Product.hasMany(Tag); - - 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', async function() { - const Product = this.sequelize.define('Product', { - title: Sequelize.STRING - }); - const Tag = this.sequelize.define('Tag', { - name: Sequelize.STRING - }); - - const Categories = Product.hasMany(Tag, { as: 'categories' }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING - }); - - User.hasOne(Task); - - 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING - }); - - const Task = this.sequelize.define('Task', { - title: Sequelize.STRING - }); - - const Job = User.hasOne(Task, { as: 'job' }); - - - 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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - hooks: { - afterCreate(user) { - user.areIncludesCreatedOnAfterCreate = user.Tasks && - user.Tasks.every(task => { - return !!task.id; - }); - } - } - }); - - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }, { - hooks: { - afterCreate(task, options) { - task.createOptions = options; - } - } - }); - - User.belongsToMany(Task, { through: 'user_task' }); - Task.belongsToMany(User, { through: 'user_task' }); - - 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', async function() { - const Post = this.sequelize.define('Post', { - title: DataTypes.STRING - }, { - tableName: 'posts', - underscored: true - }); - - const Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { - tableName: 'tags', - underscored: true - }); - - const ItemTag = this.sequelize.define('ItemTag', { - tag_id: { - type: DataTypes.INTEGER, - references: { - model: 'tags', - key: 'id' - } - }, - taggable_id: { - type: DataTypes.INTEGER, - references: null - }, - taggable: { - type: DataTypes.STRING - } - }, { - tableName: 'item_tag', - underscored: true - }); - - Post.belongsToMany(Tag, { - as: 'tags', - foreignKey: 'taggable_id', - constraints: false, - through: { - model: ItemTag, - scope: { - taggable: 'post' - } - } - }); - - Tag.belongsToMany(Post, { - as: 'posts', - foreignKey: 'tag_id', - constraints: false, - through: { - model: ItemTag, - scope: { - taggable: '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', async function() { - const User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - - const Task = this.sequelize.define('Task', { - title: DataTypes.STRING, - active: DataTypes.BOOLEAN - }); - - const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); - Task.belongsToMany(User, { through: 'user_job' }); - - 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 deleted file mode 100644 index 380a92e3f23d..000000000000 --- a/test/integration/model/findAll.test.js +++ /dev/null @@ -1,1684 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - Op = Sequelize.Op, - DataTypes = require('../../../lib/data-types'), - dialect = Support.getTestDialect(), - _ = require('lodash'), - moment = require('moment'), - current = Support.sequelize, - promiseProps = require('p-props'); - -describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN, - binary: DataTypes.STRING(16, true) - }); - - await this.User.sync({ force: true }); - }); - - describe('findAll', () => { - if (current.dialect.supports.transactions) { - 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', 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(async function() { - this.buf = Buffer.alloc(16); - this.buf.fill('\x01'); - - 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', async function() { - const users = await this.User.findAll({ - where: { - username: ['boo', 'boo2'] - } - }); - - expect(users).to.have.length(2); - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - binary: [this.buf, this.buf] - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - username: { - [Op.like]: '%2' - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - username: { - [Op.notLike]: '%2' - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - username: { - [Op.iLike]: '%2' - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - username: { - [Op.notILike]: '%2' - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - theDate: { - [Op.between]: ['2013-01-02', '2013-01-11'] - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - intVal: { - [Op.notBetween]: [8, 10] - } - } - }); - - 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...', async function() { - const User = this.User; - - await User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]); - - 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...', async function() { - const User = this.User, - Passports = this.sequelize.define('Passports', { - isActive: Sequelize.BOOLEAN - }); - - User.hasMany(Passports); - Passports.belongsTo(User); - - await User.sync({ force: true }); - await Passports.sync({ force: true }); - - await User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]); - - await Passports.bulkCreate([ - { isActive: true }, - { isActive: false } - ]); - - 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: { - type: DataTypes.STRING(16, true), - primaryKey: true - } - }); - - const buf1 = this.buf; - const buf2 = Buffer.alloc(16); - buf2.fill('\x02'); - - User.belongsTo(Binary, { foreignKey: 'binary' }); - - await this.sequelize.sync({ force: true }); - - await User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]); - - await Binary.bulkCreate([ - { id: buf1 }, - { id: buf2 } - ]); - - const user = await User.findByPk(1); - const binary = await Binary.findByPk(buf1); - await user.setBinary(binary); - const _user = await User.findByPk(2); - const _binary = await Binary.findByPk(buf2); - await _user.setBinary(_binary); - const _binaryRetrieved = await _user.getBinary(); - const binaryRetrieved = await user.getBinary(); - expect(binaryRetrieved.id).to.have.length(16); - expect(_binaryRetrieved.id).to.have.length(16); - expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString()); - expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString()); - }); - - it('should be able to find a row between a certain date', async function() { - const users = await this.User.findAll({ - where: { - theDate: { - [Op.between]: ['2013-01-02', '2013-01-11'] - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - theDate: { - [Op.between]: ['2013-01-02', '2013-01-11'] - }, - intVal: 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', async function() { - const users = await this.User.findAll({ - where: { - intVal: { - [Op.notBetween]: [8, 10] - } - } - }); - - 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', 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'] - } - } - }); - - 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', 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')] - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - theDate: { - [Op.gte]: new Date('2013-01-09') - } - } - }); - - 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', async function() { - const user = await this.User.findOne({ - where: { - intVal: { - [Op.gte]: 6 - } - } - }); - - expect(user.username).to.equal('boo2'); - expect(user.intVal).to.equal(10); - }); - - it('should be able to find a row using greater than', async function() { - const user = await this.User.findOne({ - where: { - intVal: { - [Op.gt]: 5 - } - } - }); - - 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', async function() { - const user = await this.User.findOne({ - where: { - intVal: { - [Op.lte]: 5 - } - } - }); - - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); - }); - - it('should be able to find a row using lesser than', async function() { - const user = await this.User.findOne({ - where: { - intVal: { - [Op.lt]: 6 - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - intVal: { - [Op.lt]: 6, - [Op.gt]: 4 - } - } - }); - - 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', async function() { - const user = await this.User.findOne({ - where: { - intVal: { - [Op.ne]: 10 - } - } - }); - - 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', async function() { - const users = await this.User.findAll({ - where: { - intVal: { - [Op.lte]: 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', async function() { - const User = this.sequelize.define('UsersWithCaseInsensitiveName', { - username: Sequelize.CITEXT - }); - - 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', 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 } - ]); - - const users = await this.User.findAll({ - where: { - intVal: 8 - }, - include: [] - }); - - 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(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); - - 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', 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', 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', async function() { - const tasks = await this.Task.findAll({ - where: { title: 'homework' }, - include: [this.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', async function() { - const tasks = await this.Task.findAll({ - where: { title: 'homework' }, - include: [this.Worker], - limit: 1, - order: [['title', 'DESC']] - }); - - expect(tasks).to.exist; - expect(tasks[0].Worker).to.exist; - expect(tasks[0].Worker.name).to.equal('worker'); - }); - }); - - describe('hasOne', () => { - 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); - 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [this.Task] - }); - - expect(workers).to.exist; - expect(workers[0].TaskHasOne).to.exist; - expect(workers[0].TaskHasOne.title).to.equal('homework'); - }); - }); - - describe('hasOne with alias', () => { - 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' }); - 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', 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDo' }] - }); - - 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDo' }] - }); - - expect(workers[0].ToDo.title).to.equal('homework'); - }); - }); - - describe('hasMany', () => { - 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); - 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [this.Task] - }); - - 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', async function() { - const User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - field: 'some_other_name' - } - }); - const Project = this.sequelize.define('project', { title: Sequelize.STRING }); - User.hasMany(Project); - - 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', async function() { - const User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - field: 'some_other_name' - } - }); - const Project = this.sequelize.define('project', { title: Sequelize.STRING }); - User.hasMany(Project); - - 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', async function() { - const User = this.sequelize.define('user', { - name: { - type: Sequelize.STRING, - field: 'some_other_name' - }, - age: { - type: Sequelize.INTEGER, - field: 'a_g_e' - } - }); - const Project = this.sequelize.define('project', { title: Sequelize.STRING }); - User.hasMany(Project); - - 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(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' }); - 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', 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDos' }] - }); - - 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', async function() { - const workers = await this.Worker.findAll({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDos' }] - }); - - expect(workers[0].ToDos[0].title).to.equal('homework'); - }); - }); - - describe('queryOptions', () => { - beforeEach(async function() { - const user = await this.User.create({ username: 'barfooz' }); - this.user = 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', 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', 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(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 }); - this.Person = this.sequelize.define('person', { name: Sequelize.STRING, lastName: Sequelize.STRING }); - - this.Continent.hasMany(this.Country); - this.Country.belongsTo(this.Continent); - this.Country.belongsToMany(this.Industry, { through: 'country_industry' }); - this.Industry.belongsToMany(this.Country, { through: 'country_industry' }); - this.Country.hasMany(this.Person); - this.Person.belongsTo(this.Country); - this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); - this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - - 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' }) - }); - - _.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 all associations', async function() { - const countries = await this.Country.findAll({ include: [{ all: true }] }); - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].residents).to.exist; - }); - - it('includes specific type of association', async function() { - const countries = await this.Country.findAll({ include: [{ all: 'BelongsTo' }] }); - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).not.to.exist; - expect(countries[0].people).not.to.exist; - expect(countries[0].residents).not.to.exist; - }); - - it('utilises specified attributes', async function() { - const countries = await this.Country.findAll({ include: [{ all: 'HasMany', attributes: ['name'] }] }); - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].people[0]).to.exist; - expect(countries[0].people[0].name).not.to.be.undefined; - expect(countries[0].people[0].lastName).to.be.undefined; - expect(countries[0].residents).to.exist; - expect(countries[0].residents[0]).to.exist; - expect(countries[0].residents[0].name).not.to.be.undefined; - expect(countries[0].residents[0].lastName).to.be.undefined; - }); - - it('is over-ruled by specified include', async function() { - const countries = await this.Country.findAll({ include: [{ all: true }, { model: this.Continent, attributes: ['id'] }] }); - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].continent.name).to.be.undefined; - }); - - it('includes all nested associations', async function() { - const continents = await this.Continent.findAll({ include: [{ all: true, nested: true }] }); - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].industries).to.exist; - expect(continents[0].countries[0].people).to.exist; - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].continent).not.to.exist; - }); - }); - - describe('properly handles attributes:[] cases', () => { - beforeEach(async function() { - this.Animal = this.sequelize.define('Animal', { - name: Sequelize.STRING, - age: Sequelize.INTEGER - }); - this.Kingdom = this.sequelize.define('Kingdom', { - name: Sequelize.STRING - }); - this.AnimalKingdom = this.sequelize.define('AnimalKingdom', { - relation: Sequelize.STRING, - mutation: Sequelize.BOOLEAN - }); - - this.Kingdom.belongsToMany(this.Animal, { through: this.AnimalKingdom }); - - 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: [] - }] - }); - - 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', async function() { - const kingdoms = await this.Kingdom.findAll({ - include: [{ - model: this.Animal, - where: { age: { [Op.gte]: 29 } }, - through: { - attributes: [] - } - }] - }); - - 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', async function() { - const kingdoms = await this.Kingdom.findAll({ - include: [{ - model: this.Animal, - where: { age: { [Op.gte]: 29 } }, - attributes: [], - through: { - attributes: ['mutation'] - } - }] - }); - - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - // include.attributes: [], model doesn't exists - expect(kingdom.Animals).to.not.exist; - }); - }); - }); - }); - - describe('order by eager loaded tables', () => { - describe('HasMany', () => { - 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 }); - - this.Continent.hasMany(this.Country); - this.Country.belongsTo(this.Continent); - this.Country.hasMany(this.Person); - this.Person.belongsTo(this.Country); - this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); - this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - - 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', async function() { - await Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(async params => { - const continents = await this.Continent.findAll({ - order: [['name', params[0]]] - }); - - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - })); - }); - - 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]]] - }); - - 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', async function() { - await Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(async params => { - const continents = await this.Continent.findAll({ - include: [{ - model: this.Country, - required: false, - where: { - name: params[3] - } - }], - limit: 2, - order: [['name', params[0]], [this.Country, 'name', params[0]]] - }); - - 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]]] - }); - - 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', 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]]] - }); - - 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', 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 - }); - - 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(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 }); - - this.Country.belongsToMany(this.Industry, { through: this.IndustryCountry }); - this.Industry.belongsToMany(this.Country, { through: this.IndustryCountry }); - - 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', 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]]] - }); - - 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', 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 - }); - - 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', 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]]] - }); - - 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(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', async function() { - const users = await this.User.findAll(); - expect(users.length).to.equal(2); - }); - - 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', 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', 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', 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', 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', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - Login: { type: Sequelize.STRING } - }); - - 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()', async function() { - const Company = this.sequelize.define('Company', { - name: Sequelize.STRING - }); - - await Company.sync(); - - await Company.findAll({ - order: [this.sequelize.col('name')] - }); - }); - - it('should pull in dependent fields for a VIRTUAL', async function() { - const User = this.sequelize.define('User', { - active: { - type: Sequelize.VIRTUAL(Sequelize.BOOLEAN, ['createdAt']), - get() { - return this.get('createdAt') > Date.now() - 7 * 24 * 60 * 60 * 1000; - } - } - }, { - timestamps: true - }); - - await User.create(); - - const users = await User.findAll({ - attributes: ['active'] - }); - - users.forEach(user => { - expect(user.get('createdAt')).to.be.ok; - expect(user.get('active')).to.equal(true); - }); - }); - - it('should pull in dependent fields for a VIRTUAL in include', async function() { - const User = this.sequelize.define('User', { - name: Sequelize.STRING - }); - - const Image = this.sequelize.define('Image', { - path: { - type: Sequelize.STRING, - allowNull: false - }, - url: { - type: Sequelize.VIRTUAL(Sequelize.STRING, ['path']), - get() { - return `https://my-cool-domain.com/${this.get('path')}`; - } - } - }); - - User.hasOne(Image); - Image.belongsTo(User); - - await this.sequelize.sync({ force: true }); - - await User.create({ - name: 'some user', - Image: { - path: 'folder1/folder2/logo.png' - } - }, { - include: { - model: Image - } - }); - - const users = await User.findAll({ - attributes: ['name'], - include: [{ - model: Image, - attributes: ['url'] - }] - }); - - users.forEach(user => { - expect(user.get('name')).to.equal('some user'); - expect(user.Image.get('url')).to.equal('https://my-cool-domain.com/folder1/folder2/logo.png'); - expect(user.Image.get('path')).to.equal('folder1/folder2/logo.png'); - }); - }); - - it('should throw for undefined where parameters', async function() { - try { - await this.User.findAll({ where: { username: undefined } }); - throw new Error('findAll should throw an error if where has a key with undefined value'); - } catch (err) { - expect(err).to.be.an.instanceof(Error); - expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); - } - }); - }); - }); - - describe('findAndCountAll', () => { - beforeEach(async function() { - await this.User.bulkCreate([ - { username: 'user', data: 'foobar' }, - { username: 'user2', data: 'bar' }, - { username: 'bobby', data: 'foo' } - ]); - - const users = await this.User.findAll(); - this.users = users; - }); - - if (current.dialect.supports.transactions) { - 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}', 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}', 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', 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', 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', 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', async function() { - const Election = this.sequelize.define('Election', { - name: Sequelize.STRING - }); - const Citizen = this.sequelize.define('Citizen', { - name: Sequelize.STRING - }); - - // Associations - Election.belongsTo(Citizen); - Election.belongsToMany(Citizen, { as: 'Voters', through: 'ElectionsVotes' }); - Citizen.hasMany(Election); - Citizen.belongsToMany(Election, { as: 'Votes', through: 'ElectionsVotes' }); - - 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', 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(async function() { - await this.User.bulkCreate([ - { username: 'user', data: 'foobar' }, - { username: 'user2', data: 'bar' } - ]); - }); - - if (current.dialect.supports.transactions) { - 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', async function() { - const users = await this.User.findAll(); - expect(users.length).to.equal(2); - }); - }); - - it('should support logging', async function() { - const spy = sinon.spy(); - - await this.User.findAll({ - where: {}, - logging: spy - }); - - expect(spy.called).to.be.ok; - }); - - describe('rejectOnEmpty mode', () => { - it('works from model options', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }, { - rejectOnEmpty: true - }); - - 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', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }, { - rejectOnEmpty: new Sequelize.ConnectionError('Some Error') //using custom error instance - }); - - 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', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }, { - rejectOnEmpty: Sequelize.ConnectionError //using custom error instance - }); - - 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 deleted file mode 100644 index fac604072b5a..000000000000 --- a/test/integration/model/findAll/group.test.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - Sequelize = Support.Sequelize, - DataTypes = require('../../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('findAll', () => { - describe('group', () => { - 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 } - }); - - const Comment = current.define('Comment', { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - text: { type: DataTypes.STRING, allowNull: false } - }); - - Post.hasMany(Comment); - - 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', async () => { - const Post = current.define('Post', { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }); - - const Comment = current.define('Comment', { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - text: { type: DataTypes.STRING, allowNull: false } - }); - - Post.hasMany(Comment); - Comment.belongsTo(Post); - - 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 deleted file mode 100644 index 0385c7464c23..000000000000 --- a/test/integration/model/findAll/groupedLimit.test.js +++ /dev/null @@ -1,260 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('../../support'), - Sequelize = Support.Sequelize, - DataTypes = require('../../../../lib/data-types'), - current = Support.sequelize, - _ = require('lodash'); - -if (current.dialect.supports['UNION ALL']) { - describe(Support.getTestDialectTeaser('Model'), () => { - describe('findAll', () => { - describe('groupedLimit', () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - this.clock.reset(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('user', { - age: Sequelize.INTEGER - }); - this.Project = this.sequelize.define('project', { - title: DataTypes.STRING - }); - this.Task = this.sequelize.define('task'); - - this.ProjectUserParanoid = this.sequelize.define('project_user_paranoid', {}, { - timestamps: true, - paranoid: true, - createdAt: false, - updatedAt: false - }); - - this.User.Projects = this.User.belongsToMany(this.Project, { through: 'project_user' }); - this.Project.belongsToMany(this.User, { as: 'members', through: 'project_user' }); - - this.User.ParanoidProjects = this.User.belongsToMany(this.Project, { through: this.ProjectUserParanoid }); - this.Project.belongsToMany(this.User, { as: 'paranoidMembers', through: this.ProjectUserParanoid }); - - this.User.Tasks = this.User.hasMany(this.Task); - - 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', async function() { - const users = await this.User.findAll({ - groupedLimit: { - limit: 3, - on: this.User.Projects, - values: this.projects.map(item => item.get('id')) - } - }); - - 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', async function() { - const users = await this.User.findAll({ - groupedLimit: { - limit: 3, - on: this.User.Projects, - values: this.projects.map(item => item.get('id')) - }, - order: ['id'], - include: [this.User.Tasks] - }); - - /* - 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', async function() { - const users = await this.User.findAll({ - attributes: ['id'], - groupedLimit: { - limit: 3, - on: this.User.Projects, - values: this.projects.map(item => item.get('id')) - }, - order: [ - Sequelize.fn('ABS', Sequelize.col('age')) - ], - include: [this.User.Tasks] - }); - - /* - 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', async function() { - const users = await this.User.findAll({ - attributes: ['id'], - groupedLimit: { - limit: 3, - on: this.User.Projects, - values: this.projects.map(item => item.get('id')) - }, - order: [ - Sequelize.fn('ABS', Sequelize.col('age')), - ['id', 'DESC'] - ], - include: [this.User.Tasks] - }); - - /* - 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', async function() { - const users0 = 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, 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(async function() { - this.User = this.sequelize.define('user'); - this.Task = this.sequelize.define('task'); - this.User.Tasks = this.User.hasMany(this.Task); - - 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', async function() { - const tasks = await this.Task.findAll({ - order: [ - ['id', 'DESC'] - ], - groupedLimit: { - limit: 3, - on: this.User.Tasks, - values: this.users.map(item => item.get('id')) - } - }); - - 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 deleted file mode 100644 index 35bfd50523c0..000000000000 --- a/test/integration/model/findAll/order.test.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('findAll', () => { - describe('order', () => { - describe('Sequelize.literal()', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - email: DataTypes.STRING - }); - - 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()', async function() { - const users = await this.User.findAll({ - order: this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`) - }); - - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); - }); - - 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')}`)] - }); - - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); - }); - - 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')}`)] - ] - }); - - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); - }); - } - }); - - describe('injections', () => { - beforeEach(async function() { - this.User = this.sequelize.define('user', { - name: DataTypes.STRING - }); - this.Group = this.sequelize.define('group', { - - }); - this.User.belongsTo(this.Group); - await this.sequelize.sync({ force: true }); - }); - - if (current.dialect.supports['ORDER NULLS']) { - it('should not throw with on NULLS LAST/NULLS FIRST', async function() { - await this.User.findAll({ - include: [this.Group], - order: [ - ['id', 'ASC NULLS LAST'], - [this.Group, 'id', 'DESC NULLS FIRST'] - ] - }); - }); - } - - 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', 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 deleted file mode 100644 index d81a848fcf89..000000000000 --- a/test/integration/model/findAll/separate.test.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const DataTypes = require('../../../../lib/data-types'); -const current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('findAll', () => { - describe('separate with limit', () => { - 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 }); - const LevelThree = current.define('LevelThree', { type: DataTypes.INTEGER }); - - Project.hasMany(LevelTwo); - LevelTwo.belongsTo(Project); - - LevelTwo.hasMany(LevelThree, { as: 'type_ones' }); - LevelTwo.hasMany(LevelThree, { as: 'type_twos' }); - LevelThree.belongsTo(LevelTwo); - - 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 - const projects = 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']] - }, - { - model: LevelThree, - as: 'type_twos', - where: { type: 1 }, - separate: true, - limit: 1, - order: [['createdAt', 'DESC']] - } - ] - } - ] - }); - - 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 deleted file mode 100644 index 6f1dcc07b327..000000000000 --- a/test/integration/model/findOne.test.js +++ /dev/null @@ -1,980 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN - }); - - await this.User.sync({ force: true }); - }); - - describe('findOne', () => { - if (current.dialect.supports.transactions) { - 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(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', async function() { - let bitUser = this.sequelize.define('bituser', { - bool: 'BIT(1)' - }, { - timestamps: false - }); - - // First use a custom data type def to create the bit field - 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', async function() { - let test = false; - - await this.UserPrimary.findOne({ - where: { 'specialkey': 'awesome' }, - logging(sql) { - test = true; - expect(sql).to.match(/WHERE ["|`|[]UserPrimary["|`|\]]\.["|`|[]specialkey["|`|\]] = N?'awesome'/); - } - }); - - expect(test).to.be.true; - }); - - 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', 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', 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', async function() { - const user = await this.User.findOne({ - where: { id: 1 }, - attributes: ['id', ['username', 'name']] - }); - - expect(user.dataValues.name).to.equal('barfooz'); - }); - - it('should fail with meaningful error message on invalid attributes definition', function() { - expect(this.User.findOne({ - where: { id: 1 }, - attributes: ['id', ['username']] - })).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', async function() { - const UserWithBoolean = this.sequelize.define('UserBoolean', { - active: Sequelize.BOOLEAN - }); - - 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', 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', async function() { - const user = await this.User.findOne({ where: { username: 'foo' } }); - expect(user).to.be.null; - }); - - it('allows sql logging', async function() { - let test = false; - - await this.User.findOne({ - where: { username: 'foo' }, - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }); - - expect(test).to.be.true; - }); - - 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', async function() { - const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { - identifier: { type: Sequelize.STRING, primaryKey: true }, - name: Sequelize.STRING - }); - - 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', async function() { - const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { - id: { type: Sequelize.STRING, primaryKey: true }, - name: Sequelize.STRING - }); - - 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', async function() { - const permutations = [ - 0, - '0' - ]; - let count = 0; - - await this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]); - - await Promise.all(permutations.map(async perm => { - const user = await this.User.findByPk(perm, { - logging(s) { - expect(s).to.include(0); - count++; - } - }); - - expect(user).to.be.null; - })); - - expect(count).to.be.equal(permutations.length); - }); - - it('should allow us to find IDs using capital letters', async function() { - const User = this.sequelize.define(`User${Support.rand()}`, { - ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - Login: { type: Sequelize.STRING } - }); - - 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', async function() { - const User = this.sequelize.define('UserWithCaseInsensitiveName', { - username: Sequelize.CITEXT - }); - - 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'"); - }); - } - }); - - describe('eager loading', () => { - beforeEach(function() { - this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); - this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); - - 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', 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', 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', async function() { - this.Task.belongsTo(this.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', 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' }); - - 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', async function() { - this.User = this.sequelize.define('UserPKeagerbelong', { - username: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.Group = this.sequelize.define('GroupPKeagerbelong', { - name: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.User.belongsTo(this.Group); - - 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', async function() { - const User = this.sequelize.define('User', { - id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, - username: { type: Sequelize.STRING } - }); - - const Message = this.sequelize.define('Message', { - id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, - user_id: { type: Sequelize.INTEGER }, - message: { type: Sequelize.STRING } - }); - - User.hasMany(Message); - Message.belongsTo(User, { foreignKey: 'user_id' }); - - 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', async function() { - this.Worker.belongsTo(this.Task, { as: 'ToDo' }); - this.Worker.belongsTo(this.Task, { as: 'DoTo' }); - - await this.init(() => { - return this.Worker.findOne({ - include: [ - { model: this.Task, as: 'ToDo' }, - { model: this.Task, as: 'DoTo' } - ] - }); - }); - }); - }); - - describe('hasOne', () => { - beforeEach(async function() { - this.Worker.hasOne(this.Task); - - await this.init(() => { - return this.worker.setTask(this.task); - }); - }); - - 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [this.Task] - }); - - expect(worker).to.exist; - expect(worker.Task).to.exist; - expect(worker.Task.title).to.equal('homework'); - }); - - it('eager loads with non-id primary keys', async function() { - this.User = this.sequelize.define('UserPKeagerone', { - username: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.Group = this.sequelize.define('GroupPKeagerone', { - name: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.Group.hasOne(this.User); - - 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', 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(async function() { - this.Worker.hasOne(this.Task, { as: 'ToDo' }); - - 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', 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDo' }] - }); - - 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDo' }] - }); - - expect(worker.ToDo.title).to.equal('homework'); - }); - - it('allows mulitple assocations of the same model with different alias', async function() { - this.Worker.hasOne(this.Task, { as: 'DoTo' }); - - await this.init(() => { - return this.Worker.findOne({ - include: [ - { model: this.Task, as: 'ToDo' }, - { model: this.Task, as: 'DoTo' } - ] - }); - }); - }); - }); - }); - - describe('hasMany', () => { - beforeEach(async function() { - this.Worker.hasMany(this.Task); - - await this.init(() => { - return this.worker.setTasks([this.task]); - }); - }); - - 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [this.Task] - }); - - 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', 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 }); - - this.Contact.hasMany(this.Photo, { as: 'Photos' }); - this.Contact.hasMany(this.PhoneNumber); - - 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', async function() { - this.User = this.sequelize.define('UserPKeagerone', { - username: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.Group = this.sequelize.define('GroupPKeagerone', { - name: { - type: Sequelize.STRING, - primaryKey: true - } - }); - this.Group.belongsToMany(this.User, { through: 'group_user' }); - this.User.belongsToMany(this.Group, { through: 'group_user' }); - - 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', 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(async function() { - this.Worker.hasMany(this.Task, { as: 'ToDos' }); - - 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', 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDos' }] - }); - - 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', async function() { - const worker = await this.Worker.findOne({ - where: { name: 'worker' }, - include: [{ model: this.Task, as: 'ToDos' }] - }); - - expect(worker.ToDos[0].title).to.equal('homework'); - }); - - it('allows mulitple assocations of the same model with different alias', async function() { - this.Worker.hasMany(this.Task, { as: 'DoTos' }); - - await this.init(() => { - return this.Worker.findOne({ - include: [ - { model: this.Task, as: 'ToDos' }, - { model: this.Task, as: 'DoTos' } - ] - }); - }); - }); - }); - }); - - describe('hasMany (N:M) with alias', () => { - beforeEach(function() { - this.Product = this.sequelize.define('Product', { title: Sequelize.STRING }); - this.Tag = this.sequelize.define('Tag', { name: Sequelize.STRING }); - }); - - 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' }); - - 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: tags[0].id - }, - include: [ - { model: this.Product, as: 'products' } - ] - }); - - 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: products[0].id - }, - include: [ - { model: this.Tag, as: 'tags' } - ] - }); - - 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(async function() { - const user = await this.User.create({ username: 'barfooz' }); - this.user = 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', 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', 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', async function() { - const spy = sinon.spy(); - - await this.User.findOne({ - where: {}, - logging: spy - }); - - expect(spy.called).to.be.ok; - }); - - describe('rejectOnEmpty mode', () => { - it('throws error when record not found by findOne', async function() { - await expect(this.User.findOne({ - where: { - username: 'ath-kantam-pradakshnami' - }, - rejectOnEmpty: true - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); - - 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', async function() { - await expect(this.User.findOne({ - where: { - username: 'some-username-that-is-not-used-anywhere' - }, - rejectOnEmpty: true - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); - - it('works from model options', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }, { - rejectOnEmpty: true - }); - - 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', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }, { - rejectOnEmpty: true - }); - - 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', async () => { - const Model = current.define('Test', { - username: Sequelize.STRING(100) - }); - - 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 deleted file mode 100644 index 25d9eff089a1..000000000000 --- a/test/integration/model/findOrBuild.test.js +++ /dev/null @@ -1,60 +0,0 @@ -'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.User = this.sequelize.define('User', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }); - this.Project = this.sequelize.define('Project', { - name: DataTypes.STRING - }); - - this.User.hasMany(this.Project); - this.Project.belongsTo(this.User); - - await this.sequelize.sync({ force: true }); - }); - - - describe('findOrBuild', () => { - it('initialize with includes', async function() { - const [, user2] = await this.User.bulkCreate([ - { username: 'Mello', age: 10 }, - { username: 'Mello', age: 20 } - ], { 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 deleted file mode 100644 index 168f8cfbc9a3..000000000000 --- a/test/integration/model/geography.test.js +++ /dev/null @@ -1,351 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'); - -const current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.GEOGRAPHY) { - describe('GEOGRAPHY', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geography: DataTypes.GEOGRAPHY - }); - - await this.User.sync({ force: true }); - }); - - 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], - 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', 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', geography: point }); - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); - - it('should update a geography object', async function() { - const User = this.User; - 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geography: DataTypes.GEOGRAPHY('POINT') - }); - - await this.User.sync({ force: true }); - }); - - it('should create a geography object', 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', geography: point }); - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); - - it('should update a geography object', async function() { - const User = this.User; - 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geography: DataTypes.GEOGRAPHY('LINESTRING') - }); - - await this.User.sync({ force: true }); - }); - - 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]], - 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', async function() { - const User = this.User; - 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geography: DataTypes.GEOGRAPHY('POLYGON') - }); - - await this.User.sync({ force: true }); - }); - - 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]] - ], - 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', 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]] - ], - 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geography: DataTypes.GEOGRAPHY('POLYGON', 4326) - }); - - await this.User.sync({ force: true }); - }); - - 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]] - ], - 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', 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]] - ], - 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 }; - - 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(async function() { - this.Model = this.sequelize.define('Model', { - location: DataTypes.GEOGRAPHY - }); - await this.sequelize.sync({ force: true }); - }); - - it('should properly escape the single quotes', async function() { - await this.Model.create({ - location: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - }, - coordinates: [39.807222, -76.984722] - } - }); - }); - }); - } -}); diff --git a/test/integration/model/geometry.test.js b/test/integration/model/geometry.test.js deleted file mode 100644 index dea09c8d4ec1..000000000000 --- a/test/integration/model/geometry.test.js +++ /dev/null @@ -1,270 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - dialect = Support.getTestDialect(), - semver = require('semver'); - -const current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.GEOMETRY) { - describe('GEOMETRY', () => { - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geometry: DataTypes.GEOMETRY - }); - - await this.User.sync({ force: true }); - }); - - 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] }; - - 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', async function() { - const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - 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', 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geometry: DataTypes.GEOMETRY('POINT') - }); - - await this.User.sync({ force: true }); - }); - - it('should create a geometry object', async function() { - const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - 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', 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geometry: DataTypes.GEOMETRY('LINESTRING') - }); - - await this.User.sync({ force: true }); - }); - - 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]] }; - - 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', 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 }; - - 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(async function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - geometry: DataTypes.GEOMETRY('POLYGON') - }); - - await this.User.sync({ force: true }); - }); - - 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]] - ] }; - - 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', 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 props = { username: 'username', geometry: polygon1 }; - - 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(async function() { - this.Model = this.sequelize.define('Model', { - location: DataTypes.GEOMETRY - }); - await this.sequelize.sync({ force: true }); - }); - - it('should properly escape the single quotes', async function() { - await this.Model.create({ - location: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - }, - coordinates: [39.807222, -76.984722] - } - }); - }); - - 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; - } - - await this.Model.create({ - location: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - }, - coordinates: [39.807222, "'); DELETE YOLO INJECTIONS; --"] - } - }); - }); - }); - } -}); diff --git a/test/integration/model/increment.test.js b/test/integration/model/increment.test.js deleted file mode 100644 index dd52ac549e51..000000000000 --- a/test/integration/model/increment.test.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Model'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - id: { type: DataTypes.INTEGER, primaryKey: true }, - aNumber: { type: DataTypes.INTEGER }, - bNumber: { type: DataTypes.INTEGER }, - cNumber: { type: DataTypes.INTEGER, field: 'c_number' } - }); - - 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 - }]); - }); - - [ - 'increment', - 'decrement' - ].forEach(method => { - describe(method, () => { - before(function() { - this.assert = (increment, decrement) => { - return method === 'increment' ? increment : decrement; - }; - }); - - 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', 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', 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', 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', 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', 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', 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', 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', async function() { - const User = this.sequelize.define('IncrementUser', { - aNumber: DataTypes.INTEGER - }, { timestamps: true }); - - 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, 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', async function() { - const User = this.sequelize.define('IncrementUser', { - aNumber: DataTypes.INTEGER - }, { timestamps: true }); - - 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', async function() { - const User = this.sequelize.define('User', { - aNumber: DataTypes.INTEGER, - name: DataTypes.STRING - }, { - scopes: { - jeff: { - where: { - name: 'Jeff' - } - } - } - }); - - 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 deleted file mode 100644 index d4828e72f2f3..000000000000 --- a/test/integration/model/json.test.js +++ /dev/null @@ -1,792 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - Op = Sequelize.Op, - moment = require('moment'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.JSON) { - describe('JSON', () => { - beforeEach(async function() { - this.Event = this.sequelize.define('Event', { - data: { - type: DataTypes.JSON, - field: 'event_data', - index: true - }, - json: DataTypes.JSON - }); - - await this.Event.sync({ force: true }); - }); - - if (current.dialect.supports.lock) { - 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; - } - } - }); - - 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', async function() { - await this.Event.create({ - data: { - 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)', async function() { - await this.Event.bulkCreate([{ - id: 1, - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }, { - id: 2, - data: { - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Multiverse Scientist' - } - }]); - - await this.Event.update({ - 'data': { - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - } - }, { - where: { - 'data.name.first': 'Rick' - } - }); - - 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)', async function() { - await this.Event.bulkCreate([{ - id: 1, - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }, { - id: 2, - data: { - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Multiverse Scientist' - } - }]); - - await this.Event.update({ - 'data': { - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - } - }, { - where: { - data: { - name: { - first: 'Rick' - } - } - } - }); - - 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', async function() { - const event0 = await this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }); - - 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', 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: { - 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', 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(); - - await Promise.all([this.Event.create({ - json: { - user: 'Homer', - lastLogin: now - } - })]); - - const events0 = await this.Event.findAll({ - where: { - json: { - lastLogin: now - } - } - }); - - 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] } - } - } - }); - - 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', async function() { - await Promise.all([this.Event.create({ - json: { - user: 'Homer', - active: true - } - })]); - - const events0 = await this.Event.findAll({ - where: { - json: { - active: true - } - } - }); - - const event0 = events0[0]; - - expect(events0.length).to.equal(1); - expect(event0.get('json')).to.eql({ - user: 'Homer', - active: true - }); - - const events = await this.Event.findAll({ - where: { - json: { - active: { [Op.in]: [true, false] } - } - } - }); - - const event = events[0]; - - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - active: true - }); - }); - - it('should be possible to query a nested integer value', 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: { - age: { - [Op.gt]: 38 - } - } - } - }); - - 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', 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: { - 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', 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: { - status_report: { - 'red-indicator': { - 'level$$level': true - } - } - } - } - }); - - 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', async function() { - await this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }); - - await Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - })]); - - const events = await this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - }, - employment: { - [Op.ne]: 'None' - } - } - }, - order: [ - ['id', 'ASC'] - ] - }); - - 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', async function() { - await this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }); - - await Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - })]); - - const events = await this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } - } - }, - order: [ - ['data.name.first'] - ] - }); - - expect(events.length).to.equal(3); - - 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', async function() { - const conditionSearch = { - where: { - data: { - employment: 'Hacker' - } - } - }; - - 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(async function() { - this.Model = this.sequelize.define('Model', { - data: DataTypes.JSON - }); - await this.sequelize.sync({ force: true }); - }); - - it('should properly escape the single quotes', async function() { - await this.Model.create({ - data: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - } - } - }); - }); - - it('should properly escape path keys', async function() { - await this.Model.findAll({ - raw: true, - attributes: ['id'], - where: { - data: { - "a')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ": 1 - } - } - }); - }); - - 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', async function() { - await this.Model.create({ - data: { - type: 'Point', - coordinates: [39.807222, "'); DELETE YOLO INJECTIONS; --"] - } - }); - }); - - 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; -- " - } - } - }); - - const result = await this.Model.findOne({ - where: { - data: { - 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', async function() { - await this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }); - - await Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - })]); - - if (current.options.dialect === 'sqlite') { - const events = await this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } - } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - }); - - 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: { - last: 'Simpson' - } - } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - })).to.eventually.be.rejectedWith(Error); - } - }); - }); - }); - } - -}); diff --git a/test/integration/model/optimistic_locking.test.js b/test/integration/model/optimistic_locking.test.js deleted file mode 100644 index b58282bfc9b3..000000000000 --- a/test/integration/model/optimistic_locking.test.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const chai = require('chai'); -const expect = chai.expect; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('optimistic locking', () => { - let Account; - beforeEach(async function() { - Account = this.sequelize.define('Account', { - number: { - type: DataTypes.INTEGER - } - }, { - version: true - }); - await Account.sync({ force: true }); - }); - - 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', 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', 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 await accountB.save(); - })()).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); - }); - - 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', 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 deleted file mode 100644 index 9033ad795d52..000000000000 --- a/test/integration/model/paranoid.test.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const chai = require('chai'); -const expect = chai.expect; -const sinon = require('sinon'); -const current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('paranoid', () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - it('should be able to soft delete with timestamps', async function() { - const Account = this.sequelize.define('Account', { - ownerId: { - type: DataTypes.INTEGER, - allowNull: false, - field: 'owner_id' - }, - name: { - type: DataTypes.STRING - } - }, { - paranoid: true, - timestamps: true - }); - - 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', async function() { - const Account = this.sequelize.define('Account', { - ownerId: { - type: DataTypes.INTEGER, - allowNull: false, - field: 'owner_id' - }, - name: { - type: DataTypes.STRING - }, - deletedAt: { - type: DataTypes.DATE, - allowNull: true, - field: 'deleted_at' - } - }, { - paranoid: true, - timestamps: true, - deletedAt: 'deletedAt', - createdAt: false, - updatedAt: false - }); - - 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) { - describe('JSON', () => { - before(function() { - this.Model = this.sequelize.define('Model', { - name: { - type: DataTypes.STRING - }, - data: { - type: DataTypes.JSON - }, - deletedAt: { - type: DataTypes.DATE, - allowNull: true, - field: 'deleted_at' - } - }, { - paranoid: true, - timestamps: true, - deletedAt: 'deletedAt' - }); - }); - - beforeEach(async function() { - await this.Model.sync({ force: true }); - }); - - it('should soft delete with JSON condition', async function() { - await this.Model.bulkCreate([{ - name: 'One', - data: { - field: { - deep: true - } - } - }, { - name: 'Two', - data: { - field: { - deep: false - } - } - }]); - - await this.Model.destroy({ - where: { - data: { - field: { - deep: true - } - } - } - }); - - 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 deleted file mode 100644 index 376ea9758bde..000000000000 --- a/test/integration/model/schema.test.js +++ /dev/null @@ -1,550 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize, - Op = Support.Sequelize.Op; - -const SCHEMA_ONE = 'schema_one'; -const SCHEMA_TWO = 'schema_two'; - -let locationId; - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.schemas) { - - describe('global schema', () => { - before(function() { - current.options.schema = null; - this.RestaurantOne = current.define('restaurant', { - foo: DataTypes.STRING, - bar: DataTypes.STRING - }); - this.LocationOne = current.define('location', { - name: DataTypes.STRING - }); - this.RestaurantOne.belongsTo(this.LocationOne, - { - foreignKey: 'location_id', - constraints: false - }); - current.options.schema = SCHEMA_TWO; - this.RestaurantTwo = current.define('restaurant', { - foo: DataTypes.STRING, - bar: DataTypes.STRING - }); - this.LocationTwo = current.define('location', { - name: DataTypes.STRING - }); - this.RestaurantTwo.belongsTo(this.LocationTwo, - { - foreignKey: 'location_id', - constraints: false - }); - current.options.schema = null; - }); - - 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', async () => { - await current.dropSchema(SCHEMA_TWO); - }); - - describe('Add data via model.create, retrieve via model.findOne', () => { - it('should be able to sync model without schema option', function() { - expect(this.RestaurantOne._schema).to.be.null; - expect(this.RestaurantTwo._schema).to.equal(SCHEMA_TWO); - }); - - it('should be able to insert data into default table using create', async function() { - await this.RestaurantOne.create({ - foo: 'one' - }); - - 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', async function() { - await this.RestaurantTwo.create({ - foo: 'two' - }); - - 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(async function() { - await Promise.all([ - this.LocationOne.sync({ force: true }), - this.LocationTwo.sync({ force: true }) - ]); - - 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', async function() { - await this.RestaurantTwo.create({ - foo: 'two', - location_id: locationId - }); - - 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; - }); - }); - }); - - describe('schemas', () => { - before(function() { - this.Restaurant = current.define('restaurant', { - foo: DataTypes.STRING, - bar: DataTypes.STRING - }, - { tableName: 'restaurants' }); - this.Location = current.define('location', { - name: DataTypes.STRING - }, - { tableName: 'locations' }); - this.Employee = current.define('employee', { - first_name: DataTypes.STRING, - last_name: DataTypes.STRING - }, - { tableName: 'employees' }); - this.EmployeeOne = this.Employee.schema(SCHEMA_ONE); - this.Restaurant.belongsTo(this.Location, - { - foreignKey: 'location_id', - constraints: false - }); - this.Employee.belongsTo(this.Restaurant, - { - foreignKey: 'restaurant_id', - constraints: false - }); - this.Restaurant.hasMany(this.Employee, { - foreignKey: 'restaurant_id', - constraints: false - }); - this.RestaurantOne = this.Restaurant.schema(SCHEMA_ONE); - this.RestaurantTwo = this.Restaurant.schema(SCHEMA_TWO); - }); - - - beforeEach('build restaurant tables', async function() { - await Promise.all([ - current.createSchema(SCHEMA_ONE), - current.createSchema(SCHEMA_TWO) - ]); - - await Promise.all([ - this.RestaurantOne.sync({ force: true }), - this.RestaurantTwo.sync({ force: true }) - ]); - }); - - 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', async function() { - await this.RestaurantOne.create({ - foo: 'one', - location_id: locationId - }); - - 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; - }); - - 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 - }); - - 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', 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' }); - - 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(async function() { - const Location = this.Location; - - 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', async function() { - await this.RestaurantOne.create({ - foo: 'one', - location_id: locationId - }); - - 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(async function() { - const Employee = this.Employee; - - 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', async function() { - await this.RestaurantOne.create({ - foo: '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', async function() { - await this.RestaurantTwo.create({ - foo: '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', 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' }); - - 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', async function() { - const User = this.sequelize.define('User1', { - name: DataTypes.STRING, - value: DataTypes.INTEGER - }, { - schema: SCHEMA_ONE, - indexes: [ - { - name: 'test_slug_idx', - fields: ['name'] - } - ] - }); - - const Task = this.sequelize.define('Task2', { - name: DataTypes.STRING, - value: DataTypes.INTEGER - }, { - schema: SCHEMA_TWO, - indexes: [ - { - name: 'test_slug_idx', - fields: ['name'] - } - ] - }); - - 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', async function() { - const User = this.sequelize.define('UserXYZ', { - uid: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - allowNull: false - } - }), - Task = this.sequelize.define('TaskXYZ', { - }); - - Task.belongsTo(User); - - 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 deleted file mode 100644 index aa2db393ffb8..000000000000 --- a/test/integration/model/scope.test.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - beforeEach(async function() { - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - scopes: { - lowAccess: { - attributes: ['other_value', 'access_level'], - where: { - access_level: { - [Op.lte]: 5 - } - } - }, - withName: { - attributes: ['username'] - }, - highAccess: { - where: { - [Op.or]: [ - { access_level: { [Op.gte]: 5 } }, - { access_level: { [Op.eq]: 10 } } - ] - } - }, - lessThanFour: { - where: { - [Op.and]: [ - { access_level: { [Op.lt]: 4 } } - ] - } - }, - issue8473: { - where: { - [Op.or]: { - access_level: 3, - other_value: 10 - }, - access_level: 5 - } - }, - like_t: { - where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('username')), 'LIKE', '%t%') - } - } - }); - - 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', 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', 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', 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', 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 deleted file mode 100644 index 620fea0b650b..000000000000 --- a/test/integration/model/scope/aggregate.test.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('aggregate', () => { - beforeEach(async function() { - this.Child = this.sequelize.define('Child', { - priority: Sequelize.INTEGER - }); - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - } - }, - scopes: { - lowAccess: { - where: { - access_level: { - [Op.lte]: 5 - } - } - }, - withOrder: { - order: ['username'] - }, - withInclude: { - include: [{ - model: this.Child, - where: { - priority: 1 - } - }] - } - } - }); - this.Child.belongsTo(this.ScopeMe); - this.ScopeMe.hasMany(this.Child); - - 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', async function() { - await expect(this.ScopeMe.aggregate( '*', 'count' )).to.eventually.equal(2); - }); - - 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', async function() { - await expect(this.ScopeMe.unscoped().aggregate( '*', 'count' )).to.eventually.equal(4); - }); - - 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', 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', async function() { - await expect(this.ScopeMe.scope('withInclude').aggregate( 'ScopeMe.id', 'count', { - plain: true, - dataType: new Sequelize.INTEGER(), - includeIgnoreAttributes: false, - limit: null, - offset: null, - order: null, - attributes: [] - })).to.eventually.equal(1); - }); - - if (Support.sequelize.dialect.supports.schemas) { - it('aggregate with schema', async function() { - this.Hero = this.sequelize.define('Hero', { - codename: Sequelize.STRING - }, { schema: 'heroschema' }); - 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 deleted file mode 100644 index a004489ab0af..000000000000 --- a/test/integration/model/scope/associations.test.js +++ /dev/null @@ -1,433 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('associations', () => { - beforeEach(async function() { - const sequelize = this.sequelize; - - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER, - parent_id: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - } - }, - scopes: { - isTony: { - where: { - username: 'tony' - } - }, - includeActiveProjects() { - return { - include: [{ - model: sequelize.models.company, - include: [sequelize.models.project.scope('active')] - }] - }; - } - } - }); - - this.Project = this.sequelize.define('project', { - active: Sequelize.BOOLEAN - }, { - scopes: { - active: { - where: { - active: true - } - } - } - }); - - this.Company = this.sequelize.define('company', { - active: Sequelize.BOOLEAN - }, { - defaultScope: { - where: { active: true } - }, - scopes: { - notActive: { - where: { - active: false - } - }, - reversed: { - order: [['id', 'DESC']] - } - } - }); - - this.Profile = this.sequelize.define('profile', { - active: Sequelize.BOOLEAN - }, { - defaultScope: { - where: { active: true } - }, - scopes: { - notActive: { - where: { - active: false - } - } - } - }); - - this.Project.belongsToMany(this.Company, { through: 'CompanyProjects' }); - this.Company.belongsToMany(this.Project, { through: 'CompanyProjects' }); - - this.ScopeMe.hasOne(this.Profile, { foreignKey: 'userId' }); - - this.ScopeMe.belongsTo(this.Company); - this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users' }); - - 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', async function() { - // Will error with ambigous column if id is not scoped properly to `Company`.`id` - await expect(this.Company.findAll({ - where: { id: 1 }, - include: [this.UserAssociation] - })).not.to.be.rejected; - }); - - it('should apply default scope when including an associations', async function() { - const obj = await this.Company.findAll({ - include: [this.UserAssociation] - }); - - const company = await obj[0]; - expect(company.users).to.have.length(2); - }); - - it('should apply default scope when including a model', async function() { - const obj = await this.Company.findAll({ - include: [{ model: this.ScopeMe, as: 'users' }] - }); - - const company = await obj[0]; - expect(company.users).to.have.length(2); - }); - - 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' }] - }); - - const company = await obj[0]; - expect(company.users).to.have.length(1); - expect(company.users[0].get('username')).to.equal('tony'); - }); - }); - - describe('get', () => { - beforeEach(async function() { - const [p, companies] = await Promise.all([ - this.Project.create(), - this.Company.unscoped().findAll() - ]); - - await p.setCompanies(companies); - }); - - describe('it should be able to unscope', () => { - 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', async function() { - await this.Profile.create({ - active: false, - userId: 1 - }); - - const user = await this.ScopeMe.findByPk(1); - const profile = await user.getProfile({ scope: false }); - expect(profile).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', 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', async function() { - const company = await this.Company.findByPk(1); - const users = await company.getUsers(); - expect(users).to.have.length(2); - }); - - it('hasOne', async function() { - await this.Profile.create({ - active: false, - userId: 1 - }); - - const user = await this.ScopeMe.findByPk(1); - const profile = await user.getProfile(); - expect(profile).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', 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', 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', async function() { - await this.Profile.create({ - active: true, - userId: 1 - }); - - const user = await this.ScopeMe.findByPk(1); - const profile = await user.getProfile({ scope: 'notActive' }); - expect(profile).not.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', 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(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 }) - ]); - - await c.setProjects([p1, p2]); - }); - - it('should scope columns properly', async function() { - await expect(this.ScopeMe.scope('includeActiveProjects').findAll()).not.to.be.rejected; - }); - - 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', async function() { - const Child = this.sequelize.define('Child'); - const Parent = this.sequelize.define('Parent', {}, { - defaultScope: { - include: [{ model: Child }] - }, - scopes: { - children: { - include: [Child] - } - } - }); - Parent.addScope('alsoChildren', { - include: [{ model: Child }] - }); - - Child.belongsTo(Parent); - Parent.hasOne(Child); - - 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', async function() { - const Child = this.sequelize.define('Child', { name: Sequelize.STRING }); - const Parent = this.sequelize.define('Parent', { name: Sequelize.STRING }); - Parent.addScope('testScope1', { - include: [{ - model: Child, - where: { - name: 'child2' - } - }] - }); - Parent.hasMany(Child); - - 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', async function() { - const Child = this.sequelize.define('Child', { - secret: Sequelize.STRING - }, { - scopes: { - public: { - attributes: { - exclude: ['secret'] - } - } - } - }); - const Parent = this.sequelize.define('Parent'); - Child.belongsTo(Parent); - Parent.hasOne(Child); - - 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', async function() { - const Child = this.sequelize.define('Child', { - secret: Sequelize.STRING - }, { - defaultScope: { - attributes: { - exclude: ['secret'] - } - } - }); - const Parent = this.sequelize.define('Parent'); - Child.belongsTo(Parent); - - 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', 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); - - 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 deleted file mode 100644 index cc08fcd17af0..000000000000 --- a/test/integration/model/scope/count.test.js +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('count', () => { - beforeEach(async function() { - this.Child = this.sequelize.define('Child', { - priority: Sequelize.INTEGER - }); - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - aliasValue: { - field: 'alias_value', - type: Sequelize.INTEGER - }, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - }, - attributes: ['id', 'username', 'email', 'access_level'] - }, - scopes: { - lowAccess: { - where: { - access_level: { - [Op.lte]: 5 - } - } - }, - withOrder: { - order: ['username'] - }, - withInclude: { - include: [{ - model: this.Child, - where: { - priority: 1 - } - }] - }, - withIncludeFunction: () => { - return { - include: [{ - model: this.Child, - where: { - priority: 1 - } - }] - }; - }, - withIncludeFunctionAndStringAssociation: () => { - return { - include: [{ - association: 'Children', - where: { - priority: 1 - } - }] - }; - }, - withAliasedField: { - where: { - aliasValue: { [Op.ne]: 1 } - } - } - } - }); - this.Child.belongsTo(this.ScopeMe); - this.ScopeMe.hasMany(this.Child); - - 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', async function() { - await expect(this.ScopeMe.count()).to.eventually.equal(2); - }); - - 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', async function() { - await expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4); - }); - - 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', 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', 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', async function() { - await expect(this.ScopeMe.scope('withOrder').count()).to.eventually.equal(4); - }); - - 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', 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', 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 deleted file mode 100644 index ce66450c505a..000000000000 --- a/test/integration/model/scope/destroy.test.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('destroy', () => { - beforeEach(async function() { - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - } - }, - scopes: { - lowAccess: { - where: { - access_level: { - [Op.lte]: 5 - } - } - } - } - }); - - 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', 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', 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', 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', 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', 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', 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 deleted file mode 100644 index 49604344dcf5..000000000000 --- a/test/integration/model/scope/find.test.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Op = Sequelize.Op, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scopes', () => { - beforeEach(async function() { - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER, - parent_id: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - } - }, - scopes: { - highValue: { - where: { - other_value: { - [Op.gte]: 10 - } - } - }, - andScope: { - where: { - [Op.and]: [ - { - email: { - [Op.like]: '%@sequelizejs.com' - } - }, - { access_level: 3 } - ] - } - } - } - }); - - this.DefaultScopeExclude = this.sequelize.define('DefaultScopeExclude', { - name: Sequelize.STRING, - other_value: { - type: Sequelize.STRING, - field: 'otherValue' - } - }, { - defaultScope: { - attributes: { - exclude: ['name'] - } - } - }); - - this.ScopeMe.hasMany(this.DefaultScopeExclude); - - 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', 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', 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', 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', 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', 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', async function() { - const users0 = await this.ScopeMe.scope('highValue').findAll({ where: { access_level: 10 } }); - expect(users0).to.have.length(1); - - 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', 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', 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', async function() { - const Game = this.sequelize.define('Game', { - name: Sequelize.TEXT - }); - - const User = this.sequelize.define('User', { - login: Sequelize.TEXT, - session: { - type: Sequelize.VIRTUAL, - get() { - return 'New'; - } - } - }, { - defaultScope: { - attributes: { - exclude: ['login'] - } - } - }); - - Game.hasMany(User); - - 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 deleted file mode 100644 index 529fa64993ea..000000000000 --- a/test/integration/model/scope/findAndCountAll.test.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - - describe('findAndCountAll', () => { - - beforeEach(async function() { - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - }, - attributes: ['username', 'email', 'access_level'] - }, - scopes: { - lowAccess: { - where: { - access_level: { - [Op.lte]: 5 - } - } - }, - withOrder: { - order: ['username'] - } - } - }); - - 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', 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', 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', 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', 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', 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', 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 deleted file mode 100644 index 45e840a70789..000000000000 --- a/test/integration/model/scope/merge.test.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Support = require('../../support'), - combinatorics = require('js-combinatorics'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('simple merge', () => { - 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 }); - - this.Foo.belongsTo(this.Baz, { foreignKey: 'bazId' }); - this.Foo.hasOne(this.Bar, { foreignKey: 'fooId' }); - - 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 = { - includeBar: { include: this.Bar }, - includeBaz: { include: this.Baz } - }; - - this.Foo.addScope('includeBar', this.scopes.includeBar); - this.Foo.addScope('includeBaz', this.scopes.includeBaz); - - await this.createEntries(await this.sequelize.sync({ force: true })); - }); - - 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(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 }); - this.Qux = this.sequelize.define('qux', { name: Sequelize.STRING }, { timestamps: false }); - - this.Foo.hasMany(this.Bar, { foreignKey: 'fooId' }); - this.Bar.hasMany(this.Baz, { foreignKey: 'barId' }); - this.Baz.hasMany(this.Qux, { foreignKey: 'bazId' }); - - this.createFooWithDescendants = () => this.Foo.create({ - name: 'foo1', - bars: [{ - name: 'bar1', - bazs: [{ - name: 'baz1', - quxes: [{ name: 'qux1' }, { name: 'qux2' }] - }, { - name: 'baz2', - quxes: [{ name: 'qux3' }, { name: 'qux4' }] - }] - }, { - name: 'bar2', - bazs: [{ - name: 'baz3', - quxes: [{ name: 'qux5' }, { name: 'qux6' }] - }, { - name: 'baz4', - quxes: [{ name: 'qux7' }, { name: 'qux8' }] - }] - }] - }, { - include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - include: [{ - model: this.Qux - }] - }] - }] - }); - - this.scopes = { - includeEverything: { - include: { - model: this.Bar, - include: [{ - model: this.Baz, - include: this.Qux - }] - } - }, - limitedBars: { - include: [{ - model: this.Bar, - limit: 2 - }] - }, - limitedBazs: { - include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - limit: 2 - }] - }] - }, - excludeBazName: { - include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - attributes: { - exclude: ['name'] - } - }] - }] - } - }; - - this.Foo.addScope('includeEverything', this.scopes.includeEverything); - this.Foo.addScope('limitedBars', this.scopes.limitedBars); - this.Foo.addScope('limitedBazs', this.scopes.limitedBazs); - this.Foo.addScope('excludeBazName', this.scopes.excludeBazName); - - this.scopePermutations = combinatorics.permutation([ - 'includeEverything', - 'limitedBars', - 'limitedBazs', - 'excludeBazName' - ]).toArray(); - - await this.createFooWithDescendants(await this.sequelize.sync({ force: true })); - }); - - 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', 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', 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 deleted file mode 100644 index 50a3d339d6b9..000000000000 --- a/test/integration/model/scope/update.test.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../../index'), - expect = chai.expect, - Op = Sequelize.Op, - Support = require('../../support'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('scope', () => { - describe('update', () => { - beforeEach(async function() { - this.ScopeMe = this.sequelize.define('ScopeMe', { - username: Sequelize.STRING, - email: Sequelize.STRING, - access_level: Sequelize.INTEGER, - other_value: Sequelize.INTEGER - }, { - defaultScope: { - where: { - access_level: { - [Op.gte]: 5 - } - } - }, - scopes: { - lowAccess: { - where: { - access_level: { - [Op.lte]: 5 - } - } - } - } - }); - - 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', 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', 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', 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', 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', 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', async function() { - await this.ScopeMe.scope('lowAccess').update({ - username: '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 deleted file mode 100644 index 8f8a41d4600f..000000000000 --- a/test/integration/model/searchPath.test.js +++ /dev/null @@ -1,479 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const Op = Support.Sequelize.Op; - -const SEARCH_PATH_ONE = 'schema_one,public'; -const SEARCH_PATH_TWO = 'schema_two,public'; - -const current = Support.createSequelizeInstance({ - dialectOptions: { - prependSearchPath: true - } -}); - -let locationId; - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.searchPath) { - describe('SEARCH PATH', () => { - before(function() { - this.Restaurant = current.define('restaurant', { - foo: DataTypes.STRING, - bar: DataTypes.STRING - }, - { tableName: 'restaurants' }); - this.Location = current.define('location', { - name: DataTypes.STRING, - type: DataTypes.ENUM('a', 'b') - }, - { tableName: 'locations' }); - this.Employee = current.define('employee', { - first_name: DataTypes.STRING, - last_name: DataTypes.STRING - }, - { tableName: 'employees' }); - this.Restaurant.belongsTo(this.Location, - { - foreignKey: 'location_id', - constraints: false - }); - this.Employee.belongsTo(this.Restaurant, - { - foreignKey: 'restaurant_id', - constraints: false - }); - this.Restaurant.hasMany(this.Employee, { - foreignKey: 'restaurant_id', - constraints: false - }); - }); - - beforeEach('build restaurant tables', async function() { - const Restaurant = this.Restaurant; - - 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', async () => { - await current.dropSchema('schema_one'); - await current.dropSchema('schema_two'); - }); - - describe('enum case', () => { - 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', async function() { - const Restaurant = this.Restaurant; - - await Restaurant.create({ - foo: 'one', - location_id: locationId - }, { 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', async function() { - const Restaurant = this.Restaurant; - - 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', async function() { - const Restaurant = this.Restaurant; - - await Restaurant.create({ - foo: 'two', - location_id: locationId - }, { 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', async function() { - const Restaurant = this.Restaurant; - - 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', async function() { - const Restaurant = this.Restaurant; - - 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', async function() { - const Restaurant = this.Restaurant; - - let restaurauntModel = Restaurant.build({ bar: 'one.1' }); - - await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - restaurauntModel = Restaurant.build({ bar: 'one.2' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - restaurauntModel = Restaurant.build({ bar: 'two.1' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - restaurauntModel = Restaurant.build({ bar: 'two.2' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - restaurauntModel = Restaurant.build({ bar: 'two.3' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - const restaurantsOne0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - expect(restaurantsOne0).to.not.be.null; - expect(restaurantsOne0.length).to.equal(2); - restaurantsOne0.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - const restaurantsOne = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.rows.length).to.equal(2); - expect(restaurantsOne.count).to.equal(2); - restaurantsOne.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - const restaurantsTwo0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - expect(restaurantsTwo0).to.not.be.null; - expect(restaurantsTwo0.length).to.equal(3); - restaurantsTwo0.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - const restaurantsTwo = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.rows.length).to.equal(3); - expect(restaurantsTwo.count).to.equal(3); - restaurantsTwo.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); - }); - - describe('Add data via instance.save, retrieve via model.count and model.find', () => { - it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', async function() { - const Restaurant = this.Restaurant; - - let restaurauntModel = Restaurant.build({ bar: 'one.1' }); - - await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - restaurauntModel = Restaurant.build({ bar: 'one.2' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - restaurauntModel = Restaurant.build({ bar: 'two.1' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - restaurauntModel = Restaurant.build({ bar: 'two.2' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - restaurauntModel = Restaurant.build({ bar: 'two.3' }); - await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - - const restaurantsOne = await Restaurant.findAll({ - where: { bar: { [Op.like]: 'one%' } }, - searchPath: SEARCH_PATH_ONE - }); - - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - const count0 = await Restaurant.count({ searchPath: SEARCH_PATH_ONE }); - expect(count0).to.not.be.null; - expect(count0).to.equal(2); - - const restaurantsTwo = await Restaurant.findAll({ - where: { bar: { [Op.like]: 'two%' } }, - searchPath: SEARCH_PATH_TWO - }); - - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - const count = await Restaurant.count({ searchPath: SEARCH_PATH_TWO }); - expect(count).to.not.be.null; - expect(count).to.equal(3); - }); - }); - - - describe('Get associated data in public schema via include', () => { - beforeEach(async function() { - const Location = this.Location; - - 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', async function() { - const Restaurant = this.Restaurant; - const Location = this.Location; - - await Restaurant.create({ - foo: 'one', - location_id: locationId - }, { 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', async function() { - const Restaurant = this.Restaurant; - const Location = this.Location; - - await Restaurant.create({ - foo: 'two', - location_id: locationId - }, { 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(async function() { - const Employee = this.Employee; - - 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', async function() { - const Restaurant = this.Restaurant; - const Employee = this.Employee; - await Restaurant.create({ - foo: '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', async function() { - const Restaurant = this.Restaurant; - const Employee = this.Employee; - - await Restaurant.create({ - foo: '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', async function() { - const Restaurant = this.Restaurant; - - let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' }); - const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' }); - - await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); - restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); - await restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); - await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); - const restaurantsOne = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - const restaurantsTwo = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); - }); - - describe('Edit data via instance.update, retrieve updated instance via model.findAll', () => { - it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', async function() { - const Restaurant = this.Restaurant; - - 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 })) - ]); - - 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'); - })(), - (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 deleted file mode 100644 index 4f0aad682f24..000000000000 --- a/test/integration/model/sum.test.js +++ /dev/null @@ -1,42 +0,0 @@ -'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.Payment = this.sequelize.define('Payment', { - amount: DataTypes.DECIMAL, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } - }); - - 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', async function() { - await expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).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', 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 deleted file mode 100644 index c615efc78595..000000000000 --- a/test/integration/model/sync.test.js +++ /dev/null @@ -1,402 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('sync', () => { - beforeEach(async function() { - this.testSync = this.sequelize.define('testSync', { - dummy: Sequelize.STRING - }); - await this.testSync.drop(); - }); - - 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' } - }); - 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', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - 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', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.INTEGER - }); - await this.sequelize.sync(); - - await this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - - 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', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.INTEGER - }); - await this.sequelize.sync(); - - await this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - - 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)', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - await this.sequelize.sync(); - - await this.sequelize.define('testSync', { - name: Sequelize.STRING, - badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } - }); - - await this.sequelize.sync({ alter: true }); - const data = await testSync.describe(); - expect(data).to.have.ownProperty('badge_number'); - expect(data).not.to.have.ownProperty('badgeNumber'); - }); - - it('should change a column if it exists in the model but is different in the database', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.INTEGER - }); - await this.sequelize.sync(); - - await this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - }); - - 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', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - }); - await this.sequelize.sync(); - await testSync.create({ name: 'test', age: '1' }); - await this.sequelize.sync({ alter: true }); - const data = await testSync.findOne(); - expect(data.dataValues.name).to.eql('test'); - expect(data.dataValues.age).to.eql('1'); - }); - - it('should properly create composite index without affecting individual fields', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - 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', async function() { - const testSync = this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - - 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', async function() { - const foreignKeyTestSyncA = this.sequelize.define('foreignKeyTestSyncA', { - dummy: Sequelize.STRING - }); - - const foreignKeyTestSyncB = this.sequelize.define('foreignKeyTestSyncB', { - dummy: Sequelize.STRING - }); - - foreignKeyTestSyncA.hasMany(foreignKeyTestSyncB); - foreignKeyTestSyncB.belongsTo(foreignKeyTestSyncA); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING - }, - phone: { - type: Sequelize.STRING - }, - mobile: { - type: Sequelize.STRING - } - }, { - indexes: [ - { name: 'another_index_email_mobile', fields: ['email', 'mobile'] }, - { name: 'another_index_phone_mobile', fields: ['phone', 'mobile'], unique: true }, - { name: 'another_index_email', fields: ['email'] }, - { name: 'another_index_mobile', fields: ['mobile'] } - ] - }); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING - }, - phone: { - type: Sequelize.STRING - }, - mobile: { - type: Sequelize.STRING - } - }, { - indexes: [ - { fields: ['email', 'mobile'] }, - { fields: ['phone', 'mobile'], unique: true }, - { fields: ['email'] }, - { fields: ['mobile'] } - ] - }); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING, - unique: true - } - }); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING, - unique: true - }, - phone: { - type: Sequelize.STRING, - unique: true - } - }); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING, - unique: true - }, - phone: { - type: Sequelize.STRING, - unique: true - } - }, { - indexes: [ - { name: 'wow_my_index', fields: ['email', 'phone'], unique: true } - ] - }); - - 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', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING, - unique: 'wow_my_index' - } - }); - - 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); - } - }); - - it('should create only one unique index for unique:name columns', async function() { - const User = this.sequelize.define('testSync', { - email: { - type: Sequelize.STRING, - unique: 'wow_my_index' - }, - phone: { - type: Sequelize.STRING, - unique: 'wow_my_index' - } - }); - - 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 deleted file mode 100644 index fda69409baff..000000000000 --- a/test/integration/model/update.test.js +++ /dev/null @@ -1,184 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const chai = require('chai'); -const sinon = require('sinon'); -const expect = chai.expect; -const current = Support.sequelize; -const _ = require('lodash'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('update', () => { - beforeEach(async function() { - this.Account = this.sequelize.define('Account', { - ownerId: { - type: DataTypes.INTEGER, - allowNull: false, - field: 'owner_id' - }, - name: { - type: DataTypes.STRING - } - }); - await this.Account.sync({ force: true }); - }); - - 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', async function() { - const spy = sinon.spy(); - - 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', async function() { - const Model = this.sequelize.define('Model', { - ownerId: { - type: DataTypes.INTEGER, - allowNull: false, - field: 'owner_id' - }, - name: { - type: DataTypes.STRING - } - }, { - timestamps: false - }); - const spy = sinon.spy(); - - 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', 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', async function() { - const ownerId = 2; - - const account0 = await this.Account.create({ - ownerId, - name: Math.random().toString() - }); - - 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 - }); - - 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', async function() { - await this.Account.create({ - ownerId: 2, - name: 'Account Name 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 deleted file mode 100644 index 2a67cb18e6c7..000000000000 --- a/test/integration/model/upsert.test.js +++ /dev/null @@ -1,603 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - Sequelize = require('../../../index'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - dialect = Support.getTestDialect(), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - beforeEach(function() { - this.clock.reset(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('user', { - username: DataTypes.STRING, - foo: { - unique: 'foobar', - type: DataTypes.STRING - }, - bar: { - unique: 'foobar', - type: DataTypes.INTEGER - }, - baz: { - type: DataTypes.STRING, - field: 'zab', - defaultValue: 'BAZ_DEFAULT_VALUE' - }, - blob: DataTypes.BLOB - }); - - this.ModelWithFieldPK = this.sequelize.define('ModelWithFieldPK', { - userId: { - field: 'user_id', - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true - }, - foo: { - type: DataTypes.STRING, - unique: true - } - }); - - await this.sequelize.sync({ force: true }); - }); - - if (current.dialect.supports.upserts) { - describe('upsert', () => { - it('works with upsert on id', async function() { - const [, created0] = await this.User.upsert({ id: 42, username: 'john' }); - if (dialect === 'sqlite' || 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', 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', async function() { - const User = this.sequelize.define('User', { - id: { - primaryKey: true, - allowNull: false, - unique: true, - type: Sequelize.UUID, - defaultValue: Sequelize.UUIDV4 - }, - name: { - type: Sequelize.STRING - } - }); - - await User.sync({ force: true }); - await User.upsert({ name: 'John Doe' }); - }); - - it('works with upsert on a composite primary key', async function() { - const User = this.sequelize.define('user', { - a: { - type: Sequelize.STRING, - primaryKey: true - }, - b: { - type: Sequelize.STRING, - primaryKey: true - }, - username: DataTypes.STRING - }); - - 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', async function() { - const User = this.sequelize.define('user', { - email: { - type: Sequelize.STRING, - validate: { - isEmail: true - } - } - }); - - await expect(User.upsert({ email: 'notanemail' })).to.eventually.be.rejectedWith(Sequelize.ValidationError); - }); - - it('supports skipping validations', async function() { - const User = this.sequelize.define('user', { - email: { - type: Sequelize.STRING, - validate: { - isEmail: true - } - } - }); - - const options = { validate: false }; - - 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', 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', 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', 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', 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'); - }); - - 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 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', 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - unique: true - }, - email: { - type: Sequelize.STRING, - unique: true - }, - city: { - type: Sequelize.STRING - } - }); - const clock = sinon.useFakeTimers(); - await User.sync({ force: true }); - const [, created0] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); - if (dialect === 'sqlite' || 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', async function() { - const User = this.sequelize.define('User', { - username: Sequelize.STRING, - email: Sequelize.STRING, - city: Sequelize.STRING - }, { - indexes: [{ - unique: true, - fields: ['username'] - }, { - unique: true, - fields: ['email'] - }] - }); - - 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', async () => { - const User = current.define('User', { - name: DataTypes.STRING, - address: DataTypes.STRING, - city: DataTypes.STRING - }, { - indexes: [{ - unique: 'users_name_address', - fields: ['name', 'address'] - }] - }); - - 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', async function() { - const User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - primaryKey: true - } - }); - const Posts = this.sequelize.define('Posts', { - title: { - type: DataTypes.STRING, - primaryKey: true - }, - username: DataTypes.STRING - }); - Posts.belongsTo(User, { foreignKey: 'username' }); - 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', async function() { - const User = this.sequelize.define('User', { - name: { - type: DataTypes.STRING, - primaryKey: true - }, - address: DataTypes.STRING, - deletedAt: { - type: DataTypes.DATE, - primaryKey: true, - allowNull: false, - defaultValue: Infinity - } - }, { - paranoid: true - }); - - 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('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; - } - - 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', async function() { - const User = this.sequelize.define('User', { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - field: 'id_the_primary' - }, - username: { - type: DataTypes.STRING - } - }); - - 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; - } - - 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', async function() { - const User = this.sequelize.define('User', { - id: { - type: DataTypes.STRING, - primaryKey: true, - field: 'id_the_primary' - }, - username: { - type: DataTypes.STRING - } - }); - - 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; - } - - 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 deleted file mode 100644 index 46baf746cdea..000000000000 --- a/test/integration/operators.test.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../index'), - Op = Sequelize.Op, - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../lib/data-types'), - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Operators'), () => { - describe('REGEXP', () => { - beforeEach(async function() { - this.User = this.sequelize.define('user', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - field: 'userId' - }, - name: { - type: DataTypes.STRING, - field: 'full_name' - } - }, { - tableName: 'users', - timestamps: false - }); - - 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', 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', 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', 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', 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', 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', 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 deleted file mode 100644 index 0bd4bdfa1202..000000000000 --- a/test/integration/pool.test.js +++ /dev/null @@ -1,220 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -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) { - case 'postgres': - expect(oldConnection.processID).to.be.equal(newConnection.processID).and.to.be.ok; - break; - - case 'mariadb': - case 'mysql': - expect(oldConnection.threadId).to.be.equal(newConnection.threadId).and.to.be.ok; - break; - - case 'mssql': - expect(newConnection.dummyId).to.equal(oldConnection.dummyId).and.to.be.ok; - break; - - default: - throw new Error('Unsupported dialect'); - } -} - -function assertNewConnection(newConnection, oldConnection) { - switch (dialect) { - case 'postgres': - expect(oldConnection.processID).to.not.be.equal(newConnection.processID); - break; - - case 'mariadb': - case 'mysql': - expect(oldConnection.threadId).to.not.be.equal(newConnection.threadId); - break; - - case 'mssql': - expect(newConnection.dummyId).to.not.be.ok; - expect(oldConnection.dummyId).to.be.ok; - break; - - default: - throw new Error('Unsupported dialect'); - } -} - -function attachMSSQLUniqueId(connection) { - if (dialect === 'mssql') { - connection.dummyId = Math.random(); - } - - return connection; -} - -describe(Support.getTestDialectTeaser('Pooling'), () => { - if (dialect === 'sqlite' || process.env.DIALECT === 'postgres-native') return; - - beforeEach(function() { - this.sinon = sinon.createSandbox(); - }); - - afterEach(function() { - this.sinon.restore(); - }); - - describe('network / connection errors', () => { - 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; - await sequelize.sync(); - - const firstConnection = await cm.getConnection(); - simulateUnexpectedError(firstConnection); - const secondConnection = await cm.getConnection(); - - 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', 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; - await sequelize.sync(); - - const oldConnection = await cm.getConnection(); - await cm.releaseConnection(oldConnection); - simulateUnexpectedError(oldConnection); - const newConnection = await cm.getConnection(); - - assertNewConnection(newConnection, oldConnection); - expect(cm.pool.size).to.equal(1); - expect(cm.validate(oldConnection)).to.be.not.ok; - - await cm.releaseConnection(newConnection); - }); - }); - - describe('idle', () => { - it('should maintain connection within idle range', async () => { - const sequelize = Support.createSequelizeInstance({ - pool: { max: 1, idle: 100 } - }); - const cm = sequelize.connectionManager; - await sequelize.sync(); - - const firstConnection = await cm.getConnection(); - - // TODO - Do we really need this call? - attachMSSQLUniqueId(firstConnection); - - // 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(); - - assertSameConnection(secondConnection, firstConnection); - expect(cm.validate(firstConnection)).to.be.ok; - - await cm.releaseConnection(secondConnection); - }); - - it('should get new connection beyond idle range', async () => { - const sequelize = Support.createSequelizeInstance({ - pool: { max: 1, idle: 100, evict: 10 } - }); - const cm = sequelize.connectionManager; - await sequelize.sync(); - - const firstConnection = await cm.getConnection(); - - // TODO - Do we really need this call? - attachMSSQLUniqueId(firstConnection); - - // returning connection back to pool - await cm.releaseConnection(firstConnection); - - // 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', async function() { - this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { - dialect, - databaseVersion: '1.2.3', - pool: { - acquire: 10 - } - }); - - this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Promise(() => {})); - - await expect( - this.testInstance.authenticate() - ).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); - }); - - 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', - pool: { - acquire: 10, - max: 1 - } - }); - - this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Promise(() => {})); - - 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 deleted file mode 100644 index 16ab9f55b5de..000000000000 --- a/test/integration/query-interface.test.js +++ /dev/null @@ -1,658 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('./support'); -const DataTypes = require('../../lib/data-types'); -const dialect = Support.getTestDialect(); -const Sequelize = Support.Sequelize; -const current = Support.sequelize; -const _ = require('lodash'); - -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('dropAllSchema', () => { - 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', async function() { - async function cleanup() { - // NOTE: The syntax "DROP VIEW [IF EXISTS]"" is not part of the standard - // and might not be available on all RDBMSs. Therefore "DROP VIEW" is - // the compatible option, which can throw an error in case the VIEW does - // not exist. In case of error, it is ignored. - try { - await this.sequelize.query('DROP VIEW V_Fail'); - } catch (error) { - // Ignore error. - } - } - await this.queryInterface.createTable('my_test_table', { name: DataTypes.STRING }); - await cleanup(); - await this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id'); - let tableNames = await this.queryInterface.showAllTables(); - await cleanup(); - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.deep.equal(['my_test_table']); - }); - - if (dialect !== 'sqlite' && dialect !== 'postgres') { - // NOTE: sqlite doesn't allow querying between databases and - // postgres requires creating a new connection to create a new table. - it('should not show tables in other databases', 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', async function() { - await this.queryInterface.createTable('my_test_table1', { name: DataTypes.STRING }); - await this.sequelize.query('CREATE DATABASE my_test_db'); - await this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)'); - let tableNames = await this.sequelize.query( - this.queryInterface.queryGenerator.showTablesQuery(), - { - raw: true, - type: this.sequelize.QueryTypes.SHOWTABLES - } - ); - await this.sequelize.query('DROP DATABASE my_test_db'); - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - tableNames.sort(); - expect(tableNames).to.deep.equal(['my_test_table1', 'my_test_table2']); - }); - } - }); - - describe('renameTable', () => { - it('should rename table', 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', 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', async function() { - await this.queryInterface.createTable('skipme', { - name: DataTypes.STRING - }); - 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(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', 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', 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', async function() { - await this.queryInterface.addIndex('Group', ['from']); - }); - }); - - describe('renameColumn', () => { - it('rename a simple column', async function() { - const Users = this.sequelize.define('_Users', { - username: DataTypes.STRING - }, { freezeTableName: true }); - - 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', 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', async function() { - const Users = this.sequelize.define('_Users', { - username: { - type: DataTypes.STRING, - allowNull: false - } - }, { freezeTableName: true }); - - 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', async function() { - const Users = this.sequelize.define('_Users', { - active: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - } - }, { freezeTableName: true }); - - 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', async function() { - const Fruits = this.sequelize.define('Fruit', { - fruitId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - } - }, { freezeTableName: true }); - - 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', async function() { - const Users = this.sequelize.define('_Users', { - username: DataTypes.STRING - }, { freezeTableName: true }); - - 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(async function() { - await this.sequelize.createSchema('archive'); - await this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - }); - - 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 - } - }); - 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('addColumn expected error', async function() { - await this.queryInterface.createTable('level2', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - - 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)', async function() { - await this.queryInterface.addColumn('users', 'someEnum', DataTypes.ENUM('value1', 'value2', 'value3')); - }); - - 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(async function() { - await this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - 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' - }, - onUpdate: 'cascade' - }, - owner: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'set null' - } - }); - }); - - 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', 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(async function() { - this.User = this.sequelize.define('users', { - username: DataTypes.STRING, - email: DataTypes.STRING, - roles: DataTypes.STRING - }); - - this.Post = this.sequelize.define('posts', { - username: DataTypes.STRING - }); - await this.sequelize.sync({ force: true }); - }); - - - describe('unique', () => { - 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', 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', async function() { - await this.queryInterface.addConstraint('users', { - type: 'check', - fields: ['roles'], - where: { - roles: ['user', 'admin', 'guest', 'moderator'] - }, - name: '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', 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', async function() { - await this.queryInterface.addConstraint('users', { - fields: ['roles'], - type: 'default', - defaultValue: 'guest' - }); - 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', 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', 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', async function() { - try { - await this.queryInterface.removeConstraint('users', 'unknown__constraint__name', { - type: 'unique' - }); - 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 deleted file mode 100644 index 7bc12b77a2d8..000000000000 --- a/test/integration/query-interface/changeColumn.test.js +++ /dev/null @@ -1,357 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const dialect = Support.getTestDialect(); - -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('changeColumn', () => { - 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', async function() { - await this.queryInterface.createTable({ - tableName: 'users' - }, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - currency: DataTypes.INTEGER - }); - - 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)', async function() { - await this.queryInterface.createTable({ - tableName: 'users' - }, { - firstName: DataTypes.STRING - }); - - await this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM(['value1', 'value2', 'value3']) - }); - }); - - it('should work with enums (case 2)', async function() { - await this.queryInterface.createTable({ - tableName: 'users' - }, { - firstName: DataTypes.STRING - }); - - await this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM, - values: ['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']) - }); - }); - } - - //SQlite natively doesn't support ALTER Foreign key - if (dialect !== 'sqlite') { - describe('should support foreign keys', () => { - beforeEach(async function() { - await this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - level_id: { - type: DataTypes.INTEGER, - allowNull: false - } - }); - - await this.queryInterface.createTable('level', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - }); - - it('able to change column to foreign key', async function() { - const foreignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); - expect(foreignKeys).to.be.an('array'); - expect(foreignKeys).to.be.empty; - - await this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }); - - const newForeignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); - expect(newForeignKeys).to.be.an('array'); - expect(newForeignKeys).to.have.lengthOf(1); - expect(newForeignKeys[0].columnName).to.be.equal('level_id'); - }); - - it('able to change column property without affecting other properties', 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 - // 4. change column level_id AGAIN to allow null values - // 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) - 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' - }); - - 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', async function() { - const describedTable = await this.queryInterface.describeTable({ - tableName: 'users' - }); - - 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 deleted file mode 100644 index 31f2af637138..000000000000 --- a/test/integration/query-interface/createTable.test.js +++ /dev/null @@ -1,180 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const dialect = Support.getTestDialect(); - -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('createTable', () => { - it('should create a auto increment primary key', async function() { - await this.queryInterface.createTable('TableWithPK', { - table_id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - - const result = await this.queryInterface.describeTable('TableWithPK'); - - if (dialect === 'mssql' || dialect === 'mysql' || dialect === 'mariadb') { - expect(result.table_id.autoIncrement).to.be.true; - } else if (dialect === 'postgres') { - expect(result.table_id.defaultValue).to.equal('nextval("TableWithPK_table_id_seq"::regclass)'); - } - }); - - it('should create unique constraint with uniqueKeys', async function() { - await this.queryInterface.createTable('MyTable', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - name: { - type: DataTypes.STRING - }, - email: { - type: DataTypes.STRING - } - }, { - uniqueKeys: { - myCustomIndex: { - fields: ['name', 'email'] - }, - myOtherIndex: { - fields: ['name'] - } - } - }); - - 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', 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)', async function() { - await this.queryInterface.createTable('SomeTable', { - someEnum: DataTypes.ENUM('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)', async function() { - await this.queryInterface.createTable('SomeTable', { - someEnum: { - type: DataTypes.ENUM, - values: ['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)', async function() { - await this.queryInterface.createTable('SomeTable', { - someEnum: { - type: DataTypes.ENUM, - values: ['value1', 'value2', 'value3'], - field: 'otherName' - } - }); - - 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)', 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)', async function() { - await this.queryInterface.createTable('SomeTable', { - someEnum: { - type: DataTypes.ENUM(['COMMENT']), - comment: '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 deleted file mode 100644 index 5785a8ba5e6e..000000000000 --- a/test/integration/query-interface/describeTable.test.js +++ /dev/null @@ -1,166 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const dialect = Support.getTestDialect(); - -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('describeTable', () => { - if (Support.sequelize.dialect.supports.schemas) { - it('reads the metadata of the table with schema', async function() { - const MyTable1 = this.sequelize.define('my_table', { - username1: DataTypes.STRING - }); - - const MyTable2 = this.sequelize.define('my_table', { - username2: DataTypes.STRING - }, { schema: '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', 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', async function() { - const Users = this.sequelize.define('_Users', { - username: DataTypes.STRING, - city: { - type: DataTypes.STRING, - defaultValue: null, - comment: 'Users City' - }, - isAdmin: DataTypes.BOOLEAN, - enumVals: DataTypes.ENUM('hello', 'world') - }, { freezeTableName: true }); - - 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', async function() { - const Country = this.sequelize.define('_Country', { - code: { type: DataTypes.STRING, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }, { freezeTableName: true }); - const Alumni = this.sequelize.define('_Alumni', { - year: { type: DataTypes.INTEGER, primaryKey: true }, - num: { type: DataTypes.INTEGER, primaryKey: true }, - username: { type: DataTypes.STRING, allowNull: false, unique: true }, - dob: { type: DataTypes.DATEONLY, allowNull: false }, - dod: { type: DataTypes.DATEONLY, allowNull: true }, - city: { type: DataTypes.STRING, allowNull: false }, - ctrycod: { - type: DataTypes.STRING, allowNull: false, - references: { model: Country, key: 'code' } - } - }, { freezeTableName: true }); - - 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 deleted file mode 100644 index 136e30331a5c..000000000000 --- a/test/integration/query-interface/dropEnum.test.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const dialect = Support.getTestDialect(); - -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('dropEnum', () => { - 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', async function() { - await this.queryInterface.removeColumn('menus', 'structuretype'); - const enumList0 = await this.queryInterface.pgListEnums('menus'); - expect(enumList0).to.have.lengthOf(1); - expect(enumList0[0]).to.have.property('enum_name').and.to.equal('enum_menus_structuretype'); - await this.queryInterface.dropEnum('enum_menus_structuretype'); - const enumList = await this.queryInterface.pgListEnums('menus'); - expect(enumList).to.be.an('array'); - expect(enumList).to.have.lengthOf(0); - }); - } - }); -}); diff --git a/test/integration/query-interface/removeColumn.test.js b/test/integration/query-interface/removeColumn.test.js deleted file mode 100644 index 983d7a9d8ace..000000000000 --- a/test/integration/query-interface/removeColumn.test.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const dialect = Support.getTestDialect(); - -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('removeColumn', () => { - describe('(without a schema)', () => { - beforeEach(async function() { - await this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - firstName: { - type: DataTypes.STRING, - defaultValue: 'Someone' - }, - lastName: { - type: DataTypes.STRING - }, - manager: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - } - }, - email: { - type: DataTypes.STRING, - unique: true - } - }); - }); - - 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', 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', 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', 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: - // The modified column cannot be any one of the following: - // - 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', 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(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', async function() { - await this.queryInterface.removeColumn({ - tableName: 'users', - schema: 'archive' - }, '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', async function() { - await this.queryInterface.removeColumn({ - tableName: 'users', - schema: 'archive' - }, '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', async function() { - await this.queryInterface.removeColumn({ - tableName: 'users', - schema: 'archive' - }, 'id'); - - const table = await this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - - expect(table).to.not.have.property('id'); - }); - - // From MSSQL documentation on ALTER COLUMN: - // The modified column cannot be any one of the following: - // - 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', async function() { - await this.queryInterface.removeColumn({ - tableName: 'users', - schema: 'archive' - }, 'email'); - - const table = await this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - - expect(table).to.not.have.property('email'); - }); - } - }); - }); -}); diff --git a/test/integration/replication.test.js b/test/integration/replication.test.js deleted file mode 100644 index 1ba98bb12912..000000000000 --- a/test/integration/replication.test.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('./support'); -const DataTypes = require('../../lib/data-types'); -const dialect = Support.getTestDialect(); -const sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Replication'), () => { - if (dialect === 'sqlite') return; - - let sandbox; - let readSpy, writeSpy; - - beforeEach(async function() { - sandbox = sinon.createSandbox(); - - this.sequelize = Support.getSequelizeInstance(null, null, null, { - replication: { - write: Support.getConnectionOptionsWithoutPool(), - read: [Support.getConnectionOptionsWithoutPool()] - } - }); - - expect(this.sequelize.connectionManager.pool.write).to.be.ok; - expect(this.sequelize.connectionManager.pool.read).to.be.ok; - - this.User = this.sequelize.define('User', { - firstName: { - type: DataTypes.STRING, - field: 'first_name' - } - }); - - 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(() => { - sandbox.restore(); - }); - - function expectReadCalls() { - chai.expect(readSpy.callCount).least(1); - chai.expect(writeSpy.notCalled).eql(true); - } - - function expectWriteCalls() { - chai.expect(writeSpy.callCount).least(1); - chai.expect(readSpy.notCalled).eql(true); - } - - it('should be able to make a write', async function() { - await expectWriteCalls(await this.User.create({ - firstName: Math.random().toString() - })); - }); - - 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', async function() { - await expectReadCalls(await this.sequelize.transaction({ readOnly: true }, transaction => { - return this.User.findAll({ 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 }); - })); - }); -}); diff --git a/test/integration/schema.test.js b/test/integration/schema.test.js deleted file mode 100644 index 4bfe96b97a80..000000000000 --- a/test/integration/schema.test.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - DataTypes = require('../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Schema'), () => { - beforeEach(async function() { - await this.sequelize.createSchema('testschema'); - }); - - afterEach(async function() { - await this.sequelize.dropSchema('testschema'); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - aNumber: { type: DataTypes.INTEGER } - }, { - schema: 'testschema' - }); - - await this.User.sync({ force: true }); - }); - - 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', 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 deleted file mode 100644 index fbead1f9c6e6..000000000000 --- a/test/integration/sequelize.test.js +++ /dev/null @@ -1,911 +0,0 @@ -'use strict'; - -const { expect, assert } = require('chai'); -const Support = require('./support'); -const DataTypes = require('../../lib/data-types'); -const dialect = Support.getTestDialect(); -const _ = require('lodash'); -const Sequelize = require('../../index'); -const config = require('../config/config'); -const Transaction = require('../../lib/transaction'); -const sinon = require('sinon'); -const current = 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('constructor', () => { - it('should pass the global options correctly', () => { - const sequelize = Support.createSequelizeInstance({ logging: false, define: { underscored: true } }), - DAO = sequelize.define('dao', { name: DataTypes.STRING }); - - expect(DAO.options.underscored).to.be.ok; - }); - - it('should correctly set the host and the port', () => { - const sequelize = Support.createSequelizeInstance({ host: '127.0.0.1', port: 1234 }); - expect(sequelize.config.port).to.equal(1234); - expect(sequelize.config.host).to.equal('127.0.0.1'); - }); - - it('should set operators aliases on dialect queryGenerator', () => { - const operatorsAliases = { fake: true }; - const sequelize = Support.createSequelizeInstance({ operatorsAliases }); - - expect(sequelize).to.have.property('dialect'); - expect(sequelize.dialect).to.have.property('queryGenerator'); - expect(sequelize.dialect.queryGenerator).to.have.property('OperatorsAliasMap'); - expect(sequelize.dialect.queryGenerator.OperatorsAliasMap).to.be.eql(operatorsAliases); - }); - - if (dialect === 'sqlite') { - it('should work with connection strings (1)', () => { - new Sequelize('sqlite://test.sqlite'); - }); - it('should work with connection strings (2)', () => { - new Sequelize('sqlite://test.sqlite/'); - }); - it('should work with connection strings (3)', () => { - new Sequelize('sqlite://test.sqlite/lol?reconnect=true'); - }); - } - - if (dialect === 'postgres') { - const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}`; - it('should work with connection strings (postgres protocol)', () => { - const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgres' }); - // postgres://... - new Sequelize(connectionUri); - }); - it('should work with connection strings (postgresql protocol)', () => { - const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgresql' }); - // postgresql://... - new Sequelize(connectionUri); - }); - } - }); - - if (dialect !== 'sqlite') { - describe('authenticate', () => { - describe('with valid credentials', () => { - it('triggers the success event', async function() { - await this.sequelize.authenticate(); - }); - }); - - describe('with an invalid connection', () => { - beforeEach(function() { - const options = { ...this.sequelize.options, port: '99999' }; - this.sequelizeWithInvalidConnection = new Sequelize('wat', 'trololo', 'wow', options); - }); - - 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', 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', 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; - } - }); - }); - - describe('with invalid credentials', () => { - beforeEach(function() { - this.sequelizeWithInvalidCredentials = new Sequelize('localhost', 'wtf', 'lol', this.sequelize.options); - }); - - 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', async function() { - try { - await this - .sequelizeWithInvalidCredentials - .authenticate(); - } catch (err) { - expect(err).to.be.instanceof(Sequelize.Error); - } - }); - - 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; - } - }); - }); - }); - - describe('validate', () => { - it('is an alias for .authenticate()', function() { - expect(this.sequelize.validate).to.equal(this.sequelize.authenticate); - }); - }); - } - - describe('getDialect', () => { - it('returns the defined dialect', function() { - expect(this.sequelize.getDialect()).to.equal(dialect); - }); - }); - - describe('getDatabaseName', () => { - it('returns the database name', function() { - expect(this.sequelize.getDatabaseName()).to.equal(this.sequelize.config.database); - }); - }); - - describe('isDefined', () => { - it('returns false if the dao wasn\'t defined before', function() { - expect(this.sequelize.isDefined('Project')).to.be.false; - }); - - it('returns true if the dao was defined before', function() { - this.sequelize.define('Project', { - name: DataTypes.STRING - }); - expect(this.sequelize.isDefined('Project')).to.be.true; - }); - }); - - describe('model', () => { - it('throws an error if the dao being accessed is undefined', function() { - expect(() => { - this.sequelize.model('Project'); - }).to.throw(/project has not been defined/i); - }); - - it('returns the dao factory defined by daoName', function() { - const project = this.sequelize.define('Project', { - name: DataTypes.STRING - }); - - expect(this.sequelize.model('Project')).to.equal(project); - }); - }); - - describe('set', () => { - it('should be configurable with global functions', function() { - const defaultSetterMethod = sinon.spy(), - overrideSetterMethod = sinon.spy(), - defaultGetterMethod = sinon.spy(), - overrideGetterMethod = sinon.spy(), - customSetterMethod = sinon.spy(), - customOverrideSetterMethod = sinon.spy(), - customGetterMethod = sinon.spy(), - customOverrideGetterMethod = sinon.spy(); - - this.sequelize.options.define = { - 'setterMethods': { - 'default': defaultSetterMethod, - 'override': overrideSetterMethod - }, - 'getterMethods': { - 'default': defaultGetterMethod, - 'override': overrideGetterMethod - } - }; - const testEntity = this.sequelize.define('TestEntity', {}, { - 'setterMethods': { - 'custom': customSetterMethod, - 'override': customOverrideSetterMethod - }, - 'getterMethods': { - 'custom': customGetterMethod, - 'override': customOverrideGetterMethod - } - }); - - // Create Instance to test - const instance = testEntity.build(); - - // Call Getters - instance.default; - instance.custom; - instance.override; - - expect(defaultGetterMethod).to.have.been.calledOnce; - expect(customGetterMethod).to.have.been.calledOnce; - expect(overrideGetterMethod.callCount).to.be.eql(0); - expect(customOverrideGetterMethod).to.have.been.calledOnce; - - // Call Setters - instance.default = 'test'; - instance.custom = 'test'; - instance.override = 'test'; - - expect(defaultSetterMethod).to.have.been.calledOnce; - expect(customSetterMethod).to.have.been.calledOnce; - expect(overrideSetterMethod.callCount).to.be.eql(0); - expect(customOverrideSetterMethod).to.have.been.calledOnce; - }); - }); - - if (dialect === 'mysql') { - describe('set', () => { - 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', 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', 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(); - }); - }); - } - - describe('define', () => { - it('adds a new dao to the dao manager', function() { - const count = this.sequelize.modelManager.all.length; - this.sequelize.define('foo', { title: DataTypes.STRING }); - expect(this.sequelize.modelManager.all.length).to.equal(count + 1); - }); - - it('adds a new dao to sequelize.models', function() { - expect(this.sequelize.models.bar).to.equal(undefined); - const Bar = this.sequelize.define('bar', { title: DataTypes.STRING }); - expect(this.sequelize.models.bar).to.equal(Bar); - }); - - it('overwrites global options', () => { - const sequelize = Support.createSequelizeInstance({ define: { collate: 'utf8_general_ci' } }); - const DAO = sequelize.define('foo', { bar: DataTypes.STRING }, { collate: 'utf8_bin' }); - expect(DAO.options.collate).to.equal('utf8_bin'); - }); - - it('overwrites global rowFormat options', () => { - const sequelize = Support.createSequelizeInstance({ define: { rowFormat: 'compact' } }); - const DAO = sequelize.define('foo', { bar: DataTypes.STRING }, { rowFormat: 'default' }); - expect(DAO.options.rowFormat).to.equal('default'); - }); - - it('inherits global collate option', () => { - const sequelize = Support.createSequelizeInstance({ define: { collate: 'utf8_general_ci' } }); - const DAO = sequelize.define('foo', { bar: DataTypes.STRING }); - expect(DAO.options.collate).to.equal('utf8_general_ci'); - }); - - it('inherits global rowFormat option', () => { - const sequelize = Support.createSequelizeInstance({ define: { rowFormat: 'default' } }); - const DAO = sequelize.define('foo', { bar: DataTypes.STRING }); - expect(DAO.options.rowFormat).to.equal('default'); - }); - - it('uses the passed tableName', async function() { - const Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: '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', async function() { - const Project = this.sequelize.define(`project${Support.rand()}`, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: DataTypes.STRING - }); - - 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', 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', async function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }); - await User.sync(); - expect(true).to.be.true; - }); - - it('fails with incorrect match condition', async function() { - const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { - dialect: this.sequelize.options.dialect - }); - - sequelize.define('Project', { title: Sequelize.STRING }); - sequelize.define('Task', { title: Sequelize.STRING }); - - 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', async function() { - const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { - dialect: this.sequelize.options.dialect - }); - - await expect(sequelize.sync({ force: true })).to.be.rejected; - }); - - 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 }); - - try { - await User2.sync(); - expect.fail(); - } catch (err) { - if (dialect === 'postgres' || dialect === 'postgres-native') { - assert([ - 'fe_sendauth: no password supplied', - 'role "bar" does not exist', - 'FATAL: role "bar" does not exist', - 'password authentication failed for user "bar"' - ].includes(err.message.trim())); - } else if (dialect === 'mssql') { - expect(err.message).to.equal('Login failed for user \'bar\'.'); - } else { - expect(err.message.toString()).to.match(/.*Access denied.*/); - } - } - }); - - it('fails with incorrect database credentials (2)', async function() { - const sequelize = new Sequelize('db', 'user', 'pass', { - dialect: this.sequelize.options.dialect - }); - - sequelize.define('Project', { title: Sequelize.STRING }); - sequelize.define('Task', { title: Sequelize.STRING }); - - await expect(sequelize.sync({ force: true })).to.be.rejected; - }); - - it('fails with incorrect database credentials (3)', async function() { - const sequelize = new Sequelize('db', 'user', 'pass', { - dialect: this.sequelize.options.dialect, - port: 99999 - }); - - sequelize.define('Project', { title: Sequelize.STRING }); - sequelize.define('Task', { title: Sequelize.STRING }); - - await expect(sequelize.sync({ force: true })).to.be.rejected; - }); - - it('fails with incorrect database credentials (4)', async function() { - const sequelize = new Sequelize('db', 'user', 'pass', { - dialect: this.sequelize.options.dialect, - port: 99999, - pool: {} - }); - - sequelize.define('Project', { title: Sequelize.STRING }); - sequelize.define('Task', { title: Sequelize.STRING }); - - await expect(sequelize.sync({ force: true })).to.be.rejected; - }); - - it('returns an error correctly if unable to sync a foreign key referenced model', async function() { - this.sequelize.define('Application', { - authorID: { - type: Sequelize.BIGINT, - allowNull: false, - references: { - model: 'User', - key: 'id' - } - } - }); - - await expect(this.sequelize.sync()).to.be.rejected; - }); - - it('handles this dependant foreign key constraints', async function() { - const block = this.sequelize.define('block', { - id: { type: DataTypes.INTEGER, primaryKey: true }, - name: DataTypes.STRING - }, { - tableName: 'block', - timestamps: false, - paranoid: false - }); - - block.hasMany(block, { - as: 'childBlocks', - foreignKey: 'parent', - joinTableName: 'link_block_block', - useJunctionTable: true, - foreignKeyConstraint: true - }); - block.belongsTo(block, { - as: 'parentBlocks', - foreignKey: 'child', - joinTableName: 'link_block_block', - useJunctionTable: true, - foreignKeyConstraint: true - }); - - await this.sequelize.sync(); - }); - } - - 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', async function() { - const block = this.sequelize.define('block', { - id: { type: DataTypes.INTEGER, primaryKey: true }, - name: DataTypes.STRING - }, { - tableName: 'block', - timestamps: false, - paranoid: false - }); - - const result = await block.sync(); - expect(result).to.deep.equal(block); - }); - - it('handles alter: true with underscore correctly', async function() { - this.sequelize.define('access_metric', { - user_id: { - type: DataTypes.INTEGER - } - }, { - underscored: true - }); - - await this.sequelize.sync({ - alter: true - }); - }); - - describe("doesn't emit logging when explicitly saying not to", () => { - afterEach(function() { - this.sequelize.options.logging = false; - }); - - beforeEach(function() { - this.spy = sinon.spy(); - this.sequelize.options.logging = () => { this.spy(); }; - this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING }); - }); - - it('through Sequelize.sync()', async function() { - this.spy.resetHistory(); - await this.sequelize.sync({ force: true, logging: false }); - expect(this.spy.notCalled).to.be.true; - }); - - it('through DAOFactory.sync()', async function() { - this.spy.resetHistory(); - await this.User.sync({ force: true, logging: false }); - expect(this.spy.notCalled).to.be.true; - }); - }); - - describe('match', () => { - it('will return an error not matching', function() { - expect( - this.sequelize.sync({ - force: true, - match: /alibabaizshaek/ - }) - ).to.be.rejected; - }); - }); - }); - - describe('drop should work', () => { - it('correctly succeeds', async function() { - const User = this.sequelize.define('Users', { username: DataTypes.STRING }); - await User.sync({ force: true }); - await User.drop(); - }); - }); - - describe('define', () => { - it('raises an error if no values are defined', function() { - expect(() => { - this.sequelize.define('omnomnom', { - bla: { type: DataTypes.ARRAY } - }); - }).to.throw(Error, 'ARRAY is missing type definition for its values.'); - }); - }); - - describe('define', () => { - [ - { type: DataTypes.ENUM, values: ['scheduled', 'active', 'finished'] }, - DataTypes.ENUM('scheduled', 'active', 'finished') - ].forEach(status => { - describe('enum', () => { - beforeEach(async function() { - this.sequelize = Support.createSequelizeInstance({ - typeValidation: true - }); - - this.Review = this.sequelize.define('review', { status }); - await this.Review.sync({ force: true }); - }); - - it('raises an error if no values are defined', function() { - expect(() => { - this.sequelize.define('omnomnom', { - bla: { type: DataTypes.ENUM } - }); - }).to.throw(Error, 'Values for ENUM have not been defined.'); - }); - - it('correctly stores values', async function() { - const review = await this.Review.create({ status: 'active' }); - expect(review.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", 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"]'); - } - }); - }); - }); - - describe('table', () => { - [ - { id: { type: DataTypes.BIGINT, primaryKey: true } }, - { id: { type: DataTypes.STRING, allowNull: true, primaryKey: true } }, - { id: { type: DataTypes.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } } - ].forEach(customAttributes => { - - it('should be able to override options on the default attributes', async function() { - const Picture = this.sequelize.define('picture', _.cloneDeep(customAttributes)); - 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); - } - }); - }); - }); - - }); - }); - - if (current.dialect.supports.transactions) { - describe('transaction', () => { - 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', async function() { - const t = await this.sequelizeWithTransaction.transaction(); - expect(t).to.be.instanceOf(Transaction); - }); - - 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', async function() { - const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); - - 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 result.cnt; - }; - - 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', async function() { - const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); - const aliasesMapping = new Map([['_0', '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, aliasesMapping }); - - return parseInt(result.cnt, 10); - }; - - 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', async function() { - const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - - 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(async function() { - this.User = this.sequelizeWithTransaction.define('user', {}); - await this.sequelizeWithTransaction.sync({ force: true }); - }); - - 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', 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', async function() { - const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - - 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', async function() { - const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - - 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', 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', async function() { - const epochObj = new Date(0), - epoch = Number(epochObj); - const User = this.sequelize.define('user', { - username: DataTypes.STRING, - deletedAt: { - type: DataTypes.DATE, - defaultValue: epochObj - } - }, { - paranoid: true - }); - - 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 deleted file mode 100644 index 86feee7f1793..000000000000 --- a/test/integration/sequelize.transaction.test.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Transaction = require('../../lib/transaction'), - 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', async function() { - let called = false; - - const t = await this - .sequelize - .transaction(); - - await t.commit(); - called = 1; - expect(called).to.be.ok; - }); - - it('gets triggered once a transaction has been successfully rolled back', async function() { - let called = false; - - const t = await this - .sequelize - .transaction(); - - await t.rollback(); - called = 1; - expect(called).to.be.ok; - }); - - if (Support.getTestDialect() !== 'sqlite') { - 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', 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(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', 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 deleted file mode 100644 index 332baf4b2779..000000000000 --- a/test/integration/sequelize/deferrable.test.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'); - -if (!Support.sequelize.dialect.supports.deferrableConstraints) { - return; -} - -describe(Support.getTestDialectTeaser('Sequelize'), () => { - describe('Deferrable', () => { - 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('INITIALLY_IMMEDIATE', () => { - it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { - const task = await this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE); - - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); - - 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()}`; - - const task = await this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { - deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), - taskTableName - }); - - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); - }); - - describe('INITIALLY_DEFERRED', () => { - it('allows the violation of the foreign key constraint', async function() { - const task = await this - .run(Sequelize.Deferrable.INITIALLY_DEFERRED); - - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); - }); - }); - }; - - 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/log.test.js b/test/integration/sequelize/log.test.js deleted file mode 100644 index 13125c01cc11..000000000000 --- a/test/integration/sequelize/log.test.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -const { expect } = require('chai'); -const { stub, spy } = require('sinon'); -const Support = require('../support'); -const dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Sequelize'), () => { - describe('log', () => { - beforeEach(function() { - this.stub = stub(console, 'log'); - }); - - afterEach(() => { - console.log.restore(); - }); - - describe('with disabled logging', () => { - beforeEach(function() { - this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect, logging: false }); - }); - - it('does not call the log method of the logger', function() { - this.sequelize.log(); - expect(this.stub.calledOnce).to.be.false; - }); - }); - - describe('with default logging options', () => { - beforeEach(function() { - this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect }); - }); - - describe('called with no arguments', () => { - it('calls the log method', function() { - this.sequelize.log(); - expect(this.stub.calledOnce).to.be.true; - }); - - it('logs an empty string as info event', function() { - this.sequelize.log(''); - expect(this.stub.calledOnce).to.be.true; - }); - }); - - describe('called with one argument', () => { - it('logs the passed string as info event', function() { - this.sequelize.log('my message'); - expect(this.stub.withArgs('my message').calledOnce).to.be.true; - }); - }); - - describe('called with more than two arguments', () => { - it('passes the arguments to the logger', function() { - this.sequelize.log('error', 'my message', 1, { a: 1 }); - expect(this.stub.withArgs('error', 'my message', 1, { a: 1 }).calledOnce).to.be.true; - }); - }); - }); - - describe('with a custom function for logging', () => { - beforeEach(function() { - this.spy = spy(); - this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect, logging: this.spy }); - }); - - it('calls the custom logger method', function() { - this.sequelize.log('om nom'); - expect(this.spy.calledOnce).to.be.true; - }); - - it('calls the custom logger method with options', function() { - const message = 'om nom'; - const timeTaken = 5; - const options = { correlationId: 'ABC001' }; - this.sequelize.log(message, timeTaken, options); - expect(this.spy.withArgs(message, timeTaken, options).calledOnce).to.be.true; - }); - - }); - }); -}); diff --git a/test/integration/sequelize/query.test.js b/test/integration/sequelize/query.test.js deleted file mode 100644 index ff53f738eddd..000000000000 --- a/test/integration/sequelize/query.test.js +++ /dev/null @@ -1,609 +0,0 @@ -'use strict'; - -const { expect } = require('chai'); -const Support = require('../support'); -const Sequelize = Support.Sequelize; -const DataTypes = Support.Sequelize.DataTypes; -const dialect = Support.getTestDialect(); -const sinon = require('sinon'); -const moment = require('moment'); - -const qq = str => { - if (dialect === 'postgres' || dialect === 'mssql') { - return `"${str}"`; - } - if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'sqlite') { - return `\`${str}\``; - } - return str; -}; - -describe(Support.getTestDialectTeaser('Sequelize'), () => { - describe('query', () => { - afterEach(function() { - this.sequelize.options.quoteIdentifiers = true; - console.log.restore && console.log.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: Sequelize.STRING, - unique: true - }, - emailAddress: { - type: Sequelize.STRING, - field: 'email_address' - } - }); - - this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ - qq('createdAt') }, ${qq('updatedAt') - }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; - - await this.User.sync({ force: true }); - }); - - it('executes a query the internal way', async function() { - await this.sequelize.query(this.insertQuery, { raw: true }); - }); - - it('executes a query if only the sql is passed', async function() { - await this.sequelize.query(this.insertQuery); - }); - - it('executes a query if a placeholder value is an array', async function() { - await this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + - `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { - replacements: [[ - ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], - ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] - ]] - }); - - const rows = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: this.sequelize.QueryTypes.SELECT - }); - - expect(rows).to.be.lengthOf(2); - expect(rows[0].username).to.be.equal('john'); - expect(rows[1].username).to.be.equal('michael'); - }); - - describe('QueryTypes', () => { - it('RAW', async function() { - await this.sequelize.query(this.insertQuery, { - type: Sequelize.QueryTypes.RAW - }); - - const [rows, count] = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: Sequelize.QueryTypes.RAW - }); - - expect(rows).to.be.an.instanceof(Array); - expect(count).to.be.ok; - }); - }); - - describe('retry', () => { - it('properly bind parameters on extra retries', async function() { - const payload = { - username: 'test', - createdAt: '2010-10-10 00:00:00', - updatedAt: '2010-10-10 00:00:00' - }; - - const spy = sinon.spy(); - - await this.User.create(payload); - - await expect(this.sequelize.query(` - INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); - `, { - bind: payload, - logging: spy, - retry: { - max: 3, - match: [ - /Validation/ - ] - } - })).to.be.rejectedWith(Sequelize.UniqueConstraintError); - - expect(spy.callCount).to.eql(3); - }); - }); - - describe('logging', () => { - it('executes a query with global benchmarking option and custom logger', async () => { - const logger = sinon.spy(); - const sequelize = Support.createSequelizeInstance({ - logging: logger, - benchmark: true - }); - - await sequelize.query('select 1;'); - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - - it('executes a query with benchmarking option and custom logger', async function() { - const logger = sinon.spy(); - - await this.sequelize.query('select 1;', { - logging: logger, - benchmark: true - }); - - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - - describe('with logQueryParameters', () => { - beforeEach(async function() { - this.sequelize = Support.createSequelizeInstance({ - benchmark: true, - logQueryParameters: true - }); - this.User = this.sequelize.define('User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - username: { - type: DataTypes.STRING - }, - emailAddress: { - type: DataTypes.STRING - } - }, { - timestamps: false - }); - - await this.User.sync({ force: true }); - }); - - it('add parameters in log sql', async function() { - let createSql, updateSql; - - const user = await this.User.create({ - username: 'john', - emailAddress: 'john@gmail.com' - }, { - logging: s =>{ - createSql = s; - } - }); - - user.username = 'li'; - - await user.save({ - logging: s =>{ - updateSql = s; - } - }); - - expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); - expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); - }); - - it('add parameters in log sql when use bind value', async function() { - let logSql; - const typeCast = dialect === 'postgres' ? '::text' : ''; - await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }); - expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); - }); - }); - }); - - it('executes select queries correctly', async function() { - await this.sequelize.query(this.insertQuery); - const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - - it('executes select queries correctly when quoteIdentifiers is false', async function() { - const seq = Object.create(this.sequelize); - - seq.options.quoteIdentifiers = false; - await seq.query(this.insertQuery); - const [users] = await seq.query(`select * from ${qq(this.User.tableName)}`); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - - it('executes select query with dot notation results', async function() { - await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); - await this.sequelize.query(this.insertQuery); - const [users] = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); - expect(users).to.deep.equal([{ 'user.username': 'john' }]); - }); - - it('executes select query with dot notation results and nest it', async function() { - await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); - await this.sequelize.query(this.insertQuery); - const users = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); - expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); - }); - - if (dialect === 'mysql') { - it('executes stored procedures', async function() { - await this.sequelize.query(this.insertQuery); - await this.sequelize.query('DROP PROCEDURE IF EXISTS foo'); - - await this.sequelize.query( - `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` - ); - - const users = await this.sequelize.query('CALL foo()'); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - } else { - console.log('FIXME: I want to be supported in this dialect as well :-('); - } - - it('uses the passed model', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User - }); - - expect(users[0]).to.be.instanceof(this.User); - }); - - it('maps the field names to attributes based on the passed model', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User, - mapToModel: true - }); - - expect(users[0].emailAddress).to.be.equal('john@gmail.com'); - }); - - it('arbitrarily map the field names', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'userName', email_address: 'email' } - }); - - expect(users[0].userName).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - - it('keeps field names that are mapped to the same name', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'username', email_address: 'email' } - }); - - expect(users[0].username).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - - describe('rejections', () => { - it('reject if `values` and `options.replacements` are both passed', async function() { - await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.bind` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); - }); - - it('reject if `options.replacements` and `options.bind` are both passed', async function() { - await this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `sql.values` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.replacements`` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `options.bind` and `sql.replacements` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject when key is missing in the passed object', async function() { - await this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed number', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed string', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed date', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject when binds passed with object and numeric $1 is also present', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - - await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when binds passed as array and $alpha is also present', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - - await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $0 with the passed array', async function() { - await this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $01 with the passed array', async function() { - await this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed array', async function() { - await this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed object', async function() { - await this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed number for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed string for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed date for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - }); - - it('properly adds and escapes replacement value', async function() { - let logSql; - const number = 1, - date = new Date(), - string = 't\'e"st', - boolean = true, - buffer = Buffer.from('t\'e"st'); - - date.setMilliseconds(0); - - const result = await this.sequelize.query({ - query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', - values: [number, date, string, boolean, buffer] - }, { - type: this.sequelize.QueryTypes.SELECT, - logging(s) { - logSql = s; - } - }); - - const res = result[0] || {}; - res.date = res.date && new Date(res.date); - res.boolean = res.boolean && true; - if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { - res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); - } - expect(res).to.deep.equal({ - number, - date, - string, - boolean, - buffer - }); - expect(logSql).to.not.include('?'); - }); - - it('it allows to pass custom class instances', async function() { - let logSql; - class SQLStatement { - constructor() { - this.values = [1, 2]; - } - get query() { - return 'select ? as foo, ? as bar'; - } - } - const result = await this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - - it('uses properties `query` and `values` if query is tagged', async function() { - let logSql; - const result = await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - - it('uses properties `query` and `bind` if query is tagged', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - } else if (dialect === 'mssql') { - expect(logSql).to.include('@0'); - expect(logSql).to.include('@1'); - } else if (dialect === 'mysql') { - expect(logSql.match(/\?/g).length).to.equal(2); - } - }); - - it('dot separated attributes when doing a raw query without nest', async function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - await expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); - }); - - it('destructs dot separated attributes when doing a raw query using nest', async function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - const result = await this.sequelize.query(sql, { raw: true, nest: true }); - expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); - }); - - it('replaces token with the passed array', async function() { - const result = await this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - }); - - it('replaces named parameters with the passed object', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); - }); - - it('replaces named parameters with the passed object and ignore those which does not qualify', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); - }); - - it('replaces named parameters with the passed object using the same key twice', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - }); - - it('replaces named parameters with the passed object having a null property', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: null }]); - }); - - it('binds token with the passed array', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - - it('binds named parameters with the passed object', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - } - if (dialect === 'sqlite') { - expect(logSql).to.include('$one'); - } - }); - - it('binds named parameters with the passed object using the same key twice', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - expect(logSql).to.not.include('$3'); - } - }); - - it('binds named parameters with the passed object having a null property', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); - }); - - it('binds named parameters array handles escaped $$', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - - it('binds named parameters object handles escaped $$', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); - }); - - it('escape where has $ on the middle of characters', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }); - expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); - }); - - if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - it('does not improperly escape arrays of strings bound to named parameters', async function() { - const result = await this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }); - expect(result[0]).to.deep.equal([{ foo: '"string"' }]); - }); - } - - it('handles AS in conjunction with functions just fine', async function() { - let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; - if (dialect === 'mssql') { - datetime = 'GETDATE()'; - } - - const [result] = await this.sequelize.query(`SELECT ${datetime} AS t`); - expect(moment(result[0].t).isValid()).to.be.true; - }); - - if (Support.getTestDialect() === 'postgres') { - it('replaces named parameters with the passed object and ignores casts', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); - }); - - it('supports WITH queries', async function() { - await expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) - .to.eventually.deep.equal([{ 'sum': '5050' }]); - }); - } - }); -}); diff --git a/test/integration/support.js b/test/integration/support.js deleted file mode 100644 index 44d68aa1b044..000000000000 --- a/test/integration/support.js +++ /dev/null @@ -1,72 +0,0 @@ -'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 CLEANUP_TIMEOUT = Number.parseInt(process.env.SEQ_TEST_CLEANUP_TIMEOUT, 10) || 10000; - -let runningQueries = new Set(); - -before(function() { - this.sequelize.addHook('beforeQuery', (options, query) => { - runningQueries.add(query); - }); - this.sequelize.addHook('afterQuery', (options, query) => { - runningQueries.delete(query); - }); -}); - -beforeEach(async function() { - await Support.clearDatabase(this.sequelize); -}); - -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}`); - } - } -}); - -module.exports = Support; diff --git a/test/integration/timezone.test.js b/test/integration/timezone.test.js deleted file mode 100644 index 8349288e74db..000000000000 --- a/test/integration/timezone.test.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - dialect = Support.getTestDialect(); - -if (dialect !== 'sqlite') { - // Sqlite does not support setting timezone - - describe(Support.getTestDialectTeaser('Timezone'), () => { - beforeEach(function() { - this.sequelizeWithTimezone = Support.createSequelizeInstance({ - timezone: '+07:00' - }); - this.sequelizeWithNamedTimezone = Support.createSequelizeInstance({ - timezone: 'America/New_York' - }); - }); - - it('returns the same value for current timestamp', async function() { - let now = 'now()'; - const startQueryTime = Date.now(); - - if (dialect === 'mssql') { - now = 'GETDATE()'; - } - - const query = `SELECT ${now} as now`; - - const [now1, now2] = await Promise.all([ - this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), - this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }) - ]); - - 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', async function() { - const NormalUser = this.sequelize.define('user', {}), - TimezonedUser = this.sequelizeWithTimezone.define('user', {}); - - 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', async function() { - const NormalUser = this.sequelize.define('user', {}), - TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); - - 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/tmp/.gitkeep b/test/integration/tmp/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js deleted file mode 100644 index d4605dca3caf..000000000000 --- a/test/integration/transaction.test.js +++ /dev/null @@ -1,1160 +0,0 @@ -'use strict'; - -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) { - - describe(Support.getTestDialectTeaser('Transaction'), () => { - beforeEach(function() { - this.sinon = sinon.createSandbox(); - }); - - afterEach(function() { - this.sinon.restore(); - }); - - describe('constructor', () => { - it('stores options', function() { - const transaction = new Transaction(this.sequelize); - expect(transaction.options).to.be.an.instanceOf(Object); - }); - - it('generates an identifier', function() { - const transaction = new Transaction(this.sequelize); - expect(transaction.id).to.exist; - }); - - it('should call dialect specific generateTransactionId method', function() { - const transaction = new Transaction(this.sequelize); - expect(transaction.id).to.exist; - if (dialect === 'mssql') { - expect(transaction.id).to.have.lengthOf(20); - } - }); - }); - - describe('commit', () => { - it('is a commit method available', () => { - expect(Transaction).to.respondTo('commit'); - }); - }); - - describe('rollback', () => { - it('is a rollback method available', () => { - expect(Transaction).to.respondTo('rollback'); - }); - }); - - describe('autoCallback', () => { - it('supports automatically committing', async function() { - await this.sequelize.transaction(async () => {}); - }); - - it('supports automatically rolling back with a thrown error', async function() { - let t; - - await expect(this.sequelize.transaction(transaction => { - t = transaction; - throw new Error('Yolo'); - })).to.eventually.be.rejected; - - expect(t.finished).to.be.equal('rollback'); - }); - - it('supports automatically rolling back with a rejection', async function() { - let t; - - await expect(this.sequelize.transaction(async transaction => { - t = transaction; - throw new Error('Swag'); - })).to.eventually.be.rejected; - - expect(t.finished).to.be.equal('rollback'); - }); - - it('supports running hooks when a transaction is committed', async function() { - const hook = sinon.spy(); - let transaction; - - 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', async function() { - const hook = sinon.spy(); - - await expect(this.sequelize.transaction(async transaction => { - transaction.afterCommit(hook); - throw new Error('Rollback'); - }) - ).to.eventually.be.rejected; - - expect(hook).to.not.have.been.called; - }); - - if (dialect === 'postgres') { - // See #3689, #3726 and #6972 (https://github.com/sequelize/sequelize/pull/6972/files#diff-533eac602d424db379c3d72af5089e9345fd9d3bbe0a26344503c22a0a5764f7L75) - it('does not try to rollback a transaction that failed upon committing with SERIALIZABLE isolation level (#3689)', async function() { - // See https://wiki.postgresql.org/wiki/SSI - - const Dots = this.sequelize.define('dots', { color: Sequelize.STRING }); - await Dots.sync({ force: true }); - - const initialData = [ - { color: 'red' }, - { color: 'green' }, - { color: 'green' }, - { color: 'red' }, - { color: 'green' }, - { color: 'red' }, - { color: 'green' }, - { color: 'green' }, - { color: 'green' }, - { color: 'red' }, - { color: 'red' }, - { color: 'red' }, - { color: 'green' }, - { color: 'red' }, - { color: 'red' }, - { color: 'red' }, - { color: 'green' }, - { color: 'red' } - ]; - - await Dots.bulkCreate(initialData); - - const isolationLevel = Transaction.ISOLATION_LEVELS.SERIALIZABLE; - - let firstTransactionGotNearCommit = false; - let secondTransactionGotNearCommit = false; - - const firstTransaction = async () => { - await this.sequelize.transaction({ isolationLevel }, async t => { - await Dots.update({ color: 'red' }, { - where: { color: 'green' }, - transaction: t - }); - await delay(1500); - firstTransactionGotNearCommit = true; - }); - }; - - 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', 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', 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', 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', async function() { - this.sinon.stub(this.sequelize.connectionManager, '_connect') - .returns(new Promise(() => {})); - - const transaction = new Transaction(this.sequelize); - - 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', async function() { - await expect( - this.sequelize.transaction().then(async t => { - await Promise.all([ - expect(t.rollback()).to.eventually.be.fulfilled, - 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', 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', async function() { - const hook = sinon.spy(); - let transaction; - - 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) { - await transaction.rollback(); - throw err; - } - throw err; - } - })() - ).to.eventually.be.fulfilled; - }); - - it('should not run hooks if a non-auto callback transaction is rolled back', async function() { - const hook = sinon.spy(); - - await expect( - (async () => { - const t = await this.sequelize.transaction(); - t.afterCommit(hook); - await t.rollback(); - expect(hook).to.not.have.been.called; - })() - ).to.eventually.be.fulfilled; - }); - - it('should throw an error if null is passed to afterCommit', async function() { - const hook = null; - let transaction; - - await expect( - (async () => { - try { - const t = await this.sequelize.transaction(); - transaction = t; - transaction.afterCommit(hook); - return await t.commit(); - } catch (err) { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - await transaction.rollback(); - throw err; - } - throw err; - } - })() - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - - it('should throw an error if undefined is passed to afterCommit', async function() { - const hook = undefined; - let transaction; - - await expect( - (async () => { - try { - const t = await this.sequelize.transaction(); - transaction = t; - transaction.afterCommit(hook); - return await t.commit(); - } catch (err) { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - await transaction.rollback(); - throw err; - } - throw err; - } - })() - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - - it('should throw an error if an object is passed to afterCommit', async function() { - const hook = {}; - let transaction; - - await expect( - (async () => { - try { - const t = await this.sequelize.transaction(); - transaction = t; - transaction.afterCommit(hook); - return await t.commit(); - } catch (err) { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - await transaction.rollback(); - throw err; - } - throw err; - } - })() - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - - it('does not allow commits after rollback', async function() { - await expect((async () => { - const t = await this.sequelize.transaction(); - await t.rollback(); - return await t.commit(); - })()).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: rollback'); - }); - - it('does not allow rollbacks after commit', async function() { - await expect((async () => { - const t = await this.sequelize.transaction(); - await t.commit(); - return await t.rollback(); - })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: commit'); - }); - - it('does not allow rollbacks after rollback', async function() { - await expect((async () => { - const t = await this.sequelize.transaction(); - await t.rollback(); - return await t.rollback(); - })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: rollback'); - }); - - it('works even if a transaction: null option is passed', async function() { - this.sinon.spy(this.sequelize, 'query'); - - const t = await this.sequelize.transaction({ - transaction: null - }); - - 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', async function() { - this.sinon.spy(this.sequelize, 'query'); - - const t = await this.sequelize.transaction({ - transaction: undefined - }); - - 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', () => { - // 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 - // properly, it should only execute a query if we're actually inside a real transaction. If it does - // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by - // throwing an error. - // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. - await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); - - const count = await Task.count(); - // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. - expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); - }); - - it('should release the connection for a deadlocked transaction (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; - } - - 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', async () => { - const sequelize = new Support.Sequelize('database', 'username', 'password', { dialect: 'sqlite' }), - User = sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }); - - 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', 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`, async function() { - const t = await this.sequelize.transaction({ - type: key - }); - - await t.rollback(); - expect(t.options.type).to.equal(Transaction.TYPES[key]); - }); - }); - - }); - - } - - if (dialect === 'sqlite') { - it('automatically retries on SQLITE_BUSY failure', async function() { - const sequelize = await Support.prepareTransactionTest(this.sequelize); - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - await User.sync({ force: true }); - const newTransactionFunc = async function() { - const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE }); - await User.create({}, { transaction: t }); - return t.commit(); - }; - await Promise.all([newTransactionFunc(), newTransactionFunc()]); - const users = await User.findAll(); - expect(users.length).to.equal(2); - }); - - it('fails with SQLITE_BUSY when retry.match is changed', async function() { - const sequelize = await Support.prepareTransactionTest(this.sequelize); - const User = sequelize.define('User', { id: { type: Support.Sequelize.INTEGER, primaryKey: true }, username: Support.Sequelize.STRING }); - await User.sync({ force: true }); - const newTransactionFunc = async function() { - const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }); - // introduce delay to force the busy state race condition to fail - await delay(1000); - await User.create({ id: null, username: `test ${t.id}` }, { transaction: t }); - return t.commit(); - }; - await expect(Promise.all([newTransactionFunc(), newTransactionFunc()])).to.be.rejectedWith('SQLITE_BUSY: database is locked'); - }); - - } - - describe('isolation levels', () => { - it('should read the most recent committed rows when using the READ COMMITTED isolation level', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING - }); - - await expect( - this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.transaction( - { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, - async transaction => { - const users0 = await User.findAll({ transaction }); - expect(users0).to.have.lengthOf(0); - await User.create({ username: 'jan' }); // Create a User outside of the transaction - const users = await User.findAll({ transaction }); - expect(users).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction - } - ); - }) - ).to.eventually.be.fulfilled; - }); - - // mssql is excluded because it implements REPREATABLE READ using locks rather than a snapshot, and will see the new row - if (!['sqlite', 'mssql'].includes(dialect)) { - it('should not read newly committed rows when using the REPEATABLE READ isolation level', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING - }); - - await expect( - this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ }, async transaction => { - const users0 = await User.findAll({ transaction }); - await expect( users0 ).to.have.lengthOf(0); - await User.create({ username: 'jan' }); // Create a User outside of the transaction - const users = await User.findAll({ transaction }); - return expect( users ).to.have.lengthOf(0); // We SHOULD NOT see the created user inside the transaction - }); - }) - ).to.eventually.be.fulfilled; - }); - } - - // PostgreSQL is excluded because it detects Serialization Failure on commit instead of acquiring locks on the read rows - if (!['sqlite', 'postgres', 'postgres-native'].includes(dialect)) { - it('should block updates after reading a row using SERIALIZABLE', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING - }), - transactionSpy = sinon.spy(); - - await this.sequelize.sync({ force: true }); - await User.create({ username: 'jan' }); - const transaction = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }); - await User.findAll( { transaction } ); - - await Promise.all([ - // Update should not succeed before transaction has committed - User.update({ username: 'joe' }, { - where: { - username: 'jan' - } - }).then(() => { - expect(transactionSpy).to.have.been.called; - expect(transaction.finished).to.equal('commit'); - }), - - delay(4000) - .then(transactionSpy) - .then(() => transaction.commit()) - ]); - }); - } - - }); - - - if (current.dialect.supports.lock) { - describe('row locking', () => { - it('supports for update', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }), - t1Spy = sinon.spy(), - t2Spy = sinon.spy(); - - 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', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }); - - 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', 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' }); - - 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', 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' }); - - 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', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }), - t1Spy = sinon.spy(), - t2Spy = sinon.spy(); - - await this.sequelize.sync({ force: true }); - await User.create({ username: 'jan' }); - const t1 = await this.sequelize.transaction(); - - const t1Jan = await User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.NO_KEY_UPDATE, - transaction: t1 - }); - - const t2 = await this.sequelize.transaction(); - - await Promise.all([(async () => { - await User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.KEY_SHARE, - transaction: t2 - }); - - t2Spy(); - return await t2.commit(); - })(), (async () => { - await t1Jan.update({ - awesome: true - }, { - transaction: t1 - }); - - await delay(2000); - t1Spy(); - expect(t1Spy).to.have.been.calledAfter(t2Spy); - return await t1.commit(); - })()]); - }); - } - - 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 deleted file mode 100644 index 6877fbdd8841..000000000000 --- a/test/integration/trigger.test.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -const chai = require('chai'), - Sequelize = require('../../index'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize; - -if (current.dialect.supports.tmpTableTrigger) { - describe(Support.getTestDialectTeaser('Model'), () => { - describe('trigger', () => { - let User; - const triggerQuery = 'create trigger User_ChangeTracking on [users] for insert,update, delete \n' + - 'as\n' + - 'SET NOCOUNT ON\n' + - 'if exists(select 1 from inserted)\n' + - 'begin\n' + - 'select * from inserted\n' + - 'end\n' + - 'if exists(select 1 from deleted)\n' + - 'begin\n' + - 'select * from deleted\n' + - 'end\n'; - - beforeEach(async function() { - User = this.sequelize.define('user', { - username: { - type: Sequelize.STRING, - field: 'user_name' - } - }, { - hasTrigger: true - }); - - await User.sync({ force: true }); - - await this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); - }); - - it('should return output rows after insert', async () => { - await User.create({ - username: 'triggertest' - }); - - await expect(User.findOne({ username: 'triggertest' })).to.eventually.have.property('username').which.equals('triggertest'); - }); - - it('should return output rows after instance update', async () => { - const user = await User.create({ - username: 'triggertest' - }); - - 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', async () => { - const user = await User.create({ - username: 'triggertest' - }); - - 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', async () => { - const user = await User.create({ - username: 'triggertest' - }); - - 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 deleted file mode 100644 index 622047e61b21..000000000000 --- a/test/integration/utils.test.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Utils = require('../../lib/utils'), - Support = require('./support'), - DataTypes = require('../../lib/data-types'), - Sequelize = require('../../index'), - Op = Sequelize.Op; - -describe(Support.getTestDialectTeaser('Utils'), () => { - describe('underscore', () => { - describe('underscoredIf', () => { - it('is defined', () => { - expect(Utils.underscoredIf).to.be.ok; - }); - - it('underscores if second param is true', () => { - expect(Utils.underscoredIf('fooBar', true)).to.equal('foo_bar'); - }); - - it('doesn\'t underscore if second param is false', () => { - expect(Utils.underscoredIf('fooBar', false)).to.equal('fooBar'); - }); - }); - - describe('camelizeIf', () => { - it('is defined', () => { - expect(Utils.camelizeIf).to.be.ok; - }); - - it('camelizes if second param is true', () => { - expect(Utils.camelizeIf('foo_bar', true)).to.equal('fooBar'); - }); - - it('doesn\'t camelize if second param is false', () => { - expect(Utils.underscoredIf('fooBar', true)).to.equal('foo_bar'); - }); - }); - }); - - describe('format', () => { - it('should format where clause correctly when the value is truthy', () => { - const where = ['foo = ?', 1]; - expect(Utils.format(where)).to.equal('foo = 1'); - }); - - it('should format where clause correctly when the value is false', () => { - const where = ['foo = ?', 0]; - expect(Utils.format(where)).to.equal('foo = 0'); - }); - }); - - describe('cloneDeep', () => { - it('should clone objects', () => { - const obj = { foo: 1 }, - clone = Utils.cloneDeep(obj); - - expect(obj).to.not.equal(clone); - }); - - it('should clone nested objects', () => { - const obj = { foo: { bar: 1 } }, - clone = Utils.cloneDeep(obj); - - expect(obj.foo).to.not.equal(clone.foo); - }); - - it('should not call clone methods on plain objects', () => { - expect(() => { - Utils.cloneDeep({ - clone() { - throw new Error('clone method called'); - } - }); - }).to.not.throw(); - }); - - it('should not call clone methods on arrays', () => { - expect(() => { - const arr = []; - arr.clone = function() { - throw new Error('clone method called'); - }; - - Utils.cloneDeep(arr); - }).to.not.throw(); - }); - }); - - if (Support.getTestDialect() === 'postgres') { - describe('json', () => { - beforeEach(function() { - this.queryGenerator = this.sequelize.getQueryInterface().queryGenerator; - }); - - it('successfully parses a complex nested condition hash', function() { - const conditions = { - metadata: { - language: 'icelandic', - pg_rating: { 'dk': 'G' } - }, - another_json_field: { x: 1 } - }; - const expected = '("metadata"#>>\'{language}\') = \'icelandic\' AND ("metadata"#>>\'{pg_rating,dk}\') = \'G\' AND ("another_json_field"#>>\'{x}\') = \'1\''; - expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(conditions))).to.deep.equal(expected); - }); - - it('successfully parses a string using dot notation', function() { - const path = 'metadata.pg_rating.dk'; - expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal('("metadata"#>>\'{pg_rating,dk}\')'); - }); - - it('allows postgres json syntax', function() { - const path = 'metadata->pg_rating->>dk'; - expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal(path); - }); - - it('can take a value to compare against', function() { - const path = 'metadata.pg_rating.is'; - const value = 'U'; - expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path, value))).to.equal('("metadata"#>>\'{pg_rating,is}\') = \'U\''); - }); - }); - } - - describe('inflection', () => { - it('works better than lingo ;)', () => { - expect(Utils.pluralize('buy')).to.equal('buys'); - expect(Utils.pluralize('holiday')).to.equal('holidays'); - expect(Utils.pluralize('days')).to.equal('days'); - expect(Utils.pluralize('status')).to.equal('statuses'); - - expect(Utils.singularize('status')).to.equal('status'); - }); - }); - - describe('Sequelize.fn', () => { - let Airplane; - - beforeEach(async function() { - Airplane = this.sequelize.define('Airplane', { - wings: DataTypes.INTEGER, - engines: DataTypes.INTEGER - }); - - 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)', async function() { - const type = Support.getTestDialect() === 'mysql' ? 'unsigned' : 'int'; - - const [airplane] = await Airplane.findAll({ - attributes: [ - [this.sequelize.fn('COUNT', '*'), 'count'], - [Sequelize.fn('SUM', Sequelize.cast({ - engines: 1 - }, type)), 'count-engines'], - [Sequelize.fn('SUM', Sequelize.cast({ - [Op.or]: { - engines: { - [Op.gt]: 1 - }, - wings: 4 - } - }, type)), 'count-engines-wings'] - ] - }); - - // 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)', async function() { - const [airplane] = await Airplane.findAll({ - attributes: [ - [this.sequelize.fn('COUNT', '*'), 'count'], - [Sequelize.fn('SUM', { - engines: 1 - }), 'count-engines'], - [Sequelize.fn('SUM', { - [Op.or]: { - engines: { - [Op.gt]: 1 - }, - wings: 4 - } - }), 'count-engines-wings'] - ] - }); - - // 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); - }); - } - }); - - describe('flattenObjectDeep', () => { - it('should return the value if it is not an object', () => { - const value = 'non-object'; - const returnedValue = Utils.flattenObjectDeep(value); - expect(returnedValue).to.equal(value); - }); - - it('should return correctly if values are null', () => { - const value = { - name: 'John', - address: { - street: 'Fake St. 123', - city: null, - coordinates: { - longitude: 55.6779627, - latitude: 12.5964313 - } - } - }; - const returnedValue = Utils.flattenObjectDeep(value); - expect(returnedValue).to.deep.equal({ - name: 'John', - 'address.street': 'Fake St. 123', - 'address.city': null, - 'address.coordinates.longitude': 55.6779627, - 'address.coordinates.latitude': 12.5964313 - }); - }); - }); -}); diff --git a/test/integration/vectors.test.js b/test/integration/vectors.test.js deleted file mode 100644 index e604d57a4267..000000000000 --- a/test/integration/vectors.test.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../index'), - Support = require('./support'); - -chai.should(); - -describe(Support.getTestDialectTeaser('Vectors'), () => { - it('should not allow insert backslash', async function() { - const Student = this.sequelize.define('student', { - name: Sequelize.STRING - }, { - tableName: 'student' - }); - - 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/register-esbuild.js b/test/register-esbuild.js new file mode 100644 index 000000000000..7f5413b6af5c --- /dev/null +++ b/test/register-esbuild.js @@ -0,0 +1,55 @@ +'use strict'; + +const hook = require('node-hook'); +const esbuild = require('esbuild'); +const sourceMapSupport = require('source-map-support'); + +const maps = {}; + +// This logic is sourced from https://github.com/babel/babel/blob/39ba1ff300a5c9448ccd40a50a017e7f24e5cd56/packages/babel-register/src/node.js#L15-L31 +function installSourceMapSupport() { + sourceMapSupport.install({ + handleUncaughtExceptions: false, + environment: 'node', + retrieveSourceMap(source) { + const map = maps && maps[source]; + if (map) { + return { + url: null, + map, + }; + } + + return null; + }, + }); +} + +function compileFor(loader) { + return (source, sourcefile) => { + const { code, map } = esbuild.transformSync(source, { + sourcemap: true, + target: 'node18', + format: 'cjs', + sourcefile, + loader, + tsconfigRaw: { + compilerOptions: { + target: 'node18', + useDefineForClassFields: true, + experimentalDecorators: true, + }, + }, + }); + + if (Object.keys(maps).length === 0) { + installSourceMapSupport(); + } + + maps[sourcefile] = map; + + return code; + }; +} + +hook.hook('.ts', compileFor('ts')); diff --git a/test/support.js b/test/support.js deleted file mode 100644 index e62f0861362a..000000000000 --- a/test/support.js +++ /dev/null @@ -1,262 +0,0 @@ -'use strict'; - -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'); - -chai.use(require('chai-datetime')); -chai.use(require('chai-as-promised')); -chai.use(require('sinon-chai')); -chai.config.includeStack = true; -chai.should(); - -// Make sure errors get thrown when testing -process.on('uncaughtException', e => { - console.error('An unhandled exception occurred:'); - throw 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; -}); - -if (global.afterEach) { - afterEach(() => { - onNextUnhandledRejection = null; - unhandledRejections = null; - }); -} - -let lastSqliteInstance; - -const Support = { - Sequelize, - - /** - * Returns a Promise that will reject with the next unhandled rejection that occurs - * during this test (instead of failing the test) - */ - nextUnhandledRejection() { - return new Promise((resolve, reject) => onNextUnhandledRejection = reject); - }, - - /** - * Pushes all unhandled rejections that occur during this test onto destArray - * (instead of failing the test). - * - * @param {Error[]} destArray the array to push unhandled rejections onto. If you omit this, - * one will be created and returned for you. - * - * @returns {Error[]} destArray - */ - captureUnhandledRejections(destArray = []) { - return unhandledRejections = destArray; - }, - - async prepareTransactionTest(sequelize) { - const dialect = Support.getTestDialect(); - - if (dialect === 'sqlite') { - const p = path.join(__dirname, 'tmp', 'db.sqlite'); - if (lastSqliteInstance) { - await lastSqliteInstance.close(); - } - if (fs.existsSync(p)) { - fs.unlinkSync(p); - } - const options = { ...sequelize.options, storage: p }, - _sequelize = new Sequelize(sequelize.config.database, null, null, options); - - await _sequelize.sync({ force: true }); - lastSqliteInstance = _sequelize; - return _sequelize; - } - return sequelize; - }, - - createSequelizeInstance(options) { - options = options || {}; - options.dialect = this.getTestDialect(); - - const config = Config[options.dialect]; - - const sequelizeOptions = _.defaults(options, { - host: options.host || config.host, - logging: process.env.SEQ_LOG ? console.log : false, - dialect: options.dialect, - port: options.port || process.env.SEQ_PORT || config.port, - pool: config.pool, - dialectOptions: options.dialectOptions || config.dialectOptions || {}, - minifyAliases: options.minifyAliases || config.minifyAliases - }); - - if (process.env.DIALECT === 'postgres-native') { - sequelizeOptions.native = true; - } - - if (config.storage) { - sequelizeOptions.storage = config.storage; - } - - return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions); - }, - - getConnectionOptionsWithoutPool() { - // Do not break existing config object - shallow clone before `delete config.pool` - const config = { ...Config[this.getTestDialect()] }; - delete config.pool; - return config; - }, - - getSequelizeInstance(db, user, pass, options) { - options = options || {}; - options.dialect = options.dialect || this.getTestDialect(); - return new Sequelize(db, user, pass, options); - }, - - async clearDatabase(sequelize) { - const qi = sequelize.getQueryInterface(); - await qi.dropAllTables(); - sequelize.modelManager.models = []; - sequelize.models = {}; - - if (qi.dropAllEnums) { - await qi.dropAllEnums(); - } - await this.dropTestSchemas(sequelize); - }, - - async dropTestSchemas(sequelize) { - const queryInterface = sequelize.getQueryInterface(); - if (!queryInterface.queryGenerator._dialect.supports.schemas) { - return this.sequelize.drop({}); - } - - 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() { - return fs.readdirSync(`${__dirname}/../lib/dialects`) - .filter(file => !file.includes('.js') && !file.includes('abstract')); - }, - - getAbstractQueryGenerator(sequelize) { - class ModdedQueryGenerator extends AbstractQueryGenerator { - quoteIdentifier(x) { - return x; - } - } - - const queryGenerator = new ModdedQueryGenerator({ - sequelize, - _dialect: sequelize.dialect - }); - - return queryGenerator; - }, - - getTestDialect() { - let envDialect = process.env.DIALECT || 'mysql'; - - if (envDialect === 'postgres-native') { - envDialect = 'postgres'; - } - - if (!this.getSupportedDialects().includes(envDialect)) { - throw new Error(`The dialect you have passed is unknown. Did you really mean: ${envDialect}`); - } - - return envDialect; - }, - - getTestDialectTeaser(moduleName) { - let dialect = this.getTestDialect(); - - if (process.env.DIALECT === 'postgres-native') { - dialect = 'postgres-native'; - } - - 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]; - - if (!expectation) { - if (expectations['default'] !== undefined) { - expectation = expectations['default']; - if (typeof expectation === 'string') { - expectation = expectation - .replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT) - .replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT); - } - } else { - throw new Error(`Undefined expectation for "${Support.sequelize.dialect.name}"!`); - } - } - - if (query instanceof Error) { - expect(query.message).to.equal(expectation.message); - } else { - expect(query.query || query).to.equal(expectation); - } - - if (assertions.bind) { - 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)); - } -}; - -if (global.beforeEach) { - before(function() { - this.sequelize = Support.sequelize; - }); - beforeEach(function() { - this.sequelize = Support.sequelize; - }); -} - -Support.sequelize = Support.createSequelizeInstance(); -module.exports = Support; diff --git a/test/teaser.js b/test/teaser.js deleted file mode 100644 index 1dd3a22c72f9..000000000000 --- a/test/teaser.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -if (!process.env.DIALECT) { - throw new Error('Environment variable DIALECT is undefined'); -} - -const DIALECT = process.env.DIALECT; -const header = '#'.repeat(DIALECT.length + 22); -const message = `${header}\n# Running tests for ${DIALECT} #\n${header}`; - -console.log(message); diff --git a/test/tmp/.gitkeep b/test/tmp/.gitkeep deleted file mode 100644 index 8b137891791f..000000000000 --- a/test/tmp/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit/associations/association.test.js b/test/unit/associations/association.test.js deleted file mode 100644 index 5cc04391bb3d..000000000000 --- a/test/unit/associations/association.test.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const current = Support.sequelize; -const AssociationError = require('../../../lib/errors').AssociationError; - -describe(Support.getTestDialectTeaser('belongsTo'), () => { - it('should throw an AssociationError when two associations have the same alias', () => { - const User = current.define('User'); - const Task = current.define('Task'); - - User.belongsTo(Task, { as: 'task' }); - const errorFunction = User.belongsTo.bind(User, Task, { as: 'task' }); - const errorMessage = 'You have used the alias task in two separate associations. Aliased associations must have unique aliases.'; - expect(errorFunction).to.throw(AssociationError, errorMessage); - }); -}); diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js deleted file mode 100644 index c3b12ab6d3e3..000000000000 --- a/test/unit/associations/belongs-to-many.test.js +++ /dev/null @@ -1,798 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const sinon = require('sinon'); -const expect = chai.expect; -const stub = sinon.stub; -const _ = require('lodash'); -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const BelongsTo = require('../../../lib/associations/belongs-to'); -const HasMany = require('../../../lib/associations/has-many'); -const HasOne = require('../../../lib/associations/has-one'); -const current = Support.sequelize; -const AssociationError = require('../../../lib/errors').AssociationError; - -describe(Support.getTestDialectTeaser('belongsToMany'), () => { - it('throws when invalid model is passed', () => { - const User = current.define('User'); - - expect(() => { - User.belongsToMany(); - }).to.throw('User.belongsToMany called with something that\'s not a subclass of Sequelize.Model'); - }); - - it('should not inherit scopes from parent to join table', () => { - const A = current.define('a'), - B = current.define('b', {}, { - defaultScope: { - where: { - foo: 'bar' - } - }, - scopes: { - baz: { - where: { - fooz: 'zab' - } - } - } - }); - - B.belongsToMany(A, { through: 'AB' }); - - const AB = current.model('AB'); - - expect(AB.options.defaultScope).to.deep.equal({}); - expect(AB.options.scopes).to.deep.equal({}); - }); - - it('should not inherit validations from parent to join table', () => { - const A = current.define('a'), - B = current.define('b', {}, { - validate: { - validateModel() { - return true; - } - } - }); - - B.belongsToMany(A, { through: 'AB' }); - - const AB = current.model('AB'); - - expect(AB.options.validate).to.deep.equal({}); - }); - - it('should not override custom methods with association mixin', () => { - const methods = { - getTasks: 'get', - countTasks: 'count', - hasTask: 'has', - hasTasks: 'has', - setTasks: 'set', - addTask: 'add', - addTasks: 'add', - removeTask: 'remove', - removeTasks: 'remove', - createTask: 'create' - }; - const User = current.define('User'); - const Task = current.define('Task'); - - _.each(methods, (alias, method) => { - User.prototype[method] = function() { - const realMethod = this.constructor.associations.task[alias]; - expect(realMethod).to.be.a('function'); - return realMethod; - }; - }); - - User.belongsToMany(Task, { through: 'UserTasks', as: 'task' }); - - const user = User.build(); - - _.each(methods, (alias, method) => { - expect(user[method]()).to.be.a('function'); - }); - }); - - describe('proper syntax', () => { - it('throws an AssociationError if the through option is undefined, true, or null', () => { - const User = current.define('User', {}); - const Task = current.define('Task', {}); - - const errorFunction1 = User.belongsToMany.bind(User, Task, { through: true }); - const errorFunction2 = User.belongsToMany.bind(User, Task, { through: undefined }); - const errorFunction3 = User.belongsToMany.bind(User, Task, { through: null }); - for (const errorFunction of [errorFunction1, errorFunction2, errorFunction3]) { - expect(errorFunction).to.throw(AssociationError, `${User.name}.belongsToMany(${Task.name}) requires through option, pass either a string or a model`); - } - }); - it('throws an AssociationError for a self-association defined without an alias', () => { - const User = current.define('User', {}); - - const errorFunction = User.belongsToMany.bind(User, User, { through: 'jointable' }); - expect(errorFunction).to.throw(AssociationError, '\'as\' must be defined for many-to-many self-associations'); - }); - }); - - describe('timestamps', () => { - it('follows the global timestamps true option', () => { - const User = current.define('User', {}), - Task = current.define('Task', {}); - - User.belongsToMany(Task, { through: 'user_task1' }); - - expect(current.models.user_task1.rawAttributes).to.contain.all.keys(['createdAt', 'updatedAt']); - }); - - it('allows me to override the global timestamps option', () => { - const User = current.define('User', {}), - Task = current.define('Task', {}); - - User.belongsToMany(Task, { through: 'user_task2', timestamps: false }); - - expect(current.models.user_task2.rawAttributes).not.to.contain.all.keys(['createdAt', 'updatedAt']); - }); - - it('follows the global timestamps false option', () => { - const current = Support.createSequelizeInstance({ - timestamps: false - }); - - const User = current.define('User', {}), - Task = current.define('Task', {}); - - User.belongsToMany(Task, { through: 'user_task3' }); - - expect(current.models.user_task3.rawAttributes).not.to.have.all.keys(['createdAt', 'updatedAt']); - }); - }); - - describe('optimizations using bulk create, destroy and update', () => { - const User = current.define('User', { username: DataTypes.STRING }), - Task = current.define('Task', { title: DataTypes.STRING }), - UserTasks = current.define('UserTasks', {}); - - User.belongsToMany(Task, { through: UserTasks }); - Task.belongsToMany(User, { through: UserTasks }); - - const user = User.build({ - id: 42 - }), - task1 = Task.build({ - id: 15 - }), - task2 = Task.build({ - id: 16 - }); - - beforeEach(function() { - this.findAll = stub(UserTasks, 'findAll').resolves([]); - this.bulkCreate = stub(UserTasks, 'bulkCreate').resolves([]); - this.destroy = stub(UserTasks, 'destroy').resolves([]); - }); - - afterEach(function() { - this.findAll.restore(); - this.bulkCreate.restore(); - this.destroy.restore(); - }); - - 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', async function() { - this.findAll - .onFirstCall().resolves([]) - .onSecondCall().resolves([ - { userId: 42, taskId: 15 }, - { userId: 42, taskId: 16 } - ]); - - await user.setTasks([task1, task2]); - await user.setTasks(null); - expect(this.findAll).to.have.been.calledTwice; - expect(this.destroy).to.have.been.calledOnce; - }); - }); - - describe('foreign keys', () => { - it('should infer otherKey from paired BTM relationship with a through string defined', function() { - const User = this.sequelize.define('User', {}); - const Place = this.sequelize.define('Place', {}); - - const Places = User.belongsToMany(Place, { through: 'user_places', foreignKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: 'user_places', foreignKey: 'place_id' }); - - expect(Places.paired).to.equal(Users); - expect(Users.paired).to.equal(Places); - - expect(Places.foreignKey).to.equal('user_id'); - expect(Users.foreignKey).to.equal('place_id'); - - expect(Places.otherKey).to.equal('place_id'); - expect(Users.otherKey).to.equal('user_id'); - }); - - it('should infer otherKey from paired BTM relationship with a through model defined', function() { - const User = this.sequelize.define('User', {}); - const Place = this.sequelize.define('Place', {}); - const UserPlace = this.sequelize.define('UserPlace', { - id: { - primaryKey: true, - type: DataTypes.INTEGER, - autoIncrement: true - } - }, { timestamps: false }); - - const Places = User.belongsToMany(Place, { through: UserPlace, foreignKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: UserPlace, foreignKey: 'place_id' }); - - expect(Places.paired).to.equal(Users); - expect(Users.paired).to.equal(Places); - - expect(Places.foreignKey).to.equal('user_id'); - expect(Users.foreignKey).to.equal('place_id'); - - expect(Places.otherKey).to.equal('place_id'); - expect(Users.otherKey).to.equal('user_id'); - - expect(Object.keys(UserPlace.rawAttributes).length).to.equal(3); // Defined primary key and two foreign keys - }); - }); - - describe('source/target keys', () => { - it('should infer targetKey from paired BTM relationship with a through string defined', function() { - const User = this.sequelize.define('User', { user_id: DataTypes.UUID }); - const Place = this.sequelize.define('Place', { place_id: DataTypes.UUID }); - - const Places = User.belongsToMany(Place, { through: 'user_places', sourceKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: 'user_places', sourceKey: 'place_id' }); - - expect(Places.paired).to.equal(Users); - expect(Users.paired).to.equal(Places); - - expect(Places.sourceKey).to.equal('user_id'); - expect(Users.sourceKey).to.equal('place_id'); - - expect(Places.targetKey).to.equal('place_id'); - expect(Users.targetKey).to.equal('user_id'); - }); - - it('should infer targetKey from paired BTM relationship with a through model defined', function() { - const User = this.sequelize.define('User', { user_id: DataTypes.UUID }); - const Place = this.sequelize.define('Place', { place_id: DataTypes.UUID }); - const UserPlace = this.sequelize.define('UserPlace', { - id: { - primaryKey: true, - type: DataTypes.INTEGER, - autoIncrement: true - } - }, { timestamps: false }); - - const Places = User.belongsToMany(Place, { through: UserPlace, sourceKey: 'user_id' }); - const Users = Place.belongsToMany(User, { through: UserPlace, sourceKey: 'place_id' }); - - expect(Places.paired).to.equal(Users); - expect(Users.paired).to.equal(Places); - - expect(Places.sourceKey).to.equal('user_id'); - expect(Users.sourceKey).to.equal('place_id'); - - expect(Places.targetKey).to.equal('place_id'); - expect(Users.targetKey).to.equal('user_id'); - - expect(Object.keys(UserPlace.rawAttributes).length).to.equal(3); // Defined primary key and two foreign keys - }); - }); - - describe('pseudo associations', () => { - it('should setup belongsTo relations to source and target from join model with defined foreign/other keys', function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: 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, foreignKey: 'productId', otherKey: 'tagId' }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, foreignKey: 'tagId', otherKey: 'productId' }); - - expect(Product.Tags.toSource).to.be.an.instanceOf(BelongsTo); - expect(Product.Tags.toTarget).to.be.an.instanceOf(BelongsTo); - - expect(Tag.Products.toSource).to.be.an.instanceOf(BelongsTo); - expect(Tag.Products.toTarget).to.be.an.instanceOf(BelongsTo); - - expect(Product.Tags.toSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.toTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.toSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.toTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'productId', 'tagId']); - }); - - 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 - }), - Tag = this.sequelize.define('Tag', { - name: 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, foreignKey: 'productId', otherKey: 'tagId' }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, foreignKey: 'tagId', otherKey: 'productId' }); - - expect(Product.Tags.manyFromSource).to.be.an.instanceOf(HasMany); - expect(Product.Tags.manyFromTarget).to.be.an.instanceOf(HasMany); - - expect(Tag.Products.manyFromSource).to.be.an.instanceOf(HasMany); - expect(Tag.Products.manyFromTarget).to.be.an.instanceOf(HasMany); - - expect(Product.Tags.manyFromSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.manyFromTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.manyFromSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.manyFromTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - 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() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: 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, foreignKey: 'productId', otherKey: 'tagId' }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, foreignKey: 'tagId', otherKey: 'productId' }); - - 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(Product.Tags.oneFromSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.oneFromTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.oneFromSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.oneFromTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - 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 - }), - Tag = this.sequelize.define('Tag', { - name: 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, foreignKey: 'product_ID' }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, foreignKey: 'tag_ID' }); - - expect(Product.Tags.toSource).to.be.ok; - expect(Product.Tags.toTarget).to.be.ok; - - expect(Tag.Products.toSource).to.be.ok; - expect(Tag.Products.toTarget).to.be.ok; - - expect(Product.Tags.toSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.toTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.toSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.toTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'product_ID', 'tag_ID']); - }); - - it('should setup hasOne relations to source and target from join model with only foreign keys defined', function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: 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, foreignKey: 'product_ID' }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, foreignKey: 'tag_ID' }); - - 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(Product.Tags.oneFromSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.oneFromTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.oneFromSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.oneFromTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'product_ID', 'tag_ID']); - }); - - it('should setup belongsTo relations to source and target from join model with no foreign keys defined', function() { - const Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }), - Tag = this.sequelize.define('Tag', { - name: 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 }); - Tag.Products = Tag.belongsToMany(Product, { through: ProductTag }); - - expect(Product.Tags.toSource).to.be.ok; - expect(Product.Tags.toTarget).to.be.ok; - - expect(Tag.Products.toSource).to.be.ok; - expect(Tag.Products.toTarget).to.be.ok; - - expect(Product.Tags.toSource.foreignKey).to.equal(Product.Tags.foreignKey); - expect(Product.Tags.toTarget.foreignKey).to.equal(Product.Tags.otherKey); - - expect(Tag.Products.toSource.foreignKey).to.equal(Tag.Products.foreignKey); - expect(Tag.Products.toTarget.foreignKey).to.equal(Tag.Products.otherKey); - - expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); - expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'ProductId', 'TagId']); - }); - }); - - describe('associations on the join table', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', {}); - - this.UserProjects.belongsTo(this.User); - - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); - - this.UserProjects.belongsTo(this.Project); - }); - - it('should work for belongsTo associations defined before belongsToMany', function() { - expect(this.UserProjects.prototype.getUser).to.be.ok; - }); - it('should work for belongsTo associations defined after belongsToMany', function() { - expect(this.UserProjects.prototype.getProject).to.be.ok; - }); - }); - - describe('self-associations', () => { - it('does not pair multiple self associations with different through arguments', () => { - const User = current.define('user', {}), - UserFollowers = current.define('userFollowers', {}), - Invite = current.define('invite', {}); - - User.Followers = User.belongsToMany(User, { - as: 'Followers', - through: UserFollowers - }); - - User.Invites = User.belongsToMany(User, { - as: 'Invites', - foreignKey: 'InviteeId', - through: Invite - }); - - expect(User.Followers.paired).not.to.be.ok; - expect(User.Invites.paired).not.to.be.ok; - - expect(User.Followers.otherKey).not.to.equal(User.Invites.foreignKey); - }); - - it('correctly generates a foreign/other key when none are defined', () => { - const User = current.define('user', {}), - UserFollowers = current.define('userFollowers', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }, { - timestamps: false - }); - - User.Followers = User.belongsToMany(User, { - as: 'Followers', - through: UserFollowers - }); - - expect(User.Followers.foreignKey).to.be.ok; - expect(User.Followers.otherKey).to.be.ok; - - expect(Object.keys(UserFollowers.rawAttributes).length).to.equal(3); - }); - - it('works with singular and plural name for self-associations', () => { - // Models taken from https://github.com/sequelize/sequelize/issues/3796 - const Service = current.define('service', {}); - - Service.belongsToMany(Service, { through: 'Supplements', as: 'supplements' }); - Service.belongsToMany(Service, { through: 'Supplements', as: { singular: 'supplemented', plural: 'supplemented' } }); - - - expect(Service.prototype).to.have.ownProperty('getSupplements').to.be.a('function'); - - expect(Service.prototype).to.have.ownProperty('addSupplement').to.be.a('function'); - expect(Service.prototype).to.have.ownProperty('addSupplements').to.be.a('function'); - - expect(Service.prototype).to.have.ownProperty('getSupplemented').to.be.a('function'); - expect(Service.prototype).not.to.have.ownProperty('getSupplementeds').to.be.a('function'); - - expect(Service.prototype).to.have.ownProperty('addSupplemented').to.be.a('function'); - expect(Service.prototype).not.to.have.ownProperty('addSupplementeds').to.be.a('function'); - }); - }); - - describe('constraints', () => { - - it('work properly when through is a string', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}); - - User.belongsToMany(Group, { as: 'MyGroups', through: 'group_user', onUpdate: 'RESTRICT', onDelete: 'SET NULL' }); - Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user', onUpdate: 'SET NULL', onDelete: 'RESTRICT' }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT'); - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL'); - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL'); - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT'); - }); - - it('work properly when through is a model', function() { - const User = this.sequelize.define('User', {}), - Group = this.sequelize.define('Group', {}), - UserGroup = this.sequelize.define('GroupUser', {}, { tableName: 'user_groups' }); - - User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup, onUpdate: 'RESTRICT', onDelete: 'SET NULL' }); - Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, onUpdate: 'SET NULL', onDelete: 'RESTRICT' }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onUpdate).to.equal('RESTRICT'); - expect(Group.associations.MyUsers.through.model.rawAttributes.UserId.onDelete).to.equal('SET NULL'); - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onUpdate).to.equal('SET NULL'); - expect(Group.associations.MyUsers.through.model.rawAttributes.GroupId.onDelete).to.equal('RESTRICT'); - }); - - it('generate unique identifier with very long length', function() { - const User = this.sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }), - Group = this.sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }), - UserGroup = this.sequelize.define( - 'GroupUser', - { - id_user_very_long_field: { - type: DataTypes.INTEGER(1) - }, - id_group_very_long_field: { - type: DataTypes.INTEGER(1) - } - }, - { tableName: 'table_user_group_with_very_long_name' } - ); - - User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup, foreignKey: 'id_user_very_long_field' }); - Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup, foreignKey: 'id_group_very_long_field' }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.have.lengthOf(92); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.have.lengthOf(92); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('table_user_group_with_very_long_name_id_group_very_long_field_id_user_very_long_field_unique'); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.equal('table_user_group_with_very_long_name_id_group_very_long_field_id_user_very_long_field_unique'); - }); - - it('generate unique identifier with custom name', function() { - const User = this.sequelize.define('User', {}, { tableName: 'table_user_with_very_long_name' }), - Group = this.sequelize.define('Group', {}, { tableName: 'table_group_with_very_long_name' }), - UserGroup = this.sequelize.define( - 'GroupUser', - { - id_user_very_long_field: { - type: DataTypes.INTEGER(1) - }, - id_group_very_long_field: { - type: DataTypes.INTEGER(1) - } - }, - { tableName: 'table_user_group_with_very_long_name' } - ); - - User.belongsToMany(Group, { - as: 'MyGroups', - through: UserGroup, - foreignKey: 'id_user_very_long_field', - uniqueKey: 'custom_user_group_unique' - }); - Group.belongsToMany(User, { - as: 'MyUsers', - through: UserGroup, - foreignKey: 'id_group_very_long_field', - uniqueKey: 'custom_user_group_unique' - }); - - expect(Group.associations.MyUsers.through.model === User.associations.MyGroups.through.model); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.have.lengthOf(24); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.have.lengthOf(24); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique'); - expect(Group.associations.MyUsers.through.model.rawAttributes.id_group_very_long_field.unique).to.equal('custom_user_group_unique'); - }); - }); - describe('association hooks', () => { - beforeEach(function() { - this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); - this.Tasks = this.sequelize.define('Task', { title: DataTypes.STRING }); - }); - describe('beforeBelongsToManyAssociate', () => { - it('should trigger', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); - - const beforeAssociateArgs = beforeAssociate.getCall(0).args; - - expect(beforeAssociate).to.have.been.called; - expect(beforeAssociateArgs.length).to.equal(2); - - const firstArg = beforeAssociateArgs[0]; - expect(Object.keys(firstArg).join()).to.equal('source,target,type'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('BelongsToMany'); - - expect(beforeAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); - expect(beforeAssociate).to.not.have.been.called; - }); - }); - describe('afterBelongsToManyAssociate', () => { - it('should trigger', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); - - const afterAssociateArgs = afterAssociate.getCall(0).args; - - expect(afterAssociate).to.have.been.called; - expect(afterAssociateArgs.length).to.equal(2); - - const firstArg = afterAssociateArgs[0]; - expect(Object.keys(firstArg).join()).to.equal('source,target,type,association'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('BelongsToMany'); - expect(firstArg.association.constructor.name).to.equal('BelongsToMany'); - - expect(afterAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); - expect(afterAssociate).to.not.have.been.called; - }); - }); - }); -}); diff --git a/test/unit/associations/belongs-to.test.js b/test/unit/associations/belongs-to.test.js deleted file mode 100644 index a431edfc0aa8..000000000000 --- a/test/unit/associations/belongs-to.test.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - _ = require('lodash'), - DataTypes = require('../../../lib/data-types'), - Support = require('../support'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('belongsTo'), () => { - it('throws when invalid model is passed', () => { - const User = current.define('User'); - - expect(() => { - User.belongsTo(); - }).to.throw('User.belongsTo called with something that\'s not a subclass of Sequelize.Model'); - }); - - it('warn on invalid options', () => { - const User = current.define('User', {}); - const Task = current.define('Task', {}); - - expect(() => { - User.belongsTo(Task, { targetKey: 'wowow' }); - }).to.throw('Unknown attribute "wowow" passed as targetKey, define this attribute on model "Task" first'); - }); - - it('should not override custom methods with association mixin', () => { - const methods = { - getTask: 'get', - setTask: 'set', - createTask: 'create' - }; - const User = current.define('User'); - const Task = current.define('Task'); - - _.each(methods, (alias, method) => { - User.prototype[method] = function() { - const realMethod = this.constructor.associations.task[alias]; - expect(realMethod).to.be.a('function'); - return realMethod; - }; - }); - - User.belongsTo(Task, { as: 'task' }); - - const user = User.build(); - - _.each(methods, (alias, method) => { - expect(user[method]()).to.be.a('function'); - }); - }); - describe('association hooks', () => { - beforeEach(function() { - this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); - this.Tasks = this.sequelize.define('Task', { title: DataTypes.STRING }); - }); - describe('beforeBelongsToAssociate', () => { - it('should trigger', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.belongsTo(this.Tasks, { hooks: true }); - - const beforeAssociateArgs = beforeAssociate.getCall(0).args; - - expect(beforeAssociate).to.have.been.called; - expect(beforeAssociateArgs.length).to.equal(2); - - const firstArg = beforeAssociateArgs[0]; - expect(Object.keys(firstArg).join()).to.equal('source,target,type'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('BelongsTo'); - - expect(beforeAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.belongsTo(this.Tasks, { hooks: false }); - expect(beforeAssociate).to.not.have.been.called; - }); - }); - describe('afterBelongsToAssociate', () => { - it('should trigger', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.belongsTo(this.Tasks, { hooks: true }); - - const afterAssociateArgs = afterAssociate.getCall(0).args; - - expect(afterAssociate).to.have.been.called; - expect(afterAssociateArgs.length).to.equal(2); - - const firstArg = afterAssociateArgs[0]; - - expect(Object.keys(firstArg).join()).to.equal('source,target,type,association'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('BelongsTo'); - expect(firstArg.association.constructor.name).to.equal('BelongsTo'); - - expect(afterAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.belongsTo(this.Tasks, { hooks: false }); - expect(afterAssociate).to.not.have.been.called; - }); - }); - }); -}); diff --git a/test/unit/associations/dont-modify-options.test.js b/test/unit/associations/dont-modify-options.test.js deleted file mode 100644 index 8712dc4b181f..000000000000 --- a/test/unit/associations/dont-modify-options.test.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'); - -describe(Support.getTestDialectTeaser('associations'), () => { - describe('Test options.foreignKey', () => { - beforeEach(function() { - - this.A = this.sequelize.define('A', { - id: { - type: DataTypes.CHAR(20), - primaryKey: true - } - }); - this.B = this.sequelize.define('B', { - id: { - type: Sequelize.CHAR(20), - primaryKey: true - } - }); - this.C = this.sequelize.define('C', {}); - }); - - it('should not be overwritten for belongsTo', function() { - const reqValidForeignKey = { foreignKey: { allowNull: false } }; - this.A.belongsTo(this.B, reqValidForeignKey); - this.A.belongsTo(this.C, reqValidForeignKey); - expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); - }); - it('should not be overwritten for belongsToMany', function() { - const reqValidForeignKey = { foreignKey: { allowNull: false }, through: 'ABBridge' }; - this.B.belongsToMany(this.A, reqValidForeignKey); - this.A.belongsTo(this.C, reqValidForeignKey); - expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); - }); - it('should not be overwritten for hasOne', function() { - const reqValidForeignKey = { foreignKey: { allowNull: false } }; - this.B.hasOne(this.A, reqValidForeignKey); - this.A.belongsTo(this.C, reqValidForeignKey); - expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); - }); - it('should not be overwritten for hasMany', function() { - const reqValidForeignKey = { foreignKey: { allowNull: false } }; - this.B.hasMany(this.A, reqValidForeignKey); - this.A.belongsTo(this.C, reqValidForeignKey); - expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); - }); - }); -}); diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js deleted file mode 100644 index 6cadab6f330f..000000000000 --- a/test/unit/associations/has-many.test.js +++ /dev/null @@ -1,268 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - stub = sinon.stub, - _ = require('lodash'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - HasMany = require('../../../lib/associations/has-many'), - Op = require('../../../lib/operators'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('hasMany'), () => { - it('throws when invalid model is passed', () => { - const User = current.define('User'); - - expect(() => { - User.hasMany(); - }).to.throw('User.hasMany called with something that\'s not a subclass of Sequelize.Model'); - }); - - describe('optimizations using bulk create, destroy and update', () => { - const User = current.define('User', { username: DataTypes.STRING }), - Task = current.define('Task', { title: DataTypes.STRING }); - - User.hasMany(Task); - - const user = User.build({ - id: 42 - }), - task1 = Task.build({ - id: 15 - }), - task2 = Task.build({ - id: 16 - }); - - beforeEach(function() { - this.findAll = stub(Task, 'findAll').resolves([]); - this.update = stub(Task, 'update').resolves([]); - }); - - afterEach(function() { - this.findAll.restore(); - this.update.restore(); - }); - - 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', async function() { - this.findAll - .onFirstCall().resolves([]) - .onSecondCall().resolves([ - { userId: 42, taskId: 15 }, - { userId: 42, taskId: 16 } - ]); - - 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; - }); - }); - - describe('mixin', () => { - const User = current.define('User'), - Task = current.define('Task'); - - it('should mixin association methods', () => { - const as = Math.random().toString(), - association = new HasMany(User, Task, { as }), - obj = {}; - - association.mixin(obj); - - expect(obj[association.accessors.get]).to.be.an('function'); - expect(obj[association.accessors.set]).to.be.an('function'); - expect(obj[association.accessors.addMultiple]).to.be.an('function'); - expect(obj[association.accessors.add]).to.be.an('function'); - expect(obj[association.accessors.remove]).to.be.an('function'); - expect(obj[association.accessors.removeMultiple]).to.be.an('function'); - expect(obj[association.accessors.hasSingle]).to.be.an('function'); - expect(obj[association.accessors.hasAll]).to.be.an('function'); - expect(obj[association.accessors.count]).to.be.an('function'); - }); - - it('should not override custom methods', () => { - const methods = { - getTasks: 'get', - countTasks: 'count', - hasTask: 'has', - hasTasks: 'has', - setTasks: 'set', - addTask: 'add', - addTasks: 'add', - removeTask: 'remove', - removeTasks: 'remove', - createTask: 'create' - }; - - _.each(methods, (alias, method) => { - User.prototype[method] = function() { - const realMethod = this.constructor.associations.task[alias]; - expect(realMethod).to.be.a('function'); - return realMethod; - }; - }); - - User.hasMany(Task, { as: 'task' }); - - const user = User.build(); - - _.each(methods, (alias, method) => { - expect(user[method]()).to.be.a('function'); - }); - }); - - it('should not override attributes', () => { - const Project = current.define('Project', { hasTasks: DataTypes.BOOLEAN }); - - Project.hasMany(Task); - - const company = Project.build(); - - expect(company.hasTasks).not.to.be.a('function'); - }); - }); - - describe('get', () => { - const User = current.define('User', {}), - Task = current.define('Task', {}), - idA = Math.random().toString(), - idB = Math.random().toString(), - idC = Math.random().toString(), - foreignKey = 'user_id'; - - it('should fetch associations for a single instance', async () => { - const findAll = stub(Task, 'findAll').resolves([ - Task.build({}), - Task.build({}) - ]); - - User.Tasks = User.hasMany(Task, { foreignKey }); - const actual = User.Tasks.get(User.build({ id: idA })); - - const where = { - [foreignKey]: idA - }; - - expect(findAll).to.have.been.calledOnce; - expect(findAll.firstCall.args[0].where).to.deep.equal(where); - - try { - const results = await actual; - expect(results).to.be.an('array'); - expect(results.length).to.equal(2); - } finally { - findAll.restore(); - } - }); - - it('should fetch associations for multiple source instances', async () => { - const findAll = stub(Task, 'findAll').returns( - Promise.resolve([ - Task.build({ - 'user_id': idA - }), - Task.build({ - 'user_id': idA - }), - Task.build({ - 'user_id': idA - }), - Task.build({ - 'user_id': idB - }) - ])); - - User.Tasks = User.hasMany(Task, { foreignKey }); - const actual = User.Tasks.get([ - User.build({ id: idA }), - User.build({ id: idB }), - User.build({ id: idC }) - ]); - - expect(findAll).to.have.been.calledOnce; - expect(findAll.firstCall.args[0].where).to.have.property(foreignKey); - 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]); - - 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 { - findAll.restore(); - } - }); - }); - describe('association hooks', () => { - beforeEach(function() { - this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); - this.Tasks = this.sequelize.define('Task', { title: DataTypes.STRING }); - }); - describe('beforeHasManyAssociate', () => { - it('should trigger', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.hasMany(this.Tasks, { hooks: true }); - - const beforeAssociateArgs = beforeAssociate.getCall(0).args; - - expect(beforeAssociate).to.have.been.called; - expect(beforeAssociateArgs.length).to.equal(2); - - const firstArg = beforeAssociateArgs[0]; - expect(Object.keys(firstArg).join()).to.equal('source,target,type'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('HasMany'); - expect(beforeAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.hasMany(this.Tasks, { hooks: false }); - expect(beforeAssociate).to.not.have.been.called; - }); - }); - describe('afterHasManyAssociate', () => { - it('should trigger', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.hasMany(this.Tasks, { hooks: true }); - - const afterAssociateArgs = afterAssociate.getCall(0).args; - - expect(afterAssociate).to.have.been.called; - - const firstArg = afterAssociateArgs[0]; - - expect(Object.keys(firstArg).join()).to.equal('source,target,type,association'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('HasMany'); - expect(firstArg.association.constructor.name).to.equal('HasMany'); - - expect(afterAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.hasMany(this.Tasks, { hooks: false }); - expect(afterAssociate).to.not.have.been.called; - }); - }); - }); -}); diff --git a/test/unit/associations/has-one.test.js b/test/unit/associations/has-one.test.js deleted file mode 100644 index de3f11c5ed62..000000000000 --- a/test/unit/associations/has-one.test.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - _ = require('lodash'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('hasOne'), () => { - it('throws when invalid model is passed', () => { - const User = current.define('User'); - - expect(() => { - User.hasOne(); - }).to.throw('User.hasOne called with something that\'s not a subclass of Sequelize.Model'); - }); - - it('warn on invalid options', () => { - const User = current.define('User', {}); - const Task = current.define('Task', {}); - - expect(() => { - User.hasOne(Task, { sourceKey: 'wowow' }); - }).to.throw('Unknown attribute "wowow" passed as sourceKey, define this attribute on model "User" first'); - }); - - it('properly use the `as` key to generate foreign key name', () => { - const User = current.define('User', { username: DataTypes.STRING }), - Task = current.define('Task', { title: DataTypes.STRING }); - - User.hasOne(Task); - expect(Task.rawAttributes.UserId).not.to.be.empty; - - User.hasOne(Task, { as: 'Shabda' }); - expect(Task.rawAttributes.ShabdaId).not.to.be.empty; - }); - - it('should not override custom methods with association mixin', () => { - const methods = { - getTask: 'get', - setTask: 'set', - createTask: 'create' - }; - const User = current.define('User'); - const Task = current.define('Task'); - - _.each(methods, (alias, method) => { - User.prototype[method] = function() { - const realMethod = this.constructor.associations.task[alias]; - expect(realMethod).to.be.a('function'); - return realMethod; - }; - }); - - User.hasOne(Task, { as: 'task' }); - - const user = User.build(); - - _.each(methods, (alias, method) => { - expect(user[method]()).to.be.a('function'); - }); - }); - describe('association hooks', () => { - beforeEach(function() { - this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); - this.Tasks = this.sequelize.define('Task', { title: DataTypes.STRING }); - }); - describe('beforeHasOneAssociate', () => { - it('should trigger', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.hasOne(this.Tasks, { hooks: true }); - - const beforeAssociateArgs = beforeAssociate.getCall(0).args; - - expect(beforeAssociate).to.have.been.called; - expect(beforeAssociateArgs.length).to.equal(2); - - const firstArg = beforeAssociateArgs[0]; - expect(Object.keys(firstArg).join()).to.equal('source,target,type'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('HasOne'); - - expect(beforeAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); - this.Projects.hasOne(this.Tasks, { hooks: false }); - expect(beforeAssociate).to.not.have.been.called; - }); - }); - describe('afterHasOneAssociate', () => { - it('should trigger', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.hasOne(this.Tasks, { hooks: true }); - - const afterAssociateArgs = afterAssociate.getCall(0).args; - - expect(afterAssociate).to.have.been.called; - expect(afterAssociateArgs.length).to.equal(2); - - const firstArg = afterAssociateArgs[0]; - - expect(Object.keys(firstArg).join()).to.equal('source,target,type,association'); - expect(firstArg.source).to.equal(this.Projects); - expect(firstArg.target).to.equal(this.Tasks); - expect(firstArg.type.name).to.equal('HasOne'); - expect(firstArg.association.constructor.name).to.equal('HasOne'); - - expect(afterAssociateArgs[1].sequelize.constructor.name).to.equal('Sequelize'); - }); - it('should not trigger association hooks', function() { - const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); - this.Projects.hasOne(this.Tasks, { hooks: false }); - expect(afterAssociate).to.not.have.been.called; - }); - }); - }); -}); diff --git a/test/unit/configuration.test.js b/test/unit/configuration.test.js deleted file mode 100644 index 0cade77cc358..000000000000 --- a/test/unit/configuration.test.js +++ /dev/null @@ -1,196 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize, - dialect = Support.getTestDialect(), - path = require('path'); - -describe('Sequelize', () => { - describe('dialect is required', () => { - it('throw error when no dialect is supplied', () => { - expect(() => { - new Sequelize('localhost', 'test', 'test'); - }).to.throw(Error); - }); - - it('works when dialect explicitly supplied', () => { - expect(() => { - new Sequelize('localhost', 'test', 'test', { - dialect: 'mysql' - }); - }).not.to.throw(Error); - }); - }); - - it('should throw error if pool:false', () => { - expect(() => { - new Sequelize('localhost', 'test', 'test', { - dialect: 'mysql', - pool: false - }); - }).to.throw('Support for pool:false was removed in v4.0'); - }); - - describe('Instantiation with arguments', () => { - it('should accept four parameters (database, username, password, options)', () => { - const sequelize = new Sequelize('dbname', 'root', 'pass', { - port: 999, - dialect, - dialectOptions: { - supportBigNumbers: true, - bigNumberStrings: true - } - }); - const config = sequelize.config; - - expect(config.database).to.equal('dbname'); - expect(config.username).to.equal('root'); - expect(config.password).to.equal('pass'); - expect(config.port).to.equal(999); - expect(sequelize.options.dialect).to.equal(dialect); - expect(config.dialectOptions.supportBigNumbers).to.be.true; - expect(config.dialectOptions.bigNumberStrings).to.be.true; - }); - }); - - describe('Instantiation with a URL string', () => { - it('should accept username, password, host, port, and database', () => { - const sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname'); - const config = sequelize.config; - const options = sequelize.options; - - expect(options.dialect).to.equal('mysql'); - - expect(config.database).to.equal('dbname'); - expect(config.host).to.equal('example.com'); - expect(config.username).to.equal('user'); - expect(config.password).to.equal('pass'); - expect(config.port).to.equal('9821'); - }); - - describe('sqllite path inititalization', () =>{ - const current = Support.sequelize; - if (current.dialect.name === 'sqlite') { - it('should accept relative paths for sqlite', () => { - const sequelize = new Sequelize('sqlite:subfolder/dbname.db'); - const options = sequelize.options; - expect(options.dialect).to.equal('sqlite'); - expect(options.storage).to.equal(path.resolve('subfolder', 'dbname.db')); - }); - - it('should accept absolute paths for sqlite', () => { - const sequelize = new Sequelize('sqlite:/home/abs/dbname.db'); - const options = sequelize.options; - expect(options.dialect).to.equal('sqlite'); - expect(options.storage).to.equal(path.resolve('/home/abs/dbname.db')); - }); - - it('should prefer storage in options object', () => { - const sequelize = new Sequelize('sqlite:/home/abs/dbname.db', { storage: '/completely/different/path.db' }); - const options = sequelize.options; - expect(options.dialect).to.equal('sqlite'); - expect(options.storage).to.equal(path.resolve('/completely/different/path.db')); - }); - - it('should be able to use :memory:', () => { - const sequelize = new Sequelize('sqlite://:memory:'); - const options = sequelize.options; - expect(options.dialect).to.equal('sqlite'); - - // empty host is treated as :memory: - expect(options.host).to.equal(''); - expect(options.storage).to.equal(undefined); - }); - } - }); - - it('should work with no authentication options', () => { - const sequelize = new Sequelize('mysql://example.com:9821/dbname'); - const config = sequelize.config; - - expect(config.username).to.not.be.ok; - expect(config.password).to.be.null; - }); - - it('should work with no authentication options and passing additional options', () => { - const sequelize = new Sequelize('mysql://example.com:9821/dbname', {}); - const config = sequelize.config; - - expect(config.username).to.not.be.ok; - expect(config.password).to.be.null; - }); - - it('should correctly set the username, the password and the database through options', () => { - const options = { - username: 'root', - password: 'pass', - database: 'dbname' - }; - const sequelize = new Sequelize('mysql://example.com:9821', options); - const config = sequelize.config; - - expect(config.username).to.equal(options.username); - expect(config.password).to.equal(options.password); - expect(config.database).to.equal(options.database); - }); - - it('should use the default port when no other is specified', () => { - const sequelize = new Sequelize('dbname', 'root', 'pass', { - dialect - }), - config = sequelize.config; - let port; - - if (dialect === 'mysql') { - port = 3306; - } else if (dialect === 'postgres' || dialect === 'postgres-native') { - port = 5432; - } else { - // sqlite has no concept of ports when connecting - return; - } - - expect(config.port).to.equal(port); - }); - - it('should pass query string parameters to dialectOptions', () => { - const sequelize = new Sequelize('mysql://example.com:9821/dbname?ssl=true'); - const dialectOptions = sequelize.config.dialectOptions; - - expect(dialectOptions.ssl).to.equal('true'); - }); - - it('should merge query string parameters to options', () => { - const sequelize = new Sequelize('mysql://example.com:9821/dbname?ssl=true&application_name=client', { - storage: '/completely/different/path.db', - dialectOptions: { - supportBigNumbers: true, - application_name: 'server' // eslint-disable-line - } - }); - - const options = sequelize.options; - const dialectOptions = sequelize.config.dialectOptions; - - expect(options.storage).to.equal('/completely/different/path.db'); - expect(dialectOptions.supportBigNumbers).to.be.true; - 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 deleted file mode 100644 index 0b55ae53a6e4..000000000000 --- a/test/unit/connection-manager.test.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('./support'), - ConnectionManager = require('../../lib/dialects/abstract/connection-manager'); - -describe('connection manager', () => { - describe('_connect', () => { - beforeEach(function() { - this.connection = {}; - - this.dialect = { - connectionManager: { - connect: sinon.stub().resolves(this.connection) - } - }; - - this.sequelize = Support.createSequelizeInstance(); - }); - - it('should resolve connection on dialect connection manager', async function() { - const connection = {}; - this.dialect.connectionManager.connect.resolves(connection); - - const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - - const 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', async function() { - const username = Math.random().toString(), - password = Math.random().toString(); - - this.sequelize.beforeConnect(config => { - config.username = username; - config.password = password; - return config; - }); - - const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - - await connectionManager._connect({}); - expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ - username, - password - }); - }); - - it('should call afterConnect', async function() { - const spy = sinon.spy(); - this.sequelize.afterConnect(spy); - - const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - - 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({}); - }); - }); - - describe('_disconnect', () => { - beforeEach(function() { - this.connection = {}; - - this.dialect = { - connectionManager: { - disconnect: sinon.stub().resolves(this.connection) - } - }; - - this.sequelize = Support.createSequelizeInstance(); - }); - - it('should call beforeDisconnect', async function() { - const spy = sinon.spy(); - this.sequelize.beforeDisconnect(spy); - - const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - - await connectionManager._disconnect(this.connection); - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - }); - - it('should call afterDisconnect', async function() { - const spy = sinon.spy(); - this.sequelize.afterDisconnect(spy); - - const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - - 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/dialect-module-configuration.test.js b/test/unit/dialect-module-configuration.test.js deleted file mode 100644 index e6d05d6f21c6..000000000000 --- a/test/unit/dialect-module-configuration.test.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - path = require('path'), - Support = require(`${__dirname}/support`), - Sequelize = Support.Sequelize, - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Sequelize'), () => { - describe('dialectModule options', () => { - it('options.dialectModule', () => { - const dialectModule = { - verbose: () => { return dialectModule; } - }; - - const sequelize = new Sequelize('dbname', 'root', 'pass', { - port: 999, - dialect, - dialectModule - }); - expect(sequelize.connectionManager.lib).to.equal(dialectModule); - }); - - it('options.dialectModulePath', () => { - let dialectPath = path.join(process.cwd(), 'node_modules'); - - switch (dialect) { - case 'postgres': dialectPath = path.join(dialectPath, 'pg'); break; - case 'mysql': dialectPath = path.join(dialectPath, 'mysql2'); break; - case 'mariadb': dialectPath = path.join(dialectPath, 'mariadb'); break; - case 'mssql': dialectPath = path.join(dialectPath, 'tedious'); break; - case 'sqlite': dialectPath = path.join(dialectPath, 'sqlite3'); break; - default: throw Error('Unsupported dialect'); - } - - // this will throw if invalid path is passed - new Sequelize('dbname', 'root', 'pass', { - port: 999, - dialect, - dialectModulePath: dialectPath - }); - }); - - it('options.dialectModulePath fails for invalid path', () => { - expect(() => { - new Sequelize('dbname', 'root', 'pass', { - port: 999, - dialect, - dialectModulePath: '/foo/bar/baz' - }); - }).to.throw('Unable to find dialect at /foo/bar/baz'); - }); - }); -}); diff --git a/test/unit/dialects/abstract/query-generator.test.js b/test/unit/dialects/abstract/query-generator.test.js deleted file mode 100644 index 25cd79391316..000000000000 --- a/test/unit/dialects/abstract/query-generator.test.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Op = require('../../../../lib/operators'), - getAbstractQueryGenerator = require('../../support').getAbstractQueryGenerator; - -describe('QueryGenerator', () => { - describe('whereItemQuery', () => { - it('should generate correct query for Symbol operators', function() { - const QG = getAbstractQueryGenerator(this.sequelize); - QG.whereItemQuery(Op.or, [{ test: { [Op.gt]: 5 } }, { test: { [Op.lt]: 3 } }, { test: { [Op.in]: [4] } }]) - .should.be.equal('(test > 5 OR test < 3 OR test IN (4))'); - - QG.whereItemQuery(Op.and, [{ test: { [Op.between]: [2, 5] } }, { test: { [Op.ne]: 3 } }, { test: { [Op.not]: 4 } }]) - .should.be.equal('(test BETWEEN 2 AND 5 AND test != 3 AND test != 4)'); - - QG.whereItemQuery(Op.or, [{ test: { [Op.is]: null } }, { testSame: { [Op.eq]: null } }]) - .should.be.equal('(test IS NULL OR testSame IS NULL)'); - }); - - it('should not parse any strings as aliases operators', function() { - const QG = getAbstractQueryGenerator(this.sequelize); - expect(() => QG.whereItemQuery('$or', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('$and', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('test', { $gt: 5 })) - .to.throw('Invalid value { \'$gt\': 5 }'); - - expect(() => QG.whereItemQuery('test', { $between: [2, 5] })) - .to.throw('Invalid value { \'$between\': [ 2, 5 ] }'); - - expect(() => QG.whereItemQuery('test', { $ne: 3 })) - .to.throw('Invalid value { \'$ne\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $not: 3 })) - .to.throw('Invalid value { \'$not\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $in: [4] })) - .to.throw('Invalid value { \'$in\': [ 4 ] }'); - }); - - it('should parse set aliases strings as operators', function() { - const QG = getAbstractQueryGenerator(this.sequelize), - aliases = { - OR: Op.or, - '!': Op.not, - '^^': Op.gt - }; - - QG.setOperatorsAliases(aliases); - - QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }]) - .should.be.equal('(test > 5 OR test != 3 OR test IN (4))'); - - QG.whereItemQuery(Op.and, [{ test: { [Op.between]: [2, 5] } }, { test: { '!': 3 } }, { test: { '^^': 4 } }]) - .should.be.equal('(test BETWEEN 2 AND 5 AND test != 3 AND test > 4)'); - - expect(() => QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { $not: 3 } }, { test: { [Op.in]: [4] } }])) - .to.throw('Invalid value { \'$not\': 3 }'); - - expect(() => QG.whereItemQuery('OR', [{ test: { $gt: 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }])) - .to.throw('Invalid value { \'$gt\': 5 }'); - - expect(() => QG.whereItemQuery('$or', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('$and', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('test', { $gt: 5 })) - .to.throw('Invalid value { \'$gt\': 5 }'); - - expect(() => QG.whereItemQuery('test', { $between: [2, 5] })) - .to.throw('Invalid value { \'$between\': [ 2, 5 ] }'); - - expect(() => QG.whereItemQuery('test', { $ne: 3 })) - .to.throw('Invalid value { \'$ne\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $not: 3 })) - .to.throw('Invalid value { \'$not\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $in: [4] })) - .to.throw('Invalid value { \'$in\': [ 4 ] }'); - }); - - it('should correctly parse sequelize.where with .fn as logic', function() { - const QG = getAbstractQueryGenerator(this.sequelize); - QG.handleSequelizeMethod(this.sequelize.where(this.sequelize.col('foo'), 'LIKE', this.sequelize.col('bar'))) - .should.be.equal('foo LIKE bar'); - - QG.handleSequelizeMethod(this.sequelize.where(this.sequelize.col('foo'), Op.ne, null)) - .should.be.equal('foo IS NOT NULL'); - - QG.handleSequelizeMethod(this.sequelize.where(this.sequelize.col('foo'), Op.not, null)) - .should.be.equal('foo IS NOT NULL'); - }); - - it('should correctly escape $ in sequelize.fn arguments', function() { - const QG = getAbstractQueryGenerator(this.sequelize); - QG.handleSequelizeMethod(this.sequelize.fn('upper', '$user')) - .should.include('$$user'); - }); - }); - - describe('format', () => { - it('should throw an error if passed SequelizeMethod', function() { - const QG = getAbstractQueryGenerator(this.sequelize); - const value = this.sequelize.fn('UPPER', 'test'); - expect(() => QG.format(value)).to.throw(Error); - }); - }); -}); - diff --git a/test/unit/dialects/abstract/query.test.js b/test/unit/dialects/abstract/query.test.js deleted file mode 100644 index 81c71e9e99c0..000000000000 --- a/test/unit/dialects/abstract/query.test.js +++ /dev/null @@ -1,512 +0,0 @@ -'use strict'; - -const path = require('path'); -const Query = require(path.resolve('./lib/dialects/abstract/query.js')); -const Support = require(path.join(__dirname, './../../support')); -const chai = require('chai'); -const { stub, match } = require('sinon'); -const current = Support.sequelize; -const expect = chai.expect; - -describe('[ABSTRACT]', () => { - describe('_groupJoinData', () => { - - it('should hash second nested set correctly, when has multiple primary keys and one is a Buffer', () => { - const Team = current.define('team', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Player = current.define('player', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Agent = current.define('agent', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - }, - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - Team.Player = Team.hasMany(Player, { foreignKey: 'teamId' }); - Team.Agent = Team.hasMany(Agent, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association: Team.Player - }, - 'agents': { - model: Agent, - association: Team.Agent - } - } - }; - - const agentOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const agentTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - id: 'a', - 'players.id': '1-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), - 'agents.uuid': agentOneUuid, - 'agents.id': 'p', - 'agents.name': 'One' - }, - { - id: 'a', - 'players.id': '2-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), - 'agents.uuid': agentTwoUuid, - 'agents.id': 'z', - 'agents.name': 'Two' - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(1); - - expect(result[0]).to.have.property('id').and.be.equal('a'); - expect(result[0].agents).to.be.deep.equal([ - { - id: 'p', - uuid: agentOneUuid, - name: 'One' - }, - { - id: 'z', - uuid: agentTwoUuid, - name: 'Two' - } - ]); - }); - - it('should hash second nested set correctly, when primary is a Buffer', () => { - const Team = current.define('team', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Player = current.define('player', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Agent = current.define('agent', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - } - }); - - Team.Player = Team.hasMany(Player, { foreignKey: 'teamId' }); - Team.Agent = Team.hasMany(Agent, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association: Team.Player - }, - 'agents': { - model: Agent, - association: Team.Agent - } - } - }; - - const agentOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const agentTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - id: 'a', - 'players.id': '1-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z'), - 'agents.uuid': agentOneUuid, - 'agents.name': 'One' - }, - { - id: 'a', - 'players.id': '2-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z'), - 'agents.uuid': agentTwoUuid, - 'agents.name': 'Two' - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(1); - - expect(result[0]).to.have.property('id').and.be.equal('a'); - expect(result[0].agents).to.be.deep.equal([ - { - uuid: agentOneUuid, - name: 'One' - }, - { - uuid: agentTwoUuid, - name: 'Two' - } - ]); - }); - - it('should hash parents correctly, when has multiple primary keys and one is a Buffer', () => { - const Team = current.define('team', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - }, - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Player = current.define('player', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const association = Team.hasMany(Player, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association - } - } - }; - - const teamOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const teamTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - uuid: teamOneUuid, - id: 'x', - 'players.id': '1-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - uuid: teamTwoUuid, - id: 'y', - 'players.id': '2-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z') - }, - { - uuid: teamOneUuid, - id: 'x', - 'players.id': '1-2', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-24T11:16:44.000Z') - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(2); - - expect(result[0]).to.have.property('uuid').and.be.equal(teamOneUuid); - expect(result[0].players).to.be.deep.equal([ - { - 'id': '1-1', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - 'id': '1-2', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-24T11:16:44.000Z') - } - ]); - - expect(result[1]).to.have.property('uuid').and.be.equal(teamTwoUuid); - expect(result[1].players).to.be.deep.equal([{ - 'id': '2-1', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-22T11:16:44.000Z') - }]); - }); - - it('should hash parents correctly, when primary key is a Buffer', () => { - const Team = current.define('team', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - } - }); - - const Player = current.define('player', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const association = Team.hasMany(Player, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association - } - } - }; - - const teamOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const teamTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - uuid: teamOneUuid, - 'players.id': '1-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - uuid: teamTwoUuid, - 'players.id': '2-1', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z') - }, - { - uuid: teamOneUuid, - 'players.id': '1-2', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-24T11:16:44.000Z') - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(2); - - expect(result[0]).to.have.property('uuid').and.be.equal(teamOneUuid); - expect(result[0].players).to.be.deep.equal([ - { - 'id': '1-1', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - 'id': '1-2', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-24T11:16:44.000Z') - } - ]); - - expect(result[1]).to.have.property('uuid').and.be.equal(teamTwoUuid); - expect(result[1].players).to.be.deep.equal([{ - 'id': '2-1', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-22T11:16:44.000Z') - }]); - }); - - it('should hash nested correctly, when primary key is a Buffer', () => { - const Team = current.define('team', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Player = current.define('player', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - } - }); - - const association = Team.hasMany(Player, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association - } - } - }; - - const playerOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const playerTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - id: '1', - 'players.uuid': playerOneUuid, - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - id: '1', - 'players.uuid': playerTwoUuid, - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z') - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(1); - - expect(result[0]).to.have.property('id').and.be.equal('1'); - expect(result[0].players).to.be.deep.equal([ - { - 'uuid': playerOneUuid, - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - 'uuid': playerTwoUuid, - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-22T11:16:44.000Z') - } - ]); - }); - - it('should hash nested correctly, when has multiple primary keys and one is a Buffer', () => { - const Team = current.define('team', { - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const Player = current.define('player', { - uuid: { - primaryKey: true, - type: 'BINARY(16)' - }, - id: { - primaryKey: true, - type: current.Sequelize.STRING(1) - } - }); - - const association = Team.hasMany(Player, { foreignKey: 'teamId' }); - - const includeOptions = { - model: Team, - includeMap: { - 'players': { - model: Player, - association - } - } - }; - - const playerOneUuid = Buffer.from('966ea4c3028c11e7bc99a99d4c0d78cf', 'hex'); - const playerTwoUuid = Buffer.from('966ecbd0028c11e7bc99a99d4c0d78cf', 'hex'); - - const data = [ - { - id: '1', - 'players.uuid': playerOneUuid, - 'players.id': 'x', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - id: '1', - 'players.uuid': playerTwoUuid, - 'players.id': 'y', - 'players.created': new Date('2017-03-06T15:47:30.000Z'), - 'players.lastModified': new Date('2017-08-22T11:16:44.000Z') - } - ]; - - const result = Query._groupJoinData(data, includeOptions, { checkExisting: true }); - - expect(result.length).to.be.equal(1); - - expect(result[0]).to.have.property('id').and.be.equal('1'); - expect(result[0].players).to.be.deep.equal([ - { - 'uuid': playerOneUuid, - 'id': 'x', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-03-06T15:47:30.000Z') - }, - { - 'uuid': playerTwoUuid, - 'id': 'y', - 'created': new Date('2017-03-06T15:47:30.000Z'), - 'lastModified': new Date('2017-08-22T11:16:44.000Z') - } - ]); - }); - }); - - describe('_logQuery', () => { - beforeEach(function() { - this.cls = class MyQuery extends Query { }; - this.sequelizeStub = { - log: stub(), - options: {} - }; - this.connectionStub = { - uuid: 'test' - }; - }); - - it('logs before and after', function() { - const debugStub = stub(); - const qry = new this.cls(this.connectionStub, this.sequelizeStub, {}); - const complete = qry._logQuery('SELECT 1', debugStub); - complete(); - expect(this.sequelizeStub.log).to.have.been.calledOnce; - expect(this.sequelizeStub.log).to.have.been.calledWithMatch('Executing (test): SELECT 1'); - - expect(debugStub).to.have.been.calledWith('Executing (test): SELECT 1'); - expect(debugStub).to.have.been.calledWith('Executed (test): SELECT 1'); - }); - - it('logs before and after with benchmark', function() { - const debugStub = stub(); - const qry = new this.cls(this.connectionStub, this.sequelizeStub, { benchmark: true }); - const complete = qry._logQuery('SELECT 1', debugStub); - complete(); - expect(this.sequelizeStub.log).to.have.been.calledOnce; - expect(this.sequelizeStub.log).to.have.been.calledWithMatch('Executed (test): SELECT 1', match.number, { benchmark: true }); - - expect(debugStub).to.have.been.calledWith('Executing (test): SELECT 1'); - expect(debugStub).to.have.been.calledWith('Executed (test): SELECT 1'); - }); - }); -}); diff --git a/test/unit/dialects/abstract/quote-identifier.test.js b/test/unit/dialects/abstract/quote-identifier.test.js deleted file mode 100644 index 974919df6272..000000000000 --- a/test/unit/dialects/abstract/quote-identifier.test.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - QuoteHelper = require('../../../../lib/dialects/abstract/query-generator/helpers/quote'); - -describe('QuoteIdentifier', () => { - it('unknown dialect', () => { - expect( - QuoteHelper.quoteIdentifier.bind(this, 'unknown', 'id', {})).to.throw( - Error); - }); - -}); - diff --git a/test/unit/dialects/mariadb/errors.test.js b/test/unit/dialects/mariadb/errors.test.js deleted file mode 100644 index 77a15379a241..000000000000 --- a/test/unit/dialects/mariadb/errors.test.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const Sequelize = Support.Sequelize; -const dialect = Support.getTestDialect(); -const queryProto = Support.sequelize.dialect.Query.prototype; - -if (dialect === 'mariadb') { - describe('[MARIADB Specific] ForeignKeyConstraintError - error message parsing', () => { - it('FK Errors with ` quotation char are parsed correctly', () => { - const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).'); - - fakeErr.errno = 1451; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.reltype).to.equal('parent'); - expect(parsedErr.table).to.equal('people'); - expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); - expect(parsedErr.value).to.be.undefined; - expect(parsedErr.index).to.equal('brothers_ibfk_1'); - }); - - it('FK Errors with " quotation char are parsed correctly', () => { - const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).'); - - fakeErr.errno = 1451; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.reltype).to.equal('parent'); - expect(parsedErr.table).to.equal('people'); - expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); - expect(parsedErr.value).to.be.undefined; - expect(parsedErr.index).to.equal('brothers_ibfk_1'); - }); - - it('newlines contained in err message are parsed correctly', () => { - const fakeErr = new Error('(conn=43, no: 1062, SQLState: 23000) Duplicate entry \'unique name one\r\' for key \'models_uniqueName2_unique\'\nsql: INSERT INTO `models` (`id`,`uniqueName1`,`uniqueName2`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?); - parameters:[\'this is ok\',\'unique name one\',\'2019-01-18 09:05:28.496\',\'2019-01-18 09:05:28.496\']'); - - fakeErr.errno = 1062; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.fields.models_uniqueName2_unique).to.equal('unique name one\r'); - }); - }); -} diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js deleted file mode 100644 index b320c10d5d38..000000000000 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ /dev/null @@ -1,846 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - _ = require('lodash'), - Op = require('../../../../lib/operators'), - IndexHints = require('../../../../lib/index-hints'), - QueryGenerator = require('../../../../lib/dialects/mariadb/query-generator'); - -if (dialect === 'mariadb') { - describe('[MARIADB Specific] QueryGenerator', () => { - const suites = { - createDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase`;' - }, - { - arguments: ['myDatabase', { charset: 'utf8mb4' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT CHARACTER SET \'utf8mb4\';' - }, - { - arguments: ['myDatabase', { collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - }, - { - arguments: ['myDatabase', { charset: 'utf8mb4', collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT CHARACTER SET \'utf8mb4\' DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - } - ], - dropDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'DROP DATABASE IF EXISTS `myDatabase`;' - } - ], - createSchema: [ - { - arguments: ['mySchema'], - expectation: 'CREATE SCHEMA IF NOT EXISTS `mySchema`;' - }, - { - arguments: ['mySchema', { charset: 'utf8mb4' }], - expectation: 'CREATE SCHEMA IF NOT EXISTS `mySchema` DEFAULT CHARACTER SET \'utf8mb4\';' - }, - { - arguments: ['mySchema', { collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE SCHEMA IF NOT EXISTS `mySchema` DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - }, - { - arguments: ['mySchema', { charset: 'utf8mb4', collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE SCHEMA IF NOT EXISTS `mySchema` DEFAULT CHARACTER SET \'utf8mb4\' DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - } - ], - dropSchema: [ - { - arguments: ['mySchema'], - expectation: 'DROP SCHEMA IF EXISTS `mySchema`;' - } - ], - showSchemasQuery: [ - { - arguments: [{}], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\');' - }, - { - arguments: [{ skip: [] }], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\');' - }, - { - 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\');' - }, - { - 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\');' - } - - ], - arithmeticQuery: [ - { - title: 'Should use the plus operator', - arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' - }, - { - title: 'Should use the plus operator with where clause', - 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' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' - }, - { - title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' - }, - { - title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' - } - ], - attributesToSQL: [ - { - arguments: [{ id: 'INTEGER' }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], - expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' } - }, - { - arguments: [{ id: { type: 'INTEGER' } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false } }], - expectation: { id: 'INTEGER NOT NULL' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: true } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], - expectation: { id: 'INTEGER auto_increment PRIMARY KEY' } - }, - { - arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], - expectation: { id: 'INTEGER DEFAULT 0' } - }, - { - title: 'Add column level comment', - arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], - expectation: { id: 'INTEGER COMMENT \'Test\'' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true } }], - expectation: { id: 'INTEGER UNIQUE' } - }, - { - arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], - expectation: { id: 'INTEGER AFTER `Bar`' } - }, - // No Default Values allowed for certain types - { - title: 'No Default value for MariaDB BLOB allowed', - arguments: [{ id: { type: 'BLOB', defaultValue: [] } }], - expectation: { id: 'BLOB' } - }, - { - title: 'No Default value for MariaDB TEXT allowed', - arguments: [{ id: { type: 'TEXT', defaultValue: [] } }], - expectation: { id: 'TEXT' } - }, - { - title: 'No Default value for MariaDB GEOMETRY allowed', - arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }], - expectation: { id: 'GEOMETRY' } - }, - { - title: 'No Default value for MariaDB JSON allowed', - arguments: [{ id: { type: 'JSON', defaultValue: [] } }], - expectation: { id: 'JSON' } - }, - // New references style - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT' } - } - ], - - createTableQuery: [ - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { data: 'BLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` BLOB) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { data: 'LONGBLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` LONGBLOB) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { engine: 'MyISAM' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'utf8', collate: 'utf8_unicode_ci' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'latin1' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;' - }, - { - arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }, { charset: 'latin1' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM("A", "B", "C"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { rowFormat: 'default' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB ROW_FORMAT=default;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER , PRIMARY KEY (`id`)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER, FOREIGN KEY (`otherId`) REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { uniqueKeys: [{ fields: ['title', 'name'], customIndex: true }] }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), UNIQUE `uniq_myTable_title_name` (`title`, `name`)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { id: 'INTEGER auto_increment PRIMARY KEY' }, { initialAutoIncrement: 1000001 }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER auto_increment , PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1000001;' - } - ], - - dropTableQuery: [ - { - arguments: ['myTable'], - expectation: 'DROP TABLE IF EXISTS `myTable`;' - } - ], - - selectQuery: [ - { - arguments: ['myTable'], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT `id`, `name` FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: "foo';DROP TABLE myTable;" } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo\\';DROP TABLE myTable;';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], - expectation: 'SELECT count(*) AS `count` FROM `foo`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id', 'DESC'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['myTable.id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['id', 'DESC']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take functions as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']] - }; - }], - expectation: 'SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take all types as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [ - [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], - [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'] - ] - }; - }], - expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55.000') ASC;", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and default comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = 'jan' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and LIKE comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE '%t%' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'single string argument should be quoted', - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - title: 'functions work for group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt'))] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'It is possible to mix sequelize.fn and string arguments to group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', - context: QueryGenerator - }, { - title: 'HAVING clause works with where-like hash', - arguments: ['myTable', function(sequelize) { - return { - attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], - group: ['creationYear', 'title'], - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - { archived: null }, - sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }) - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (`myTable`.`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN ('Lost', 'Found'));", - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM `myTable` LIMIT 10;', - context: QueryGenerator - }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10;', - context: QueryGenerator - }, { - title: 'uses default limit if only offset is specified', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10000000000000;', - context: QueryGenerator - }, { - title: 'uses limit 0', - arguments: ['myTable', { limit: 0 }], - expectation: 'SELECT * FROM `myTable` LIMIT 0;', - context: QueryGenerator - }, { - title: 'uses offset 0', - arguments: ['myTable', { offset: 0 }], - expectation: 'SELECT * FROM `myTable` LIMIT 0, 10000000000000;', - context: QueryGenerator - }, { - title: 'multiple where arguments', - arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat` = 'canoe' AND `myTable`.`weather` = 'cold';", - context: QueryGenerator - }, { - title: 'no where arguments (object)', - arguments: ['myTable', { where: {} }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'no where arguments (string)', - arguments: ['myTable', { where: [''] }], - expectation: 'SELECT * FROM `myTable` WHERE 1=1;', - context: QueryGenerator - }, { - title: 'no where arguments (null)', - arguments: ['myTable', { where: null }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'buffer as where argument', - arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` = X'53657175656c697a65';", - context: QueryGenerator - }, { - title: 'use != if ne !== null', - arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 0;', - context: QueryGenerator - }, { - title: 'use IS NOT if ne === null', - arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT NULL;', - context: QueryGenerator - }, { - title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: true } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT true;', - context: QueryGenerator - }, { - title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 3;', - context: QueryGenerator - }, { - title: 'Regular Expression in where clause', - arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` REGEXP '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Regular Expression negation in where clause', - arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` NOT REGEXP '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Empty having', - arguments: ['myTable', function() { - return { - having: {} - }; - }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Having in subquery', - arguments: ['myTable', function() { - return { - subQuery: true, - tableAs: 'test', - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `creationYear` > 2002) AS `test`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Contains fields with "." characters.', - arguments: ['myTable', { - attributes: ['foo.bar.baz'], - model: { - rawAttributes: { - 'foo.bar.baz': {} - } - } - }], - expectation: 'SELECT `foo.bar.baz` FROM `myTable`;', - context: QueryGenerator - } - ], - - insertQuery: [ - { - arguments: ['myTable', { name: 'foo' }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ["foo';DROP TABLE myTable;"] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`birthday`) VALUES ($1,$2);', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1 }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - } - }, { - arguments: ['myTable', { data: Buffer.from('Sequelize') }], - expectation: { - query: 'INSERT INTO `myTable` (`data`) VALUES ($1);', - bind: [Buffer.from('Sequelize')] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `mySchema`.`myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { foo: false }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);', - bind: [false] - } - }, { - arguments: ['myTable', { foo: true }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);', - bind: [true] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - foo: sequelize.fn('NOW') - }; - }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', - bind: [] - }, - needsSequelize: true - } - ], - - bulkInsertQuery: [ - { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }]], - expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL,NULL);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: true } } // Note: We don't honour this because it makes little sense when some rows may have nulls and others not - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: undefined }, { name: 'bar', foo: 2, undefinedValue: undefined }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`,`undefinedValue`) VALUES ('foo',1,NULL,NULL),('bar',2,NULL,NULL);", - context: { options: { omitNull: true } } // Note: As above - }, { - arguments: ['myTable', [{ name: 'foo', value: true }, { name: 'bar', value: false }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], - expectation: "INSERT IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { updateOnDuplicate: ['name'] }], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON DUPLICATE KEY UPDATE `name`=VALUES(`name`);" - } - ], - - updateQuery: [ - { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), 2] - } - - }, { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), 2] - } - }, { - arguments: ['myTable', { bar: 2 }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [2, 'foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1 WHERE `name` = $2', - bind: ["foo';DROP TABLE myTable;", 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { bar: false }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [false, 'foo'] - } - }, { - arguments: ['myTable', { bar: true }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [true, 'foo'] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.fn('NOW') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.col('foo') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - } - ], - - showIndexesQuery: [ - { - arguments: ['User'], - expectation: 'SHOW INDEX FROM `User`' - }, { - arguments: ['User', { database: 'sequelize' }], - expectation: 'SHOW INDEX FROM `User` FROM `sequelize`' - } - ], - - removeIndexQuery: [ - { - arguments: ['User', 'user_foo_bar'], - expectation: 'DROP INDEX `user_foo_bar` ON `User`' - }, { - arguments: ['User', ['foo', 'bar']], - expectation: 'DROP INDEX `user_foo_bar` ON `User`' - } - ], - getForeignKeyQuery: [ - { - arguments: ['User', 'email'], - expectation: "SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (REFERENCED_TABLE_NAME = 'User' AND REFERENCED_COLUMN_NAME = 'email') OR (TABLE_NAME = 'User' AND COLUMN_NAME = 'email' AND REFERENCED_TABLE_NAME IS NOT NULL)" - } - ], - - selectFromTableFragment: [ - { - arguments: [{}, null, ['*'], '`Project`'], - expectation: 'SELECT * FROM `Project`' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` FORCE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.IGNORE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` IGNORE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name', 'index_project_on_name_and_foo'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`,`index_project_on_name_and_foo`)' - }, { - arguments: [ - { indexHints: [{ type: 'FOO', values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project`' - } - ] - }; - - _.each(suites, (tests, suiteTitle) => { - describe(suiteTitle, () => { - beforeEach(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - tests.forEach(test => { - const query = test.expectation.query || test.expectation; - const title = test.title || `MariaDB correctly returns ${query} for ${JSON.stringify(test.arguments)}`; - it(title, function() { - if (test.needsSequelize) { - if (typeof test.arguments[1] === 'function') test.arguments[1] = test.arguments[1](this.sequelize); - if (typeof test.arguments[2] === 'function') test.arguments[2] = test.arguments[2](this.sequelize); - } - - // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - 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 deleted file mode 100644 index f9ec050586a9..000000000000 --- a/test/unit/dialects/mssql/connection-manager.test.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../../../index'), - Support = require('../../support'), - dialect = Support.getTestDialect(), - sinon = require('sinon'); - -if (dialect === 'mssql') { - describe('[MSSQL Specific] Connection Manager', () => { - beforeEach(function() { - this.config = { - dialect: 'mssql', - database: 'none', - username: 'none', - password: 'none', - host: 'localhost', - port: 2433, - pool: {}, - dialectOptions: { - domain: 'TEST.COM' - } - }; - this.instance = new Sequelize( - this.config.database, - this.config.username, - this.config.password, - this.config - ); - 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', async function() { - this.Connection = { - STATE: {}, - state: '', - once(event, cb) { - if (event === 'connect') { - setTimeout(() => { - cb(); - }, 500); - } - }, - removeListener: () => {}, - on: () => {} - }; - - 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', async function() { - this.Connection = { - STATE: {}, - state: '', - once(event, cb) { - if (event === 'end') { - setTimeout(() => { - cb(); - }, 500); - } - }, - 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: () => {} - }; - - 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 deleted file mode 100644 index 525b7ff99da4..000000000000 --- a/test/unit/dialects/mssql/query-generator.test.js +++ /dev/null @@ -1,357 +0,0 @@ -'use strict'; - -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'); - -if (current.dialect.name === 'mssql') { - describe('[MSSQL Specific] QueryGenerator', () => { - before(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - it('createDatabaseQuery', function() { - expectsql(this.queryGenerator.createDatabaseQuery('myDatabase'), { - mssql: "IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'myDatabase' ) BEGIN CREATE DATABASE [myDatabase] ; END;" - }); - }); - - 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;" - }); - }); - - it('dropDatabaseQuery', function() { - expectsql(this.queryGenerator.dropDatabaseQuery('myDatabase'), { - mssql: "IF EXISTS (SELECT * FROM sys.databases WHERE name = 'myDatabase' ) BEGIN DROP DATABASE [myDatabase] ; END;" - }); - }); - - it('createTableQuery', function() { - expectsql(this.queryGenerator.createTableQuery('myTable', { int: 'INTEGER' }, {}), { - mssql: "IF OBJECT_ID('[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([int] INTEGER);" - }); - }); - - it('createTableQuery with comments', function() { - expectsql(this.queryGenerator.createTableQuery('myTable', { int: 'INTEGER COMMENT Foo Bar', varchar: 'VARCHAR(50) UNIQUE COMMENT Bar Foo' }, {}), { - mssql: "IF OBJECT_ID('[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([int] INTEGER, [varchar] VARCHAR(50) UNIQUE); EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo Bar', @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [int]; EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Bar Foo', @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [varchar];" }); - }); - - it('getDefaultConstraintQuery', function() { - expectsql(this.queryGenerator.getDefaultConstraintQuery({ tableName: 'myTable', schema: 'mySchema' }, 'myColumn'), { - mssql: "SELECT name FROM sys.default_constraints WHERE PARENT_OBJECT_ID = OBJECT_ID('[mySchema].[myTable]', 'U') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('myColumn') AND object_id = OBJECT_ID('[mySchema].[myTable]', 'U'));" - }); - }); - - it('dropConstraintQuery', function() { - expectsql(this.queryGenerator.dropConstraintQuery({ tableName: 'myTable', schema: 'mySchema' }, 'myConstraint'), { - mssql: 'ALTER TABLE [mySchema].[myTable] DROP CONSTRAINT [myConstraint];' - }); - }); - - it('bulkInsertQuery', function() { - //normal cases - expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ name: 'foo' }, { name: 'bar' }]), { - mssql: "INSERT INTO [myTable] ([name]) VALUES (N'foo'),(N'bar');" - }); - - expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ username: 'username', firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName' }]), { - mssql: "INSERT INTO [myTable] ([username],[firstName],[lastName]) VALUES (N'username',N'firstName',N'lastName'),(NULL,N'user1FirstName',N'user1LastName');" - }); - - expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName' }]), { - mssql: "INSERT INTO [myTable] ([firstName],[lastName]) VALUES (N'firstName',N'lastName'),(N'user1FirstName',N'user1LastName');" - }); - - //Bulk Insert With autogenerated primary key - const attributes = { id: { autoIncrement: true } }; - expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ id: null }], {}, attributes), { - mssql: 'INSERT INTO [myTable] DEFAULT VALUES' - }); - }); - - it('selectFromTableFragment', function() { - const modifiedGen = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - // Test newer versions first - // Should be all the same since handling is done in addLimitAndOffset - // for SQL Server 2012 and higher (>= v11.0.0) - modifiedGen.sequelize = { - options: { - databaseVersion: '11.0.0' - } - }; - - // Base case - expectsql(modifiedGen.selectFromTableFragment({}, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName', 'WHERE id=1'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName' - }); - - // With tableHint - nolock - expectsql(modifiedGen.selectFromTableFragment({ tableHint: TableHints.NOLOCK }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName WITH (NOLOCK)' - }); - - // With tableHint - NOWAIT - expectsql(modifiedGen.selectFromTableFragment({ tableHint: TableHints.NOWAIT }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName WITH (NOWAIT)' - }); - - // With limit - expectsql(modifiedGen.selectFromTableFragment({ limit: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName' - }); - - // With offset - expectsql(modifiedGen.selectFromTableFragment({ offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName' - }); - - // With both limit and offset - expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName' - }); - - // Test older version (< v11.0.0) - modifiedGen.sequelize.options.databaseVersion = '10.0.0'; - - // Base case - expectsql(modifiedGen.selectFromTableFragment({}, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName', 'WHERE id=1'), { - mssql: 'SELECT id, name FROM myTable AS myOtherName' - }); - - // With limit - expectsql(modifiedGen.selectFromTableFragment({ limit: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT TOP 10 id, name FROM myTable AS myOtherName' - }); - - // 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' - }); - - // 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' - }); - }); - - it('getPrimaryKeyConstraintQuery', function() { - expectsql(this.queryGenerator.getPrimaryKeyConstraintQuery('myTable', 'myColumnName'), { - mssql: 'SELECT K.TABLE_NAME AS tableName, K.COLUMN_NAME AS columnName, K.CONSTRAINT_NAME AS constraintName FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K ON C.TABLE_NAME = K.TABLE_NAME AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\' AND K.COLUMN_NAME = \'myColumnName\' AND K.TABLE_NAME = \'myTable\';' - }); - }); - - it('createSchema', function() { - expectsql(this.queryGenerator.createSchema('mySchema'), { - mssql: 'IF NOT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'mySchema\' ) BEGIN EXEC sp_executesql N\'CREATE SCHEMA [mySchema] ;\' END;' - }); - }); - - it('dropSchema', function() { - expectsql(this.queryGenerator.dropSchema('mySchema'), { - mssql: 'IF EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'mySchema\' ) BEGIN DECLARE @id INT, @ms_sql NVARCHAR(2000); DECLARE @cascade TABLE ( id INT NOT NULL IDENTITY PRIMARY KEY, ms_sql NVARCHAR(2000) NOT NULL ); INSERT INTO @cascade ( ms_sql ) SELECT CASE WHEN o.type IN (\'F\',\'PK\') THEN N\'ALTER TABLE [\'+ s.name + N\'].[\' + p.name + N\'] DROP CONSTRAINT [\' + o.name + N\']\' ELSE N\'DROP TABLE [\'+ s.name + N\'].[\' + o.name + N\']\' END FROM sys.objects o JOIN sys.schemas s on o.schema_id = s.schema_id LEFT OUTER JOIN sys.objects p on o.parent_object_id = p.object_id WHERE o.type IN (\'F\', \'PK\', \'U\') AND s.name = \'mySchema\' ORDER BY o.type ASC; SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id; WHILE @id IS NOT NULL BEGIN BEGIN TRY EXEC sp_executesql @ms_sql; END TRY BEGIN CATCH BREAK; THROW; END CATCH; DELETE FROM @cascade WHERE id = @id; SELECT @id = NULL, @ms_sql = NULL; SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id; END EXEC sp_executesql N\'DROP SCHEMA [mySchema] ;\' END;' - }); - }); - - it('showSchemasQuery', function() { - expectsql(this.queryGenerator.showSchemasQuery(), { - mssql: 'SELECT "name" as "schema_name" FROM sys.schemas as s WHERE "s"."name" NOT IN ( \'INFORMATION_SCHEMA\', \'dbo\', \'guest\', \'sys\', \'archive\' ) AND "s"."name" NOT LIKE \'db_%\'' - }); - }); - - it('versionQuery', function() { - expectsql(this.queryGenerator.versionQuery(), { - mssql: "DECLARE @ms_ver NVARCHAR(20); SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion'))); SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'" - }); - }); - - it('renameTableQuery', function() { - expectsql(this.queryGenerator.renameTableQuery('oldTableName', 'newTableName'), { - mssql: 'EXEC sp_rename [oldTableName], [newTableName];' - }); - }); - - it('showTablesQuery', function() { - expectsql(this.queryGenerator.showTablesQuery(), { - mssql: "SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';" - }); - }); - - it('dropTableQuery', function() { - expectsql(this.queryGenerator.dropTableQuery('dirtyTable'), { - mssql: "IF OBJECT_ID('[dirtyTable]', 'U') IS NOT NULL DROP TABLE [dirtyTable];" - }); - }); - - it('addColumnQuery', function() { - expectsql(this.queryGenerator.addColumnQuery('myTable', 'myColumn', { type: 'VARCHAR(255)' }), { - mssql: 'ALTER TABLE [myTable] ADD [myColumn] VARCHAR(255) NULL;' - }); - }); - - it('addColumnQuery with comment', function() { - expectsql(this.queryGenerator.addColumnQuery('myTable', 'myColumn', { type: 'VARCHAR(255)', comment: 'This is a comment' }), { - mssql: 'ALTER TABLE [myTable] ADD [myColumn] VARCHAR(255) NULL; EXEC sp_addextendedproperty ' + - '@name = N\'MS_Description\', @value = N\'This is a comment\', ' + - '@level0type = N\'Schema\', @level0name = \'dbo\', ' + - '@level1type = N\'Table\', @level1name = [myTable], ' + - '@level2type = N\'Column\', @level2name = [myColumn];' - }); - }); - - it('removeColumnQuery', function() { - expectsql(this.queryGenerator.removeColumnQuery('myTable', 'myColumn'), { - mssql: 'ALTER TABLE [myTable] DROP COLUMN [myColumn];' - }); - }); - - it('quoteIdentifier', function() { - expectsql(this.queryGenerator.quoteIdentifier("'myTable'.'Test'"), { - mssql: '[myTable.Test]' - }); - }); - - it('getForeignKeysQuery', function() { - expectsql(this.queryGenerator.getForeignKeysQuery('myTable'), { - mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable'" - }); - - expectsql(this.queryGenerator.getForeignKeysQuery('myTable', 'myDatabase'), { - mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintCatalog = 'myDatabase', constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), tableCatalog = 'myDatabase', columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedCatalog = 'myDatabase', referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable'" - }); - - expectsql(this.queryGenerator.getForeignKeysQuery({ - tableName: 'myTable', - schema: 'mySchema' - }, 'myDatabase'), { - mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintCatalog = 'myDatabase', constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), tableCatalog = 'myDatabase', columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedCatalog = 'myDatabase', referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable' AND SCHEMA_NAME(TB.SCHEMA_ID) ='mySchema'" - }); - }); - - it('getForeignKeyQuery', function() { - expectsql(this.queryGenerator.getForeignKeyQuery('myTable', 'myColumn'), { - mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable' AND COL.NAME ='myColumn'" - }); - expectsql(this.queryGenerator.getForeignKeyQuery({ - tableName: 'myTable', - schema: 'mySchema' - }, 'myColumn'), { - mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable' AND COL.NAME ='myColumn' AND SCHEMA_NAME(TB.SCHEMA_ID) ='mySchema'" - }); - }); - - it('dropForeignKeyQuery', function() { - expectsql(this.queryGenerator.dropForeignKeyQuery('myTable', 'myColumnKey'), { - mssql: 'ALTER TABLE [myTable] DROP [myColumnKey]' - }); - }); - - describe('arithmeticQuery', () => { - [ - { - title: 'Should use the plus operator', - 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', { 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 }], - expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\'' - }, - { - title: 'Should use the minus operator', - 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 }, {}, {}], - expectation: 'UPDATE [myTable] SET [foo]=[foo]- -1 OUTPUT INSERTED.*' - }, - { - title: 'Should use the minus operator with where clause', - 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 }], - expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\'' - } - ].forEach(test => { - it(test.title, function() { - expectsql(this.queryGenerator.arithmeticQuery(...test.arguments), { - mssql: test.expectation - }); - }); - }); - }); - }); -} diff --git a/test/unit/dialects/mssql/query.test.js b/test/unit/dialects/mssql/query.test.js deleted file mode 100644 index 5487f5057458..000000000000 --- a/test/unit/dialects/mssql/query.test.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -const path = require('path'); -const Query = require(path.resolve('./lib/dialects/mssql/query.js')); -const Support = require('../../support'); -const dialect = Support.getTestDialect(); -const sequelize = Support.sequelize; -const sinon = require('sinon'); -const expect = require('chai').expect; -const tedious = require('tedious'); -const tediousIsolationLevel = tedious.ISOLATION_LEVEL; -const connectionStub = { beginTransaction: () => {}, lib: tedious }; - -let sandbox, query; - -if (dialect === 'mssql') { - describe('[MSSQL Specific] Query', () => { - beforeEach(() => { - sandbox = sinon.createSandbox(); - const options = { - transaction: { name: 'transactionName' }, - isolationLevel: 'REPEATABLE_READ', - logging: false - }; - sandbox.stub(connectionStub, 'beginTransaction').callsArg(0); - query = new Query(connectionStub, sequelize, options); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('beginTransaction', () => { - it('should call beginTransaction with correct arguments', 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); - }); - }); - - describe('formatBindParameters', () => { - it('should convert Sequelize named binding format to MSSQL format', () => { - const sql = 'select $one as a, $two as b, $one as c, $three as d, $one as e'; - const values = { one: 1, two: 2, three: 3 }; - - const expected = 'select @one as a, @two as b, @one as c, @three as d, @one as e'; - - const result = Query.formatBindParameters(sql, values, dialect); - expect(result[0]).to.be.a('string'); - expect(result[0]).to.equal(expected); - }); - - it('should convert Sequelize numbered binding format to MSSQL format', () => { - const sql = 'select $1 as a, $2 as b, $1 as c, $3 as d, $1 as e'; - const values = [1, 2, 3]; - - const expected = 'select @0 as a, @1 as b, @0 as c, @2 as d, @0 as e'; - - const result = Query.formatBindParameters(sql, values, dialect); - expect(result[0]).to.be.a('string'); - 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/mysql/errors.test.js b/test/unit/dialects/mysql/errors.test.js deleted file mode 100644 index 06f917ccb94a..000000000000 --- a/test/unit/dialects/mysql/errors.test.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const Sequelize = Support.Sequelize; -const dialect = Support.getTestDialect(); -const queryProto = Support.sequelize.dialect.Query.prototype; - -if (dialect === 'mysql') { - describe('[MYSQL Specific] ForeignKeyConstraintError - error message parsing', () => { - it('FK Errors with ` quotation char are parsed correctly', () => { - const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).'); - - fakeErr.code = 1451; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.reltype).to.equal('parent'); - expect(parsedErr.table).to.equal('people'); - expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); - expect(parsedErr.value).to.be.undefined; - expect(parsedErr.index).to.equal('brothers_ibfk_1'); - }); - - it('FK Errors with " quotation char are parsed correctly', () => { - const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).'); - - fakeErr.code = 1451; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.reltype).to.equal('parent'); - expect(parsedErr.table).to.equal('people'); - expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); - expect(parsedErr.value).to.be.undefined; - expect(parsedErr.index).to.equal('brothers_ibfk_1'); - }); - - it('newlines contained in err message are parsed correctly', () => { - const fakeErr = new Error("Duplicate entry '13888888888\r' for key 'num'"); - - fakeErr.code = 1062; - - const parsedErr = queryProto.formatError(fakeErr); - - expect(parsedErr).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(parsedErr.parent).to.equal(fakeErr); - expect(parsedErr.fields.num).to.equal('13888888888\r'); - }); - - }); -} diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js deleted file mode 100644 index fb57dd7e895b..000000000000 --- a/test/unit/dialects/mysql/query-generator.test.js +++ /dev/null @@ -1,797 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - _ = require('lodash'), - Op = require('../../../../lib/operators'), - IndexHints = require('../../../../lib/index-hints'), - QueryGenerator = require('../../../../lib/dialects/mysql/query-generator'); - -if (dialect === 'mysql') { - describe('[MYSQL Specific] QueryGenerator', () => { - const suites = { - createDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase`;' - }, - { - arguments: ['myDatabase', { charset: 'utf8mb4' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT CHARACTER SET \'utf8mb4\';' - }, - { - arguments: ['myDatabase', { collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - }, - { - arguments: ['myDatabase', { charset: 'utf8mb4', collate: 'utf8mb4_unicode_ci' }], - expectation: 'CREATE DATABASE IF NOT EXISTS `myDatabase` DEFAULT CHARACTER SET \'utf8mb4\' DEFAULT COLLATE \'utf8mb4_unicode_ci\';' - } - ], - dropDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'DROP DATABASE IF EXISTS `myDatabase`;' - } - ], - arithmeticQuery: [ - { - title: 'Should use the plus operator', - arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' - }, - { - title: 'Should use the plus operator with where clause', - 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' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' - }, - { - title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' - }, - { - title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' - } - ], - attributesToSQL: [ - { - arguments: [{ id: 'INTEGER' }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], - expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' } - }, - { - arguments: [{ id: { type: 'INTEGER' } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false } }], - expectation: { id: 'INTEGER NOT NULL' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: true } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], - expectation: { id: 'INTEGER auto_increment PRIMARY KEY' } - }, - { - arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], - expectation: { id: 'INTEGER DEFAULT 0' } - }, - { - title: 'Add column level comment', - arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }], - expectation: { id: 'INTEGER COMMENT \'Test\'' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true } }], - expectation: { id: 'INTEGER UNIQUE' } - }, - { - arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }], - expectation: { id: 'INTEGER AFTER `Bar`' } - }, - // No Default Values allowed for certain types - { - title: 'No Default value for MySQL BLOB allowed', - arguments: [{ id: { type: 'BLOB', defaultValue: [] } }], - expectation: { id: 'BLOB' } - }, - { - title: 'No Default value for MySQL TEXT allowed', - arguments: [{ id: { type: 'TEXT', defaultValue: [] } }], - expectation: { id: 'TEXT' } - }, - { - title: 'No Default value for MySQL GEOMETRY allowed', - arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }], - expectation: { id: 'GEOMETRY' } - }, - { - title: 'No Default value for MySQL JSON allowed', - arguments: [{ id: { type: 'JSON', defaultValue: [] } }], - expectation: { id: 'JSON' } - }, - // New references style - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT' } - } - ], - - createTableQuery: [ - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { data: 'BLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` BLOB) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { data: 'LONGBLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` LONGBLOB) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { engine: 'MyISAM' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'utf8', collate: 'utf8_unicode_ci' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'latin1' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;' - }, - { - arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }, { charset: 'latin1' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM("A", "B", "C"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { rowFormat: 'default' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB ROW_FORMAT=default;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER , PRIMARY KEY (`id`)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER, FOREIGN KEY (`otherId`) REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { uniqueKeys: [{ fields: ['title', 'name'], customIndex: true }] }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), UNIQUE `uniq_myTable_title_name` (`title`, `name`)) ENGINE=InnoDB;' - }, - { - arguments: ['myTable', { id: 'INTEGER auto_increment PRIMARY KEY' }, { initialAutoIncrement: 1000001 }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER auto_increment , PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1000001;' - } - ], - - dropTableQuery: [ - { - arguments: ['myTable'], - expectation: 'DROP TABLE IF EXISTS `myTable`;' - } - ], - - selectQuery: [ - { - arguments: ['myTable'], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT `id`, `name` FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: "foo';DROP TABLE myTable;" } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo\\';DROP TABLE myTable;';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], - expectation: 'SELECT count(*) AS `count` FROM `foo`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id', 'DESC'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['myTable.id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['id', 'DESC']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take functions as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']] - }; - }], - expectation: 'SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take all types as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [ - [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], - [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'] - ] - }; - }], - expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55') ASC;", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and default comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = 'jan' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and LIKE comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE '%t%' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'single string argument should be quoted', - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - title: 'functions work for group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt'))] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'It is possible to mix sequelize.fn and string arguments to group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', - context: QueryGenerator - }, { - title: 'HAVING clause works with where-like hash', - arguments: ['myTable', function(sequelize) { - return { - attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], - group: ['creationYear', 'title'], - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Combination of sequelize.fn, sequelize.col and { in: ... }', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - { archived: null }, - sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }) - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (`myTable`.`archived` IS NULL AND COALESCE(`place_type_codename`, `announcement_type_codename`) IN ('Lost', 'Found'));", - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM `myTable` LIMIT 10;', - context: QueryGenerator - }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10;', - context: QueryGenerator - }, { - title: 'uses default limit if only offset is specified', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10000000000000;', - context: QueryGenerator - }, { - title: 'uses limit 0', - arguments: ['myTable', { limit: 0 }], - expectation: 'SELECT * FROM `myTable` LIMIT 0;', - context: QueryGenerator - }, { - title: 'uses offset 0', - arguments: ['myTable', { offset: 0 }], - expectation: 'SELECT * FROM `myTable` LIMIT 0, 10000000000000;', - context: QueryGenerator - }, { - title: 'multiple where arguments', - arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat` = 'canoe' AND `myTable`.`weather` = 'cold';", - context: QueryGenerator - }, { - title: 'no where arguments (object)', - arguments: ['myTable', { where: {} }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'no where arguments (string)', - arguments: ['myTable', { where: [''] }], - expectation: 'SELECT * FROM `myTable` WHERE 1=1;', - context: QueryGenerator - }, { - title: 'no where arguments (null)', - arguments: ['myTable', { where: null }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'buffer as where argument', - arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` = X'53657175656c697a65';", - context: QueryGenerator - }, { - title: 'use != if ne !== null', - arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 0;', - context: QueryGenerator - }, { - title: 'use IS NOT if ne === null', - arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT NULL;', - context: QueryGenerator - }, { - title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: true } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT true;', - context: QueryGenerator - }, { - title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 3;', - context: QueryGenerator - }, { - title: 'Regular Expression in where clause', - arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` REGEXP '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Regular Expression negation in where clause', - arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` NOT REGEXP '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Empty having', - arguments: ['myTable', function() { - return { - having: {} - }; - }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Having in subquery', - arguments: ['myTable', function() { - return { - subQuery: true, - tableAs: 'test', - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `creationYear` > 2002) AS `test`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Contains fields with "." characters.', - arguments: ['myTable', { - attributes: ['foo.bar.baz'], - model: { - rawAttributes: { - 'foo.bar.baz': {} - } - } - }], - expectation: 'SELECT `foo.bar.baz` FROM `myTable`;', - context: QueryGenerator - } - ], - - insertQuery: [ - { - arguments: ['myTable', { name: 'foo' }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ["foo';DROP TABLE myTable;"] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`birthday`) VALUES ($1,$2);', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1 }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - } - }, { - arguments: ['myTable', { data: Buffer.from('Sequelize') }], - expectation: { - query: 'INSERT INTO `myTable` (`data`) VALUES ($1);', - bind: [Buffer.from('Sequelize')] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { foo: false }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);', - bind: [false] - } - }, { - arguments: ['myTable', { foo: true }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);', - bind: [true] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - foo: sequelize.fn('NOW') - }; - }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', - bind: [] - }, - needsSequelize: true - } - ], - - bulkInsertQuery: [ - { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }]], - expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55'),('bar','2012-03-27 10:01:55');" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL,NULL);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: true } } // Note: We don't honour this because it makes little sense when some rows may have nulls and others not - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: undefined }, { name: 'bar', foo: 2, undefinedValue: undefined }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`,`undefinedValue`) VALUES ('foo',1,NULL,NULL),('bar',2,NULL,NULL);", - context: { options: { omitNull: true } } // Note: As above - }, { - arguments: ['myTable', [{ name: 'foo', value: true }, { name: 'bar', value: false }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], - expectation: "INSERT IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { updateOnDuplicate: ['name'] }], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON DUPLICATE KEY UPDATE `name`=VALUES(`name`);" - } - ], - - updateQuery: [ - { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), 2] - } - - }, { - arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), 2] - } - }, { - arguments: ['myTable', { bar: 2 }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [2, 'foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1 WHERE `name` = $2', - bind: ["foo';DROP TABLE myTable;", 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { bar: false }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [false, 'foo'] - } - }, { - arguments: ['myTable', { bar: true }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [true, 'foo'] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.fn('NOW') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.col('foo') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - } - ], - - showIndexesQuery: [ - { - arguments: ['User'], - expectation: 'SHOW INDEX FROM `User`' - }, { - arguments: ['User', { database: 'sequelize' }], - expectation: 'SHOW INDEX FROM `User` FROM `sequelize`' - } - ], - - removeIndexQuery: [ - { - arguments: ['User', 'user_foo_bar'], - expectation: 'DROP INDEX `user_foo_bar` ON `User`' - }, { - arguments: ['User', ['foo', 'bar']], - expectation: 'DROP INDEX `user_foo_bar` ON `User`' - } - ], - - getForeignKeyQuery: [ - { - arguments: ['User', 'email'], - expectation: "SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (REFERENCED_TABLE_NAME = 'User' AND REFERENCED_COLUMN_NAME = 'email') OR (TABLE_NAME = 'User' AND COLUMN_NAME = 'email' AND REFERENCED_TABLE_NAME IS NOT NULL)" - } - ], - - selectFromTableFragment: [ - { - arguments: [{}, null, ['*'], '`Project`'], - expectation: 'SELECT * FROM `Project`' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` FORCE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.IGNORE, values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` IGNORE INDEX (`index_project_on_name`)' - }, { - arguments: [ - { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name', 'index_project_on_name_and_foo'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`,`index_project_on_name_and_foo`)' - }, { - arguments: [ - { indexHints: [{ type: 'FOO', values: ['index_project_on_name'] }] }, - null, - ['*'], - '`Project`' - ], - expectation: 'SELECT * FROM `Project`' - } - ] - }; - - _.each(suites, (tests, suiteTitle) => { - describe(suiteTitle, () => { - beforeEach(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - tests.forEach(test => { - const query = test.expectation.query || test.expectation; - const title = test.title || `MySQL correctly returns ${query} for ${JSON.stringify(test.arguments)}`; - it(title, function() { - if (test.needsSequelize) { - if (typeof test.arguments[1] === 'function') test.arguments[1] = test.arguments[1](this.sequelize); - if (typeof test.arguments[2] === 'function') test.arguments[2] = test.arguments[2](this.sequelize); - } - - // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - 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 deleted file mode 100644 index 2f0465c55541..000000000000 --- a/test/unit/dialects/mysql/query.test.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -const path = require('path'); -const Query = require(path.resolve('./lib/dialects/mysql/query.js')); -const Support = require(path.join(__dirname, './../../support')); -const chai = require('chai'); -const sinon = require('sinon'); - -const current = Support.sequelize; -const expect = chai.expect; - -describe('[MYSQL/MARIADB Specific] Query', () => { - describe('logWarnings', () => { - beforeEach(() => { - sinon.spy(console, 'log'); - }); - - afterEach(() => { - console.log.restore(); - }); - - it('check iterable', async () => { - const validWarning = []; - const invalidWarning = {}; - const warnings = [validWarning, undefined, invalidWarning]; - - const query = new Query({}, current, {}); - const stub = sinon.stub(query, 'run'); - stub.onFirstCall().resolves(warnings); - - 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/data-types.test.js b/test/unit/dialects/postgres/data-types.test.js deleted file mode 100644 index 64660ec5d8c4..000000000000 --- a/test/unit/dialects/postgres/data-types.test.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - dialect = Support.getTestDialect(), - BaseTypes = require('../../../../lib/data-types'), - DataTypes = require('../../../../lib/dialects/postgres/data-types')(BaseTypes), - QueryGenerator = require('../../../../lib/dialects/postgres/query-generator'); - -if (dialect.match(/^postgres/)) { - describe('[POSTGRES Specific] DataTypes', () => { - beforeEach(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - describe('GEOMETRY', () => { - it('should use bindParam fn', function() { - const value = { type: 'Point' }; - const bind = []; - const bindParam = this.queryGenerator.bindParam(bind); - const result = DataTypes.GEOMETRY.prototype.bindParam(value, { bindParam }); - expect(result).to.equal('ST_GeomFromGeoJSON($1)'); - expect(bind).to.eql([value]); - }); - }); - - describe('GEOGRAPHY', () => { - it('should use bindParam fn', function() { - const value = { type: 'Point' }; - const bind = []; - const bindParam = this.queryGenerator.bindParam(bind); - const result = DataTypes.GEOGRAPHY.prototype.bindParam(value, { bindParam }); - expect(result).to.equal('ST_GeomFromGeoJSON($1)'); - expect(bind).to.eql([value]); - }); - }); - }); -} diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js deleted file mode 100644 index e7b085d6db16..000000000000 --- a/test/unit/dialects/postgres/query-generator.test.js +++ /dev/null @@ -1,1309 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Op = require('../../../../lib/operators'), - QueryGenerator = require('../../../../lib/dialects/postgres/query-generator'), - Support = require('../../support'), - dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'), - moment = require('moment'), - current = Support.sequelize, - _ = require('lodash'); - -if (dialect.startsWith('postgres')) { - describe('[POSTGRES Specific] QueryGenerator', () => { - const suites = { - createDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'CREATE DATABASE "myDatabase";' - }, - { - arguments: ['myDatabase', { encoding: 'UTF8' }], - expectation: 'CREATE DATABASE "myDatabase" ENCODING = \'UTF8\';' - }, - { - arguments: ['myDatabase', { collate: 'en_US.UTF-8' }], - expectation: 'CREATE DATABASE "myDatabase" LC_COLLATE = \'en_US.UTF-8\';' - }, - { - arguments: ['myDatabase', { encoding: 'UTF8' }], - expectation: 'CREATE DATABASE "myDatabase" ENCODING = \'UTF8\';' - }, - { - arguments: ['myDatabase', { ctype: 'zh_TW.UTF-8' }], - expectation: 'CREATE DATABASE "myDatabase" LC_CTYPE = \'zh_TW.UTF-8\';' - }, - { - arguments: ['myDatabase', { template: 'template0' }], - expectation: 'CREATE DATABASE "myDatabase" TEMPLATE = \'template0\';' - }, - { - arguments: ['myDatabase', { encoding: 'UTF8', collate: 'en_US.UTF-8', ctype: 'zh_TW.UTF-8', template: 'template0' }], - expectation: 'CREATE DATABASE "myDatabase" ENCODING = \'UTF8\' LC_COLLATE = \'en_US.UTF-8\' LC_CTYPE = \'zh_TW.UTF-8\' TEMPLATE = \'template0\';' - } - ], - - dropDatabaseQuery: [ - { - arguments: ['myDatabase'], - expectation: 'DROP DATABASE IF EXISTS "myDatabase";' - } - ], - - arithmeticQuery: [ - { - title: 'Should use the plus operator', - arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' - }, - { - title: 'Should use the plus operator with where clause', - 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 }], - 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 *' - }, - { - title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' - }, - { - title: 'Should use the minus operator with where clause', - 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 }], - expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\'' - } - ], - - attributesToSQL: [ - { - arguments: [{ id: 'INTEGER' }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], - expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' } - }, - { - arguments: [{ id: { type: 'INTEGER' } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false } }], - expectation: { id: 'INTEGER NOT NULL' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: true } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], - expectation: { id: 'INTEGER SERIAL PRIMARY KEY' } - }, - { - arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true, autoIncrementIdentity: true } }], - expectation: { id: 'INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY' } - }, - { - arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], - expectation: { id: 'INTEGER DEFAULT 0' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true } }], - expectation: { id: 'INTEGER UNIQUE' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true, comment: 'This is my comment' } }], - expectation: { id: 'INTEGER UNIQUE COMMENT This is my comment' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true, comment: 'This is my comment' } }, { context: 'addColumn', key: 'column', table: { schema: 'foo', tableName: 'bar' } }], - expectation: { id: 'INTEGER UNIQUE; COMMENT ON COLUMN "foo"."bar"."column" IS \'This is my comment\'' } - }, - // New references style - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }], - expectation: { id: 'INTEGER REFERENCES "Bar" ("id")' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }], - expectation: { id: 'INTEGER REFERENCES "Bar" ("pk")' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }], - expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT' } - }, - - // Variants when quoteIdentifiers is false - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }], - expectation: { id: 'INTEGER REFERENCES Bar (id)' }, - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }], - expectation: { id: 'INTEGER REFERENCES Bar (pk)' }, - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }], - expectation: { id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE' }, - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT' }, - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT' }, - context: { options: { quoteIdentifiers: false } } - } - ], - - createTableQuery: [ - { - arguments: ['myTable', { int: 'INTEGER', bigint: 'BIGINT', smallint: 'SMALLINT' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("int" INTEGER, "bigint" BIGINT, "smallint" SMALLINT);' - }, - { - arguments: ['myTable', { serial: 'INTEGER SERIAL', bigserial: 'BIGINT SERIAL', smallserial: 'SMALLINT SERIAL' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("serial" SERIAL, "bigserial" BIGSERIAL, "smallserial" SMALLSERIAL);' - }, - { - arguments: ['myTable', { int: 'INTEGER COMMENT Test', foo: 'INTEGER COMMENT Foo Comment' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("int" INTEGER , "foo" INTEGER ); COMMENT ON COLUMN "myTable"."int" IS \'Test\'; COMMENT ON COLUMN "myTable"."foo" IS \'Foo Comment\';' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255));' - }, - { - arguments: ['myTable', { data: current.normalizeDataType(DataTypes.BLOB).toSql() }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("data" BYTEA);' - }, - { - arguments: ['myTable', { data: current.normalizeDataType(DataTypes.BLOB('long')).toSql() }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("data" BYTEA);' - }, - { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS "mySchema"."myTable" ("title" VARCHAR(255), "name" VARCHAR(255));' - }, - { - arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" "public"."enum_myTable_title", "name" VARCHAR(255));' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255), "id" INTEGER , PRIMARY KEY ("id"));' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION' }], - expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255), "otherId" INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION);' - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS mySchema.myTable (title VARCHAR(255), name VARCHAR(255));', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS myTable (title public."enum_myTable_title", name VARCHAR(255));', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }], - expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), id INTEGER , PRIMARY KEY (id));', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION' }], - expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), otherId INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION);', - context: { options: { quoteIdentifiers: false } } - } - ], - - dropTableQuery: [ - { - arguments: ['myTable'], - expectation: 'DROP TABLE IF EXISTS "myTable";' - }, - { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }], - expectation: 'DROP TABLE IF EXISTS "mySchema"."myTable";' - }, - { - arguments: ['myTable', { cascade: true }], - expectation: 'DROP TABLE IF EXISTS "myTable" CASCADE;' - }, - { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { cascade: true }], - expectation: 'DROP TABLE IF EXISTS "mySchema"."myTable" CASCADE;' - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable'], - expectation: 'DROP TABLE IF EXISTS myTable;', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }], - expectation: 'DROP TABLE IF EXISTS mySchema.myTable;', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: ['myTable', { cascade: true }], - expectation: 'DROP TABLE IF EXISTS myTable CASCADE;', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { cascade: true }], - expectation: 'DROP TABLE IF EXISTS mySchema.myTable CASCADE;', - context: { options: { quoteIdentifiers: false } } - } - ], - - changeColumnQuery: [ - { - arguments: ['myTable', { - col_1: "ENUM('value 1', 'value 2') NOT NULL", - col_2: "ENUM('value 3', 'value 4') NOT NULL" - }], - expectation: 'ALTER TABLE "myTable" ALTER COLUMN "col_1" SET NOT NULL;ALTER TABLE "myTable" ALTER COLUMN "col_1" DROP DEFAULT;CREATE TYPE "public"."enum_myTable_col_1" AS ENUM(\'value 1\', \'value 2\');ALTER TABLE "myTable" ALTER COLUMN "col_1" TYPE "public"."enum_myTable_col_1" USING ("col_1"::"public"."enum_myTable_col_1");ALTER TABLE "myTable" ALTER COLUMN "col_2" SET NOT NULL;ALTER TABLE "myTable" ALTER COLUMN "col_2" DROP DEFAULT;CREATE TYPE "public"."enum_myTable_col_2" AS ENUM(\'value 3\', \'value 4\');ALTER TABLE "myTable" ALTER COLUMN "col_2" TYPE "public"."enum_myTable_col_2" USING ("col_2"::"public"."enum_myTable_col_2");' - } - ], - - selectQuery: [ - { - arguments: ['myTable'], - expectation: 'SELECT * FROM "myTable";' - }, { - arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT "id", "name" FROM "myTable";' - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;' - }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\" = 'foo';" - }, { - arguments: ['myTable', { where: { name: "foo';DROP TABLE myTable;" } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\" = 'foo'';DROP TABLE myTable;';" - }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;' - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], - expectation: 'SELECT count(*) AS "count" FROM "foo";' - }, { - arguments: ['myTable', { order: ['id'] }], - expectation: 'SELECT * FROM "myTable" ORDER BY "id";', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id', 'DESC'] }], - expectation: 'SELECT * FROM "myTable" ORDER BY "id", "DESC";', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['myTable.id'] }], - expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id" DESC;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['id', 'DESC']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC, "myTable"."name";', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'uses limit 0', - arguments: ['myTable', { limit: 0 }], - expectation: 'SELECT * FROM "myTable" LIMIT 0;', - context: QueryGenerator - }, { - title: 'uses offset 0', - arguments: ['myTable', { offset: 0 }], - expectation: 'SELECT * FROM "myTable" OFFSET 0;', - context: QueryGenerator - }, { - title: 'sequelize.where with .fn as attribute and default comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), - { type: 1 } - ) - }; - }], - expectation: 'SELECT * FROM "myTable" WHERE (LOWER("user"."name") = \'jan\' AND "myTable"."type" = 1);', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and LIKE comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), - { type: 1 } - ) - }; - }], - expectation: 'SELECT * FROM "myTable" WHERE (LOWER("user"."name") LIKE \'%t%\' AND "myTable"."type" = 1);', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take functions as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']] - }; - }], - expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2("id")) DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take all types as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [ - [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], - [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'] - ] - }; - }], - expectation: 'SELECT * FROM "myTable" ORDER BY f1("myTable"."id") DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55.000 +00:00\') ASC;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'Combination of sequelize.fn, sequelize.col and { Op.in: ... }', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - { archived: null }, - sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }) - ) - }; - }], - expectation: 'SELECT * FROM "myTable" WHERE ("myTable"."archived" IS NULL AND COALESCE("place_type_codename", "announcement_type_codename") IN (\'Lost\', \'Found\'));', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'single string argument should be quoted', - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM "myTable" GROUP BY "name";' - }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM "myTable" GROUP BY "name";' - }, { - title: 'functions work for group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt'))] - }; - }], - expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt");', - needsSequelize: true - }, { - title: 'It is possible to mix sequelize.fn and string arguments to group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'] - }; - }], - expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt"), "title";', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { group: ['name', 'title'] }], - expectation: 'SELECT * FROM "myTable" GROUP BY "name", "title";' - }, { - title: 'HAVING clause works with where-like hash', - arguments: ['myTable', function(sequelize) { - return { - attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], - group: ['creationYear', 'title'], - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT *, YEAR("createdAt") AS "creationYear" FROM "myTable" GROUP BY "creationYear", "title" HAVING "creationYear" > 2002;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM "myTable" LIMIT 10;' - }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM "myTable" LIMIT 10 OFFSET 2;' - }, { - title: 'uses offset even if no limit was passed', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM "myTable" OFFSET 2;' - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }], - expectation: 'SELECT * FROM "mySchema"."myTable";' - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { where: { name: "foo';DROP TABLE mySchema.myTable;" } }], - expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\" = 'foo'';DROP TABLE mySchema.myTable;';" - }, { - title: 'buffer as where argument', - arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\" = E'\\\\x53657175656c697a65';", - context: QueryGenerator - }, { - title: 'string in array should escape \' as \'\'', - arguments: ['myTable', { where: { aliases: { [Op.contains]: ['Queen\'s'] } } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"aliases\" @> ARRAY['Queen''s'];" - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable'], - expectation: 'SELECT * FROM myTable;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT id, name FROM myTable;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: "SELECT * FROM myTable WHERE myTable.name = 'foo';", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { where: { name: "foo';DROP TABLE myTable;" } }], - expectation: "SELECT * FROM myTable WHERE myTable.name = 'foo'';DROP TABLE myTable;';", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], - expectation: 'SELECT count(*) AS count FROM foo;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { order: ['id DESC'] }], - expectation: 'SELECT * FROM myTable ORDER BY id DESC;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM myTable GROUP BY name;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM myTable GROUP BY name;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { group: ['name', 'title'] }], - expectation: 'SELECT * FROM myTable GROUP BY name, title;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM myTable LIMIT 10;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM myTable LIMIT 10 OFFSET 2;', - context: { options: { quoteIdentifiers: false } } - }, { - title: 'uses offset even if no limit was passed', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM myTable OFFSET 2;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }], - expectation: 'SELECT * FROM mySchema.myTable;', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { where: { name: "foo';DROP TABLE mySchema.myTable;" } }], - expectation: "SELECT * FROM mySchema.myTable WHERE mySchema.myTable.name = 'foo'';DROP TABLE mySchema.myTable;';", - context: { options: { quoteIdentifiers: false } } - }, { - title: 'use != if Op.ne !== null', - arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], - expectation: 'SELECT * FROM myTable WHERE myTable.field != 0;', - context: { options: { quoteIdentifiers: false } } - }, { - title: 'use IS NOT if Op.ne === null', - arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], - expectation: 'SELECT * FROM myTable WHERE myTable.field IS NOT NULL;', - context: { options: { quoteIdentifiers: false } } - }, { - title: 'use IS NOT if Op.not === BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: true } } }], - expectation: 'SELECT * FROM myTable WHERE myTable.field IS NOT true;', - context: { options: { quoteIdentifiers: false } } - }, { - title: 'use != if Op.not !== BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], - expectation: 'SELECT * FROM myTable WHERE myTable.field != 3;', - context: { options: { quoteIdentifiers: false } } - }, { - title: 'Regular Expression in where clause', - arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\" ~ '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Regular Expression negation in where clause', - arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\" !~ '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Case-insensitive Regular Expression in where clause', - arguments: ['myTable', { where: { field: { [Op.iRegexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\" ~* '^[h|a|t]';", - context: QueryGenerator - }, { - title: 'Case-insensitive Regular Expression negation in where clause', - arguments: ['myTable', { where: { field: { [Op.notIRegexp]: '^[h|a|t]' } } }], - expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"field\" !~* '^[h|a|t]';", - context: QueryGenerator - } - ], - - insertQuery: [ - { - arguments: ['myTable', {}], - expectation: { - query: 'INSERT INTO "myTable" DEFAULT VALUES;', - bind: [] - } - }, - { - arguments: ['myTable', { name: 'foo' }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1);', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: 'foo' }, {}, { ignoreDuplicates: true }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1) ON CONFLICT DO NOTHING;', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: 'foo' }, {}, { returning: true }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1) RETURNING *;', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: 'foo' }, {}, { ignoreDuplicates: true, returning: true }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1) ON CONFLICT DO NOTHING RETURNING *;', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1);', - bind: ["foo';DROP TABLE myTable;"] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }], - expectation: { - query: 'INSERT INTO "myTable" ("name","birthday") VALUES ($1,$2);', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate()] - } - }, { - arguments: ['myTable', { data: Buffer.from('Sequelize') }], - expectation: { - query: 'INSERT INTO "myTable" ("data") VALUES ($1);', - bind: [Buffer.from('Sequelize')] - } - }, { - arguments: ['myTable', { name: 'foo', numbers: [1, 2, 3] }], - expectation: { - query: 'INSERT INTO "myTable" ("name","numbers") VALUES ($1,$2);', - bind: ['foo', [1, 2, 3]] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1 }], - expectation: { - query: 'INSERT INTO "myTable" ("name","foo") VALUES ($1,$2);', - bind: ['foo', 1] - } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO "myTable" ("name","nullValue") VALUES ($1,$2);', - bind: ['foo', null] - } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO "myTable" ("name","nullValue") VALUES ($1,$2);', - bind: ['foo', null] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1);', - bind: ['foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: undefined }], - expectation: { - query: 'INSERT INTO "myTable" ("name") VALUES ($1);', - bind: ['foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: 'foo' }], - expectation: { - query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($1);', - bind: ['foo'] - } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: JSON.stringify({ info: 'Look ma a " quote' }) }], - expectation: { - query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($1);', - bind: ['{"info":"Look ma a \\" quote"}'] - } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: "foo';DROP TABLE mySchema.myTable;" }], - expectation: { - query: 'INSERT INTO "mySchema"."myTable" ("name") VALUES ($1);', - bind: ["foo';DROP TABLE mySchema.myTable;"] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - foo: sequelize.fn('NOW') - }; - }], - expectation: { - query: 'INSERT INTO "myTable" ("foo") VALUES (NOW());', - bind: [] - }, - needsSequelize: true - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable', { name: 'foo' }], - expectation: { - query: 'INSERT INTO myTable (name) VALUES ($1);', - bind: ['foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }], - expectation: { - query: 'INSERT INTO myTable (name) VALUES ($1);', - bind: ["foo';DROP TABLE myTable;"] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }], - expectation: { - query: 'INSERT INTO myTable (name,birthday) VALUES ($1,$2);', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate()] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', numbers: [1, 2, 3] }], - expectation: { - query: 'INSERT INTO myTable (name,numbers) VALUES ($1,$2);', - bind: ['foo', [1, 2, 3]] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1 }], - expectation: { - query: 'INSERT INTO myTable (name,foo) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO myTable (name,nullValue) VALUES ($1,$2);', - bind: ['foo', null] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO myTable (name,nullValue) VALUES ($1,$2);', - bind: ['foo', null] - }, - context: { options: { omitNull: false, quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: null }], - expectation: { - query: 'INSERT INTO myTable (name) VALUES ($1);', - bind: ['foo'] - }, - context: { options: { omitNull: true, quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', nullValue: undefined }], - expectation: { - query: 'INSERT INTO myTable (name) VALUES ($1);', - bind: ['foo'] - }, - context: { options: { omitNull: true, quoteIdentifiers: false } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: 'foo' }], - expectation: { - query: 'INSERT INTO mySchema.myTable (name) VALUES ($1);', - bind: ['foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: JSON.stringify({ info: 'Look ma a " quote' }) }], - expectation: { - query: 'INSERT INTO mySchema.myTable (name) VALUES ($1);', - bind: ['{"info":"Look ma a \\" quote"}'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: "foo';DROP TABLE mySchema.myTable;" }], - expectation: { - query: 'INSERT INTO mySchema.myTable (name) VALUES ($1);', - bind: ["foo';DROP TABLE mySchema.myTable;"] - }, - context: { options: { quoteIdentifiers: false } } - } - ], - - bulkInsertQuery: [ - { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') ON CONFLICT DO NOTHING;" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { returning: true }], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { returning: ['id', 'sentToId'] }], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING \"id\",\"sentToId\";" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true, returning: true }], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') ON CONFLICT DO NOTHING RETURNING *;" - }, { - arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { name: 'bar', birthday: moment('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2);" - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);" - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);", - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);", - context: { options: { omitNull: true } } // Note: We don't honour this because it makes little sense when some rows may have nulls and others not - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: undefined }, { name: 'bar', nullValue: undefined }]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);", - context: { options: { omitNull: true } } // Note: As above - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar');" - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: JSON.stringify({ info: 'Look ma a " quote' }) }, { name: JSON.stringify({ info: 'Look ma another " quote' }) }]], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}');" - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar');" - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: 'foo' }, { name: 'bar' }], { updateOnDuplicate: ['name'], upsertKeys: ['name'] }], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar') ON CONFLICT (\"name\") DO UPDATE SET \"name\"=EXCLUDED.\"name\";" - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar');", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', [{ name: "foo';DROP TABLE myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar');", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', [{ name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { name: 'bar', birthday: moment('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }]], - expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]], - expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2);", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", - context: { options: { quoteIdentifiers: false, omitNull: false } } - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: null }, { name: 'bar', nullValue: null }]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", - context: { options: { omitNull: true, quoteIdentifiers: false } } // Note: We don't honour this because it makes little sense when some rows may have nulls and others not - }, { - arguments: ['myTable', [{ name: 'foo', nullValue: undefined }, { name: 'bar', nullValue: undefined }]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", - context: { options: { omitNull: true, quoteIdentifiers: false } } // Note: As above - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar');", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: JSON.stringify({ info: 'Look ma a " quote' }) }, { name: JSON.stringify({ info: 'Look ma another " quote' }) }]], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}');", - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, [{ name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'bar' }]], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar');", - context: { options: { quoteIdentifiers: false } } - } - ], - - updateQuery: [ - { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE "myTable" SET "name"=$1,"birthday"=$2 WHERE "id" = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE "myTable" SET "name"=$1,"birthday"=$2 WHERE "id" = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - } - }, { - arguments: ['myTable', { bar: 2 }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1 WHERE "name" = $2', - bind: [2, 'foo'] - } - }, { - arguments: ['myTable', { bar: 2 }, { name: 'foo' }, { returning: true }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1 WHERE "name" = $2 RETURNING *', - bind: [2, 'foo'] - } - }, { - arguments: ['myTable', { numbers: [1, 2, 3] }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "numbers"=$1 WHERE "name" = $2', - bind: [[1, 2, 3], 'foo'] - } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "name"=$1 WHERE "name" = $2', - bind: ["foo';DROP TABLE myTable;", 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1,"nullValue"=$2 WHERE "name" = $3', - bind: [2, null, 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1,"nullValue"=$2 WHERE "name" = $3', - bind: [2, null, 'foo'] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1 WHERE "name" = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { bar: 2, nullValue: undefined }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=$1 WHERE "name" = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE "mySchema"."myTable" SET "name"=$1,"birthday"=$2 WHERE "id" = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - } - }, { - arguments: [{ tableName: 'myTable', schema: 'mySchema' }, { name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE "mySchema"."myTable" SET "name"=$1 WHERE "name" = $2', - bind: ["foo';DROP TABLE mySchema.myTable;", 'foo'] - } - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.fn('NOW') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"=NOW() WHERE "name" = $1', - bind: ['foo'] - }, - needsSequelize: true - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.col('foo') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE "myTable" SET "bar"="foo" WHERE "name" = $1', - bind: ['foo'] - }, - needsSequelize: true - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE myTable SET name=$1,birthday=$2 WHERE id = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE myTable SET name=$1,birthday=$2 WHERE id = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { bar: 2 }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET bar=$1 WHERE name = $2', - bind: [2, 'foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { numbers: [1, 2, 3] }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET numbers=$1 WHERE name = $2', - bind: [[1, 2, 3], 'foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { name: "foo';DROP TABLE myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET name=$1 WHERE name = $2', - bind: ["foo';DROP TABLE myTable;", 'foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET bar=$1,nullValue=$2 WHERE name = $3', - bind: [2, null, 'foo'] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET bar=$1,nullValue=$2 WHERE name = $3', - bind: [2, null, 'foo'] - }, - context: { options: { omitNull: false, quoteIdentifiers: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET bar=$1 WHERE name = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true, quoteIdentifiers: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: undefined }, { name: 'foo' }], - expectation: { - query: 'UPDATE myTable SET bar=$1 WHERE name = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true, quoteIdentifiers: false } } - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE mySchema.myTable SET name=$1,birthday=$2 WHERE id = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - }, - context: { options: { quoteIdentifiers: false } } - }, { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, { name: "foo';DROP TABLE mySchema.myTable;" }, { name: 'foo' }], - expectation: { - query: 'UPDATE mySchema.myTable SET name=$1 WHERE name = $2', - bind: ["foo';DROP TABLE mySchema.myTable;", 'foo'] - }, - context: { options: { quoteIdentifiers: false } } - } - ], - - removeIndexQuery: [ - { - arguments: ['User', 'user_foo_bar'], - expectation: 'DROP INDEX IF EXISTS "user_foo_bar"' - }, { - arguments: ['User', ['foo', 'bar']], - expectation: 'DROP INDEX IF EXISTS "user_foo_bar"' - }, { - arguments: ['User', 'mySchema.user_foo_bar'], - expectation: 'DROP INDEX IF EXISTS "mySchema"."user_foo_bar"' - }, - - // Variants when quoteIdentifiers is false - { - arguments: ['User', 'user_foo_bar'], - expectation: 'DROP INDEX IF EXISTS user_foo_bar', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['User', ['foo', 'bar']], - expectation: 'DROP INDEX IF EXISTS user_foo_bar', - context: { options: { quoteIdentifiers: false } } - }, { - arguments: ['User', 'mySchema.user_foo_bar'], - expectation: 'DROP INDEX IF EXISTS mySchema.user_foo_bar', - context: { options: { quoteIdentifiers: false } } - } - ], - - startTransactionQuery: [ - { - arguments: [{}], - expectation: 'START TRANSACTION;', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ parent: 'MockTransaction', name: 'transaction-uid' }], - expectation: 'SAVEPOINT "transaction-uid";', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ parent: 'MockTransaction', name: 'transaction-uid' }], - expectation: 'SAVEPOINT "transaction-uid";', - context: { options: { quoteIdentifiers: true } } - } - ], - - rollbackTransactionQuery: [ - { - arguments: [{}], - expectation: 'ROLLBACK;', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ parent: 'MockTransaction', name: 'transaction-uid' }], - expectation: 'ROLLBACK TO SAVEPOINT "transaction-uid";', - context: { options: { quoteIdentifiers: false } } - }, - { - arguments: [{ parent: 'MockTransaction', name: 'transaction-uid' }], - expectation: 'ROLLBACK TO SAVEPOINT "transaction-uid";', - context: { options: { quoteIdentifiers: true } } - } - ], - - createTrigger: [ - { - arguments: ['myTable', 'myTrigger', 'after', ['insert'], 'myFunction', [], []], - expectation: 'CREATE TRIGGER "myTrigger" AFTER INSERT ON "myTable" EXECUTE PROCEDURE myFunction();' - }, - { - arguments: ['myTable', 'myTrigger', 'before', ['insert', 'update'], 'myFunction', [{ name: 'bar', type: 'INTEGER' }], []], - expectation: 'CREATE TRIGGER "myTrigger" BEFORE INSERT OR UPDATE ON "myTable" EXECUTE PROCEDURE myFunction(bar INTEGER);' - }, - { - arguments: ['myTable', 'myTrigger', 'instead_of', ['insert', 'update'], 'myFunction', [], ['FOR EACH ROW']], - expectation: 'CREATE TRIGGER "myTrigger" INSTEAD OF INSERT OR UPDATE ON "myTable" FOR EACH ROW EXECUTE PROCEDURE myFunction();' - }, - { - arguments: ['myTable', 'myTrigger', 'after_constraint', ['insert', 'update'], 'myFunction', [{ name: 'bar', type: 'INTEGER' }], ['FOR EACH ROW']], - expectation: 'CREATE CONSTRAINT TRIGGER "myTrigger" AFTER INSERT OR UPDATE ON "myTable" FOR EACH ROW EXECUTE PROCEDURE myFunction(bar INTEGER);' - } - ], - - dropTrigger: [ - { - arguments: ['myTable', 'myTrigger'], - expectation: 'DROP TRIGGER "myTrigger" ON "myTable" RESTRICT;' - } - ], - - renameTrigger: [ - { - arguments: ['myTable', 'oldTrigger', 'newTrigger'], - expectation: 'ALTER TRIGGER "oldTrigger" ON "myTable" RENAME TO "newTrigger";' - } - ], - - getForeignKeyReferenceQuery: [ - { - arguments: ['myTable', 'myColumn'], - expectation: 'SELECT ' + - 'DISTINCT tc.constraint_name as constraint_name, ' + - 'tc.constraint_schema as constraint_schema, ' + - 'tc.constraint_catalog as constraint_catalog, ' + - 'tc.table_name as table_name,' + - 'tc.table_schema as table_schema,' + - 'tc.table_catalog as table_catalog,' + - 'kcu.column_name as column_name,' + - 'ccu.table_schema AS referenced_table_schema,' + - 'ccu.table_catalog AS referenced_table_catalog,' + - 'ccu.table_name AS referenced_table_name,' + - 'ccu.column_name AS referenced_column_name ' + - 'FROM information_schema.table_constraints AS tc ' + - 'JOIN information_schema.key_column_usage AS kcu ' + - 'ON tc.constraint_name = kcu.constraint_name ' + - 'JOIN information_schema.constraint_column_usage AS ccu ' + - 'ON ccu.constraint_name = tc.constraint_name ' + - 'WHERE constraint_type = \'FOREIGN KEY\' AND tc.table_name=\'myTable\' AND kcu.column_name = \'myColumn\'' - }, - { - arguments: [{ schema: 'mySchema', tableName: 'myTable' }, 'myColumn'], - expectation: 'SELECT ' + - 'DISTINCT tc.constraint_name as constraint_name, ' + - 'tc.constraint_schema as constraint_schema, ' + - 'tc.constraint_catalog as constraint_catalog, ' + - 'tc.table_name as table_name,' + - 'tc.table_schema as table_schema,' + - 'tc.table_catalog as table_catalog,' + - 'kcu.column_name as column_name,' + - 'ccu.table_schema AS referenced_table_schema,' + - 'ccu.table_catalog AS referenced_table_catalog,' + - 'ccu.table_name AS referenced_table_name,' + - 'ccu.column_name AS referenced_column_name ' + - 'FROM information_schema.table_constraints AS tc ' + - 'JOIN information_schema.key_column_usage AS kcu ' + - 'ON tc.constraint_name = kcu.constraint_name ' + - 'JOIN information_schema.constraint_column_usage AS ccu ' + - 'ON ccu.constraint_name = tc.constraint_name ' + - 'WHERE constraint_type = \'FOREIGN KEY\' AND tc.table_name=\'myTable\' AND kcu.column_name = \'myColumn\'' + - ' AND tc.table_schema = \'mySchema\'' - } - ] - }; - - _.each(suites, (tests, suiteTitle) => { - describe(suiteTitle, () => { - beforeEach(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - tests.forEach(test => { - const query = test.expectation.query || test.expectation; - const title = test.title || `Postgres correctly returns ${query} for ${JSON.stringify(test.arguments)}`; - it(title, function() { - if (test.needsSequelize) { - if (typeof test.arguments[1] === 'function') test.arguments[1] = test.arguments[1](this.sequelize); - if (typeof test.arguments[2] === 'function') test.arguments[2] = test.arguments[2](this.sequelize); - } - - // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - 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); - }); - }); - }); - }); - - 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/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js deleted file mode 100644 index a3dd0dd51440..000000000000 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ /dev/null @@ -1,661 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../../lib/data-types'), - dialect = Support.getTestDialect(), - _ = require('lodash'), - moment = require('moment'), - Op = require('../../../../lib/operators'), - QueryGenerator = require('../../../../lib/dialects/sqlite/query-generator'); - -if (dialect === 'sqlite') { - describe('[SQLITE Specific] QueryGenerator', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', { - username: DataTypes.STRING - }); - return this.User.sync({ force: true }); - }); - - const suites = { - arithmeticQuery: [ - { - title: 'Should use the plus operator', - arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' - }, - { - title: 'Should use the plus operator with where clause', - 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' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' - }, - { - title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' - }, - { - title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' - } - ], - attributesToSQL: [ - { - arguments: [{ id: 'INTEGER' }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }], - expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' } - }, - { - arguments: [{ id: { type: 'INTEGER' } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false } }], - expectation: { id: 'INTEGER NOT NULL' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: true } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }], - expectation: { id: 'INTEGER PRIMARY KEY AUTOINCREMENT' } - }, - { - arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }], - expectation: { id: 'INTEGER DEFAULT 0' } - }, - { - arguments: [{ id: { type: 'INTEGER', defaultValue: undefined } }], - expectation: { id: 'INTEGER' } - }, - { - arguments: [{ id: { type: 'INTEGER', unique: true } }], - expectation: { id: 'INTEGER UNIQUE' } - }, - - // New references style - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`pk`)' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE' } - }, - { - arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT' } - }, - { - arguments: [{ id: { type: 'INTEGER', allowNull: false, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }], - expectation: { id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT' } - } - ], - - createTableQuery: [ - { - arguments: ['myTable', { data: 'BLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` BLOB);' - }, - { - arguments: ['myTable', { data: 'LONGBLOB' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`data` LONGBLOB);' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));' - }, - { - arguments: ['myTable', { title: 'VARCHAR BINARY(255)', number: 'INTEGER(5) UNSIGNED PRIMARY KEY ' }], // length and unsigned are not allowed on primary key - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER PRIMARY KEY);' - }, - { - arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM("A", "B", "C"), `name` VARCHAR(255));' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER PRIMARY KEY);' - }, - { - arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION);' - }, - { - arguments: ['myTable', { id: 'INTEGER PRIMARY KEY AUTOINCREMENT', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255));' - }, - { - arguments: ['myTable', { id: 'INTEGER(4) PRIMARY KEY AUTOINCREMENT', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255));' - }, - { - arguments: ['myTable', { id: 'SMALLINT(4) PRIMARY KEY AUTOINCREMENT UNSIGNED', name: 'VARCHAR(255)' }], - expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255));' - }, - { - 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`));' - } - ], - - selectQuery: [ - { - arguments: ['myTable'], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { attributes: ['id', 'name'] }], - expectation: 'SELECT `id`, `name` FROM `myTable`;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { id: 2 } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: 'foo' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: { name: "foo';DROP TABLE myTable;" } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo'';DROP TABLE myTable;';", - context: QueryGenerator - }, { - arguments: ['myTable', { where: 2 }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;', - context: QueryGenerator - }, { - arguments: ['foo', { attributes: [['count(*)', 'count']] }], - expectation: 'SELECT count(*) AS `count` FROM `foo`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['id', 'DESC'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: ['myTable.id'] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['myTable.id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator - }, { - arguments: ['myTable', { order: [['id', 'DESC']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function(sequelize) {return sequelize.define('myTable', {});}], - expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and default comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) = 'jan' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'sequelize.where with .fn as attribute and LIKE comparator', - arguments: ['myTable', function(sequelize) { - return { - where: sequelize.and( - sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'), - { type: 1 } - ) - }; - }], - expectation: "SELECT * FROM `myTable` WHERE (LOWER(`user`.`name`) LIKE '%t%' AND `myTable`.`type` = 1);", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take functions as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']] - }; - }], - expectation: 'SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'functions can take all types as arguments', - arguments: ['myTable', function(sequelize) { - return { - order: [ - [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'], - [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'] - ] - }; - }], - expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55.000 +00:00') ASC;", - context: QueryGenerator, - needsSequelize: true - }, { - title: 'single string argument should be quoted', - arguments: ['myTable', { group: 'name' }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - arguments: ['myTable', { group: ['name'] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`;', - context: QueryGenerator - }, { - title: 'functions work for group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt'))] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);', - context: QueryGenerator, - needsSequelize: true - }, { - title: 'It is possible to mix sequelize.fn and string arguments to group by', - arguments: ['myTable', function(sequelize) { - return { - group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'] - }; - }], - expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { group: ['name', 'title'] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name`, `title`;', - context: QueryGenerator - }, { - arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }], - expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;', - context: QueryGenerator - }, { - title: 'HAVING clause works with where-like hash', - arguments: ['myTable', function(sequelize) { - return { - attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']], - group: ['creationYear', 'title'], - having: { creationYear: { [Op.gt]: 2002 } } - }; - }], - expectation: 'SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;', - context: QueryGenerator, - needsSequelize: true - }, { - arguments: ['myTable', { limit: 10 }], - expectation: 'SELECT * FROM `myTable` LIMIT 10;', - context: QueryGenerator - }, { - arguments: ['myTable', { limit: 10, offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10;', - context: QueryGenerator - }, { - title: 'uses default limit if only offset is specified', - arguments: ['myTable', { offset: 2 }], - expectation: 'SELECT * FROM `myTable` LIMIT 2, 10000000000000;', - context: QueryGenerator - }, { - title: 'multiple where arguments', - arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat` = 'canoe' AND `myTable`.`weather` = 'cold';", - context: QueryGenerator - }, { - title: 'no where arguments (object)', - arguments: ['myTable', { where: {} }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'no where arguments (string)', - arguments: ['myTable', { where: [''] }], - expectation: 'SELECT * FROM `myTable` WHERE 1=1;', - context: QueryGenerator - }, { - title: 'no where arguments (null)', - arguments: ['myTable', { where: null }], - expectation: 'SELECT * FROM `myTable`;', - context: QueryGenerator - }, { - title: 'buffer as where argument', - arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }], - expectation: "SELECT * FROM `myTable` WHERE `myTable`.`field` = X'53657175656c697a65';", - context: QueryGenerator - }, { - title: 'use != if ne !== null', - arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 0;', - context: QueryGenerator - }, { - title: 'use IS NOT if ne === null', - arguments: ['myTable', { where: { field: { [Op.ne]: null } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT NULL;', - context: QueryGenerator - }, { - title: 'use IS NOT if not === BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: true } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` IS NOT 1;', - context: QueryGenerator - }, { - title: 'use != if not !== BOOLEAN', - arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }], - expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`field` != 3;', - context: QueryGenerator - } - ], - - insertQuery: [ - { - arguments: ['myTable', { name: 'foo' }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ['foo'] - } - }, { - arguments: ['myTable', { name: "'bar'" }], - expectation: { - query: 'INSERT INTO `myTable` (`name`) VALUES ($1);', - bind: ["'bar'"] - } - }, { - arguments: ['myTable', { data: Buffer.from('Sequelize') }], - expectation: { - query: 'INSERT INTO `myTable` (`data`) VALUES ($1);', - bind: [Buffer.from('Sequelize')] - } - }, { - arguments: ['myTable', { name: 'bar', value: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);', - bind: ['bar', null] - } - }, { - arguments: ['myTable', { name: 'bar', value: undefined }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);', - bind: ['bar', undefined] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`birthday`) VALUES ($1,$2);', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate()] - } - }, { - arguments: ['myTable', { name: 'foo', value: true }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);', - bind: ['foo', true] - } - }, { - arguments: ['myTable', { name: 'foo', value: false }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);', - bind: ['foo', false] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);', - bind: ['foo', 1, null] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }], - expectation: { - query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);', - bind: ['foo', 1] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', function(sequelize) { - return { - foo: sequelize.fn('NOW') - }; - }], - expectation: { - query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', - bind: [] - }, - needsSequelize: true - } - ], - - bulkInsertQuery: [ - { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: "'bar'" }, { name: 'foo' }]], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');" - }, { - arguments: ['myTable', [{ name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { name: 'bar', birthday: moment('2012-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }]], - expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');" - }, { - arguments: ['myTable', [{ name: 'bar', value: null }, { name: 'foo', value: 1 }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);" - }, { - arguments: ['myTable', [{ name: 'bar', value: undefined }, { name: 'bar', value: 2 }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);" - }, { - arguments: ['myTable', [{ name: 'foo', value: true }, { name: 'bar', value: false }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);" - }, { - arguments: ['myTable', [{ name: 'foo', value: false }, { name: 'bar', value: false }]], - expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);" - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: true } } // Note: We don't honour this because it makes little sense when some rows may have nulls and others not - }, { - arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]], - expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", - context: { options: { omitNull: true } } // Note: As above - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }], - expectation: "INSERT OR IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');" - }, { - arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { updateOnDuplicate: ['name'], upsertKeys: ['name'] }], - expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar') ON CONFLICT (`name`) DO UPDATE SET `name`=EXCLUDED.`name`;" - } - ], - - updateQuery: [ - { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - } - }, { - arguments: ['myTable', { name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate() }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`birthday`=$2 WHERE `id` = $3', - bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate(), 2] - } - }, { - arguments: ['myTable', { name: 'foo' }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1 WHERE `id` = $2', - bind: ['foo', 2] - } - }, { - arguments: ['myTable', { name: "'bar'" }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1 WHERE `id` = $2', - bind: ["'bar'", 2] - } - }, { - arguments: ['myTable', { name: 'bar', value: null }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`value`=$2 WHERE `id` = $3', - bind: ['bar', null, 2] - } - }, { - arguments: ['myTable', { name: 'bar', value: undefined }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `name`=$1,`value`=$2 WHERE `id` = $3', - bind: ['bar', undefined, 2] - } - }, { - arguments: ['myTable', { flag: true }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `flag`=$1 WHERE `id` = $2', - bind: [true, 2] - } - }, { - arguments: ['myTable', { flag: false }, { id: 2 }], - expectation: { - query: 'UPDATE `myTable` SET `flag`=$1 WHERE `id` = $2', - bind: [false, 2] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1,`nullValue`=$2 WHERE `name` = $3', - bind: [2, null, 'foo'] - }, - context: { options: { omitNull: false } } - }, { - arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=$1 WHERE `name` = $2', - bind: [2, 'foo'] - }, - context: { options: { omitNull: true } } - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.fn('NOW') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=NOW() WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - }, { - arguments: ['myTable', function(sequelize) { - return { - bar: sequelize.col('foo') - }; - }, { name: 'foo' }], - expectation: { - query: 'UPDATE `myTable` SET `bar`=`foo` WHERE `name` = $1', - bind: ['foo'] - }, - needsSequelize: true - } - ], - renameColumnQuery: [ - { - title: 'Properly quotes column names', - arguments: ['myTable', 'foo', 'commit', { commit: 'VARCHAR(255)', bar: 'VARCHAR(255)' }], - expectation: - '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));' + - 'INSERT INTO `myTable` SELECT `commit`, `bar` FROM `myTable_backup`;' + - 'DROP TABLE `myTable_backup`;' - } - ], - removeColumnQuery: [ - { - title: 'Properly quotes column names', - arguments: ['myTable', { commit: 'VARCHAR(255)', bar: 'VARCHAR(255)' }], - expectation: - 'CREATE TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + - 'INSERT INTO `myTable_backup` SELECT `commit`, `bar` FROM `myTable`;' + - 'DROP TABLE `myTable`;' + - 'CREATE TABLE IF NOT EXISTS `myTable` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + - 'INSERT INTO `myTable` SELECT `commit`, `bar` FROM `myTable_backup`;' + - 'DROP TABLE `myTable_backup`;' - } - ] - }; - - _.each(suites, (tests, suiteTitle) => { - describe(suiteTitle, () => { - beforeEach(function() { - this.queryGenerator = new QueryGenerator({ - sequelize: this.sequelize, - _dialect: this.sequelize.dialect - }); - }); - - tests.forEach(test => { - const query = test.expectation.query || test.expectation; - const title = test.title || `SQLite correctly returns ${query} for ${JSON.stringify(test.arguments)}`; - it(title, function() { - if (test.needsSequelize) { - if (typeof test.arguments[1] === 'function') test.arguments[1] = test.arguments[1](this.sequelize); - if (typeof test.arguments[2] === 'function') test.arguments[2] = test.arguments[2](this.sequelize); - } - - // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - 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 deleted file mode 100644 index b8a7c1e12186..000000000000 --- a/test/unit/errors.test.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -const errors = require('../../lib/errors'); -const expect = require('chai').expect; - -describe('errors', () => { - it('should maintain stack trace with message', () => { - const errorsWithMessage = [ - 'BaseError', 'ValidationError', 'InstanceError', - 'EmptyResultError', 'EagerLoadingError', 'AssociationError', 'QueryError' - ]; - - errorsWithMessage.forEach(errorName => { - function throwError() { - throw new errors[errorName]('this is a message'); - } - let err; - try { - throwError(); - } catch (error) { - err = error; - } - expect(err).to.exist; - const stackParts = err.stack.split('\n'); - const fullErrorName = `Sequelize${errorName}`; - expect(stackParts[0]).to.equal(`${fullErrorName}: this is a message`); - expect(stackParts[1]).to.match(/^ {4}at throwError \(.*errors.test.js:\d+:\d+\)$/); - }); - }); - - it('should maintain stack trace without message', () => { - const errorsWithoutMessage = [ - 'ConnectionError', 'ConnectionRefusedError', 'ConnectionTimedOutError', - 'AccessDeniedError', 'HostNotFoundError', 'HostNotReachableError', 'InvalidConnectionError' - ]; - - errorsWithoutMessage.forEach(errorName => { - function throwError() { - throw new errors[errorName](null); - } - let err; - try { - throwError(); - } catch (error) { - err = error; - } - expect(err).to.exist; - const stackParts = err.stack.split('\n'); - - const fullErrorName = `Sequelize${errorName}`; - expect(stackParts[0]).to.equal(fullErrorName); - 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 deleted file mode 100644 index 92ee7f961ee5..000000000000 --- a/test/unit/hooks.test.js +++ /dev/null @@ -1,419 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('./support'), - _ = require('lodash'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { - this.Model = current.define('m'); - }); - - it('does not expose non-model hooks', function() { - for (const badHook of ['beforeDefine', 'afterDefine', 'beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect', 'beforeInit', 'afterInit']) { - expect(this.Model).to.not.have.property(badHook); - } - }); - - describe('arguments', () => { - it('hooks can modify passed arguments', async function() { - this.Model.addHook('beforeCreate', options => { - options.answer = 41; - }); - - const options = {}; - await this.Model.runHooks('beforeCreate', options); - expect(options.answer).to.equal(41); - }); - }); - - describe('proxies', () => { - beforeEach(() => { - sinon.stub(current, 'query').resolves([{ - _previousDataValues: {}, - dataValues: { id: 1, name: 'abc' } - }]); - }); - - afterEach(() => { - current.query.restore(); - }); - - describe('defined by options.hooks', () => { - beforeEach(function() { - this.beforeSaveHook = sinon.spy(); - this.afterSaveHook = sinon.spy(); - this.afterCreateHook = sinon.spy(); - - this.Model = current.define('m', { - name: Support.Sequelize.STRING - }, { - hooks: { - beforeSave: this.beforeSaveHook, - afterSave: this.afterSaveHook, - afterCreate: this.afterCreateHook - } - }); - }); - - it('calls beforeSave/afterSave', async function() { - await this.Model.create({}); - expect(this.afterCreateHook).to.have.been.calledOnce; - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); - }); - - describe('defined by addHook method', () => { - beforeEach(function() { - this.beforeSaveHook = sinon.spy(); - this.afterSaveHook = sinon.spy(); - - this.Model = current.define('m', { - name: Support.Sequelize.STRING - }); - - this.Model.addHook('beforeSave', this.beforeSaveHook); - this.Model.addHook('afterSave', this.afterSaveHook); - }); - - it('calls beforeSave/afterSave', async function() { - await this.Model.create({}); - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); - }); - - describe('defined by hook method', () => { - beforeEach(function() { - this.beforeSaveHook = sinon.spy(); - this.afterSaveHook = sinon.spy(); - - this.Model = current.define('m', { - name: Support.Sequelize.STRING - }); - - this.Model.addHook('beforeSave', this.beforeSaveHook); - this.Model.addHook('afterSave', this.afterSaveHook); - }); - - it('calls beforeSave/afterSave', async function() { - await this.Model.create({}); - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); - }); - }); - - describe('multiple hooks', () => { - beforeEach(function() { - this.hook1 = sinon.spy(); - this.hook2 = sinon.spy(); - this.hook3 = sinon.spy(); - }); - - describe('runs all hooks on success', () => { - afterEach(function() { - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).to.have.been.calledOnce; - expect(this.hook3).to.have.been.calledOnce; - }); - - it('using addHook', async function() { - this.Model.addHook('beforeCreate', this.hook1); - this.Model.addHook('beforeCreate', this.hook2); - this.Model.addHook('beforeCreate', this.hook3); - - await this.Model.runHooks('beforeCreate'); - }); - - it('using function', async function() { - this.Model.beforeCreate(this.hook1); - this.Model.beforeCreate(this.hook2); - this.Model.beforeCreate(this.hook3); - - await this.Model.runHooks('beforeCreate'); - }); - - it('using define', async function() { - await current.define('M', {}, { - hooks: { - beforeCreate: [this.hook1, this.hook2, this.hook3] - } - }).runHooks('beforeCreate'); - }); - - it('using a mixture', async function() { - const Model = current.define('M', {}, { - hooks: { - beforeCreate: this.hook1 - } - }); - Model.beforeCreate(this.hook2); - Model.addHook('beforeCreate', this.hook3); - - await Model.runHooks('beforeCreate'); - }); - }); - - it('stops execution when a hook throws', async function() { - this.Model.beforeCreate(() => { - this.hook1(); - - throw new Error('No!'); - }); - this.Model.beforeCreate(this.hook2); - - 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', async function() { - this.Model.beforeCreate(async () => { - this.hook1(); - - throw new Error('No!'); - }); - this.Model.beforeCreate(this.hook2); - - 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', async function() { - const globalHook = sinon.spy(); - - current.addHook('beforeUpdate', globalHook); - - await this.Model.runHooks('beforeUpdate'); - expect(globalHook).to.have.been.calledOnce; - }); - - it('invokes the global hook, when the model also has a hook', async () => { - const globalHookBefore = sinon.spy(), - globalHookAfter = sinon.spy(), - localHook = sinon.spy(); - - current.addHook('beforeUpdate', globalHookBefore); - - const Model = current.define('m', {}, { - hooks: { - beforeUpdate: localHook - } - }); - - current.addHook('beforeUpdate', globalHookAfter); - - 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); - }); - }); - - describe('using define hooks', () => { - beforeEach(function() { - this.beforeCreate = sinon.spy(); - this.sequelize = Support.createSequelizeInstance({ - define: { - hooks: { - beforeCreate: this.beforeCreate - } - } - }); - }); - - 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 - } - }); - - 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', async function() { - const localHook = sinon.spy(), - Model = this.sequelize.define('M', {}, { - hooks: { - beforeCreate: localHook - } - }); - - await Model.runHooks('beforeCreate'); - expect(this.beforeCreate).not.to.have.been.called; - expect(localHook).to.have.been.calledOnce; - }); - }); - }); - - describe('#removeHook', () => { - it('should remove hook', async function() { - const hook1 = sinon.spy(), - hook2 = sinon.spy(); - - this.Model.addHook('beforeCreate', 'myHook', hook1); - this.Model.beforeCreate('myHook2', hook2); - - await this.Model.runHooks('beforeCreate'); - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - - hook1.resetHistory(); - hook2.resetHistory(); - - this.Model.removeHook('beforeCreate', 'myHook'); - this.Model.removeHook('beforeCreate', 'myHook2'); - - 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', async function() { - const hook1 = sinon.spy(), - hook2 = sinon.spy(), - hook3 = sinon.spy(), - hook4 = sinon.spy(); - - this.Model.addHook('beforeCreate', hook1); - this.Model.addHook('beforeCreate', 'myHook', hook2); - this.Model.beforeCreate('myHook2', hook3); - this.Model.beforeCreate(hook4); - - 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', async function() { - const hook1 = sinon.spy(), - hook2 = sinon.spy(); - - const Model = this.sequelize.define('Model', {}, { - hooks: { beforeCreate: hook1 } - }); - - Model.addHook('beforeCreate', hook2); - - await Model.runHooks('beforeCreate'); - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - }); - }); - - describe('promises', () => { - it('can return a promise', async function() { - this.Model.beforeBulkCreate(async () => { - // This space intentionally left blank - }); - - await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; - }); - - it('can return undefined', async function() { - this.Model.beforeBulkCreate(() => { - // This space intentionally left blank - }); - - await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; - }); - - it('can return an error by rejecting', async function() { - this.Model.beforeCreate(async () => { - throw new Error('Forbidden'); - }); - - await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); - }); - - it('can return an error by throwing', async function() { - this.Model.beforeCreate(() => { - throw new Error('Forbidden'); - }); - - await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); - }); - }); - - describe('sync hooks', () => { - beforeEach(function() { - this.hook1 = sinon.spy(); - this.hook2 = sinon.spy(); - this.hook3 = sinon.spy(); - this.hook4 = sinon.spy(); - }); - - it('runs all beforInit/afterInit hooks', function() { - Support.Sequelize.addHook('beforeInit', 'h1', this.hook1); - Support.Sequelize.addHook('beforeInit', 'h2', this.hook2); - Support.Sequelize.addHook('afterInit', 'h3', this.hook3); - Support.Sequelize.addHook('afterInit', 'h4', this.hook4); - - Support.createSequelizeInstance(); - - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).to.have.been.calledOnce; - expect(this.hook3).to.have.been.calledOnce; - expect(this.hook4).to.have.been.calledOnce; - - // cleanup hooks on Support.Sequelize - Support.Sequelize.removeHook('beforeInit', 'h1'); - Support.Sequelize.removeHook('beforeInit', 'h2'); - Support.Sequelize.removeHook('afterInit', 'h3'); - Support.Sequelize.removeHook('afterInit', 'h4'); - - Support.createSequelizeInstance(); - - // check if hooks were removed - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).to.have.been.calledOnce; - expect(this.hook3).to.have.been.calledOnce; - expect(this.hook4).to.have.been.calledOnce; - }); - - it('runs all beforDefine/afterDefine hooks', function() { - const sequelize = Support.createSequelizeInstance(); - sequelize.addHook('beforeDefine', this.hook1); - sequelize.addHook('beforeDefine', this.hook2); - sequelize.addHook('afterDefine', this.hook3); - sequelize.addHook('afterDefine', this.hook4); - sequelize.define('Test', {}); - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).to.have.been.calledOnce; - expect(this.hook3).to.have.been.calledOnce; - expect(this.hook4).to.have.been.calledOnce; - }); - }); -}); diff --git a/test/unit/increment.test.js b/test/unit/increment.test.js deleted file mode 100644 index 4568912ba9e9..000000000000 --- a/test/unit/increment.test.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('increment', () => { - describe('options tests', () => { - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - }, - count: Sequelize.BIGINT - }); - - 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', 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/build.test.js b/test/unit/instance/build.test.js deleted file mode 100644 index f2d671c98e77..000000000000 --- a/test/unit/instance/build.test.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('build', () => { - it('should populate NOW default values', async () => { - const Model = current.define('Model', { - created_time: { - type: DataTypes.DATE, - allowNull: true, - defaultValue: DataTypes.NOW - }, - updated_time: { - type: DataTypes.DATE, - allowNull: true, - defaultValue: DataTypes.NOW - }, - ip: { - type: DataTypes.STRING, - validate: { - isIP: true - } - }, - ip2: { - type: DataTypes.STRING, - validate: { - isIP: { - msg: 'test' - } - } - } - }, { - timestamp: false - }), - instance = Model.build({ ip: '127.0.0.1', ip2: '0.0.0.0' }); - - expect(instance.get('created_time')).to.be.ok; - expect(instance.get('created_time')).to.be.an.instanceof(Date); - - expect(instance.get('updated_time')).to.be.ok; - expect(instance.get('updated_time')).to.be.an.instanceof(Date); - - await instance.validate(); - }); - - it('should populate explicitly undefined UUID primary keys', () => { - const Model = current.define('Model', { - id: { - type: DataTypes.UUID, - primaryKey: true, - allowNull: false, - defaultValue: DataTypes.UUIDV4 - } - }), - instance = Model.build({ - id: undefined - }); - - expect(instance.get('id')).not.to.be.undefined; - expect(instance.get('id')).to.be.ok; - }); - - it('should populate undefined columns with default value', () => { - const Model = current.define('Model', { - number1: { - type: DataTypes.INTEGER, - defaultValue: 1 - }, - number2: { - type: DataTypes.INTEGER, - defaultValue: 2 - } - }), - instance = Model.build({ - number1: undefined - }); - - expect(instance.get('number1')).not.to.be.undefined; - expect(instance.get('number1')).to.equal(1); - expect(instance.get('number2')).not.to.be.undefined; - expect(instance.get('number2')).to.equal(2); - }); - - it('should clone the default values', () => { - const Model = current.define('Model', { - data: { - type: DataTypes.JSONB, - defaultValue: { foo: 'bar' } - } - }), - instance = Model.build(); - instance.data.foo = 'biz'; - - expect(instance.get('data')).to.eql({ foo: 'biz' }); - expect(Model.build().get('data')).to.eql({ foo: 'bar' }); - }); - }); -}); diff --git a/test/unit/instance/changed.test.js b/test/unit/instance/changed.test.js deleted file mode 100644 index a44ae2047093..000000000000 --- a/test/unit/instance/changed.test.js +++ /dev/null @@ -1,188 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('changed', () => { - beforeEach(function() { - this.User = current.define('User', { - name: DataTypes.STRING, - birthday: DataTypes.DATE, - yoj: DataTypes.DATEONLY, - meta: DataTypes.JSON - }); - }); - - it('should return true for changed primitive', function() { - const user = this.User.build({ - name: 'a' - }, { - isNewRecord: false, - raw: true - }); - - expect(user.changed('meta')).to.equal(false); - user.set('name', 'b'); - user.set('meta', null); - expect(user.changed('name')).to.equal(true); - expect(user.changed('meta')).to.equal(true); - }); - - it('should return falsy for unchanged primitive', function() { - const user = this.User.build({ - name: 'a', - meta: null - }, { - isNewRecord: false, - raw: true - }); - - user.set('name', 'a'); - user.set('meta', null); - expect(user.changed('name')).to.equal(false); - expect(user.changed('meta')).to.equal(false); - }); - - it('should return true for multiple changed values', function() { - const user = this.User.build({ - name: 'a', - birthday: new Date(new Date() - 10) - }, { - isNewRecord: false, - raw: true - }); - - user.set('name', 'b'); - user.set('birthday', new Date()); - expect(user.changed('name')).to.equal(true); - expect(user.changed('birthday')).to.equal(true); - }); - - it('should return false for two instances with same value', function() { - const milliseconds = 1436921941088; - const firstDate = new Date(milliseconds); - const secondDate = new Date(milliseconds); - - const user = this.User.build({ - birthday: firstDate - }, { - isNewRecord: false, - raw: true - }); - - user.set('birthday', secondDate); - expect(user.changed('birthday')).to.equal(false); - }); - - it('should not detect changes when equal', function() { - for (const value of [null, 1, 'asdf', new Date(), [], {}, Buffer.from('')]) { - const t = new this.User({ - json: value - }, { - isNewRecord: false, - raw: true - }); - t.json = value; - expect(t.changed('json')).to.be.false; - expect(t.changed()).to.be.false; - } - }); - - it('should return true for JSON dot.separated key with changed values', function() { - const user = this.User.build({ - meta: { - city: 'Stockholm' - } - }, { - isNewRecord: false, - raw: true - }); - - user.set('meta.city', 'Gothenburg'); - expect(user.changed('meta')).to.equal(true); - }); - - it('should return false for JSON dot.separated key with same value', function() { - const user = this.User.build({ - meta: { - city: 'Gothenburg' - } - }, { - isNewRecord: false, - raw: true - }); - - user.set('meta.city', 'Gothenburg'); - expect(user.changed('meta')).to.equal(false); - }); - - it('should return true for JSON dot.separated key with object', function() { - const user = this.User.build({ - meta: { - address: { street: 'Main street', number: '40' } - } - }, { - isNewRecord: false, - raw: true - }); - - user.set('meta.address', { street: 'Second street', number: '1' } ); - expect(user.changed('meta')).to.equal(true); - }); - - it('should return false for JSON dot.separated key with same object', function() { - const user = this.User.build({ - meta: { - address: { street: 'Main street', number: '40' } - } - }, { - isNewRecord: false, - raw: true - }); - - user.set('meta.address', { street: 'Main street', number: '40' } ); - expect(user.changed('meta')).to.equal(false); - }); - - it('should return false when changed from null to null', function() { - const attributes = {}; - for (const attr in this.User.rawAttributes) { - attributes[attr] = null; - } - - const user = this.User.build(attributes, { - isNewRecord: false, - raw: true - }); - - for (const attr in this.User.rawAttributes) { - user.set(attr, null); - } - - for (const attr in this.User.rawAttributes) { - expect(user.changed(attr), `${attr} is not changed`).to.equal(false); - } - }); - - describe('setDataValue', () => { - it('should return falsy for unchanged primitive', function() { - const user = this.User.build({ - name: 'a', - meta: null - }, { - isNewRecord: false, - raw: true - }); - - user.setDataValue('name', 'a'); - user.setDataValue('meta', null); - expect(user.changed('name')).to.equal(false); - expect(user.changed('meta')).to.equal(false); - }); - }); - }); -}); diff --git a/test/unit/instance/decrement.test.js b/test/unit/instance/decrement.test.js deleted file mode 100644 index 24fa2e991a7e..000000000000 --- a/test/unit/instance/decrement.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('decrement', () => { - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - } - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - { - _previousDataValues: { id: 3 }, - dataValues: { id: 1 } - } - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow decrements even if options are not given', () => { - instance = Model.build({ id: 3 }, { isNewRecord: false }); - expect(() => { - instance.decrement(['id']); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/destroy.test.js b/test/unit/instance/destroy.test.js deleted file mode 100644 index 4932da5b473c..000000000000 --- a/test/unit/instance/destroy.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('destroy', () => { - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - } - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - { - _previousDataValues: {}, - dataValues: { id: 1 } - } - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow destroies even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); - expect(() => { - instance.destroy(); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/get.test.js b/test/unit/instance/get.test.js deleted file mode 100644 index 516c5a0763a5..000000000000 --- a/test/unit/instance/get.test.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('get', () => { - beforeEach(function() { - this.getSpy = sinon.spy(); - this.User = current.define('User', { - name: { - type: DataTypes.STRING, - get: this.getSpy - } - }); - }); - - it('invokes getter if raw: false', function() { - this.User.build().get('name'); - - expect(this.getSpy).to.have.been.called; - }); - - it('does not invoke getter if raw: true', function() { - this.User.build().get('name', { raw: true }); - - expect(this.getSpy).not.to.have.been.called; - }); - }); -}); diff --git a/test/unit/instance/increment.test.js b/test/unit/instance/increment.test.js deleted file mode 100644 index 891bb56b48c0..000000000000 --- a/test/unit/instance/increment.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('increment', () => { - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - } - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - { - _previousDataValues: { id: 1 }, - dataValues: { id: 3 } - } - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow increments even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); - expect(() => { - instance.increment(['id']); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/is-soft-deleted.test.js b/test/unit/instance/is-soft-deleted.test.js deleted file mode 100644 index 6f49a1b63088..000000000000 --- a/test/unit/instance/is-soft-deleted.test.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - DataTypes = require('../../../lib/data-types'), - Sequelize = Support.Sequelize, - moment = require('moment'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('isSoftDeleted', () => { - beforeEach(function() { - const User = current.define('User', { - name: DataTypes.STRING, - birthdate: DataTypes.DATE, - meta: DataTypes.JSON, - deletedAt: { - type: Sequelize.DATE - } - }); - - const ParanoidUser = current.define('User', { - name: DataTypes.STRING, - birthdate: DataTypes.DATE, - meta: DataTypes.JSON, - deletedAt: { - type: Sequelize.DATE - } - }, { - paranoid: true - }); - - this.paranoidUser = ParanoidUser.build({ - name: 'a' - }, { - isNewRecord: false, - raw: true - }); - - this.user = User.build({ - name: 'a' - }, { - isNewRecord: false, - raw: true - }); - }); - - it('should not throw if paranoid is set to true', function() { - expect(() => { - this.paranoidUser.isSoftDeleted(); - }).to.not.throw(); - }); - - it('should throw if paranoid is set to false', function() { - expect(() => { - this.user.isSoftDeleted(); - }).to.throw('Model is not paranoid'); - }); - - it('should return false if the soft-delete property is the same as the default value', function() { - this.paranoidUser.setDataValue('deletedAt', null); - expect(this.paranoidUser.isSoftDeleted()).to.be.false; - }); - - it('should return true if the soft-delete property is set', function() { - this.paranoidUser.setDataValue('deletedAt', moment().subtract(5, 'days').format()); - expect(this.paranoidUser.isSoftDeleted()).to.be.true; - }); - }); -}); diff --git a/test/unit/instance/reload.test.js b/test/unit/instance/reload.test.js deleted file mode 100644 index e7ab006bc5a9..000000000000 --- a/test/unit/instance/reload.test.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('reload', () => { - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - }, - deletedAt: { - type: Sequelize.DATE - } - }, { - paranoid: true - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - { - _previousDataValues: { id: 1 }, - dataValues: { id: 2 } - } - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow reloads even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); - expect(() => { - instance.reload(); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/restore.test.js b/test/unit/instance/restore.test.js deleted file mode 100644 index b2b6083acee9..000000000000 --- a/test/unit/instance/restore.test.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('restore', () => { - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - }, - deletedAt: { - type: Sequelize.DATE - } - }, { - paranoid: true - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - [{ - _previousDataValues: { id: 1 }, - dataValues: { id: 2 } - }, 1] - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow restores even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); - expect(() => { - instance.restore(); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/save.test.js b/test/unit/instance/save.test.js deleted file mode 100644 index 5b87e675514d..000000000000 --- a/test/unit/instance/save.test.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - Sequelize = Support.Sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('save', () => { - it('should disallow saves if no primary key values is present', async () => { - const Model = current.define('User', { - - }), - instance = Model.build({}, { isNewRecord: false }); - - await expect(instance.save()).to.be.rejected; - }); - - describe('options tests', () => { - let stub, instance; - const Model = current.define('User', { - id: { - type: Sequelize.BIGINT, - primaryKey: true, - autoIncrement: true - } - }); - - before(() => { - stub = sinon.stub(current, 'query').resolves( - [{ - _previousDataValues: {}, - dataValues: { id: 1 } - }, 1] - ); - }); - - after(() => { - stub.restore(); - }); - - it('should allow saves even if options are not given', () => { - instance = Model.build({}); - expect(() => { - instance.save(); - }).to.not.throw(); - }); - }); - }); -}); diff --git a/test/unit/instance/set.test.js b/test/unit/instance/set.test.js deleted file mode 100644 index dc2ef6056ee7..000000000000 --- a/test/unit/instance/set.test.js +++ /dev/null @@ -1,149 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('set', () => { - it('sets nested keys in JSON objects', () => { - const User = current.define('User', { - meta: DataTypes.JSONB - }); - const user = User.build({ - meta: { - location: 'Stockhollm' - } - }, { - isNewRecord: false, - raw: true - }); - - const meta = user.get('meta'); - - user.set('meta.location', 'Copenhagen'); - expect(user.dataValues['meta.location']).not.to.be.ok; - expect(user.get('meta').location).to.equal('Copenhagen'); - expect(user.get('meta') === meta).to.equal(true); - expect(user.get('meta') === meta).to.equal(true); - }); - - it('doesnt mutate the JSONB defaultValue', () => { - const User = current.define('User', { - meta: { - type: DataTypes.JSONB, - allowNull: false, - defaultValue: {} - } - }); - const user1 = User.build({}); - user1.set('meta.location', 'Stockhollm'); - const user2 = User.build({}); - expect(user2.get('meta')).to.deep.equal({}); - }); - - it('sets the date "1970-01-01" to previously null field', () => { - const User = current.define('User', { - date: { - type: DataTypes.DATE, - allowNull: true - } - }); - const user1 = User.build({ - date: null - }); - user1.set('date', '1970-01-01'); - expect(user1.get('date')).to.be.ok; - expect(user1.get('date').getTime()).to.equal(new Date('1970-01-01').getTime()); - }); - - it('overwrites non-date originalValue with date', () => { - const User = current.define('User', { - date: DataTypes.DATE - }); - const user = User.build({ - date: ' ' - }, { - isNewRecord: false, - raw: true - }); - - user.set('date', new Date()); - expect(user.get('date')).to.be.an.instanceof(Date); - expect(user.get('date')).not.to.be.NaN; - }); - - describe('custom setter', () => { - before(function() { - this.stubCreate = sinon.stub(current.getQueryInterface(), 'insert').callsFake(async instance => [instance, 1]); - }); - - after(function() { - this.stubCreate.restore(); - }); - - const User = current.define('User', { - phoneNumber: { - type: DataTypes.STRING, - set(val) { - if (typeof val === 'object' && val !== null) { - val = `00${val.country}${val.area}${val.local}`; - } - if (typeof val === 'string') { - // Canonicalize phone number - val = val.replace(/^\+/, '00').replace(/\(0\)|[\s+/.\-()]/g, ''); - } - this.setDataValue('phoneNumber', val); - } - } - }); - - 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' - }); - 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; - }); - - 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' - }); - 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; - }); - - 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' - }); - 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; - }); - - 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' - }); - 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; - }); - }); - }); -}); diff --git a/test/unit/instance/to-json.test.js b/test/unit/instance/to-json.test.js deleted file mode 100644 index ef84e93388ff..000000000000 --- a/test/unit/instance/to-json.test.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Instance'), () => { - describe('toJSON', () => { - it('returns copy of json', () => { - const User = current.define('User', { - name: DataTypes.STRING - }); - const user = User.build({ name: 'my-name' }); - const json1 = user.toJSON(); - expect(json1).to.have.property('name').and.be.equal('my-name'); - - // remove value from json and ensure it's not changed in the instance - delete json1.name; - - const json2 = user.toJSON(); - expect(json2).to.have.property('name').and.be.equal('my-name'); - }); - - it('returns clone of JSON data-types', () => { - const User = current.define('User', { - name: DataTypes.STRING, - permissions: DataTypes.JSON - }); - const user = User.build({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); - const json = user.toJSON(); - - expect(json) - .to.have.property('permissions') - .that.does.not.equal(user.permissions); - - json.permissions.admin = false; - - expect(user.permissions) - .to.have.property('admin') - .that.equals(true); - }); - }); -}); diff --git a/test/unit/model/bulkcreate.test.js b/test/unit/model/bulkcreate.test.js deleted file mode 100644 index 81a8bb403b12..000000000000 --- a/test/unit/model/bulkcreate.test.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - sinon = require('sinon'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('bulkCreate', () => { - before(function() { - this.Model = current.define('model', { - accountId: { - type: DataTypes.INTEGER(11).UNSIGNED, - allowNull: false, - field: 'account_id' - } - }, { timestamps: false }); - - this.stub = sinon.stub(current.getQueryInterface(), 'bulkInsert').resolves([]); - }); - - afterEach(function() { - this.stub.resetHistory(); - }); - - after(function() { - this.stub.restore(); - }); - - describe('validations', () => { - it('should not fail for renamed fields', async function() { - await this.Model.bulkCreate([ - { accountId: 42 } - ], { 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 deleted file mode 100644 index 08838b351057..000000000000 --- a/test/unit/model/count.test.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = Support.Sequelize, - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('method count', () => { - before(function() { - this.oldFindAll = Sequelize.Model.findAll; - this.oldAggregate = Sequelize.Model.aggregate; - - Sequelize.Model.findAll = sinon.stub().resolves(); - - this.User = current.define('User', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }); - this.Project = current.define('Project', { - name: DataTypes.STRING - }); - - this.User.hasMany(this.Project); - this.Project.belongsTo(this.User); - }); - - after(function() { - Sequelize.Model.findAll = this.oldFindAll; - Sequelize.Model.aggregate = this.oldAggregate; - }); - - beforeEach(function() { - this.stub = Sequelize.Model.aggregate = sinon.stub().resolves(); - }); - - describe('should pass the same options to model.aggregate as findAndCountAll', () => { - it('with includes', async function() { - const queryObject = { - include: [this.Project] - }; - 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', async function() { - const queryObject = { - attributes: ['username'] - }; - 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/define.test.js b/test/unit/model/define.test.js deleted file mode 100644 index 7f387649d7e6..000000000000 --- a/test/unit/model/define.test.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - current = Support.sequelize, - dialect = Support.getTestDialect(); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('define', () => { - it('should allow custom timestamps with underscored: true', () => { - const Model = current.define('User', {}, { - createdAt: 'createdAt', - updatedAt: 'updatedAt', - timestamps: true, - underscored: true - }); - - expect(Model.rawAttributes).to.haveOwnProperty('createdAt'); - expect(Model.rawAttributes).to.haveOwnProperty('updatedAt'); - - expect(Model._timestampAttributes.createdAt).to.equal('createdAt'); - expect(Model._timestampAttributes.updatedAt).to.equal('updatedAt'); - - expect(Model.rawAttributes).not.to.have.property('created_at'); - expect(Model.rawAttributes).not.to.have.property('updated_at'); - }); - - it('should throw when id is added but not marked as PK', () => { - expect(() => { - current.define('foo', { - id: DataTypes.INTEGER - }); - }).to.throw("A column called 'id' was added to the attributes of 'foos' but not marked with 'primaryKey: true'"); - - expect(() => { - current.define('bar', { - id: { - type: DataTypes.INTEGER - } - }); - }).to.throw("A column called 'id' was added to the attributes of 'bars' but not marked with 'primaryKey: true'"); - }); - - it('should defend against null or undefined "unique" attributes', () => { - expect(() => { - current.define('baz', { - foo: { - type: DataTypes.STRING, - unique: null - }, - bar: { - type: DataTypes.STRING, - unique: undefined - }, - bop: { - type: DataTypes.DATE - } - }); - }).not.to.throw(); - }); - - it('should throw for unknown data type', () => { - expect(() => { - current.define('bar', { - name: { - type: DataTypes.MY_UNKNOWN_TYPE - } - }); - }).to.throw('Unrecognized datatype for attribute "bar.name"'); - }); - - it('should throw for notNull validator without allowNull', () => { - expect(() => { - current.define('user', { - name: { - type: DataTypes.STRING, - allowNull: true, - validate: { - notNull: { - msg: 'Please enter the name' - } - } - } - }); - }).to.throw('Invalid definition for "user.name", "notNull" validator is only allowed with "allowNull:false"'); - - expect(() => { - current.define('part', { - name: { - type: DataTypes.STRING, - validate: { - notNull: { - msg: 'Please enter the part name' - } - } - } - }); - }).to.throw('Invalid definition for "part.name", "notNull" validator is only allowed with "allowNull:false"'); - }); - - describe('datatype warnings', () => { - beforeEach(() => { - sinon.spy(console, 'warn'); - }); - - afterEach(() => { - console.warn.restore(); - }); - - it('warn for unsupported INTEGER options', () => { - current.define('A', { - age: { - type: DataTypes.TINYINT.UNSIGNED - } - }); - - if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - expect(true).to.equal(console.warn.calledOnce); - expect(console.warn.args[0][0]).to.contain("does not support 'TINYINT'"); - } else { - expect(false).to.equal(console.warn.calledOnce); - } - }); - }); - }); -}); diff --git a/test/unit/model/destroy.test.js b/test/unit/model/destroy.test.js deleted file mode 100644 index 8cd1233ffbc8..000000000000 --- a/test/unit/model/destroy.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - - describe('method destroy', () => { - const User = current.define('User', { - name: DataTypes.STRING, - secretValue: DataTypes.INTEGER - }); - - before(function() { - this.stubDelete = sinon.stub(current.getQueryInterface(), 'bulkDelete').resolves([]); - }); - - beforeEach(function() { - this.deloptions = { where: { secretValue: '1' } }; - this.cloneOptions = { ...this.deloptions }; - this.stubDelete.resetHistory(); - }); - - afterEach(function() { - delete this.deloptions; - delete this.cloneOptions; - }); - - after(function() { - this.stubDelete.restore(); - }); - - it('can detect complex objects', async () => { - const Where = function() { this.secretValue = '1'; }; - - 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 deleted file mode 100644 index 0cf1b2e8c17e..000000000000 --- a/test/unit/model/find-and-count-all.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('findAndCountAll', () => { - describe('should handle promise rejection', () => { - before(function() { - this.stub = sinon.stub(); - - process.on('unhandledRejection', this.stub); - - this.User = current.define('User', { - username: DataTypes.STRING, - age: DataTypes.INTEGER - }); - - this.findAll = sinon.stub(this.User, 'findAll').rejects(new Error()); - - this.count = sinon.stub(this.User, 'count').rejects(new Error()); - }); - - after(function() { - this.findAll.resetBehavior(); - this.count.resetBehavior(); - }); - - 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 deleted file mode 100644 index bc1df8cda81f..000000000000 --- a/test/unit/model/find-create-find.test.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - UniqueConstraintError = require('../../../lib/errors').UniqueConstraintError, - current = Support.sequelize, - sinon = require('sinon'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('findCreateFind', () => { - const Model = current.define('Model', {}); - - beforeEach(function() { - this.sinon = sinon.createSandbox(); - }); - - afterEach(function() { - this.sinon.restore(); - }); - - 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); - - await expect(Model.findCreateFind({ - 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', async function() { - const result = {}, - where = { prop: Math.random().toString() }, - createSpy = this.sinon.stub(Model, 'create').resolves(result); - - this.sinon.stub(Model, 'findOne').resolves(null); - - await expect(Model.findCreateFind({ - 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', async function() { - const result = {}, - where = { prop: Math.random().toString() }, - findSpy = this.sinon.stub(Model, 'findOne'); - - this.sinon.stub(Model, 'create').rejects(new UniqueConstraintError()); - - findSpy.onFirstCall().resolves(null); - findSpy.onSecondCall().resolves(result); - - await expect(Model.findCreateFind({ - where - })).to.eventually.eql([result, false]); - - expect(findSpy).to.have.been.calledTwice; - expect(findSpy.getCall(1).args[0].where).to.equal(where); - }); - }); -}); diff --git a/test/unit/model/find-or-create.test.js b/test/unit/model/find-or-create.test.js deleted file mode 100644 index 087273a11a3d..000000000000 --- a/test/unit/model/find-or-create.test.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - cls = require('cls-hooked'), - sinon = require('sinon'), - stub = sinon.stub; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('method findOrCreate', () => { - before(() => { - current.constructor.useCLS(cls.createNamespace('sequelize')); - }); - - after(() => { - cls.destroyNamespace('sequelize'); - delete current.constructor._cls; - }); - - beforeEach(function() { - this.User = current.define('User', {}, { - name: 'John' - }); - - this.transactionStub = stub(this.User.sequelize, 'transaction').rejects(new Error('abort')); - - this.clsStub = stub(current.constructor._cls, 'get').returns({ id: 123 }); - }); - - afterEach(function() { - this.transactionStub.restore(); - this.clsStub.restore(); - }); - - it('should use transaction from cls if available', async function() { - const options = { - where: { - name: 'John' - } - }; - - try { - await this.User.findOrCreate(options); - expect.fail('expected to fail'); - } catch (err) { - if (!/abort/.test(err.message)) throw err; - expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); - } - }); - - it('should not use transaction from cls if provided as argument', async function() { - const options = { - where: { - name: 'John' - }, - transaction: { id: 123 } - }; - - try { - await this.User.findOrCreate(options); - expect.fail('expected to fail'); - } catch (err) { - if (!/abort/.test(err.message)) throw err; - expect(this.clsStub.called).to.equal(false); - } - }); - }); -}); diff --git a/test/unit/model/findall.test.js b/test/unit/model/findall.test.js deleted file mode 100644 index ed01ea620e04..000000000000 --- a/test/unit/model/findall.test.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const current = Support.sequelize; -const sinon = require('sinon'); -const DataTypes = require('../../../lib/data-types'); -const { Logger } = require('../../../lib/utils/logger'); -const sequelizeErrors = require('../../../lib/errors'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('warnOnInvalidOptions', () => { - beforeEach(function() { - this.loggerSpy = sinon.spy(Logger.prototype, 'warn'); - }); - - afterEach(function() { - this.loggerSpy.restore(); - }); - - it('Warns the user if they use a model attribute without a where clause', function() { - const User = current.define('User', { firstName: 'string' }); - User.warnOnInvalidOptions({ firstName: 12, order: [] }, ['firstName']); - const expectedError = 'Model attributes (firstName) passed into finder method options of model User, but the options.where object is empty. Did you forget to use options.where?'; - expect(this.loggerSpy.calledWith(expectedError)).to.equal(true); - }); - - it('Does not warn the user if they use a model attribute without a where clause that shares its name with a query option', function() { - const User = current.define('User', { order: 'string' }); - User.warnOnInvalidOptions({ order: [] }, ['order']); - expect(this.loggerSpy.called).to.equal(false); - }); - - it('Does not warn the user if they use valid query options', function() { - const User = current.define('User', { order: 'string' }); - User.warnOnInvalidOptions({ where: { order: 1 }, order: [] }); - expect(this.loggerSpy.called).to.equal(false); - }); - }); - - describe('method findAll', () => { - const Model = current.define('model', { - name: DataTypes.STRING - }, { timestamps: false }); - - before(function() { - this.stub = sinon.stub(current.getQueryInterface(), 'select').callsFake(() => Model.build({})); - this.warnOnInvalidOptionsStub = sinon.stub(Model, 'warnOnInvalidOptions'); - }); - - beforeEach(function() { - this.stub.resetHistory(); - this.warnOnInvalidOptionsStub.resetHistory(); - }); - - after(function() { - this.stub.restore(); - this.warnOnInvalidOptionsStub.restore(); - }); - - describe('handles input validation', () => { - it('calls warnOnInvalidOptions', function() { - Model.findAll(); - expect(this.warnOnInvalidOptionsStub.calledOnce).to.equal(true); - }); - - 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', async function() { - await Model.findAll({ - attributes: { - include: ['foobar'] - } - }); - - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name', - 'foobar' - ]); - }); - - it('allows me to exclude attributes', async function() { - await Model.findAll({ - attributes: { - exclude: ['name'] - } - }); - - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id' - ]); - }); - - it('include takes precendence over exclude', async function() { - await Model.findAll({ - attributes: { - exclude: ['name'], - include: ['name'] - } - }); - - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name' - ]); - }); - - 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'); - - await Model.findAll({ - attributes: { - include: ['name'] - }, - include: [Foo] - }); - - 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 deleted file mode 100644 index 93ebe7d21080..000000000000 --- a/test/unit/model/findone.test.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = Support.Sequelize, - Op = Sequelize.Op, - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('method findOne', () => { - before(function() { - this.oldFindAll = Sequelize.Model.findAll; - }); - after(function() { - Sequelize.Model.findAll = this.oldFindAll; - }); - - beforeEach(function() { - this.stub = Sequelize.Model.findAll = sinon.stub().resolves(); - }); - - describe('should not add limit when querying on a primary key', () => { - it('with id primary key', async function() { - const Model = current.define('model'); - - 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', async function() { - const Model = current.define('model', { - uid: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - - 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', async function() { - const Model = current.define('model', { - id: { - type: DataTypes.BLOB, - primaryKey: true, - autoIncrement: true - } - }); - - 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', async function() { - const Model = current.define('model'); - - 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', async function() { - const Model = current.define('model', { - unique: { - type: DataTypes.INTEGER, - unique: true - } - }); - - 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', async function() { - const Model = current.define('model', { - unique: { - type: DataTypes.BLOB, - unique: true - } - }); - - 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', async function() { - const Model = current.define('model', { - unique1: { - type: DataTypes.INTEGER, - unique: 'unique' - }, - unique2: { - type: DataTypes.INTEGER, - unique: 'unique' - } - }); - - 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/helpers.test.js b/test/unit/model/helpers.test.js deleted file mode 100644 index 6f13f358e6b1..000000000000 --- a/test/unit/model/helpers.test.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('hasAlias', () => { - beforeEach(function() { - this.User = current.define('user'); - this.Task = current.define('task'); - }); - - it('returns true if a model has an association with the specified alias', function() { - this.Task.belongsTo(this.User, { as: 'owner' }); - expect(this.Task.hasAlias('owner')).to.equal(true); - }); - - it('returns false if a model does not have an association with the specified alias', function() { - this.Task.belongsTo(this.User, { as: 'owner' }); - expect(this.Task.hasAlias('notOwner')).to.equal(false); - }); - }); -}); diff --git a/test/unit/model/include.test.js b/test/unit/model/include.test.js deleted file mode 100644 index 6fc39725f795..000000000000 --- a/test/unit/model/include.test.js +++ /dev/null @@ -1,531 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('all', () => { - const Referral = current.define('referal'); - - Referral.belongsTo(Referral); - - it('can expand nested self-reference', () => { - const options = { include: [{ all: true, nested: true }] }; - - Sequelize.Model._expandIncludeAll.call(Referral, options); - - expect(options.include).to.deep.equal([ - { model: Referral } - ]); - }); - }); - - describe('_validateIncludedElements', () => { - beforeEach(function() { - this.User = this.sequelize.define('User'); - this.Task = this.sequelize.define('Task', { - title: Sequelize.STRING - }); - this.Company = this.sequelize.define('Company', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'field_id' - }, - name: Sequelize.STRING - }); - - this.User.Tasks = this.User.hasMany(this.Task); - this.User.Company = this.User.belongsTo(this.Company); - this.Company.Employees = this.Company.hasMany(this.User); - this.Company.Owner = this.Company.belongsTo(this.User, { as: 'Owner', foreignKey: 'ownerId' }); - }); - - describe('attributes', () => { - it('should not inject the aliased PK again, if it\'s already there', function() { - let options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { - model: this.Company, - attributes: ['name'] - } - ] - }); - - expect(options.include[0].attributes).to.deep.equal([['field_id', 'id'], 'name']); - - options = Sequelize.Model._validateIncludedElements(options); - - // Calling validate again shouldn't add the pk again - expect(options.include[0].attributes).to.deep.equal([['field_id', 'id'], 'name']); - }); - - describe('include / exclude', () => { - it('allows me to include additional attributes', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { - model: this.Company, - attributes: { - include: ['foobar'] - } - } - ] - }); - - expect(options.include[0].attributes).to.deep.equal([ - ['field_id', 'id'], - 'name', - 'createdAt', - 'updatedAt', - 'ownerId', - 'foobar' - ]); - }); - - it('allows me to exclude attributes', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { - model: this.Company, - attributes: { - exclude: ['name'] - } - } - ] - }); - - expect(options.include[0].attributes).to.deep.equal([ - ['field_id', 'id'], - 'createdAt', - 'updatedAt', - 'ownerId' - ]); - }); - - it('include takes precendence over exclude', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { - model: this.Company, - attributes: { - exclude: ['name'], - include: ['name'] - } - } - ] - }); - - expect(options.include[0].attributes).to.deep.equal([ - ['field_id', 'id'], - 'createdAt', - 'updatedAt', - 'ownerId', - 'name' - ]); - }); - }); - }); - - describe('scope', () => { - beforeEach(function() { - this.Project = this.sequelize.define('project', { - bar: { - type: Sequelize.STRING, - field: 'foo' - } - }, { - defaultScope: { - where: { - active: true - } - }, scopes: { - this: { - where: { this: true } - }, - that: { - where: { that: false }, - limit: 12 - }, - attr: { - attributes: ['baz'] - }, - foobar: { - where: { - bar: 42 - } - } - } - }); - - this.User.hasMany(this.Project); - - this.User.hasMany(this.Project.scope('this'), { as: 'thisProject' }); - }); - - it('adds the default scope to where', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ model: this.Project }] - }); - - expect(options.include[0]).to.have.property('where').which.deep.equals({ active: true }); - }); - - it('adds the where from a scoped model', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ model: this.Project.scope('that') }] - }); - - expect(options.include[0]).to.have.property('where').which.deep.equals({ that: false }); - expect(options.include[0]).to.have.property('limit').which.equals(12); - }); - - it('adds the attributes from a scoped model', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ model: this.Project.scope('attr') }] - }); - - expect(options.include[0]).to.have.property('attributes').which.deep.equals(['baz']); - }); - - it('merges where with the where from a scoped model', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ where: { active: false }, model: this.Project.scope('that') }] - }); - - expect(options.include[0]).to.have.property('where').which.deep.equals({ active: false, that: false }); - }); - - it('add the where from a scoped associated model', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ model: this.Project, as: 'thisProject' }] - }); - - expect(options.include[0]).to.have.property('where').which.deep.equals({ this: true }); - }); - - it('handles a scope with an aliased column (.field)', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [{ model: this.Project.scope('foobar') }] - }); - - expect(options.include[0]).to.have.property('where').which.deep.equals({ foo: 42 }); - }); - }); - - describe('duplicating', () => { - it('should tag a hasMany association as duplicating: true if undefined', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - this.User.Tasks - ] - }); - - expect(options.include[0].duplicating).to.equal(true); - }); - - it('should respect include.duplicating for a hasMany', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks, duplicating: false } - ] - }); - - expect(options.include[0].duplicating).to.equal(false); - }); - }); - - describe('_conformInclude', () => { - it('should expand association from string alias', function() { - const options = { - include: ['Owner'] - }; - Sequelize.Model._conformIncludes(options, this.Company); - - expect(options.include[0]).to.deep.equal({ - model: this.User, - association: this.Company.Owner, - as: 'Owner' - }); - }); - - it('should expand string association', function() { - const options = { - include: [{ - association: 'Owner', - attributes: ['id'] - }] - }; - Sequelize.Model._conformIncludes(options, this.Company); - - expect(options.include[0]).to.deep.equal({ - model: this.User, - association: this.Company.Owner, - attributes: ['id'], - as: 'Owner' - }); - }); - - it('should throw an error if invalid model is passed', function() { - const options = { - include: [{ - model: null - }] - }; - - expect(() => { - Sequelize.Model._conformIncludes(options, this.Company); - }).to.throw('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); - - it('should throw an error if invalid association is passed', function() { - const options = { - include: [{ - association: null - }] - }; - - expect(() => { - Sequelize.Model._conformIncludes(options, this.Company); - }).to.throw('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); - }); - - describe('_getIncludedAssociation', () => { - it('returns an association when there is a single unaliased association', function() { - expect(this.User._getIncludedAssociation(this.Task)).to.equal(this.User.Tasks); - }); - - it('returns an association when there is a single aliased association', function() { - const User = this.sequelize.define('User'); - const Task = this.sequelize.define('Task'); - const Tasks = Task.belongsTo(User, { as: 'owner' }); - expect(Task._getIncludedAssociation(User, 'owner')).to.equal(Tasks); - }); - - it('returns an association when there are multiple aliased associations', function() { - expect(this.Company._getIncludedAssociation(this.User, 'Owner')).to.equal(this.Company.Owner); - }); - }); - - describe('subQuery', () => { - it('should be true if theres a duplicating association', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - }); - - it('should be false if theres a duplicating association but no limit', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks } - ], - limit: null - }); - - expect(options.subQuery).to.equal(false); - }); - - it('should be true if theres a nested duplicating association', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, include: [ - this.Company.Employees - ] } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - }); - - it('should be false if theres a nested duplicating association but no limit', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, include: [ - this.Company.Employees - ] } - ], - limit: null - }); - - expect(options.subQuery).to.equal(false); - }); - - it('should tag a required hasMany association', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks, required: true } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - expect(options.include[0].subQuery).to.equal(false); - expect(options.include[0].subQueryFilter).to.equal(true); - }); - - it('should not tag a required hasMany association with duplicating false', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks, required: true, duplicating: false } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(false); - expect(options.include[0].subQuery).to.equal(false); - 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, - include: [ - { association: this.User.Tasks, where: { title: Math.random().toString() } } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - expect(options.include[0].subQuery).to.equal(false); - expect(options.include[0].subQueryFilter).to.equal(true); - }); - - it('should not tag a hasMany association with where and duplicating false', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Tasks, where: { title: Math.random().toString() }, duplicating: false } - ], - limit: 3 - }); - - 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 required belongsTo alongside a duplicating association', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, required: true }, - { association: this.User.Tasks } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - expect(options.include[0].subQuery).to.equal(true); - }); - - it('should not tag a required belongsTo alongside a duplicating association with duplicating false', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, required: true }, - { association: this.User.Tasks, duplicating: false } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(false); - expect(options.include[0].subQuery).to.equal(false); - }); - - it('should tag a belongsTo association with where alongside a duplicating association', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, where: { name: Math.random().toString() } }, - { association: this.User.Tasks } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - expect(options.include[0].subQuery).to.equal(true); - }); - - it('should tag a required belongsTo association alongside a duplicating association with a nested belongsTo', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, required: true, include: [ - this.Company.Owner - ] }, - this.User.Tasks - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(true); - expect(options.include[0].subQuery).to.equal(true); - expect(options.include[0].include[0].subQuery).to.equal(false); - expect(options.include[0].include[0].parent.subQuery).to.equal(true); - }); - - it('should tag a belongsTo association with where alongside a duplicating association with duplicating false', function() { - const options = Sequelize.Model._validateIncludedElements({ - model: this.User, - include: [ - { association: this.User.Company, where: { name: Math.random().toString() } }, - { association: this.User.Tasks, duplicating: false } - ], - limit: 3 - }); - - expect(options.subQuery).to.equal(false); - expect(options.include[0].subQuery).to.equal(false); - }); - }); - }); -}); diff --git a/test/unit/model/indexes.test.js b/test/unit/model/indexes.test.js deleted file mode 100644 index 76d0c1cec2f4..000000000000 --- a/test/unit/model/indexes.test.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('indexes', () => { - it('should automatically set a gin index for JSONB indexes', () => { - const Model = current.define('event', { - eventData: { - type: DataTypes.JSONB, - index: true, - field: 'data' - } - }); - - expect(Model.rawAttributes.eventData.index).not.to.equal(true); - expect(Model._indexes.length).to.equal(1); - expect(Model._indexes[0].fields).to.eql(['data']); - expect(Model._indexes[0].using).to.equal('gin'); - }); - - it('should set the unique property when type is unique', () => { - const Model = current.define('m', {}, { - indexes: [ - { - type: 'unique', - fields: ['name'] - }, - { - type: 'UNIQUE', - fields: ['name'] - } - ] - }); - - expect(Model._indexes[0].unique).to.eql(true); - expect(Model._indexes[1].unique).to.eql(true); - }); - - it('should not set rawAttributes when indexes are defined via options', () => { - const User = current.define('User', { - username: DataTypes.STRING - }, { - indexes: [{ - unique: true, - fields: ['username'] - }] - }); - - expect(User.rawAttributes.username.unique).to.be.undefined; - }); - - it('should not set rawAttributes when composite unique indexes are defined via options', () => { - const User = current.define('User', { - name: DataTypes.STRING, - address: DataTypes.STRING - }, { - indexes: [{ - unique: 'users_name_address', - fields: ['name', 'address'] - }] - }); - - expect(User.rawAttributes.name.unique).to.be.undefined; - expect(User.rawAttributes.address.unique).to.be.undefined; - }); - }); -}); diff --git a/test/unit/model/overwriting-builtins.test.js b/test/unit/model/overwriting-builtins.test.js deleted file mode 100644 index c547bb9b09a9..000000000000 --- a/test/unit/model/overwriting-builtins.test.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - - describe('not breaking built-ins', () => { - it('it should not break instance.set by defining a model set attribute', function() { - const User = this.sequelize.define('OverWrittenKeys', { - set: DataTypes.STRING - }); - - const user = User.build({ set: 'A' }); - expect(user.get('set')).to.equal('A'); - user.set('set', 'B'); - expect(user.get('set')).to.equal('B'); - }); - }); -}); diff --git a/test/unit/model/removeAttribute.test.js b/test/unit/model/removeAttribute.test.js deleted file mode 100644 index 918f834ccd6f..000000000000 --- a/test/unit/model/removeAttribute.test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - _ = require('lodash'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('removeAttribute', () => { - it('should support removing the primary key', () => { - const Model = current.define('m', { - name: DataTypes.STRING - }); - - expect(Model.primaryKeyAttribute).not.to.be.undefined; - expect(_.size(Model.primaryKeys)).to.equal(1); - - Model.removeAttribute('id'); - - expect(Model.primaryKeyAttribute).to.be.undefined; - expect(_.size(Model.primaryKeys)).to.equal(0); - }); - - it('should not add undefined attribute after removing primary key', () => { - const Model = current.define('m', { - name: DataTypes.STRING - }); - - Model.removeAttribute('id'); - - const instance = Model.build(); - expect(instance.dataValues).not.to.include.keys('undefined'); - }); - }); -}); diff --git a/test/unit/model/schema.test.js b/test/unit/model/schema.test.js deleted file mode 100644 index 4ab70eef9463..000000000000 --- a/test/unit/model/schema.test.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize; - -describe(`${Support.getTestDialectTeaser('Model')}Schemas`, () => { - if (current.dialect.supports.schemas) { - const Project = current.define('project'), - Company = current.define('company', {}, { - schema: 'default', - schemaDelimiter: '&' - }); - - describe('schema', () => { - it('should work with no default schema', () => { - expect(Project._schema).to.be.null; - }); - - it('should apply default schema from define', () => { - expect(Company._schema).to.equal('default'); - }); - - it('should be able to override the default schema', () => { - expect(Company.schema('newSchema')._schema).to.equal('newSchema'); - }); - - it('should be able nullify schema', () => { - expect(Company.schema(null)._schema).to.be.null; - }); - - it('should support multiple, coexistent schema models', () => { - const schema1 = Company.schema('schema1'), - schema2 = Company.schema('schema1'); - - expect(schema1._schema).to.equal('schema1'); - expect(schema2._schema).to.equal('schema1'); - }); - }); - - describe('schema delimiter', () => { - it('should work with no default schema delimiter', () => { - expect(Project._schemaDelimiter).to.equal(''); - }); - - it('should apply default schema delimiter from define', () => { - expect(Company._schemaDelimiter).to.equal('&'); - }); - - it('should be able to override the default schema delimiter', () => { - expect(Company.schema(Company._schema, '^')._schemaDelimiter).to.equal('^'); - }); - - it('should support multiple, coexistent schema delimiter models', () => { - const schema1 = Company.schema(Company._schema, '$'), - schema2 = Company.schema(Company._schema, '#'); - - expect(schema1._schemaDelimiter).to.equal('$'); - expect(schema2._schemaDelimiter).to.equal('#'); - }); - }); - } -}); diff --git a/test/unit/model/scope.test.js b/test/unit/model/scope.test.js deleted file mode 100644 index c479cc19de7e..000000000000 --- a/test/unit/model/scope.test.js +++ /dev/null @@ -1,540 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../../index'), - Op = Sequelize.Op, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - current = Support.sequelize; - -describe(Support.getTestDialectTeaser('Model'), () => { - const Project = current.define('project'), - User = current.define('user'); - - const scopes = { - complexFunction(value) { - return { - where: [`${value} IN (SELECT foobar FROM some_sql_function(foo.bar))`] - }; - }, - somethingTrue: { - where: { - something: true, - somethingElse: 42 - }, - limit: 5 - }, - somethingFalse: { - where: { - something: false - } - }, - sequelize_where: { - where: Sequelize.where() - }, - users: { - include: [ - { model: User } - ] - }, - alsoUsers: { - include: [ - { model: User, where: { something: 42 } } - ] - }, - projects: { - include: [Project] - }, - groupByCompanyId: { - group: ['company.id'] - }, - groupByProjectId: { - group: ['project.id'], - include: [Project] - }, - noArgs() { - // This does not make much sense, since it does not actually need to be in a function, - // In reality it could be used to do for example new Date or random in the scope - but we want it deterministic - - return { - where: { - other_value: 7 - } - }; - }, - actualValue(value) { - return { - where: { - other_value: value - } - }; - } - }; - - const Company = current.define('company', {}, { - defaultScope: { - include: [Project], - where: { active: true } - }, - scopes - }); - - describe('.scope', () => { - describe('attribute exclude / include', () => { - const User = current.define('user', { - password: DataTypes.STRING, - value: DataTypes.INTEGER, - name: DataTypes.STRING - }, { - defaultScope: { - attributes: { - exclude: ['password'] - } - }, - scopes: { - aScope: { - attributes: { - exclude: ['value'] - } - } - } - }); - - it('should not expand attributes', () => { - expect(User._scope.attributes).to.deep.equal({ exclude: ['password'] }); - }); - - it('should not expand attributes', () => { - expect(User.scope('aScope')._scope.attributes).to.deep.equal({ exclude: ['value'] }); - }); - - it('should unite attributes with array', () => { - expect(User.scope('aScope', 'defaultScope')._scope.attributes).to.deep.equal({ exclude: ['value', 'password'] }); - }); - - it('should not modify the original scopes when merging them', () => { - expect(User.scope('defaultScope', 'aScope').options.defaultScope.attributes).to.deep.equal({ exclude: ['password'] }); - }); - }); - - it('defaultScope should be an empty object if not overridden', () => { - const Foo = current.define('foo', {}, {}); - - expect(Foo.scope('defaultScope')._scope).to.deep.equal({}); - }); - - it('should apply default scope', () => { - expect(Company._scope).to.deep.equal({ - include: [Project], - where: { active: true } - }); - }); - - it('should be able to unscope', () => { - expect(Company.scope(null)._scope).to.be.empty; - expect(Company.unscoped()._scope).to.be.empty; - // Yes, being unscoped is also a scope - this prevents inject defaultScope, when including a scoped model, see #4663 - expect(Company.unscoped().scoped).to.be.ok; - }); - - it('should be able to merge scopes', () => { - expect(Company.scope('somethingTrue', 'somethingFalse', 'sequelize_where')._scope).to.deep.equal({ - where: { - something: false, - somethingElse: 42, - [Op.and]: Sequelize.where() - }, - limit: 5 - }); - }); - - it('should support multiple, coexistent scoped models', () => { - const scoped1 = Company.scope('somethingTrue'), - scoped2 = Company.scope('somethingFalse'); - - expect(scoped1._scope).to.deep.equal(scopes.somethingTrue); - expect(scoped2._scope).to.deep.equal(scopes.somethingFalse); - }); - - it('should work with function scopes', () => { - expect(Company.scope({ method: ['actualValue', 11] })._scope).to.deep.equal({ - where: { - other_value: 11 - } - }); - - expect(Company.scope('noArgs')._scope).to.deep.equal({ - where: { - other_value: 7 - } - }); - }); - - it('should work with consecutive function scopes', () => { - const scope = { method: ['actualValue', 11] }; - expect(Company.scope(scope)._scope).to.deep.equal({ - where: { - other_value: 11 - } - }); - - expect(Company.scope(scope)._scope).to.deep.equal({ - where: { - other_value: 11 - } - }); - }); - - it('should be able to check default scope name', () => { - expect(Company._scopeNames).to.include('defaultScope'); - }); - - it('should be able to check custom scope name', () => { - expect(Company.scope('users')._scopeNames).to.include('users'); - }); - - it('should be able to check multiple custom scope names', () => { - expect(Company.scope('users', 'projects')._scopeNames).to.include.members(['users', 'projects']); - }); - - it('should be able to merge two scoped includes', () => { - expect(Company.scope('users', 'projects')._scope).to.deep.equal({ - include: [ - { model: User }, - { model: Project } - ] - }); - }); - - 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); - }); - - it('should be able to combine default with another scope', () => { - expect(Company.scope(['defaultScope', { method: ['actualValue', 11] }])._scope).to.deep.equal({ - include: [{ model: Project }], - where: { - active: true, - other_value: 11 - } - }); - }); - - it('should be able to use raw queries', () => { - expect(Company.scope([{ method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({ - where: ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'] - }); - }); - - it('should override the default scope', () => { - expect(Company.scope(['defaultScope', { method: ['complexFunction', 'qux'] }])._scope).to.deep.equal({ - include: [{ model: Project }], - where: ['qux IN (SELECT foobar FROM some_sql_function(foo.bar))'] - }); - }); - - it("should emit an error for scopes that don't exist", () => { - expect(() => { - Company.scope('doesntexist'); - }).to.throw('Invalid scope doesntexist called.'); - }); - - it('should concatenate scope groups', () => { - expect(Company.scope('groupByCompanyId', 'groupByProjectId')._scope).to.deep.equal({ - group: ['company.id', 'project.id'], - include: [{ model: Project }] - }); - }); - }); - - describe('addScope', () => { - it('works if the model does not have any initial scopes', () => { - const Model = current.define('model'); - - expect(() => { - Model.addScope('anything', {}); - }).not.to.throw(); - }); - - it('allows me to add a new scope', () => { - expect(() => { - Company.scope('newScope'); - }).to.throw('Invalid scope newScope called.'); - - Company.addScope('newScope', { - where: { - this: 'that' - }, - include: [{ model: Project }] - }); - - expect(Company.scope('newScope')._scope).to.deep.equal({ - where: { this: 'that' }, - include: [{ model: Project }] - }); - }); - - it('warns me when overriding an existing scope', () => { - expect(() => { - Company.addScope('somethingTrue', {}); - }).to.throw('The scope somethingTrue already exists. Pass { override: true } as options to silence this error'); - }); - - it('allows me to override an existing scope', () => { - Company.addScope('somethingTrue', { - where: { - something: false - } - }, { override: true }); - - expect(Company.scope('somethingTrue')._scope).to.deep.equal({ - where: { something: false } - }); - }); - - it('warns me when overriding an existing default scope', () => { - expect(() => { - Company.addScope('defaultScope', {}); - }).to.throw('The scope defaultScope already exists. Pass { override: true } as options to silence this error'); - }); - - it('should not warn if default scope is not defined', () => { - const Model = current.define('model'); - - expect(() => { - Model.addScope('defaultScope', {}); - }).not.to.throw(); - }); - - it('allows me to override a default scope', () => { - Company.addScope('defaultScope', { - include: [{ model: Project }] - }, { override: true }); - - expect(Company._scope).to.deep.equal({ - include: [{ model: Project }] - }); - }); - - it('should keep exclude and include attributes', () => { - Company.addScope('newIncludeScope', { - attributes: { - include: ['foobar'], - exclude: ['createdAt'] - } - }); - - expect(Company.scope('newIncludeScope')._scope).to.deep.equal({ - attributes: { - include: ['foobar'], - exclude: ['createdAt'] - } - }); - }); - - it('should be able to merge scopes with the same include', () => { - Company.addScope('project', { - include: [{ model: Project, where: { something: false, somethingElse: 99 } }] - }); - Company.addScope('alsoProject', { - include: [{ model: Project, where: { something: true }, limit: 1 }] - }); - expect(Company.scope(['project', 'alsoProject'])._scope).to.deep.equal({ - include: [{ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }] - }); - }); - }); - - describe('_injectScope', () => { - it('should be able to merge scope and where', () => { - Sequelize.Model._scope = { - where: { - something: true, - somethingElse: 42 - }, - limit: 15, - offset: 3 - }; - - const options = { - where: { - something: false - }, - limit: 9 - }; - - Sequelize.Model._injectScope(options); - - expect(options).to.deep.equal({ - where: { - something: false, - somethingElse: 42 - }, - limit: 9, - offset: 3 - }); - }); - - it('should be able to merge scope and having', () => { - Sequelize.Model._scope = { - having: { - something: true, - somethingElse: 42 - }, - limit: 15, - offset: 3 - }; - - const options = { - having: { - something: false - }, - limit: 9 - }; - - Sequelize.Model._injectScope(options); - - expect(options).to.deep.equal({ - having: { - something: false, - somethingElse: 42 - }, - limit: 9, - offset: 3 - }); - }); - - - it('should be able to merge scopes with the same include', () => { - Sequelize.Model._scope = { - include: [ - { model: Project, where: { something: false, somethingElse: 99 } }, - { model: Project, where: { something: true }, limit: 1 } - ] - }; - - const options = {}; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(1); - expect(options.include[0]).to.deep.equal({ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }); - }); - - it('should be able to merge scoped include', () => { - Sequelize.Model._scope = { - include: [{ model: Project, where: { something: false, somethingElse: 99 } }] - }; - - const options = { - include: [{ model: Project, where: { something: true }, limit: 1 }] - }; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(1); - expect(options.include[0]).to.deep.equal({ model: Project, where: { something: true, somethingElse: 99 }, limit: 1 }); - }); - - it('should be able to merge aliased includes with the same model', () => { - Sequelize.Model._scope = { - include: [{ model: User, as: 'someUser' }] - }; - - const options = { - include: [{ model: User, as: 'otherUser' }] - }; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(2); - expect(options.include[0]).to.deep.equal({ model: User, as: 'someUser' }); - expect(options.include[1]).to.deep.equal({ model: User, as: 'otherUser' }); - }); - - it('should be able to merge scoped include with include in find', () => { - Sequelize.Model._scope = { - include: [ - { model: Project, where: { something: false } } - ] - }; - - const options = { - include: [ - { model: User, where: { something: true } } - ] - }; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(2); - expect(options.include[0]).to.deep.equal({ model: Project, where: { something: false } }); - expect(options.include[1]).to.deep.equal({ model: User, where: { something: true } }); - }); - - describe('include all', () => { - it('scope with all', () => { - Sequelize.Model._scope = { - include: [ - { all: true } - ] - }; - - const options = { - include: [ - { model: User, where: { something: true } } - ] - }; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(2); - expect(options.include[0]).to.deep.equal({ all: true }); - expect(options.include[1]).to.deep.equal({ model: User, where: { something: true } }); - }); - - - it('options with all', () => { - Sequelize.Model._scope = { - include: [ - { model: User, where: { something: true } } - ] - }; - - const options = { - include: [ - { all: true } - ] - }; - - Sequelize.Model._injectScope(options); - - expect(options.include).to.have.length(2); - expect(options.include[0]).to.deep.equal({ model: User, where: { something: true } }); - expect(options.include[1]).to.deep.equal({ all: true }); - }); - }); - }); -}); diff --git a/test/unit/model/underscored.test.js b/test/unit/model/underscored.test.js deleted file mode 100644 index f9c53dcac9fd..000000000000 --- a/test/unit/model/underscored.test.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('options.underscored', () => { - beforeEach(function() { - this.N = this.sequelize.define('N', { - id: { - type: DataTypes.CHAR(10), - primaryKey: true, - field: 'n_id' - } - }, { - underscored: true - }); - - this.M = this.sequelize.define('M', { - id: { - type: Sequelize.CHAR(20), - primaryKey: true, - field: 'm_id' - } - }, { - underscored: true - }); - this.NM = this.sequelize.define('NM', {}); - }); - - it('should properly set field when defining', function() { - expect(this.N.rawAttributes['id'].field).to.equal('n_id'); - expect(this.M.rawAttributes['id'].field).to.equal('m_id'); - }); - - it('hasOne does not override already defined field', function() { - this.N.rawAttributes['mId'] = { - type: Sequelize.CHAR(20), - field: 'n_m_id' - }; - this.N.refreshAttributes(); - - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - this.M.hasOne(this.N, { foreignKey: 'mId' }); - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - }); - - it('belongsTo does not override already defined field', function() { - this.N.rawAttributes['mId'] = { - type: Sequelize.CHAR(20), - field: 'n_m_id' - }; - this.N.refreshAttributes(); - - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - this.N.belongsTo(this.M, { foreignKey: 'mId' }); - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - }); - - it('hasOne/belongsTo does not override already defined field', function() { - this.N.rawAttributes['mId'] = { - type: Sequelize.CHAR(20), - field: 'n_m_id' - }; - this.N.refreshAttributes(); - - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - this.N.belongsTo(this.M, { foreignKey: 'mId' }); - this.M.hasOne(this.N, { foreignKey: 'mId' }); - expect(this.N.rawAttributes['mId'].field).to.equal('n_m_id'); - }); - - it('hasMany does not override already defined field', function() { - this.M.rawAttributes['nId'] = { - type: Sequelize.CHAR(20), - field: 'nana_id' - }; - this.M.refreshAttributes(); - - expect(this.M.rawAttributes['nId'].field).to.equal('nana_id'); - - this.N.hasMany(this.M, { foreignKey: 'nId' }); - this.M.belongsTo(this.N, { foreignKey: 'nId' }); - - expect(this.M.rawAttributes['nId'].field).to.equal('nana_id'); - }); - - it('belongsToMany does not override already defined field', function() { - this.NM = this.sequelize.define('NM', { - n_id: { - type: Sequelize.CHAR(10), - field: 'nana_id' - }, - m_id: { - type: Sequelize.CHAR(20), - field: 'mama_id' - } - }, { - underscored: true - }); - - this.N.belongsToMany(this.M, { through: this.NM, foreignKey: 'n_id' }); - this.M.belongsToMany(this.N, { through: this.NM, foreignKey: 'm_id' }); - - expect(this.NM.rawAttributes['n_id'].field).to.equal('nana_id'); - expect(this.NM.rawAttributes['m_id'].field).to.equal('mama_id'); - }); - }); -}); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js deleted file mode 100644 index 042b53faaa7e..000000000000 --- a/test/unit/model/update.test.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - describe('method update', () => { - before(function() { - this.User = current.define('User', { - name: DataTypes.STRING, - secretValue: DataTypes.INTEGER - }); - }); - - beforeEach(function() { - this.stubUpdate = sinon.stub(current.getQueryInterface(), 'bulkUpdate').resolves([]); - this.updates = { name: 'Batman', secretValue: '7' }; - this.cloneUpdates = { ...this.updates }; - }); - - afterEach(function() { - this.stubUpdate.restore(); - }); - - afterEach(function() { - delete this.updates; - delete this.cloneUpdates; - }); - - describe('properly clones input values', () => { - 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', 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', async function() { - const Where = function() { this.secretValue = '1'; }; - - 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 deleted file mode 100644 index 85e42bbd8784..000000000000 --- a/test/unit/model/upsert.test.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Sequelize = require('../../../index'), - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'); - -describe(Support.getTestDialectTeaser('Model'), () => { - if (current.dialect.supports.upserts) { - describe('method upsert', () => { - before(function() { - this.User = current.define('User', { - name: DataTypes.STRING, - virtualValue: { - type: DataTypes.VIRTUAL, - set(val) { - return this.value = val; - }, - get() { - return this.value; - } - }, - value: DataTypes.STRING, - secretValue: { - type: DataTypes.INTEGER, - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at' - } - }); - - this.UserNoTime = current.define('UserNoTime', { - name: DataTypes.STRING - }, { - timestamps: false - }); - }); - - beforeEach(function() { - this.query = sinon.stub(current, 'query').resolves(); - this.stub = sinon.stub(current.getQueryInterface(), 'upsert').resolves([this.User.build(), true]); - }); - - afterEach(function() { - this.query.restore(); - this.stub.restore(); - }); - - 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', async function() { - await this.User - .upsert({ - name: 'Young Cat', - virtualValue: 999 - }); - - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name', 'value', 'created_at', 'updatedAt' - ]); - }); - - it('creates new record with timestamps disabled', async function() { - await this.UserNoTime - .upsert({ - name: 'Young Cat' - }); - - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name' - ]); - }); - - it('updates all changed fields by default', async function() { - await this.User - .upsert({ - name: 'Old Cat', - virtualValue: 111 - }); - - 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 deleted file mode 100644 index 56942e7f281e..000000000000 --- a/test/unit/model/validation.test.js +++ /dev/null @@ -1,745 +0,0 @@ -'use strict'; - -const chai = require('chai'), - sinon = require('sinon'), - expect = chai.expect, - Sequelize = require('../../../index'), - Op = Sequelize.Op, - Support = require('../support'), - current = Support.sequelize; - - -describe(Support.getTestDialectTeaser('InstanceValidator'), () => { - describe('validations', () => { - const checks = { - is: { - spec: { args: ['[a-z]', 'i'] }, - fail: '0', - pass: 'a' - }, - not: { - spec: { args: ['[a-z]', 'i'] }, - fail: 'a', - pass: '0' - }, - isEmail: { - fail: 'a', - pass: 'abc@abc.com' - }, - isUrl: { - fail: 'abc', - pass: 'http://abc.com' - }, - isIP: { - fail: 'abc', - pass: '129.89.23.1' - }, - isIPv4: { - fail: 'abc', - pass: '129.89.23.1' - }, - isIPv6: { - fail: '1111:2222:3333::5555:', - pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156' - }, - isAlpha: { - stringOrBoolean: true, - spec: { args: 'en-GB' }, - fail: '012', - pass: 'abc' - }, - isAlphanumeric: { - stringOrBoolean: true, - spec: { args: 'en-GB' }, - fail: '_abc019', - pass: 'abc019' - }, - isNumeric: { - fail: 'abc', - pass: '019' - }, - isInt: { - fail: '9.2', - pass: '-9' - }, - isLowercase: { - fail: 'AB', - pass: 'ab' - }, - isUppercase: { - fail: 'ab', - pass: 'AB' - }, - isDecimal: { - fail: 'a', - pass: '0.2' - }, - isFloat: { - fail: 'a', - pass: '9.2' - }, - isNull: { - fail: 0, - pass: null - }, - notEmpty: { - fail: ' ', - pass: 'a' - }, - equals: { - spec: { args: 'bla bla bla' }, - fail: 'bla', - pass: 'bla bla bla' - }, - contains: { - spec: { args: 'bla' }, - fail: 'la', - pass: '0bla23' - }, - notContains: { - spec: { args: 'bla' }, - fail: '0bla23', - pass: 'la' - }, - regex: { - spec: { args: ['[a-z]', 'i'] }, - fail: '0', - pass: 'a' - }, - notRegex: { - spec: { args: ['[a-z]', 'i'] }, - fail: 'a', - pass: '0' - }, - len: { - spec: { args: [2, 4] }, - fail: ['1', '12345'], - pass: ['12', '123', '1234'], - raw: true - }, - len$: { - spec: [2, 4], - fail: ['1', '12345'], - pass: ['12', '123', '1234'], - raw: true - }, - isUUID: { - spec: { args: 4 }, - fail: 'f47ac10b-58cc-3372-a567-0e02b2c3d479', - pass: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' - }, - isDate: { - fail: 'not a date', - pass: '2011-02-04' - }, - isAfter: { - spec: { args: '2011-11-05' }, - fail: '2011-11-04', - pass: '2011-11-06' - }, - isBefore: { - spec: { args: '2011-11-05' }, - fail: '2011-11-06', - pass: '2011-11-04' - }, - isIn: { - spec: { args: 'abcdefghijk' }, - fail: 'ghik', - pass: 'ghij' - }, - notIn: { - spec: { args: 'abcdefghijk' }, - fail: 'ghij', - pass: 'ghik' - }, - max: { - spec: { args: 23 }, - fail: '24', - pass: '23' - }, - max$: { - spec: 23, - fail: '24', - pass: '23' - }, - min: { - spec: { args: 23 }, - fail: '22', - pass: '23' - }, - min$: { - spec: 23, - fail: '22', - pass: '23' - }, - isCreditCard: { - fail: '401288888888188f', - pass: '4012888888881881' - } - }; - - 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}"`, async function() { - const validations = {}, - message = `${validator}(${failingValue})`; - - validations[validator] = validatorDetails.spec || {}; - validations[validator].msg = message; - - const UserFail = this.sequelize.define(`User${Support.rand()}`, { - name: { - type: Sequelize.STRING, - validate: validations - } - }); - - const failingUser = UserFail.build({ name: 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}"`, async function() { - const validations = {}, - message = `${validator}(${succeedingValue})`; - - validations[validator] = validatorDetails.spec || {}; - - if (type === 'msg') { - validations[validator].msg = message; - } else if (type === 'args') { - validations[validator].args = validations[validator].args || true; - validations[validator].msg = message; - } else if (type === 'true') { - validations[validator] = true; - } - - const UserSuccess = this.sequelize.define(`User${Support.rand()}`, { - name: { - type: Sequelize.STRING, - validate: validations - } - }); - const successfulUser = UserSuccess.build({ name: succeedingValue }); - await expect(successfulUser.validate()).not.to.be.rejected; - }); - }; - - for (let validator in checks) { - if (checks.hasOwnProperty(validator)) { - validator = validator.replace(/\$$/, ''); - const validatorDetails = checks[validator]; - - if (!validatorDetails.raw) { - validatorDetails.fail = Array.isArray(validatorDetails.fail) ? validatorDetails.fail : [validatorDetails.fail]; - validatorDetails.pass = Array.isArray(validatorDetails.pass) ? validatorDetails.pass : [validatorDetails.pass]; - } - - for (let i = 0; i < validatorDetails.fail.length; i++) { - applyFailTest(validatorDetails, i, validator); - } - - for (let i = 0; i < validatorDetails.pass.length; i++) { - applyPassTest(validatorDetails, i, validator); - applyPassTest(validatorDetails, i, validator, 'msg'); - applyPassTest(validatorDetails, i, validator, 'args'); - if (validatorDetails.stringOrBoolean || validatorDetails.spec === undefined) { - applyPassTest(validatorDetails, i, validator, 'true'); - } - } - } - } - }); - - describe('datatype validations', () => { - const current = Support.createSequelizeInstance({ - typeValidation: true - }); - - const User = current.define('user', { - age: Sequelize.INTEGER, - name: Sequelize.STRING, - awesome: Sequelize.BOOLEAN, - number: Sequelize.DECIMAL, - uid: Sequelize.UUID, - date: Sequelize.DATE - }); - - before(function() { - this.stub = sinon.stub(current, 'query').callsFake(async () => Promise.resolve([User.build({}), 1])); - }); - - after(function() { - this.stub.restore(); - }); - - describe('should not throw', () => { - describe('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', async () => { - await expect(User.create({ - number: '12.6' - })).not.to.be.rejected; - }); - - 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', async () => { - await expect(User.create({ - number: '2321312301230128391820831289123012' - })).not.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', async () => { - await expect(User.create({ - name: 12 - })).not.to.be.rejected; - }); - - 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', async () => { - await expect(User.create({ - awesome: '1' - })).not.to.be.rejected; - }); - - 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', async () => { - await expect(User.findAll({ - where: { - name: { - [Op.like]: { - [Op.any]: ['foo%', 'bar%'] - } - } - } - })).not.to.be.rejected; - }); - - it('should allow $like for uuid', async () => { - await expect(User.findAll({ - where: { - uid: { - [Op.like]: '12345678%' - } - } - })).not.to.be.rejected; - }); - }); - }); - - describe('should throw validationerror', () => { - - describe('create', () => { - it('should throw when passing string', async () => { - await expect(User.create({ - age: 'jan' - })).to.be.rejectedWith(Sequelize.ValidationError) - .which.eventually.have.property('errors') - .that.is.an('array') - .with.lengthOf(1) - .and.with.property(0) - .that.is.an.instanceOf(Sequelize.ValidationErrorItem) - .and.include({ - type: 'Validation error', - path: 'age', - value: 'jan', - instance: null, - validatorKey: 'INTEGER validator' - }); - }); - - it('should throw when passing decimal', async () => { - await expect(User.create({ - age: 4.5 - })).to.be.rejectedWith(Sequelize.ValidationError) - .which.eventually.have.property('errors') - .that.is.an('array') - .with.lengthOf(1) - .and.with.property(0) - .that.is.an.instanceOf(Sequelize.ValidationErrorItem) - .and.include({ - type: 'Validation error', - path: 'age', - value: 4.5, - instance: null, - validatorKey: 'INTEGER validator' - }); - }); - }); - - describe('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') - .that.is.an('array') - .with.lengthOf(1) - .and.with.property(0) - .that.is.an.instanceOf(Sequelize.ValidationErrorItem) - .and.include({ - type: 'Validation error', - path: 'age', - value: 'jan', - instance: null, - validatorKey: 'INTEGER validator' - }); - }); - - 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') - .that.is.an('array') - .with.lengthOf(1) - .and.with.property(0) - .that.is.an.instanceOf(Sequelize.ValidationErrorItem) - .and.include({ - type: 'Validation error', - path: 'age', - value: 4.5, - instance: null, - validatorKey: 'INTEGER validator' - }); - }); - }); - - }); - }); - - describe('custom validation functions', () => { - - const User = current.define('user', { - age: { - type: Sequelize.INTEGER, - validate: { - customFn(val, next) { - if (val < 0) { - next('age must be greater or equal zero'); - } else { - next(); - } - } - } - }, - name: Sequelize.STRING - }, { - validate: { - customFn() { - if (this.get('name') === 'error') { - throw new Error('Error from model validation promise'); - } - } - } - }); - - before(function() { - this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); - }); - - after(function() { - this.stub.restore(); - }); - - describe('should not throw', () => { - describe('create', () => { - it('custom validation functions are successful', async () => { - await expect(User.create({ - age: 1, - name: 'noerror' - })).not.to.be.rejected; - }); - }); - - describe('update', () => { - it('custom validation functions are successful', async () => { - await expect(User.update({ - age: 1, - name: 'noerror' - }, { where: {} })).not.to.be.rejected; - }); - }); - }); - - describe('should throw validationerror', () => { - - describe('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', async () => { - await expect(User.create({ - name: 'error' - })).to.be.rejectedWith(Sequelize.ValidationError); - }); - }); - - describe('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', async () => { - await expect(User.update({ - name: 'error' - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); - }); - }); - }); - }); - - describe('custom validation functions returning promises', () => { - - const User = current.define('user', { - name: Sequelize.STRING - }, { - validate: { - async customFn() { - if (this.get('name') === 'error') { - throw new Error('Error from model validation promise'); - } - } - } - }); - - before(function() { - this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); - }); - - after(function() { - this.stub.restore(); - }); - - describe('should not throw', () => { - describe('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', async () => { - await expect(User.update({ - name: 'noerror' - }, { where: {} })).not.to.be.rejected; - }); - }); - }); - - describe('should throw validationerror', () => { - - describe('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', async () => { - await expect(User.update({ - name: 'error' - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); - }); - }); - }); - }); - - describe('custom validation functions and null values', () => { - - before(function() { - this.customValidator = sinon.fake(function(value) { - if (value === null && this.age !== 10) { - throw new Error("name can't be null unless age is 10"); - } - }); - }); - - describe('with allowNull set to true', () => { - - before(function() { - this.User = current.define('user', { - age: Sequelize.INTEGER, - name: { - type: Sequelize.STRING, - allowNull: true, - validate: { - customValidator: this.customValidator - } - } - }); - - this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); - }); - - after(function() { - this.stub.restore(); - }); - - describe('should call validator and not throw', () => { - beforeEach(function() { - this.customValidator.resetHistory(); - }); - - it('on create', async function() { - await expect(this.User.create({ - age: 10, - name: null - })).not.to.be.rejected; - - await expect(this.customValidator).to.have.been.calledOnce; - }); - it('on update', async function() { - await expect(this.User.update({ - age: 10, - name: null - }, { where: {} })).not.to.be.rejected; - - await expect(this.customValidator).to.have.been.calledOnce; - }); - }); - - describe('should call validator and throw ValidationError', () => { - beforeEach(function() { - this.customValidator.resetHistory(); - }); - - it('on create', async function() { - await expect(this.User.create({ - age: 11, - name: null - })).to.be.rejectedWith(Sequelize.ValidationError); - - await expect(this.customValidator).to.have.been.calledOnce; - }); - it('on update', async function() { - await expect(this.User.update({ - age: 11, - name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); - - await expect(this.customValidator).to.have.been.calledOnce; - }); - }); - - }); - - describe('with allowNull set to false', () => { - - before(function() { - this.User = current.define('user', { - age: Sequelize.INTEGER, - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - customValidator: this.customValidator - } - } - }); - - this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); - }); - - after(function() { - this.stub.restore(); - }); - - describe('should not call validator and throw ValidationError', () => { - beforeEach(function() { - this.customValidator.resetHistory(); - }); - - it('on create', async function() { - await expect(this.User.create({ - age: 99, - name: null - })).to.be.rejectedWith(Sequelize.ValidationError); - - await expect(this.customValidator).to.have.not.been.called; - }); - it('on update', async function() { - await expect(this.User.update({ - age: 99, - name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); - - await expect(this.customValidator).to.have.not.been.called; - }); - }); - - describe('should call validator and not throw', () => { - beforeEach(function() { - this.customValidator.resetHistory(); - }); - - it('on create', async function() { - await expect(this.User.create({ - age: 99, - name: 'foo' - })).not.to.be.rejected; - - await expect(this.customValidator).to.have.been.calledOnce; - }); - it('on update', async function() { - await expect(this.User.update({ - age: 99, - name: 'foo' - }, { where: {} })).not.to.be.rejected; - - await expect(this.customValidator).to.have.been.calledOnce; - }); - }); - - }); - - }); - -}); diff --git a/test/unit/sql/add-column.test.js b/test/unit/sql/add-column.test.js deleted file mode 100644 index b4179ed427d4..000000000000 --- a/test/unit/sql/add-column.test.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize, - sql = current.dialect.queryGenerator; - - -if (['mysql', 'mariadb'].includes(current.dialect.name)) { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('addColumn', () => { - - const Model = current.define('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }, { timestamps: false }); - - it('properly generate alter queries', () => { - return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ - type: DataTypes.FLOAT, - allowNull: false - })), { - mariadb: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;', - mysql: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;' - }); - }); - - it('properly generate alter queries for foreign keys', () => { - return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - })), { - mariadb: 'ALTER TABLE `users` ADD `level_id` INTEGER, ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', - mysql: 'ALTER TABLE `users` ADD `level_id` INTEGER, ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;' - }); - }); - - it('properly generate alter queries with FIRST', () => { - return expectsql(sql.addColumnQuery(Model.getTableName(), 'test_added_col_first', current.normalizeAttribute({ - type: DataTypes.STRING, - first: true - })), { - mariadb: 'ALTER TABLE `users` ADD `test_added_col_first` VARCHAR(255) FIRST;', - mysql: 'ALTER TABLE `users` ADD `test_added_col_first` VARCHAR(255) FIRST;' - }); - }); - - it('properly generates alter queries with column level comment', () => { - return expectsql(sql.addColumnQuery(Model.getTableName(), 'column_with_comment', current.normalizeAttribute({ - type: DataTypes.STRING, - comment: 'This is a comment' - })), { - mariadb: 'ALTER TABLE `users` ADD `column_with_comment` VARCHAR(255) COMMENT \'This is a comment\';', - mysql: 'ALTER TABLE `users` ADD `column_with_comment` VARCHAR(255) COMMENT \'This is a comment\';' - }); - }); - }); - }); -} diff --git a/test/unit/sql/add-constraint.test.js b/test/unit/sql/add-constraint.test.js deleted file mode 100644 index 239d8d7052e2..000000000000 --- a/test/unit/sql/add-constraint.test.js +++ /dev/null @@ -1,225 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const current = Support.sequelize; -const expectsql = Support.expectsql; -const sql = current.dialect.queryGenerator; -const Op = Support.Sequelize.Op; -const expect = require('chai').expect; -const sinon = require('sinon'); - -if (current.dialect.supports.constraints.addConstraint) { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('addConstraint', () => { - describe('unique', () => { - it('naming', () => { - expectsql(sql.addConstraintQuery('myTable', { - name: 'unique_mytable_mycolumn', - type: 'UNIQUE', - fields: ['myColumn'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [unique_mytable_mycolumn] UNIQUE ([myColumn]);' - }); - }); - - it('should create constraint name if not passed', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'UNIQUE', - fields: ['myColumn'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_uk] UNIQUE ([myColumn]);' - }); - }); - - it('should work with multiple columns', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'UNIQUE', - fields: ['myColumn1', 'myColumn2'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn1_myColumn2_uk] UNIQUE ([myColumn1], [myColumn2]);' - }); - }); - }); - - describe('check', () => { - it('naming', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'CHECK', - fields: ['myColumn'], - where: { - myColumn: ['value1', 'value2', 'value3'] - } - }), { - mssql: "ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN (N'value1', N'value2', N'value3'));", - default: "ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN ('value1', 'value2', 'value3'));" - }); - }); - - it('where', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'CHECK', - fields: ['myColumn'], - name: 'check_mycolumn_where', - where: { - myColumn: { - [Op.and]: { - [Op.gt]: 50, - [Op.lt]: 100 - } - } - } - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [check_mycolumn_where] CHECK (([myColumn] > 50 AND [myColumn] < 100));' - }); - }); - - }); - - if (current.dialect.supports.constraints.default) { - describe('default', () => { - it('naming', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'default', - fields: ['myColumn'], - defaultValue: 0 - }), { - mssql: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_df] DEFAULT (0) FOR [myColumn];' - }); - }); - - it('string', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'default', - fields: ['myColumn'], - defaultValue: 'some default value', - name: 'default_mytable_null' - }), { - mssql: "ALTER TABLE [myTable] ADD CONSTRAINT [default_mytable_null] DEFAULT (N'some default value') FOR [myColumn];" - }); - }); - - it('validation', () => { - expect(sql.addConstraintQuery.bind(sql, { - tableName: 'myTable', - schema: 'mySchema' - }, { - type: 'default', - fields: [{ - attribute: 'myColumn' - }] - })).to.throw('Default value must be specifed for DEFAULT CONSTRAINT'); - }); - - }); - } - describe('primary key', () => { - it('naming', () => { - expectsql(sql.addConstraintQuery('myTable', { - name: 'primary_mytable_mycolumn', - type: 'primary key', - fields: ['myColumn'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [primary_mytable_mycolumn] PRIMARY KEY ([myColumn]);' - }); - }); - - it('should create constraint name if not passed', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'PRIMARY KEY', - fields: ['myColumn'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_pk] PRIMARY KEY ([myColumn]);' - }); - }); - - it('should work with multiple columns', () => { - expectsql(sql.addConstraintQuery('myTable', { - type: 'PRIMARY KEY', - fields: ['myColumn1', 'myColumn2'] - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn1_myColumn2_pk] PRIMARY KEY ([myColumn1], [myColumn2]);' - }); - }); - }); - - describe('foreign key', () => { - it('naming', () => { - expectsql(sql.addConstraintQuery('myTable', { - name: 'foreignkey_mytable_mycolumn', - type: 'foreign key', - fields: ['myColumn'], - references: { - table: 'myOtherTable', - field: 'id' - } - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [foreignkey_mytable_mycolumn] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]);' - }); - }); - - 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', - fields: ['myColumn'], - references: { - table: 'myOtherTable', - field: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }), { - default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_myOtherTable_fk] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]) ON UPDATE CASCADE ON DELETE CASCADE;' - }); - }); - - it('errors if references object is not passed', () => { - expect(sql.addConstraintQuery.bind(sql, 'myTable', { - type: 'foreign key', - fields: ['myColumn'] - })).to.throw('references object with table and field must be specified'); - }); - - - }); - - describe('validation', () => { - it('throw error on invalid type', () => { - expect(sql.addConstraintQuery.bind(sql, 'myTable', { type: 'some type', fields: [] })).to.throw('some type is invalid'); - }); - - it('calls getConstraintSnippet function', () => { - const options = { type: 'unique', fields: ['myColumn'] }; - const addConstraintQuerySpy = sinon.stub(sql, 'addConstraintQuery'); - sql.addConstraintQuery('myTable', options); - expect(sql.addConstraintQuery).to.have.been.calledWith('myTable', options); - addConstraintQuerySpy.restore(); - }); - - if (!current.dialect.supports.constraints.default) { - it('should throw error if default constraints are used in other dialects', () => { - expect(sql.addConstraintQuery.bind(sql, 'myTable', { type: 'default', defaultValue: 0, fields: [] })).to.throw('Default constraints are supported only for MSSQL dialect.'); - }); - } - }); - }); - }); -} diff --git a/test/unit/sql/change-column.test.js b/test/unit/sql/change-column.test.js deleted file mode 100644 index 00e2f568cf93..000000000000 --- a/test/unit/sql/change-column.test.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const sinon = require('sinon'), - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize; - -if (current.dialect.name !== 'sqlite') { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('changeColumn', () => { - - const Model = current.define('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - level_id: { - type: DataTypes.INTEGER - } - }, { timestamps: false }); - - before(function() { - this.stub = sinon.stub(current, 'query').resolvesArg(0); - }); - - beforeEach(function() { - this.stub.resetHistory(); - }); - - after(function() { - this.stub.restore(); - }); - - it('properly generate alter queries', () => { - return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { - type: DataTypes.FLOAT, - allowNull: false - }).then(sql => { - expectsql(sql, { - mssql: 'ALTER TABLE [users] ALTER COLUMN [level_id] FLOAT NOT NULL;', - mariadb: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', - mysql: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', - postgres: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE FLOAT;' - }); - }); - }); - - it('properly generate alter queries for foreign keys', () => { - return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }).then(sql => { - expectsql(sql, { - mssql: 'ALTER TABLE [users] ADD FOREIGN KEY ([level_id]) REFERENCES [level] ([id]) ON DELETE CASCADE;', - mariadb: 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', - mysql: 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', - postgres: 'ALTER TABLE "users" ADD FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;' - }); - }); - }); - - }); - }); -} diff --git a/test/unit/sql/create-schema.test.js b/test/unit/sql/create-schema.test.js deleted file mode 100644 index b7e38cb622b6..000000000000 --- a/test/unit/sql/create-schema.test.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const expectsql = Support.expectsql; -const current = Support.sequelize; -const sql = current.dialect.queryGenerator; - -describe(Support.getTestDialectTeaser('SQL'), () => { - if (current.dialect.name === 'postgres') { - describe('dropSchema', () => { - it('IF EXISTS', () => { - expectsql(sql.dropSchema('foo'), { - postgres: 'DROP SCHEMA IF EXISTS foo CASCADE;' - }); - }); - }); - - describe('createSchema', () => { - before(function() { - this.version = current.options.databaseVersion; - }); - - after(function() { - current.options.databaseVersion = this.version; - }); - - it('9.2.0 or above', () => { - current.options.databaseVersion = '9.2.0'; - expectsql(sql.createSchema('foo'), { - postgres: 'CREATE SCHEMA IF NOT EXISTS foo;' - }); - }); - - it('below 9.2.0', () => { - current.options.databaseVersion = '9.0.0'; - expectsql(sql.createSchema('foo'), { - postgres: 'CREATE SCHEMA foo;' - }); - }); - }); - } -}); diff --git a/test/unit/sql/create-table.test.js b/test/unit/sql/create-table.test.js deleted file mode 100644 index 53fa2623482a..000000000000 --- a/test/unit/sql/create-table.test.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize, - sql = current.dialect.queryGenerator, - _ = require('lodash'); - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('createTable', () => { - const FooUser = current.define('user', { - mood: DataTypes.ENUM('happy', 'sad') - }, { - schema: 'foo', - timestamps: false - }); - - describe('with enums', () => { - it('references enum in the right schema #3171', () => { - expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { - sqlite: 'CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `mood` TEXT);', - postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));', - mariadb: "CREATE TABLE IF NOT EXISTS `foo`.`users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;", - mysql: "CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;", - mssql: "IF OBJECT_ID('[foo].[users]', 'U') IS NULL CREATE TABLE [foo].[users] ([id] INTEGER NOT NULL IDENTITY(1,1) , [mood] VARCHAR(255) CHECK ([mood] IN(N'happy', N'sad')), PRIMARY KEY ([id]));" - }); - }); - }); - - describe('with references', () => { - const BarUser = current.define('user', { - timestamps: false - }).schema('bar'); - - const BarProject = current.define('project', { - user_id: { - type: DataTypes.INTEGER, - references: { model: BarUser }, - onUpdate: 'CASCADE', - onDelete: 'NO ACTION' - } - }, { - timestamps: false - }).schema('bar'); - - BarProject.belongsTo(BarUser, { foreignKey: 'user_id' }); - - it('references right schema when adding foreign key #9029', () => { - expectsql(sql.createTableQuery(BarProject.getTableName(), sql.attributesToSQL(BarProject.rawAttributes), { }), { - sqlite: 'CREATE TABLE IF NOT EXISTS `bar.projects` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user_id` INTEGER REFERENCES `bar.users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE);', - postgres: 'CREATE TABLE IF NOT EXISTS "bar"."projects" ("id" SERIAL , "user_id" INTEGER REFERENCES "bar"."users" ("id") ON DELETE NO ACTION ON UPDATE CASCADE, PRIMARY KEY ("id"));', - mariadb: 'CREATE TABLE IF NOT EXISTS `bar`.`projects` (`id` INTEGER NOT NULL auto_increment , `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `bar`.`users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;', - mysql: 'CREATE TABLE IF NOT EXISTS `bar.projects` (`id` INTEGER NOT NULL auto_increment , `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `bar.users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;', - mssql: 'IF OBJECT_ID(\'[bar].[projects]\', \'U\') IS NULL CREATE TABLE [bar].[projects] ([id] INTEGER NOT NULL IDENTITY(1,1) , [user_id] INTEGER NULL, PRIMARY KEY ([id]), FOREIGN KEY ([user_id]) REFERENCES [bar].[users] ([id]) ON DELETE NO ACTION);' - }); - }); - }); - - describe('with references on primary key', () => { - const File = current.define('file', {}, { timestamps: false }); - const Image = current.define('image', { - id: { - primaryKey: true, - autoIncrement: true, - type: DataTypes.INTEGER, - references: { - model: File, - key: 'id' - } - } - }, { - timestamps: false - }); - - it('references on primary key #9461', () => { - expectsql(sql.createTableQuery(Image.getTableName(), sql.attributesToSQL(Image.rawAttributes), { }), { - sqlite: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER PRIMARY KEY AUTOINCREMENT REFERENCES `files` (`id`));', - postgres: 'CREATE TABLE IF NOT EXISTS "images" ("id" SERIAL REFERENCES "files" ("id"), PRIMARY KEY ("id"));', - mariadb: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;', - mysql: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;', - mssql: 'IF OBJECT_ID(\'[images]\', \'U\') IS NULL CREATE TABLE [images] ([id] INTEGER IDENTITY(1,1) , PRIMARY KEY ([id]), FOREIGN KEY ([id]) REFERENCES [files] ([id]));' - }); - }); - }); - - if (current.dialect.name === 'postgres') { - describe('IF NOT EXISTS version check', () => { - const modifiedSQL = _.clone(sql); - const createTableQueryModified = sql.createTableQuery.bind(modifiedSQL); - it('it will not have IF NOT EXISTS for version 9.0 or below', () => { - modifiedSQL.sequelize.options.databaseVersion = '9.0.0'; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { - postgres: 'CREATE TABLE "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' - }); - }); - it('it will have IF NOT EXISTS for version 9.1 or above', () => { - modifiedSQL.sequelize.options.databaseVersion = '9.1.0'; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { - postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' - }); - }); - it('it will have IF NOT EXISTS for default version', () => { - modifiedSQL.sequelize.options.databaseVersion = 0; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { - postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' - }); - }); - }); - } - }); -}); diff --git a/test/unit/sql/data-types.test.js b/test/unit/sql/data-types.test.js deleted file mode 100644 index 5737ef30e962..000000000000 --- a/test/unit/sql/data-types.test.js +++ /dev/null @@ -1,1468 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = Support.Sequelize, - chai = require('chai'), - util = require('util'), - uuid = require('uuid'), - expectsql = Support.expectsql, - current = Support.sequelize, - expect = chai.expect, - dialect = Support.getTestDialect(); - -// 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('DataTypes', () => { - const testsql = function(description, dataType, expectation) { - it(description, () => { - return expectsql(current.normalizeDataType(dataType).toSql(), expectation); - }); - }; - - describe('STRING', () => { - testsql('STRING', DataTypes.STRING, { - default: 'VARCHAR(255)', - mssql: 'NVARCHAR(255)' - }); - - testsql('STRING(1234)', DataTypes.STRING(1234), { - default: 'VARCHAR(1234)', - mssql: 'NVARCHAR(1234)' - }); - - testsql('STRING({ length: 1234 })', DataTypes.STRING({ length: 1234 }), { - default: 'VARCHAR(1234)', - mssql: 'NVARCHAR(1234)' - }); - - testsql('STRING(1234).BINARY', DataTypes.STRING(1234).BINARY, { - default: 'VARCHAR(1234) BINARY', - sqlite: 'VARCHAR BINARY(1234)', - mssql: 'BINARY(1234)', - postgres: 'BYTEA' - }); - - testsql('STRING.BINARY', DataTypes.STRING.BINARY, { - default: 'VARCHAR(255) BINARY', - sqlite: 'VARCHAR BINARY(255)', - mssql: 'BINARY(255)', - postgres: 'BYTEA' - }); - - describe('validate', () => { - it('should return `true` if `value` is a string', () => { - const type = DataTypes.STRING(); - - expect(type.validate('foobar')).to.equal(true); - expect(type.validate(new String('foobar'))).to.equal(true); - expect(type.validate(12)).to.equal(true); - }); - }); - }); - - describe('TEXT', () => { - testsql('TEXT', DataTypes.TEXT, { - default: 'TEXT', - mssql: 'NVARCHAR(MAX)' // in mssql text is actually representing a non unicode text field - }); - - testsql('TEXT("tiny")', DataTypes.TEXT('tiny'), { - default: 'TEXT', - mssql: 'NVARCHAR(256)', - mariadb: 'TINYTEXT', - mysql: 'TINYTEXT' - }); - - testsql('TEXT({ length: "tiny" })', DataTypes.TEXT({ length: 'tiny' }), { - default: 'TEXT', - mssql: 'NVARCHAR(256)', - mariadb: 'TINYTEXT', - mysql: 'TINYTEXT' - }); - - testsql('TEXT("medium")', DataTypes.TEXT('medium'), { - default: 'TEXT', - mssql: 'NVARCHAR(MAX)', - mariadb: 'MEDIUMTEXT', - mysql: 'MEDIUMTEXT' - }); - - testsql('TEXT("long")', DataTypes.TEXT('long'), { - default: 'TEXT', - mssql: 'NVARCHAR(MAX)', - mariadb: 'LONGTEXT', - mysql: 'LONGTEXT' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.TEXT(); - - expect(() => { - type.validate(12345); - }).to.throw(Sequelize.ValidationError, '12345 is not a valid string'); - }); - - it('should return `true` if `value` is a string', () => { - const type = DataTypes.TEXT(); - - expect(type.validate('foobar')).to.equal(true); - }); - }); - }); - - describe('CHAR', () => { - testsql('CHAR', DataTypes.CHAR, { - default: 'CHAR(255)' - }); - - testsql('CHAR(12)', DataTypes.CHAR(12), { - default: 'CHAR(12)' - }); - - testsql('CHAR({ length: 12 })', DataTypes.CHAR({ length: 12 }), { - default: 'CHAR(12)' - }); - - testsql('CHAR(12).BINARY', DataTypes.CHAR(12).BINARY, { - default: 'CHAR(12) BINARY', - sqlite: 'CHAR BINARY(12)', - postgres: 'BYTEA' - }); - - testsql('CHAR.BINARY', DataTypes.CHAR.BINARY, { - default: 'CHAR(255) BINARY', - sqlite: 'CHAR BINARY(255)', - postgres: 'BYTEA' - }); - }); - - describe('BOOLEAN', () => { - testsql('BOOLEAN', DataTypes.BOOLEAN, { - postgres: 'BOOLEAN', - mssql: 'BIT', - mariadb: 'TINYINT(1)', - mysql: 'TINYINT(1)', - sqlite: 'TINYINT(1)' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.BOOLEAN(); - - expect(() => { - type.validate(12345); - }).to.throw(Sequelize.ValidationError, '12345 is not a valid boolean'); - }); - - it('should return `true` if `value` is a boolean', () => { - const type = DataTypes.BOOLEAN(); - - expect(type.validate(true)).to.equal(true); - expect(type.validate(false)).to.equal(true); - expect(type.validate('1')).to.equal(true); - expect(type.validate('0')).to.equal(true); - expect(type.validate('true')).to.equal(true); - expect(type.validate('false')).to.equal(true); - }); - }); - }); - - describe('DATE', () => { - testsql('DATE', DataTypes.DATE, { - postgres: 'TIMESTAMP WITH TIME ZONE', - mssql: 'DATETIMEOFFSET', - mariadb: 'DATETIME', - mysql: 'DATETIME', - sqlite: 'DATETIME' - }); - - testsql('DATE(6)', DataTypes.DATE(6), { - postgres: 'TIMESTAMP WITH TIME ZONE', - mssql: 'DATETIMEOFFSET', - mariadb: 'DATETIME(6)', - mysql: 'DATETIME(6)', - sqlite: 'DATETIME' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.DATE(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid date'); - }); - - it('should return `true` if `value` is a date', () => { - const type = DataTypes.DATE(); - - expect(type.validate(new Date())).to.equal(true); - }); - }); - }); - - if (current.dialect.supports.HSTORE) { - describe('HSTORE', () => { - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.HSTORE(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid hstore'); - }); - - it('should return `true` if `value` is an hstore', () => { - const type = DataTypes.HSTORE(); - - expect(type.validate({ foo: 'bar' })).to.equal(true); - }); - }); - }); - } - - describe('UUID', () => { - testsql('UUID', DataTypes.UUID, { - postgres: 'UUID', - mssql: 'CHAR(36)', - mariadb: 'CHAR(36) BINARY', - mysql: 'CHAR(36) BINARY', - sqlite: 'UUID' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.UUID(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid uuid'); - - expect(() => { - type.validate(['foobar']); - }).to.throw(Sequelize.ValidationError, '["foobar"] is not a valid uuid'); - }); - - it('should return `true` if `value` is an uuid', () => { - const type = DataTypes.UUID(); - - expect(type.validate(uuid.v4())).to.equal(true); - }); - - it('should return `true` if `value` is a string and we accept strings', () => { - const type = DataTypes.UUID(); - - expect(type.validate('foobar', { acceptStrings: true })).to.equal(true); - }); - }); - }); - - describe('UUIDV1', () => { - testsql('UUIDV1', DataTypes.UUIDV1, { - default: 'UUIDV1' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.UUIDV1(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid uuid'); - - expect(() => { - type.validate(['foobar']); - }).to.throw(Sequelize.ValidationError, '["foobar"] is not a valid uuid'); - }); - - it('should return `true` if `value` is an uuid', () => { - const type = DataTypes.UUIDV1(); - - expect(type.validate(uuid.v1())).to.equal(true); - }); - - it('should return `true` if `value` is a string and we accept strings', () => { - const type = DataTypes.UUIDV1(); - - expect(type.validate('foobar', { acceptStrings: true })).to.equal(true); - }); - }); - }); - - describe('UUIDV4', () => { - testsql('UUIDV4', DataTypes.UUIDV4, { - default: 'UUIDV4' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.UUIDV4(); - const value = uuid.v1(); - - expect(() => { - type.validate(value); - }).to.throw(Sequelize.ValidationError, util.format('%j is not a valid uuidv4', value)); - - expect(() => { - type.validate(['foobar']); - }).to.throw(Sequelize.ValidationError, '["foobar"] is not a valid uuidv4'); - }); - - it('should return `true` if `value` is an uuid', () => { - const type = DataTypes.UUIDV4(); - - expect(type.validate(uuid.v4())).to.equal(true); - }); - - it('should return `true` if `value` is a string and we accept strings', () => { - const type = DataTypes.UUIDV4(); - - expect(type.validate('foobar', { acceptStrings: true })).to.equal(true); - }); - }); - }); - - describe('NOW', () => { - testsql('NOW', DataTypes.NOW, { - default: 'NOW', - mssql: 'GETDATE()' - }); - }); - - describe('INTEGER', () => { - testsql('INTEGER', DataTypes.INTEGER, { - default: 'INTEGER' - }); - - testsql('INTEGER.UNSIGNED', DataTypes.INTEGER.UNSIGNED, { - default: 'INTEGER UNSIGNED', - postgres: 'INTEGER', - mssql: 'INTEGER', - sqlite: 'INTEGER' - }); - - testsql('INTEGER.UNSIGNED.ZEROFILL', DataTypes.INTEGER.UNSIGNED.ZEROFILL, { - default: 'INTEGER UNSIGNED ZEROFILL', - postgres: 'INTEGER', - mssql: 'INTEGER', - sqlite: 'INTEGER' - }); - - testsql('INTEGER(11)', DataTypes.INTEGER(11), { - default: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - testsql('INTEGER({ length: 11 })', DataTypes.INTEGER({ length: 11 }), { - default: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - testsql('INTEGER(11).UNSIGNED', DataTypes.INTEGER(11).UNSIGNED, { - default: 'INTEGER(11) UNSIGNED', - sqlite: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - testsql('INTEGER(11).UNSIGNED.ZEROFILL', DataTypes.INTEGER(11).UNSIGNED.ZEROFILL, { - default: 'INTEGER(11) UNSIGNED ZEROFILL', - sqlite: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - testsql('INTEGER(11).ZEROFILL', DataTypes.INTEGER(11).ZEROFILL, { - default: 'INTEGER(11) ZEROFILL', - sqlite: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - testsql('INTEGER(11).ZEROFILL.UNSIGNED', DataTypes.INTEGER(11).ZEROFILL.UNSIGNED, { - default: 'INTEGER(11) UNSIGNED ZEROFILL', - sqlite: 'INTEGER(11)', - postgres: 'INTEGER', - mssql: 'INTEGER' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.INTEGER(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid integer'); - - expect(() => { - type.validate('123.45'); - }).to.throw(Sequelize.ValidationError, '"123.45" is not a valid integer'); - - expect(() => { - type.validate(123.45); - }).to.throw(Sequelize.ValidationError, '123.45 is not a valid integer'); - }); - - it('should return `true` if `value` is a valid integer', () => { - const type = DataTypes.INTEGER(); - - expect(type.validate('12345')).to.equal(true); - expect(type.validate(12345)).to.equal(true); - }); - }); - }); - - describe('TINYINT', () => { - const cases = [ - { - title: 'TINYINT', - dataType: DataTypes.TINYINT, - expect: { - default: 'TINYINT' - } - }, - { - title: 'TINYINT(2)', - dataType: DataTypes.TINYINT(2), - expect: { - default: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - }, - { - title: 'TINYINT({ length: 2 })', - dataType: DataTypes.TINYINT({ length: 2 }), - expect: { - default: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - }, - { - title: 'TINYINT.UNSIGNED', - dataType: DataTypes.TINYINT.UNSIGNED, - expect: { - default: 'TINYINT UNSIGNED', - mssql: 'TINYINT', - postgres: 'TINYINT', - sqlite: 'TINYINT' - } - }, - { - title: 'TINYINT(2).UNSIGNED', - dataType: DataTypes.TINYINT(2).UNSIGNED, - expect: { - default: 'TINYINT(2) UNSIGNED', - sqlite: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - }, - { - title: 'TINYINT.UNSIGNED.ZEROFILL', - dataType: DataTypes.TINYINT.UNSIGNED.ZEROFILL, - expect: { - default: 'TINYINT UNSIGNED ZEROFILL', - mssql: 'TINYINT', - postgres: 'TINYINT', - sqlite: 'TINYINT' - } - }, - { - title: 'TINYINT(2).UNSIGNED.ZEROFILL', - dataType: DataTypes.TINYINT(2).UNSIGNED.ZEROFILL, - expect: { - default: 'TINYINT(2) UNSIGNED ZEROFILL', - sqlite: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - }, - { - title: 'TINYINT.ZEROFILL', - dataType: DataTypes.TINYINT.ZEROFILL, - expect: { - default: 'TINYINT ZEROFILL', - mssql: 'TINYINT', - postgres: 'TINYINT', - sqlite: 'TINYINT' - } - }, - { - title: 'TINYINT(2).ZEROFILL', - dataType: DataTypes.TINYINT(2).ZEROFILL, - expect: { - default: 'TINYINT(2) ZEROFILL', - sqlite: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - }, - { - title: 'TINYINT.ZEROFILL.UNSIGNED', - dataType: DataTypes.TINYINT.ZEROFILL.UNSIGNED, - expect: { - default: 'TINYINT UNSIGNED ZEROFILL', - mssql: 'TINYINT', - postgres: 'TINYINT', - sqlite: 'TINYINT' - } - }, - { - title: 'TINYINT(2).ZEROFILL.UNSIGNED', - dataType: DataTypes.TINYINT(2).ZEROFILL.UNSIGNED, - expect: { - default: 'TINYINT(2) UNSIGNED ZEROFILL', - sqlite: 'TINYINT(2)', - mssql: 'TINYINT', - postgres: 'TINYINT' - } - } - ]; - cases.forEach(row => { - testsql(row.title, row.dataType, row.expect); - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.TINYINT(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid tinyint'); - - expect(() => { - type.validate(123.45); - }).to.throw(Sequelize.ValidationError, '123.45 is not a valid tinyint'); - }); - - it('should return `true` if `value` is an integer', () => { - const type = DataTypes.TINYINT(); - - expect(type.validate(-128)).to.equal(true); - expect(type.validate('127')).to.equal(true); - }); - }); - }); - - describe('SMALLINT', () => { - const cases = [ - { - title: 'SMALLINT', - dataType: DataTypes.SMALLINT, - expect: { - default: 'SMALLINT' - } - }, - { - title: 'SMALLINT(4)', - dataType: DataTypes.SMALLINT(4), - expect: { - default: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - }, - { - title: 'SMALLINT({ length: 4 })', - dataType: DataTypes.SMALLINT({ length: 4 }), - expect: { - default: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - }, - { - title: 'SMALLINT.UNSIGNED', - dataType: DataTypes.SMALLINT.UNSIGNED, - expect: { - default: 'SMALLINT UNSIGNED', - postgres: 'SMALLINT', - mssql: 'SMALLINT', - sqlite: 'SMALLINT' - } - }, - { - title: 'SMALLINT(4).UNSIGNED', - dataType: DataTypes.SMALLINT(4).UNSIGNED, - expect: { - default: 'SMALLINT(4) UNSIGNED', - sqlite: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - }, - { - title: 'SMALLINT.UNSIGNED.ZEROFILL', - dataType: DataTypes.SMALLINT.UNSIGNED.ZEROFILL, - expect: { - default: 'SMALLINT UNSIGNED ZEROFILL', - postgres: 'SMALLINT', - mssql: 'SMALLINT', - sqlite: 'SMALLINT' - } - }, - { - title: 'SMALLINT(4).UNSIGNED.ZEROFILL', - dataType: DataTypes.SMALLINT(4).UNSIGNED.ZEROFILL, - expect: { - default: 'SMALLINT(4) UNSIGNED ZEROFILL', - sqlite: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - }, - { - title: 'SMALLINT.ZEROFILL', - dataType: DataTypes.SMALLINT.ZEROFILL, - expect: { - default: 'SMALLINT ZEROFILL', - postgres: 'SMALLINT', - mssql: 'SMALLINT', - sqlite: 'SMALLINT' - } - }, - { - title: 'SMALLINT(4).ZEROFILL', - dataType: DataTypes.SMALLINT(4).ZEROFILL, - expect: { - default: 'SMALLINT(4) ZEROFILL', - sqlite: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - }, - { - title: 'SMALLINT.ZEROFILL.UNSIGNED', - dataType: DataTypes.SMALLINT.ZEROFILL.UNSIGNED, - expect: { - default: 'SMALLINT UNSIGNED ZEROFILL', - postgres: 'SMALLINT', - mssql: 'SMALLINT', - sqlite: 'SMALLINT' - } - }, - { - title: 'SMALLINT(4).ZEROFILL.UNSIGNED', - dataType: DataTypes.SMALLINT(4).ZEROFILL.UNSIGNED, - expect: { - default: 'SMALLINT(4) UNSIGNED ZEROFILL', - sqlite: 'SMALLINT(4)', - postgres: 'SMALLINT', - mssql: 'SMALLINT' - } - } - ]; - cases.forEach(row => { - testsql(row.title, row.dataType, row.expect); - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.SMALLINT(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid smallint'); - - expect(() => { - type.validate(123.45); - }).to.throw(Sequelize.ValidationError, '123.45 is not a valid smallint'); - }); - - it('should return `true` if `value` is an integer', () => { - const type = DataTypes.SMALLINT(); - - expect(type.validate(-32768)).to.equal(true); - expect(type.validate('32767')).to.equal(true); - }); - }); - }); - - describe('MEDIUMINT', () => { - const cases = [ - { - title: 'MEDIUMINT', - dataType: DataTypes.MEDIUMINT, - expect: { - default: 'MEDIUMINT' - } - }, - { - title: 'MEDIUMINT(6)', - dataType: DataTypes.MEDIUMINT(6), - expect: { - default: 'MEDIUMINT(6)' - } - }, - { - title: 'MEDIUMINT({ length: 6 })', - dataType: DataTypes.MEDIUMINT({ length: 6 }), - expect: { - default: 'MEDIUMINT(6)' - } - }, - { - title: 'MEDIUMINT.UNSIGNED', - dataType: DataTypes.MEDIUMINT.UNSIGNED, - expect: { - default: 'MEDIUMINT UNSIGNED', - sqlite: 'MEDIUMINT' - } - }, - { - title: 'MEDIUMINT(6).UNSIGNED', - dataType: DataTypes.MEDIUMINT(6).UNSIGNED, - expect: { - default: 'MEDIUMINT(6) UNSIGNED', - sqlite: 'MEDIUMINT(6)' - } - }, - { - title: 'MEDIUMINT.UNSIGNED.ZEROFILL', - dataType: DataTypes.MEDIUMINT.UNSIGNED.ZEROFILL, - expect: { - default: 'MEDIUMINT UNSIGNED ZEROFILL', - sqlite: 'MEDIUMINT' - } - }, - { - title: 'MEDIUMINT(6).UNSIGNED.ZEROFILL', - dataType: DataTypes.MEDIUMINT(6).UNSIGNED.ZEROFILL, - expect: { - default: 'MEDIUMINT(6) UNSIGNED ZEROFILL', - sqlite: 'MEDIUMINT(6)' - } - }, - { - title: 'MEDIUMINT.ZEROFILL', - dataType: DataTypes.MEDIUMINT.ZEROFILL, - expect: { - default: 'MEDIUMINT ZEROFILL', - sqlite: 'MEDIUMINT' - } - }, - { - title: 'MEDIUMINT(6).ZEROFILL', - dataType: DataTypes.MEDIUMINT(6).ZEROFILL, - expect: { - default: 'MEDIUMINT(6) ZEROFILL', - sqlite: 'MEDIUMINT(6)' - } - }, - { - title: 'MEDIUMINT.ZEROFILL.UNSIGNED', - dataType: DataTypes.MEDIUMINT.ZEROFILL.UNSIGNED, - expect: { - default: 'MEDIUMINT UNSIGNED ZEROFILL', - sqlite: 'MEDIUMINT' - } - }, - { - title: 'MEDIUMINT(6).ZEROFILL.UNSIGNED', - dataType: DataTypes.MEDIUMINT(6).ZEROFILL.UNSIGNED, - expect: { - default: 'MEDIUMINT(6) UNSIGNED ZEROFILL', - sqlite: 'MEDIUMINT(6)' - } - } - ]; - cases.forEach(row => { - testsql(row.title, row.dataType, row.expect); - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.MEDIUMINT(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid mediumint'); - - expect(() => { - type.validate(123.45); - }).to.throw(Sequelize.ValidationError, '123.45 is not a valid mediumint'); - }); - - it('should return `true` if `value` is an integer', () => { - const type = DataTypes.MEDIUMINT(); - - expect(type.validate(-8388608)).to.equal(true); - expect(type.validate('8388607')).to.equal(true); - }); - }); - }); - - describe('BIGINT', () => { - testsql('BIGINT', DataTypes.BIGINT, { - default: 'BIGINT' - }); - - testsql('BIGINT.UNSIGNED', DataTypes.BIGINT.UNSIGNED, { - default: 'BIGINT UNSIGNED', - postgres: 'BIGINT', - mssql: 'BIGINT', - sqlite: 'BIGINT' - }); - - testsql('BIGINT.UNSIGNED.ZEROFILL', DataTypes.BIGINT.UNSIGNED.ZEROFILL, { - default: 'BIGINT UNSIGNED ZEROFILL', - postgres: 'BIGINT', - mssql: 'BIGINT', - sqlite: 'BIGINT' - }); - - testsql('BIGINT(11)', DataTypes.BIGINT(11), { - default: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - testsql('BIGINT({ length: 11 })', DataTypes.BIGINT({ length: 11 }), { - default: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - testsql('BIGINT(11).UNSIGNED', DataTypes.BIGINT(11).UNSIGNED, { - default: 'BIGINT(11) UNSIGNED', - sqlite: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - testsql('BIGINT(11).UNSIGNED.ZEROFILL', DataTypes.BIGINT(11).UNSIGNED.ZEROFILL, { - default: 'BIGINT(11) UNSIGNED ZEROFILL', - sqlite: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - testsql('BIGINT(11).ZEROFILL', DataTypes.BIGINT(11).ZEROFILL, { - default: 'BIGINT(11) ZEROFILL', - sqlite: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - testsql('BIGINT(11).ZEROFILL.UNSIGNED', DataTypes.BIGINT(11).ZEROFILL.UNSIGNED, { - default: 'BIGINT(11) UNSIGNED ZEROFILL', - sqlite: 'BIGINT(11)', - postgres: 'BIGINT', - mssql: 'BIGINT' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.BIGINT(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid bigint'); - - expect(() => { - type.validate(123.45); - }).to.throw(Sequelize.ValidationError, '123.45 is not a valid bigint'); - }); - - it('should return `true` if `value` is an integer', () => { - const type = DataTypes.BIGINT(); - - expect(type.validate('9223372036854775807')).to.equal(true); - }); - }); - }); - - describe('REAL', () => { - testsql('REAL', DataTypes.REAL, { - default: 'REAL' - }); - - testsql('REAL.UNSIGNED', DataTypes.REAL.UNSIGNED, { - default: 'REAL UNSIGNED', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11)', DataTypes.REAL(11), { - default: 'REAL(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL({ length: 11 })', DataTypes.REAL({ length: 11 }), { - default: 'REAL(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11).UNSIGNED', DataTypes.REAL(11).UNSIGNED, { - default: 'REAL(11) UNSIGNED', - sqlite: 'REAL UNSIGNED(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11).UNSIGNED.ZEROFILL', DataTypes.REAL(11).UNSIGNED.ZEROFILL, { - default: 'REAL(11) UNSIGNED ZEROFILL', - sqlite: 'REAL UNSIGNED ZEROFILL(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11).ZEROFILL', DataTypes.REAL(11).ZEROFILL, { - default: 'REAL(11) ZEROFILL', - sqlite: 'REAL ZEROFILL(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11).ZEROFILL.UNSIGNED', DataTypes.REAL(11).ZEROFILL.UNSIGNED, { - default: 'REAL(11) UNSIGNED ZEROFILL', - sqlite: 'REAL UNSIGNED ZEROFILL(11)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11, 12)', DataTypes.REAL(11, 12), { - default: 'REAL(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11, 12).UNSIGNED', DataTypes.REAL(11, 12).UNSIGNED, { - default: 'REAL(11,12) UNSIGNED', - sqlite: 'REAL UNSIGNED(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL({ length: 11, decimals: 12 }).UNSIGNED', DataTypes.REAL({ length: 11, decimals: 12 }).UNSIGNED, { - default: 'REAL(11,12) UNSIGNED', - sqlite: 'REAL UNSIGNED(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11, 12).UNSIGNED.ZEROFILL', DataTypes.REAL(11, 12).UNSIGNED.ZEROFILL, { - default: 'REAL(11,12) UNSIGNED ZEROFILL', - sqlite: 'REAL UNSIGNED ZEROFILL(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11, 12).ZEROFILL', DataTypes.REAL(11, 12).ZEROFILL, { - default: 'REAL(11,12) ZEROFILL', - sqlite: 'REAL ZEROFILL(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - - testsql('REAL(11, 12).ZEROFILL.UNSIGNED', DataTypes.REAL(11, 12).ZEROFILL.UNSIGNED, { - default: 'REAL(11,12) UNSIGNED ZEROFILL', - sqlite: 'REAL UNSIGNED ZEROFILL(11,12)', - postgres: 'REAL', - mssql: 'REAL' - }); - }); - - describe('DOUBLE PRECISION', () => { - testsql('DOUBLE', DataTypes.DOUBLE, { - default: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE.UNSIGNED', DataTypes.DOUBLE.UNSIGNED, { - default: 'DOUBLE PRECISION UNSIGNED', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11)', DataTypes.DOUBLE(11), { - default: 'DOUBLE PRECISION(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11).UNSIGNED', DataTypes.DOUBLE(11).UNSIGNED, { - default: 'DOUBLE PRECISION(11) UNSIGNED', - sqlite: 'DOUBLE PRECISION UNSIGNED(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE({ length: 11 }).UNSIGNED', DataTypes.DOUBLE({ length: 11 }).UNSIGNED, { - default: 'DOUBLE PRECISION(11) UNSIGNED', - sqlite: 'DOUBLE PRECISION UNSIGNED(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11).UNSIGNED.ZEROFILL', DataTypes.DOUBLE(11).UNSIGNED.ZEROFILL, { - default: 'DOUBLE PRECISION(11) UNSIGNED ZEROFILL', - sqlite: 'DOUBLE PRECISION UNSIGNED ZEROFILL(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11).ZEROFILL', DataTypes.DOUBLE(11).ZEROFILL, { - default: 'DOUBLE PRECISION(11) ZEROFILL', - sqlite: 'DOUBLE PRECISION ZEROFILL(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11).ZEROFILL.UNSIGNED', DataTypes.DOUBLE(11).ZEROFILL.UNSIGNED, { - default: 'DOUBLE PRECISION(11) UNSIGNED ZEROFILL', - sqlite: 'DOUBLE PRECISION UNSIGNED ZEROFILL(11)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11, 12)', DataTypes.DOUBLE(11, 12), { - default: 'DOUBLE PRECISION(11,12)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11, 12).UNSIGNED', DataTypes.DOUBLE(11, 12).UNSIGNED, { - default: 'DOUBLE PRECISION(11,12) UNSIGNED', - sqlite: 'DOUBLE PRECISION UNSIGNED(11,12)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11, 12).UNSIGNED.ZEROFILL', DataTypes.DOUBLE(11, 12).UNSIGNED.ZEROFILL, { - default: 'DOUBLE PRECISION(11,12) UNSIGNED ZEROFILL', - sqlite: 'DOUBLE PRECISION UNSIGNED ZEROFILL(11,12)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11, 12).ZEROFILL', DataTypes.DOUBLE(11, 12).ZEROFILL, { - default: 'DOUBLE PRECISION(11,12) ZEROFILL', - sqlite: 'DOUBLE PRECISION ZEROFILL(11,12)', - postgres: 'DOUBLE PRECISION' - }); - - testsql('DOUBLE(11, 12).ZEROFILL.UNSIGNED', DataTypes.DOUBLE(11, 12).ZEROFILL.UNSIGNED, { - default: 'DOUBLE PRECISION(11,12) UNSIGNED ZEROFILL', - sqlite: 'DOUBLE PRECISION UNSIGNED ZEROFILL(11,12)', - postgres: 'DOUBLE PRECISION' - }); - }); - - describe('FLOAT', () => { - testsql('FLOAT', DataTypes.FLOAT, { - default: 'FLOAT', - postgres: 'FLOAT' - }); - - testsql('FLOAT.UNSIGNED', DataTypes.FLOAT.UNSIGNED, { - default: 'FLOAT UNSIGNED', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT(11)', DataTypes.FLOAT(11), { - default: 'FLOAT(11)', - postgres: 'FLOAT(11)', // 1-24 = 4 bytes; 35-53 = 8 bytes - mssql: 'FLOAT(11)' // 1-24 = 4 bytes; 35-53 = 8 bytes - }); - - testsql('FLOAT(11).UNSIGNED', DataTypes.FLOAT(11).UNSIGNED, { - default: 'FLOAT(11) UNSIGNED', - sqlite: 'FLOAT UNSIGNED(11)', - postgres: 'FLOAT(11)', - mssql: 'FLOAT(11)' - }); - - testsql('FLOAT(11).UNSIGNED.ZEROFILL', DataTypes.FLOAT(11).UNSIGNED.ZEROFILL, { - default: 'FLOAT(11) UNSIGNED ZEROFILL', - sqlite: 'FLOAT UNSIGNED ZEROFILL(11)', - postgres: 'FLOAT(11)', - mssql: 'FLOAT(11)' - }); - - testsql('FLOAT(11).ZEROFILL', DataTypes.FLOAT(11).ZEROFILL, { - default: 'FLOAT(11) ZEROFILL', - sqlite: 'FLOAT ZEROFILL(11)', - postgres: 'FLOAT(11)', - mssql: 'FLOAT(11)' - }); - - testsql('FLOAT({ length: 11 }).ZEROFILL', DataTypes.FLOAT({ length: 11 }).ZEROFILL, { - default: 'FLOAT(11) ZEROFILL', - sqlite: 'FLOAT ZEROFILL(11)', - postgres: 'FLOAT(11)', - mssql: 'FLOAT(11)' - }); - - testsql('FLOAT(11).ZEROFILL.UNSIGNED', DataTypes.FLOAT(11).ZEROFILL.UNSIGNED, { - default: 'FLOAT(11) UNSIGNED ZEROFILL', - sqlite: 'FLOAT UNSIGNED ZEROFILL(11)', - postgres: 'FLOAT(11)', - mssql: 'FLOAT(11)' - }); - - testsql('FLOAT(11, 12)', DataTypes.FLOAT(11, 12), { - default: 'FLOAT(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT(11, 12).UNSIGNED', DataTypes.FLOAT(11, 12).UNSIGNED, { - default: 'FLOAT(11,12) UNSIGNED', - sqlite: 'FLOAT UNSIGNED(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT({ length: 11, decimals: 12 }).UNSIGNED', DataTypes.FLOAT({ length: 11, decimals: 12 }).UNSIGNED, { - default: 'FLOAT(11,12) UNSIGNED', - sqlite: 'FLOAT UNSIGNED(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT(11, 12).UNSIGNED.ZEROFILL', DataTypes.FLOAT(11, 12).UNSIGNED.ZEROFILL, { - default: 'FLOAT(11,12) UNSIGNED ZEROFILL', - sqlite: 'FLOAT UNSIGNED ZEROFILL(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT(11, 12).ZEROFILL', DataTypes.FLOAT(11, 12).ZEROFILL, { - default: 'FLOAT(11,12) ZEROFILL', - sqlite: 'FLOAT ZEROFILL(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - testsql('FLOAT(11, 12).ZEROFILL.UNSIGNED', DataTypes.FLOAT(11, 12).ZEROFILL.UNSIGNED, { - default: 'FLOAT(11,12) UNSIGNED ZEROFILL', - sqlite: 'FLOAT UNSIGNED ZEROFILL(11,12)', - postgres: 'FLOAT', - mssql: 'FLOAT' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.FLOAT(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid float'); - }); - - it('should return `true` if `value` is a float', () => { - const type = DataTypes.FLOAT(); - - expect(type.validate(1.2)).to.equal(true); - expect(type.validate('1')).to.equal(true); - expect(type.validate('1.2')).to.equal(true); - expect(type.validate('-0.123')).to.equal(true); - expect(type.validate('-0.22250738585072011e-307')).to.equal(true); - }); - }); - }); - - if (current.dialect.supports.NUMERIC) { - testsql('NUMERIC', DataTypes.NUMERIC, { - default: 'DECIMAL' - }); - - testsql('NUMERIC(15,5)', DataTypes.NUMERIC(15, 5), { - default: 'DECIMAL(15,5)' - }); - } - - describe('DECIMAL', () => { - testsql('DECIMAL', DataTypes.DECIMAL, { - default: 'DECIMAL' - }); - - testsql('DECIMAL(10, 2)', DataTypes.DECIMAL(10, 2), { - default: 'DECIMAL(10,2)' - }); - - testsql('DECIMAL({ precision: 10, scale: 2 })', DataTypes.DECIMAL({ precision: 10, scale: 2 }), { - default: 'DECIMAL(10,2)' - }); - - testsql('DECIMAL(10)', DataTypes.DECIMAL(10), { - default: 'DECIMAL(10)' - }); - - testsql('DECIMAL({ precision: 10 })', DataTypes.DECIMAL({ precision: 10 }), { - default: 'DECIMAL(10)' - }); - - testsql('DECIMAL.UNSIGNED', DataTypes.DECIMAL.UNSIGNED, { - mariadb: 'DECIMAL UNSIGNED', - mysql: 'DECIMAL UNSIGNED', - default: 'DECIMAL' - }); - - testsql('DECIMAL.UNSIGNED.ZEROFILL', DataTypes.DECIMAL.UNSIGNED.ZEROFILL, { - mariadb: 'DECIMAL UNSIGNED ZEROFILL', - mysql: 'DECIMAL UNSIGNED ZEROFILL', - default: 'DECIMAL' - }); - - testsql('DECIMAL({ precision: 10, scale: 2 }).UNSIGNED', DataTypes.DECIMAL({ precision: 10, scale: 2 }).UNSIGNED, { - mariadb: 'DECIMAL(10,2) UNSIGNED', - mysql: 'DECIMAL(10,2) UNSIGNED', - default: 'DECIMAL(10,2)' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.DECIMAL(10); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid decimal'); - - expect(() => { - type.validate('0.1a'); - }).to.throw(Sequelize.ValidationError, '"0.1a" is not a valid decimal'); - - expect(() => { - type.validate(NaN); - }).to.throw(Sequelize.ValidationError, 'null is not a valid decimal'); - }); - - it('should return `true` if `value` is a decimal', () => { - const type = DataTypes.DECIMAL(10); - - expect(type.validate(123)).to.equal(true); - expect(type.validate(1.2)).to.equal(true); - expect(type.validate(-0.25)).to.equal(true); - expect(type.validate(0.0000000000001)).to.equal(true); - expect(type.validate('123')).to.equal(true); - expect(type.validate('1.2')).to.equal(true); - expect(type.validate('-0.25')).to.equal(true); - expect(type.validate('0.0000000000001')).to.equal(true); - }); - }); - }); - - describe('ENUM', () => { - // TODO: Fix Enums and add more tests - // testsql('ENUM("value 1", "value 2")', DataTypes.ENUM('value 1', 'value 2'), { - // default: 'ENUM' - // }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.ENUM('foo'); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid choice in ["foo"]'); - }); - - it('should return `true` if `value` is a valid choice', () => { - const type = DataTypes.ENUM('foobar', 'foobiz'); - - expect(type.validate('foobar')).to.equal(true); - expect(type.validate('foobiz')).to.equal(true); - }); - }); - }); - - describe('BLOB', () => { - testsql('BLOB', DataTypes.BLOB, { - default: 'BLOB', - mssql: 'VARBINARY(MAX)', - postgres: 'BYTEA' - }); - - testsql('BLOB("tiny")', DataTypes.BLOB('tiny'), { - default: 'TINYBLOB', - mssql: 'VARBINARY(256)', - postgres: 'BYTEA' - }); - - testsql('BLOB("medium")', DataTypes.BLOB('medium'), { - default: 'MEDIUMBLOB', - mssql: 'VARBINARY(MAX)', - postgres: 'BYTEA' - }); - - testsql('BLOB({ length: "medium" })', DataTypes.BLOB({ length: 'medium' }), { - default: 'MEDIUMBLOB', - mssql: 'VARBINARY(MAX)', - postgres: 'BYTEA' - }); - - testsql('BLOB("long")', DataTypes.BLOB('long'), { - default: 'LONGBLOB', - mssql: 'VARBINARY(MAX)', - postgres: 'BYTEA' - }); - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.BLOB(); - - expect(() => { - type.validate(12345); - }).to.throw(Sequelize.ValidationError, '12345 is not a valid blob'); - }); - - it('should return `true` if `value` is a blob', () => { - const type = DataTypes.BLOB(); - - expect(type.validate('foobar')).to.equal(true); - expect(type.validate(Buffer.from('foobar'))).to.equal(true); - }); - }); - }); - - describe('RANGE', () => { - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.RANGE(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid range'); - }); - - it('should throw an error if `value` is not an array with two elements', () => { - const type = DataTypes.RANGE(); - - expect(() => { - type.validate([1]); - }).to.throw(Sequelize.ValidationError, 'A range must be an array with two elements'); - }); - - it('should return `true` if `value` is a range', () => { - const type = DataTypes.RANGE(); - - expect(type.validate([1, 2])).to.equal(true); - }); - }); - }); - - if (current.dialect.supports.ARRAY) { - describe('ARRAY', () => { - testsql('ARRAY(VARCHAR)', DataTypes.ARRAY(DataTypes.STRING), { - postgres: 'VARCHAR(255)[]' - }); - - testsql('ARRAY(VARCHAR(100))', DataTypes.ARRAY(DataTypes.STRING(100)), { - postgres: 'VARCHAR(100)[]' - }); - - testsql('ARRAY(INTEGER)', DataTypes.ARRAY(DataTypes.INTEGER), { - postgres: 'INTEGER[]' - }); - - testsql('ARRAY(HSTORE)', DataTypes.ARRAY(DataTypes.HSTORE), { - postgres: 'HSTORE[]' - }); - - testsql('ARRAY(ARRAY(VARCHAR(255)))', DataTypes.ARRAY(DataTypes.ARRAY(DataTypes.STRING)), { - postgres: 'VARCHAR(255)[][]' - }); - - testsql('ARRAY(TEXT)', DataTypes.ARRAY(DataTypes.TEXT), { - postgres: 'TEXT[]' - }); - - testsql('ARRAY(DATE)', DataTypes.ARRAY(DataTypes.DATE), { - postgres: 'TIMESTAMP WITH TIME ZONE[]' - }); - - testsql('ARRAY(BOOLEAN)', DataTypes.ARRAY(DataTypes.BOOLEAN), { - postgres: 'BOOLEAN[]' - }); - - testsql('ARRAY(DECIMAL)', DataTypes.ARRAY(DataTypes.DECIMAL), { - postgres: 'DECIMAL[]' - }); - - testsql('ARRAY(DECIMAL(6))', DataTypes.ARRAY(DataTypes.DECIMAL(6)), { - postgres: 'DECIMAL(6)[]' - }); - - testsql('ARRAY(DECIMAL(6,4))', DataTypes.ARRAY(DataTypes.DECIMAL(6, 4)), { - postgres: 'DECIMAL(6,4)[]' - }); - - testsql('ARRAY(DOUBLE)', DataTypes.ARRAY(DataTypes.DOUBLE), { - postgres: 'DOUBLE PRECISION[]' - }); - - testsql('ARRAY(REAL))', DataTypes.ARRAY(DataTypes.REAL), { - postgres: 'REAL[]' - }); - - if (current.dialect.supports.JSON) { - testsql('ARRAY(JSON)', DataTypes.ARRAY(DataTypes.JSON), { - postgres: 'JSON[]' - }); - } - - if (current.dialect.supports.JSONB) { - testsql('ARRAY(JSONB)', DataTypes.ARRAY(DataTypes.JSONB), { - postgres: 'JSONB[]' - }); - } - - if (dialect === 'postgres') { - testsql('ARRAY(CITEXT)', DataTypes.ARRAY(DataTypes.CITEXT), { - postgres: 'CITEXT[]' - }); - } - - describe('validate', () => { - it('should throw an error if `value` is invalid', () => { - const type = DataTypes.ARRAY(); - - expect(() => { - type.validate('foobar'); - }).to.throw(Sequelize.ValidationError, '"foobar" is not a valid array'); - }); - - it('should return `true` if `value` is an array', () => { - const type = DataTypes.ARRAY(); - - expect(type.validate(['foo', 'bar'])).to.equal(true); - }); - }); - }); - } - - if (current.dialect.supports.GEOMETRY) { - describe('GEOMETRY', () => { - testsql('GEOMETRY', DataTypes.GEOMETRY, { - default: 'GEOMETRY' - }); - - testsql('GEOMETRY(\'POINT\')', DataTypes.GEOMETRY('POINT'), { - postgres: 'GEOMETRY(POINT)', - mariadb: 'POINT', - mysql: 'POINT' - }); - - testsql('GEOMETRY(\'LINESTRING\')', DataTypes.GEOMETRY('LINESTRING'), { - postgres: 'GEOMETRY(LINESTRING)', - mariadb: 'LINESTRING', - mysql: 'LINESTRING' - }); - - testsql('GEOMETRY(\'POLYGON\')', DataTypes.GEOMETRY('POLYGON'), { - postgres: 'GEOMETRY(POLYGON)', - mariadb: 'POLYGON', - mysql: 'POLYGON' - }); - - testsql('GEOMETRY(\'POINT\',4326)', DataTypes.GEOMETRY('POINT', 4326), { - postgres: 'GEOMETRY(POINT,4326)', - mariadb: 'POINT', - mysql: 'POINT' - }); - }); - } - }); -}); diff --git a/test/unit/sql/delete.test.js b/test/unit/sql/delete.test.js deleted file mode 100644 index 7c671eda050e..000000000000 --- a/test/unit/sql/delete.test.js +++ /dev/null @@ -1,216 +0,0 @@ -'use strict'; - -const Support = require('../support'), - QueryTypes = require('../../../lib/query-types'), - util = require('util'), - _ = require('lodash'), - expectsql = Support.expectsql, - current = Support.sequelize, - Sequelize = Support.Sequelize, - 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('delete', () => { - const User = current.define('test_user', {}, { - timestamps: false, - schema: 'public' - }); - - describe('truncate #4306', () => { - const options = { - table: User.getTableName(), - where: {}, - truncate: true, - cascade: true, - limit: 10, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.truncateTableQuery( - options.table, - options - ), { - postgres: 'TRUNCATE "public"."test_users" CASCADE', - mssql: 'TRUNCATE TABLE [public].[test_users]', - mariadb: 'TRUNCATE `public`.`test_users`', - mysql: 'TRUNCATE `public.test_users`', - sqlite: 'DELETE FROM `public.test_users`' - } - ); - }); - }); - - describe('truncate with cascade and restartIdentity', () => { - const options = { - table: User.getTableName(), - where: {}, - truncate: true, - cascade: true, - restartIdentity: true, - limit: 10, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.truncateTableQuery( - options.table, - options - ), { - postgres: 'TRUNCATE "public"."test_users" RESTART IDENTITY CASCADE', - mssql: 'TRUNCATE TABLE [public].[test_users]', - mariadb: 'TRUNCATE `public`.`test_users`', - mysql: 'TRUNCATE `public.test_users`', - sqlite: 'DELETE FROM `public.test_users`; DELETE FROM `sqlite_sequence` WHERE `name` = \'public.test_users\';' - } - ); - }); - }); - - describe('delete without limit', () => { - const options = { - table: User.getTableName(), - where: { name: 'foo' }, - limit: null, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.deleteQuery( - options.table, - options.where, - options, - User - ), { - default: "DELETE FROM [public.test_users] WHERE `name` = 'foo'", - postgres: 'DELETE FROM "public"."test_users" WHERE "name" = \'foo\'', - mariadb: 'DELETE FROM `public`.`test_users` WHERE `name` = \'foo\'', - sqlite: "DELETE FROM `public.test_users` WHERE `name` = 'foo'", - mssql: "DELETE FROM [public].[test_users] WHERE [name] = N'foo'; SELECT @@ROWCOUNT AS AFFECTEDROWS;" - } - ); - }); - }); - - describe('delete with limit', () => { - const options = { - table: User.getTableName(), - where: { name: "foo';DROP TABLE mySchema.myTable;" }, - limit: 10, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.deleteQuery( - options.table, - options.where, - options, - User - ), { - postgres: 'DELETE FROM "public"."test_users" WHERE "id" IN (SELECT "id" FROM "public"."test_users" WHERE "name" = \'foo\'\';DROP TABLE mySchema.myTable;\' LIMIT 10)', - mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10", - sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = 'foo'';DROP TABLE mySchema.myTable;' LIMIT 10)", - mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;", - default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10" - } - ); - }); - }); - - describe('delete with limit and without model', () => { - const options = { - table: User.getTableName(), - where: { name: "foo';DROP TABLE mySchema.myTable;" }, - limit: 10, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - let query; - try { - query = sql.deleteQuery( - options.table, - options.where, - options, - null - ); - } catch (err) { - query = err; - } - - return expectsql( - query, { - postgres: new Error('Cannot LIMIT delete without a model.'), - mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10", - sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = 'foo'';DROP TABLE mySchema.myTable;' LIMIT 10)", - mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;", - default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10" - } - ); - }); - }); - - describe('delete when the primary key has a different field name', () => { - const User = current.define('test_user', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - field: 'test_user_id' - } - }, { - timestamps: false, - schema: 'public' - }); - - const options = { - table: 'test_user', - where: { 'test_user_id': 100 }, - type: QueryTypes.BULKDELETE - }; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.deleteQuery( - options.table, - options.where, - options, - User - ), { - postgres: 'DELETE FROM "test_user" WHERE "test_user_id" = 100', - sqlite: 'DELETE FROM `test_user` WHERE `test_user_id` = 100', - mssql: 'DELETE FROM [test_user] WHERE [test_user_id] = 100; SELECT @@ROWCOUNT AS AFFECTEDROWS;', - default: 'DELETE FROM [test_user] WHERE [test_user_id] = 100' - } - ); - }); - }); - - describe('delete with undefined parameter in where', () => { - const options = { - table: User.getTableName(), - type: QueryTypes.BULKDELETE, - where: { name: undefined }, - limit: null - }; - - it(util.inspect(options, { depth: 2 }), () => { - const sqlOrError = _.attempt( - sql.deleteQuery.bind(sql), - options.table, - options.where, - options, - User - ); - return expectsql(sqlOrError, { - default: new Error('WHERE parameter "name" has invalid "undefined" value') - }); - }); - }); - }); -}); diff --git a/test/unit/sql/enum.test.js b/test/unit/sql/enum.test.js deleted file mode 100644 index d9edb6fa4587..000000000000 --- a/test/unit/sql/enum.test.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize, - sql = current.dialect.queryGenerator, - expect = require('chai').expect; - - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('enum', () => { - if (Support.getTestDialect() === 'postgres') { - const FooUser = current.define('user', { - mood: DataTypes.ENUM('happy', 'sad') - }, { - schema: 'foo' - }); - - const PublicUser = current.define('user', { - mood: { - type: DataTypes.ENUM('happy', 'sad'), - field: 'theirMood' - } - }); - - describe('pgEnumName', () => { - it('does not add schema when options: { schema: false }', () => { - expect(sql.pgEnumName(PublicUser.getTableName(), 'mood', { schema: false })) - .to.equal('"enum_users_mood"'); - expect(sql.pgEnumName(FooUser.getTableName(), 'theirMood', { schema: false })) - .to.equal('"enum_users_theirMood"'); - }); - - it('properly quotes both the schema and the enum name', () => { - expect(sql.pgEnumName(PublicUser.getTableName(), 'mood', PublicUser.rawAttributes.mood.type)) - .to.equal('"public"."enum_users_mood"'); - expect(sql.pgEnumName(FooUser.getTableName(), 'theirMood', FooUser.rawAttributes.mood.type)) - .to.equal('"foo"."enum_users_theirMood"'); - }); - }); - - describe('pgEnum', () => { - it('uses schema #3171', () => { - expectsql(sql.pgEnum(FooUser.getTableName(), 'mood', FooUser.rawAttributes.mood.type), { - postgres: 'CREATE TYPE "foo"."enum_users_mood" AS ENUM(\'happy\', \'sad\');' - }); - }); - - it('does add schema when public', () => { - expectsql(sql.pgEnum(PublicUser.getTableName(), 'theirMood', PublicUser.rawAttributes.mood.type), { - postgres: 'CREATE TYPE "public"."enum_users_theirMood" AS ENUM(\'happy\', \'sad\');' - }); - }); - }); - - describe('pgEnumAdd', () => { - it('creates alter type with exists on 9.4', () => { - current.options.databaseVersion = '9.4.0'; - expectsql(sql.pgEnumAdd(PublicUser.getTableName(), 'mood', 'neutral', { after: 'happy' }), { - postgres: 'ALTER TYPE "public"."enum_users_mood" ADD VALUE IF NOT EXISTS \'neutral\' AFTER \'happy\'' - }); - }); - - it('creates alter type without exists on 9.2 ', () => { - current.options.databaseVersion = '9.2.0'; - expectsql(sql.pgEnumAdd(PublicUser.getTableName(), 'mood', 'neutral', { after: 'happy' }), { - postgres: 'ALTER TYPE "public"."enum_users_mood" ADD VALUE \'neutral\' AFTER \'happy\'' - }); - }); - }); - - describe('pgListEnums', () => { - it('works with schema #3563', () => { - expectsql(sql.pgListEnums(FooUser.getTableName(), 'mood'), { - postgres: 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = \'foo\' AND t.typname=\'enum_users_mood\' GROUP BY 1' - }); - }); - - it('uses the default schema if no options given', () => { - expectsql(sql.pgListEnums(), { - postgres: 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = \'public\' GROUP BY 1' - }); - }); - }); - } - }); -}); - diff --git a/test/unit/sql/generateJoin.test.js b/test/unit/sql/generateJoin.test.js deleted file mode 100644 index ba29f63a5924..000000000000 --- a/test/unit/sql/generateJoin.test.js +++ /dev/null @@ -1,351 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../lib/sequelize'), - util = require('util'), - _ = require('lodash'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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 - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('generateJoin', () => { - const testsql = function(path, options, expectation) { - - const name = `${path}, ${util.inspect(options, { depth: 10 })}`; - - Sequelize.Model._conformIncludes(options); - options = Sequelize.Model._validateIncludedElements(options); - - const include = _.at(options, path)[0]; - - it(name, () => { - - const join = sql.generateJoin(include, - { - options, - subQuery: options.subQuery === undefined ? options.limit && options.hasMultiAssociation : options.subQuery - } - ); - - return expectsql(`${join.join} ${join.body} ON ${join.condition}`, expectation); - }); - }; - - const User = current.define('User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id_user' - }, - companyId: { - type: DataTypes.INTEGER, - field: 'company_id' - } - }, { - tableName: 'user' - }); - const Task = current.define('Task', { - title: Sequelize.STRING, - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - } - }, { - tableName: 'task' - }); - - const Company = current.define('Company', { - name: Sequelize.STRING, - ownerId: { - type: Sequelize.INTEGER, - field: 'owner_id' - }, - public: { - type: Sequelize.BOOLEAN - } - }, { - tableName: 'company' - }); - - const Profession = current.define('Profession', { - name: Sequelize.STRING - }, { - tableName: 'profession' - }); - - User.Tasks = User.hasMany(Task, { as: 'Tasks', foreignKey: 'userId' }); - User.Company = User.belongsTo(Company, { foreignKey: 'companyId' }); - User.Profession = User.belongsTo(Profession, { foreignKey: 'professionId' }); - Profession.Professionals = Profession.hasMany(User, { as: 'Professionals', foreignKey: 'professionId' }); - Company.Employees = Company.hasMany(User, { as: 'Employees', foreignKey: 'companyId' }); - Company.Owner = Company.belongsTo(User, { as: 'Owner', foreignKey: 'ownerId' }); - - /* - * BelongsTo - */ - - testsql( - 'include[0]', - { - model: User, - include: [ - User.Company - ] - }, - { - default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id]' - } - ); - - testsql( - 'include[0]', - { - model: User, - include: [ - { - association: User.Company, - where: { public: true }, - or: true - } - ] - }, - { - default: 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = true', - sqlite: 'INNER JOIN `company` AS `Company` ON `User`.`company_id` = `Company`.`id` OR `Company`.`public` = 1', - mssql: 'INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = 1' - } - ); - - testsql( - 'include[0].include[0]', - { - model: Profession, - include: [ - { - association: Profession.Professionals, - limit: 3, - include: [ - User.Company - ] - } - ] - }, - { - default: 'LEFT OUTER JOIN [company] AS [Professionals->Company] ON [Professionals].[company_id] = [Professionals->Company].[id]' - } - ); - - testsql( - 'include[0]', - { - model: User, - subQuery: true, - include: [ - User.Company - ] - }, - { - default: 'LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]' - } - ); - - testsql( - 'include[0]', - { - model: User, - subQuery: true, - include: [ - { - association: User.Company, required: false, where: { name: 'ABC' } - } - ] - }, - { - default: "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = 'ABC'", - mssql: "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id] AND [Company].[name] = N'ABC'" - } - ); - - testsql( - 'include[0]', - { - model: User, - subQuery: true, - include: [ - { - association: User.Company, right: true - } - ] - }, - { - default: `${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]` - } - ); - - testsql( - 'include[0].include[0]', - { - subQuery: true, - model: User, - include: [ - { - association: User.Company, include: [ - Company.Owner - ] - } - ] - - }, - { - default: 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]' - } - ); - - testsql( - 'include[0].include[0].include[0]', - { - model: User, - subQuery: true, - include: [ - { - association: User.Company, - include: [{ - association: Company.Owner, - include: [ - User.Profession - ] - }] - } - ] - }, - { default: 'LEFT OUTER JOIN [profession] AS [Company->Owner->Profession] ON [Company->Owner].[professionId] = [Company->Owner->Profession].[id]' } - ); - - testsql( - 'include[0].include[0]', - { - model: User, - subQuery: true, - include: [ - { - association: User.Company, - required: true, - include: [ - Company.Owner - ] - } - ] - }, - { default: 'LEFT OUTER JOIN [user] AS [Company->Owner] ON [Company].[owner_id] = [Company->Owner].[id_user]' } - ); - - testsql( - 'include[0]', - { - model: User, - subQuery: true, - include: [ - { association: User.Company, required: true } - ] - }, - { - default: 'INNER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]' - } - ); - - // /* - // * HasMany - // */ - - testsql( - 'include[0]', - { - model: User, - include: [ - User.Tasks - ] - }, - { default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id_user] = [Tasks].[user_id]' } - ); - - testsql( - 'include[0]', - { - model: User, - subQuery: true, - include: [ - User.Tasks - ] - }, - { - // The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of - default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]' - } - ); - - testsql( - 'include[0]', - { - model: User, - include: [ - { - association: User.Tasks, on: { - [Op.or]: [ - { '$User.id_user$': { [Op.col]: 'Tasks.user_id' } }, - { '$Tasks.user_id$': 2 } - ] - } - } - ] - }, { default: 'LEFT OUTER JOIN [task] AS [Tasks] ON ([User].[id_user] = [Tasks].[user_id] OR [Tasks].[user_id] = 2)' } - ); - - testsql( - 'include[0]', - { - model: User, - include: [ - { - association: User.Tasks, - on: { 'user_id': { [Op.col]: 'User.alternative_id' } } - } - ] - }, { default: 'LEFT OUTER JOIN [task] AS [Tasks] ON [Tasks].[user_id] = [User].[alternative_id]' } - ); - - testsql( - 'include[0].include[0]', - { - subQuery: true, - model: User, - include: [ - { - association: User.Company, - include: [ - { - association: Company.Owner, - on: { - [Op.or]: [ - { '$Company.owner_id$': { [Op.col]: 'Company.Owner.id_user' } }, - { '$Company.Owner.id_user$': 2 } - ] - } - } - ] - } - ] - - }, - { - default: 'LEFT OUTER JOIN [user] AS [Company->Owner] ON ([Company].[owner_id] = [Company->Owner].[id_user] OR [Company->Owner].[id_user] = 2)' - } - ); - - }); -}); diff --git a/test/unit/sql/get-constraint-snippet.test.js b/test/unit/sql/get-constraint-snippet.test.js deleted file mode 100644 index 997d45e53ba2..000000000000 --- a/test/unit/sql/get-constraint-snippet.test.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const current = Support.sequelize; -const expectsql = Support.expectsql; -const sql = current.dialect.queryGenerator; -const expect = require('chai').expect; -const Op = Support.Sequelize.Op; - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('getConstraintSnippet', () => { - describe('unique', () => { - it('naming', () => { - expectsql(sql.getConstraintSnippet('myTable', { - name: 'unique_mytable_mycolumn', - type: 'UNIQUE', - fields: ['myColumn'] - }), { - default: 'CONSTRAINT [unique_mytable_mycolumn] UNIQUE ([myColumn])' - }); - }); - - it('should create constraint name if not passed', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'UNIQUE', - fields: ['myColumn'] - }), { - default: 'CONSTRAINT [myTable_myColumn_uk] UNIQUE ([myColumn])' - }); - }); - - it('should work with multiple columns', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'UNIQUE', - fields: ['myColumn1', 'myColumn2'] - }), { - default: 'CONSTRAINT [myTable_myColumn1_myColumn2_uk] UNIQUE ([myColumn1], [myColumn2])' - }); - }); - }); - - describe('check', () => { - it('naming', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'CHECK', - fields: [{ - attribute: 'myColumn' - }], - where: { - myColumn: ['value1', 'value2', 'value3'] - } - }), { - mssql: "CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN (N'value1', N'value2', N'value3'))", - default: "CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN ('value1', 'value2', 'value3'))" - }); - }); - - it('where', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'CHECK', - fields: ['myColumn'], - name: 'check_mycolumn_where', - where: { - myColumn: { - [Op.and]: { - [Op.gt]: 50, - [Op.lt]: 100 - } - } - } - }), { - default: 'CONSTRAINT [check_mycolumn_where] CHECK (([myColumn] > 50 AND [myColumn] < 100))' - }); - }); - - }); - - describe('primary key', () => { - it('naming', () => { - expectsql(sql.getConstraintSnippet('myTable', { - name: 'primary_mytable_mycolumn', - type: 'primary key', - fields: ['myColumn'] - }), { - default: 'CONSTRAINT [primary_mytable_mycolumn] PRIMARY KEY ([myColumn])' - }); - }); - - it('should create constraint name if not passed', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'PRIMARY KEY', - fields: ['myColumn'] - }), { - default: 'CONSTRAINT [myTable_myColumn_pk] PRIMARY KEY ([myColumn])' - }); - }); - - it('should work with multiple columns', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'PRIMARY KEY', - fields: ['myColumn1', 'myColumn2'] - }), { - default: 'CONSTRAINT [myTable_myColumn1_myColumn2_pk] PRIMARY KEY ([myColumn1], [myColumn2])' - }); - }); - }); - - describe('foreign key', () => { - it('naming', () => { - expectsql(sql.getConstraintSnippet('myTable', { - name: 'foreignkey_mytable_mycolumn', - type: 'foreign key', - fields: ['myColumn'], - references: { - table: 'myOtherTable', - field: 'id' - } - }), { - default: 'CONSTRAINT [foreignkey_mytable_mycolumn] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id])' - }); - }); - - it('uses onDelete, onUpdate', () => { - expectsql(sql.getConstraintSnippet('myTable', { - type: 'foreign key', - fields: ['myColumn'], - references: { - table: 'myOtherTable', - field: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }), { - default: 'CONSTRAINT [myTable_myColumn_myOtherTable_fk] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]) ON UPDATE CASCADE ON DELETE CASCADE' - }); - }); - - it('errors if references object is not passed', () => { - expect(sql.getConstraintSnippet.bind(sql, 'myTable', { - type: 'foreign key', - fields: ['myColumn'] - })).to.throw('references object with table and field must be specified'); - }); - - - }); - - describe('validation', () => { - it('throw error on invalid type', () => { - expect(sql.getConstraintSnippet.bind(sql, 'myTable', { type: 'some type', fields: [] })).to.throw('some type is invalid'); - }); - }); - }); -}); diff --git a/test/unit/sql/group.test.js b/test/unit/sql/group.test.js deleted file mode 100644 index 4059c6477350..000000000000 --- a/test/unit/sql/group.test.js +++ /dev/null @@ -1,54 +0,0 @@ -'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 deleted file mode 100644 index 18f6161c3ad3..000000000000 --- a/test/unit/sql/index.test.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict'; - -const Support = require('../support'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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 - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('addIndex', () => { - it('naming', () => { - expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), { - default: 'CREATE INDEX [table_column1_column2] ON [table] ([column1], [column2])', - mariadb: 'ALTER TABLE `table` ADD INDEX `table_column1_column2` (`column1`, `column2`)', - mysql: 'ALTER TABLE `table` ADD INDEX `table_column1_column2` (`column1`, `column2`)' - }); - - if (current.dialect.supports.schemas) { - expectsql(sql.addIndexQuery('schema.table', ['column1', 'column2'], {}), { - default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])', - mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)' - }); - - expectsql(sql.addIndexQuery({ - schema: 'schema', - tableName: 'table' - }, ['column1', 'column2'], {}, 'schema_table'), { - default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])', - mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)' - }); - - expectsql(sql.addIndexQuery(sql.quoteTable(sql.addSchema({ - _schema: 'schema', - tableName: 'table' - })), ['column1', 'column2'], {}), { - default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])', - mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)' - }); - } - }); - - it('type and using', () => { - expectsql(sql.addIndexQuery('User', ['fieldC'], { - type: 'FULLTEXT', - concurrently: true - }), { - sqlite: 'CREATE INDEX `user_field_c` ON `User` (`fieldC`)', - mssql: 'CREATE FULLTEXT INDEX [user_field_c] ON [User] ([fieldC])', - postgres: 'CREATE INDEX CONCURRENTLY "user_field_c" ON "User" ("fieldC")', - mariadb: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)', - mysql: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)' - }); - - expectsql(sql.addIndexQuery('User', ['fieldB', { attribute: 'fieldA', collate: 'en_US', order: 'DESC', length: 5 }], { - name: 'a_b_uniq', - unique: true, - using: 'BTREE', - parser: 'foo' - }), { - sqlite: 'CREATE UNIQUE INDEX `a_b_uniq` ON `User` (`fieldB`, `fieldA` COLLATE `en_US` DESC)', - mssql: 'CREATE UNIQUE INDEX [a_b_uniq] ON [User] ([fieldB], [fieldA] DESC)', - postgres: 'CREATE UNIQUE INDEX "a_b_uniq" ON "User" USING BTREE ("fieldB", "fieldA" COLLATE "en_US" DESC)', - mariadb: 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo', - mysql: 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo' - }); - }); - - it('POJO field', () => { - expectsql(sql.addIndexQuery('table', [{ attribute: 'column', collate: 'BINARY', length: 5, order: 'DESC' }], {}, 'table'), { - default: 'CREATE INDEX [table_column] ON [table] ([column] COLLATE [BINARY] DESC)', - mssql: 'CREATE INDEX [table_column] ON [table] ([column] DESC)', - mariadb: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)', - mysql: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)' - }); - }); - - it('function', () => { - expectsql(sql.addIndexQuery('table', [current.fn('UPPER', current.col('test'))], { name: 'myindex' }), { - default: 'CREATE INDEX [myindex] ON [table] (UPPER([test]))', - mariadb: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))', - mysql: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))' - }); - }); - - if (current.dialect.supports.index.using === 2) { - it('USING', () => { - expectsql(sql.addIndexQuery('table', { - fields: ['event'], - using: 'gin' - }), { - postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event")' - }); - }); - } - - if (current.dialect.supports.index.where) { - it('WHERE', () => { - expectsql(sql.addIndexQuery('table', { - fields: ['type'], - where: { - type: 'public' - } - }), { - sqlite: 'CREATE INDEX `table_type` ON `table` (`type`) WHERE `type` = \'public\'', - postgres: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" = \'public\'', - mssql: 'CREATE INDEX [table_type] ON [table] ([type]) WHERE [type] = N\'public\'' - }); - - expectsql(sql.addIndexQuery('table', { - fields: ['type'], - where: { - type: { - [Op.or]: [ - 'group', - 'private' - ] - } - } - }), { - sqlite: 'CREATE INDEX `table_type` ON `table` (`type`) WHERE (`type` = \'group\' OR `type` = \'private\')', - postgres: 'CREATE INDEX "table_type" ON "table" ("type") WHERE ("type" = \'group\' OR "type" = \'private\')', - mssql: 'CREATE INDEX [table_type] ON [table] ([type]) WHERE ([type] = N\'group\' OR [type] = N\'private\')' - }); - - expectsql(sql.addIndexQuery('table', { - fields: ['type'], - where: { - type: { - [Op.ne]: null - } - } - }), { - sqlite: 'CREATE INDEX `table_type` ON `table` (`type`) WHERE `type` IS NOT NULL', - postgres: 'CREATE INDEX "table_type" ON "table" ("type") WHERE "type" IS NOT NULL', - mssql: 'CREATE INDEX [table_type] ON [table] ([type]) WHERE [type] IS NOT NULL' - }); - }); - } - - if (current.dialect.supports.JSONB) { - it('operator', () => { - expectsql(sql.addIndexQuery('table', { - fields: ['event'], - using: 'gin', - operator: 'jsonb_path_ops' - }), { - postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event" jsonb_path_ops)' - }); - }); - } - - if (current.dialect.name === 'postgres') { - it('show indexes', () => { - expectsql(sql.showIndexesQuery('table'), { - postgres: 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' + - 'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' + - 'AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a ' + - 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' + - 't.relkind = \'r\' and t.relname = \'table\' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;' - }); - - expectsql(sql.showIndexesQuery({ tableName: 'table', schema: 'schema' }), { - postgres: 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' + - 'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' + - 'AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a, pg_namespace s ' + - 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' + - 't.relkind = \'r\' and t.relname = \'table\' AND s.oid = t.relnamespace AND s.nspname = \'schema\' ' + - 'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;' - }); - }); - } - - 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', () => { - it('naming', () => { - expectsql(sql.removeIndexQuery('table', ['column1', 'column2'], {}, 'table'), { - mariadb: 'DROP INDEX `table_column1_column2` ON `table`', - mysql: 'DROP INDEX `table_column1_column2` ON `table`', - mssql: 'DROP INDEX [table_column1_column2] ON [table]', - default: 'DROP INDEX IF EXISTS [table_column1_column2]' - }); - }); - }); -}); diff --git a/test/unit/sql/insert.test.js b/test/unit/sql/insert.test.js deleted file mode 100644 index 34b09acdaf27..000000000000 --- a/test/unit/sql/insert.test.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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('insert', () => { - it('with temp table for trigger', () => { - const User = Support.sequelize.define('user', { - username: { - type: DataTypes.STRING, - field: 'user_name' - } - }, { - timestamps: false, - hasTrigger: true - }); - - const options = { - returning: true, - hasTrigger: true - }; - expectsql(sql.insertQuery(User.tableName, { user_name: 'triggertest' }, User.rawAttributes, options), - { - query: { - mssql: 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] INTO @tmp VALUES ($1); SELECT * FROM @tmp;', - postgres: 'INSERT INTO "users" ("user_name") VALUES ($1) RETURNING "id","user_name";', - default: 'INSERT INTO `users` (`user_name`) VALUES ($1);' - }, - bind: ['triggertest'] - }); - - }); - }); - - describe('dates', () => { - it('formats the date correctly when inserting', () => { - const timezoneSequelize = Support.createSequelizeInstance({ - timezone: Support.getTestDialect() === 'sqlite' ? '+00:00' : 'CET' - }); - - const User = timezoneSequelize.define('user', { - date: { - type: DataTypes.DATE - } - }, { - timestamps: false - }); - - 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);', - mssql: 'INSERT INTO [users] ([date]) VALUES ($1);', - default: 'INSERT INTO `users` (`date`) VALUES ($1);' - }, - bind: { - sqlite: ['2015-01-20 00:00:00.000 +00:00'], - mysql: ['2015-01-20 01:00:00'], - mariadb: ['2015-01-20 01:00:00.000'], - default: ['2015-01-20 01:00:00.000 +01:00'] - } - }); - }); - - it('formats date correctly when sub-second precision is explicitly specified', () => { - const timezoneSequelize = Support.createSequelizeInstance({ - timezone: Support.getTestDialect() === 'sqlite' ? '+00:00' : 'CET' - }); - - const User = timezoneSequelize.define('user', { - date: { - type: DataTypes.DATE(3) - } - }, { - timestamps: false - }); - - 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);', - mssql: 'INSERT INTO [users] ([date]) VALUES ($1);', - default: 'INSERT INTO `users` (`date`) VALUES ($1);' - }, - bind: { - sqlite: ['2015-01-20 01:02:03.089 +00:00'], - mariadb: ['2015-01-20 02:02:03.089'], - mysql: ['2015-01-20 02:02:03.089'], - default: ['2015-01-20 02:02:03.089 +01:00'] - } - }); - }); - }); - - describe('strings', () => { - it('formats null characters correctly when inserting', () => { - const User = Support.sequelize.define('user', { - username: { - type: DataTypes.STRING, - field: 'user_name' - } - }, { - timestamps: false - }); - - expectsql(sql.insertQuery(User.tableName, { user_name: 'null\0test' }, User.rawAttributes), - { - query: { - postgres: 'INSERT INTO "users" ("user_name") VALUES ($1);', - mssql: 'INSERT INTO [users] ([user_name]) VALUES ($1);', - default: 'INSERT INTO `users` (`user_name`) VALUES ($1);' - }, - bind: { - postgres: ['null\u0000test'], - default: ['null\0test'] - } - }); - }); - }); - - describe('bulkCreate', () => { - it('bulk create with onDuplicateKeyUpdate', () => { - const User = Support.sequelize.define('user', { - username: { - type: DataTypes.STRING, - field: 'user_name', - primaryKey: true - }, - password: { - type: DataTypes.STRING, - field: 'pass_word' - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at' - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at' - } - }, { - timestamps: true - }); - - // mapping primary keys to their "field" override values - const primaryKeys = User.primaryKeyAttributes.map(attr => User.rawAttributes[attr].field || attr); - - expectsql(sql.bulkInsertQuery(User.tableName, [{ user_name: 'testuser', pass_word: '12345' }], { updateOnDuplicate: ['user_name', 'pass_word', 'updated_at'], upsertKeys: primaryKeys }, User.fieldRawAttributesMap), - { - default: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\');', - postgres: '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";', - mssql: 'INSERT INTO [users] ([user_name],[pass_word]) VALUES (N\'testuser\',N\'12345\');', - mariadb: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);', - mysql: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);', - 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`;' - }); - }); - }); -}); diff --git a/test/unit/sql/json.test.js b/test/unit/sql/json.test.js deleted file mode 100644 index ac092134f1b7..000000000000 --- a/test/unit/sql/json.test.js +++ /dev/null @@ -1,185 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expect = require('chai').expect, - expectsql = Support.expectsql, - Sequelize = Support.Sequelize, - current = Support.sequelize, - 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) { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('JSON', () => { - describe('escape', () => { - it('plain string', () => { - expectsql(sql.escape('string', { type: new DataTypes.JSON() }), { - default: '\'"string"\'', - mariadb: '\'\\"string\\"\'', - mysql: '\'\\"string\\"\'' - }); - }); - - it('plain int', () => { - expectsql(sql.escape(0, { type: new DataTypes.JSON() }), { - default: '\'0\'' - }); - expectsql(sql.escape(123, { type: new DataTypes.JSON() }), { - default: '\'123\'' - }); - }); - - it('boolean', () => { - expectsql(sql.escape(true, { type: new DataTypes.JSON() }), { - default: '\'true\'' - }); - expectsql(sql.escape(false, { type: new DataTypes.JSON() }), { - default: '\'false\'' - }); - }); - - it('NULL', () => { - expectsql(sql.escape(null, { type: new DataTypes.JSON() }), { - default: 'NULL' - }); - }); - - it('nested object', () => { - expectsql(sql.escape({ some: 'nested', more: { nested: true }, answer: 42 }, { type: new DataTypes.JSON() }), { - default: '\'{"some":"nested","more":{"nested":true},"answer":42}\'', - mariadb: '\'{\\"some\\":\\"nested\\",\\"more\\":{\\"nested\\":true},\\"answer\\":42}\'', - mysql: '\'{\\"some\\":\\"nested\\",\\"more\\":{\\"nested\\":true},\\"answer\\":42}\'' - }); - }); - - if (current.dialect.supports.ARRAY) { - it('array of JSON', () => { - expectsql(sql.escape([ - { some: 'nested', more: { nested: true }, answer: 42 }, - 43, - 'joe' - ], { type: DataTypes.ARRAY(DataTypes.JSON) }), { - postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]' - }); - }); - - if (current.dialect.supports.JSONB) { - it('array of JSONB', () => { - expectsql(sql.escape([ - { some: 'nested', more: { nested: true }, answer: 42 }, - 43, - 'joe' - ], { type: DataTypes.ARRAY(DataTypes.JSONB) }), { - postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSONB[]' - }); - }); - } - } - }); - - describe('path extraction', () => { - it('condition object', () => { - expectsql(sql.whereItemQuery(undefined, Sequelize.json({ id: 1 })), { - postgres: '("id"#>>\'{}\') = \'1\'', - sqlite: "json_extract(`id`,'$') = '1'", - mariadb: "json_unquote(json_extract(`id`,'$')) = '1'", - mysql: "json_unquote(json_extract(`id`,'$')) = '1'" - }); - }); - - it('nested condition object', () => { - expectsql(sql.whereItemQuery(undefined, Sequelize.json({ profile: { id: 1 } })), { - postgres: '("profile"#>>\'{id}\') = \'1\'', - sqlite: "json_extract(`profile`,'$.id') = '1'", - mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'", - mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '1'" - }); - }); - - it('multiple condition object', () => { - expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: { value: 1 }, another: { value: 'string' } })), { - postgres: '("property"#>>\'{value}\') = \'1\' AND ("another"#>>\'{value}\') = \'string\'', - sqlite: "json_extract(`property`,'$.value') = '1' AND json_extract(`another`,'$.value') = 'string'", - mariadb: "json_unquote(json_extract(`property`,'$.value')) = '1' AND json_unquote(json_extract(`another`,'$.value')) = 'string'", - mysql: "json_unquote(json_extract(`property`,'$.\\\"value\\\"')) = '1' AND json_unquote(json_extract(`another`,'$.\\\"value\\\"')) = 'string'" - }); - }); - - it('property array object', () => { - expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: [[4, 6], [8]] })), { - postgres: '("property"#>>\'{0,0}\') = \'4\' AND ("property"#>>\'{0,1}\') = \'6\' AND ("property"#>>\'{1,0}\') = \'8\'', - sqlite: "json_extract(`property`,'$[0][0]') = '4' AND json_extract(`property`,'$[0][1]') = '6' AND json_extract(`property`,'$[1][0]') = '8'", - mariadb: "json_unquote(json_extract(`property`,'$[0][0]')) = '4' AND json_unquote(json_extract(`property`,'$[0][1]')) = '6' AND json_unquote(json_extract(`property`,'$[1][0]')) = '8'", - mysql: "json_unquote(json_extract(`property`,'$[0][0]')) = '4' AND json_unquote(json_extract(`property`,'$[0][1]')) = '6' AND json_unquote(json_extract(`property`,'$[1][0]')) = '8'" - }); - }); - - it('dot notation', () => { - expectsql(sql.whereItemQuery(Sequelize.json('profile.id'), '1'), { - postgres: '("profile"#>>\'{id}\') = \'1\'', - sqlite: "json_extract(`profile`,'$.id') = '1'", - mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'", - mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '1'" - }); - }); - - it('item dot notation array', () => { - expectsql(sql.whereItemQuery(Sequelize.json('profile.id.0.1'), '1'), { - postgres: '("profile"#>>\'{id,0,1}\') = \'1\'', - sqlite: "json_extract(`profile`,'$.id[0][1]') = '1'", - mariadb: "json_unquote(json_extract(`profile`,'$.id[0][1]')) = '1'", - mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"[0][1]')) = '1'" - }); - }); - - it('column named "json"', () => { - expectsql(sql.whereItemQuery(Sequelize.json('json'), '{}'), { - postgres: '("json"#>>\'{}\') = \'{}\'', - sqlite: "json_extract(`json`,'$') = '{}'", - mariadb: "json_unquote(json_extract(`json`,'$')) = '{}'", - mysql: "json_unquote(json_extract(`json`,'$')) = '{}'" - }); - }); - }); - - describe('raw json query', () => { - if (current.dialect.name === 'postgres') { - it('#>> operator', () => { - expectsql(sql.whereItemQuery(Sequelize.json('("data"#>>\'{id}\')'), 'id'), { - postgres: '("data"#>>\'{id}\') = \'id\'' - }); - }); - } - - it('json function', () => { - expectsql(sql.handleSequelizeMethod(Sequelize.json('json(\'{"profile":{"name":"david"}}\')')), { - default: 'json(\'{"profile":{"name":"david"}}\')' - }); - }); - - it('nested json functions', () => { - expectsql(sql.handleSequelizeMethod(Sequelize.json('json_extract(json_object(\'{"profile":null}\'), "profile")')), { - default: 'json_extract(json_object(\'{"profile":null}\'), "profile")' - }); - }); - - it('escaped string argument', () => { - expectsql(sql.handleSequelizeMethod(Sequelize.json('json(\'{"quote":{"single":"\'\'","double":""""},"parenthesis":"())("}\')')), { - default: 'json(\'{"quote":{"single":"\'\'","double":""""},"parenthesis":"())("}\')' - }); - }); - - it('unbalnced statement', () => { - expect(() => sql.handleSequelizeMethod(Sequelize.json('json())'))).to.throw(); - expect(() => sql.handleSequelizeMethod(Sequelize.json('json_extract(json()'))).to.throw(); - }); - - it('separator injection', () => { - expect(() => sql.handleSequelizeMethod(Sequelize.json('json(; DELETE YOLO INJECTIONS; -- )'))).to.throw(); - expect(() => sql.handleSequelizeMethod(Sequelize.json('json(); DELETE YOLO INJECTIONS; -- '))).to.throw(); - }); - }); - }); - }); -} diff --git a/test/unit/sql/offset-limit.test.js b/test/unit/sql/offset-limit.test.js deleted file mode 100644 index 5b5fc3afb36e..000000000000 --- a/test/unit/sql/offset-limit.test.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; - -const Support = require('../support'), - util = require('util'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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('offset/limit', () => { - const testsql = function(options, expectation) { - const model = options.model; - - it(util.inspect(options, { depth: 2 }), () => { - return expectsql( - sql.addLimitAndOffset( - options, - model - ), - expectation - ); - }); - }; - - testsql({ - limit: 10, //when no order by present, one is automagically prepended, test its existence - model: { primaryKeyField: 'id', name: 'tableRef' } - }, { - default: ' LIMIT 10', - mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' - }); - - testsql({ - limit: 10, - order: [ - ['email', 'DESC'] // for MSSQL - ] - }, { - default: ' LIMIT 10', - mssql: ' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' - }); - - testsql({ - limit: 10, - offset: 20, - order: [ - ['email', 'DESC'] // for MSSQL - ] - }, { - default: ' LIMIT 20, 10', - postgres: ' LIMIT 10 OFFSET 20', - mssql: ' OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY' - }); - - testsql({ - limit: "';DELETE FROM user", - order: [ - ['email', 'DESC'] // for MSSQL - ] - }, { - default: " LIMIT ''';DELETE FROM user'", - mariadb: " LIMIT '\\';DELETE FROM user'", - mysql: " LIMIT '\\';DELETE FROM user'", - mssql: " OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY" - }); - - testsql({ - limit: 10, - offset: "';DELETE FROM user", - order: [ - ['email', 'DESC'] // for MSSQL - ] - }, { - sqlite: " LIMIT ''';DELETE FROM user', 10", - postgres: " LIMIT 10 OFFSET ''';DELETE FROM user'", - mariadb: " LIMIT '\\';DELETE FROM user', 10", - 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 deleted file mode 100644 index 355c5e599b5c..000000000000 --- a/test/unit/sql/order.test.js +++ /dev/null @@ -1,407 +0,0 @@ -'use strict'; - -const util = require('util'); -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const Model = require('../../../lib/model'); -const expectsql = Support.expectsql; -const current = Support.sequelize; -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 - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('order', () => { - const testsql = (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 - ); - }); - }; - - // models - const User = Support.sequelize.define('User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id' - }, - name: { - type: DataTypes.STRING, - field: 'name', - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at', - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at', - allowNull: true - } - }, { - tableName: 'user', - timestamps: true - }); - - const Project = Support.sequelize.define('Project', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id' - }, - name: { - type: DataTypes.STRING, - field: 'name', - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at', - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at', - allowNull: true - } - }, { - tableName: 'project', - timestamps: true - }); - - const ProjectUser = Support.sequelize.define('ProjectUser', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id' - }, - userId: { - type: DataTypes.INTEGER, - field: 'user_id', - allowNull: false - }, - projectId: { - type: DataTypes.INTEGER, - field: 'project_id', - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at', - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at', - allowNull: true - } - }, { - tableName: 'project_user', - timestamps: true - }); - - const Task = Support.sequelize.define('Task', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id' - }, - name: { - type: DataTypes.STRING, - field: 'name', - allowNull: false - }, - projectId: { - type: DataTypes.INTEGER, - field: 'project_id', - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at', - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at', - allowNull: true - } - }, { - tableName: 'task', - timestamps: true - }); - - const Subtask = Support.sequelize.define('Subtask', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id' - }, - name: { - type: DataTypes.STRING, - field: 'name', - allowNull: false - }, - taskId: { - type: DataTypes.INTEGER, - field: 'task_id', - allowNull: false - }, - createdAt: { - type: DataTypes.DATE, - field: 'created_at', - allowNull: false - }, - updatedAt: { - type: DataTypes.DATE, - field: 'updated_at', - allowNull: true - } - }, { - tableName: 'subtask', - timestamps: true - }); - - // Relations - User.belongsToMany(Project, { - as: 'ProjectUserProjects', - through: ProjectUser, - foreignKey: 'user_id', - otherKey: 'project_id' - }); - - Project.belongsToMany(User, { - as: 'ProjectUserUsers', - through: ProjectUser, - foreignKey: 'project_id', - otherKey: 'user_id' - }); - - Project.hasMany(Task, { - as: 'Tasks', - foreignKey: 'project_id' - }); - - ProjectUser.belongsTo(User, { - as: 'User', - foreignKey: 'user_id' - }); - - ProjectUser.belongsTo(User, { - as: 'Project', - foreignKey: 'project_id' - }); - - Task.belongsTo(Project, { - as: 'Project', - foreignKey: 'project_id' - }); - - Task.hasMany(Subtask, { - as: 'Subtasks', - foreignKey: 'task_id' - }); - - Subtask.belongsTo(Task, { - as: 'Task', - foreignKey: 'task_id' - }); - - testsql({ - model: Subtask, - attributes: [ - 'id', - 'name', - 'createdAt' - ], - include: Model._validateIncludedElements({ - include: [ - { - association: Subtask.associations.Task, - required: true, - attributes: [ - 'id', - 'name', - 'createdAt' - ], - include: [ - { - association: Task.associations.Project, - required: true, - attributes: [ - 'id', - 'name', - 'createdAt' - ] - } - ] - } - ], - model: Subtask - }).include, - order: [ - // order with multiple simple association syntax with direction - [ - { - model: Task, - as: 'Task' - }, - { - model: Project, - as: 'Project' - }, - 'createdAt', - 'ASC' - ], - // order with multiple simple association syntax without direction - [ - { - model: Task, - as: 'Task' - }, - { - model: Project, - as: 'Project' - }, - 'createdAt' - ], - - // order with simple association syntax with direction - [ - { - model: Task, - as: 'Task' - }, - 'createdAt', - 'ASC' - ], - // order with simple association syntax without direction - [ - { - model: Task, - as: 'Task' - }, - 'createdAt' - ], - - // through model object as array with direction - [Task, Project, 'createdAt', 'ASC'], - // through model object as array without direction - [Task, Project, 'createdAt'], - - // model object as array with direction - [Task, 'createdAt', 'ASC'], - // model object as array without direction - [Task, 'createdAt'], - - // through association object as array with direction - [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'ASC'], - // through association object as array without direction - [Subtask.associations.Task, Task.associations.Project, 'createdAt'], - - // association object as array with direction - [Subtask.associations.Task, 'createdAt', 'ASC'], - // association object as array without direction - [Subtask.associations.Task, 'createdAt'], - - // through association name order as array with direction - ['Task', 'Project', 'createdAt', 'ASC'], - // through association name as array without direction - ['Task', 'Project', 'createdAt'], - - // association name as array with direction - ['Task', 'createdAt', 'ASC'], - // association name as array without direction - ['Task', 'createdAt'], - - // main order as array with direction - ['createdAt', 'ASC'], - // main order as array without direction - ['createdAt'], - // main order as string - 'createdAt' - ] - }, { - default: 'SELECT [Subtask].[id], [Subtask].[name], [Subtask].[createdAt], [Task].[id] AS [Task.id], [Task].[name] AS [Task.name], [Task].[created_at] AS [Task.createdAt], [Task->Project].[id] AS [Task.Project.id], [Task->Project].[name] AS [Task.Project.name], [Task->Project].[created_at] AS [Task.Project.createdAt] FROM [subtask] AS [Subtask] INNER JOIN [task] AS [Task] ON [Subtask].[task_id] = [Task].[id] INNER JOIN [project] AS [Task->Project] ON [Task].[project_id] = [Task->Project].[id] ORDER BY [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Task->Project].[created_at] ASC, [Task->Project].[created_at], [Task].[created_at] ASC, [Task].[created_at], [Subtask].[created_at] ASC, [Subtask].[created_at], [Subtask].[created_at];', - postgres: 'SELECT "Subtask"."id", "Subtask"."name", "Subtask"."createdAt", "Task"."id" AS "Task.id", "Task"."name" AS "Task.name", "Task"."created_at" AS "Task.createdAt", "Task->Project"."id" AS "Task.Project.id", "Task->Project"."name" AS "Task.Project.name", "Task->Project"."created_at" AS "Task.Project.createdAt" FROM "subtask" AS "Subtask" INNER JOIN "task" AS "Task" ON "Subtask"."task_id" = "Task"."id" INNER JOIN "project" AS "Task->Project" ON "Task"."project_id" = "Task->Project"."id" ORDER BY "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Task->Project"."created_at" ASC, "Task->Project"."created_at", "Task"."created_at" ASC, "Task"."created_at", "Subtask"."created_at" ASC, "Subtask"."created_at", "Subtask"."created_at";' - }); - - testsql({ - model: Subtask, - attributes: ['id', 'name'], - order: [ - Support.sequelize.random() - ] - }, { - mssql: 'SELECT [id], [name] FROM [subtask] AS [Subtask] ORDER BY RAND();', - mariadb: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', - mysql: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();', - postgres: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();', - sqlite: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RANDOM();' - }); - - describe('Invalid', () => { - it('Error on invalid association', () => { - return expect(Subtask.findAll({ - order: [ - [Project, 'createdAt', 'ASC'] - ] - })).to.eventually.be.rejectedWith(Error, 'Unable to find a valid association for model, \'Project\''); - }); - - it('Error on invalid structure', () => { - return expect(Subtask.findAll({ - order: [ - [Subtask.associations.Task, 'createdAt', Task.associations.Project, 'ASC'] - ] - })).to.eventually.be.rejectedWith(Error, 'Unknown structure passed to order / group: Project'); - }); - - it('Error when the order is a string', () => { - return expect(Subtask.findAll({ - order: 'i am a silly string' - })).to.eventually.be.rejectedWith(Error, 'Order must be type of array or instance of a valid sequelize method.'); - }); - - it('Error when the order contains a `{raw: "..."}` object', () => { - return expect(Subtask.findAll({ - order: [ - { - raw: 'this should throw an error' - } - ] - })).to.eventually.be.rejectedWith(Error, 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.'); - }); - - it('Error when the order contains a `{raw: "..."}` object wrapped in an array', () => { - return expect(Subtask.findAll({ - order: [ - [ - { - raw: 'this should throw an error' - } - ] - ] - })).to.eventually.be.rejectedWith(Error, 'The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.'); - }); - }); - }); -}); diff --git a/test/unit/sql/remove-column.test.js b/test/unit/sql/remove-column.test.js deleted file mode 100644 index 9b5d186513c0..000000000000 --- a/test/unit/sql/remove-column.test.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const Support = require('../support'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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.name !== 'sqlite') { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('removeColumn', () => { - it('schema', () => { - expectsql(sql.removeColumnQuery({ - schema: 'archive', - tableName: 'user' - }, 'email'), { - mssql: 'ALTER TABLE [archive].[user] DROP COLUMN [email];', - mariadb: 'ALTER TABLE `archive`.`user` DROP `email`;', - mysql: 'ALTER TABLE `archive.user` DROP `email`;', - postgres: 'ALTER TABLE "archive"."user" DROP COLUMN "email";' - }); - }); - }); - }); -} diff --git a/test/unit/sql/remove-constraint.test.js b/test/unit/sql/remove-constraint.test.js deleted file mode 100644 index 76920461d7bc..000000000000 --- a/test/unit/sql/remove-constraint.test.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const current = Support.sequelize; -const expectsql = Support.expectsql; -const sql = current.dialect.queryGenerator; - -if (current.dialect.supports.constraints.dropConstraint) { - describe(Support.getTestDialectTeaser('SQL'), () => { - describe('removeConstraint', () => { - it('naming', () => { - expectsql(sql.removeConstraintQuery('myTable', 'constraint_name'), { - default: 'ALTER TABLE [myTable] DROP CONSTRAINT [constraint_name]' - }); - }); - - if (current.dialect.supports.schemas) { - it('schema', () => { - expectsql(sql.removeConstraintQuery({ - tableName: 'myTable', - schema: 'inspections' - }, 'constraint_name'), { - default: 'ALTER TABLE [inspections].[myTable] DROP CONSTRAINT [constraint_name]' - }); - }); - } - }); - }); -} diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js deleted file mode 100644 index a511bbb66ef3..000000000000 --- a/test/unit/sql/select.test.js +++ /dev/null @@ -1,809 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Model = require('../../../lib/model'), - util = require('util'), - chai = require('chai'), - expect = chai.expect, - expectsql = Support.expectsql, - current = Support.sequelize, - 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 - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('select', () => { - 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 - ); - }); - }; - - testsql({ - table: 'User', - attributes: [ - 'email', - ['first_name', 'firstName'] - ], - where: { - email: 'jon.snow@gmail.com' - }, - order: [ - ['email', 'DESC'] - ], - limit: 10 - }, { - default: "SELECT [email], [first_name] AS [firstName] FROM [User] WHERE [User].[email] = 'jon.snow@gmail.com' ORDER BY [email] DESC LIMIT 10;", - mssql: "SELECT [email], [first_name] AS [firstName] FROM [User] WHERE [User].[email] = N'jon.snow@gmail.com' ORDER BY [email] DESC OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;" - }); - - testsql({ - table: 'User', - attributes: [ - 'email', - ['first_name', 'firstName'], - ['last_name', 'lastName'] - ], - order: [ - ['last_name', 'ASC'] - ], - groupedLimit: { - limit: 3, - on: 'companyId', - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [User].* FROM (${ - [ - `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 1 ORDER BY [last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub`, - `SELECT * FROM (SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 5 ORDER BY [last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [User];` - }); - - (function() { - const User = Support.sequelize.define('user', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id_user' - } - }); - const Project = Support.sequelize.define('project', { - title: DataTypes.STRING - }); - - const ProjectUser = Support.sequelize.define('project_user', { - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - }, - projectId: { - type: DataTypes.INTEGER, - field: 'project_id' - } - }, { timestamps: false }); - - User.Projects = User.belongsToMany(Project, { through: ProjectUser }); - Project.belongsToMany(User, { through: ProjectUser }); - - testsql({ - table: User.getTableName(), - model: User, - attributes: [ - ['id_user', 'id'] - ], - order: [ - ['last_name', 'ASC'] - ], - groupedLimit: { - limit: 3, - on: User.Projects, - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [user].* FROM (${ - [ - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 1 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub`, - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 5 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [user] ORDER BY [subquery_order_0] ASC;` - }); - - testsql({ - table: User.getTableName(), - model: User, - attributes: [ - ['id_user', 'id'] - ], - order: [ - ['last_name', 'ASC'] - ], - groupedLimit: { - limit: 3, - through: { - where: { - status: 1 - } - }, - on: User.Projects, - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [user].* FROM (${ - [ - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 1 AND [project_users].[status] = 1 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub`, - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[last_name] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 5 AND [project_users].[status] = 1 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [user] ORDER BY [subquery_order_0] ASC;` - }); - - testsql({ - table: User.getTableName(), - model: User, - attributes: [ - ['id_user', 'id'] - ], - order: [ - ['id_user', 'ASC'] - ], - where: { - age: { - [Op.gte]: 21 - } - }, - groupedLimit: { - limit: 3, - on: User.Projects, - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [user].* FROM (${ - [ - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 1 WHERE [user].[age] >= 21 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub`, - `SELECT * FROM (SELECT [user].[id_user] AS [id], [user].[id_user] AS [subquery_order_0], [project_users].[user_id] AS [project_users.userId], [project_users].[project_id] AS [project_users.projectId] FROM [users] AS [user] INNER JOIN [project_users] AS [project_users] ON [user].[id_user] = [project_users].[user_id] AND [project_users].[project_id] = 5 WHERE [user].[age] >= 21 ORDER BY [subquery_order_0] ASC${ current.dialect.name === 'mssql' ? ', [user].[id_user]' : ''}${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [user] ORDER BY [subquery_order_0] ASC;` - }); - }()); - - (function() { - const User = Support.sequelize.define('user', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id_user' - }, - email: DataTypes.STRING, - firstName: { - type: DataTypes.STRING, - field: 'first_name' - }, - lastName: { - type: DataTypes.STRING, - field: 'last_name' - } - }, - { - tableName: 'users' - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING, - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - } - }, - { - tableName: 'post' - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'userId', as: 'POSTS' }); - - const Comment = Support.sequelize.define('Comment', { - title: DataTypes.STRING, - postId: { - type: DataTypes.INTEGER, - field: 'post_id' - } - }, - { - tableName: 'comment' - }); - - Post.Comments = Post.hasMany(Comment, { foreignKey: 'postId', as: 'COMMENTS' }); - - const include = Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts - }], - model: User - }).include; - - testsql({ - table: User.getTableName(), - model: User, - include, - attributes: [ - ['id_user', 'id'], - 'email', - ['first_name', 'firstName'], - ['last_name', 'lastName'] - ], - order: [ - ['last_name', 'ASC'] - ], - groupedLimit: { - limit: 3, - on: 'companyId', - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (${ - [ - `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [user].[last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: [['last_name', 'ASC']] })}) AS sub`, - `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [user].[last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: [['last_name', 'ASC']] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id];` - }); - - testsql({ - table: User.getTableName(), - model: User, - include, - attributes: [ - ['id_user', 'id'], - 'email', - ['first_name', 'firstName'], - ['last_name', 'lastName'] - ], - order: [['[last_name]'.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT).replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT), 'ASC']], - limit: 30, - offset: 10, - hasMultiAssociation: true, //must be set only for mssql dialect here - subQuery: true - }, { - default: `${'SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title] FROM (' + - 'SELECT [user].[id_user] AS [id], [user].[email], [user].[first_name] AS [firstName], [user].[last_name] AS [lastName] FROM [users] AS [user] ORDER BY [user].[last_name] ASC'}${ - sql.addLimitAndOffset({ limit: 30, offset: 10, order: [['`user`.`last_name`', 'ASC']] }) - }) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id_user] = [POSTS].[user_id] ORDER BY [user].[last_name] ASC;` - }); - - const nestedInclude = Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts, - include: [{ - attributes: ['title'], - association: Post.Comments - }] - }], - model: User - }).include; - - testsql({ - table: User.getTableName(), - model: User, - include: nestedInclude, - attributes: [ - ['id_user', 'id'], - 'email', - ['first_name', 'firstName'], - ['last_name', 'lastName'] - ], - order: [ - ['last_name', 'ASC'] - ], - groupedLimit: { - limit: 3, - on: 'companyId', - values: [ - 1, - 5 - ] - } - }, { - default: `SELECT [user].*, [POSTS].[id] AS [POSTS.id], [POSTS].[title] AS [POSTS.title], [POSTS->COMMENTS].[id] AS [POSTS.COMMENTS.id], [POSTS->COMMENTS].[title] AS [POSTS.COMMENTS.title] FROM (${ - [ - `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 1 ORDER BY [user].[last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub`, - `SELECT * FROM (SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] AS [user] WHERE [user].[companyId] = 5 ORDER BY [user].[last_name] ASC${sql.addLimitAndOffset({ limit: 3, order: ['last_name', 'ASC'] })}) AS sub` - ].join(current.dialect.supports['UNION ALL'] ? ' UNION ALL ' : ' UNION ') - }) AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id] LEFT OUTER JOIN [comment] AS [POSTS->COMMENTS] ON [POSTS].[id] = [POSTS->COMMENTS].[post_id];` - }); - })(); - - it('include (left outer join)', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];' - }); - }); - - it('include (right outer join)', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts, - right: true - }], - model: User - }).include, - model: User - }, User), { - default: `SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title] FROM [User] AS [User] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];` - }); - }); - - it('include through (right outer join)', () => { - const User = Support.sequelize.define('user', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - field: 'id_user' - } - }); - const Project = Support.sequelize.define('project', { - title: DataTypes.STRING - }); - - const ProjectUser = Support.sequelize.define('project_user', { - userId: { - type: DataTypes.INTEGER, - field: 'user_id' - }, - projectId: { - type: DataTypes.INTEGER, - field: 'project_id' - } - }, { timestamps: false }); - - User.Projects = User.belongsToMany(Project, { through: ProjectUser }); - Project.belongsToMany(User, { through: ProjectUser }); - - expectsql(sql.selectQuery('User', { - attributes: ['id_user', 'id'], - include: Model._validateIncludedElements({ - include: [{ - model: Project, - right: true - }], - model: User - }).include, - model: User - }, User), { - default: `SELECT [user].[id_user], [user].[id], [projects].[id] AS [projects.id], [projects].[title] AS [projects.title], [projects].[createdAt] AS [projects.createdAt], [projects].[updatedAt] AS [projects.updatedAt], [projects->project_user].[user_id] AS [projects.project_user.userId], [projects->project_user].[project_id] AS [projects.project_user.projectId] FROM [User] AS [user] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN ( [project_users] AS [projects->project_user] INNER JOIN [projects] AS [projects] ON [projects].[id] = [projects->project_user].[project_id]) ON [user].[id_user] = [projects->project_user].[user_id];`, - 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\`;` - }); - }); - - it('include (subQuery alias)', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'postaliasname' }); - - 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].*, [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];' - }); - }); - - it('properly stringify IN values as per field definition', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER, - data: DataTypes.BLOB - }, { - freezeTableName: true - }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age', 'data'], - where: { - data: ['123'] - } - }, User), { - postgres: 'SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN (E\'\\\\x313233\');', - mariadb: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');', - mysql: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');', - sqlite: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');', - mssql: 'SELECT [name], [age], [data] FROM [User] AS [User] WHERE [User].[data] IN (0x313233);' - }); - }); - - describe('attribute escaping', () => { - it('plain attributes (1)', () => { - expectsql(sql.selectQuery('User', { - attributes: ['* FROM [User]; DELETE FROM [User];SELECT [id]'.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT).replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT)] - }), { - default: 'SELECT \'* FROM [User]; DELETE FROM [User];SELECT [id]\' FROM [User];', - mssql: 'SELECT [* FROM User; DELETE FROM User;SELECT id] FROM [User];' - }); - }); - - it('plain attributes (2)', () => { - expectsql(sql.selectQuery('User', { - attributes: ['* FROM User; DELETE FROM User;SELECT id'] - }), { - default: 'SELECT [* FROM User; DELETE FROM User;SELECT id] FROM [User];' - }); - }); - - it('plain attributes (3)', () => { - expectsql(sql.selectQuery('User', { - attributes: ['a\', * FROM User; DELETE FROM User;SELECT id'] - }), { - default: "SELECT [a', * FROM User; DELETE FROM User;SELECT id] FROM [User];", - mssql: 'SELECT [a, * FROM User; DELETE FROM User;SELECT id] FROM [User];' - }); - }); - - it('plain attributes (4)', () => { - expectsql(sql.selectQuery('User', { - attributes: ['*, COUNT(*) FROM User; DELETE FROM User;SELECT id'] - }), { - default: 'SELECT [*, COUNT(*) FROM User; DELETE FROM User;SELECT id] FROM [User];' - }); - }); - - it('aliased attributes (1)', () => { - expectsql(sql.selectQuery('User', { - attributes: [ - ['* FROM [User]; DELETE FROM [User];SELECT [id]'.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT).replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT), 'myCol'] - ] - }), { - default: 'SELECT [* FROM User; DELETE FROM User;SELECT id] AS [myCol] FROM [User];' - }); - }); - - it('aliased attributes (2)', () => { - expectsql(sql.selectQuery('User', { - attributes: [ - ['* FROM User; DELETE FROM User;SELECT id', 'myCol'] - ] - }), { - default: 'SELECT [* FROM User; DELETE FROM User;SELECT id] AS [myCol] FROM [User];' - }); - }); - - it('aliased attributes (3)', () => { - expectsql(sql.selectQuery('User', { - attributes: [ - ['id', '* FROM User; DELETE FROM User;SELECT id'] - ] - }), { - default: 'SELECT [id] AS [* FROM User; DELETE FROM User;SELECT id] FROM [User];' - }); - }); - - it('attributes from includes', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: ['* FROM [User]; DELETE FROM [User];SELECT [id]'.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT).replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT)], - association: User.Posts - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM User; DELETE FROM User;SELECT id] AS [Posts.* FROM User; DELETE FROM User;SELECT id] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];' - }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: [ - ['* FROM [User]; DELETE FROM [User];SELECT [id]'.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT).replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT), 'data'] - ], - association: User.Posts - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM User; DELETE FROM User;SELECT id] AS [Posts.data] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];' - }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: [ - ['* FROM User; DELETE FROM User;SELECT id', 'data'] - ], - association: User.Posts - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[* FROM User; DELETE FROM User;SELECT id] AS [Posts.data] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];' - }); - }); - }); - }); - - describe('queryIdentifiers: false', () => { - beforeEach(() => { - sql.options.quoteIdentifiers = false; - }); - afterEach(() => { - sql.options.quoteIdentifiers = true; - }); - - it('*', () => { - expectsql(sql.selectQuery('User'), { - default: 'SELECT * FROM [User];', - postgres: 'SELECT * FROM "User";' - }); - }); - - it('with attributes', () => { - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'] - }), { - default: 'SELECT [name], [age] FROM [User];', - postgres: 'SELECT name, age FROM "User";' - }); - }); - - it('include (left outer join)', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];', - postgres: 'SELECT "User".name, "User".age, Posts.id AS "Posts.id", Posts.title AS "Posts.title" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id;' - }); - }); - - - it('nested include (left outer join)', () => { - const User = Support.sequelize.define('User', { - name: DataTypes.STRING, - age: DataTypes.INTEGER - }, - { - freezeTableName: true - }); - const Post = Support.sequelize.define('Post', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - const Comment = Support.sequelize.define('Comment', { - title: DataTypes.STRING - }, - { - freezeTableName: true - }); - - User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); - Post.Comments = Post.hasMany(Comment, { foreignKey: 'post_id' }); - - expectsql(sql.selectQuery('User', { - attributes: ['name', 'age'], - include: Model._validateIncludedElements({ - include: [{ - attributes: ['title'], - association: User.Posts, - include: [ - { - model: Comment - } - ] - }], - model: User - }).include, - model: User - }, User), { - default: 'SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title], [Posts->Comments].[id] AS [Posts.Comments.id], [Posts->Comments].[title] AS [Posts.Comments.title], [Posts->Comments].[createdAt] AS [Posts.Comments.createdAt], [Posts->Comments].[updatedAt] AS [Posts.Comments.updatedAt], [Posts->Comments].[post_id] AS [Posts.Comments.post_id] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id] LEFT OUTER JOIN [Comment] AS [Posts->Comments] ON [Posts].[id] = [Posts->Comments].[post_id];', - postgres: 'SELECT "User".name, "User".age, Posts.id AS "Posts.id", Posts.title AS "Posts.title", "Posts->Comments".id AS "Posts.Comments.id", "Posts->Comments".title AS "Posts.Comments.title", "Posts->Comments".createdAt AS "Posts.Comments.createdAt", "Posts->Comments".updatedAt AS "Posts.Comments.updatedAt", "Posts->Comments".post_id AS "Posts.Comments.post_id" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id LEFT OUTER JOIN Comment AS "Posts->Comments" ON Posts.id = "Posts->Comments".post_id;' - }); - }); - - }); - - describe('raw query', () => { - it('raw replacements for where', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - where: ['name IN (?)', [1, 'test', 3, 'derp']] - }); - }).to.throw(Error, 'Support for literal replacements in the `where` object has been removed.'); - }); - - it('raw replacements for nested where', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - where: [['name IN (?)', [1, 'test', 3, 'derp']]] - }); - }).to.throw(Error, 'Support for literal replacements in the `where` object has been removed.'); - }); - - it('raw replacements for having', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - having: ['name IN (?)', [1, 'test', 3, 'derp']] - }); - }).to.throw(Error, 'Support for literal replacements in the `where` object has been removed.'); - }); - - it('raw replacements for nested having', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - having: [['name IN (?)', [1, 'test', 3, 'derp']]] - }); - }).to.throw(Error, 'Support for literal replacements in the `where` object has been removed.'); - }); - - it('raw string from where', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - where: 'name = \'something\'' - }); - }).to.throw(Error, 'Support for `{where: \'raw query\'}` has been removed.'); - }); - - it('raw string from having', () => { - expect(() => { - sql.selectQuery('User', { - attributes: ['*'], - having: 'name = \'something\'' - }); - }).to.throw(Error, 'Support for `{where: \'raw query\'}` has been removed.'); - }); - }); -}); diff --git a/test/unit/sql/show-constraints.test.js b/test/unit/sql/show-constraints.test.js deleted file mode 100644 index f6cbc239fddb..000000000000 --- a/test/unit/sql/show-constraints.test.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const Support = require('../support'); -const current = Support.sequelize; -const expectsql = Support.expectsql; -const sql = current.dialect.queryGenerator; - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('showConstraint', () => { - it('naming', () => { - expectsql(sql.showConstraintsQuery('myTable'), { - mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';", - postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';', - mariadb: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';", - mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';", - default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable';" - }); - }); - - it('should add constraint_name to where clause if passed in case of mysql', () => { - expectsql(sql.showConstraintsQuery('myTable', 'myConstraintName'), { - mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';", - postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';', - mariadb: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';", - mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';", - default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable' AND sql LIKE '%myConstraintName%';" - }); - }); - }); -}); diff --git a/test/unit/sql/update.test.js b/test/unit/sql/update.test.js deleted file mode 100644 index 71f99050b380..000000000000 --- a/test/unit/sql/update.test.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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: { - type: DataTypes.STRING, - field: 'user_name' - } - }, { - timestamps: false, - hasTrigger: true - }); - - const options = { - returning: true, - hasTrigger: true - }; - expectsql(sql.updateQuery(User.tableName, { user_name: 'triggertest' }, { id: 2 }, options, User.rawAttributes), - { - query: { - mssql: 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); UPDATE [users] SET [user_name]=$1 OUTPUT INSERTED.[id],INSERTED.[user_name] INTO @tmp WHERE [id] = $2; SELECT * FROM @tmp', - postgres: 'UPDATE "users" SET "user_name"=$1 WHERE "id" = $2 RETURNING "id","user_name"', - default: 'UPDATE `users` SET `user_name`=$1 WHERE `id` = $2' - }, - bind: { - default: ['triggertest', 2] - } - }); - }); - - it('works with limit', () => { - const User = Support.sequelize.define('User', { - username: { - type: DataTypes.STRING - }, - userId: { - type: DataTypes.INTEGER - } - }, { - timestamps: false - }); - - expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), { - query: { - 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)', - default: 'UPDATE [Users] SET [username]=$1 WHERE [username] = $2' - }, - bind: { - default: ['new.username', 'username'] - } - }); - }); - }); -}); diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js deleted file mode 100644 index 7739395f2d8e..000000000000 --- a/test/unit/sql/where.test.js +++ /dev/null @@ -1,1275 +0,0 @@ -'use strict'; - -const Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - QueryTypes = require('../../../lib/query-types'), - util = require('util'), - _ = require('lodash'), - expectsql = Support.expectsql, - current = Support.sequelize, - 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 - -describe(Support.getTestDialectTeaser('SQL'), () => { - describe('whereQuery', () => { - const testsql = function(params, options, expectation) { - if (expectation === undefined) { - expectation = options; - options = undefined; - } - - it(util.inspect(params, { depth: 10 }) + (options && `, ${util.inspect(options)}` || ''), () => { - const sqlOrError = _.attempt(sql.whereQuery.bind(sql), params, options); - return expectsql(sqlOrError, expectation); - }); - }; - - testsql({}, { - default: '' - }); - testsql([], { - default: '' - }); - testsql({ id: undefined }, { - default: new Error('WHERE parameter "id" has invalid "undefined" value') - }); - testsql({ id: 1 }, { - default: 'WHERE [id] = 1' - }); - testsql({ id: 1, user: undefined }, { - default: new Error('WHERE parameter "user" has invalid "undefined" value') - }); - testsql({ id: 1, user: undefined }, { type: QueryTypes.SELECT }, { - default: new Error('WHERE parameter "user" has invalid "undefined" value') - }); - testsql({ id: 1, user: undefined }, { type: QueryTypes.BULKDELETE }, { - default: new Error('WHERE parameter "user" has invalid "undefined" value') - }); - testsql({ id: 1, user: undefined }, { type: QueryTypes.BULKUPDATE }, { - default: new Error('WHERE parameter "user" has invalid "undefined" value') - }); - testsql({ id: 1 }, { prefix: 'User' }, { - 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' })) }), { - default: 'WHERE [yolo.User].[id] = 1', - postgres: 'WHERE "yolo"."User"."id" = 1', - mariadb: 'WHERE `yolo`.`User`.`id` = 1', - mssql: 'WHERE [yolo].[User].[id] = 1' - }); - }); - - testsql({ - name: 'a project', - [Op.or]: [ - { id: [1, 2, 3] }, - { id: { [Op.gt]: 10 } } - ] - }, { - default: "WHERE ([id] IN (1, 2, 3) OR [id] > 10) AND [name] = 'a project'", - mssql: "WHERE ([id] IN (1, 2, 3) OR [id] > 10) AND [name] = N'a project'" - }); - - testsql({ - name: 'a project', - id: { - [Op.or]: [ - [1, 2, 3], - { [Op.gt]: 10 } - ] - } - }, { - default: "WHERE [name] = 'a project' AND ([id] IN (1, 2, 3) OR [id] > 10)", - mssql: "WHERE [name] = N'a project' AND ([id] IN (1, 2, 3) OR [id] > 10)" - }); - - testsql({ - name: 'here is a null char: \0' - }, { - default: "WHERE [name] = 'here is a null char: \\0'", - mssql: "WHERE [name] = N'here is a null char: \0'", - sqlite: "WHERE `name` = 'here is a null char: \0'" - }); - }); - - describe('whereItemQuery', () => { - const testsql = function(key, value, options, expectation) { - if (expectation === undefined) { - expectation = options; - options = undefined; - } - - it(`${String(key)}: ${util.inspect(value, { depth: 10 })}${options && `, ${util.inspect(options)}` || ''}`, () => { - return expectsql(sql.whereItemQuery(key, value, options), expectation); - }); - }; - - testsql(undefined, 'lol=1', { - default: 'lol=1' - }); - - testsql('deleted', null, { - default: '`deleted` IS NULL', - postgres: '"deleted" IS NULL', - mssql: '[deleted] IS NULL' - }); - - describe('Op.in', () => { - testsql('equipment', { - [Op.in]: [1, 3] - }, { - default: '[equipment] IN (1, 3)' - }); - - testsql('equipment', { - [Op.in]: [] - }, { - default: '[equipment] IN (NULL)' - }); - - testsql('muscles', { - [Op.in]: [2, 4] - }, { - default: '[muscles] IN (2, 4)' - }); - - testsql('equipment', { - [Op.in]: current.literal( - '(select order_id from product_orders where product_id = 3)' - ) - }, { - default: '[equipment] IN (select order_id from product_orders where product_id = 3)' - }); - }); - - describe('Buffer', () => { - testsql('field', Buffer.from('Sequelize'), { - postgres: '"field" = E\'\\\\x53657175656c697a65\'', - sqlite: "`field` = X'53657175656c697a65'", - mariadb: "`field` = X'53657175656c697a65'", - mysql: "`field` = X'53657175656c697a65'", - mssql: '[field] = 0x53657175656c697a65' - }); - }); - - describe('Op.not', () => { - testsql('deleted', { - [Op.not]: true - }, { - default: '[deleted] IS NOT true', - mssql: '[deleted] IS NOT 1', - sqlite: '`deleted` IS NOT 1' - }); - - testsql('deleted', { - [Op.not]: null - }, { - default: '[deleted] IS NOT NULL' - }); - - testsql('muscles', { - [Op.not]: 3 - }, { - default: '[muscles] != 3' - }); - }); - - describe('Op.notIn', () => { - testsql('equipment', { - [Op.notIn]: [] - }, { - default: '' - }); - - testsql('equipment', { - [Op.notIn]: [4, 19] - }, { - default: '[equipment] NOT IN (4, 19)' - }); - - testsql('equipment', { - [Op.notIn]: current.literal( - '(select order_id from product_orders where product_id = 3)' - ) - }, { - default: '[equipment] NOT IN (select order_id from product_orders where product_id = 3)' - }); - }); - - describe('Op.ne', () => { - testsql('email', { - [Op.ne]: 'jack.bauer@gmail.com' - }, { - default: "[email] != 'jack.bauer@gmail.com'", - mssql: "[email] != N'jack.bauer@gmail.com'" - }); - }); - - describe('Op.and/Op.or/Op.not', () => { - describe('Op.or', () => { - testsql('email', { - [Op.or]: ['maker@mhansen.io', 'janzeh@gmail.com'] - }, { - default: '([email] = \'maker@mhansen.io\' OR [email] = \'janzeh@gmail.com\')', - mssql: '([email] = N\'maker@mhansen.io\' OR [email] = N\'janzeh@gmail.com\')' - }); - - testsql('rank', { - [Op.or]: { - [Op.lt]: 100, - [Op.eq]: null - } - }, { - default: '([rank] < 100 OR [rank] IS NULL)' - }); - - testsql(Op.or, [ - { email: 'maker@mhansen.io' }, - { email: 'janzeh@gmail.com' } - ], { - default: '([email] = \'maker@mhansen.io\' OR [email] = \'janzeh@gmail.com\')', - mssql: '([email] = N\'maker@mhansen.io\' OR [email] = N\'janzeh@gmail.com\')' - }); - - testsql(Op.or, { - email: 'maker@mhansen.io', - name: 'Mick Hansen' - }, { - default: '([email] = \'maker@mhansen.io\' OR [name] = \'Mick Hansen\')', - mssql: '([email] = N\'maker@mhansen.io\' OR [name] = N\'Mick Hansen\')' - }); - - testsql(Op.or, { - equipment: [1, 3], - muscles: { - [Op.in]: [2, 4] - } - }, { - default: '([equipment] IN (1, 3) OR [muscles] IN (2, 4))' - }); - - testsql(Op.or, [ - { - roleName: 'NEW' - }, { - roleName: 'CLIENT', - type: 'CLIENT' - } - ], { - default: "([roleName] = 'NEW' OR ([roleName] = 'CLIENT' AND [type] = 'CLIENT'))", - mssql: "([roleName] = N'NEW' OR ([roleName] = N'CLIENT' AND [type] = N'CLIENT'))" - }); - - it('sequelize.or({group_id: 1}, {user_id: 2})', function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.or({ group_id: 1 }, { user_id: 2 })), { - default: '([group_id] = 1 OR [user_id] = 2)' - }); - }); - - it("sequelize.or({group_id: 1}, {user_id: 2, role: 'admin'})", function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.or({ group_id: 1 }, { user_id: 2, role: 'admin' })), { - default: "([group_id] = 1 OR ([user_id] = 2 AND [role] = 'admin'))", - mssql: "([group_id] = 1 OR ([user_id] = 2 AND [role] = N'admin'))" - }); - }); - - testsql(Op.or, [], { - default: '0 = 1' - }); - - testsql(Op.or, {}, { - default: '0 = 1' - }); - - it('sequelize.or()', function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.or()), { - default: '0 = 1' - }); - }); - }); - - describe('Op.and', () => { - testsql(Op.and, { - [Op.or]: { - group_id: 1, - user_id: 2 - }, - shared: 1 - }, { - default: '(([group_id] = 1 OR [user_id] = 2) AND [shared] = 1)' - }); - - testsql(Op.and, [ - { - name: { - [Op.like]: '%hello' - } - }, - { - name: { - [Op.like]: 'hello%' - } - } - ], { - default: "([name] LIKE '%hello' AND [name] LIKE 'hello%')", - mssql: "([name] LIKE N'%hello' AND [name] LIKE N'hello%')" - }); - - testsql('rank', { - [Op.and]: { - [Op.ne]: 15, - [Op.between]: [10, 20] - } - }, { - default: '([rank] != 15 AND [rank] BETWEEN 10 AND 20)' - }); - - testsql('name', { - [Op.and]: [ - { [Op.like]: '%someValue1%' }, - { [Op.like]: '%someValue2%' } - ] - }, { - default: "([name] LIKE '%someValue1%' AND [name] LIKE '%someValue2%')", - mssql: "([name] LIKE N'%someValue1%' AND [name] LIKE N'%someValue2%')" - }); - - it('sequelize.and({shared: 1, sequelize.or({group_id: 1}, {user_id: 2}))', function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.and({ shared: 1 }, this.sequelize.or({ group_id: 1 }, { user_id: 2 }))), { - default: '([shared] = 1 AND ([group_id] = 1 OR [user_id] = 2))' - }); - }); - }); - - describe('Op.not', () => { - testsql(Op.not, { - [Op.or]: { - group_id: 1, - user_id: 2 - }, - shared: 1 - }, { - default: 'NOT (([group_id] = 1 OR [user_id] = 2) AND [shared] = 1)' - }); - - testsql(Op.not, [], { - default: '0 = 1' - }); - - testsql(Op.not, {}, { - default: '0 = 1' - }); - }); - }); - - describe('Op.col', () => { - testsql('userId', { - [Op.col]: 'user.id' - }, { - default: '[userId] = [user].[id]' - }); - - testsql('userId', { - [Op.eq]: { - [Op.col]: 'user.id' - } - }, { - default: '[userId] = [user].[id]' - }); - - testsql('userId', { - [Op.gt]: { - [Op.col]: 'user.id' - } - }, { - default: '[userId] > [user].[id]' - }); - - testsql(Op.or, [ - { 'ownerId': { [Op.col]: 'user.id' } }, - { 'ownerId': { [Op.col]: 'organization.id' } } - ], { - default: '([ownerId] = [user].[id] OR [ownerId] = [organization].[id])' - }); - - testsql('$organization.id$', { - [Op.col]: 'user.organizationId' - }, { - default: '[organization].[id] = [user].[organizationId]' - }); - - testsql('$offer.organization.id$', { - [Op.col]: 'offer.user.organizationId' - }, { - default: '[offer->organization].[id] = [offer->user].[organizationId]' - }); - }); - - describe('Op.gt', () => { - testsql('rank', { - [Op.gt]: 2 - }, { - default: '[rank] > 2' - }); - - testsql('created_at', { - [Op.lt]: { - [Op.col]: 'updated_at' - } - }, { - default: '[created_at] < [updated_at]' - }); - }); - - describe('Op.like', () => { - testsql('username', { - [Op.like]: '%swagger' - }, { - default: "[username] LIKE '%swagger'", - mssql: "[username] LIKE N'%swagger'" - }); - }); - - describe('Op.startsWith', () => { - testsql('username', { - [Op.startsWith]: 'swagger' - }, { - 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', () => { - testsql('username', { - [Op.endsWith]: 'swagger' - }, { - 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', () => { - testsql('username', { - [Op.substring]: 'swagger' - }, { - 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', () => { - testsql('date', { - [Op.between]: ['2013-01-01', '2013-01-11'] - }, { - default: "[date] BETWEEN '2013-01-01' AND '2013-01-11'", - mssql: "[date] BETWEEN N'2013-01-01' AND N'2013-01-11'" - }); - - testsql('date', { - [Op.between]: [new Date('2013-01-01'), new Date('2013-01-11')] - }, { - default: "[date] BETWEEN '2013-01-01 00:00:00.000 +00:00' AND '2013-01-11 00:00:00.000 +00:00'", - mysql: "`date` BETWEEN '2013-01-01 00:00:00' AND '2013-01-11 00:00:00'", - mariadb: "`date` BETWEEN '2013-01-01 00:00:00.000' AND '2013-01-11 00:00:00.000'" - }); - - testsql('date', { - [Op.between]: [1356998400000, 1357862400000] - }, { - model: { - rawAttributes: { - date: { - type: new DataTypes.DATE() - } - } - } - }, - { - default: "[date] BETWEEN '2013-01-01 00:00:00.000 +00:00' AND '2013-01-11 00:00:00.000 +00:00'", - mssql: "[date] BETWEEN N'2013-01-01 00:00:00.000 +00:00' AND N'2013-01-11 00:00:00.000 +00:00'" - }); - - testsql('date', { - [Op.between]: ['2012-12-10', '2013-01-02'], - [Op.notBetween]: ['2013-01-04', '2013-01-20'] - }, { - default: "([date] BETWEEN '2012-12-10' AND '2013-01-02' AND [date] NOT BETWEEN '2013-01-04' AND '2013-01-20')", - mssql: "([date] BETWEEN N'2012-12-10' AND N'2013-01-02' AND [date] NOT BETWEEN N'2013-01-04' AND N'2013-01-20')" - }); - }); - - describe('Op.notBetween', () => { - testsql('date', { - [Op.notBetween]: ['2013-01-01', '2013-01-11'] - }, { - default: "[date] NOT BETWEEN '2013-01-01' AND '2013-01-11'", - mssql: "[date] NOT BETWEEN N'2013-01-01' AND N'2013-01-11'" - }); - }); - - if (current.dialect.supports.ARRAY) { - describe('ARRAY', () => { - describe('Op.contains', () => { - testsql('muscles', { - [Op.contains]: [2, 3] - }, { - postgres: '"muscles" @> ARRAY[2,3]' - }); - - testsql('muscles', { - [Op.contained]: [6, 8] - }, { - postgres: '"muscles" <@ ARRAY[6,8]' - }); - - testsql('muscles', { - [Op.contains]: [2, 5] - }, { - field: { - type: DataTypes.ARRAY(DataTypes.INTEGER) - } - }, { - postgres: '"muscles" @> ARRAY[2,5]::INTEGER[]' - }); - - testsql('muscles', { - [Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3'] - }, { - postgres: '"muscles" @> ARRAY[\'stringValue1\',\'stringValue2\',\'stringValue3\']' - }); - - testsql('muscles', { - [Op.contained]: ['stringValue1', 'stringValue2', 'stringValue3'] - }, { - postgres: '"muscles" <@ ARRAY[\'stringValue1\',\'stringValue2\',\'stringValue3\']' - }); - - testsql('muscles', { - [Op.contains]: ['stringValue1', 'stringValue2'] - }, { - field: { - type: DataTypes.ARRAY(DataTypes.STRING) - } - }, { - postgres: '"muscles" @> ARRAY[\'stringValue1\',\'stringValue2\']::VARCHAR(255)[]' - }); - }); - - describe('Op.overlap', () => { - testsql('muscles', { - [Op.overlap]: [3, 11] - }, { - postgres: '"muscles" && ARRAY[3,11]' - }); - }); - - describe('Op.any', () => { - testsql('userId', { - [Op.any]: [4, 5, 6] - }, { - postgres: '"userId" = ANY (ARRAY[4,5,6])' - }); - - testsql('userId', { - [Op.any]: [2, 5] - }, { - field: { - type: DataTypes.ARRAY(DataTypes.INTEGER) - } - }, { - postgres: '"userId" = ANY (ARRAY[2,5]::INTEGER[])' - }); - - describe('Op.values', () => { - testsql('userId', { - [Op.any]: { - [Op.values]: [4, 5, 6] - } - }, { - postgres: '"userId" = ANY (VALUES (4), (5), (6))' - }); - - testsql('userId', { - [Op.any]: { - [Op.values]: [2, 5] - } - }, { - field: { - type: DataTypes.ARRAY(DataTypes.INTEGER) - } - }, { - postgres: '"userId" = ANY (VALUES (2), (5))' - }); - }); - }); - - describe('Op.all', () => { - testsql('userId', { - [Op.all]: [4, 5, 6] - }, { - postgres: '"userId" = ALL (ARRAY[4,5,6])' - }); - - testsql('userId', { - [Op.all]: [2, 5] - }, { - field: { - type: DataTypes.ARRAY(DataTypes.INTEGER) - } - }, { - postgres: '"userId" = ALL (ARRAY[2,5]::INTEGER[])' - }); - - describe('Op.values', () => { - testsql('userId', { - [Op.all]: { - [Op.values]: [4, 5, 6] - } - }, { - postgres: '"userId" = ALL (VALUES (4), (5), (6))' - }); - - testsql('userId', { - [Op.all]: { - [Op.values]: [2, 5] - } - }, { - field: { - type: DataTypes.ARRAY(DataTypes.INTEGER) - } - }, { - postgres: '"userId" = ALL (VALUES (2), (5))' - }); - }); - }); - - describe('Op.like', () => { - testsql('userId', { - [Op.like]: { - [Op.any]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" LIKE ANY (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.iLike]: { - [Op.any]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" ILIKE ANY (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.notLike]: { - [Op.any]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" NOT LIKE ANY (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.notILike]: { - [Op.any]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" NOT ILIKE ANY (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.like]: { - [Op.all]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" LIKE ALL (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.iLike]: { - [Op.all]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" ILIKE ALL (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.notLike]: { - [Op.all]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" NOT LIKE ALL (ARRAY['foo','bar','baz'])" - }); - - testsql('userId', { - [Op.notILike]: { - [Op.all]: ['foo', 'bar', 'baz'] - } - }, { - postgres: "\"userId\" NOT ILIKE ALL (ARRAY['foo','bar','baz'])" - }); - }); - }); - } - - if (current.dialect.supports.RANGE) { - describe('RANGE', () => { - - testsql('range', { - [Op.contains]: new Date(Date.UTC(2000, 1, 1)) - }, { - field: { - type: new DataTypes.postgres.RANGE(DataTypes.DATE) - }, - prefix: 'Timeline' - }, { - postgres: "\"Timeline\".\"range\" @> '2000-02-01 00:00:00.000 +00:00'::timestamptz" - }); - - testsql('range', { - [Op.contains]: [new Date(Date.UTC(2000, 1, 1)), new Date(Date.UTC(2000, 2, 1))] - }, { - field: { - type: new DataTypes.postgres.RANGE(DataTypes.DATE) - }, - prefix: 'Timeline' - }, { - postgres: "\"Timeline\".\"range\" @> '[\"2000-02-01 00:00:00.000 +00:00\",\"2000-03-01 00:00:00.000 +00:00\")'" - }); - - testsql('range', { - [Op.contained]: [new Date(Date.UTC(2000, 1, 1)), new Date(Date.UTC(2000, 2, 1))] - }, { - field: { - type: new DataTypes.postgres.RANGE(DataTypes.DATE) - }, - prefix: 'Timeline' - }, { - postgres: "\"Timeline\".\"range\" <@ '[\"2000-02-01 00:00:00.000 +00:00\",\"2000-03-01 00:00:00.000 +00:00\")'" - }); - - testsql('unboundedRange', { - [Op.contains]: [new Date(Date.UTC(2000, 1, 1)), null] - }, { - field: { - type: new DataTypes.postgres.RANGE(DataTypes.DATE) - }, - prefix: 'Timeline' - }, { - postgres: "\"Timeline\".\"unboundedRange\" @> '[\"2000-02-01 00:00:00.000 +00:00\",)'" - }); - - testsql('unboundedRange', { - [Op.contains]: [-Infinity, Infinity] - }, { - field: { - type: new DataTypes.postgres.RANGE(DataTypes.DATE) - }, - prefix: 'Timeline' - }, { - postgres: "\"Timeline\".\"unboundedRange\" @> '[-infinity,infinity)'" - }); - - testsql('reservedSeats', { - [Op.overlap]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" && '[1,4)'" - }); - - testsql('reservedSeats', { - [Op.adjacent]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" -|- '[1,4)'" - }); - - testsql('reservedSeats', { - [Op.strictLeft]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" << '[1,4)'" - }); - - testsql('reservedSeats', { - [Op.strictRight]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" >> '[1,4)'" - }); - - testsql('reservedSeats', { - [Op.noExtendRight]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" &< '[1,4)'" - }); - - testsql('reservedSeats', { - [Op.noExtendLeft]: [1, 4] - }, { - field: { - type: new DataTypes.postgres.RANGE() - }, - prefix: 'Room' - }, { - postgres: "\"Room\".\"reservedSeats\" &> '[1,4)'" - }); - - }); - } - - if (current.dialect.supports.JSON) { - describe('JSON', () => { - it('sequelize.json("profile.id"), sequelize.cast(2, \'text\')")', function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.json('profile.id', this.sequelize.cast('12346-78912', 'text'))), { - postgres: "(\"profile\"#>>'{id}') = CAST('12346-78912' AS TEXT)", - sqlite: "json_extract(`profile`,'$.id') = CAST('12346-78912' AS TEXT)", - mariadb: "json_unquote(json_extract(`profile`,'$.id')) = CAST('12346-78912' AS CHAR)", - mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = CAST('12346-78912' AS CHAR)" - }); - }); - - it('sequelize.json({profile: {id: "12346-78912", name: "test"}})', function() { - expectsql(sql.whereItemQuery(undefined, this.sequelize.json({ profile: { id: '12346-78912', name: 'test' } })), { - postgres: "(\"profile\"#>>'{id}') = '12346-78912' AND (\"profile\"#>>'{name}') = 'test'", - sqlite: "json_extract(`profile`,'$.id') = '12346-78912' AND json_extract(`profile`,'$.name') = 'test'", - mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '12346-78912' AND json_unquote(json_extract(`profile`,'$.name')) = 'test'", - mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '12346-78912' AND json_unquote(json_extract(`profile`,'$.\\\"name\\\"')) = 'test'" - }); - }); - - testsql('data', { - nested: { - attribute: 'value' - } - }, { - field: { - type: new DataTypes.JSONB() - }, - prefix: 'User' - }, { - mariadb: "json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value'", - mysql: "json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value'", - postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'", - sqlite: "json_extract(`User`.`data`,'$.nested.attribute') = 'value'" - }); - - testsql('data', { - nested: { - [Op.in]: [1, 2] - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) IN (1, 2)", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\"')) AS DECIMAL) IN (1, 2)", - postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)", - sqlite: "CAST(json_extract(`data`,'$.nested') AS DOUBLE PRECISION) IN (1, 2)" - }); - - testsql('data', { - nested: { - [Op.between]: [1, 2] - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) BETWEEN 1 AND 2", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\"')) AS DECIMAL) BETWEEN 1 AND 2", - postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2", - sqlite: "CAST(json_extract(`data`,'$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2" - }); - - testsql('data', { - nested: { - attribute: 'value', - prop: { - [Op.ne]: 'None' - } - } - }, { - field: { - type: new DataTypes.JSONB() - }, - 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')", - postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')", - sqlite: "(json_extract(`User`.`data`,'$.nested.attribute') = 'value' AND json_extract(`User`.`data`,'$.nested.prop') != 'None')" - }); - - testsql('data', { - name: { - last: 'Simpson' - }, - employment: { - [Op.ne]: 'None' - } - }, { - field: { - type: new DataTypes.JSONB() - }, - prefix: 'User' - }, { - mariadb: "(json_unquote(json_extract(`User`.`data`,'$.name.last')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.employment')) != 'None')", - mysql: "(json_unquote(json_extract(`User`.`data`,'$.\\\"name\\\".\\\"last\\\"')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.\\\"employment\\\"')) != 'None')", - postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')", - sqlite: "(json_extract(`User`.`data`,'$.name.last') = 'Simpson' AND json_extract(`User`.`data`,'$.employment') != 'None')" - }); - - testsql('data', { - price: 5, - name: 'Product' - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "(CAST(json_unquote(json_extract(`data`,'$.price')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.name')) = 'Product')", - mysql: "(CAST(json_unquote(json_extract(`data`,'$.\\\"price\\\"')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.\\\"name\\\"')) = 'Product')", - postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 5 AND (\"data\"#>>'{name}') = 'Product')", - sqlite: "(CAST(json_extract(`data`,'$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`,'$.name') = 'Product')" - }); - - testsql('data.nested.attribute', 'value', { - model: { - rawAttributes: { - data: { - type: new DataTypes.JSONB() - } - } - } - }, { - mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'value'", - mysql: "json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value'", - postgres: "(\"data\"#>>'{nested,attribute}') = 'value'", - sqlite: "json_extract(`data`,'$.nested.attribute') = 'value'" - }); - - testsql('data.nested.attribute', 4, { - model: { - rawAttributes: { - data: { - type: new DataTypes.JSON() - } - } - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) = 4", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) AS DECIMAL) = 4", - postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4", - sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) = 4" - }); - - testsql('data.nested.attribute', { - [Op.in]: [3, 7] - }, { - model: { - rawAttributes: { - data: { - type: new DataTypes.JSONB() - } - } - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) IN (3, 7)", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) AS DECIMAL) IN (3, 7)", - postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) IN (3, 7)", - sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)" - }); - - testsql('data', { - nested: { - attribute: { - [Op.gt]: 2 - } - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) AS DECIMAL) > 2", - postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2", - sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) > 2" - }); - - testsql('data', { - nested: { - 'attribute::integer': { - [Op.gt]: 2 - } - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2", - mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) AS DECIMAL) > 2", - postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2", - sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS INTEGER) > 2" - }); - - const dt = new Date(); - testsql('data', { - nested: { - attribute: { - [Op.gt]: dt - } - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: `CAST(json_unquote(json_extract(\`data\`,'$.nested.attribute')) AS DATETIME) > ${sql.escape(dt)}`, - mysql: `CAST(json_unquote(json_extract(\`data\`,'$.\\"nested\\".\\"attribute\\"')) AS DATETIME) > ${sql.escape(dt)}`, - postgres: `CAST(("data"#>>'{nested,attribute}') AS TIMESTAMPTZ) > ${sql.escape(dt)}`, - sqlite: `json_extract(\`data\`,'$.nested.attribute') > ${sql.escape(dt.toISOString())}` - }); - - testsql('data', { - nested: { - attribute: true - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'true'", - mysql: "json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'true'", - postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true", - sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS BOOLEAN) = 1" - }); - - testsql('metaData.nested.attribute', 'value', { - model: { - rawAttributes: { - metaData: { - field: 'meta_data', - fieldName: 'metaData', - type: new DataTypes.JSONB() - } - } - } - }, { - mariadb: "json_unquote(json_extract(`meta_data`,'$.nested.attribute')) = 'value'", - mysql: "json_unquote(json_extract(`meta_data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value'", - postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'", - sqlite: "json_extract(`meta_data`,'$.nested.attribute') = 'value'" - }); - }); - } - - if (current.dialect.supports.JSONB) { - describe('JSONB', () => { - testsql('data', { - [Op.contains]: { - company: 'Magnafone' - } - }, { - field: { - type: new DataTypes.JSONB() - } - }, { - default: '[data] @> \'{"company":"Magnafone"}\'' - }); - }); - } - - if (current.dialect.supports.REGEXP) { - describe('Op.regexp', () => { - testsql('username', { - [Op.regexp]: '^sw.*r$' - }, { - mariadb: "`username` REGEXP '^sw.*r$'", - mysql: "`username` REGEXP '^sw.*r$'", - postgres: '"username" ~ \'^sw.*r$\'' - }); - }); - - describe('Op.regexp', () => { - testsql('newline', { - [Op.regexp]: '^new\nline$' - }, { - mariadb: "`newline` REGEXP '^new\\nline$'", - mysql: "`newline` REGEXP '^new\\nline$'", - postgres: '"newline" ~ \'^new\nline$\'' - }); - }); - - describe('Op.notRegexp', () => { - testsql('username', { - [Op.notRegexp]: '^sw.*r$' - }, { - mariadb: "`username` NOT REGEXP '^sw.*r$'", - mysql: "`username` NOT REGEXP '^sw.*r$'", - postgres: '"username" !~ \'^sw.*r$\'' - }); - }); - - describe('Op.notRegexp', () => { - testsql('newline', { - [Op.notRegexp]: '^new\nline$' - }, { - mariadb: "`newline` NOT REGEXP '^new\\nline$'", - mysql: "`newline` NOT REGEXP '^new\\nline$'", - postgres: '"newline" !~ \'^new\nline$\'' - }); - }); - - if (current.dialect.name === 'postgres') { - describe('Op.iRegexp', () => { - testsql('username', { - [Op.iRegexp]: '^sw.*r$' - }, { - postgres: '"username" ~* \'^sw.*r$\'' - }); - }); - - describe('Op.iRegexp', () => { - testsql('newline', { - [Op.iRegexp]: '^new\nline$' - }, { - postgres: '"newline" ~* \'^new\nline$\'' - }); - }); - - describe('Op.notIRegexp', () => { - testsql('username', { - [Op.notIRegexp]: '^sw.*r$' - }, { - postgres: '"username" !~* \'^sw.*r$\'' - }); - }); - - describe('Op.notIRegexp', () => { - testsql('newline', { - [Op.notIRegexp]: '^new\nline$' - }, { - postgres: '"newline" !~* \'^new\nline$\'' - }); - }); - } - } - - 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') }), { - default: "WHERE [name] = LOWER('DERP')", - mssql: "WHERE [name] = LOWER(N'DERP')" - }); - }); - }); - }); - - describe('getWhereConditions', () => { - const testsql = function(value, expectation) { - const User = current.define('user', {}); - - it(util.inspect(value, { depth: 10 }), () => { - return expectsql(sql.getWhereConditions(value, User.tableName, User), expectation); - }); - }; - - testsql(current.where(current.fn('lower', current.col('name')), null), { - default: 'lower([name]) IS NULL' - }); - - testsql(current.where(current.fn('SUM', current.col('hours')), '>', 0), { - default: 'SUM([hours]) > 0' - }); - - testsql(current.where(current.fn('SUM', current.col('hours')), Op.gt, 0), { - default: 'SUM([hours]) > 0' - }); - - testsql(current.where(current.fn('lower', current.col('name')), Op.ne, null), { - default: 'lower([name]) IS NOT NULL' - }); - - testsql(current.where(current.fn('lower', current.col('name')), Op.not, null), { - default: 'lower([name]) IS NOT NULL' - }); - - testsql([current.where(current.fn('SUM', current.col('hours')), Op.gt, 0), - 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/support.js b/test/unit/support.js deleted file mode 100644 index 7740b463c631..000000000000 --- a/test/unit/support.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('../support'); diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js deleted file mode 100644 index 2ab1d1d877f3..000000000000 --- a/test/unit/transaction.test.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const sinon = require('sinon'); -const Support = require('./support'); -const Sequelize = Support.Sequelize; -const dialect = Support.getTestDialect(); -const current = Support.sequelize; - -describe('Transaction', () => { - before(function() { - this.stub = sinon.stub(current, 'query').resolves({}); - - this.stubConnection = sinon.stub(current.connectionManager, 'getConnection') - .resolves({ - uuid: 'ssfdjd-434fd-43dfg23-2d', - close() {} - }); - - this.stubRelease = sinon.stub(current.connectionManager, 'releaseConnection') - .resolves(); - }); - - beforeEach(function() { - this.stub.resetHistory(); - this.stubConnection.resetHistory(); - this.stubRelease.resetHistory(); - }); - - after(function() { - this.stub.restore(); - this.stubConnection.restore(); - }); - - it('should run auto commit query only when needed', async function() { - const expectations = { - all: [ - 'START TRANSACTION;' - ], - sqlite: [ - 'BEGIN DEFERRED TRANSACTION;' - ], - mssql: [ - 'BEGIN TRANSACTION;' - ] - }; - - await current.transaction(async () => { - expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - }); - }); - - it('should set isolation level correctly', async function() { - const expectations = { - all: [ - 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', - 'START TRANSACTION;' - ], - postgres: [ - 'START TRANSACTION;', - 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' - ], - sqlite: [ - 'BEGIN DEFERRED TRANSACTION;', - 'PRAGMA read_uncommitted = ON;' - ], - mssql: [ - 'BEGIN TRANSACTION;' - ] - }; - - await current.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED }, async () => { - expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - }); - }); -}); diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js deleted file mode 100644 index 3258834234c7..000000000000 --- a/test/unit/utils.test.js +++ /dev/null @@ -1,280 +0,0 @@ -'use strict'; - -const chai = require('chai'); -const expect = chai.expect; -const Support = require('./support'); -const DataTypes = require('../../lib/data-types'); -const Utils = require('../../lib/utils'); -const { logger } = require('../../lib/utils/logger'); -const Op = Support.Sequelize.Op; - -describe(Support.getTestDialectTeaser('Utils'), () => { - describe('merge', () => { - it('does not clone sequelize models', () => { - const User = Support.sequelize.define('user'); - const merged = Utils.merge({}, { include: [{ model: User }] }); - const merged2 = Utils.merge({}, { user: User }); - - expect(merged.include[0].model).to.equal(User); - expect(merged2.user).to.equal(User); - }); - }); - - describe('canTreatArrayAsAnd', () => { - it('Array can be treated as and', () => { - expect(Utils.canTreatArrayAsAnd([{ 'uuid': 1 }])).to.equal(true); - expect(Utils.canTreatArrayAsAnd([{ 'uuid': 1 }, { 'uuid': 2 }, 1])).to.equal(true); - expect(Utils.canTreatArrayAsAnd([new Utils.Where('uuid', 1)])).to.equal(true); - expect(Utils.canTreatArrayAsAnd([new Utils.Where('uuid', 1), new Utils.Where('uuid', 2)])).to.equal(true); - expect(Utils.canTreatArrayAsAnd([new Utils.Where('uuid', 1), { 'uuid': 2 }, 1])).to.equal(true); - }); - it('Array cannot be treated as and', () => { - expect(Utils.canTreatArrayAsAnd([1, 'uuid'])).to.equal(false); - expect(Utils.canTreatArrayAsAnd([1])).to.equal(false); - }); - }); - - describe('toDefaultValue', () => { - it('return plain data types', () => { - expect(Utils.toDefaultValue(DataTypes.UUIDV4)).to.equal('UUIDV4'); - }); - it('return uuid v1', () => { - expect(/^[a-z0-9-]{36}$/.test(Utils.toDefaultValue(DataTypes.UUIDV1()))).to.be.equal(true); - }); - it('return uuid v4', () => { - expect(/^[a-z0-9-]{36}/.test(Utils.toDefaultValue(DataTypes.UUIDV4()))).to.be.equal(true); - }); - it('return now', () => { - expect(Object.prototype.toString.call(Utils.toDefaultValue(DataTypes.NOW()))).to.be.equal('[object Date]'); - }); - it('return plain string', () => { - expect(Utils.toDefaultValue('Test')).to.equal('Test'); - }); - it('return plain object', () => { - chai.assert.deepEqual({}, Utils.toDefaultValue({})); - }); - }); - - describe('defaults', () => { - it('defaults normal object', () => { - expect(Utils.defaults( - { a: 1, c: 3 }, - { b: 2 }, - { c: 4, d: 4 } - )).to.eql({ - a: 1, - b: 2, - c: 3, - d: 4 - }); - }); - - it('defaults symbol keys', () => { - expect(Utils.defaults( - { a: 1, [Symbol.for('c')]: 3 }, - { b: 2 }, - { [Symbol.for('c')]: 4, [Symbol.for('d')]: 4 } - )).to.eql({ - a: 1, - b: 2, - [Symbol.for('c')]: 3, - [Symbol.for('d')]: 4 - }); - }); - }); - - describe('mapFinderOptions', () => { - it('virtual attribute dependencies', () => { - expect(Utils.mapFinderOptions({ - attributes: [ - 'active' - ] - }, Support.sequelize.define('User', { - createdAt: { - type: DataTypes.DATE, - field: 'created_at' - }, - active: { - type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']) - } - })).attributes).to.eql([ - [ - 'created_at', - 'createdAt' - ] - ]); - }); - - it('multiple calls', () => { - const Model = Support.sequelize.define('User', { - createdAt: { - type: DataTypes.DATE, - field: 'created_at' - }, - active: { - type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']) - } - }); - - expect( - Utils.mapFinderOptions( - Utils.mapFinderOptions({ - attributes: [ - 'active' - ] - }, Model), - Model - ).attributes - ).to.eql([ - [ - 'created_at', - 'createdAt' - ] - ]); - }); - }); - - describe('mapOptionFieldNames', () => { - it('plain where', () => { - expect(Utils.mapOptionFieldNames({ - where: { - firstName: 'Paul', - lastName: 'Atreides' - } - }, Support.sequelize.define('User', { - firstName: { - type: DataTypes.STRING, - field: 'first_name' - }, - lastName: { - type: DataTypes.STRING, - field: 'last_name' - } - }))).to.eql({ - where: { - first_name: 'Paul', - last_name: 'Atreides' - } - }); - }); - - it('Op.or where', () => { - expect(Utils.mapOptionFieldNames({ - where: { - [Op.or]: { - firstName: 'Paul', - lastName: 'Atreides' - } - } - }, Support.sequelize.define('User', { - firstName: { - type: DataTypes.STRING, - field: 'first_name' - }, - lastName: { - type: DataTypes.STRING, - field: 'last_name' - } - }))).to.eql({ - where: { - [Op.or]: { - first_name: 'Paul', - last_name: 'Atreides' - } - } - }); - }); - - it('Op.or[] where', () => { - expect(Utils.mapOptionFieldNames({ - where: { - [Op.or]: [ - { firstName: 'Paul' }, - { lastName: 'Atreides' } - ] - } - }, Support.sequelize.define('User', { - firstName: { - type: DataTypes.STRING, - field: 'first_name' - }, - lastName: { - type: DataTypes.STRING, - field: 'last_name' - } - }))).to.eql({ - where: { - [Op.or]: [ - { first_name: 'Paul' }, - { last_name: 'Atreides' } - ] - } - }); - }); - - it('$and where', () => { - expect(Utils.mapOptionFieldNames({ - where: { - [Op.and]: { - firstName: 'Paul', - lastName: 'Atreides' - } - } - }, Support.sequelize.define('User', { - firstName: { - type: DataTypes.STRING, - field: 'first_name' - }, - lastName: { - type: DataTypes.STRING, - field: 'last_name' - } - }))).to.eql({ - where: { - [Op.and]: { - first_name: 'Paul', - last_name: 'Atreides' - } - } - }); - }); - }); - - describe('Sequelize.cast', () => { - const sql = Support.sequelize; - const generator = sql.queryInterface.queryGenerator; - const run = generator.handleSequelizeMethod.bind(generator); - const expectsql = Support.expectsql; - - it('accepts condition object (auto casting)', () => { - expectsql(run(sql.fn('SUM', sql.cast({ - [Op.or]: { - foo: 'foo', - bar: 'bar' - } - }, 'int'))), { - default: 'SUM(CAST(([foo] = \'foo\' OR [bar] = \'bar\') AS INT))', - mssql: 'SUM(CAST(([foo] = N\'foo\' OR [bar] = N\'bar\') AS INT))' - }); - }); - }); - - describe('Logger', () => { - it('debug', () => { - expect(logger.debugContext).to.be.a('function'); - logger.debugContext('test debug'); - }); - - it('warn', () => { - expect(logger.warn).to.be.a('function'); - logger.warn('test warning'); - }); - - it('debugContext', () => { - expect(logger.debugContext).to.be.a('function'); - const testLogger = logger.debugContext('test'); - - expect(testLogger).to.be.a('function'); - }); - }); -}); diff --git a/tsconfig-preset.json b/tsconfig-preset.json new file mode 100644 index 000000000000..e39b52ae1688 --- /dev/null +++ b/tsconfig-preset.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowJs": false, + "declaration": true, + "sourceRoot": "", + "outDir": "./lib", + "strict": true, + "rootDir": "./src", + "types": ["node", "mocha", "chai"], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "checkJs": false, + "stripInternal": true, + "exactOptionalPropertyTypes": true + }, + "ts-node": { + "transpileOnly": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000000..620cddb33d20 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig-preset.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "experimentalDecorators": true + }, + "include": ["./**/*.ts"] +} diff --git a/typedoc.base.json b/typedoc.base.json new file mode 100644 index 000000000000..f9924bd5d04e --- /dev/null +++ b/typedoc.base.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://typedoc.org/schema.json" +} diff --git a/typedoc.js b/typedoc.js new file mode 100644 index 000000000000..c4b3310904d0 --- /dev/null +++ b/typedoc.js @@ -0,0 +1,10 @@ +module.exports = { + entryPointStrategy: 'packages', + // Note: packages/postgres cannot be included until https://github.com/TypeStrong/typedoc/issues/2467 is fixed + entryPoints: ['packages/core', 'packages/utils', 'packages/validator-js'], + out: './.typedoc-build', + readme: 'none', + plugin: ['typedoc-plugin-missing-exports', 'typedoc-plugin-mdn-links'], + treatWarningsAsErrors: true, + highlightLanguages: ['typescript', 'sql', 'javascript', 'shellscript'], +}; diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index d604421b9b65..000000000000 --- a/types/index.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import DataTypes = require('./lib/data-types'); -import Deferrable = require('./lib/deferrable'); -import Op = require('./lib/operators'); -import QueryTypes = require('./lib/query-types'); -import TableHints = require('./lib/table-hints'); -import IndexHints = require('./lib/index-hints'); -import Utils = require('./lib/utils'); - -export * from './lib/sequelize'; -export * from './lib/query-interface'; -export * from './lib/data-types'; -export * from './lib/model'; -export * from './lib/transaction'; -export * from './lib/associations/index'; -export * from './lib/errors'; -export { BaseError as Error } from './lib/errors'; -export { useInflection } from './lib/utils'; -export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable }; -export { Validator as validator } from './lib/utils/validator-extras'; - -/** - * Type helper for making certain fields of an object optional. This is helpful - * for creating the `CreationAttributes` from your `Attributes` for a Model. - */ -export type Optional = Omit & Partial>; diff --git a/types/lib/associations/base.d.ts b/types/lib/associations/base.d.ts deleted file mode 100644 index 186005992fed..000000000000 --- a/types/lib/associations/base.d.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ColumnOptions, Model, ModelCtor, Hookable } from '../model'; - -export abstract class Association { - public associationType: string; - public source: ModelCtor; - public target: ModelCtor; - public isSelfAssociation: boolean; - public isSingleAssociation: boolean; - public isMultiAssociation: boolean; - public as: string; - public isAliased: boolean; - public foreignKey: string; - public identifier: string; - public inspect(): string; -} - -export interface SingleAssociationAccessors { - get: string; - set: string; - create: string; -} - -export interface MultiAssociationAccessors { - get: string; - set: string; - addMultiple: string; - add: string; - create: string; - remove: string; - removeMultiple: string; - hasSingle: string; - hasAll: string; - count: string; -} - -/** Foreign Key Options */ -export interface ForeignKeyOptions extends ColumnOptions { - /** Attribute name for the relation */ - name?: string; -} - -/** - * Options provided when associating models - */ -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 - * distinguish between them. If you provide an alias when creating the assocition, you should provide the - * same alias when eager loading and when getting associated models. Defaults to the singularized name of - * target - */ - as?: string | { singular: string; plural: string }; - - /** - * 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 - */ - foreignKey?: string | ForeignKeyOptions; - - /** - * What happens when delete occurs. - * - * Cascade if this is a n:m, and set null if it is a 1:m - * - * @default 'SET NULL' or 'CASCADE' - */ - onDelete?: string; - - /** - * What happens when update occurs - * - * @default 'CASCADE' - */ - onUpdate?: string; - - /** - * Should on update and on delete constraints be enabled on the foreign key. - */ - constraints?: boolean; - foreignKeyConstraint?: boolean; - - scope?: AssociationScope; -} - -/** - * Options for Association Scope - */ -export interface AssociationScope { - /** - * The name of the column that will be used for the associated scope and it's value - */ - [scopeName: string]: unknown; -} - -/** - * Options provided for many-to-many relationships - */ -export interface ManyToManyOptions extends AssociationOptions { - /** - * A key/value set that will be used for association create and find defaults on the target. - * (sqlite not supported for N:M) - */ - scope?: AssociationScope; -} diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts deleted file mode 100644 index b04a728af6ee..000000000000 --- a/types/lib/associations/belongs-to-many.d.ts +++ /dev/null @@ -1,493 +0,0 @@ -import { - BulkCreateOptions, - CreateOptions, - Filterable, - FindAttributeOptions, - FindOptions, - InstanceDestroyOptions, - InstanceUpdateOptions, - Model, - ModelCtor, - ModelType, - Transactionable, - WhereOptions, -} from '../model'; -import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, MultiAssociationAccessors } from './base'; - -/** - * Used for a association table in n:m associations. - */ -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: 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. - * (Remember to add the attributes to the through model) - */ - scope?: AssociationScope; - - /** - * 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) - * - * @default true - */ - unique?: boolean; -} - -/** - * Attributes for the join table - */ -export interface JoinTableAttributes { - [attribute: string]: unknown; -} - -/** - * Options provided when associating models with belongsToMany relationship - */ -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: ModelType | string | ThroughOptions; - - /** - * 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 colum. Defaults to the name of target + primary key of - * target - */ - otherKey?: string | ForeignKeyOptions; - - /** - * 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 - */ - sourceKey?: string; - - /** - * The name of the field to use as the key for the association in the target table. Defaults to the primary - * key of the target table - */ - targetKey?: string; - - /** - * Should the join model have timestamps - */ - timestamps?: boolean; - - /** - * The unique key name to override the autogenerated one when primary key is not present on through model - */ - uniqueKey?: string; -} - -export class BelongsToMany extends Association { - public otherKey: string; - public sourceKey: string; - public targetKey: string; - public accessors: MultiAssociationAccessors; - constructor(source: ModelCtor, target: ModelCtor, options: BelongsToManyOptions); -} - -/** - * The options for the getAssociations mixin of the belongsToMany association. - * @see BelongsToManyGetAssociationsMixin - */ -export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { - /** - * A list of the attributes from the join table that you want to select. - */ - joinTableAttributes?: FindAttributeOptions - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | boolean; -} - -/** - * The getAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * getRoles: Sequelize.BelongsToManyGetAssociationsMixin; - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyGetAssociationsMixin = ( - options?: BelongsToManyGetAssociationsMixinOptions -) => Promise; - -/** - * The options for the setAssociations mixin of the belongsToMany association. - * @see BelongsToManySetAssociationsMixin - */ -export interface BelongsToManySetAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, - InstanceDestroyOptions { - through?: JoinTableAttributes; -} - -/** - * The setAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * setRoles: Sequelize.BelongsToManySetAssociationsMixin; - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManySetAssociationsMixin = ( - newAssociations?: (TModel | TModelPrimaryKey)[], - options?: BelongsToManySetAssociationsMixinOptions -) => Promise; - -/** - * The options for the addAssociations mixin of the belongsToMany association. - * @see BelongsToManyAddAssociationsMixin - */ -export interface BelongsToManyAddAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, - InstanceDestroyOptions { - through?: JoinTableAttributes; -} - -/** - * The addAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * addRoles: Sequelize.BelongsToManyAddAssociationsMixin; - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyAddAssociationsMixin = ( - newAssociations?: (TModel | TModelPrimaryKey)[], - options?: BelongsToManyAddAssociationsMixinOptions -) => Promise; - -/** - * The options for the addAssociation mixin of the belongsToMany association. - * @see BelongsToManyAddAssociationMixin - */ -export interface BelongsToManyAddAssociationMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, - InstanceDestroyOptions { - through?: JoinTableAttributes; -} - -/** - * The addAssociation mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * addRole: Sequelize.BelongsToManyAddAssociationMixin; - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyAddAssociationMixin = ( - newAssociation?: TModel | TModelPrimaryKey, - options?: BelongsToManyAddAssociationMixinOptions -) => Promise; - -/** - * The options for the createAssociation mixin of the belongsToMany association. - * @see BelongsToManyCreateAssociationMixin - */ -export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { - through?: JoinTableAttributes; -} -/** - * The createAssociation mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * createRole: Sequelize.BelongsToManyCreateAssociationMixin; - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, - options?: BelongsToManyCreateAssociationMixinOptions -) => Promise; - -/** - * The options for the removeAssociation mixin of the belongsToMany association. - * @see BelongsToManyRemoveAssociationMixin - */ -export interface BelongsToManyRemoveAssociationMixinOptions extends InstanceDestroyOptions {} - -/** - * The removeAssociation mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * removeRole: Sequelize.BelongsToManyRemoveAssociationMixin; - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyRemoveAssociationMixin = ( - oldAssociated?: TModel | TModelPrimaryKey, - options?: BelongsToManyRemoveAssociationMixinOptions -) => Promise; - -/** - * The options for the removeAssociations mixin of the belongsToMany association. - * @see BelongsToManyRemoveAssociationsMixin - */ -export interface BelongsToManyRemoveAssociationsMixinOptions extends InstanceDestroyOptions, InstanceDestroyOptions {} - -/** - * The removeAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * removeRoles: Sequelize.BelongsToManyRemoveAssociationsMixin; - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyRemoveAssociationsMixin = ( - oldAssociateds?: (TModel | TModelPrimaryKey)[], - options?: BelongsToManyRemoveAssociationsMixinOptions -) => Promise; - -/** - * The options for the hasAssociation mixin of the belongsToMany association. - * @see BelongsToManyHasAssociationMixin - */ -export interface BelongsToManyHasAssociationMixinOptions extends BelongsToManyGetAssociationsMixinOptions {} - -/** - * The hasAssociation mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * hasRole: Sequelize.BelongsToManyHasAssociationMixin; - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyHasAssociationMixin = ( - target: TModel | TModelPrimaryKey, - options?: BelongsToManyHasAssociationMixinOptions -) => Promise; - -/** - * The options for the hasAssociations mixin of the belongsToMany association. - * @see BelongsToManyHasAssociationsMixin - */ -export interface BelongsToManyHasAssociationsMixinOptions extends BelongsToManyGetAssociationsMixinOptions {} - -/** - * The removeAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles - * // hasRole... - * hasRoles: Sequelize.BelongsToManyHasAssociationsMixin; - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyHasAssociationsMixin = ( - targets: (TModel | TModelPrimaryKey)[], - options?: BelongsToManyHasAssociationsMixinOptions -) => Promise; - -/** - * The options for the countAssociations mixin of the belongsToMany association. - * @see BelongsToManyCountAssociationsMixin - */ -export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable { - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | boolean; -} - -/** - * The countAssociations mixin applied to models with belongsToMany. - * An example of usage is as follows: - * - * ```js - * - * User.belongsToMany(Role, { through: UserRole }); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * countRoles: Sequelize.BelongsToManyCountAssociationsMixin; - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html - * @see Instance - */ -export type BelongsToManyCountAssociationsMixin = ( - options?: BelongsToManyCountAssociationsMixinOptions -) => Promise; diff --git a/types/lib/associations/belongs-to.d.ts b/types/lib/associations/belongs-to.d.ts deleted file mode 100644 index 17754ac93775..000000000000 --- a/types/lib/associations/belongs-to.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { DataType } from '../data-types'; -import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; - -// type ModelCtor = InstanceType; -/** - * Options provided when associating models with belongsTo relationship - * - * @see Association class belongsTo method - */ -export interface BelongsToOptions extends AssociationOptions { - /** - * The name of the field to use as the key for the association in the target table. Defaults to the primary - * key of the target table - */ - targetKey?: string; - - /** - * A string or a data type to represent the identifier in the table - */ - keyType?: DataType; -} - -export class BelongsTo extends Association { - public accessors: SingleAssociationAccessors; - constructor(source: ModelCtor, target: ModelCtor, options: BelongsToOptions); -} - -/** - * The options for the getAssociation mixin of the belongsTo association. - * @see BelongsToGetAssociationMixin - */ -export interface BelongsToGetAssociationMixinOptions extends FindOptions { - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | string[] | boolean; -} - -/** - * The getAssociation mixin applied to models with belongsTo. - * An example of usage is as follows: - * - * ```js - * - * User.belongsTo(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttrib { - * getRole: Sequelize.BelongsToGetAssociationMixin; - * // setRole... - * // createRole... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html - * @see Instance - */ -export type BelongsToGetAssociationMixin = (options?: BelongsToGetAssociationMixinOptions) => Promise; - -/** - * The options for the setAssociation mixin of the belongsTo association. - * @see BelongsToSetAssociationMixin - */ -export interface BelongsToSetAssociationMixinOptions extends SaveOptions { - /** - * Skip saving this after setting the foreign key if false. - */ - save?: boolean; -} - -/** - * The setAssociation mixin applied to models with belongsTo. - * An example of usage is as follows: - * - * ```js - * - * User.belongsTo(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRole... - * setRole: Sequelize.BelongsToSetAssociationMixin; - * // createRole... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html - * @see Instance - */ -export type BelongsToSetAssociationMixin = ( - newAssociation?: TModel | TPrimaryKey, - options?: BelongsToSetAssociationMixinOptions -) => Promise; - -/** - * The options for the createAssociation mixin of the belongsTo association. - * @see BelongsToCreateAssociationMixin - */ -export interface BelongsToCreateAssociationMixinOptions - extends CreateOptions, BelongsToSetAssociationMixinOptions {} - -/** - * The createAssociation mixin applied to models with belongsTo. - * An example of usage is as follows: - * - * ```js - * - * User.belongsTo(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRole... - * // setRole... - * createRole: Sequelize.BelongsToCreateAssociationMixin; - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html - * @see Instance - */ -export type BelongsToCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, - options?: BelongsToCreateAssociationMixinOptions -) => Promise; - -export default BelongsTo; diff --git a/types/lib/associations/has-many.d.ts b/types/lib/associations/has-many.d.ts deleted file mode 100644 index 802ad45a7181..000000000000 --- a/types/lib/associations/has-many.d.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { DataType } from '../data-types'; -import { - CreateOptions, - Filterable, - FindOptions, - InstanceUpdateOptions, - Model, - ModelCtor, - Transactionable, -} from '../model'; -import { Association, ManyToManyOptions, MultiAssociationAccessors } from './base'; - -/** - * Options provided when associating models with hasMany relationship - */ -export interface HasManyOptions extends ManyToManyOptions { - - /** - * 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 - */ - sourceKey?: string; - - /** - * A string or a data type to represent the identifier in the table - */ - keyType?: DataType; -} - -export class HasMany extends Association { - public accessors: MultiAssociationAccessors; - constructor(source: ModelCtor, target: ModelCtor, options: HasManyOptions); -} - -/** - * The options for the getAssociations mixin of the hasMany association. - * @see HasManyGetAssociationsMixin - */ -export interface HasManyGetAssociationsMixinOptions extends FindOptions { - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | string[] | boolean; -} - -/** - * The getAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * getRoles: Sequelize.HasManyGetAssociationsMixin; - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyGetAssociationsMixin = (options?: HasManyGetAssociationsMixinOptions) => Promise; - -/** - * The options for the setAssociations mixin of the hasMany association. - * @see HasManySetAssociationsMixin - */ -export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} - -/** - * The setAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * setRoles: Sequelize.HasManySetAssociationsMixin; - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManySetAssociationsMixin = ( - newAssociations?: (TModel | TModelPrimaryKey)[], - options?: HasManySetAssociationsMixinOptions -) => Promise; - -/** - * The options for the addAssociations mixin of the hasMany association. - * @see HasManyAddAssociationsMixin - */ -export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} - -/** - * The addAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * addRoles: Sequelize.HasManyAddAssociationsMixin; - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyAddAssociationsMixin = ( - newAssociations?: (TModel | TModelPrimaryKey)[], - options?: HasManyAddAssociationsMixinOptions -) => Promise; - -/** - * The options for the addAssociation mixin of the hasMany association. - * @see HasManyAddAssociationMixin - */ -export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} - -/** - * The addAssociation mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * addRole: Sequelize.HasManyAddAssociationMixin; - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyAddAssociationMixin = ( - newAssociation?: TModel | TModelPrimaryKey, - options?: HasManyAddAssociationMixinOptions -) => Promise; - -/** - * The options for the createAssociation mixin of the hasMany association. - * @see HasManyCreateAssociationMixin - */ -export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} - -/** - * The createAssociation mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * createRole: Sequelize.HasManyCreateAssociationMixin; - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, - options?: HasManyCreateAssociationMixinOptions -) => Promise; - -/** - * The options for the removeAssociation mixin of the hasMany association. - * @see HasManyRemoveAssociationMixin - */ -export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} - -/** - * The removeAssociation mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * removeRole: Sequelize.HasManyRemoveAssociationMixin; - * // removeRoles... - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyRemoveAssociationMixin = ( - oldAssociated?: TModel | TModelPrimaryKey, - options?: HasManyRemoveAssociationMixinOptions -) => Promise; - -/** - * The options for the removeAssociations mixin of the hasMany association. - * @see HasManyRemoveAssociationsMixin - */ -export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} - -/** - * The removeAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * removeRoles: Sequelize.HasManyRemoveAssociationsMixin; - * // hasRole... - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyRemoveAssociationsMixin = ( - oldAssociateds?: (TModel | TModelPrimaryKey)[], - options?: HasManyRemoveAssociationsMixinOptions -) => Promise; - -/** - * The options for the hasAssociation mixin of the hasMany association. - * @see HasManyHasAssociationMixin - */ -export interface HasManyHasAssociationMixinOptions extends HasManyGetAssociationsMixinOptions {} - -/** - * The hasAssociation mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * hasRole: Sequelize.HasManyHasAssociationMixin; - * // hasRoles... - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyHasAssociationMixin = ( - target: TModel | TModelPrimaryKey, - options?: HasManyHasAssociationMixinOptions -) => Promise; - -/** - * The options for the hasAssociations mixin of the hasMany association. - * @see HasManyHasAssociationsMixin - */ -export interface HasManyHasAssociationsMixinOptions extends HasManyGetAssociationsMixinOptions {} - -/** - * The removeAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles - * // hasRole... - * hasRoles: Sequelize.HasManyHasAssociationsMixin; - * // countRoles... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyHasAssociationsMixin = ( - targets: (TModel | TModelPrimaryKey)[], - options?: HasManyHasAssociationsMixinOptions -) => Promise; - -/** - * The options for the countAssociations mixin of the hasMany association. - * @see HasManyCountAssociationsMixin - */ -export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable { - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | boolean; -} - -/** - * The countAssociations mixin applied to models with hasMany. - * An example of usage is as follows: - * - * ```js - * - * User.hasMany(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRoles... - * // setRoles... - * // addRoles... - * // addRole... - * // createRole... - * // removeRole... - * // removeRoles... - * // hasRole... - * // hasRoles... - * countRoles: Sequelize.HasManyCountAssociationsMixin; - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html - * @see Instance - */ -export type HasManyCountAssociationsMixin = (options?: HasManyCountAssociationsMixinOptions) => Promise; diff --git a/types/lib/associations/has-one.d.ts b/types/lib/associations/has-one.d.ts deleted file mode 100644 index 7b41fc05196e..000000000000 --- a/types/lib/associations/has-one.d.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { DataType } from '../data-types'; -import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; - -/** - * Options provided when associating models with hasOne relationship - */ -export interface HasOneOptions extends AssociationOptions { - - /** - * 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 - */ - sourceKey?: string; - - /** - * A string or a data type to represent the identifier in the table - */ - keyType?: DataType; -} - -export class HasOne extends Association { - public accessors: SingleAssociationAccessors; - constructor(source: ModelCtor, target: ModelCtor, options: HasOneOptions); -} - -/** - * The options for the getAssociation mixin of the hasOne association. - * @see HasOneGetAssociationMixin - */ -export interface HasOneGetAssociationMixinOptions extends FindOptions { - /** - * Apply a scope on the related model, or remove its default scope by passing false. - */ - scope?: string | string[] | boolean; -} - -/** - * The getAssociation mixin applied to models with hasOne. - * An example of usage is as follows: - * - * ```js - * - * User.hasOne(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttrib { - * getRole: Sequelize.HasOneGetAssociationMixin; - * // setRole... - * // createRole... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html - * @see Instance - */ -export type HasOneGetAssociationMixin = (options?: HasOneGetAssociationMixinOptions) => Promise; - -/** - * The options for the setAssociation mixin of the hasOne association. - * @see HasOneSetAssociationMixin - */ -export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions { - /** - * Skip saving this after setting the foreign key if false. - */ - save?: boolean; -} - -/** - * The setAssociation mixin applied to models with hasOne. - * An example of usage is as follows: - * - * ```js - * - * User.hasOne(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRole... - * setRole: Sequelize.HasOneSetAssociationMixin; - * // createRole... - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html - * @see Instance - */ -export type HasOneSetAssociationMixin = ( - newAssociation?: TModel | TModelPrimaryKey, - options?: HasOneSetAssociationMixinOptions -) => Promise; - -/** - * The options for the createAssociation mixin of the hasOne association. - * @see HasOneCreateAssociationMixin - */ -export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} - -/** - * The createAssociation mixin applied to models with hasOne. - * An example of usage is as follows: - * - * ```js - * - * User.hasOne(Role); - * - * interface UserInstance extends Sequelize.Instance, UserAttributes { - * // getRole... - * // setRole... - * createRole: Sequelize.HasOneCreateAssociationMixin; - * } - * ``` - * - * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html - * @see Instance - */ -export type HasOneCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, - options?: HasOneCreateAssociationMixinOptions -) => Promise; diff --git a/types/lib/associations/index.d.ts b/types/lib/associations/index.d.ts deleted file mode 100644 index 2fbdbe0412e6..000000000000 --- a/types/lib/associations/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './base'; -export * from './belongs-to'; -export * from './has-one'; -export * from './has-many'; -export * from './belongs-to-many'; diff --git a/types/lib/connection-manager.d.ts b/types/lib/connection-manager.d.ts deleted file mode 100644 index 8bb7674918b9..000000000000 --- a/types/lib/connection-manager.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -export interface GetConnectionOptions { - /** - * Set which replica to use. Available options are `read` and `write` - */ - type: 'read' | 'write'; - /** - * Force master or write replica to get connection from - */ - useMaster?: boolean; -} - -export type Connection = object; - -export interface ConnectionManager { - refreshTypeParser(dataTypes: object): void; - /** - * Drain the pool and close it permanently - */ - close(): Promise; - /** - * Initialize connection pool. By default pool autostart is set to false, so no connection will be - * be created unless `pool.acquire` is called. - */ - initPools(): void; - /** - * Get connection from pool. It sets database version if it's not already set. - * Call pool.acquire to get a connection. - */ - getConnection(opts: GetConnectionOptions): Promise; - /** - * Release a pooled connection so it can be utilized by other connection requests - */ - releaseConnection(conn: Connection): Promise; -} diff --git a/types/lib/data-types.d.ts b/types/lib/data-types.d.ts deleted file mode 100644 index 617b01e84ec0..000000000000 --- a/types/lib/data-types.d.ts +++ /dev/null @@ -1,610 +0,0 @@ -/** - * The datatypes are used when defining a new model using `Model.init`, like this: - * ```js - * class MyModel extends MyModel {} - * MyModel.init({ column: DataTypes.INTEGER }, { sequelize }); - * ``` - * When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean - * that that column will be returned as an instance of `Buffer` when being fetched by sequelize. - * - * Some data types have special properties that can be accessed in order to change the data type. - * For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`. - * The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well. The available properties are listed under each data type. - * - * To provide a length for the data type, you can invoke it like a function: `INTEGER(2)` - * - * Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for - * defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard: - * ```js - * class MyModel extends Model {} - * MyModel.init({ - * uuid: { - * type: DataTypes.UUID, - * defaultValue: DataTypes.UUIDV1, - * primaryKey: true - * } - * }, { sequelize }) - * ``` - * There may be times when you want to generate your own UUID conforming to some other algorithm. This is accomplised - * using the defaultValue property as well, but instead of specifying one of the supplied UUID types, you return a value - * from a function. - * ```js - * class MyModel extends Model {} - * MyModel.init({ - * uuid: { - * type: DataTypes.UUID, - * defaultValue() { - * return generateMyId() - * }, - * primaryKey: true - * } - * }, { sequelize }) - * ``` - */ - -/** - * - */ -export type DataType = string | AbstractDataTypeConstructor | AbstractDataType; - -export const ABSTRACT: AbstractDataTypeConstructor; - -interface AbstractDataTypeConstructor { - key: string; - warn(link: string, text: string): void; -} - -export interface AbstractDataType { - key: string; - dialectTypes: string; - toSql(): string; - stringify(value: unknown, options?: object): string; - toString(options: object): string; -} - -/** - * A variable length string. Default length 255 - */ -export const STRING: StringDataTypeConstructor; - -interface StringDataTypeConstructor extends AbstractDataTypeConstructor { - new (length?: number, binary?: boolean): StringDataType; - new (options?: StringDataTypeOptions): StringDataType; - (length?: number, binary?: boolean): StringDataType; - (options?: StringDataTypeOptions): StringDataType; -} - -export interface StringDataType extends AbstractDataType { - options?: StringDataTypeOptions; - BINARY: this; - validate(value: unknown): boolean; -} - -export interface StringDataTypeOptions { - length?: number; - binary?: boolean; -} - -/** - * A fixed length string. Default length 255 - */ -export const CHAR: CharDataTypeConstructor; - -interface CharDataTypeConstructor extends StringDataTypeConstructor { - new (length?: number, binary?: boolean): CharDataType; - new (options?: CharDataTypeOptions): CharDataType; - (length?: number, binary?: boolean): CharDataType; - (options?: CharDataTypeOptions): CharDataType; -} - -export interface CharDataType extends StringDataType { - options: CharDataTypeOptions; -} - -export interface CharDataTypeOptions extends StringDataTypeOptions {} - -export type TextLength = 'tiny' | 'medium' | 'long'; - -/** - * An (un)limited length text column. Available lengths: `tiny`, `medium`, `long` - */ -export const TEXT: TextDataTypeConstructor; - -interface TextDataTypeConstructor extends AbstractDataTypeConstructor { - new (length?: TextLength): TextDataType; - (options?: TextDataTypeOptions): TextDataType; -} - -export interface TextDataType extends AbstractDataType { - options: TextDataTypeOptions; - validate(value: unknown): boolean; -} - -export interface TextDataTypeOptions { - length?: TextLength; -} - -export const NUMBER: NumberDataTypeConstructor; - -interface NumberDataTypeConstructor extends AbstractDataTypeConstructor { - options: NumberDataTypeOptions; - UNSIGNED: this; - ZEROFILL: this; - new (options?: NumberDataTypeOptions): NumberDataType; - (options?: NumberDataTypeOptions): NumberDataType; - validate(value: unknown): boolean; -} - -export interface NumberDataType extends AbstractDataType { - options: NumberDataTypeOptions; - UNSIGNED: this; - ZEROFILL: this; - validate(value: unknown): boolean; -} - -export interface IntegerDataTypeOptions { - length?: number; - zerofill?: boolean; - unsigned?: boolean; -} -export interface NumberDataTypeOptions extends IntegerDataTypeOptions { - decimals?: number; - precision?: number; - scale?: number; -} - -/** - * A 8 bit integer. - */ -export const TINYINT: TinyIntegerDataTypeConstructor; - -interface TinyIntegerDataTypeConstructor extends NumberDataTypeConstructor { - new (options?: IntegerDataTypeOptions): TinyIntegerDataType; - (options?: IntegerDataTypeOptions): TinyIntegerDataType; -} - -export interface TinyIntegerDataType extends NumberDataType { - options: IntegerDataTypeOptions; -} - -/** - * A 16 bit integer. - */ -export const SMALLINT: SmallIntegerDataTypeConstructor; - -interface SmallIntegerDataTypeConstructor extends NumberDataTypeConstructor { - new (options?: IntegerDataTypeOptions): SmallIntegerDataType; - (options?: IntegerDataTypeOptions): SmallIntegerDataType; -} - -export interface SmallIntegerDataType extends NumberDataType { - options: IntegerDataTypeOptions; -} - -/** - * A 24 bit integer. - */ -export const MEDIUMINT: MediumIntegerDataTypeConstructor; - -interface MediumIntegerDataTypeConstructor extends NumberDataTypeConstructor { - new (options?: IntegerDataTypeOptions): MediumIntegerDataType; - (options?: IntegerDataTypeOptions): MediumIntegerDataType; -} - -export interface MediumIntegerDataType extends NumberDataType { - options: IntegerDataTypeOptions; -} - -/** - * A 32 bit integer. - */ -export const INTEGER: IntegerDataTypeConstructor; - -interface IntegerDataTypeConstructor extends NumberDataTypeConstructor { - new (options?: NumberDataTypeOptions): IntegerDataType; - (options?: NumberDataTypeOptions): IntegerDataType; -} - -export interface IntegerDataType extends NumberDataType { - options: NumberDataTypeOptions; -} - -/** - * A 64 bit integer. - * - * Available properties: `UNSIGNED`, `ZEROFILL` - * - */ -export const BIGINT: BigIntDataTypeConstructor; - -interface BigIntDataTypeConstructor extends NumberDataTypeConstructor { - new (options?: IntegerDataTypeOptions): BigIntDataType; - (options?: IntegerDataTypeOptions): BigIntDataType; -} - -export interface BigIntDataType extends NumberDataType { - options: IntegerDataTypeOptions; -} - -/** - * Floating point number (4-byte precision). Accepts one or two arguments for precision - */ -export const FLOAT: FloatDataTypeConstructor; - -interface FloatDataTypeConstructor extends NumberDataTypeConstructor { - new (length?: number, decimals?: number): FloatDataType; - new (options?: FloatDataTypeOptions): FloatDataType; - (length?: number, decimals?: number): FloatDataType; - (options?: FloatDataTypeOptions): FloatDataType; -} - -export interface FloatDataType extends NumberDataType { - options: FloatDataTypeOptions; -} - -export interface FloatDataTypeOptions { - length?: number; - decimals?: number; -} - -/** - * Floating point number (4-byte precision). Accepts one or two arguments for precision - */ -export const REAL: RealDataTypeConstructor; - -interface RealDataTypeConstructor extends NumberDataTypeConstructor { - new (length?: number, decimals?: number): RealDataType; - new (options?: RealDataTypeOptions): RealDataType; - (length?: number, decimals?: number): RealDataType; - (options?: RealDataTypeOptions): RealDataType; -} - -export interface RealDataType extends NumberDataType { - options: RealDataTypeOptions; -} - -export interface RealDataTypeOptions { - length?: number; - decimals?: number; -} - -/** - * Floating point number (8-byte precision). Accepts one or two arguments for precision - */ -export const DOUBLE: DoubleDataTypeConstructor; - -interface DoubleDataTypeConstructor extends NumberDataTypeConstructor { - new (length?: number, decimals?: number): DoubleDataType; - new (options?: DoubleDataTypeOptions): DoubleDataType; - (length?: number, decimals?: number): DoubleDataType; - (options?: DoubleDataTypeOptions): DoubleDataType; -} - -export interface DoubleDataType extends NumberDataType { - options: DoubleDataTypeOptions; -} - -export interface DoubleDataTypeOptions { - length?: number; - decimals?: number; -} - -/** - * Decimal number. Accepts one or two arguments for precision - */ -export const DECIMAL: DecimalDataTypeConstructor; - -interface DecimalDataTypeConstructor extends NumberDataTypeConstructor { - PRECISION: this; - SCALE: this; - new (precision?: number, scale?: number): DecimalDataType; - new (options?: DecimalDataTypeOptions): DecimalDataType; - (precision?: number, scale?: number): DecimalDataType; - (options?: DecimalDataTypeOptions): DecimalDataType; -} - -export interface DecimalDataType extends NumberDataType { - options: DecimalDataTypeOptions; -} - -export interface DecimalDataTypeOptions { - precision?: number; - scale?: number; -} - -/** - * A boolean / tinyint column, depending on dialect - */ -export const BOOLEAN: AbstractDataTypeConstructor; - -/** - * A time column - */ -export const TIME: AbstractDataTypeConstructor; - -/** - * A datetime column - */ -export const DATE: DateDataTypeConstructor; - -interface DateDataTypeConstructor extends AbstractDataTypeConstructor { - new (length?: string | number): DateDataType; - new (options?: DateDataTypeOptions): DateDataType; - (length?: string | number): DateDataType; - (options?: DateDataTypeOptions): DateDataType; -} - -export interface DateDataType extends AbstractDataTypeConstructor { - options: DateDataTypeOptions; -} - -export interface DateDataTypeOptions { - length?: string | number; -} - -/** - * A date only column - */ -export const DATEONLY: DateOnlyDataTypeConstructor; - -interface DateOnlyDataTypeConstructor extends AbstractDataTypeConstructor { - new (): DateOnlyDataType; - (): DateOnlyDataType; -} - -export interface DateOnlyDataType extends AbstractDataType { -} - - -/** - * A key / value column. Only available in postgres. - */ -export const HSTORE: AbstractDataTypeConstructor; - -/** - * A JSON string column. Only available in postgres. - */ -export const JSON: AbstractDataTypeConstructor; - -/** - * A pre-processed JSON data column. Only available in postgres. - */ -export const JSONB: AbstractDataTypeConstructor; - -/** - * A default value of the current timestamp - */ -export const NOW: AbstractDataTypeConstructor; - -/** - * Binary storage. Available lengths: `tiny`, `medium`, `long` - */ -export const BLOB: BlobDataTypeConstructor; - -export type BlobSize = 'tiny' | 'medium' | 'long'; - -interface BlobDataTypeConstructor extends AbstractDataTypeConstructor { - new (length?: BlobSize): BlobDataType; - new (options?: BlobDataTypeOptions): BlobDataType; - (length?: BlobSize): BlobDataType; - (options?: BlobDataTypeOptions): BlobDataType; -} - -export interface BlobDataType extends AbstractDataType { - options: BlobDataTypeOptions; - escape: boolean; -} - -export interface BlobDataTypeOptions { - length?: BlobSize; - escape?: boolean; -} - -/** - * Range types are data types representing a range of values of some element type (called the range's subtype). - * Only available in postgres. - * - * See [Postgres documentation](http://www.postgresql.org/docs/9.4/static/rangetypes.html) for more details - */ -export const RANGE: RangeDataTypeConstructor; - -export type RangeableDataType = - | IntegerDataTypeConstructor - | IntegerDataType - | BigIntDataTypeConstructor - | BigIntDataType - | DecimalDataTypeConstructor - | DecimalDataType - | DateOnlyDataTypeConstructor - | DateOnlyDataType - | DateDataTypeConstructor - | DateDataType; - -interface RangeDataTypeConstructor extends AbstractDataTypeConstructor { - new (subtype?: T): RangeDataType; - new (options: RangeDataTypeOptions): RangeDataType; - (subtype?: T): RangeDataType; - (options: RangeDataTypeOptions): RangeDataType; -} - -export interface RangeDataType extends AbstractDataType { - options: RangeDataTypeOptions; -} - -export interface RangeDataTypeOptions { - subtype?: T; -} - -/** - * A column storing a unique universal identifier. Use with `UUIDV1` or `UUIDV4` for default values. - */ -export const UUID: AbstractDataTypeConstructor; - -/** - * A default unique universal identifier generated following the UUID v1 standard - */ -export const UUIDV1: AbstractDataTypeConstructor; - -/** - * A default unique universal identifier generated following the UUID v4 standard - */ -export const UUIDV4: AbstractDataTypeConstructor; - -/** - * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB. - * - * You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example: - * ```js - * class User extends Model {} - * User.init({ - * password_hash: DataTypes.STRING, - * password: { - * type: DataTypes.VIRTUAL, - * set (val) { - * this.setDataValue('password', val); // Remember to set the data value, otherwise it won't be validated - * this.setDataValue('password_hash', this.salt + val); - * }, - * validate: { - * isLongEnough (val) { - * if (val.length < 7) { - * throw new Error("Please choose a longer password") - * } - * } - * } - * } - * }, { sequelize }); - * ``` - * - * VIRTUAL also takes a return type and dependency fields as arguments - * If a virtual attribute is present in `attributes` it will automatically pull in the extra fields as well. - * Return type is mostly useful for setups that rely on types like GraphQL. - * ```js - * { - * active: { - * type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']), - * get() { - * return this.get('createdAt') > Date.now() - (7 * 24 * 60 * 60 * 1000) - * } - * } - * } - * ``` - * - * In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB. - */ -export const VIRTUAL: VirtualDataTypeConstructor; - -interface VirtualDataTypeConstructor extends AbstractDataTypeConstructor { - new (ReturnType: T, fields?: string[]): VirtualDataType< - T - >; - (ReturnType: T, fields?: string[]): VirtualDataType; -} - -export interface VirtualDataType extends AbstractDataType { - returnType: T; - fields: string[]; -} - -/** - * An enumeration. `DataTypes.ENUM('value', 'another value')`. - */ -export const ENUM: EnumDataTypeConstructor; - -interface EnumDataTypeConstructor extends AbstractDataTypeConstructor { - new (...values: T[]): EnumDataType; - new (options: EnumDataTypeOptions): EnumDataType; - (...values: T[]): EnumDataType; - (options: EnumDataTypeOptions): EnumDataType; -} - -export interface EnumDataType extends AbstractDataType { - values: T[]; - options: EnumDataTypeOptions; -} - -export interface EnumDataTypeOptions { - values: T[]; -} - -/** - * An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres. - */ -export const ARRAY: ArrayDataTypeConstructor; - -interface ArrayDataTypeConstructor extends AbstractDataTypeConstructor { - new (type: T): ArrayDataType; - new (options: ArrayDataTypeOptions): ArrayDataType; - (type: T): ArrayDataType; - (options: ArrayDataTypeOptions): ArrayDataType; - is(obj: unknown, type: T): obj is ArrayDataType; -} - -export interface ArrayDataType extends AbstractDataType { - options: ArrayDataTypeOptions; -} - -export interface ArrayDataTypeOptions { - type: T; -} - -/** - * A geometry datatype represents two dimensional spacial objects. - */ -export const GEOMETRY: GeometryDataTypeConstructor; - -interface GeometryDataTypeConstructor extends AbstractDataTypeConstructor { - new (type: string, srid?: number): GeometryDataType; - new (options: GeometryDataTypeOptions): GeometryDataType; - (type: string, srid?: number): GeometryDataType; - (options: GeometryDataTypeOptions): GeometryDataType; -} - -export interface GeometryDataType extends AbstractDataType { - options: GeometryDataTypeOptions; - type: string; - srid?: number; - escape: boolean; -} - -export interface GeometryDataTypeOptions { - type: string; - srid?: number; -} - -/** - * A geography datatype represents two dimensional spacial objects in an elliptic coord system. - */ -export const GEOGRAPHY: GeographyDataTypeConstructor; - -interface GeographyDataTypeConstructor extends AbstractDataTypeConstructor { - new (type: string, srid?: number): GeographyDataType; - new (options: GeographyDataTypeOptions): GeographyDataType; - (type: string, srid?: number): GeographyDataType; - (options: GeographyDataTypeOptions): GeographyDataType; -} - -export interface GeographyDataType extends AbstractDataType { - options: GeographyDataTypeOptions; - type: string; - srid?: number; - escape: boolean; -} - -export interface GeographyDataTypeOptions { - type: string; - srid?: number; -} - -export const CIDR: AbstractDataTypeConstructor; - -export const INET: AbstractDataTypeConstructor; - -export const MACADDR: AbstractDataTypeConstructor; - -/** - * Case incenstive text - */ -export const CITEXT: AbstractDataTypeConstructor; - -// umzug compatibility -export type DataTypeAbstract = AbstractDataTypeConstructor; diff --git a/types/lib/deferrable.d.ts b/types/lib/deferrable.d.ts deleted file mode 100644 index 887cb781fe60..000000000000 --- a/types/lib/deferrable.d.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Can be used to - * make foreign key constraints deferrable and to set the constaints within a - * transaction. This is only supported in PostgreSQL. - * - * The foreign keys can be configured like this. It will create a foreign key - * that will check the constraints immediately when the data was inserted. - * - * ```js - * class MyModel extends Model {} - * MyModel.init({ - * foreign_id: { - * type: Sequelize.INTEGER, - * references: { - * model: OtherModel, - * key: 'id', - * deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE - * } - * } - * }, { sequelize }); - * ``` - * - * The constraints can be configured in a transaction like this. It will - * trigger a query once the transaction has been started and set the constraints - * to be checked at the very end of the transaction. - * - * ```js - * sequelize.transaction({ - * deferrable: Sequelize.Deferrable.SET_DEFERRED - * }); - * ``` - */ - -/** - * - */ -export interface AbstractDeferrableStatic { - new (): Deferrable; - (): Deferrable; -} -export interface Deferrable { - toString(): string; - toSql(): string; -} - -export interface InitiallyDeferredDeferrableStatic extends AbstractDeferrableStatic { - new (): InitiallyDeferredDeferrable; - (): InitiallyDeferredDeferrable; -} -export interface InitiallyDeferredDeferrable extends Deferrable {} -export const INITIALLY_DEFERRED: InitiallyDeferredDeferrableStatic; - -export interface InitiallyImmediateDeferrableStatic extends AbstractDeferrableStatic { - new (): InitiallyImmediateDeferrable; - (): InitiallyImmediateDeferrable; -} -export interface InitiallyImmediateDeferrable extends Deferrable {} -export const INITIALLY_IMMEDIATE: InitiallyImmediateDeferrableStatic; - -export interface NotDeferrableStatic extends AbstractDeferrableStatic { - new (): NotDeferrable; - (): NotDeferrable; -} -export interface NotDeferrable extends Deferrable {} -/** - * Will 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. - */ -export const NOT: NotDeferrableStatic; - -export interface SetDeferredDeferrableStatic extends AbstractDeferrableStatic { - /** - * @param constraints An array of constraint names. Will defer all constraints by default. - */ - new (constraints: string[]): SetDeferredDeferrable; - /** - * @param constraints An array of constraint names. Will defer all constraints by default. - */ - (constraints: string[]): SetDeferredDeferrable; -} -export interface SetDeferredDeferrable extends Deferrable {} -/** - * Will trigger an additional query at the beginning of a - * transaction which sets the constraints to deferred. - */ -export const SET_DEFERRED: SetDeferredDeferrableStatic; - -export interface SetImmediateDeferrableStatic extends AbstractDeferrableStatic { - /** - * @param constraints An array of constraint names. Will defer all constraints by default. - */ - new (constraints: string[]): SetImmediateDeferrable; - /** - * @param constraints An array of constraint names. Will defer all constraints by default. - */ - (constraints: string[]): SetImmediateDeferrable; -} -export interface SetImmediateDeferrable extends Deferrable {} -/** - * Will trigger an additional query at the beginning of a - * transaction which sets the constraints to immediately. - * - * @param constraints An array of constraint names. Will defer all constraints by default. - */ -export const SET_IMMEDIATE: SetImmediateDeferrableStatic; diff --git a/types/lib/errors.d.ts b/types/lib/errors.d.ts deleted file mode 100644 index a575486bec62..000000000000 --- a/types/lib/errors.d.ts +++ /dev/null @@ -1,220 +0,0 @@ -import Model from "./model"; - -/** - * The Base Error all Sequelize Errors inherit from. - */ -export class BaseError extends Error { - public name: string; -} - -/** - * Scope Error. Thrown when the sequelize cannot query the specified scope. - */ -export class SequelizeScopeError extends BaseError {} - -export class ValidationError extends BaseError { - /** Array of ValidationErrorItem objects describing the validation errors */ - public readonly errors: ValidationErrorItem[]; - - /** - * Validation Error. Thrown when the sequelize validation has failed. The error contains an `errors` - * property, which is an array with 1 or more ValidationErrorItems, one for each validation that failed. - * - * @param message Error message - * @param errors Array of ValidationErrorItem objects describing the validation errors - */ - constructor(message: string, errors?: ValidationErrorItem[]); - - /** - * Gets all validation error items for the path / field specified. - * - * @param path The path to be checked for error items - */ - public get(path: string): ValidationErrorItem[]; -} - -export class ValidationErrorItem { - /** An error message */ - public readonly message: string; - - /** The type/origin of the validation error */ - public readonly type: string | null; - - /** The field that triggered the validation error */ - public readonly path: string | null; - - /** The value that generated the error */ - 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; - - /** - * 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/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, - instance?: object, - validatorKey?: string, - fnName?: string, - fnArgs?: unknown[] - ); -} - -export interface CommonErrorProperties { - /** The database specific error which triggered this one */ - readonly parent: Error; - - /** The database specific error which triggered this one */ - readonly original: Error; - - /** The SQL that triggered the error */ - readonly sql: string; -} - -/** - * Thrown when a record was not found, Usually used with rejectOnEmpty mode (see message for details) - */ -export class EmptyResultError extends BaseError { -} - -export class DatabaseError extends BaseError implements CommonErrorProperties { - public readonly parent: Error; - public readonly original: Error; - public readonly sql: string; - public readonly parameters: Array; - /** - * A base class for all database related errors. - * @param parent The database specific error which triggered this one - */ - constructor(parent: Error); -} - -/** Thrown when a database query times out because of a deadlock */ -export class TimeoutError extends DatabaseError {} - -export interface UniqueConstraintErrorOptions { - parent?: Error; - message?: string; - errors?: ValidationErrorItem[]; - fields?: { [key: string]: unknown }; - original?: Error; -} - -/** - * Thrown when a unique constraint is violated in the database - */ -export class UniqueConstraintError extends ValidationError implements CommonErrorProperties { - public readonly parent: Error; - public readonly original: Error; - public readonly sql: string; - public readonly fields: { [key: string]: unknown }; - constructor(options?: UniqueConstraintErrorOptions); -} - -/** - * Thrown when a foreign key constraint is violated in the database - */ -export class ForeignKeyConstraintError extends DatabaseError { - public table: string; - public fields: { [field: string]: string }; - public value: unknown; - public index: string; - constructor(options: { parent?: Error; message?: string; index?: string; fields?: string[]; table?: string }); -} - -/** - * Thrown when an exclusion constraint is violated in the database - */ -export class ExclusionConstraintError 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 - */ -export class OptimisticLockError extends BaseError { - constructor(options: { message?: string, modelName?: string, values?: { [key: string]: any }, where?: { [key: string]: any } }); -} - -/** - * A base class for all connection related errors. - */ -export class ConnectionError extends BaseError { - public parent: Error; - public original: Error; - constructor(parent: Error); -} - -/** - * Thrown when a connection to a database is refused - */ -export class ConnectionRefusedError extends ConnectionError {} - -/** - * Thrown when a connection to a database is refused due to insufficient privileges - */ -export class AccessDeniedError extends ConnectionError {} - -/** - * Thrown when a connection to a database has a hostname that was not found - */ -export class HostNotFoundError extends ConnectionError {} - -/** - * Thrown when a connection to a database has a hostname that was not reachable - */ -export class HostNotReachableError extends ConnectionError {} - -/** - * Thrown when a connection to a database has invalid values for any of the connection parameters - */ -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 deleted file mode 100644 index d19f93065f55..000000000000 --- a/types/lib/hooks.d.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ModelType } from '../index'; -import { ValidationOptions } from './instance-validator'; -import Model, { - BulkCreateOptions, - CountOptions, - CreateOptions, - DestroyOptions, - RestoreOptions, - FindOptions, - InstanceDestroyOptions, - InstanceRestoreOptions, - InstanceUpdateOptions, - ModelAttributes, - ModelOptions, - UpdateOptions, -} from './model'; -import { Config, Options, Sequelize, SyncOptions } from './sequelize'; - -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 { - beforeValidate(instance: M, options: ValidationOptions): HookReturn; - afterValidate(instance: M, options: ValidationOptions): HookReturn; - beforeCreate(attributes: M, options: CreateOptions): HookReturn; - afterCreate(attributes: M, options: CreateOptions): HookReturn; - beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; - afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; - 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: 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; -} - -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; - afterConnect(connection: unknown, config: Config): HookReturn; - beforeDisconnect(connection: unknown): HookReturn; - afterDisconnect(connection: unknown): HookReturn; -} - -/** - * Virtual class for deduplication - */ -export class Hooks< - M extends Model = Model, - TModelAttributes extends {} = any, - TCreationAttributes extends {} = TModelAttributes -> { - /** - * A dummy variable that doesn't exist on the real object. This exists so - * Typescript can infer the type of the attributes in static functions. Don't - * try to access this! - */ - _model: M; - /** - * A similar dummy variable that doesn't exist on the real object. Do not - * try to access this in real code. - */ - _attributes: TModelAttributes; - /** - * A similar dummy variable that doesn't exist on the real object. Do not - * try to access this in real code. - */ - _creationAttributes: TCreationAttributes; - - /** - * Add a hook to the model - * - * @param name Provide a name for the hook function. It can be used to remove the hook later or to order - * hooks based on some sort of priority system in the future. - */ - public static addHook< - H extends Hooks, - K extends keyof SequelizeHooks - >( - this: HooksStatic, - hookType: K, - name: string, - fn: SequelizeHooks[K] - ): HooksCtor; - public static addHook< - H extends Hooks, - K extends keyof SequelizeHooks - >( - this: HooksStatic, - hookType: K, - fn: SequelizeHooks[K] - ): HooksCtor; - - /** - * Remove hook from the model - */ - public static removeHook( - this: HooksStatic, - hookType: keyof SequelizeHooks, - name: string, - ): HooksCtor; - - /** - * Check whether the mode has any hooks of this type - */ - public static hasHook( - this: HooksStatic, - hookType: keyof SequelizeHooks, - ): boolean; - public static hasHooks( - this: HooksStatic, - hookType: keyof SequelizeHooks, - ): boolean; - - /** - * Add a hook to the model - * - * @param name Provide a name for the hook function. It can be used to remove the hook later or to order - * hooks based on some sort of priority system in the future. - */ - public addHook>( - hookType: K, - name: string, - fn: SequelizeHooks[K] - ): this; - public addHook>( - hookType: K, fn: SequelizeHooks[K]): this; - /** - * Remove hook from the model - */ - 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; -} - -export type HooksCtor = typeof Hooks & { new(): H }; - -export type HooksStatic = { new(): H }; diff --git a/types/lib/index-hints.d.ts b/types/lib/index-hints.d.ts deleted file mode 100644 index f33d182d9e8c..000000000000 --- a/types/lib/index-hints.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Available index hints to be used for querying data in mysql for index hints. - */ -declare enum IndexHints { - USE = 'USE', - FORCE = 'FORCE', - IGNORE = 'IGNORE' -} - -export = IndexHints; diff --git a/types/lib/instance-validator.d.ts b/types/lib/instance-validator.d.ts deleted file mode 100644 index c2f3469d81ac..000000000000 --- a/types/lib/instance-validator.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Hookable } from "./model"; - -export interface ValidationOptions extends Hookable { - /** - * An array of strings. All properties that are in this array will not be validated - */ - skip?: string[]; - /** - * An array of strings. Only the properties that are in this array will be validated - */ - fields?: string[]; -} diff --git a/types/lib/model-manager.d.ts b/types/lib/model-manager.d.ts deleted file mode 100644 index 41e72dae6636..000000000000 --- a/types/lib/model-manager.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Model, ModelType } from './model'; -import { Sequelize } from './sequelize'; - -export class ModelManager { - public sequelize: Sequelize; - public models: typeof Model[]; - public all: typeof Model[]; - - constructor(sequelize: Sequelize); - public addModel(model: T): T; - public removeModel(model: ModelType): void; - public getModel(against: unknown, options?: { attribute?: string }): typeof Model; -} - -export default ModelManager; diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts deleted file mode 100644 index 29a754e09aa4..000000000000 --- a/types/lib/model.d.ts +++ /dev/null @@ -1,2858 +0,0 @@ -import { IndexHints } from '..'; -import { Association, BelongsTo, BelongsToMany, BelongsToManyOptions, BelongsToOptions, HasMany, HasManyOptions, HasOne, HasOneOptions } from './associations/index'; -import { DataType } from './data-types'; -import { Deferrable } from './deferrable'; -import { HookReturn, Hooks, ModelHooks } from './hooks'; -import { ValidationOptions } from './instance-validator'; -import { QueryOptions, IndexesOptions, TableName } from './query-interface'; -import { Sequelize, SyncOptions } from './sequelize'; -import { Transaction, LOCK } from './transaction'; -import { Col, Fn, Literal, Where } from './utils'; -import Op = require('./operators'); - -export interface Logging { - /** - * A function that gets executed while running the query to log the sql. - */ - logging?: boolean | ((sql: string, timing?: number) => void); - - /** - * Pass query execution time in milliseconds as second argument to logging function (options.logging). - */ - benchmark?: boolean; -} - -export interface Poolable { - /** - * Force the query to use the write pool, regardless of the query type. - * - * @default false - */ - useMaster?: boolean; -} - -export interface Transactionable { - /** - * Transaction to run query under - */ - transaction?: Transaction | null; -} - -export interface SearchPathable { - /** - * An optional parameter to specify the schema search_path (Postgres only) - */ - searchPath?: string; -} - -export interface Filterable { - /** - * Attribute has to be matched for rows to be selected for the given action. - */ - where?: WhereOptions; -} - -export interface Projectable { - /** - * A list of the attributes that you want to select. 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 - */ - attributes?: FindAttributeOptions; -} - -export interface Paranoid { - /** - * 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. - */ - paranoid?: boolean; -} - -export type GroupOption = string | Fn | Col | (string | Fn | Col)[]; - -/** - * Options to pass to Model on drop - */ -export interface DropOptions extends Logging { - /** - * Also drop all objects depending on this table, such as views. Only works in postgres - */ - cascade?: boolean; -} - -/** - * Schema Options provided for applying a schema to a model - */ -export interface SchemaOptions extends Logging { - /** - * The character(s) that separates the schema name from the table name - */ - schemaDelimiter?: string; -} - -/** - * Scope Options for Model.scope - */ -export interface ScopeOptions { - /** - * 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. - */ - method: string | readonly [string, ...unknown[]]; -} - -/** - * The type accepted by every `where` option - */ -export type WhereOptions = - | WhereAttributeHash - | AndOperator - | OrOperator - | Literal - | Fn - | Where; - -/** - * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` - * - * _PG only_ - */ -export interface AnyOperator { - [Op.any]: readonly (string | number)[]; -} - -/** TODO: Undocumented? */ -export interface AllOperator { - [Op.all]: readonly (string | number | Date | Literal)[]; -} - -export type Rangable = readonly [number, number] | readonly [Date, Date] | Literal; - -/** - * Operators that can be used in WhereOptions - * - * See https://sequelize.org/master/en/v3/docs/querying/#operators - */ -export interface WhereOperators { - /** - * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` - * - * _PG only_ - */ - [Op.any]?: readonly (string | number | Literal)[] | Literal; - - /** Example: `[Op.gte]: 6,` becomes `>= 6` */ - [Op.gte]?: number | string | Date | Literal; - - /** Example: `[Op.lt]: 10,` becomes `< 10` */ - [Op.lt]?: number | string | Date | Literal; - - /** Example: `[Op.lte]: 10,` becomes `<= 10` */ - [Op.lte]?: number | string | Date | Literal; - - /** Example: `[Op.ne]: 20,` becomes `!= 20` */ - [Op.ne]?: null | string | number | Literal | WhereOperators; - - /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ - [Op.not]?: null | boolean | string | number | Literal | WhereOperators; - - /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ - [Op.between]?: Rangable; - - /** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */ - [Op.in]?: readonly (string | number | Literal)[] | Literal; - - /** Example: `[Op.notIn]: [1, 2],` becomes `NOT IN [1, 2]` */ - [Op.notIn]?: readonly (string | number | Literal)[] | Literal; - - /** - * Examples: - * - `[Op.like]: '%hat',` becomes `LIKE '%hat'` - * - `[Op.like]: { [Op.any]: ['cat', 'hat']}` becomes `LIKE ANY ARRAY['cat', 'hat']` - */ - [Op.like]?: string | Literal | AnyOperator | AllOperator; - - /** - * Examples: - * - `[Op.notLike]: '%hat'` becomes `NOT LIKE '%hat'` - * - `[Op.notLike]: { [Op.any]: ['cat', 'hat']}` becomes `NOT LIKE ANY ARRAY['cat', 'hat']` - */ - [Op.notLike]?: string | Literal | AnyOperator | AllOperator; - - /** - * case insensitive PG only - * - * Examples: - * - `[Op.iLike]: '%hat'` becomes `ILIKE '%hat'` - * - `[Op.iLike]: { [Op.any]: ['cat', 'hat']}` becomes `ILIKE ANY ARRAY['cat', 'hat']` - */ - [Op.iLike]?: string | Literal | AnyOperator | AllOperator; - - /** - * PG array overlap operator - * - * Example: `[Op.overlap]: [1, 2]` becomes `&& [1, 2]` - */ - [Op.overlap]?: Rangable; - - /** - * PG array contains operator - * - * Example: `[Op.contains]: [1, 2]` becomes `@> [1, 2]` - */ - [Op.contains]?: readonly (string | number)[] | Rangable; - - /** - * PG array contained by operator - * - * Example: `[Op.contained]: [1, 2]` becomes `<@ [1, 2]` - */ - [Op.contained]?: readonly (string | number)[] | Rangable; - - /** Example: `[Op.gt]: 6,` becomes `> 6` */ - [Op.gt]?: number | string | Date | Literal; - - /** - * PG only - * - * Examples: - * - `[Op.notILike]: '%hat'` becomes `NOT ILIKE '%hat'` - * - `[Op.notLike]: ['cat', 'hat']` becomes `LIKE ANY ARRAY['cat', 'hat']` - */ - [Op.notILike]?: string | Literal | AnyOperator | AllOperator; - - /** Example: `[Op.notBetween]: [11, 15],` becomes `NOT BETWEEN 11 AND 15` */ - [Op.notBetween]?: Rangable; - - /** - * Strings starts with value. - */ - [Op.startsWith]?: string; - - /** - * String ends with value. - */ - [Op.endsWith]?: string; - /** - * String contains value. - */ - [Op.substring]?: string; - - /** - * MySQL/PG only - * - * Matches regular expression, case sensitive - * - * Example: `[Op.regexp]: '^[h|a|t]'` becomes `REGEXP/~ '^[h|a|t]'` - */ - [Op.regexp]?: string; - - /** - * MySQL/PG only - * - * Does not match regular expression, case sensitive - * - * Example: `[Op.notRegexp]: '^[h|a|t]'` becomes `NOT REGEXP/!~ '^[h|a|t]'` - */ - [Op.notRegexp]?: string; - - /** - * PG only - * - * Matches regular expression, case insensitive - * - * Example: `[Op.iRegexp]: '^[h|a|t]'` becomes `~* '^[h|a|t]'` - */ - [Op.iRegexp]?: string; - - /** - * PG only - * - * Does not match regular expression, case insensitive - * - * Example: `[Op.notIRegexp]: '^[h|a|t]'` becomes `!~* '^[h|a|t]'` - */ - [Op.notIRegexp]?: string; - - /** - * PG only - * - * Forces the operator to be strictly left eg. `<< [a, b)` - */ - [Op.strictLeft]?: Rangable; - - /** - * PG only - * - * Forces the operator to be strictly right eg. `>> [a, b)` - */ - [Op.strictRight]?: Rangable; - - /** - * PG only - * - * Forces the operator to not extend the left eg. `&> [1, 2)` - */ - [Op.noExtendLeft]?: Rangable; - - /** - * PG only - * - * Forces the operator to not extend the left eg. `&< [1, 2)` - */ - [Op.noExtendRight]?: Rangable; - -} - -/** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */ -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 | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; -} - -/** - * Where Geometry Options - */ -export interface WhereGeometryOptions { - type: string; - coordinates: readonly (number[] | number)[]; -} - -/** - * Used for the right hand side of WhereAttributeHash. - * WhereAttributeHash is in there for JSON columns. - */ -export type WhereValue = - | string - | number - | bigint - | boolean - | Date - | Buffer - | null - | WhereOperators - | WhereAttributeHash // for JSON columns - | Col // reference another column - | Fn - | OrOperator - | AndOperator - | WhereGeometryOptions - | readonly (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] - -/** - * A hash of attributes to describe your search. - */ -export type WhereAttributeHash = { - /** - * Possible key values: - * - A simple attribute name - * - A nested key for JSON columns - * - * { - * "meta.audio.length": { - * [Op.gt]: 20 - * } - * } - */ - [field in keyof TAttributes]?: WhereValue | WhereOptions; -} -/** - * Through options for Include Options - */ -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; -} - -/** - * Options for eager-loading associated models, also allowing for all associations to be loaded at once - */ -export type Includeable = ModelType | Association | IncludeOptions | { all: true, nested?: true } | string; - -/** - * Complex include options - */ -export interface IncludeOptions extends Filterable, Projectable, Paranoid { - /** - * Mark the include as duplicating, will prevent a subquery from being used. - */ - duplicating?: boolean; - /** - * The model you want to eagerly load - */ - model?: ModelType; - - /** - * 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; - - /** - * The association you want to eagerly load. (This can be used instead of providing a model/as pair) - */ - association?: Association | string; - - /** - * Custom `on` clause, overrides default. - */ - on?: WhereOptions; - - /** - * Note that this converts the eager load to an inner join, - * unless you explicitly set `required: false` - */ - where?: WhereOptions; - - /** - * 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. - */ - required?: boolean; - - /** - * If true, converts to a right join if dialect support it. Ignored if `include.required` is true. - */ - right?: boolean; - - /** - * Limit include. Only available when setting `separate` to true. - */ - limit?: number; - - /** - * Run include in separate queries. - */ - separate?: boolean; - - /** - * Through Options - */ - through?: IncludeThroughOptions; - - /** - * Load further nested related models - */ - include?: Includeable[]; - - /** - * Order include. Only available when setting `separate` to true. - */ - order?: Order; - - /** - * Use sub queries. This should only be used if you know for sure the query does not result in a cartesian product. - */ - subQuery?: boolean; -} - -type OrderItemAssociation = Association | ModelStatic | { model: ModelStatic; as: string } | string -type OrderItemColumn = string | Col | Fn | Literal -export type OrderItem = - | string - | Fn - | Col - | Literal - | [OrderItemColumn, string] - | [OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] -export type Order = Fn | Col | Literal | OrderItem[]; - -/** - * Please note if this is used the aliased property will not be available on the model instance - * as a property but only via `instance.get('alias')`. - */ -export type ProjectionAlias = readonly [string | Literal | Fn | Col, string]; - -export type FindAttributeOptions = - | (string | ProjectionAlias)[] - | { - exclude: string[]; - include?: (string | ProjectionAlias)[]; - } - | { - exclude?: string[]; - include: (string | ProjectionAlias)[]; - }; - -export interface IndexHint { - type: IndexHints; - values: string[]; -} - -export interface IndexHintable { - /** - * MySQL only. - */ - indexHints?: IndexHint[]; -} - -type Omit = Pick> - -/** - * Options that are passed to any model creating a SELECT query - * - * A hash of options to describe the scope of the search - */ -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 - * `{ include: [{ all: true }]}`. - * If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in - * the as attribute when eager loading Y). - */ - include?: Includeable | Includeable[]; - - /** - * Specifies an ordering. If a string is provided, it will be escaped. 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. - */ - order?: Order; - - /** - * GROUP BY in sql - */ - group?: GroupOption; - - /** - * Limit the results - */ - limit?: number; - - /** - * Skip the results; - */ - offset?: number; - - /** - * 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) - */ - lock?: - | LOCK - | { level: LOCK; of: ModelStatic } - | boolean; - /** - * Skip locked rows. Only supported in Postgres. - */ - skipLocked?: boolean; - - /** - * Return raw result. See sequelize.query for more information. - */ - raw?: boolean; - - /** - * Select group rows after groups and aggregates are computed. - */ - having?: WhereOptions; - - /** - * Use sub queries (internal) - */ - subQuery?: boolean; -} - -export interface NonNullFindOptions extends FindOptions { - /** - * Throw if nothing was found. - */ - rejectOnEmpty: boolean | Error; -} - -/** - * Options for Model.count method - */ -export interface CountOptions - extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable -{ - /** - * Include options. See `find` for details - */ - include?: Includeable | Includeable[]; - - /** - * Apply COUNT(DISTINCT(col)) - */ - distinct?: boolean; - - /** - * GROUP BY in sql - * Used in conjunction with `attributes`. - * @see Projectable - */ - group?: GroupOption; - - /** - * The column to aggregate on. - */ - col?: string; -} - -/** - * Options for Model.count when GROUP BY is used - */ -export interface CountWithOptions extends CountOptions { - /** - * GROUP BY in sql - * Used in conjunction with `attributes`. - * @see Projectable - */ - group: GroupOption; -} - -export interface FindAndCountOptions extends CountOptions, FindOptions { } - -/** - * Options for Model.build method - */ -export interface BuildOptions { - /** - * If set to true, values will ignore field and virtual setters. - */ - raw?: boolean; - - /** - * Is this record new - */ - isNewRecord?: boolean; - - /** - * An array of include options. A single option is also supported - Used to build prefetched/included model instances. See `set` - * - * TODO: See set - */ - include?: Includeable | Includeable[]; -} - -export interface Silent { - /** - * If true, the updatedAt timestamp will not be updated. - * - * @default false - */ - silent?: boolean; -} - -/** - * Options for Model.create method - */ -export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { - /** - * If set, only columns matching those in fields will be saved - */ - fields?: (keyof TAttributes)[]; - - /** - * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE - */ - ignoreDuplicates?: boolean; - - /** - * Return the affected rows (only for postgres) - */ - returning?: boolean | (keyof TAttributes)[]; - - /** - * If false, validations won't be run. - * - * @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 FindOptions -{ - /** - * The fields to insert / update. Defaults to all fields - */ - fields?: (keyof TAttributes)[]; - /** - * Default values to use if building a new instance - */ - defaults?: TCreationAttributes; -} - -/** - * Options for Model.upsert method - */ -export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { - /** - * The fields to insert / update. Defaults to all fields - */ - fields?: (keyof TAttributes)[]; - - /** - * Return the affected rows (only for postgres) - */ - returning?: boolean | (keyof TAttributes)[]; - - /** - * Run validations before the row is inserted - */ - validate?: boolean; -} - -/** - * Options for Model.bulkCreate method - */ -export interface BulkCreateOptions extends Logging, Transactionable, Hookable { - /** - * Fields to insert (defaults to all fields) - */ - fields?: (keyof TAttributes)[]; - - /** - * Should each row be subject to validation before it is inserted. The whole insert will fail if one row - * fails validation - */ - validate?: boolean; - - /** - * Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if - * options.hooks is true. - */ - individualHooks?: boolean; - - /** - * Ignore duplicate values for primary keys? - * - * @default false - */ - ignoreDuplicates?: boolean; - - /** - * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, - * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. - */ - updateOnDuplicate?: (keyof TAttributes)[]; - - /** - * Include options. See `find` for details - */ - include?: Includeable | Includeable[]; - - /** - * Return all columns or only the specified columns for the affected rows (only for postgres) - */ - returning?: boolean | (keyof TAttributes)[]; -} - -/** - * The options passed to Model.destroy in addition to truncate - */ -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. - * - * @default false; - */ - cascade?: boolean; - - /** - * If set to true, destroy will SELECT all records matching the where parameter and will execute before / - * after destroy hooks on each row - */ - individualHooks?: boolean; - - /** - * How many rows to delete - */ - limit?: number; - - /** - * Delete instead of setting deletedAt to current timestamp (only applicable if `paranoid` is enabled) - */ - force?: boolean; - - /** - * Only used in conjunction with `truncate`. - * Automatically restart sequences owned by columns of the truncated table - */ - restartIdentity?: boolean; -} - -/** - * Options used for Model.destroy - */ -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 - */ - truncate?: boolean; -} - -/** - * Options for Model.restore - */ -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 - * bulkRestore hooks on each row - */ - individualHooks?: boolean; - - /** - * How many rows to undelete - */ - limit?: number; -} - -/** - * Options used for Model.update - */ -export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { - /** - * Options to describe the scope of the search. - */ - where: WhereOptions; - - /** - * Fields to update (defaults to all fields) - */ - fields?: (keyof TAttributes)[]; - - /** - * Should each row be subject to validation before it is inserted. The whole insert will fail if one row - * fails validation. - * - * @default true - */ - validate?: boolean; - - /** - * Whether or not to update the side effects of any virtual setters. - * - * @default true - */ - sideEffects?: boolean; - - /** - * Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. - * A select is needed, because the row data needs to be passed to the hooks - * - * @default false - */ - individualHooks?: boolean; - - /** - * Return the affected rows (only for postgres) - */ - returning?: boolean | (keyof TAttributes)[]; - - /** - * How many rows to update (only for mysql and mariadb) - */ - limit?: number; - - /** - * If true, the updatedAt timestamp will not be updated. - */ - silent?: boolean; -} - -/** - * Options used for Model.aggregate - */ -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. - */ - dataType?: string | T; - - /** - * Applies DISTINCT to the field being aggregated over - */ - distinct?: boolean; -} - -// instance - -/** - * Options used for Instance.increment method - */ -export interface IncrementDecrementOptions - extends Logging, Transactionable, Silent, SearchPathable, Filterable { } - -/** - * Options used for Instance.increment method - */ -export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { - /** - * The number to increment by - * - * @default 1 - */ - by?: number; -} - -/** - * Options used for Instance.restore method - */ -export interface InstanceRestoreOptions extends Logging, Transactionable { } - -/** - * Options used for Instance.destroy method - */ -export interface InstanceDestroyOptions extends Logging, Transactionable { - /** - * If set to true, paranoid models will actually be deleted - */ - force?: boolean; -} - -/** - * Options used for Instance.update method - */ -export interface InstanceUpdateOptions extends - SaveOptions, SetOptions, Filterable { } - -/** - * Options used for Instance.set method - */ -export interface SetOptions { - /** - * If set to true, field and virtual setters will be ignored - */ - raw?: boolean; - - /** - * Clear all previously set data values - */ - reset?: boolean; -} - -/** - * Options used for Instance.save method - */ -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?: (keyof TAttributes)[]; - - /** - * If false, validations won't be run. - * - * @default true - */ - validate?: boolean; -} - -/** - * 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. - * - * The validations are implemented by validator.js. - */ -export interface ModelValidateOptions { - /** - * - `{ is: ['^[a-z]+$','i'] }` will only allow letters - * - `{ is: /^[a-z]+$/i }` also only allows letters - */ - is?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; - - /** - * - `{ not: ['[a-z]','i'] }` will not allow letters - */ - not?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; - - /** - * checks for email format (foo@bar.com) - */ - isEmail?: boolean | { msg: string }; - - /** - * checks for url format (http://foo.com) - */ - isUrl?: boolean | { msg: string }; - - /** - * checks for IPv4 (129.89.23.1) or IPv6 format - */ - isIP?: boolean | { msg: string }; - - /** - * checks for IPv4 (129.89.23.1) - */ - isIPv4?: boolean | { msg: string }; - - /** - * checks for IPv6 format - */ - isIPv6?: boolean | { msg: string }; - - /** - * will only allow letters - */ - isAlpha?: boolean | { msg: string }; - - /** - * will only allow alphanumeric characters, so "_abc" will fail - */ - isAlphanumeric?: boolean | { msg: string }; - - /** - * will only allow numbers - */ - isNumeric?: boolean | { msg: string }; - - /** - * checks for valid integers - */ - isInt?: boolean | { msg: string }; - - /** - * checks for valid floating point numbers - */ - isFloat?: boolean | { msg: string }; - - /** - * checks for any numbers - */ - isDecimal?: boolean | { msg: string }; - - /** - * checks for lowercase - */ - isLowercase?: boolean | { msg: string }; - - /** - * checks for uppercase - */ - isUppercase?: boolean | { msg: string }; - - /** - * won't allow null - */ - notNull?: boolean | { msg: string }; - - /** - * only allows null - */ - isNull?: boolean | { msg: string }; - - /** - * don't allow empty strings - */ - notEmpty?: boolean | { msg: string }; - - /** - * only allow a specific value - */ - equals?: string | { msg: string }; - - /** - * force specific substrings - */ - contains?: string | { msg: string }; - - /** - * check the value is not one of these - */ - notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; - - /** - * check the value is one of these - */ - isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; - - /** - * don't allow specific substrings - */ - notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string }; - - /** - * only allow values with length between 2 and 10 - */ - len?: readonly [number, number] | { msg: string; args: readonly [number, number] }; - - /** - * only allow uuids - */ - isUUID?: number | { msg: string; args: number }; - - /** - * only allow date strings - */ - isDate?: boolean | { msg: string; args: boolean }; - - /** - * only allow date strings after a specific date - */ - isAfter?: string | { msg: string; args: string }; - - /** - * only allow date strings before a specific date - */ - isBefore?: string | { msg: string; args: string }; - - /** - * only allow values - */ - max?: number | { msg: string; args: readonly [number] }; - - /** - * only allow values >= 23 - */ - min?: number | { msg: string; args: readonly [number] }; - - /** - * only allow arrays - */ - isArray?: boolean | { msg: string; args: boolean }; - - /** - * check for valid credit card numbers - */ - 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 - */ - [name: string]: unknown; -} - -/** - * Interface for indexes property in InitOptions - */ -export type ModelIndexesOptions = IndexesOptions - -/** - * Interface for name property in InitOptions - */ -export interface ModelNameOptions { - /** - * Singular model name - */ - singular?: string; - - /** - * Plural model name - */ - plural?: string; -} - -/** - * Interface for getterMethods in InitOptions - */ -export interface ModelGetterOptions { - [name: string]: (this: M) => unknown; -} - -/** - * Interface for setterMethods in InitOptions - */ -export interface ModelSetterOptions { - [name: string]: (this: M, val: any) => void; -} - -/** - * Interface for Define Scope Options - */ -export interface ModelScopeOptions { - /** - * Name of the scope and it's query - */ - [scopeName: string]: FindOptions | ((...args: readonly any[]) => FindOptions); -} - -/** - * General column options - */ -export interface ColumnOptions { - /** - * If false, the column will have a NOT NULL constraint, and a not null validation will be run before an - * instance is saved. - * @default true - */ - allowNull?: boolean; - - /** - * If set, sequelize will map the attribute name to a different name in the database - */ - field?: string; - - /** - * A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`) - */ - defaultValue?: unknown; -} - -/** - * References options for the column's attributes - */ -export interface ModelAttributeColumnReferencesOptions { - /** - * If this column references another table, provide it here as a Model, or a string - */ - model?: TableName | ModelType; - - /** - * The column of the foreign table that this column references - */ - key?: string; - - /** - * When to check for the foreign key constraing - * - * PostgreSQL only - */ - deferrable?: Deferrable; -} - -/** - * Column options for the model schema attributes - */ -export interface ModelAttributeColumnOptions extends ColumnOptions { - /** - * A string or a data type - */ - type: DataType; - - /** - * If true, the column will get a unique constraint. If a string is provided, the column will be part of a - * composite unique index. If multiple columns have the same string, they will be part of the same unique - * index - */ - unique?: boolean | string | { name: string; msg: string }; - - /** - * Primary key flag - */ - primaryKey?: boolean; - - /** - * Is this field an auto increment field - */ - autoIncrement?: boolean; - - /** - * If this field is a Postgres auto increment field, use Postgres `GENERATED BY DEFAULT AS IDENTITY` instead of `SERIAL`. Postgres 10+ only. - */ - autoIncrementIdentity?: boolean; - - /** - * Comment for the database - */ - comment?: string; - - /** - * An object with reference configurations or the column name as string - */ - references?: string | ModelAttributeColumnReferencesOptions; - - /** - * What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or - * NO ACTION - */ - onUpdate?: string; - - /** - * What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or - * NO ACTION - */ - onDelete?: string; - - - /** - * 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 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, it - * it is async, the callback should be called with the error text. - */ - validate?: ModelValidateOptions; - - /** - * Usage in object notation - * - * ```js - * class MyModel extends Model {} - * MyModel.init({ - * states: { - * type: Sequelize.ENUM, - * values: ['active', 'pending', 'deleted'] - * } - * }, { sequelize }) - * ``` - */ - values?: readonly string[]; - - /** - * Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying - * values. - */ - get?(this: M): unknown; - - /** - * Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the - * underlying values. - */ - set?(this: M, val: unknown): void; -} - -/** - * Interface for Attributes provided for a column - */ -export type ModelAttributes = { - /** - * The description of a database column - */ - [name in keyof TCreationAttributes]: DataType | ModelAttributeColumnOptions; -} - -/** - * Possible types for primary keys - */ -export type Identifier = number | string | Buffer; - -/** - * Options for model definition - */ -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; - - /** - * 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; - - /** - * Don't persits null values. This means that all columns with null values will not be saved. - */ - omitNull?: boolean; - - /** - * Adds createdAt and updatedAt timestamps to the model. Default true. - */ - timestamps?: boolean; - - /** - * Calling destroy will not delete the model, but instead set a deletedAt timestamp if this is true. Needs - * timestamps=true to work. Default false. - */ - paranoid?: boolean; - - /** - * Converts all camelCased columns to underscored if true. Default false. - */ - underscored?: boolean; - - /** - * Indicates if the model's table has a trigger associated with it. Default false. - */ - hasTrigger?: boolean; - - /** - * If freezeTableName is true, sequelize will not try to alter the DAO name to get the table name. - * Otherwise, the dao name will be pluralized. Default false. - */ - freezeTableName?: boolean; - - /** - * An object with two attributes, `singular` and `plural`, which are used when this model is associated to - * others. - */ - name?: ModelNameOptions; - - /** - * Set name of the model. By default its same as Class name. - */ - modelName?: string; - - /** - * Indexes for the provided database table - */ - indexes?: readonly ModelIndexesOptions[]; - - /** - * Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps - * must be true. Not affected by underscored setting. - */ - createdAt?: string | boolean; - - /** - * Override the name of the deletedAt column if a string is provided, or disable it if false. Timestamps - * must be true. Not affected by underscored setting. - */ - deletedAt?: string | boolean; - - /** - * Override the name of the updatedAt column if a string is provided, or disable it if false. Timestamps - * must be true. Not affected by underscored setting. - */ - updatedAt?: string | boolean; - - /** - * @default pluralized model name, unless freezeTableName is true, in which case it uses model name - * verbatim - */ - tableName?: string; - - schema?: string; - - /** - * You can also change the database engine, e.g. to MyISAM. InnoDB is the default. - */ - engine?: string; - - charset?: string; - - /** - * Finaly you can specify a comment for the table in MySQL and PG - */ - comment?: string; - - collate?: string; - - /** - * Set the initial AUTO_INCREMENT value for the table in MySQL. - */ - initialAutoIncrement?: string; - - /** - * An object of hook function that are called before and after certain lifecycle events. - * 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>; - - /** - * An object of model wide validations. Validations have access to all model values via `this`. If the - * validator function takes an argument, it is asumed to be async, and is called with a callback that - * accepts an optional error. - */ - validate?: ModelValidateOptions; - - /** - * Allows defining additional setters that will be available on model instances. - */ - setterMethods?: ModelSetterOptions; - - /** - * Allows defining additional getters that will be available on model instances. - */ - getterMethods?: ModelGetterOptions; - - /** - * 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. - * - If string: Uses the named attribute. - * - If boolean: Uses `version`. - * @default false - */ - version?: boolean | string; -} - -/** - * Options passed to [[Model.init]] - */ -export interface InitOptions extends ModelOptions { - /** - * The sequelize connection. Required ATM. - */ - sequelize: Sequelize; -} - -/** - * AddScope Options for Model.addScope - */ -export interface AddScopeOptions { - /** - * If a scope of the same name already exists, should it be overwritten? - */ - override: boolean; -} - -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; - - /** - * The name of the primary key attribute - */ - public static readonly primaryKeyAttribute: string; - - /** - * The name of the primary key attributes - */ - public static readonly primaryKeyAttributes: readonly string[]; - - /** - * An object hash from alias to association object - */ - public static readonly associations: { - [key: string]: Association; - }; - - /** - * The options that the model was initialized with - */ - public static readonly options: InitOptions; - - /** - * The attributes of the model - */ - public static readonly rawAttributes: { [attribute: string]: ModelAttributeColumnOptions }; - - /** - * Reference to the sequelize instance the model was initialized with - */ - public static readonly sequelize?: Sequelize; - - /** - * Initialize a model, representing a table in the DB, with attributes and options. - * - * The table columns are define by the hash that is given as the second argument. Each attribute of the hash represents a column. A short table definition might look like this: - * - * ```js - * Project.init({ - * columnA: { - * type: Sequelize.BOOLEAN, - * validate: { - * is: ['[a-z]','i'], // will only allow letters - * max: 23, // only allow values <= 23 - * isIn: { - * args: [['en', 'zh']], - * msg: "Must be English or Chinese" - * } - * }, - * field: 'column_a' - * // Other attributes here - * }, - * columnB: Sequelize.STRING, - * columnC: 'MY VERY OWN COLUMN TYPE' - * }, {sequelize}) - * - * sequelize.models.modelName // The model will now be available in models under the class name - * ``` - * - * As shown above, column definitions can be either strings, a reference to one of the datatypes that are predefined on the Sequelize constructor, or an object that allows you to specify both the type of the column, and other attributes such as default values, foreign key constraints and custom setters and getters. - * - * For a list of possible data types, see https://sequelize.org/master/en/latest/docs/models-definition/#data-types - * - * For more about getters and setters, see https://sequelize.org/master/en/latest/docs/models-definition/#getters-setters - * - * For more about instance and class methods, see https://sequelize.org/master/en/latest/docs/models-definition/#expansion-of-models - * - * For more about validation, see https://sequelize.org/master/en/latest/docs/models-definition/#validations - * - * @param 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 options These options are merged with the default define options provided to the Sequelize constructor - * @return Return the initialized model - */ - public static init, M extends InstanceType>( - this: MS, - attributes: ModelAttributes, options: InitOptions - ): MS; - - /** - * Remove attribute from model definition - * - * @param attribute - */ - public static removeAttribute(attribute: string): void; - - /** - * 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; - - /** - * Drop the table represented by this Model - * - * @param options - */ - public static drop(options?: DropOptions): Promise; - - /** - * Apply a schema to this model. For postgres, this will actually place the schema in front of the table - * name - * - `"schema"."tableName"`, while the schema will be prepended to the table name for mysql and - * sqlite - `'schema.tablename'`. - * - * @param schema The name of the schema - * @param options - */ - public static schema( - this: ModelStatic, - schema: string, - options?: SchemaOptions - ): ModelCtor; - - /** - * Get the tablename 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. - * - * @param options The hash of options from any query. You can use one model to access tables with matching - * schemas by overriding `getTableName` and using custom key/values to alter the name of the table. - * (eg. - * subscribers_1, subscribers_2) - */ - public static getTableName(): string | { - tableName: string; - schema: string; - delimiter: string; - }; - - /** - * Apply a scope created in `define` to the model. First let's look at how to create scopes: - * ```js - * class MyModel extends Model {} - * MyModel.init(attributes, { - * defaultScope: { - * where: { - * username: 'dan' - * }, - * limit: 12 - * }, - * scopes: { - * isALie: { - * where: { - * stuff: 'cake' - * } - * }, - * complexFunction(email, accessLevel) { - * return { - * where: { - * email: { - * [Op.like]: email - * }, - * accesss_level { - * [Op.gte]: accessLevel - * } - * } - * } - * } - * }, - * sequelize, - * }) - * ``` - * Now, since you defined a default scope, every time you do Model.find, the default scope is appended to - * your query. Here's a couple of examples: - * ```js - * Model.findAll() // WHERE username = 'dan' - * Model.findAll({ where: { age: { gt: 12 } } }) // WHERE age > 12 AND username = 'dan' - * ``` - * - * To invoke scope functions you can do: - * ```js - * Model.scope({ method: ['complexFunction' 'dan@sequelize.com', 42]}).findAll() - * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 - * ``` - * - * @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: ModelStatic, - options?: string | ScopeOptions | readonly (string | ScopeOptions)[] | WhereAttributeHash - ): ModelCtor; - - /** - * Add a new scope to the model - * - * This is especially useful for adding scopes with includes, when the model you want to - * include is not available at the time this model is defined. 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. - */ - 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. - * - * __Simple search using AND and =__ - * ```js - * Model.findAll({ - * where: { - * attr1: 42, - * attr2: 'cake' - * } - * }) - * ``` - * ```sql - * WHERE attr1 = 42 AND attr2 = 'cake' - * ``` - * - * __Using greater than, less than etc.__ - * ```js - * - * Model.findAll({ - * where: { - * attr1: { - * gt: 50 - * }, - * attr2: { - * lte: 45 - * }, - * attr3: { - * in: [1,2,3] - * }, - * attr4: { - * ne: 5 - * } - * } - * }) - * ``` - * ```sql - * WHERE attr1 > 50 AND attr2 <= 45 AND attr3 IN (1,2,3) AND attr4 != 5 - * ``` - * Possible options are: `[Op.ne], [Op.in], [Op.not], [Op.notIn], [Op.gte], [Op.gt], [Op.lte], [Op.lt], [Op.like], [Op.ilike]/[Op.iLike], [Op.notLike], - * [Op.notILike], '..'/[Op.between], '!..'/[Op.notBetween], '&&'/[Op.overlap], '@>'/[Op.contains], '<@'/[Op.contained]` - * - * __Queries using OR__ - * ```js - * Model.findAll({ - * where: Sequelize.and( - * { name: 'a project' }, - * Sequelize.or( - * { id: [1,2,3] }, - * { id: { gt: 10 } } - * ) - * ) - * }) - * ``` - * ```sql - * WHERE name = 'a project' AND (id` IN (1,2,3) OR id > 10) - * ``` - * - * The success listener is called with an array of instances if the query succeeds. - * - * @see {Sequelize#query} - */ - 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: ModelStatic, - identifier: Identifier, - options: Omit, 'where'> - ): Promise; - public static findByPk( - this: ModelStatic, - identifier?: Identifier, - options?: Omit, 'where'> - ): Promise; - - /** - * Search for a single instance. Returns the first instance found, or null if none can be found. - */ - public static findOne( - this: ModelStatic, - options: NonNullFindOptions - ): Promise; - public static findOne( - this: ModelStatic, - options?: FindOptions - ): Promise; - - /** - * Run an aggregation method on the specified field - * - * @param field The field to aggregate over. Can be a field name or * - * @param aggregateFunction The function to use for aggregation, e.g. sum, max etc. - * @param options Query options. See sequelize.query for full options - * @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: ModelStatic, - field: keyof M['_attributes'] | '*', - aggregateFunction: string, - options?: AggregateOptions - ): Promise; - - /** - * Count number of records if group by is used - */ - 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( - this: ModelStatic, - options?: CountOptions - ): Promise; - - /** - * Find all the rows matching your query, within a specified offset / limit, and get the total number of - * rows matching your query. This is very usefull for paging - * - * ```js - * 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. - * - * When you add includes, only those which are required (either because they have a where clause, or - * because - * `required` is explicitly set to true on the include) 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 - */ - public static findAndCountAll( - this: ModelStatic, - options?: FindAndCountOptions - ): Promise<{ rows: M[]; count: number }>; - - /** - * Find the maximum value of field - */ - public static max( - this: ModelStatic, - field: keyof M['_attributes'], - options?: AggregateOptions - ): Promise; - - /** - * Find the minimum value of field - */ - public static min( - this: ModelStatic, - field: keyof M['_attributes'], - options?: AggregateOptions - ): Promise; - - /** - * Find the sum of field - */ - 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: ModelStatic, - record?: M['_creationAttributes'], - options?: BuildOptions - ): M; - - /** - * Undocumented bulkBuild - */ - public static bulkBuild( - this: ModelStatic, - records: ReadonlyArray, - options?: BuildOptions - ): M[]; - - /** - * Builds a new model instance and calls save on it. - */ - 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(([...]))` - */ - public static findOrBuild( - this: ModelStatic, - options: FindOrCreateOptions - ): Promise<[M, boolean]>; - - /** - * Find a row that matches the query, or build and save the row if none is found - * The successful result of the promise will be (instance, created) - Make sure to use `.then(([...]))` - * - * If no transaction is passed in the `options` object, a new transaction will be created internally, to - * prevent the race condition where a matching row is created by another connection after the find but - * before the insert call. However, it is not always possible to handle this case in SQLite, specifically - * if one transaction inserts and another tries to select before the first one has comitted. In this case, - * an instance of sequelize.TimeoutError will be thrown instead. If a transaction is created, a savepoint - * will be created instead, and any unique constraint violation will be handled internally. - */ - public static findOrCreate( - this: ModelStatic, - options: FindOrCreateOptions - ): Promise<[M, boolean]>; - - /** - * A more performant findOrCreate that will not work under a transaction (at least not in postgres) - * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again - */ - public static findCreateFind( - this: ModelStatic, - options: FindOrCreateOptions - ): Promise<[M, boolean]>; - - /** - * Insert or update a single row. An update will be executed if a row which matches the supplied values on - * either the primary key or a unique key is found. Note that the unique index must be defined in your - * sequelize model and not just in the table. Otherwise you may experience a unique constraint violation, - * because sequelize fails to identify the row that should be updated. - * - * **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 - * - * **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: ModelStatic, - values: M['_creationAttributes'], - options?: UpsertOptions - ): Promise<[M, boolean | null]>; - - /** - * Create and insert multiple instances in bulk. - * - * The success handler is passed an array of instances, but please notice that these may not completely - * represent the state of the rows in the DB. This is because MySQL 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. - * - * @param records List of objects (key/value pairs) to create instances from - */ - public static bulkCreate( - 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( - 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( - this: ModelStatic, - options?: DestroyOptions - ): Promise; - - /** - * Restore multiple instances if `paranoid` is enabled. - */ - 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 and mssql with `options.returning` true.) - */ - public static update( - 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: ModelStatic, - field: keyof M['_attributes'], - options: IncrementDecrementOptionsWithBy - ): Promise; - - /** - * Increments multiple fields by the same value. - */ - public static increment( - this: ModelStatic, - fields: ReadonlyArray, - options: IncrementDecrementOptionsWithBy - ): Promise; - - /** - * Increments multiple fields by different values. - */ - public static increment( - 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. - */ - public static describe(): Promise; - - /** - * Unscope the model - */ - public static unscoped(this: M): M; - - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeValidate( - this: ModelStatic, - name: string, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - public static beforeValidate( - this: ModelStatic, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterValidate( - this: ModelStatic, - name: string, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - public static afterValidate( - this: ModelStatic, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeCreate( - this: ModelStatic, - name: string, - fn: (instance: M, options: CreateOptions) => HookReturn - ): void; - public static beforeCreate( - this: ModelStatic, - fn: (instance: M, options: CreateOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static afterCreate( - this: ModelStatic, - name: string, - fn: (instance: M, options: CreateOptions) => HookReturn - ): void; - public static afterCreate( - this: ModelStatic, - fn: (instance: M, options: CreateOptions) => HookReturn - ): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeDestroy( - this: ModelStatic, - name: string, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - public static beforeDestroy( - this: ModelStatic, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterDestroy( - this: ModelStatic, - name: string, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - public static afterDestroy( - this: ModelStatic, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeUpdate( - this: ModelStatic, - name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - public static beforeUpdate( - this: ModelStatic, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterUpdate( - this: ModelStatic, - name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - public static afterUpdate( - this: ModelStatic, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeSave( - this: ModelStatic, - name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - public static beforeSave( - this: ModelStatic, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterSave( - this: ModelStatic, - name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - public static afterSave( - this: ModelStatic, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static beforeBulkCreate( - this: ModelStatic, - name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - public static beforeBulkCreate( - this: ModelStatic, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static afterBulkCreate( - this: ModelStatic, - name: string, - fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn - ): void; - public static afterBulkCreate( - this: ModelStatic, - fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn - ): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkDestroy( - this: ModelStatic, - name: string, fn: (options: BulkCreateOptions) => HookReturn): void; - public static beforeBulkDestroy( - this: ModelStatic, - fn: (options: BulkCreateOptions) => HookReturn - ): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkDestroy( - this: ModelStatic, - name: string, fn: (options: DestroyOptions) => HookReturn - ): void; - public static afterBulkDestroy( - this: ModelStatic, - fn: (options: DestroyOptions) => HookReturn - ): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkUpdate( - this: ModelStatic, - name: string, fn: (options: UpdateOptions) => HookReturn - ): void; - public static beforeBulkUpdate( - this: ModelStatic, - fn: (options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkUpdate( - this: ModelStatic, - name: string, fn: (options: UpdateOptions) => HookReturn - ): void; - public static afterBulkUpdate( - this: ModelStatic, - fn: (options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFind( - this: ModelStatic, - name: string, fn: (options: FindOptions) => HookReturn - ): void; - public static beforeFind( - this: ModelStatic, - fn: (options: FindOptions) => HookReturn - ): void; - - /** - * A hook that is run before a count query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeCount( - this: ModelStatic, - name: string, fn: (options: CountOptions) => HookReturn - ): void; - public static beforeCount( - this: ModelStatic, - fn: (options: CountOptions) => HookReturn - ): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterExpandIncludeAll( - this: ModelStatic, - name: string, fn: (options: FindOptions) => HookReturn - ): void; - public static beforeFindAfterExpandIncludeAll( - this: ModelStatic, - fn: (options: FindOptions) => HookReturn - ): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterOptions( - this: ModelStatic, - name: string, fn: (options: FindOptions) => HookReturn - ): void; - public static beforeFindAfterOptions( - this: ModelStatic, - fn: (options: FindOptions) => void - ): HookReturn; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public static afterFind( - this: ModelStatic, - name: string, - fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn - ): void; - public static afterFind( - this: ModelStatic, - fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn - ): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * Creates an association between this (the source) and the provided target. The foreign key is added - * on the target. - * - * Example: `User.hasOne(Profile)`. This will add userId to the profile table. - * - * @param target The model that will be associated with hasOne relationship - * @param options Options for the association - */ - public static hasOne( - this: ModelStatic, target: ModelStatic, options?: HasOneOptions - ): HasOne; - - /** - * Creates an association between this (the source) and the provided target. The foreign key is added on the - * source. - * - * Example: `Profile.belongsTo(User)`. This will add userId to the profile table. - * - * @param target The model that will be associated with hasOne relationship - * @param options Options for the association - */ - public static belongsTo( - this: ModelStatic, target: ModelStatic, options?: BelongsToOptions - ): BelongsTo; - - /** - * Create an association that is either 1:m or n:m. - * - * ```js - * // Create a 1:m association between user and project - * User.hasMany(Project) - * ``` - * ```js - * // Create a n:m association between user and project - * User.hasMany(Project) - * Project.hasMany(User) - * ``` - * By default, the name of the join table will be source+target, so in this case projectsusers. This can be - * overridden by providing either a string or a Model as `through` in the options. If you use a through - * model with custom attributes, these attributes can be set when adding / setting new associations in two - * ways. Consider users and projects from before with a join table that stores whether the project has been - * started yet: - * ```js - * class UserProjects extends Model {} - * UserProjects.init({ - * started: Sequelize.BOOLEAN - * }, { sequelize }) - * User.hasMany(Project, { through: UserProjects }) - * Project.hasMany(User, { through: UserProjects }) - * ``` - * ```js - * jan.addProject(homework, { started: false }) // The homework project is not started yet - * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner have been - * started - * ``` - * - * 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: - * - * ```js - * p1.userprojects { - * started: true - * } - * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that. - * ``` - * - * Similarily, when fetching through a join table with custom attributes, these attributes will be - * available as an object with the name of the through model. - * ```js - * user.getProjects().then(projects => { - * const p1 = projects[0] - * p1.userprojects.started // Is this project started yet? - * }) - * ``` - * - * @param target The model that will be associated with hasOne relationship - * @param options Options for the association - */ - public static hasMany( - this: ModelStatic, target: ModelStatic, options?: HasManyOptions - ): HasMany; - - /** - * Create an N:M association with a join table - * - * ```js - * User.belongsToMany(Project) - * Project.belongsToMany(User) - * ``` - * By default, the name of the join table will be source+target, so in this case projectsusers. This can be - * overridden by providing either a string or a Model as `through` in the options. - * - * If you use a through model with custom attributes, these attributes can be set when adding / setting new - * associations in two ways. Consider users and projects from before with a join table that stores whether - * the project has been started yet: - * ```js - * class UserProjects extends Model {} - * UserProjects.init({ - * started: Sequelize.BOOLEAN - * }, { sequelize }); - * User.belongsToMany(Project, { through: UserProjects }) - * Project.belongsToMany(User, { through: UserProjects }) - * ``` - * ```js - * jan.addProject(homework, { started: false }) // The homework project is not started yet - * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner has been started - * ``` - * - * 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: - * - * ```js - * p1.userprojects { - * started: true - * } - * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that. - * ``` - * - * Similarily, when fetching through a join table with custom attributes, these attributes will be - * available as an object with the name of the through model. - * ```js - * user.getProjects().then(projects => { - * const p1 = projects[0] - * p1.userprojects.started // Is this project started yet? - * }) - * ``` - * - * @param target The model that will be associated with hasOne relationship - * @param options Options for the association - * - */ - public static belongsToMany( - this: ModelStatic, target: ModelStatic, options: BelongsToManyOptions - ): BelongsToMany; - - /** - * Returns true if this instance has not yet been persisted to the database - */ - public isNewRecord: boolean; - - /** - * A reference to the sequelize instance - */ - public sequelize: Sequelize; - - /** - * Builds a new model instance. - * @param values an object of key value pairs - */ - constructor(values?: TCreationAttributes, options?: BuildOptions); - - /** - * Get an object representing the query for this instance, use with `options.where` - */ - public where(): object; - - /** - * Get the value of the underlying data value - */ - public getDataValue(key: K): TModelAttributes[K]; - - /** - * Update the underlying data value - */ - public setDataValue(key: K, value: TModelAttributes[K]): void; - - /** - * If no key is given, returns all values of the instance, also invoking virtual getters. - * - * 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 options.plain If set to true, included instances will be returned as plain objects - */ - 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; - - /** - * Set is used to update values on the instance (the sequelize representation of the instance that is, - * remember that nothing will be persisted before you actually call `save`). In its most basic form `set` - * will update a value stored in the underlying `dataValues` object. However, if a custom setter function - * is defined for the key, that function will be called instead. To bypass the setter, you can pass `raw: - * true` in the options object. - * - * If set is called with an object, it will loop over the object, and call set recursively for each key, - * value pair. If you set raw to true, the underlying dataValues will either be set directly to the object - * passed, or used to extend dataValues, if dataValues already contain values. - * - * When set is called, the previous value of the field is stored and sets a changed flag(see `changed`). - * - * Set can also be used to build instances for associations, if you have values for those. - * When using set with associations you need to make sure the property key matches the alias of the - * association while also making sure that the proper include options have been set (from .build() or - * .findOne()) - * - * If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the - * entire object as changed. - * - * @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: 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 - * `dataValues` is different from the value in `_previousDataValues`. - * - * If changed is called without an argument, it will return an array of keys that have changed. - * - * If changed is called with two arguments, it will set the property to `dirty`. - * - * If changed is called without an argument and no keys have changed, it will return `false`. - */ - public changed(key: K): boolean; - public changed(key: K, dirty: boolean): void; - public changed(): false | string[]; - - /** - * Returns the previous value for key from `_previousDataValues`. - */ - public previous(): Partial; - public previous(key: K): TCreationAttributes[K] | undefined; - - /** - * Validates this instance, and if the validation passes, persists it to the database. - * - * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). - * - * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. - * - * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. - */ - public save(options?: SaveOptions): Promise; - - /** - * Refresh the current instance in-place, i.e. update the object with current data from the DB and return - * the same object. This is different from doing a `find(Instance.id)`, because that would create and - * 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; - - /** - * Validate the attribute of this instance according to validation rules set in the model definition. - * - * Emits null if and only if validation successful; otherwise an Error instance containing - * { field name : [error msgs] } entries. - * - * @param options.skip An array of strings. All properties that are in this array will not be validated - */ - public validate(options?: ValidationOptions): Promise; - - /** - * This is the same as calling `set` and then calling `save`. - */ - 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 - * either be completely deleted, or have its deletedAt timestamp set to the current time. - */ - public destroy(options?: InstanceDestroyOptions): Promise; - - /** - * Restore the row corresponding to this instance. Only available for paranoid models. - */ - public restore(options?: InstanceRestoreOptions): Promise; - - /** - * Increment the value of one or more columns. This is done in the database, which means it does not use - * the values currently stored on the Instance. The increment is done using a - * ```sql - * SET column = column + X - * ``` - * query. To get the correct value after an increment into the Instance you should do a reload. - * - * ```js - * instance.increment('number') // increment number by 1 - * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2 - * instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1. - * // `by` is ignored, since each column has its own - * // value - * ``` - * - * @param 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. - */ - public increment( - fields: K | readonly K[] | Partial, - options?: IncrementDecrementOptionsWithBy - ): Promise; - - /** - * Decrement the value of one or more columns. This is done in the database, which means it does not use - * the values currently stored on the Instance. The decrement is done using a - * ```sql - * SET column = column - X - * ``` - * query. To get the correct value after an decrement into the Instance you should do a reload. - * - * ```js - * instance.decrement('number') // decrement number by 1 - * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2 - * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1. - * // `by` is ignored, since each column has its own - * // value - * ``` - * - * @param 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 - */ - public decrement( - fields: K | readonly K[] | Partial, - options?: IncrementDecrementOptionsWithBy - ): Promise; - - /** - * Check whether all values of this and `other` Instance are the same - */ - public equals(other: this): boolean; - - /** - * Check if this is equal to one of `others` by calling equals - */ - 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; - - /** - * Helper method to determine if a instance is "soft deleted". This is - * particularly useful if the implementer renamed the deletedAt attribute to - * something different. This method requires paranoid to be enabled. - * - * Throws an error if paranoid is not enabled. - */ - public isSoftDeleted(): boolean; -} - -export type ModelType = 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 ModelStatic = { new(): M }; - -export default Model; diff --git a/types/lib/operators.d.ts b/types/lib/operators.d.ts deleted file mode 100644 index 85bc0ddb6678..000000000000 --- a/types/lib/operators.d.ts +++ /dev/null @@ -1,472 +0,0 @@ -/** - * object that holds all operator symbols - */ -declare const Op: { - /** - * Operator -|- (PG range is adjacent to operator) - * - * ```js - * [Op.adjacent]: [1, 2] - * ``` - * In SQL - * ```sql - * -|- [1, 2) - * ``` - */ - readonly adjacent: unique symbol; - /** - * Operator ALL - * - * ```js - * [Op.gt]: { - * [Op.all]: literal('SELECT 1') - * } - * ``` - * In SQL - * ```sql - * > ALL (SELECT 1) - * ``` - */ - readonly all: unique symbol; - /** - * Operator AND - * - * ```js - * [Op.and]: {a: 5} - * ``` - * In SQL - * ```sql - * AND (a = 5) - * ``` - */ - readonly and: unique symbol; - /** - * Operator ANY ARRAY (PG only) - * - * ```js - * [Op.any]: [2,3] - * ``` - * In SQL - * ```sql - * ANY ARRAY[2, 3]::INTEGER - * ``` - * - * Operator LIKE ANY ARRAY (also works for iLike and notLike) - * - * ```js - * [Op.like]: { [Op.any]: ['cat', 'hat']} - * ``` - * In SQL - * ```sql - * LIKE ANY ARRAY['cat', 'hat'] - * ``` - */ - readonly any: unique symbol; - /** - * Operator BETWEEN - * - * ```js - * [Op.between]: [6, 10] - * ``` - * In SQL - * ```sql - * BETWEEN 6 AND 10 - * ``` - */ - readonly between: unique symbol; - /** - * With dialect specific column identifiers (PG in this example) - * - * ```js - * [Op.col]: 'user.organization_id' - * ``` - * In SQL - * ```sql - * = "user"."organization_id" - * ``` - */ - readonly col: unique symbol; - /** - * Operator <@ (PG array contained by operator) - * - * ```js - * [Op.contained]: [1, 2] - * ``` - * In SQL - * ```sql - * <@ [1, 2) - * ``` - */ - readonly contained: unique symbol; - /** - * Operator @> (PG array contains operator) - * - * ```js - * [Op.contains]: [1, 2] - * ``` - * In SQL - * ```sql - * @> [1, 2) - * ``` - */ - readonly contains: unique symbol; - /** - * Operator LIKE - * - * ```js - * [Op.endsWith]: 'hat' - * ``` - * In SQL - * ```sql - * LIKE '%hat' - * ``` - */ - readonly endsWith: unique symbol; - /** - * Operator = - * - * ```js - * [Op.eq]: 3 - * ``` - * In SQL - * ```sql - * = 3 - * ``` - */ - readonly eq: unique symbol; - /** - * Operator > - * - * ```js - * [Op.gt]: 6 - * ``` - * In SQL - * ```sql - * > 6 - * ``` - */ - readonly gt: unique symbol; - /** - * Operator >= - * - * ```js - * [Op.gte]: 6 - * ``` - * In SQL - * ```sql - * >= 6 - * ``` - */ - readonly gte: unique symbol; - - /** - * Operator ILIKE (case insensitive) (PG only) - * - * ```js - * [Op.iLike]: '%hat' - * ``` - * In SQL - * ```sql - * ILIKE '%hat' - * ``` - */ - readonly iLike: unique symbol; - /** - * Operator IN - * - * ```js - * [Op.in]: [1, 2] - * ``` - * In SQL - * ```sql - * IN [1, 2] - * ``` - */ - readonly in: unique symbol; - /** - * Operator ~* (PG only) - * - * ```js - * [Op.iRegexp]: '^[h|a|t]' - * ``` - * In SQL - * ```sql - * ~* '^[h|a|t]' - * ``` - */ - readonly iRegexp: unique symbol; - /** - * Operator IS - * - * ```js - * [Op.is]: null - * ``` - * In SQL - * ```sql - * IS null - * ``` - */ - readonly is: unique symbol; - /** - * Operator LIKE - * - * ```js - * [Op.like]: '%hat' - * ``` - * In SQL - * ```sql - * LIKE '%hat' - * ``` - */ - readonly like: unique symbol; - /** - * Operator < - * - * ```js - * [Op.lt]: 10 - * ``` - * In SQL - * ```sql - * < 10 - * ``` - */ - readonly lt: unique symbol; - /** - * Operator <= - * - * ```js - * [Op.lte]: 10 - * ``` - * In SQL - * ```sql - * <= 10 - * ``` - */ - readonly lte: unique symbol; - /** - * Operator != - * - * ```js - * [Op.ne]: 20 - * ``` - * In SQL - * ```sql - * != 20 - * ``` - */ - readonly ne: unique symbol; - /** - * Operator &> (PG range does not extend to the left of operator) - * - * ```js - * [Op.noExtendLeft]: [1, 2] - * ``` - * In SQL - * ```sql - * &> [1, 2) - * ``` - */ - readonly noExtendLeft: unique symbol; - /** - * Operator &< (PG range does not extend to the right of operator) - * - * ```js - * [Op.noExtendRight]: [1, 2] - * ``` - * In SQL - * ```sql - * &< [1, 2) - * ``` - */ - readonly noExtendRight: unique symbol; - /** - * Operator NOT - * - * ```js - * [Op.not]: true - * ``` - * In SQL - * ```sql - * IS NOT TRUE - * ``` - */ - readonly not: unique symbol; - /** - * Operator NOT BETWEEN - * - * ```js - * [Op.notBetween]: [11, 15] - * ``` - * In SQL - * ```sql - * NOT BETWEEN 11 AND 15 - * ``` - */ - readonly notBetween: unique symbol; - /** - * Operator NOT ILIKE (case insensitive) (PG only) - * - * ```js - * [Op.notILike]: '%hat' - * ``` - * In SQL - * ```sql - * NOT ILIKE '%hat' - * ``` - */ - readonly notILike: unique symbol; - /** - * Operator NOT IN - * - * ```js - * [Op.notIn]: [1, 2] - * ``` - * In SQL - * ```sql - * NOT IN [1, 2] - * ``` - */ - readonly notIn: unique symbol; - /** - * Operator !~* (PG only) - * - * ```js - * [Op.notIRegexp]: '^[h|a|t]' - * ``` - * In SQL - * ```sql - * !~* '^[h|a|t]' - * ``` - */ - readonly notIRegexp: unique symbol; - /** - * Operator NOT LIKE - * - * ```js - * [Op.notLike]: '%hat' - * ``` - * In SQL - * ```sql - * NOT LIKE '%hat' - * ``` - */ - readonly notLike: unique symbol; - /** - * Operator NOT REGEXP (MySQL/PG only) - * - * ```js - * [Op.notRegexp]: '^[h|a|t]' - * ``` - * In SQL - * ```sql - * NOT REGEXP/!~ '^[h|a|t]' - * ``` - */ - readonly notRegexp: unique symbol; - /** - * Operator OR - * - * ```js - * [Op.or]: [{a: 5}, {a: 6}] - * ``` - * In SQL - * ```sql - * (a = 5 OR a = 6) - * ``` - */ - readonly or: unique symbol; - /** - * Operator && (PG array overlap operator) - * - * ```js - * [Op.overlap]: [1, 2] - * ``` - * In SQL - * ```sql - * && [1, 2) - * ``` - */ - readonly overlap: unique symbol; - /** - * Internal placeholder - * - * ```js - * [Op.placeholder]: true - * ``` - */ - readonly placeholder: unique symbol; - /** - * Operator REGEXP (MySQL/PG only) - * - * ```js - * [Op.regexp]: '^[h|a|t]' - * ``` - * In SQL - * ```sql - * REGEXP/~ '^[h|a|t]' - * ``` - */ - readonly regexp: unique symbol; - /** - * Operator LIKE - * - * ```js - * [Op.startsWith]: 'hat' - * ``` - * In SQL - * ```sql - * LIKE 'hat%' - * ``` - */ - readonly startsWith: unique symbol; - /** - * Operator << (PG range strictly left of operator) - * - * ```js - * [Op.strictLeft]: [1, 2] - * ``` - * In SQL - * ```sql - * << [1, 2) - * ``` - */ - readonly strictLeft: unique symbol; - /** - * Operator >> (PG range strictly right of operator) - * - * ```js - * [Op.strictRight]: [1, 2] - * ``` - * In SQL - * ```sql - * >> [1, 2) - * ``` - */ - readonly strictRight: unique symbol; - /** - * Operator LIKE - * - * ```js - * [Op.substring]: 'hat' - * ``` - * In SQL - * ```sql - * LIKE '%hat%' - * ``` - */ - readonly substring: unique symbol; - /** - * Operator VALUES - * - * ```js - * [Op.values]: [4, 5, 6] - * ``` - * In SQL - * ```sql - * VALUES (4), (5), (6) - * ``` - */ - readonly values: unique symbol; -}; - -export = Op; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts deleted file mode 100644 index ec4529af6d42..000000000000 --- a/types/lib/query-interface.d.ts +++ /dev/null @@ -1,693 +0,0 @@ -import { DataType } from './data-types'; -import { - Logging, - Model, - ModelAttributeColumnOptions, - ModelAttributes, - Transactionable, - WhereOptions, - Filterable, - Poolable, - ModelCtor, ModelStatic, ModelType -} from './model'; -import QueryTypes = require('./query-types'); -import { Sequelize, RetryOptions } from './sequelize'; -import { Transaction } from './transaction'; -import { SetRequired } from './../type-helpers/set-required'; -import { Fn, Literal } from './utils'; -import { Deferrable } from './deferrable'; - -type BindOrReplacements = { [key: string]: unknown } | unknown[]; -type FieldMap = { [key: string]: string }; - -/** - * Interface for query options - */ -export interface QueryOptions extends Logging, Transactionable, Poolable { - /** - * If true, sequelize will not try to format the results of the query, or build an instance of a model from - * the result - */ - raw?: boolean; - - /** - * 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. - */ - type?: string; - - /** - * 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 - * - * @default false - */ - nest?: boolean; - - /** - * Sets the query type to `SELECT` and return a single row - */ - plain?: boolean; - - /** - * Either an object of named parameter replacements in the format `:param` or an array of unnamed - * replacements to replace `?` in your SQL. - */ - replacements?: BindOrReplacements; - - /** - * Either an object of named parameter bindings in the format `$param` or an array of unnamed - * values to bind to `$1`, `$2`, etc in your SQL. - */ - bind?: BindOrReplacements; - - /** - * A sequelize instance used to build the return instance - */ - instance?: Model; - - /** - * Map returned fields to model's fields if `options.model` or `options.instance` is present. - * Mapping will occur before building the model instance. - */ - mapToModel?: boolean; - - retry?: RetryOptions; - - /** - * Map returned fields to arbitrary names for SELECT query type if `options.fieldMaps` is present. - */ - fieldMap?: FieldMap; -} - -export interface QueryOptionsWithWhere extends QueryOptions, Filterable { - -} - -export interface QueryOptionsWithModel extends QueryOptions { - /** - * A sequelize model used to build the returned model instances (used to be called callee) - */ - model: ModelStatic; -} - -export interface QueryOptionsWithType extends QueryOptions { - /** - * 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. - */ - type: T; -} - -export interface QueryOptionsWithForce extends QueryOptions { - force?: boolean; -} - -/** -* Most of the methods accept options and use only the logger property of the options. That's why the most used -* interface type for options in a method is separated here as another interface. -*/ -export interface QueryInterfaceOptions extends Logging, Transactionable {} - -export interface CollateCharsetOptions { - collate?: string; - charset?: string; -} - -export interface QueryInterfaceCreateTableOptions extends QueryInterfaceOptions, CollateCharsetOptions { - engine?: string; - /** - * Used for compound unique keys. - */ - uniqueKeys?: { - [keyName: string]: { - fields: string[]; - customIndex?: boolean; - }; - }; -} - -export interface QueryInterfaceDropTableOptions extends QueryInterfaceOptions { - cascade?: boolean; - force?: boolean; -} - -export interface QueryInterfaceDropAllTablesOptions extends QueryInterfaceOptions { - skip?: string[]; -} - -export interface TableNameWithSchema { - tableName: string; - schema?: string; - delimiter?: string; - as?: string; - name?: string; -} -export type TableName = string | TableNameWithSchema; - -export type IndexType = 'UNIQUE' | 'FULLTEXT' | 'SPATIAL'; -export type IndexMethod = 'BTREE' | 'HASH' | 'GIST' | 'SPGIST' | 'GIN' | 'BRIN' | string; - -export interface IndexesOptions { - /** - * The name of the index. Defaults to model name + _ + fields concatenated - */ - name?: string; - - /** For FULLTEXT columns set your parser */ - parser?: string | null; - - /** - * Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL` - */ - type?: IndexType; - - /** - * Should the index by unique? Can also be triggered by setting type to `UNIQUE` - * - * @default false - */ - unique?: boolean; - - /** - * PostgreSQL will build the index without taking any write locks. Postgres only - * - * @default false - */ - concurrently?: boolean; - - /** - * 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), `operator` (likes IndexesOptions['operator']) - */ - 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 - * postgres, and postgres additionally supports GIST, SPGIST, BRIN and GIN. - */ - using?: IndexMethod; - - /** - * Index operator type. Postgres only - */ - operator?: string; - - /** - * Optional where parameter for index. Can be used to limit the index to certain rows. - */ - where?: WhereOptions; - - /** - * Prefix to append to the index name. - */ - prefix?: string; -} - -export interface QueryInterfaceIndexOptions extends IndexesOptions, QueryInterfaceOptions {} - -export interface BaseConstraintOptions { - name?: string; - fields: string[]; -} - -export interface AddUniqueConstraintOptions extends BaseConstraintOptions { - type: 'unique'; - deferrable?: Deferrable; -} - -export interface AddDefaultConstraintOptions extends BaseConstraintOptions { - type: 'default'; - defaultValue?: unknown; -} - -export interface AddCheckConstraintOptions extends BaseConstraintOptions { - type: 'check'; - where?: WhereOptions; -} - -export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { - type: 'primary key'; - deferrable?: Deferrable; -} - -export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { - type: 'foreign key'; - references?: { - table: TableName; - field: string; - }; - onDelete: string; - onUpdate: string; - deferrable?: Deferrable; -} - -export type AddConstraintOptions = -| AddUniqueConstraintOptions -| AddDefaultConstraintOptions -| AddCheckConstraintOptions -| AddPrimaryKeyConstraintOptions -| AddForeignKeyConstraintOptions; - -export interface CreateDatabaseOptions extends CollateCharsetOptions, QueryOptions { - encoding?: string; -} - -export interface FunctionParam { - type: string; - name?: string; - 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 -* referenced anyway, so it can be used. -*/ -export class QueryInterface { - /** - * Returns the dialect-specific sql generator. - * - * We don't have a definition for the QueryGenerator, because I doubt it is commonly in use separately. - */ - public queryGenerator: unknown; - - /** - * Returns the current sequelize instance. - */ - public sequelize: Sequelize; - - constructor(sequelize: Sequelize); - - /** - * Queries the schema (table list). - * - * @param schema The schema to query. Applies only to Postgres. - */ - public createSchema(schema?: string, options?: QueryInterfaceOptions): Promise; - - /** - * Drops the specified schema (table). - * - * @param schema The schema to query. Applies only to Postgres. - */ - public dropSchema(schema?: string, options?: QueryInterfaceOptions): Promise; - - /** - * Drops all tables. - */ - public dropAllSchemas(options?: QueryInterfaceDropAllTablesOptions): Promise; - - /** - * Queries all table names in the database. - * - * @param options - */ - public showAllSchemas(options?: QueryOptions): Promise; - - /** - * Return database version - */ - public databaseVersion(options?: QueryInterfaceOptions): Promise; - - /** - * Creates a table with specified attributes. - * - * @param tableName Name of table to create - * @param attributes Hash of attributes, key is attribute name, value is data type - * @param options Table options. - */ - public createTable( - tableName: TableName, - attributes: ModelAttributes, - options?: QueryInterfaceCreateTableOptions - ): Promise; - - /** - * Drops the specified table. - * - * @param tableName Table name. - * @param options Query options, particularly "force". - */ - public dropTable(tableName: TableName, options?: QueryInterfaceDropTableOptions): Promise; - - /** - * Drops all tables. - * - * @param options - */ - public dropAllTables(options?: QueryInterfaceDropAllTablesOptions): Promise; - - /** - * Drops all defined enums - * - * @param options - */ - public dropAllEnums(options?: QueryOptions): Promise; - - /** - * Renames a table - */ - public renameTable(before: TableName, after: TableName, options?: QueryInterfaceOptions): Promise; - - /** - * Returns all tables - */ - public showAllTables(options?: QueryOptions): Promise; - - /** - * Describe a table - */ - public describeTable( - tableName: TableName, - options?: string | { schema?: string; schemaDelimiter?: string } & Logging - ): Promise; - - /** - * Adds a new column to a table - */ - public addColumn( - table: TableName, - key: string, - attribute: ModelAttributeColumnOptions | DataType, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Removes a column from a table - */ - public removeColumn( - table: TableName, - attribute: string, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Changes a column - */ - public changeColumn( - tableName: TableName, - attributeName: string, - dataTypeOrOptions?: DataType | ModelAttributeColumnOptions, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Renames a column - */ - public renameColumn( - tableName: TableName, - attrNameBefore: string, - attrNameAfter: string, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Adds a new index to a table - */ - public addIndex( - tableName: TableName, - attributes: string[], - options?: QueryInterfaceIndexOptions, - rawTablename?: string - ): Promise; - public addIndex( - tableName: TableName, - options: SetRequired, - rawTablename?: string - ): Promise; - - /** - * Removes an index of a table - */ - 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: TableName, - options?: AddConstraintOptions & QueryInterfaceOptions - ): Promise; - - /** - * Removes constraints from a table - */ - public removeConstraint(tableName: TableName, constraintName: string, options?: QueryInterfaceOptions): Promise; - - /** - * Shows the index of a table - */ - public showIndex(tableName: string | object, options?: QueryOptions): Promise; - - /** - * Put a name to an index - */ - public nameIndexes(indexes: string[], rawTablename: string): Promise; - - /** - * Returns all foreign key constraints of requested tables - */ - public getForeignKeysForTables(tableNames: string[], options?: QueryInterfaceOptions): Promise; - - /** - * Get foreign key references details for the table - */ - public getForeignKeyReferencesForTable(tableName: TableName, options?: QueryInterfaceOptions): Promise; - - /** - * Inserts a new record - */ - public insert(instance: Model | null, tableName: string, values: object, options?: QueryOptions): Promise; - - /** - * Inserts or Updates a record in the database - */ - public upsert( - tableName: TableName, - insertValues: object, - updateValues: object, - where: object, - model: ModelType, - options?: QueryOptions - ): Promise; - - /** - * Inserts multiple records at once - */ - public bulkInsert( - tableName: TableName, - records: object[], - options?: QueryOptions, - attributes?: string[] | string - ): Promise; - - /** - * Updates a row - */ - public update( - instance: M, - tableName: TableName, - values: object, - identifier: WhereOptions, - options?: QueryOptions - ): Promise; - - /** - * Updates multiple rows at once - */ - public bulkUpdate( - tableName: TableName, - values: object, - identifier: WhereOptions, - options?: QueryOptions, - attributes?: string[] | string - ): Promise; - - /** - * Deletes a row - */ - public delete( - instance: Model | null, - tableName: TableName, - identifier: WhereOptions, - options?: QueryOptions - ): Promise; - - /** - * Deletes multiple rows at once - */ - public bulkDelete( - tableName: TableName, - identifier: WhereOptions, - options?: QueryOptions, - model?: ModelType - ): Promise; - - /** - * Returns selected rows - */ - public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise; - - /** - * Increments a row value - */ - public increment( - instance: Model, - tableName: TableName, - values: object, - identifier: WhereOptions, - options?: QueryOptions - ): Promise; - - /** - * Selects raw without parsing the string into an object - */ - public rawSelect( - tableName: TableName, - options: QueryOptionsWithWhere, - attributeSelector: string | string[], - model?: ModelType - ): Promise; - - /** - * Postgres only. Creates a trigger on specified table to call the specified function with supplied - * parameters. - */ - public createTrigger( - tableName: TableName, - triggerName: string, - timingType: string, - fireOnArray: { - [key: string]: unknown; - }[], - functionName: string, - functionParams: FunctionParam[], - optionsArray: string[], - options?: QueryInterfaceOptions - ): Promise; - - /** - * Postgres only. Drops the specified trigger. - */ - public dropTrigger(tableName: TableName, triggerName: string, options?: QueryInterfaceOptions): Promise; - - /** - * Postgres only. Renames a trigger - */ - public renameTrigger( - tableName: TableName, - oldTriggerName: string, - newTriggerName: string, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Postgres only. Create a function - */ - public createFunction( - functionName: string, - params: FunctionParam[], - returnType: string, - language: string, - body: string, - optionsArray?: string[], - options?: QueryOptionsWithForce - ): Promise; - - /** - * Postgres only. Drops a function - */ - public dropFunction(functionName: string, params: FunctionParam[], options?: QueryInterfaceOptions): Promise; - - /** - * Postgres only. Rename a function - */ - public renameFunction( - oldFunctionName: string, - params: FunctionParam[], - newFunctionName: string, - options?: QueryInterfaceOptions - ): Promise; - - /** - * Escape an identifier (e.g. a table or attribute name). If force is true, the identifier will be quoted - * even if the `quoteIdentifiers` option is false. - */ - public quoteIdentifier(identifier: string, force: boolean): string; - - /** - * Escape a table name - */ - public quoteTable(identifier: TableName): string; - - /** - * Split an identifier into .-separated tokens and quote each part. If force is true, the identifier will be - * quoted even if the `quoteIdentifiers` option is false. - */ - public quoteIdentifiers(identifiers: string, force: boolean): string; - - /** - * Escape a value (e.g. a string, number or date) - */ - public escape(value?: string | number | Date): string; - - /** - * Set option for autocommit of a transaction - */ - public setAutocommit(transaction: Transaction, value: boolean, options?: QueryOptions): Promise; - - /** - * Set the isolation level of a transaction - */ - public setIsolationLevel(transaction: Transaction, value: string, options?: QueryOptions): Promise; - - /** - * Begin a new transaction - */ - public startTransaction(transaction: Transaction, options?: QueryOptions): Promise; - - /** - * Defer constraints - */ - public deferConstraints(transaction: Transaction, options?: QueryOptions): Promise; - - /** - * Commit an already started transaction - */ - public commitTransaction(transaction: Transaction, options?: QueryOptions): Promise; - - /** - * Rollback ( revert ) a transaction that has'nt been commited - */ - public rollbackTransaction(transaction: Transaction, options?: QueryOptions): Promise; - - /** - * Creates a database - */ - public createDatabase(name: string, options?: CreateDatabaseOptions): Promise; - - /** - * Creates a database - */ - public dropDatabase(name: string, options?: QueryOptions): Promise; -} diff --git a/types/lib/query-types.d.ts b/types/lib/query-types.d.ts deleted file mode 100644 index 5e5d18a2baa3..000000000000 --- a/types/lib/query-types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -declare enum QueryTypes { - SELECT = 'SELECT', - INSERT = 'INSERT', - UPDATE = 'UPDATE', - BULKUPDATE = 'BULKUPDATE', - BULKDELETE = 'BULKDELETE', - DELETE = 'DELETE', - UPSERT = 'UPSERT', - VERSION = 'VERSION', - SHOWTABLES = 'SHOWTABLES', - SHOWINDEXES = 'SHOWINDEXES', - DESCRIBE = 'DESCRIBE', - RAW = 'RAW', - FOREIGNKEYS = 'FOREIGNKEYS', -} - -export = QueryTypes; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts deleted file mode 100644 index b88d5ec4e5d7..000000000000 --- a/types/lib/sequelize.d.ts +++ /dev/null @@ -1,1478 +0,0 @@ -import * as DataTypes from './data-types'; -import { HookReturn, Hooks, SequelizeHooks } from './hooks'; -import { ValidationOptions } from './instance-validator'; -import { - AndOperator, - BulkCreateOptions, - CreateOptions, - DestroyOptions, - DropOptions, - FindOptions, - InstanceDestroyOptions, - Logging, - Model, - ModelAttributeColumnOptions, - ModelAttributes, - ModelOptions, - OrOperator, - UpdateOptions, - WhereAttributeHash, - WhereOperators, - ModelCtor, - Hookable, - ModelType, -} from './model'; -import { ModelManager } from './model-manager'; -import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; -import QueryTypes = require('./query-types'); -import { Transaction, TransactionOptions } from './transaction'; -import { Cast, Col, Fn, Json, Literal, Where } from './utils'; -import { ConnectionManager } from './connection-manager'; - -/** - * Additional options for table altering during sync - */ -export interface SyncAlterOptions { - /** - * Prevents any drop statements while altering a table when set to `false` - */ - drop?: boolean; -} - -/** - * Sync Options - */ -export interface SyncOptions extends Logging, Hookable { - /** - * If force is true, each DAO will do DROP TABLE IF EXISTS ..., before it tries to create its own table - */ - force?: boolean; - - /** - * If alter is true, each DAO will do ALTER TABLE ... CHANGE ... - * Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. - */ - alter?: boolean | SyncAlterOptions; - - /** - * 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 - */ - match?: RegExp; - - /** - * The schema that the tables should be created in. This can be overridden for each table in sequelize.define - */ - schema?: string; - - /** - * An optional parameter to specify the schema search_path (Postgres only) - */ - searchPath?: string; - -} - -export interface DefaultSetOptions { } - -/** - * Connection Pool options - */ -export interface PoolOptions { - /** - * Maximum number of connections in pool. Default is 5 - */ - max?: number; - - /** - * Minimum number of connections in pool. Default is 0 - */ - min?: number; - - /** - * The maximum time, in milliseconds, that a connection can be idle before being released - */ - idle?: number; - - /** - * The maximum time, in milliseconds, that pool will try to get connection before throwing error - */ - acquire?: number; - - /** - * The time interval, in milliseconds, after which sequelize-pool will remove idle connections. - */ - 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 - */ - validate?(client?: unknown): boolean; -} - -export interface ConnectionOptions { - host?: string; - port?: string | number; - username?: string; - password?: string; - database?: string; -} - -/** - * Interface for replication Options in the sequelize constructor - */ -export interface ReplicationOptions { - read: ConnectionOptions[]; - - write: ConnectionOptions; -} - -/** - * Used to map operators to their Symbol representations - */ -export interface OperatorsAliases { - [K: string]: symbol; -} - -/** - * Final config options generated by sequelize. - */ -export interface Config { - readonly database: string; - readonly dialectModule?: object; - readonly host?: string; - readonly port?: string; - readonly username: string; - readonly password: string | null; - readonly pool?: { - readonly acquire: number; - readonly idle: number; - readonly max: number; - readonly min: number; - }; - readonly protocol: 'tcp'; - readonly native: boolean; - readonly ssl: boolean; - readonly replication: boolean; - readonly dialectModulePath: null | string; - readonly keepDefaultTimezone?: boolean; - readonly dialectOptions?: { - readonly charset?: string; - readonly timeout?: number; - }; -} - -export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; - -export interface RetryOptions { - match?: (RegExp | string | Function)[]; - max?: number; -} - -/** - * Options for the constructor of Sequelize main class - */ -export interface Options extends Logging { - /** - * The dialect of the database you are connecting to. One of mysql, postgres, sqlite, mariadb and mssql. - * - * @default 'mysql' - */ - dialect?: Dialect; - - /** - * If specified, will use the provided module as the dialect. - * - * @example - * `dialectModule: require('@myorg/tedious'),` - */ - dialectModule?: object; - - - /** - * 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 'pg.js' here - */ - dialectModulePath?: string; - - /** - * An object of additional options, which are passed directly to the connection library - */ - dialectOptions?: object; - - /** - * Only used by sqlite. - * - * @default ':memory:' - */ - storage?: string; - - /** - * The name of the database - */ - database?: string; - - /** - * The username which is used to authenticate against the database. - */ - username?: string; - - /** - * The password which is used to authenticate against the database. - */ - password?: string; - - /** - * The host of the relational database. - * - * @default 'localhost' - */ - host?: string; - - /** - * The port of the relational database. - */ - port?: number; - - /** - * A flag that defines if is used SSL. - */ - ssl?: boolean; - - /** - * The protocol of the relational database. - * - * @default 'tcp' - */ - protocol?: string; - - /** - * Default options for model definitions. See Model.init. - */ - define?: ModelOptions; - - /** - * Default options for sequelize.query - */ - query?: QueryOptions; - - /** - * Default options for sequelize.set - */ - set?: DefaultSetOptions; - - /** - * Default options for sequelize.sync - */ - sync?: SyncOptions; - - /** - * 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. - * - * @default '+00:00' - */ - timezone?: string; - - /** - * A flag that defines if null values should be passed to SQL queries or not. - * - * @default false - */ - omitNull?: boolean; - - /** - * A flag that defines if native library shall be used or not. Currently only has an effect for postgres - * - * @default false - */ - native?: boolean; - - /** - * 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` - * - * @default false - */ - replication?: ReplicationOptions; - - /** - * Connection pool options - */ - pool?: PoolOptions; - - /** - * Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of - * them. - * - * @default true - */ - quoteIdentifiers?: boolean; - - /** - * Set the default transaction isolation level. See `Sequelize.Transaction.ISOLATION_LEVELS` for possible - * options. - * - * @default 'REPEATABLE_READ' - */ - 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. - * - * @default false - */ - typeValidation?: boolean; - - /** - * Sets available operator aliases. - * See (https://sequelize.org/master/manual/querying.html#operators) for more information. - * WARNING: Setting this to boolean value was deprecated and is no-op. - * - * @default all aliases - */ - operatorsAliases?: OperatorsAliases; - - - /** - * The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. - * WARNING: Setting this to false may expose vulnerabilities and is not recommended! - * - * @default true - */ - 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>; - - /** - * Set to `true` to automatically minify aliases generated by sequelize. - * Mostly useful to circumvent the POSTGRES alias limit of 64 characters. - * - * @default false - */ - minifyAliases?: boolean; - - /** - * Set to `true` to show bind parameters in log. - * - * @default false - */ - logQueryParameters?: boolean; - - retry?: RetryOptions; -} - -export interface QueryOptionsTransactionRequired { } - -/** - * This is the main class, the entry point to sequelize. To use it, you just need to - * import sequelize: - * - * ```js - * const Sequelize = require('sequelize'); - * ``` - * - * In addition to sequelize, the connection library for the dialect you want to use - * should also be installed in your project. You don't need to import it however, as - * sequelize will take care of that. - */ -export class Sequelize extends Hooks { - - // -------------------- Utilities ------------------------------------------------------------------------ - - /** - * Creates a object representing a database function. This can be used in search queries, both in where and - * order parts, and as default values in column definitions. If you want to refer to columns in your - * function, you should use `sequelize.col`, so that the columns are properly interpreted as columns and - * not a strings. - * - * Convert a user's username to upper case - * ```js - * instance.update({ - * username: self.sequelize.fn('upper', self.sequelize.col('username')) - * }) - * ``` - * @param fn The function you want to call - * @param args All further arguments will be passed as arguments to the function - */ - public static fn: typeof fn; - - /** - * Creates a object representing a column in the DB. This is often useful in conjunction with - * `sequelize.fn`, since raw string arguments to fn will be escaped. - * - * @param col The name of the column - */ - public static col: typeof col; - - /** - * Creates a object representing a call to the cast function. - * - * @param val The value to cast - * @param type The type to cast it to - */ - public static cast: typeof cast; - - /** - * Creates a object representing a literal, i.e. something that will not be escaped. - * - * @param val - */ - public static literal: typeof literal; - - /** - * An AND query - * - * @param args Each argument will be joined by AND - */ - public static and: typeof and; - - /** - * An OR query - * - * @param args Each argument will be joined by OR - */ - public static or: typeof or; - - /** - * Creates an object representing nested where conditions for postgres's json data-type. - * - * @param conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot - * notation or a string using postgres json syntax. - * @param value An optional value to compare against. Produces a string of the form " = - * ''". - */ - public static json: typeof json; - - /** - * A way of specifying attr = condition. - * - * The attr can either be an object taken from `Model.rawAttributes` (for example `Model.rawAttributes.id` - * or - * `Model.rawAttributes.name`). The attribute should be defined in your model definition. The attribute can - * also be an object from one of the sequelize utility functions (`sequelize.fn`, `sequelize.col` etc.) - * - * For string attributes, use the regular `{ where: { attr: something }}` syntax. If you don't want your - * string to be escaped, use `sequelize.literal`. - * - * @param 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 comparator Comparator - * @param logic The condition. Can be both a simply type, or a further condition (`.or`, `.and`, `.literal` - * etc.) - */ - public static where: typeof where; - - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public static beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public static afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public static beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public static afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeSave( - name: string, - fn: (instance: Model, options: UpdateOptions | CreateOptions) => void - ): void; - public static beforeSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - - /** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterSave( - name: string, - fn: (instance: Model, options: UpdateOptions | CreateOptions) => void - ): void; - public static afterSave( - fn: (instance: Model, options: UpdateOptions | CreateOptions) => void - ): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static beforeBulkCreate( - name: string, - fn: (instances: Model[], options: BulkCreateOptions) => void - ): void; - public static beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static afterBulkCreate( - name: string, fn: (instances: Model[], options: BulkCreateOptions) => void - ): void; - public static afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public static afterBulkDestroy(fn: (options: DestroyOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static afterBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFind(name: string, fn: (options: FindOptions) => void): void; - public static beforeFind(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a connection is established - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeConnect(name: string, fn: (options: Config) => void): void; - public static beforeConnect(fn: (options: Config) => void): void; - - /** - * A hook that is run after a connection is established - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterConnect(name: string, fn: (connection: unknown, options: Config) => void): void; - public static afterConnect(fn: (connection: unknown, options: Config) => void): void; - - /** - * A hook that is run before a connection is released - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeDisconnect(name: string, fn: (connection: unknown) => void): void; - public static beforeDisconnect(fn: (connection: unknown) => void): void; - - /** - * A hook that is run after a connection is released - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterDisconnect(name: string, fn: (connection: unknown) => void): void; - public static afterDisconnect(fn: (connection: unknown) => void): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterOptions(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public static afterFind( - name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - public static afterFind( - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - - /** - * A hook that is run before a define call - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeDefine( - name: string, - fn: (attributes: ModelAttributes, options: ModelOptions) => void - ): void; - public static beforeDefine( - fn: (attributes: ModelAttributes, options: ModelOptions) => void - ): void; - - /** - * A hook that is run after a define call - * - * @param name - * @param fn A callback function that is called with factory - */ - public static afterDefine(name: string, fn: (model: ModelType) => void): void; - public static afterDefine(fn: (model: ModelType) => void): void; - - /** - * A hook that is run before Sequelize() call - * - * @param name - * @param fn A callback function that is called with config, options - */ - public static beforeInit(name: string, fn: (config: Config, options: Options) => void): void; - public static beforeInit(fn: (config: Config, options: Options) => void): void; - - /** - * A hook that is run after Sequelize() call - * - * @param name - * @param fn A callback function that is called with sequelize - */ - public static afterInit(name: string, fn: (sequelize: Sequelize) => void): void; - public static afterInit(fn: (sequelize: Sequelize) => void): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static beforeBulkSync(dname: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * Use CLS with Sequelize. - * CLS namespace provided is stored as `Sequelize._cls` - * and Promise is patched to use the namespace, using `cls-hooked` module. - * - * @param namespace - */ - public static useCLS(namespace: object): typeof Sequelize; - - /** - * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. - */ - public Sequelize: typeof Sequelize; - - /** - * Final config that is used by sequelize. - */ - public readonly config: Config; - - public readonly modelManager: ModelManager; - - public readonly connectionManager: ConnectionManager; - - /** - * Dictionary of all models linked with this instance. - */ - public readonly models: { - [key: string]: ModelCtor; - }; - - /** - * Instantiate sequelize with name of database, username and password - * - * #### Example usage - * - * ```javascript - * // without password and options - * const sequelize = new Sequelize('database', 'username') - * - * // without options - * const sequelize = new Sequelize('database', 'username', 'password') - * - * // without password / with blank password - * const sequelize = new Sequelize('database', 'username', null, {}) - * - * // with password and options - * const sequelize = new Sequelize('my_database', 'john', 'doe', {}) - * - * // with uri (see below) - * const sequelize = new Sequelize('mysql://localhost:3306/database', {}) - * ``` - * - * @param database The name of the database - * @param username The username which is used to authenticate against the - * database. - * @param password The password which is used to authenticate against the - * database. - * @param options An object with options. - */ - constructor(database: string, username: string, password?: string, options?: Options); - constructor(database: string, username: string, options?: Options); - constructor(options?: Options); - - /** - * Instantiate sequelize with an URI - * @param uri A full database URI - * @param options See above for possible options - */ - constructor(uri: string, options?: Options); - - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public afterBulkDestroy(fn: (options: DestroyOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public afterBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFind(name: string, fn: (options: FindOptions) => void): void; - public beforeFind(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterOptions(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public afterFind( - name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - public afterFind(fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void): void; - - /** - * A hook that is run before a define call - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public beforeDefine(name: string, fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; - public beforeDefine(fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; - - /** - * A hook that is run after a define call - * - * @param name - * @param fn A callback function that is called with factory - */ - public afterDefine(name: string, fn: (model: ModelType) => void): void; - public afterDefine(fn: (model: ModelType) => void): void; - - /** - * A hook that is run before Sequelize() call - * - * @param name - * @param fn A callback function that is called with config, options - */ - public beforeInit(name: string, fn: (config: Config, options: Options) => void): void; - public beforeInit(fn: (config: Config, options: Options) => void): void; - - /** - * A hook that is run after Sequelize() call - * - * @param name - * @param fn A callback function that is called with sequelize - */ - public afterInit(name: string, fn: (sequelize: Sequelize) => void): void; - public afterInit(fn: (sequelize: Sequelize) => void): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public afterSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * Returns the specified dialect. - */ - public getDialect(): string; - - /** - * Returns the database name. - */ - - public getDatabaseName(): string; - - /** - * Returns an instance of QueryInterface. - */ - public getQueryInterface(): QueryInterface; - - /** - * Define a new model, representing a table in the DB. - * - * The table columns are defined by the hash that is given as the second argument. Each attribute of the - * hash - * represents a column. A short table definition might look like this: - * - * ```js - * class MyModel extends Model {} - * MyModel.init({ - * columnA: { - * type: Sequelize.BOOLEAN, - * validate: { - * is: ["[a-z]",'i'], // will only allow letters - * max: 23, // only allow values <= 23 - * isIn: { - * args: [['en', 'zh']], - * msg: "Must be English or Chinese" - * } - * }, - * field: 'column_a' - * // Other attributes here - * }, - * columnB: Sequelize.STRING, - * columnC: 'MY VERY OWN COLUMN TYPE' - * }, { sequelize }) - * - * sequelize.models.modelName // The model will now be available in models under the name given to define - * ``` - * - * As shown above, column definitions can be either strings, a reference to one of the datatypes that are - * predefined on the Sequelize constructor, or an object that allows you to specify both the type of the - * column, and other attributes such as default values, foreign key constraints and custom setters and - * getters. - * - * For a list of possible data types, see - * https://sequelize.org/master/en/latest/docs/models-definition/#data-types - * - * For more about getters and setters, see - * https://sequelize.org/master/en/latest/docs/models-definition/#getters-setters - * - * For more about instance and class methods, see - * https://sequelize.org/master/en/latest/docs/models-definition/#expansion-of-models - * - * For more about validation, see - * https://sequelize.org/master/en/latest/docs/models-definition/#validations - * - * @param modelName The name of the model. The model will be stored in `sequelize.models` under this name - * @param 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 options These options are merged with the default define options provided to the Sequelize - * constructor - */ - public define( - modelName: string, - attributes: ModelAttributes, - options?: ModelOptions - ): ModelCtor; - - /** - * Fetch a Model which is already defined - * - * @param modelName The name of a model defined with Sequelize.define - */ - public model(modelName: string): ModelCtor; - - /** - * Checks whether a model with the given name is defined - * - * @param modelName The name of a model defined with Sequelize.define - */ - public isDefined(modelName: string): boolean; - - /** - * 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 `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 - * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring - * - * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring - * ``` - * - * @param sql - * @param options Query options - */ - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise<[undefined, number]>; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise<[number, number]>; - 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; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; - public query( - sql: string | { query: string; values: unknown[] }, - options: QueryOptionsWithModel - ): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType & { plain: true }): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: (QueryOptions | QueryOptionsWithType) & { plain: true }): Promise<{ [key: string]: unknown }>; - public query(sql: string | { query: string; values: unknown[] }, options?: QueryOptions | QueryOptionsWithType): Promise<[unknown[], unknown]>; - - /** - * Get the fn for random based on the dialect - */ - public random(): Fn; - - /** - * 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 variables object with multiple variables. - * @param options Query options. - */ - public set(variables: object, options: QueryOptionsTransactionRequired): Promise; - - /** - * Escape value. - * - * @param value Value that needs to be escaped - */ - public escape(value: string | number | Date): string; - - /** - * Create a new database schema. - * - * Note,that 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 command will do nothing. - * - * @param schema Name of the schema - * @param options Options supplied - */ - public createSchema(schema: string, options: Logging): Promise; - - /** - * Show all defined schemas - * - * Note,that 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 options Options supplied - */ - public showAllSchemas(options: Logging): Promise; - - /** - * Drop a single schema - * - * Note,that 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 drop a table matching the schema name - * - * @param schema Name of the schema - * @param options Options supplied - */ - public dropSchema(schema: string, options: Logging): Promise; - - /** - * Drop all schemas - * - * Note,that 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 options Options supplied - */ - public dropAllSchemas(options: Logging): Promise; - - /** - * Sync all defined models to the DB. - * - * @param options Sync Options - */ - public sync(options?: SyncOptions): Promise; - - /** - * Truncate all tables defined through the sequelize models. This is done - * by calling Model.truncate() on each model. - * - * @param [options] The options passed to Model.destroy in addition to truncate - */ - public truncate(options?: DestroyOptions): Promise; - - /** - * Drop all tables defined through this sequelize instance. This is done by calling Model.drop on each model - * - * @param options The options passed to each call to Model.drop - */ - public drop(options?: DropOptions): Promise; - - /** - * Test the connection by trying to authenticate - * - * @param options Query Options for authentication - */ - public authenticate(options?: QueryOptions): Promise; - public validate(options?: QueryOptions): Promise; - - /** - * Start a transaction. When using transactions, you should pass the transaction in the options argument - * in order for the query to happen under that transaction - * - * ```js - * try { - * const transaction = await sequelize.transaction(); - * const user = await User.findOne(..., { transaction }); - * await user.update(..., { transaction }); - * await transaction.commit(); - * } catch(err) { - * await transaction.rollback(); - * } - * }) - * ``` - * - * A syntax for automatically committing or rolling back based on the promise chain resolution is also - * supported: - * - * ```js - * try { - * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments - * const user = await User.findOne(..., {transaction}); - * await user.update(..., {transaction}); - * }); - * // Committed - * } catch(err) { - * // Rolled back - * console.error(err); - * } - * ``` - * - * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction - * will automatically be passed to any query that runs witin the callback. To enable CLS, add it do your - * project, create a namespace and set it on the sequelize constructor: - * - * ```js - * const cls = require('cls-hooked'); - * const namespace = cls.createNamespace('....'); - * const Sequelize = require('sequelize'); - * Sequelize.useCLS(namespace); - * ``` - * Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace - * - * @param options Transaction Options - * @param autoCallback Callback for the transaction - */ - public transaction(options: TransactionOptions, autoCallback: (t: Transaction) => PromiseLike): Promise; - public transaction(autoCallback: (t: Transaction) => PromiseLike): Promise; - public transaction(options?: TransactionOptions): Promise; - - /** - * Close all connections used by this sequelize instance, and free all references so the instance can be - * garbage collected. - * - * Normally this is done on process exit, so you only need to call this method if you are creating multiple - * instances, and want to garbage collect some of them. - */ - public close(): Promise; - - /** - * Returns the database version - */ - public databaseVersion(): Promise; -} - -// Utilities - -/** - * Creates a object representing a database function. This can be used in search queries, both in where and - * order parts, and as default values in column definitions. If you want to refer to columns in your - * function, you should use `sequelize.col`, so that the columns are properly interpreted as columns and - * not a strings. - * - * Convert a user's username to upper case - * ```js - * instance.update({ - * username: self.sequelize.fn('upper', self.sequelize.col('username')) - * }) - * ``` - * @param fn The function you want to call - * @param args All further arguments will be passed as arguments to the function - */ -export function fn(fn: string, ...args: unknown[]): Fn; - -/** - * Creates a object representing a column in the DB. This is often useful in conjunction with - * `sequelize.fn`, since raw string arguments to fn will be escaped. - * - * @param col The name of the column - */ -export function col(col: string): Col; - -/** - * Creates a object representing a call to the cast function. - * - * @param val The value to cast - * @param type The type to cast it to - */ -export function cast(val: unknown, type: string): Cast; - -/** - * Creates a object representing a literal, i.e. something that will not be escaped. - * - * @param val - */ -export function literal(val: string): Literal; - -/** - * An AND query - * - * @param args Each argument will be joined by AND - */ -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; - -/** - * Creates an object representing nested where conditions for postgres's json data-type. - * - * @param conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot - * notation or a string using postgres json syntax. - * @param value An optional value to compare against. Produces a string of the form " = - * ''". - */ -export function json(conditionsOrPath: string | object, value?: string | number | boolean): Json; - -export type AttributeType = Fn | Col | Literal | ModelAttributeColumnOptions | string; -export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; - -/** - * A way of specifying attr = condition. - * - * The attr can either be an object taken from `Model.rawAttributes` (for example `Model.rawAttributes.id` - * or - * `Model.rawAttributes.name`). The attribute should be defined in your model definition. The attribute can - * also be an object from one of the sequelize utility functions (`sequelize.fn`, `sequelize.col` etc.) - * - * For string attributes, use the regular `{ where: { attr: something }}` syntax. If you don't want your - * string to be escaped, use `sequelize.literal`. - * - * @param 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 comparator Comparator - * @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 | symbol, logic: LogicType): Where; -export function where(attr: AttributeType, logic: LogicType): Where; - -export default Sequelize; diff --git a/types/lib/sql-string.d.ts b/types/lib/sql-string.d.ts deleted file mode 100644 index bf2d068acd98..000000000000 --- a/types/lib/sql-string.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Escapable = undefined | null | boolean | number | string | Date; -export function escapeId(val: string, forbidQualified?: boolean): string; -export function escape(val: Escapable | Escapable[], timeZone?: string, dialect?: string, format?: string): string; -export function format(sql: string, values: unknown[], timeZone?: string, dialect?: string): string; -export function formatNamedParameters(sql: string, values: unknown[], timeZone?: string, dialect?: string): string; diff --git a/types/lib/table-hints.d.ts b/types/lib/table-hints.d.ts deleted file mode 100644 index 8e5eeacdb7ee..000000000000 --- a/types/lib/table-hints.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -declare enum TableHints { - NOLOCK = 'NOLOCK', - READUNCOMMITTED = 'READUNCOMMITTED', - UPDLOCK = 'UPDLOCK', - REPEATABLEREAD = 'REPEATABLEREAD', - SERIALIZABLE = 'SERIALIZABLE', - READCOMMITTED = 'READCOMMITTED', - TABLOCK = 'TABLOCK', - TABLOCKX = 'TABLOCKX', - PAGLOCK = 'PAGLOCK', - ROWLOCK = 'ROWLOCK', - NOWAIT = 'NOWAIT', - READPAST = 'READPAST', - XLOCK = 'XLOCK', - SNAPSHOT = 'SNAPSHOT', - NOEXPAND = 'NOEXPAND', -} - -export = TableHints; diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts deleted file mode 100644 index 0ebc2cad0933..000000000000 --- a/types/lib/transaction.d.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Deferrable } from './deferrable'; -import { Logging } from './model'; -import { Sequelize } from './sequelize'; - -/** - * The transaction object is used to identify a running transaction. It is created by calling - * `Sequelize.transaction()`. - * - * To run a query under a transaction, you should pass the transaction in the options object. - */ -export class Transaction { - constructor(sequelize: Sequelize, options: TransactionOptions); - - /** - * Commit the transaction - */ - public commit(): Promise; - - /** - * Rollback (abort) the transaction - */ - public rollback(): Promise; - - /** - * Adds hook that is run after a transaction is committed - */ - public afterCommit(fn: (transaction: this) => void | Promise): void; - - /** - * Returns possible options for row locking - */ - static get LOCK(): LOCK; - - /** - * Same as its static version, but can also be called on instances of - * transactions to get possible options for row locking directly from the - * instance. - */ - get LOCK(): LOCK; -} - -// tslint:disable-next-line no-namespace -export namespace Transaction { - /** - * Isolations levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`. - * Default to `REPEATABLE_READ` but you can override the default isolation level by passing `options.isolationLevel` in `new Sequelize`. - * - * The possible isolations levels to use when starting a transaction: - * - * ```js - * { - * READ_UNCOMMITTED: "READ UNCOMMITTED", - * READ_COMMITTED: "READ COMMITTED", - * REPEATABLE_READ: "REPEATABLE READ", - * SERIALIZABLE: "SERIALIZABLE" - * } - * ``` - * - * Pass in the desired level as the first argument: - * - * ```js - * try { - * await sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { - * // your transactions - * }); - * // transaction has been committed. Do something after the commit if required. - * } catch(err) { - * // do something with the err. - * } - * ``` - */ - enum ISOLATION_LEVELS { - READ_UNCOMMITTED = 'READ UNCOMMITTED', - READ_COMMITTED = 'READ COMMITTED', - REPEATABLE_READ = 'REPEATABLE READ', - SERIALIZABLE = 'SERIALIZABLE', - } - - enum TYPES { - DEFERRED = 'DEFERRED', - IMMEDIATE = 'IMMEDIATE', - EXCLUSIVE = 'EXCLUSIVE', - } -} - -/** - * Possible options for row locking. Used in conjunction with `find` calls: - * - * ```js - * t1 // is a transaction - * t1.LOCK.UPDATE, - * t1.LOCK.SHARE, - * t1.LOCK.KEY_SHARE, // Postgres 9.3+ only - * t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only - * ``` - * - * Usage: - * ```js - * t1 // is a transaction - * Model.findAll({ - * where: ..., - * transaction: t1, - * lock: t1.LOCK... - * }); - * ``` - * - * Postgres also supports specific locks while eager loading by using OF: - * ```js - * UserModel.findAll({ - * where: ..., - * include: [TaskModel, ...], - * transaction: t1, - * lock: { - * level: t1.LOCK..., - * of: UserModel - * } - * }); - * ``` - * UserModel will be locked but TaskModel won't! - */ -export enum LOCK { - UPDATE = 'UPDATE', - SHARE = 'SHARE', - /** - * Postgres 9.3+ only - */ - KEY_SHARE = 'KEY SHARE', - /** - * Postgres 9.3+ only - */ - NO_KEY_UPDATE = 'NO KEY UPDATE', -} - -interface LOCK { - UPDATE: LOCK.UPDATE; - SHARE: LOCK.SHARE; - KEY_SHARE: LOCK.KEY_SHARE; - NO_KEY_UPDATE: LOCK.NO_KEY_UPDATE; -} - -/** - * Options provided when the transaction is created - */ -export interface TransactionOptions extends Logging { - autocommit?: boolean; - isolationLevel?: Transaction.ISOLATION_LEVELS; - type?: Transaction.TYPES; - deferrable?: string | Deferrable; - /** - * Parent transaction. - */ - transaction?: Transaction | null; -} - -export default Transaction; diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts deleted file mode 100644 index b06f12cb16dd..000000000000 --- a/types/lib/utils.d.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { DataType } from './data-types'; -import { Model, ModelCtor, ModelType, WhereOptions } from './model'; - -export type Primitive = 'string' | 'number' | 'boolean'; - -export interface Inflector { - singularize(str: string): string; - pluralize(str: string): string; -} - -export function useInflection(inflection: Inflector): void; - -export function camelizeIf(string: string, condition?: boolean): string; -export function underscoredIf(string: string, condition?: boolean): string; -export function isPrimitive(val: unknown): val is Primitive; - -/** Same concept as _.merge, but don't overwrite properties that have already been assigned */ -export function mergeDefaults(a: T, b: Partial): T; -export function spliceStr(str: string, index: number, count: number, add: string): string; -export function camelize(str: string): string; -export function format(arr: string[], dialect: string): string; -export function formatNamedParameters(sql: string, parameters: { - [key: string]: string | number | boolean; -}, dialect: string): string; -export function cloneDeep(obj: T, fn?: (el: unknown) => unknown): T; - -export interface OptionsForMapping { - attributes?: string[]; - where?: WhereOptions; -} - -/** Expand and normalize finder options */ -export function mapFinderOptions>( - options: T, - model: ModelCtor -): T; - -/* Used to map field names in attributes and where conditions */ -export function mapOptionFieldNames>( - options: T, model: ModelCtor -): T; - -export function mapWhereFieldNames(attributes: object, model: ModelType): object; -/** Used to map field names in values */ -export function mapValueFieldNames(dataValues: object, fields: string[], model: ModelType): object; - -export function isColString(value: string): boolean; -export function canTreatArrayAsAnd(arr: unknown[]): boolean; -export function combineTableNames(tableName1: string, tableName2: string): string; - -export function singularize(s: string): string; -export function pluralize(s: string): string; - -export function toDefaultValue(value: unknown): unknown; - -/** - * Determine if the default value provided exists and can be described - * in a db schema using the DEFAULT directive. - * - * @param value Any default value. - */ -export function defaultValueSchemable(hash: DataType): boolean; -export function stack(): NodeJS.CallSite[]; -export function now(dialect: string): Date; - -// Note: Use the `quoteIdentifier()` and `escape()` methods on the -// `QueryInterface` instead for more portable code. -export const TICK_CHAR: string; -export function addTicks(s: string, tickChar?: string): string; -export function removeTicks(s: string, tickChar?: string): string; - -/** - * Wraps a constructor to not need the `new` keyword using a proxy. - * Only used for data types. - */ -export function classToInvokable any>(ctor: T): T & { - (...args: ConstructorParameters): T; -} - -export class SequelizeMethod { - -} - -/* - * 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. - */ -export class Fn extends SequelizeMethod { - constructor(fn: string, args: unknown[]); - public clone(): this; -} - -export class Col extends SequelizeMethod { - public col: string; - constructor(col: string); -} - -export class Cast extends SequelizeMethod { - public val: unknown; - public type: string; - constructor(val: unknown, type?: string); -} - -export class Literal extends SequelizeMethod { - public val: unknown; - constructor(val: unknown); -} - -export class Json extends SequelizeMethod { - public conditions: object; - public path: string; - public value: string | number | boolean; - constructor(conditionsOrPath: string | object, value?: string | number | boolean); -} - -export class Where extends SequelizeMethod { - public attribute: object; - public comparator: string; - public logic: string | object; - constructor(attr: object, comparator: string, logic: string | object); - constructor(attr: object, logic: string | object); -} diff --git a/types/lib/utils/logger.d.ts b/types/lib/utils/logger.d.ts deleted file mode 100644 index 2ad866819fa6..000000000000 --- a/types/lib/utils/logger.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface LoggerConfig { - /** - * @default `sequelize` - */ - context?: string; - /** - * @default `true` - */ - debug?: boolean; -} - -export class Logger { - constructor(config: LoggerConfig) - public debug(message: string): void; - public warn(message: string): void; -} - -export const logger: Logger; diff --git a/types/test/attributes.ts b/types/test/attributes.ts deleted file mode 100644 index 2c754923debd..000000000000 --- a/types/test/attributes.ts +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index 5c2006fb053a..000000000000 --- a/types/test/connection.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expectTypeOf } from "expect-type"; -import { QueryTypes, Sequelize, SyncOptions } from 'sequelize'; -import { User } from 'models/User'; - -export const sequelize = new Sequelize('uri'); - -sequelize.afterBulkSync((options: SyncOptions) => { - console.log('synced'); -}); - -async function test() { - expectTypeOf( - await sequelize.query('SELECT * FROM `test`', { type: QueryTypes.SELECT }) - ).toEqualTypeOf(); - - expectTypeOf( - await sequelize.query('INSERT into test set test=1', { type: QueryTypes.INSERT }) - ).toEqualTypeOf<[number, number]>(); -} - -sequelize.transaction(async transaction => { - expectTypeOf( - await sequelize.query('SELECT * FROM `user`', { - retry: { - max: 123, - }, - model: User, - transaction, - logging: true, - }) - ).toEqualTypeOf(); -}); - -sequelize.query( - 'SELECT * FROM `user` WHERE status = $1', - { bind: ['active'], type: QueryTypes.SELECT } -); - -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 deleted file mode 100644 index 57607f4e1f14..000000000000 --- a/types/test/count.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expectTypeOf } from "expect-type"; -import { Model, Op } from 'sequelize'; - -class MyModel extends Model {} - -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 deleted file mode 100644 index a370249b47f6..000000000000 --- a/types/test/create.ts +++ /dev/null @@ -1,74 +0,0 @@ -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 deleted file mode 100644 index 01d463f2135b..000000000000 --- a/types/test/data-types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { expectTypeOf } from 'expect-type'; -import { DataTypes } from 'sequelize'; - -const { TINYINT, SMALLINT, MEDIUMINT, BIGINT, INTEGER } = DataTypes; - -// TINYINT -expectTypeOf(TINYINT()).toEqualTypeOf(); -expectTypeOf(new TINYINT()).toEqualTypeOf(); -expectTypeOf(TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -expectTypeOf(new TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); - -// SMALLINT -expectTypeOf(SMALLINT()).toEqualTypeOf(); -expectTypeOf(new SMALLINT()).toEqualTypeOf(); -expectTypeOf(SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -expectTypeOf(new SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); - -// MEDIUMINT -expectTypeOf(MEDIUMINT()).toEqualTypeOf(); -expectTypeOf(new MEDIUMINT()).toEqualTypeOf(); -expectTypeOf(MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -expectTypeOf(new MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); - -// 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 deleted file mode 100644 index 54d422c88747..000000000000 --- a/types/test/define.ts +++ /dev/null @@ -1,71 +0,0 @@ -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 UserAttributes { - id: number; - username: string; - firstName: string; - lastName: string; -} - -interface UserCreationAttributes extends Optional {} - -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() { - 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 = () => {}; - -async function testUntyped() { - UntypedUser.customStaticMethod(); - - expectTypeOf().toMatchTypeOf(UntypedUser.build()); - - const user = await UntypedUser.findOne(); - expectTypeOf(user).toEqualTypeOf(); - - 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 deleted file mode 100644 index 0840e5a6a0f7..000000000000 --- a/types/test/e2e/docs-example.ts +++ /dev/null @@ -1,175 +0,0 @@ -// This file is used as example. - -import { BuildOptions, DataTypes, Model, Sequelize } from 'sequelize'; -import { - Association, - HasManyAddAssociationMixin, - HasManyCountAssociationsMixin, - HasManyCreateAssociationMixin, - HasManyGetAssociationsMixin, - HasManyHasAssociationMixin -} from '../../lib/associations'; -import QueryTypes = require("../../lib/query-types"); - -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: 'users', - 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() { - 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 - - const user = await sequelize.query('SELECT * FROM users WHERE name = :userName',{ - type: QueryTypes.SELECT, - replacements: { - userName: 'Johnny' - }, - mapToModel: true, - model: User - }) -} - -// Legacy models - -// 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, - } -}); - -async function stuffTwo() { - const myModel = await MyDefineModel.findByPk(1, { - rejectOnEmpty: true, - }); - console.log(myModel.id); -} diff --git a/types/test/errors.ts b/types/test/errors.ts deleted file mode 100644 index 0a938a37f264..000000000000 --- a/types/test/errors.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { expectTypeOf } from "expect-type"; -import { BaseError, EmptyResultError, Error as AliasedBaseError, UniqueConstraintError } from 'sequelize'; -import { OptimisticLockError } from '../lib/errors'; - -expectTypeOf().toEqualTypeOf(); -expectTypeOf().toHaveProperty('sql').toBeString(); -expectTypeOf().toMatchTypeOf(); -expectTypeOf().toMatchTypeOf(); -expectTypeOf().toMatchTypeOf(); diff --git a/types/test/findByPk.ts b/types/test/findByPk.ts deleted file mode 100644 index a094369e895e..000000000000 --- a/types/test/findByPk.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { User } from './models/User'; - -User.findByPk(Buffer.from('asdf')); diff --git a/types/test/findOne.ts b/types/test/findOne.ts deleted file mode 100644 index 1a71a0647d9f..000000000000 --- a/types/test/findOne.ts +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 53b2e73c383f..000000000000 --- a/types/test/hooks.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { expectTypeOf } from "expect-type"; -import { SemiDeepWritable } from "./type-helpers/deep-writable"; -import { Model, SaveOptions, Sequelize, FindOptions, ModelCtor, ModelType, ModelDefined, ModelStatic } from "sequelize"; -import { ModelHooks } from "../lib/hooks"; - -{ - class TestModel extends Model {} - - const hooks: Partial = { - beforeSave(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? - }, - afterSave(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? - }, - afterFind(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toEqualTypeOf(); - } - }; - - const sequelize = new Sequelize('uri', { hooks }); - TestModel.init({}, { sequelize, hooks }); - - TestModel.addHook('beforeSave', hooks.beforeSave!); - TestModel.addHook('afterSave', hooks.afterSave!); - TestModel.addHook('afterFind', hooks.afterFind!); - - TestModel.beforeSave(hooks.beforeSave!); - TestModel.afterSave(hooks.afterSave!); - TestModel.afterFind(hooks.afterFind!); - - Sequelize.beforeSave(hooks.beforeSave!); - Sequelize.afterSave(hooks.afterSave!); - Sequelize.afterFind(hooks.afterFind!); - Sequelize.afterFind('namedAfterFind', hooks.afterFind!); -} - -// #12959 -{ - const hooks: ModelHooks = 0 as any; - - hooks.beforeValidate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeSave = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeBulkCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeBulkDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeBulkRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeBulkUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeFind = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeCount = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeFindAfterExpandIncludeAll = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeFindAfterOptions = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; - hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; -} diff --git a/types/test/include.ts b/types/test/include.ts deleted file mode 100644 index 7561e8d0b685..000000000000 --- a/types/test/include.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Model, Sequelize, HasMany } from 'sequelize'; - -class MyModel extends Model { - public static associations: { - relation: HasMany - }; -} - -class AssociatedModel extends Model {} - -MyModel.findAll({ - include: [ - { - duplicating: true, - limit: 1, - model: AssociatedModel, - on: { - a: 1, - }, - order: [['id', 'DESC'], [ 'AssociatedModel', MyModel, 'id', 'DESC' ], [ MyModel, 'id' ] ], - separate: true, - where: { state: Sequelize.col('project.state') }, - all: true, - nested: true, - }, - ], -}); - -MyModel.findAll({ - include: [{ all: true }], -}); - -MyModel.findAll({ - include: [{ - limit: 1, - association: 'relation', - order: [['id', 'DESC'], 'id', [ AssociatedModel, MyModel, 'id', 'ASC' ]], - separate: true, - where: { state: Sequelize.col('project.state') }, - }] -}); diff --git a/types/test/index-hints.ts b/types/test/index-hints.ts deleted file mode 100644 index e6a9c6aed1b9..000000000000 --- a/types/test/index-hints.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { User } from 'models/User'; -import { IndexHints } from '..'; - -User.findAll({ - indexHints: [{ - type: IndexHints.FORCE, - values: ['some_index'], - }], -}); diff --git a/types/test/model.ts b/types/test/model.ts deleted file mode 100644 index fea3091a9f04..000000000000 --- a/types/test/model.ts +++ /dev/null @@ -1,187 +0,0 @@ -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: { - other: HasOne; - }; - public static async customStuff() { - return this.sequelize!.query('select 1'); - } -} - -class OtherModel extends Model {} - -const Instance: MyModel = new MyModel({ int: 10 }); - -expectTypeOf(Instance.get('num')).toEqualTypeOf(); - -MyModel.findOne({ - include: [ - { - through: { - as: "OtherModel", - attributes: ['num'] - } - } - ] -}); - -MyModel.findOne({ - include: [ - { model: OtherModel, paranoid: true } - ] -}); - -MyModel.hasOne(OtherModel, { as: 'OtherModelAlias' }); - -MyModel.findOne({ include: ['OtherModelAlias'] }); - -MyModel.findOne({ include: OtherModel }); - -MyModel.count({ include: OtherModel }); - -MyModel.build({ int: 10 }, { include: OtherModel }); - -MyModel.bulkCreate([{ int: 10 }], { include: OtherModel }); - -MyModel.update({}, { where: { foo: 'bar' }, paranoid: false}); - -const sequelize = new Sequelize('mysql://user:user@localhost:3306/mydb'); - -const model: typeof MyModel = MyModel.init({ - virtual: { - type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['num']), - get() { - return this.getDataValue('num') + 2; - }, - set(value: number) { - this.setDataValue('num', value - 2); - } - } -}, { - indexes: [ - { - fields: ['foo'], - using: 'gin', - operator: 'jsonb_path_ops', - } - ], - sequelize, - tableName: 'my_model', - getterMethods: { - multiply: function() { - return this.num * 2; - } - } -}); - -/** - * Tests for findCreateFind() type. - */ -class UserModel extends Model {} - -UserModel.init({ - username: { type: DataTypes.STRING, allowNull: false }, - beta_user: { type: DataTypes.BOOLEAN, allowNull: false } -}, { - sequelize: sequelize -}) - -UserModel.findCreateFind({ - where: { - username: "new user username" - }, - defaults: { - beta_user: true - } -}) - -/** - * 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>(); diff --git a/types/test/models/User.ts b/types/test/models/User.ts deleted file mode 100644 index d69639358238..000000000000 --- a/types/test/models/User.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { - BelongsTo, - BelongsToCreateAssociationMixin, - BelongsToGetAssociationMixin, - BelongsToSetAssociationMixin, - DataTypes, - FindOptions, - Model, - ModelCtor, - Op, - Optional -} from 'sequelize'; -import { sequelize } from '../connection'; - -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; - }; - - public id!: number; - public username!: string; - public firstName!: string; - public lastName!: string; - public groupId!: number; - public createdAt!: Date; - public updatedAt!: Date; - - // mixins for association (optional) - public group?: UserGroup; - public getGroup!: BelongsToGetAssociationMixin; - public setGroup!: BelongsToSetAssociationMixin; - public createGroup!: BelongsToCreateAssociationMixin; -} - -User.init( - { - id: { - type: DataTypes.NUMBER, - primaryKey: true, - }, - firstName: DataTypes.STRING, - lastName: DataTypes.STRING, - username: DataTypes.STRING, - groupId: DataTypes.NUMBER, - }, - { - version: true, - getterMethods: { - a() { - return 1; - }, - }, - setterMethods: { - b(val: string) { - this.username = val; - }, - }, - scopes: { - custom(a: number) { - return { - where: { - firstName: a, - }, - }; - }, - custom2() { - return {} - } - }, - indexes: [{ - fields: ['firstName'], - using: 'BTREE', - name: 'firstNameIdx', - concurrently: true, - }], - sequelize, - } -); - -User.afterSync(() => { - sequelize.getQueryInterface().addIndex(User.tableName, { - fields: ['lastName'], - using: 'BTREE', - name: 'lastNameIdx', - concurrently: true, - }) -}) - -// Hooks -User.afterFind((users, options) => { - console.log('found'); -}); - -// TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly -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: { - firstName: { - [Op.is]: null, - }, - }, -}); - -User.addScope( - 'withFirstName', - (firstName: string) => ({ - where: { firstName }, - }), -); - -// associate -// it is important to import _after_ the model above is already exported so the circular reference works. -import { UserGroup } from './UserGroup'; -export const Group = User.belongsTo(UserGroup, { as: 'group', foreignKey: 'groupId' }); - -// associations refer to their Model -const userType: ModelCtor = User.associations.group.source; -const groupType: ModelCtor = User.associations.group.target; - -User.scope([ - 'custom2', - { method: [ 'custom', 32 ] } -]) - -const instance = new User({ username: 'foo', firstName: 'bar', lastName: 'baz' }); -instance.isSoftDeleted() diff --git a/types/test/models/UserGroup.ts b/types/test/models/UserGroup.ts deleted file mode 100644 index 66cc046fbc3a..000000000000 --- a/types/test/models/UserGroup.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - DataTypes, - HasMany, - HasManyAddAssociationMixin, - HasManyAddAssociationsMixin, - HasManyCountAssociationsMixin, - HasManyCreateAssociationMixin, - HasManyGetAssociationsMixin, - HasManyHasAssociationMixin, - HasManyRemoveAssociationMixin, - HasManyRemoveAssociationsMixin, - HasManySetAssociationsMixin, - Model, -} from 'sequelize'; -import { sequelize } from '../connection'; - -// This class doesn't extend the generic Model, but should still -// function just fine, with a bit less safe type-checking -export class UserGroup extends Model { - public static associations: { - users: HasMany - }; - - public id!: number; - public name!: string; - - // mixins for association (optional) - public users!: User[]; - public getUsers!: HasManyGetAssociationsMixin; - public setUsers!: HasManySetAssociationsMixin; - public addUser!: HasManyAddAssociationMixin; - public addUsers!: HasManyAddAssociationsMixin; - public createUser!: HasManyCreateAssociationMixin; - public countUsers!: HasManyCountAssociationsMixin; - public hasUser!: HasManyHasAssociationMixin; - public removeUser!: HasManyRemoveAssociationMixin; - public removeUsers!: HasManyRemoveAssociationsMixin; -} - -// attach all the metadata to the 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/query-interface.ts b/types/test/query-interface.ts deleted file mode 100644 index 09c403b23810..000000000000 --- a/types/test/query-interface.ts +++ /dev/null @@ -1,225 +0,0 @@ -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; - -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, - }, - }, - { - charset: 'latin1', // default: null - collate: 'latin1_general_ci', - engine: 'MYISAM', // default: 'InnoDB' - uniqueKeys: { - test: { - customIndex: true, - fields: ['attr2', 'attr3'], - } - } - } - ); - await queryInterface.createTable({ tableName: '' }, {}); - - await queryInterface.dropTable('nameOfTheExistingTable'); - await queryInterface.dropTable({ schema: '', tableName: 'nameOfTheExistingTable' }); - - await queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); - - const bulkInsertRes: Promise = queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); - - await queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar', as: 'baz', name: 'quz' }, {}, {}); - - await queryInterface.dropTrigger({ tableName: 'foo', as: 'bar', name: 'baz' }, 'foo', {}); - - await queryInterface.quoteTable({ tableName: 'foo', delimiter: 'bar' }); - - await queryInterface.dropAllTables(); - - await queryInterface.renameTable('Person', 'User'); - await queryInterface.renameTable( - { schema: '', tableName: 'Person' }, - { schema: '', tableName: 'User' }, - ); - - const tableNames: string[] = await queryInterface.showAllTables(); - - /* - attributes will be something like: - - { - 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 - } - } - */ - const attributes: object = await queryInterface.describeTable('Person'); - - await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', DataTypes.STRING); - - // or - - await queryInterface.addColumn( - { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, - 'nameOfTheNewAttribute', - DataTypes.STRING - ); - - // or - - await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { - allowNull: false, - type: DataTypes.STRING, - }); - - await queryInterface.removeColumn('Person', 'signature'); - - // or - - await queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); - - await queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { - allowNull: false, - defaultValue: 0.0, - type: DataTypes.FLOAT, - }); - - // or - - 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, - }, - }); - - await queryInterface.delete(null, 'Person', { - where: { - a: 1, - }, - }); - - class TestModel extends Model {} - - await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, TestModel, {}); - - await queryInterface.insert(null, 'test', {}); -} diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts deleted file mode 100644 index 2b5af1c5dfb4..000000000000 --- a/types/test/sequelize.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Config, Sequelize, Model, QueryTypes, ModelCtor } from 'sequelize'; -import { Fn } from '../lib/utils'; - -Sequelize.useCLS({ -}); - -export const sequelize = new Sequelize({ - hooks: { - afterConnect: (connection: unknown, config: Config) => { - // noop - } - }, - retry: { - max: 123, - match: ['hurr'], - }, - dialectModule: {}, - pool: { - evict: 1000, - } -}); - -const databaseName = sequelize.getDatabaseName(); - -const conn = sequelize.connectionManager; - -// hooks - -sequelize.beforeCreate('test', () => { - // noop -}); - -sequelize - .addHook('beforeConnect', (config: Config) => { - // noop - }) - .addHook('beforeBulkSync', () => { - // noop - }); - -Sequelize.addHook('beforeCreate', () => { - // noop -}).addHook('beforeBulkCreate', () => { - // noop -}); - -Sequelize.beforeConnect(() => { - -}); - -Sequelize.afterConnect(() => { - -}); - -const rnd: Fn = sequelize.random(); - -class Model1 extends Model{} -class Model2 extends Model{} -const myModel: ModelCtor = sequelize.models.asd; -myModel.hasOne(Model2) -myModel.findAll(); - -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 deleted file mode 100644 index b77cf12e4eaf..000000000000 --- a/types/test/transaction.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Deferrable, Sequelize, Transaction } from 'sequelize'; -import { User } from './models/User'; - -export const sequelize = new Sequelize('uri'); - -async function trans() { - const a: number = await sequelize.transaction(async transaction => { - transaction.afterCommit(() => console.log('transaction complete')); - User.create( - { - firstName: 'John', - }, - { - transaction, - } - ); - return 1; - }); -} - -async function trans2() { - return await sequelize.transaction(async transaction => { - transaction.afterCommit(() => console.log('transaction complete')); - User.findAll( - { - transaction, - lock: transaction.LOCK.UPDATE, - } - ); - return 1; - }); -} - -async function trans3() { - return await sequelize.transaction(async transaction => { - transaction.afterCommit(() => console.log('transaction complete')); - User.findAll( - { - transaction, - lock: true, - } - ); - return 1; - }); -} - -async function trans4() { - return await sequelize.transaction(async transaction => { - transaction.afterCommit(() => console.log('transaction complete')); - User.findAll( - { - transaction, - lock: { - level: transaction.LOCK.UPDATE, - of: User, - }, - } - ); - return 1; - }); -} - -async function transact() { - const t = await sequelize.transaction({ - deferrable: Deferrable.SET_DEFERRED(['test']), - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED, - type: Transaction.TYPES.DEFERRED, - }); - await t.commit(); - await t.rollback(); -} - -transact(); - -async function nestedTransact() { - const tr = await sequelize.transaction({ - transaction: await sequelize.transaction(), - }); - 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 deleted file mode 100644 index 843200282732..000000000000 --- a/types/test/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "noEmit": true, - "strict": true, - "baseUrl": ".", - "paths": { - "sequelize": ["../"], - "sequelize/*": ["../"] - }, - "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 deleted file mode 100644 index 19d34e5db1eb..000000000000 --- a/types/test/type-helpers/deep-writable.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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 deleted file mode 100644 index 9e222311053d..000000000000 --- a/types/test/typescriptDocs/Define.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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 deleted file mode 100644 index ac2d70069a13..000000000000 --- a/types/test/typescriptDocs/DefineNoAttributes.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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 deleted file mode 100644 index 163cb8ec88ee..000000000000 --- a/types/test/typescriptDocs/ModelInit.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * 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 deleted file mode 100644 index b53ea86fc12a..000000000000 --- a/types/test/typescriptDocs/ModelInitNoAttributes.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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 deleted file mode 100644 index 9412eebc89ad..000000000000 --- a/types/test/update.ts +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 5879482fbe22..000000000000 --- a/types/test/upsert.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {Model} from "sequelize" -import {sequelize} from './connection'; - -class TestModel extends Model<{ foo: string; bar: string }, {}> { -} - -TestModel.init({ - foo: '', - bar: '', -}, {sequelize}) - -sequelize.transaction(async trx => { - const res1: [TestModel, boolean | null] = await TestModel.upsert({}, { - benchmark: true, - fields: ['foo'], - hooks: true, - logging: true, - returning: true, - searchPath: 'DEFAULT', - transaction: trx, - validate: true, - }); - - const res2: [TestModel, boolean | null] = await TestModel.upsert({}, { - benchmark: true, - fields: ['foo'], - hooks: true, - logging: true, - returning: false, - searchPath: 'DEFAULT', - transaction: trx, - validate: true, - }); - - const res3: [TestModel, boolean | null] = await TestModel.upsert({}, { - benchmark: true, - fields: ['foo'], - hooks: true, - logging: true, - returning: ['foo'], - searchPath: 'DEFAULT', - transaction: trx, - validate: true, - }); -}) diff --git a/types/test/usage.ts b/types/test/usage.ts deleted file mode 100644 index 0f7598ae5a30..000000000000 --- a/types/test/usage.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Group, User } from './models/User'; - -async function test(): Promise { - let user = await User.findOne({ include: [Group] }); - if (!user) { - return; - } - User.update({}, { where: {} }); - user.firstName = 'John'; - await user.save(); - await user.setGroup(2); - - user = new User(); - user = new User({ firstName: 'John' }); - - user = await User.findOne(); - - if (!user) { - return; - } - - user.update({}, {}); - user.update({}, { - silent: true - }); - - const user2 = await User.create({ firstName: 'John', groupId: 1 }); - await User.findAndCountAll({ distinct: true }); - - const user3 = await User.create({ firstName: 'Jane', groupId: 1 }, { validate: false }); - await User.findAndCountAll({ distinct: true }); -} diff --git a/types/test/where.ts b/types/test/where.ts deleted file mode 100644 index 5b4565248e90..000000000000 --- a/types/test/where.ts +++ /dev/null @@ -1,285 +0,0 @@ -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; -} - -// Simple options - -expectTypeOf({ - string: 'foo', - strings: ['foo'], - number: 1, - numbers: [1], - boolean: true, - buffer: Buffer.alloc(0), - buffers: [Buffer.alloc(0)], - null: null, - date: new Date() -}).toMatchTypeOf(); - -// Optional values -expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf(); - -// Misusing optional values (typings allow this, sequelize will throw an error during runtime) -// This might be solved by updates to typescript itself (https://github.com/microsoft/TypeScript/issues/13195) -// expectTypeOf({ needed: 2, optional: undefined }).not.toMatchTypeOf(); - -// Operators - -expectTypeOf({ - [Op.and]: { a: 5 }, // AND (a = 5) -}).toMatchTypeOf(); -expectTypeOf({ - [Op.and]: { a: 5 }, // AND (a = 5) -}).toMatchTypeOf>(); - -expectTypeOf({ - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) -}).toMatchTypeOf(); -expectTypeOf({ - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) -}).toMatchTypeOf>(); - -expectTypeOf({ - [Op.gt]: 6, // > 6 - [Op.gte]: 6, // >= 6 - [Op.lt]: 10, // < 10 - [Op.lte]: 10, // <= 10 - [Op.ne]: 20, // != 20 - [Op.not]: true, // IS NOT TRUE - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.startsWith]: 'hat', - [Op.endsWith]: 'hat', - [Op.substring]: 'hat', - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) - [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) - [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) - [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) - [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) -} as const).toMatchTypeOf(); - -expectTypeOf({ - [Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - [Op.iLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - [Op.notLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - [Op.notILike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] -}).toMatchTypeOf(); - -// Complex where options via combinations - -expectTypeOf([ - { [Op.or]: [{ a: 5 }, { a: 6 }] }, - Sequelize.and(), - Sequelize.or(), - { [Op.and]: [] }, - { rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, - { rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, - { rank: { [Op.or]: { [Op.lt]: 1000, [Op.eq]: null } } }, - { - createdAt: { - [Op.lt]: new Date(), - [Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000), - } - }, - { - [Op.or]: [ - { title: { [Op.like]: 'Boat%' } }, - { description: { [Op.like]: '%boat%' } } - ] - }, - { - meta: { - [Op.contains]: { - site: { - url: 'https://sequelize.org/' - } - } - }, - meta2: { - [Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3'] - }, - meta3: { - [Op.contains]: [1, 2, 3, 4] - }, - }, - { - name: 'a project', - [Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }] - }, - { - id: { - [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] - }, - name: 'a project' - }, - { - id: { - [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] - }, - name: 'a project' - }, - { - name: 'a project', - type: { - [Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }], - }, - }, - { - name: 'a project', - [Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], - }, - { - meta: { - video: { - url: { - [Op.ne]: null, - }, - }, - }, - }, - { - 'meta.audio.length': { - [Op.gt]: 20, - }, - }, - { - [Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], - }, - { - [Op.gt]: fn('NOW'), - }, - whereFn('test', { [Op.gt]: new Date() }), - literal('true'), - fn('LOWER', 'asd'), - { [Op.lt]: Sequelize.literal('SOME_STRING') } -]).toMatchTypeOf(); - -// Relations / Associations -// Find all projects with a least one task where task.state === project.task -MyModel.findAll({ - include: [ - { - model: MyModel, - where: { state: Sequelize.col('project.state') }, - }, - ], -}); - -{ - 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 }); - - // 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] } }); - - // 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()], // 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.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/tsconfig.json b/types/tsconfig.json deleted file mode 100644 index 95b00268ae49..000000000000 --- a/types/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "module": "commonjs", - "moduleResolution": "node", - "noEmit": true, - "lib": ["es2016"] - }, - "types": ["node"], - "include": ["lib/**/*.d.ts", "index.d.ts"] -} diff --git a/types/type-helpers/set-required.d.ts b/types/type-helpers/set-required.d.ts deleted file mode 100644 index db9109189b8a..000000000000 --- a/types/type-helpers/set-required.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 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..440555eb2002 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,16836 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@aws-crypto/crc32@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/eab9581d3363af5ea498ae0e72de792f54d8890360e14a9d8261b7b5c55ebe080279fb2556e07994d785341cdaa99ab0b1ccf137832b53b5904cd6928f2b094b + languageName: node + linkType: hard + +"@aws-crypto/crc32c@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32c@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/223efac396cdebaf5645568fa9a38cd0c322c960ae1f4276bedfe2e1031d0112e49d7d39225d386354680ecefae29f39af469a84b2ddfa77cb6692036188af77 + languageName: node + linkType: hard + +"@aws-crypto/sha1-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha1-browser@npm:5.2.0" + dependencies: + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/51fed0bf078c10322d910af179871b7d299dde5b5897873ffbeeb036f427e5d11d23db9794439226544b73901920fd19f4d86bbc103ed73cc0cfdea47a83c6ac + languageName: node + linkType: hard + +"@aws-crypto/sha256-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-browser@npm:5.2.0" + dependencies: + "@aws-crypto/sha256-js": "npm:^5.2.0" + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/05f6d256794df800fe9aef5f52f2ac7415f7f3117d461f85a6aecaa4e29e91527b6fd503681a17136fa89e9dd3d916e9c7e4cfb5eba222875cb6c077bdc1d00d + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:5.2.0, @aws-crypto/sha256-js@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-js@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/6c48701f8336341bb104dfde3d0050c89c288051f6b5e9bdfeb8091cf3ffc86efcd5c9e6ff2a4a134406b019c07aca9db608128f8d9267c952578a3108db9fd1 + languageName: node + linkType: hard + +"@aws-crypto/supports-web-crypto@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/supports-web-crypto@npm:5.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/4d2118e29d68ca3f5947f1e37ce1fbb3239a0c569cc938cdc8ab8390d595609b5caf51a07c9e0535105b17bf5c52ea256fed705a07e9681118120ab64ee73af2 + languageName: node + linkType: hard + +"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/util@npm:5.2.0" + dependencies: + "@aws-sdk/types": "npm:^3.222.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0362d4c197b1fd64b423966945130207d1fe23e1bb2878a18e361f7743c8d339dad3f8729895a29aa34fff6a86c65f281cf5167c4bf253f21627ae80b6dd2951 + languageName: node + linkType: hard + +"@aws-sdk/client-cloudfront@npm:^3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/client-cloudfront@npm:3.956.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/credential-provider-node": "npm:3.956.0" + "@aws-sdk/middleware-host-header": "npm:3.956.0" + "@aws-sdk/middleware-logger": "npm:3.956.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.956.0" + "@aws-sdk/middleware-user-agent": "npm:3.956.0" + "@aws-sdk/region-config-resolver": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-endpoints": "npm:3.956.0" + "@aws-sdk/util-user-agent-browser": "npm:3.956.0" + "@aws-sdk/util-user-agent-node": "npm:3.956.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.0" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.1" + "@smithy/middleware-retry": "npm:^4.4.17" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.16" + "@smithy/util-defaults-mode-node": "npm:^4.2.19" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/util-waiter": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/ee0a8d087aebbca21880fd608fd807a4524e25b5db379ab0b1b41dffd6e58ac02c4caef8a28172f99e433adee13e652d1852beb07ef8fe85945fa86489e6dab9 + languageName: node + linkType: hard + +"@aws-sdk/client-s3@npm:^3.726.0, @aws-sdk/client-s3@npm:^3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/client-s3@npm:3.956.0" + dependencies: + "@aws-crypto/sha1-browser": "npm:5.2.0" + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/credential-provider-node": "npm:3.956.0" + "@aws-sdk/middleware-bucket-endpoint": "npm:3.956.0" + "@aws-sdk/middleware-expect-continue": "npm:3.956.0" + "@aws-sdk/middleware-flexible-checksums": "npm:3.956.0" + "@aws-sdk/middleware-host-header": "npm:3.956.0" + "@aws-sdk/middleware-location-constraint": "npm:3.956.0" + "@aws-sdk/middleware-logger": "npm:3.956.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.956.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.956.0" + "@aws-sdk/middleware-ssec": "npm:3.956.0" + "@aws-sdk/middleware-user-agent": "npm:3.956.0" + "@aws-sdk/region-config-resolver": "npm:3.956.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-endpoints": "npm:3.956.0" + "@aws-sdk/util-user-agent-browser": "npm:3.956.0" + "@aws-sdk/util-user-agent-node": "npm:3.956.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.0" + "@smithy/eventstream-serde-browser": "npm:^4.2.7" + "@smithy/eventstream-serde-config-resolver": "npm:^4.3.7" + "@smithy/eventstream-serde-node": "npm:^4.2.7" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-blob-browser": "npm:^4.2.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/hash-stream-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/md5-js": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.1" + "@smithy/middleware-retry": "npm:^4.4.17" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.16" + "@smithy/util-defaults-mode-node": "npm:^4.2.19" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/util-waiter": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/f15a3afead20bdd610ba1e165d2d245555dc2b74147665e456c969f892f1d2644fcf56930201ec53b0cd5be9a1dce9e3f9df903f50ebe0f963ca846cecfb6ea4 + languageName: node + linkType: hard + +"@aws-sdk/client-sso@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/client-sso@npm:3.956.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/middleware-host-header": "npm:3.956.0" + "@aws-sdk/middleware-logger": "npm:3.956.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.956.0" + "@aws-sdk/middleware-user-agent": "npm:3.956.0" + "@aws-sdk/region-config-resolver": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-endpoints": "npm:3.956.0" + "@aws-sdk/util-user-agent-browser": "npm:3.956.0" + "@aws-sdk/util-user-agent-node": "npm:3.956.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.0" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.1" + "@smithy/middleware-retry": "npm:^4.4.17" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.16" + "@smithy/util-defaults-mode-node": "npm:^4.2.19" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/1154b2293c70922595cf74ec6f2cc1a2159dadfb837f4b26833a98105cc21a77f16ecd892e55eb122392bae7dca0b125818b20179b70b3999c58e4e6cf0eedf9 + languageName: node + linkType: hard + +"@aws-sdk/core@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/core@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/xml-builder": "npm:3.956.0" + "@smithy/core": "npm:^3.20.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0ad031aeca71c3565c62156024d24b135c4ffabb890473cf1d1d601524044bf1e5f736fb1fb06881229517b30befe6eba03defcc139a6926a576e691d9a009ae + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-env@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/de46448a26bf85c43509e724b24e4d6943c5fc2b16bb8aae11597070bdc62a1461415dfccb6e26f5bbe4ad1cc3df7f21ed201edb25616696b385667d932de84d + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-http@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-stream": "npm:^4.5.8" + tslib: "npm:^2.6.2" + checksum: 10c0/8ca6eea1178f32a57f7e2cadf9a9b5c9456782e9348a73c8b4898012dc3d559f2d79ed28e22aefb3b24190600eecb36d80f6a40aea2bb0fd3b0327478e243904 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-ini@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/credential-provider-env": "npm:3.956.0" + "@aws-sdk/credential-provider-http": "npm:3.956.0" + "@aws-sdk/credential-provider-login": "npm:3.956.0" + "@aws-sdk/credential-provider-process": "npm:3.956.0" + "@aws-sdk/credential-provider-sso": "npm:3.956.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.956.0" + "@aws-sdk/nested-clients": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/1d7d0c1f1fed0ac547549a8d094182eec9a2c3d4b4076b271466e08cad8b881b2d11942351185c1f85fc955a786f72d373a0289a9da14a0cc98f9f190ee6492d + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-login@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-login@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/nested-clients": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/03b5d162d19115273a06efd80f33abb30e66f29801669b4cf620555dcc3704d57fe44479c148ffe112b4560d0107110cfde4de39e9ad76586bc2a1eb979dae13 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-node@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.956.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.956.0" + "@aws-sdk/credential-provider-http": "npm:3.956.0" + "@aws-sdk/credential-provider-ini": "npm:3.956.0" + "@aws-sdk/credential-provider-process": "npm:3.956.0" + "@aws-sdk/credential-provider-sso": "npm:3.956.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/5d8a2f32b0ec62ea7f02455616384cfdbeb7fb20b220fbb2995b39036e8b8fb8b07914deea5a06b512fe8da83825dd2c2dab16822693bb68a6674d5c2d0da0a7 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-process@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/73c9c80f80035f0f472ff0fe741523939a025ce1c8cfd7c6fc329026509c2488b1ca2658a95022bb84af29afd19f06400997e9f99adc3576c35b38f09a19603e + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-sso@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.956.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.956.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/token-providers": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/fe3edbaf9bcfd31ff91a07975b8ca6bb5b070c24e9ba79da4357db4c7542a40367c2913d93a7c8f1e44cbcbc91e3e1bb97660d3b5ec66dcbe980efb1c6eb26b7 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-web-identity@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/nested-clients": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e6e26f6ce863713c32db1e71cf302aeb1c006f018d054dcd93655646824e33e77aba18c0a8822c0c35f58f89a1c5434d71809704859c519e869bb18aacb768f7 + languageName: node + linkType: hard + +"@aws-sdk/middleware-bucket-endpoint@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-arn-parser": "npm:3.953.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/05dcb996ef67d5296e5934e488e6b0553a9ce390c84ca55ff30d25183e16261936cc73d2226580a54f755f1305bd0c338df60283dcc622d104aca10c34ae6cf3 + languageName: node + linkType: hard + +"@aws-sdk/middleware-expect-continue@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/13fbf2550c35a858c794e83e3516cddfb58d2205886169931d502f766af8865e1584900462a47085a34f9c3e50771b62f5a290ba3364b45c068253368b27de3e + languageName: node + linkType: hard + +"@aws-sdk/middleware-flexible-checksums@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.956.0" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@aws-crypto/crc32c": "npm:5.2.0" + "@aws-crypto/util": "npm:5.2.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/is-array-buffer": "npm:^4.2.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e50f2be8d1ab1c6aac43c1f94de952872ca5219c7c7a91001e458d4a9da943fa67c69a95e118468dabb96cf40b6ee73c2eb1f10baf0959f034f3347446a8fbef + languageName: node + linkType: hard + +"@aws-sdk/middleware-host-header@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/2b1827c323daede1f0e8c162d32baa7a0552e11cd097c3bdc3f35938cb3edf0b6f9e285bc1803f0fd8c719e137610364b5bc10998cabed7307dca6591536c525 + languageName: node + linkType: hard + +"@aws-sdk/middleware-location-constraint@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/8a51408e1c122b58800a05e9e0163701579cbc4438c3739092a66bf9581f1673d0b48488c2e73fc6ea5cb0081a08ecfe45947ace9090e6f4d780a01afd0b8764 + languageName: node + linkType: hard + +"@aws-sdk/middleware-logger@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-logger@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/29db9b64e8fb747dc9f3e96b7f56502b08ccb658aac8ba1794d4efaa8aacf5f3cd5a8f7d056fa7c723a8eaffe3c129941948042f968c658e303dda23d977ce08 + languageName: node + linkType: hard + +"@aws-sdk/middleware-recursion-detection@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@aws/lambda-invoke-store": "npm:^0.2.2" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/a458d98c022c5553d2f4d821645f4ed311d58fb791e8305e904285b086d2ee68976d1ff07f2fe0a337d10fdf8b9b09f91faacf7976872f4c832ee54978e327ee + languageName: node + linkType: hard + +"@aws-sdk/middleware-sdk-s3@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-arn-parser": "npm:3.953.0" + "@smithy/core": "npm:^3.20.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0c07ca65c36de648e655b44b55e5cf5930dd2bf3af2643f95035035b55e3b29ec1c4158b77e162659b75ab384c02a71f4cd819a88f2e355d83c7fa800537b94c + languageName: node + linkType: hard + +"@aws-sdk/middleware-ssec@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/442c411c8f760736161fbf377abb29dfb7b1198eecc7b8834dbab044fd600ecac272105347a34d2e2fc1c8946f132fd3a684398d28b072b7e57196c3235594e4 + languageName: node + linkType: hard + +"@aws-sdk/middleware-user-agent@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-endpoints": "npm:3.956.0" + "@smithy/core": "npm:^3.20.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/f6a220adfb4446faad1823577f68e6d7fbb1d220c7249fc621ac4e982b280291fa4a16e7ecc94046a7a1447dd648d9c5e27afda103da02511140ce3918704b05 + languageName: node + linkType: hard + +"@aws-sdk/nested-clients@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/nested-clients@npm:3.956.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/middleware-host-header": "npm:3.956.0" + "@aws-sdk/middleware-logger": "npm:3.956.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.956.0" + "@aws-sdk/middleware-user-agent": "npm:3.956.0" + "@aws-sdk/region-config-resolver": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@aws-sdk/util-endpoints": "npm:3.956.0" + "@aws-sdk/util-user-agent-browser": "npm:3.956.0" + "@aws-sdk/util-user-agent-node": "npm:3.956.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.0" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.1" + "@smithy/middleware-retry": "npm:^4.4.17" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.16" + "@smithy/util-defaults-mode-node": "npm:^4.2.19" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/61cf1a0c0c53ebc90b32cc448138ac6a39ae516f2c140feac4ef948f875a47bff3d809a6c35768252768ac135835a56ca402bf6b0615e9d96ff5e80c606f39dd + languageName: node + linkType: hard + +"@aws-sdk/region-config-resolver@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/040114f1a9d824cd2bc8adb54428b0da86f33b2e16b8f144824982d4f3618f4b4686ab2983393751575dbf0ab24e957121aa89bf7c07857c4642b939505e02ff + languageName: node + linkType: hard + +"@aws-sdk/signature-v4-multi-region@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.956.0" + dependencies: + "@aws-sdk/middleware-sdk-s3": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/b9bcc480003beaabb2eb3f01f4dbe1477f11c16fb4fbf74d7434e59b3b78e93f100053e1bca299ca24e50a4f0cb8bd4fc1df6daa8994a381fe132b3489aef5a2 + languageName: node + linkType: hard + +"@aws-sdk/token-providers@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/token-providers@npm:3.956.0" + dependencies: + "@aws-sdk/core": "npm:3.956.0" + "@aws-sdk/nested-clients": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/1771692416cc15ea2e60bb9ba16b8b940a61810b0d31db5f507a69029a3bf29c39ee5ee8d7784cc58612a3600b138e79d255831eb33c389166c99956b90fba31 + languageName: node + linkType: hard + +"@aws-sdk/types@npm:3.956.0, @aws-sdk/types@npm:^3.222.0": + version: 3.956.0 + resolution: "@aws-sdk/types@npm:3.956.0" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ffc183a9f7bec2ea985819325481a6606832df7cfb6982aa6a40052f4cbe203805efd521cd00e45463f6d3e5f6c59f2f667e34602fe1035f3300897872b18877 + languageName: node + linkType: hard + +"@aws-sdk/util-arn-parser@npm:3.953.0": + version: 3.953.0 + resolution: "@aws-sdk/util-arn-parser@npm:3.953.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/dead4892f01ae994ee5ad39c3e21cc736d80d95874f624d5b278934016f0cd509f578423da34b53e7314d9ca9ed0b38a30c18fdbd1f34199d2cee0926f2b784f + languageName: node + linkType: hard + +"@aws-sdk/util-endpoints@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/util-endpoints@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-endpoints": "npm:^3.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/e42e3a0f10a681ff96142ddeea7374722a5d988206f3f7e2ff0798c388b8acd93ac7b907a3d94a5d89dbf2dd3bd69aa89595a3bbb4bc910c6296f44d73f9512b + languageName: node + linkType: hard + +"@aws-sdk/util-locate-window@npm:^3.0.0": + version: 3.723.0 + resolution: "@aws-sdk/util-locate-window@npm:3.723.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/c9c75d3ee06bd1d1edad78bea8324f2d4ad6086803f27731e1f3c25e946bb630c8db2991a5337e4dbeee06507deab9abea80b134ba4e3fbb27471d438a030639 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-browser@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.956.0" + dependencies: + "@aws-sdk/types": "npm:3.956.0" + "@smithy/types": "npm:^4.11.0" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/8132b1d0c837a55335dbc40d5fdff7b6375a0c6c5e836b1543b6a70fe98537b657eb28de574de1ad55a8d177c8b0433170eb888c33a1371acaff8f12eb82ed08 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-node@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.956.0" + dependencies: + "@aws-sdk/middleware-user-agent": "npm:3.956.0" + "@aws-sdk/types": "npm:3.956.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 10c0/eadc957ac4f52b355d0889c2f9aafd0332022867c2446bea5ca2c8da35d6b0a99be911a48dd394df44d4aafab1700edbccb7198dad1cbe984a1b2b2bf70e49da + languageName: node + linkType: hard + +"@aws-sdk/xml-builder@npm:3.956.0": + version: 3.956.0 + resolution: "@aws-sdk/xml-builder@npm:3.956.0" + dependencies: + "@smithy/types": "npm:^4.11.0" + fast-xml-parser: "npm:5.2.5" + tslib: "npm:^2.6.2" + checksum: 10c0/168aa4b60af33a888d9301155e00839ca81334e1481c5e2dcc82f8bbfe49ed34174e8e4d8a2d9e1b0f8d7318269b4a10edfb1b65dc42797731539a570c7a4114 + languageName: node + linkType: hard + +"@aws/lambda-invoke-store@npm:^0.2.2": + version: 0.2.2 + resolution: "@aws/lambda-invoke-store@npm:0.2.2" + checksum: 10c0/0ce2f527e2ab6b07372a08a137991163b99bf646b8dbbb01dbc5370f4e578aa6ddf7f09a63ecead576f04ce54e52cb927c12683f4d97e322dcb76ddfc5843784 + languageName: node + linkType: hard + +"@azure/abort-controller@npm:^2.0.0, @azure/abort-controller@npm:^2.1.2": + version: 2.1.2 + resolution: "@azure/abort-controller@npm:2.1.2" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/3771b6820e33ebb56e79c7c68e2288296b8c2529556fbd29cf4cf2fbff7776e7ce1120072972d8df9f1bf50e2c3224d71a7565362b589595563f710b8c3d7b79 + languageName: node + linkType: hard + +"@azure/core-auth@npm:^1.3.0, @azure/core-auth@npm:^1.4.0, @azure/core-auth@npm:^1.7.2, @azure/core-auth@npm:^1.8.0, @azure/core-auth@npm:^1.9.0": + version: 1.9.0 + resolution: "@azure/core-auth@npm:1.9.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-util": "npm:^1.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/b7d8f33b81a8c9a76531acacc7af63d888429f0d763bb1ab8e28e91ddbf1626fc19cf8ca74f79c39b0a3e5acb315bdc4c4276fb979816f315712ea1bd611273d + languageName: node + linkType: hard + +"@azure/core-client@npm:^1.3.0, @azure/core-client@npm:^1.5.0, @azure/core-client@npm:^1.6.2, @azure/core-client@npm:^1.9.2": + version: 1.9.4 + resolution: "@azure/core-client@npm:1.9.4" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-auth": "npm:^1.4.0" + "@azure/core-rest-pipeline": "npm:^1.20.0" + "@azure/core-tracing": "npm:^1.0.0" + "@azure/core-util": "npm:^1.6.1" + "@azure/logger": "npm:^1.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/c38c494c0bf085a89720d97c5bfc098cd1a2bbc5b9c41f8c32ecf2f3b81b476af1afe2f0d996d7e23900581415306e59280d507410bf0aa80804e0e411b8a2be + languageName: node + linkType: hard + +"@azure/core-http-compat@npm:^2.0.0, @azure/core-http-compat@npm:^2.0.1": + version: 2.3.0 + resolution: "@azure/core-http-compat@npm:2.3.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-client": "npm:^1.3.0" + "@azure/core-rest-pipeline": "npm:^1.20.0" + checksum: 10c0/b66e7060bfc80bf07ae251c636272d387bf2b548a1061e0b1adbe3cec67084b5592fef2d2dae43f72e7085cf3660aab9d264a29dfc23c1a31ab918cfcece1c97 + languageName: node + linkType: hard + +"@azure/core-lro@npm:^2.2.0": + version: 2.7.2 + resolution: "@azure/core-lro@npm:2.7.2" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-util": "npm:^1.2.0" + "@azure/logger": "npm:^1.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/bee809e47661b40021bbbedf88de54019715fdfcc95ac552b1d901719c29d78e293eeab51257b8f5155aac768eb4ea420715004d00d6e32109f5f97db5960d39 + languageName: node + linkType: hard + +"@azure/core-paging@npm:^1.1.1": + version: 1.6.2 + resolution: "@azure/core-paging@npm:1.6.2" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/c727782f8dc66eff50c03421af2ca55f497f33e14ec845f5918d76661c57bc8e3a7ca9fa3d39181287bfbfa45f28cb3d18b67c31fd36bbe34146387dbd07b440 + languageName: node + linkType: hard + +"@azure/core-rest-pipeline@npm:^1.10.1, @azure/core-rest-pipeline@npm:^1.17.0, @azure/core-rest-pipeline@npm:^1.20.0, @azure/core-rest-pipeline@npm:^1.8.0, @azure/core-rest-pipeline@npm:^1.8.1": + version: 1.20.0 + resolution: "@azure/core-rest-pipeline@npm:1.20.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-auth": "npm:^1.8.0" + "@azure/core-tracing": "npm:^1.0.1" + "@azure/core-util": "npm:^1.11.0" + "@azure/logger": "npm:^1.0.0" + "@typespec/ts-http-runtime": "npm:^0.2.2" + tslib: "npm:^2.6.2" + checksum: 10c0/d82094805fad3ef7b5c2646c21e3fc62ff9534806bfc9840a9439244800cdb3ea7e539f40f5077e7c4db2656f882d8a56ea736e81167ee39dd9f426919fe72f2 + languageName: node + linkType: hard + +"@azure/core-tracing@npm:^1.0.0, @azure/core-tracing@npm:^1.0.1, @azure/core-tracing@npm:^1.1.2": + version: 1.2.0 + resolution: "@azure/core-tracing@npm:1.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/7cd114b3c11730a1b8b71d89b64f9d033dfe0710f2364ef65645683381e2701173c08ff8625a0b0bc65bb3c3e0de46c80fdb2735e37652425489b65a283f043d + languageName: node + linkType: hard + +"@azure/core-util@npm:^1.0.0, @azure/core-util@npm:^1.10.0, @azure/core-util@npm:^1.11.0, @azure/core-util@npm:^1.2.0, @azure/core-util@npm:^1.6.1": + version: 1.12.0 + resolution: "@azure/core-util@npm:1.12.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@typespec/ts-http-runtime": "npm:^0.2.2" + tslib: "npm:^2.6.2" + checksum: 10c0/9335e619078781a14c616840125deaaaef89198d9c99e6c9cd4452e9b18d4f95823246da4b8277ba186647e05ebcd88b419372616d1acad020c0172340ae02e5 + languageName: node + linkType: hard + +"@azure/core-xml@npm:^1.4.3": + version: 1.4.5 + resolution: "@azure/core-xml@npm:1.4.5" + dependencies: + fast-xml-parser: "npm:^5.0.7" + tslib: "npm:^2.8.1" + checksum: 10c0/2c62106376ebc3f6959d833c41b91e3be5793a217ee83ed286cc0ad34971c20d294ebfafdc906975e92c65071e410b4bce2e81a65ea3c8966a1042718a207c91 + languageName: node + linkType: hard + +"@azure/identity@npm:^4.2.1": + version: 4.9.1 + resolution: "@azure/identity@npm:4.9.1" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-auth": "npm:^1.9.0" + "@azure/core-client": "npm:^1.9.2" + "@azure/core-rest-pipeline": "npm:^1.17.0" + "@azure/core-tracing": "npm:^1.0.0" + "@azure/core-util": "npm:^1.11.0" + "@azure/logger": "npm:^1.0.0" + "@azure/msal-browser": "npm:^4.2.0" + "@azure/msal-node": "npm:^3.5.0" + open: "npm:^10.1.0" + tslib: "npm:^2.2.0" + checksum: 10c0/c60221d5e8fcaa70ce431f239492db6f62038a9e61fd356a15faf76670cead4718fad680fa12a80140b09da230688379485fe3a427070588252ab126f42a5362 + languageName: node + linkType: hard + +"@azure/keyvault-common@npm:^2.0.0": + version: 2.0.0 + resolution: "@azure/keyvault-common@npm:2.0.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-auth": "npm:^1.3.0" + "@azure/core-client": "npm:^1.5.0" + "@azure/core-rest-pipeline": "npm:^1.8.0" + "@azure/core-tracing": "npm:^1.0.0" + "@azure/core-util": "npm:^1.10.0" + "@azure/logger": "npm:^1.1.4" + tslib: "npm:^2.2.0" + checksum: 10c0/79499d5e6dde8cd5d6b119d36ef5789aec3f2c4ecfb4ce45f6487c15c23088963ce7bc09e3fa18d44abf31038f0a1779c470e8aa085e03cb0f0c204c7f7faf5c + languageName: node + linkType: hard + +"@azure/keyvault-keys@npm:^4.4.0": + version: 4.9.0 + resolution: "@azure/keyvault-keys@npm:4.9.0" + dependencies: + "@azure/abort-controller": "npm:^2.0.0" + "@azure/core-auth": "npm:^1.3.0" + "@azure/core-client": "npm:^1.5.0" + "@azure/core-http-compat": "npm:^2.0.1" + "@azure/core-lro": "npm:^2.2.0" + "@azure/core-paging": "npm:^1.1.1" + "@azure/core-rest-pipeline": "npm:^1.8.1" + "@azure/core-tracing": "npm:^1.0.0" + "@azure/core-util": "npm:^1.0.0" + "@azure/keyvault-common": "npm:^2.0.0" + "@azure/logger": "npm:^1.0.0" + tslib: "npm:^2.2.0" + checksum: 10c0/65bcb2ba24f6df9122e466b770eb9905d98219475d9aa7fab459ae57277be22ffe881914113719ec654ca01ff24305d0dfa7b0ca6c460eed5d0b5506557116aa + languageName: node + linkType: hard + +"@azure/logger@npm:^1.0.0, @azure/logger@npm:^1.1.4": + version: 1.2.0 + resolution: "@azure/logger@npm:1.2.0" + dependencies: + "@typespec/ts-http-runtime": "npm:^0.2.2" + tslib: "npm:^2.6.2" + checksum: 10c0/a04693423143204d25e82bde1c57ddb53456fd00be0dbd91d1b078b0e17a1d564ababfd92467d9e51e201a0a756c6611918d7207b17bc4feb417885b80770ecd + languageName: node + linkType: hard + +"@azure/msal-browser@npm:^4.2.0": + version: 4.11.1 + resolution: "@azure/msal-browser@npm:4.11.1" + dependencies: + "@azure/msal-common": "npm:15.5.2" + checksum: 10c0/0bc9e094196f0855b0cdf184df316fb917f9d4de677826481af9b109833dc8f088332b38d171ba596c105a30ad30a0e20f284b91f26893fca013c2d166c55d35 + languageName: node + linkType: hard + +"@azure/msal-common@npm:15.5.2": + version: 15.5.2 + resolution: "@azure/msal-common@npm:15.5.2" + checksum: 10c0/45513b26f5b5967e12ca43c4c978205c0d3d2b1b5df5e854e6c0af0afe9788fd85946abf319ef2f1b03377cf51e1f5631633d7655e5bc0b11eaaa772e8525ef6 + languageName: node + linkType: hard + +"@azure/msal-node@npm:^3.5.0": + version: 3.5.2 + resolution: "@azure/msal-node@npm:3.5.2" + dependencies: + "@azure/msal-common": "npm:15.5.2" + jsonwebtoken: "npm:^9.0.0" + uuid: "npm:^8.3.0" + checksum: 10c0/0ad10467f464fe448e4e30f979695b896d812ae3c68c0064956cbc2985a0272c0920560502389d9a26ca234ad3801f724bb61933b9e94681f31e9e497051198b + languageName: node + linkType: hard + +"@azure/storage-blob@npm:12.26.x": + version: 12.26.0 + resolution: "@azure/storage-blob@npm:12.26.0" + dependencies: + "@azure/abort-controller": "npm:^2.1.2" + "@azure/core-auth": "npm:^1.4.0" + "@azure/core-client": "npm:^1.6.2" + "@azure/core-http-compat": "npm:^2.0.0" + "@azure/core-lro": "npm:^2.2.0" + "@azure/core-paging": "npm:^1.1.1" + "@azure/core-rest-pipeline": "npm:^1.10.1" + "@azure/core-tracing": "npm:^1.1.2" + "@azure/core-util": "npm:^1.6.1" + "@azure/core-xml": "npm:^1.4.3" + "@azure/logger": "npm:^1.0.0" + events: "npm:^3.0.0" + tslib: "npm:^2.2.0" + checksum: 10c0/069b7a85dddb33ee793efd74fbc1a3377c6d14dbb11094c2ebae87e324f16d23292806d5dcdf04280456dafc4d960e847968f6f01e384039b47363d61faf1017 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/compat-data@npm:7.27.1" + checksum: 10c0/03e3a01b6772858dc5064f332ad4dc16fbbc0353f2180fd663a2651e8305058e35b6db57114e345d925def9b73cd7a322e95a45913428b8db705a098fd3dd289 + languageName: node + linkType: hard + +"@babel/core@npm:^7.21.4, @babel/core@npm:^7.23.9": + version: 7.27.1 + resolution: "@babel/core@npm:7.27.1" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.1" + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helpers": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/0fc31f87f5401ac5d375528cb009f4ea5527fc8c5bb5b64b5b22c033b60fd0ad723388933a5f3f5db14e1edd13c958e9dd7e5c68f9b68c767aeb496199c8a4bb + languageName: node + linkType: hard + +"@babel/eslint-parser@npm:^7.21.3": + version: 7.27.1 + resolution: "@babel/eslint-parser@npm:7.27.1" + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals": "npm:5.1.1-v1" + eslint-visitor-keys: "npm:^2.1.0" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + checksum: 10c0/dedb2bc7ef00307eaf8e40d5ec7f1e8ae4649838fa2e7ec5e1b47eaf9bd8708949503b05175de922e2431ce69a5f3b5fab1c1202003b1d9457d3c0b2c3bdc4ec + languageName: node + linkType: hard + +"@babel/generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/generator@npm:7.27.1" + dependencies: + "@babel/parser": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/c4156434b21818f558ebd93ce45f027c53ee570ce55a84fd2d9ba45a79ad204c17e0bff753c886fb6c07df3385445a9e34dc7ccb070d0ac7e80bb91c8b57f423 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-compilation-targets@npm:7.27.1" + dependencies: + "@babel/compat-data": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/1cfd3760a1bf1e367ea4a91214c041be7076197ba7a4f3c0710cab00fb5734eb010a2946efe6ecfb1ca9dc63e6c69644a1afa399db4082f374b9311e129f6f0b + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-transforms@npm:7.27.1" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/196ab29635fe6eb5ba6ead2972d41b1c0d40f400f99bd8fc109cef21440de24c26c972fabf932585e618694d590379ab8d22def8da65a54459d38ec46112ead7 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.19.1, @babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helpers@npm:7.27.1" + dependencies: + "@babel/template": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e078257b9342dae2c041ac050276c5a28701434ad09478e6dc6976abd99f721a5a92e4bebddcbca6b1c3a7e8acace56a946340c701aad5e7507d2c87446459ba + languageName: node + linkType: hard + +"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/parser@npm:7.27.1" + dependencies: + "@babel/types": "npm:^7.27.1" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/ae4a5eda3ada3fd54c9942d9f14385df7a18e71b386cf2652505bb9a40a32250dfde3bdda71fb08af00b1e154f0a6213e6cdaaa88e9941229ec0003f7fead759 + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/template@npm:7.27.1" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/155a8e056e82f1f1e2413b7bf9d96890e371d617c7f77f25621fb0ddb32128958d86bc5c3356f00be266e9f8c121d886de5b4143dbb72eac362377f53aba72a2 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/traverse@npm:7.27.1" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/d912110037b03b1d70a2436cfd51316d930366a5f54252da2bced1ba38642f644f848240a951e5caf12f1ef6c40d3d96baa92ea6e84800f2e891c15e97b25d50 + languageName: node + linkType: hard + +"@babel/types@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/types@npm:7.27.1" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/ed736f14db2fdf0d36c539c8e06b6bb5e8f9649a12b5c0e1c516fed827f27ef35085abe08bf4d1302a4e20c9a254e762eed453bce659786d4a6e01ba26a91377 + languageName: node + linkType: hard + +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.1.0, @emnapi/core@npm:^1.4.0": + version: 1.4.3 + resolution: "@emnapi/core@npm:1.4.3" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.2" + tslib: "npm:^2.4.0" + checksum: 10c0/e30101d16d37ef3283538a35cad60e22095aff2403fb9226a35330b932eb6740b81364d525537a94eb4fb51355e48ae9b10d779c0dd1cdcd55d71461fe4b45c7 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.4.0": + version: 1.4.3 + resolution: "@emnapi/runtime@npm:1.4.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/3b7ab72d21cb4e034f07df80165265f85f445ef3f581d1bc87b67e5239428baa00200b68a7d5e37a0425c3a78320b541b07f76c5530f6f6f95336a6294ebf30b + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.2": + version: 1.0.2 + resolution: "@emnapi/wasi-threads@npm:1.0.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f0621b1fc715221bd2d8332c0ca922617bcd77cdb3050eae50a124eb8923c54fa425d23982dc8f29d505c8798a62d1049bace8b0686098ff9dd82270e06d772e + languageName: node + linkType: hard + +"@ephys/eslint-config-typescript@npm:20.1.4": + version: 20.1.4 + resolution: "@ephys/eslint-config-typescript@npm:20.1.4" + dependencies: + "@ephys/eslint-config": "npm:^20.1.4" + "@typescript-eslint/eslint-plugin": "npm:^7.2.0" + "@typescript-eslint/parser": "npm:^7.2.0" + eslint-import-resolver-typescript: "npm:^3.6.1" + checksum: 10c0/27598291f71eef8b2f9f4a2713e02067ade467442ebf3a616687ff184e79215530b845491a0eb5a59a14621558f572d8a4f06dca530062685bdc1ae7c4812aa6 + languageName: node + linkType: hard + +"@ephys/eslint-config@npm:^20.1.4": + version: 20.1.4 + resolution: "@ephys/eslint-config@npm:20.1.4" + dependencies: + "@babel/core": "npm:^7.21.4" + "@babel/eslint-parser": "npm:^7.21.3" + eslint: "npm:^8.38.0" + eslint-plugin-eslint-comments: "npm:^3.2.0" + eslint-plugin-import: "npm:^2.27.5" + eslint-plugin-jest: "npm:^27.2.1" + eslint-plugin-json: "npm:^3.1.0" + eslint-plugin-jsx-a11y: "npm:^6.7.1" + eslint-plugin-lodash: "npm:^7.4.0" + eslint-plugin-react: "npm:^7.32.2" + eslint-plugin-react-hooks: "npm:^4.6.0" + eslint-plugin-small-import: "npm:^1.0.0" + eslint-plugin-sort-destructure-keys: "npm:^1.5.0" + eslint-plugin-unicorn: "npm:^46.0.0" + checksum: 10c0/c7bd5ce1c65c0ce6cbac5aae17e27be166cdfdf3cf44526fdefaf3739472f900fc2c7f26f3e2a87732f4c8e0668e50b3ae597c5b38b10d1e932981d28984e947 + languageName: node + linkType: hard + +"@epic-web/invariant@npm:^1.0.0": + version: 1.0.0 + resolution: "@epic-web/invariant@npm:1.0.0" + checksum: 10c0/72dbeb026e4e4eb3bc9c65739b91408ca77ab7d603a2494fa2eff3790ec22892c4caba751cffdf30f5ccf0e7ba79c1e9c96cf0a357404b9432bf1365baac23ca + languageName: node + linkType: hard + +"@es-joy/jsdoccomment@npm:~0.76.0": + version: 0.76.0 + resolution: "@es-joy/jsdoccomment@npm:0.76.0" + dependencies: + "@types/estree": "npm:^1.0.8" + "@typescript-eslint/types": "npm:^8.46.0" + comment-parser: "npm:1.4.1" + esquery: "npm:^1.6.0" + jsdoc-type-pratt-parser: "npm:~6.10.0" + checksum: 10c0/8fe4edec7d60562787ea8c77193ebe8737a9e28ec3143d383506b63890d0ffd45a2813e913ad1f00f227cb10e3a1fb913e5a696b33d499dc564272ff1a6f3fdb + languageName: node + linkType: hard + +"@es-joy/resolve.exports@npm:1.2.0": + version: 1.2.0 + resolution: "@es-joy/resolve.exports@npm:1.2.0" + checksum: 10c0/7e4713471f5eccb17a925a12415a2d9e372a42376813a19f6abd9c35e8d01ab1403777265817da67c6150cffd4f558d9ad51e26a8de6911dad89d9cb7eedacd8 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": + version: 4.7.0 + resolution: "@eslint-community/eslint-utils@npm:4.7.0" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.12.1 + resolution: "@eslint-community/regexpp@npm:4.12.1" + checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 + languageName: node + linkType: hard + +"@gar/promisify@npm:^1.0.1": + version: 1.1.3 + resolution: "@gar/promisify@npm:1.1.3" + checksum: 10c0/0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff + languageName: node + linkType: hard + +"@gerrit0/mini-shiki@npm:^1.24.0": + version: 1.27.2 + resolution: "@gerrit0/mini-shiki@npm:1.27.2" + dependencies: + "@shikijs/engine-oniguruma": "npm:^1.27.2" + "@shikijs/types": "npm:^1.27.2" + "@shikijs/vscode-textmate": "npm:^10.0.1" + checksum: 10c0/aee681637d123e0e8c9ec154b117ce166dd8b4b9896341e617fef16d0b21ef91a17f6f90cc874137e33ca85b5898817b59bb8aea178cdb2fa6e75b0fff3640c2 + languageName: node + linkType: hard + +"@google-cloud/paginator@npm:^5.0.0": + version: 5.0.2 + resolution: "@google-cloud/paginator@npm:5.0.2" + dependencies: + arrify: "npm:^2.0.0" + extend: "npm:^3.0.2" + checksum: 10c0/aac4ed986c2b274ac9fdca3f68d5ba6ee95f4c35370b11db25c288bf485352e2ec5df16bf9c3cff554a2e73a07e62f10044d273788df61897b81fe47bb18106d + languageName: node + linkType: hard + +"@google-cloud/projectify@npm:^4.0.0": + version: 4.0.0 + resolution: "@google-cloud/projectify@npm:4.0.0" + checksum: 10c0/0d0a6ceca76a138973fcb3ad577f209acdbd9d9aed1c645b09f98d5e5a258053dbbe6c1f13e6f85310cc0d9308f5f3a84f8fa4f1a132549a68d86174fb21067f + languageName: node + linkType: hard + +"@google-cloud/promisify@npm:<4.1.0": + version: 4.0.0 + resolution: "@google-cloud/promisify@npm:4.0.0" + checksum: 10c0/4332cbd923d7c6943ecdf46f187f1417c84bb9c801525cd74d719c766bfaad650f7964fb74576345f6537b6d6273a4f2992c8d79ebec6c8b8401b23d626b8dd3 + languageName: node + linkType: hard + +"@google-cloud/storage@npm:^7.7.0": + version: 7.16.0 + resolution: "@google-cloud/storage@npm:7.16.0" + dependencies: + "@google-cloud/paginator": "npm:^5.0.0" + "@google-cloud/projectify": "npm:^4.0.0" + "@google-cloud/promisify": "npm:<4.1.0" + abort-controller: "npm:^3.0.0" + async-retry: "npm:^1.3.3" + duplexify: "npm:^4.1.3" + fast-xml-parser: "npm:^4.4.1" + gaxios: "npm:^6.0.2" + google-auth-library: "npm:^9.6.3" + html-entities: "npm:^2.5.2" + mime: "npm:^3.0.0" + p-limit: "npm:^3.0.1" + retry-request: "npm:^7.0.0" + teeny-request: "npm:^9.0.0" + uuid: "npm:^8.0.0" + checksum: 10c0/a2a3f341232415d702c8fb054ec2eecbb6908a613848d7279f0b36db9b556ad93444a77a2f2a4c4c6b3f8901b3e7b2350120408c89b17c0ee17b31867a261462 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^2.0.3": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c + languageName: node + linkType: hard + +"@hutson/parse-repository-url@npm:^3.0.0": + version: 3.0.2 + resolution: "@hutson/parse-repository-url@npm:3.0.2" + checksum: 10c0/d9197757ecad2df18d29d3e1d1fe0716d458fd88b849c71cbec9e78239f911074c97e8d764dfd8ed890431c1137e52dd7a337207fd65be20ce0784f7860ae4d1 + languageName: node + linkType: hard + +"@inquirer/ansi@npm:^1.0.2": + version: 1.0.2 + resolution: "@inquirer/ansi@npm:1.0.2" + checksum: 10c0/8e408cc628923aa93402e66657482ccaa2ad5174f9db526d9a8b443f9011e9cd8f70f0f534f5fe3857b8a9df3bce1e25f66c96f666d6750490bd46e2b4f3b829 + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^4.3.2": + version: 4.3.2 + resolution: "@inquirer/checkbox@npm:4.3.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/771d23bc6b16cd5c21a4f1073e98e306147f90c0e2487fe887ee054b8bf86449f1f9e6e6f9c218c1aa45ae3be2533197d53654abe9c0545981aebb0920d5f471 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^3.1.22": + version: 3.2.0 + resolution: "@inquirer/confirm@npm:3.2.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/a2cbfc8ae9c880bba4cce1993f5c399fb0d12741fdd574917c87fceb40ece62ffa60e35aaadf4e62d7c114f54008e45aee5d6d90497bb62d493996c02725d243 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^5.1.21": + version: 5.1.21 + resolution: "@inquirer/confirm@npm:5.1.21" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/a95bbdbb17626c484735a4193ed6b6a6fbb078cf62116ec8e1667f647e534dd6618e688ecc7962585efcc56881b544b8c53db3914599bbf2ab842e7f224b0fca + languageName: node + linkType: hard + +"@inquirer/core@npm:^10.3.2": + version: 10.3.2 + resolution: "@inquirer/core@npm:10.3.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^2.0.0" + signal-exit: "npm:^4.1.0" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/f0f27e07fe288e01e3949b4ad216c19751f025ce77c610366e08d8b0f7a135d064dc074732031d251584b454c576f1e5c849e4abe259186dd5d4974c8f85c13e + languageName: node + linkType: hard + +"@inquirer/core@npm:^9.1.0": + version: 9.2.1 + resolution: "@inquirer/core@npm:9.2.1" + dependencies: + "@inquirer/figures": "npm:^1.0.6" + "@inquirer/type": "npm:^2.0.0" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^22.5.5" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/11c14be77a9fa85831de799a585721b0a49ab2f3b7d8fd1780c48ea2b29229c6bdc94e7892419086d0f7734136c2ba87b6a32e0782571eae5bbd655b1afad453 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^4.2.23": + version: 4.2.23 + resolution: "@inquirer/editor@npm:4.2.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/external-editor": "npm:^1.0.3" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/aa02028ee35ae039a4857b6a9490d295a1b3558f042e7454dee0aa36fbc83ac25586a2dfe0b46a5ea7ea151e3f5cb97a8ee6229131b4619f3b3466ad74b9519f + languageName: node + linkType: hard + +"@inquirer/expand@npm:^4.0.23": + version: 4.0.23 + resolution: "@inquirer/expand@npm:4.0.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/294c92652760c3d1a46c4b900a99fd553ea9e5734ba261d4e71d7b8499d86a8b15e38a2467ddb7c95c197daf7e472bdab209fc3f7c38cbc70842cd291f4ce39d + languageName: node + linkType: hard + +"@inquirer/external-editor@npm:^1.0.3": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.15, @inquirer/figures@npm:^1.0.5, @inquirer/figures@npm:^1.0.6": + version: 1.0.15 + resolution: "@inquirer/figures@npm:1.0.15" + checksum: 10c0/6e39a040d260ae234ae220180b7994ff852673e20be925f8aa95e78c7934d732b018cbb4d0ec39e600a410461bcb93dca771e7de23caa10630d255692e440f69 + languageName: node + linkType: hard + +"@inquirer/input@npm:^2.2.4": + version: 2.3.0 + resolution: "@inquirer/input@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10c0/44c8cea38c9192f528cae556f38709135a00230132deab3b9bb9a925375fce0513fecf4e8c1df7c4319e1ed7aa31fb4dd2c4956c8bc9dd39af087aafff5b6f1f + languageName: node + linkType: hard + +"@inquirer/input@npm:^4.3.1": + version: 4.3.1 + resolution: "@inquirer/input@npm:4.3.1" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/9e81d6ae56e5b59f96475ae1327e7e7beeef0d917b83762e0c2ed5a75239ad6b1a39fc05553ce45fe6f6de49681dade8704b5f1c11c2f555663a74d0ac998af3 + languageName: node + linkType: hard + +"@inquirer/number@npm:^3.0.23": + version: 3.0.23 + resolution: "@inquirer/number@npm:3.0.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/3944a524be2a2e0834822a0e483f2e2fd56ad597b5feeca2155b956821f88e22e07ce3816f66113b040601636ed7146865aee7d7afb2a06939acc77491330ccc + languageName: node + linkType: hard + +"@inquirer/password@npm:^4.0.23": + version: 4.0.23 + resolution: "@inquirer/password@npm:4.0.23" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/9fd3d0462d02735bb1521c4e221d057a94d9aaac308e9a192e59d6c1e8efc707c2376ab627151d589bc3633f6b14b74b60b91c3d473a32adfd100ef1f6cfdef7 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^7.10.1": + version: 7.10.1 + resolution: "@inquirer/prompts@npm:7.10.1" + dependencies: + "@inquirer/checkbox": "npm:^4.3.2" + "@inquirer/confirm": "npm:^5.1.21" + "@inquirer/editor": "npm:^4.2.23" + "@inquirer/expand": "npm:^4.0.23" + "@inquirer/input": "npm:^4.3.1" + "@inquirer/number": "npm:^3.0.23" + "@inquirer/password": "npm:^4.0.23" + "@inquirer/rawlist": "npm:^4.1.11" + "@inquirer/search": "npm:^3.2.2" + "@inquirer/select": "npm:^4.4.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/eac309cc75712bc94fc8b6761d6a736786ca1942cf9c90805b2a6049a05ce6131bcfb3aa703d1dbe66874d1b78c2b446044ad9735a2bb76743b8ddcb3dcb4d2a + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^4.1.11": + version: 4.1.11 + resolution: "@inquirer/rawlist@npm:4.1.11" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/33792b40cd0fbf77f547c9c4805087dd1188342c6a5ca512c73b0b6c4d132225fc5ae8bc4fd5035309484da3698a90fcef17aad100b9ae57624fda7b07d92227 + languageName: node + linkType: hard + +"@inquirer/search@npm:^3.2.2": + version: 3.2.2 + resolution: "@inquirer/search@npm:3.2.2" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/e7849663a51fe95e3ce99d274c815b8dc8933d6a5ddcaaf6130bf43f5f10316062c9f7a37c2923a14b8dcd09d202b0bb9cc3eaf0adb0336f6a704ea52e03ef8c + languageName: node + linkType: hard + +"@inquirer/select@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/select@npm:2.5.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/280fa700187ff29da0ad4bf32aa11db776261584ddf5cc1ceac5caebb242a4ac0c5944af522a2579d78b6ec7d6e8b1b9f6564872101abd8dcc69929b4e33fc4c + languageName: node + linkType: hard + +"@inquirer/select@npm:^4.4.2": + version: 4.4.2 + resolution: "@inquirer/select@npm:4.4.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/6978a5a92928b4d439dd6b688f2db51fd49be209f24be224bb81ed8f75b76e0715e79bdb05dab2a33bbdc7091c779a99f8603fe0ca199f059528ca2b1d0d4944 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.5.3": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/4c41736c09ba9426b5a9e44993bdd54e8f532e791518802e33866f233a2a6126a25c1c82c19d1abbf1df627e57b1b957dd3f8318ea96073d8bfc32193943bcb3 + languageName: node + linkType: hard + +"@inquirer/type@npm:^2.0.0": + version: 2.0.0 + resolution: "@inquirer/type@npm:2.0.0" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/8c663d52beb2b89a896d3c3d5cc3d6d024fa149e565555bcb42fa640cbe23fba7ff2c51445342cef1fe6e46305e2d16c1590fa1d11ad0ddf93a67b655ef41f0a + languageName: node + linkType: hard + +"@inquirer/type@npm:^3.0.10": + version: 3.0.10 + resolution: "@inquirer/type@npm:3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/a846c7a570e3bf2657d489bcc5dcdc3179d24c7323719de1951dcdb722400ac76e5b2bfe9765d0a789bc1921fac810983d7999f021f30a78a6a174c23fc78dc9 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@isaacs/string-locale-compare@npm:^1.1.0": + version: 1.1.0 + resolution: "@isaacs/string-locale-compare@npm:1.1.0" + checksum: 10c0/d67226ff7ac544a495c77df38187e69e0e3a0783724777f86caadafb306e2155dc3b5787d5927916ddd7fb4a53561ac8f705448ac3235d18ea60da5854829fdf + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/diff-sequences@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/diff-sequences@npm:30.0.1" + checksum: 10c0/3a840404e6021725ef7f86b11f7b2d13dd02846481264db0e447ee33b7ee992134e402cdc8b8b0ac969d37c6c0183044e382dedee72001cdf50cfb3c8088de74 + languageName: node + linkType: hard + +"@jest/get-type@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/get-type@npm:30.0.1" + checksum: 10c0/92437ae42d0df57e8acc2d067288151439db4752cde4f5e680c73c8a6e34568bbd8c1c81a2f2f9a637a619c2aac8bc87553fb80e31475b59e2ed789a71e5e540 + languageName: node + linkType: hard + +"@jest/schemas@npm:30.0.5": + version: 30.0.5 + resolution: "@jest/schemas@npm:30.0.5" + dependencies: + "@sinclair/typebox": "npm:^0.34.0" + checksum: 10c0/449dcd7ec5c6505e9ac3169d1143937e67044ae3e66a729ce4baf31812dfd30535f2b3b2934393c97cfdf5984ff581120e6b38f62b8560c8b5b7cc07f4175f65 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@js-joda/core@npm:^5.6.5": + version: 5.6.5 + resolution: "@js-joda/core@npm:5.6.5" + checksum: 10c0/bad9d67736eb7dfbc99f09b2f485c63c4d64cad2661648be88028e777f93769767c41d6019c0a35ba6f55a1b9846666f3528ae8746496a9f1ed9b60eade174be + languageName: node + linkType: hard + +"@lerna/create@npm:8.2.4": + version: 8.2.4 + resolution: "@lerna/create@npm:8.2.4" + dependencies: + "@npmcli/arborist": "npm:7.5.4" + "@npmcli/package-json": "npm:5.2.0" + "@npmcli/run-script": "npm:8.1.0" + "@nx/devkit": "npm:>=17.1.2 < 21" + "@octokit/plugin-enterprise-rest": "npm:6.0.1" + "@octokit/rest": "npm:20.1.2" + aproba: "npm:2.0.0" + byte-size: "npm:8.1.1" + chalk: "npm:4.1.0" + clone-deep: "npm:4.0.1" + cmd-shim: "npm:6.0.3" + color-support: "npm:1.1.3" + columnify: "npm:1.6.0" + console-control-strings: "npm:^1.1.0" + conventional-changelog-core: "npm:5.0.1" + conventional-recommended-bump: "npm:7.0.1" + cosmiconfig: "npm:9.0.0" + dedent: "npm:1.5.3" + execa: "npm:5.0.0" + fs-extra: "npm:^11.2.0" + get-stream: "npm:6.0.0" + git-url-parse: "npm:14.0.0" + glob-parent: "npm:6.0.2" + graceful-fs: "npm:4.2.11" + has-unicode: "npm:2.0.1" + ini: "npm:^1.3.8" + init-package-json: "npm:6.0.3" + inquirer: "npm:^8.2.4" + is-ci: "npm:3.0.1" + is-stream: "npm:2.0.0" + js-yaml: "npm:4.1.0" + libnpmpublish: "npm:9.0.9" + load-json-file: "npm:6.2.0" + make-dir: "npm:4.0.0" + minimatch: "npm:3.0.5" + multimatch: "npm:5.0.0" + node-fetch: "npm:2.6.7" + npm-package-arg: "npm:11.0.2" + npm-packlist: "npm:8.0.2" + npm-registry-fetch: "npm:^17.1.0" + nx: "npm:>=17.1.2 < 21" + p-map: "npm:4.0.0" + p-map-series: "npm:2.1.0" + p-queue: "npm:6.6.2" + p-reduce: "npm:^2.1.0" + pacote: "npm:^18.0.6" + pify: "npm:5.0.0" + read-cmd-shim: "npm:4.0.0" + resolve-from: "npm:5.0.0" + rimraf: "npm:^4.4.1" + semver: "npm:^7.3.4" + set-blocking: "npm:^2.0.0" + signal-exit: "npm:3.0.7" + slash: "npm:^3.0.0" + ssri: "npm:^10.0.6" + string-width: "npm:^4.2.3" + tar: "npm:6.2.1" + temp-dir: "npm:1.0.0" + through: "npm:2.3.8" + tinyglobby: "npm:0.2.12" + upath: "npm:2.0.1" + uuid: "npm:^10.0.0" + validate-npm-package-license: "npm:^3.0.4" + validate-npm-package-name: "npm:5.0.1" + wide-align: "npm:1.1.5" + write-file-atomic: "npm:5.0.1" + write-pkg: "npm:4.0.0" + yargs: "npm:17.7.2" + yargs-parser: "npm:21.1.1" + checksum: 10c0/9a3aee341523f95cd3bf0c2742baedd7ba35a97c0245ee581d0670df9b14317b2ef7c1e6820812b16974449fee9a3156e5e9259add76f760618ee6686494680f + languageName: node + linkType: hard + +"@mapbox/node-pre-gyp@npm:^1.0.5": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:0.2.4": + version: 0.2.4 + resolution: "@napi-rs/wasm-runtime@npm:0.2.4" + dependencies: + "@emnapi/core": "npm:^1.1.0" + "@emnapi/runtime": "npm:^1.1.0" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10c0/1040de49b2ef509db207e2517465dbf7fb3474f20e8ec32897672a962ff4f59872385666dac61dc9dbeae3cae5dad265d8dc3865da756adeb07d1634c67b03a1 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^0.2.9": + version: 0.2.9 + resolution: "@napi-rs/wasm-runtime@npm:0.2.9" + dependencies: + "@emnapi/core": "npm:^1.4.0" + "@emnapi/runtime": "npm:^1.4.0" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10c0/1cc40b854b255f84e12ade634456ba489f6bf90659ef8164a16823c515c294024c96ee2bb81ab51f35493ba9496f62842b960f915dbdcdc1791f221f989e9e59 + languageName: node + linkType: hard + +"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": + version: 5.1.1-v1 + resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" + dependencies: + eslint-scope: "npm:5.1.1" + checksum: 10c0/75dda3e623b8ad7369ca22552d6beee337a814b2d0e8a32d23edd13fcb65c8082b32c5d86e436f3860dd7ade30d91d5db55d4ef9a08fb5a976c718ecc0d88a74 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@nolyfill/is-core-module@npm:1.0.39": + version: 1.0.39 + resolution: "@nolyfill/is-core-module@npm:1.0.39" + checksum: 10c0/34ab85fdc2e0250879518841f74a30c276bca4f6c3e13526d2d1fe515e1adf6d46c25fcd5989d22ea056d76f7c39210945180b4859fc83b050e2da411aa86289 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + languageName: node + linkType: hard + +"@npmcli/arborist@npm:7.5.4": + version: 7.5.4 + resolution: "@npmcli/arborist@npm:7.5.4" + dependencies: + "@isaacs/string-locale-compare": "npm:^1.1.0" + "@npmcli/fs": "npm:^3.1.1" + "@npmcli/installed-package-contents": "npm:^2.1.0" + "@npmcli/map-workspaces": "npm:^3.0.2" + "@npmcli/metavuln-calculator": "npm:^7.1.1" + "@npmcli/name-from-folder": "npm:^2.0.0" + "@npmcli/node-gyp": "npm:^3.0.0" + "@npmcli/package-json": "npm:^5.1.0" + "@npmcli/query": "npm:^3.1.0" + "@npmcli/redact": "npm:^2.0.0" + "@npmcli/run-script": "npm:^8.1.0" + bin-links: "npm:^4.0.4" + cacache: "npm:^18.0.3" + common-ancestor-path: "npm:^1.0.1" + hosted-git-info: "npm:^7.0.2" + json-parse-even-better-errors: "npm:^3.0.2" + json-stringify-nice: "npm:^1.1.4" + lru-cache: "npm:^10.2.2" + minimatch: "npm:^9.0.4" + nopt: "npm:^7.2.1" + npm-install-checks: "npm:^6.2.0" + npm-package-arg: "npm:^11.0.2" + npm-pick-manifest: "npm:^9.0.1" + npm-registry-fetch: "npm:^17.0.1" + pacote: "npm:^18.0.6" + parse-conflict-json: "npm:^3.0.0" + proc-log: "npm:^4.2.0" + proggy: "npm:^2.0.0" + promise-all-reject-late: "npm:^1.0.0" + promise-call-limit: "npm:^3.0.1" + read-package-json-fast: "npm:^3.0.2" + semver: "npm:^7.3.7" + ssri: "npm:^10.0.6" + treeverse: "npm:^3.0.0" + walk-up-path: "npm:^3.0.1" + bin: + arborist: bin/index.js + checksum: 10c0/22417b804872e68b6486187bb769eabef7245c5d3fa055d5473f84a7088580543235f34af3047a0e9b357e70fccd768e8ef5c6c8664ed6909f659d07607ad955 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^1.0.0": + version: 1.1.1 + resolution: "@npmcli/fs@npm:1.1.1" + dependencies: + "@gar/promisify": "npm:^1.0.1" + semver: "npm:^7.3.5" + checksum: 10c0/4143c317a7542af9054018b71601e3c3392e6704e884561229695f099a71336cbd580df9a9ffb965d0024bf0ed593189ab58900fd1714baef1c9ee59c738c3e2 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0, @npmcli/fs@npm:^3.1.1": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + languageName: node + linkType: hard + +"@npmcli/git@npm:^5.0.0": + version: 5.0.8 + resolution: "@npmcli/git@npm:5.0.8" + dependencies: + "@npmcli/promise-spawn": "npm:^7.0.0" + ini: "npm:^4.1.3" + lru-cache: "npm:^10.0.1" + npm-pick-manifest: "npm:^9.0.0" + proc-log: "npm:^4.0.0" + promise-inflight: "npm:^1.0.1" + promise-retry: "npm:^2.0.1" + semver: "npm:^7.3.5" + which: "npm:^4.0.0" + checksum: 10c0/892441c968404950809c7b515a93b78167ea1db2252f259f390feae22a2c5477f3e1629e105e19a084c05afc56e585bf3f13c2f13b54a06bfd6786f0c8429532 + languageName: node + linkType: hard + +"@npmcli/installed-package-contents@npm:^2.0.1, @npmcli/installed-package-contents@npm:^2.1.0": + version: 2.1.0 + resolution: "@npmcli/installed-package-contents@npm:2.1.0" + dependencies: + npm-bundled: "npm:^3.0.0" + npm-normalize-package-bin: "npm:^3.0.0" + bin: + installed-package-contents: bin/index.js + checksum: 10c0/f5ecba0d45fc762f3e0d5def29fbfabd5d55e8147b01ae0a101769245c2e0038bc82a167836513a98aaed0a15c3d81fcdb232056bb8a962972a432533e518fce + languageName: node + linkType: hard + +"@npmcli/map-workspaces@npm:^3.0.2": + version: 3.0.6 + resolution: "@npmcli/map-workspaces@npm:3.0.6" + dependencies: + "@npmcli/name-from-folder": "npm:^2.0.0" + glob: "npm:^10.2.2" + minimatch: "npm:^9.0.0" + read-package-json-fast: "npm:^3.0.0" + checksum: 10c0/6bfcf8ca05ab9ddc2bd19c0fd91e9982f03cc6e67b0c03f04ba4d2f29b7d83f96e759c0f8f1f4b6dbe3182272483643a0d1269788352edd0c883d6fbfa2f3f14 + languageName: node + linkType: hard + +"@npmcli/metavuln-calculator@npm:^7.1.1": + version: 7.1.1 + resolution: "@npmcli/metavuln-calculator@npm:7.1.1" + dependencies: + cacache: "npm:^18.0.0" + json-parse-even-better-errors: "npm:^3.0.0" + pacote: "npm:^18.0.0" + proc-log: "npm:^4.1.0" + semver: "npm:^7.3.5" + checksum: 10c0/27402cab124bb1fca56af7549f730c38c0ab40de60cbef6264a4193c26c2d28cefb2adac29ed27f368031795704f9f8fe0c547c4c8cb0c0fa94d72330d56ac80 + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^1.0.1": + version: 1.1.2 + resolution: "@npmcli/move-file@npm:1.1.2" + dependencies: + mkdirp: "npm:^1.0.4" + rimraf: "npm:^3.0.2" + checksum: 10c0/02e946f3dafcc6743132fe2e0e2b585a96ca7265653a38df5a3e53fcf26c7c7a57fc0f861d7c689a23fdb6d6836c7eea5050c8086abf3c994feb2208d1514ff0 + languageName: node + linkType: hard + +"@npmcli/name-from-folder@npm:^2.0.0": + version: 2.0.0 + resolution: "@npmcli/name-from-folder@npm:2.0.0" + checksum: 10c0/1aa551771d98ab366d4cb06b33efd3bb62b609942f6d9c3bb667c10e5bb39a223d3e330022bc980a44402133e702ae67603862099ac8254dad11f90e77409827 + languageName: node + linkType: hard + +"@npmcli/node-gyp@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/node-gyp@npm:3.0.0" + checksum: 10c0/5d0ac17dacf2dd6e45312af2c1ae2749bb0730fcc82da101c37d3a4fd963a5e1c5d39781e5e1e5e5828df4ab1ad4e3fdbab1d69b7cd0abebad9983efb87df985 + languageName: node + linkType: hard + +"@npmcli/package-json@npm:5.2.0": + version: 5.2.0 + resolution: "@npmcli/package-json@npm:5.2.0" + dependencies: + "@npmcli/git": "npm:^5.0.0" + glob: "npm:^10.2.2" + hosted-git-info: "npm:^7.0.0" + json-parse-even-better-errors: "npm:^3.0.0" + normalize-package-data: "npm:^6.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.5.3" + checksum: 10c0/bdce8c7eed0dee1d272bf8ba500c4bce6d8ed2b4dd2ce43075d3ba02ffd3bb70c46dbcf8b3a35e19d9492d039b720dc3a4b30d1a2ddc30b7918e1d5232faa1f7 + languageName: node + linkType: hard + +"@npmcli/package-json@npm:^5.0.0, @npmcli/package-json@npm:^5.1.0": + version: 5.2.1 + resolution: "@npmcli/package-json@npm:5.2.1" + dependencies: + "@npmcli/git": "npm:^5.0.0" + glob: "npm:^10.2.2" + hosted-git-info: "npm:^7.0.0" + json-parse-even-better-errors: "npm:^3.0.0" + normalize-package-data: "npm:^6.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.5.3" + checksum: 10c0/b852e31e3121a0afe5fa20bbf4faa701a59dbc9d9dd7141f7fd57b8e919ce22c1285dcdfea490851fe410fa0f7bc9c397cafba0d268aaa53420a12d7c561dde1 + languageName: node + linkType: hard + +"@npmcli/promise-spawn@npm:^7.0.0": + version: 7.0.2 + resolution: "@npmcli/promise-spawn@npm:7.0.2" + dependencies: + which: "npm:^4.0.0" + checksum: 10c0/8f2af5bc2c1b1ccfb9bcd91da8873ab4723616d8bd5af877c0daa40b1e2cbfa4afb79e052611284179cae918c945a1b99ae1c565d78a355bec1a461011e89f71 + languageName: node + linkType: hard + +"@npmcli/query@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/query@npm:3.1.0" + dependencies: + postcss-selector-parser: "npm:^6.0.10" + checksum: 10c0/9a099677dd188a2d9eb7a49e32c69d315b09faea59e851b7c2013b5bda915a38434efa7295565c40a1098916c06ebfa1840f68d831180e36842f48c24f4c5186 + languageName: node + linkType: hard + +"@npmcli/redact@npm:^2.0.0": + version: 2.0.1 + resolution: "@npmcli/redact@npm:2.0.1" + checksum: 10c0/5f346f7ef224b44c90009939f93c446a865a3d9e5a7ebe0246cdb0ebd03219de3962ee6c6e9197298d8c6127ea33535e8c44814276e4941394dc1cdf1f30f6bc + languageName: node + linkType: hard + +"@npmcli/run-script@npm:8.1.0, @npmcli/run-script@npm:^8.0.0, @npmcli/run-script@npm:^8.1.0": + version: 8.1.0 + resolution: "@npmcli/run-script@npm:8.1.0" + dependencies: + "@npmcli/node-gyp": "npm:^3.0.0" + "@npmcli/package-json": "npm:^5.0.0" + "@npmcli/promise-spawn": "npm:^7.0.0" + node-gyp: "npm:^10.0.0" + proc-log: "npm:^4.0.0" + which: "npm:^4.0.0" + checksum: 10c0/f9f40ecff0406a9ce1b77c9f714fc7c71b561289361efc6e2e0e48ca2d630aa98d277cbbf269750f9467a40eaaac79e78766d67c458046aa9507c8c354650fee + languageName: node + linkType: hard + +"@nx/devkit@npm:>=17.1.2 < 21": + version: 20.8.1 + resolution: "@nx/devkit@npm:20.8.1" + dependencies: + ejs: "npm:^3.1.7" + enquirer: "npm:~2.3.6" + ignore: "npm:^5.0.4" + minimatch: "npm:9.0.3" + semver: "npm:^7.5.3" + tmp: "npm:~0.2.1" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + peerDependencies: + nx: ">= 19 <= 21" + checksum: 10c0/e93664c8603bbfdcc26beb31dfe382c422cdb0968d9e5723a1f30fd61828a7d48c032afbe2095fecef329847851d05af205be0938ce82e92810514597f7d2d21 + languageName: node + linkType: hard + +"@nx/nx-darwin-arm64@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-darwin-arm64@npm:20.8.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@nx/nx-darwin-arm64@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-darwin-arm64@npm:21.6.10" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@nx/nx-darwin-x64@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-darwin-x64@npm:20.8.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@nx/nx-darwin-x64@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-darwin-x64@npm:21.6.10" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@nx/nx-freebsd-x64@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-freebsd-x64@npm:20.8.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@nx/nx-freebsd-x64@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-freebsd-x64@npm:21.6.10" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@nx/nx-linux-arm-gnueabihf@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-linux-arm-gnueabihf@npm:20.8.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@nx/nx-linux-arm-gnueabihf@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-linux-arm-gnueabihf@npm:21.6.10" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@nx/nx-linux-arm64-gnu@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-linux-arm64-gnu@npm:20.8.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@nx/nx-linux-arm64-gnu@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-linux-arm64-gnu@npm:21.6.10" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@nx/nx-linux-arm64-musl@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-linux-arm64-musl@npm:20.8.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@nx/nx-linux-arm64-musl@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-linux-arm64-musl@npm:21.6.10" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@nx/nx-linux-x64-gnu@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-linux-x64-gnu@npm:20.8.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@nx/nx-linux-x64-gnu@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-linux-x64-gnu@npm:21.6.10" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@nx/nx-linux-x64-musl@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-linux-x64-musl@npm:20.8.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@nx/nx-linux-x64-musl@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-linux-x64-musl@npm:21.6.10" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@nx/nx-win32-arm64-msvc@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-win32-arm64-msvc@npm:20.8.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@nx/nx-win32-arm64-msvc@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-win32-arm64-msvc@npm:21.6.10" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@nx/nx-win32-x64-msvc@npm:20.8.2": + version: 20.8.2 + resolution: "@nx/nx-win32-x64-msvc@npm:20.8.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@nx/nx-win32-x64-msvc@npm:21.6.10": + version: 21.6.10 + resolution: "@nx/nx-win32-x64-msvc@npm:21.6.10" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@oclif/core@npm:^4, @oclif/core@npm:^4.8.0": + version: 4.8.0 + resolution: "@oclif/core@npm:4.8.0" + dependencies: + ansi-escapes: "npm:^4.3.2" + ansis: "npm:^3.17.0" + clean-stack: "npm:^3.0.1" + cli-spinners: "npm:^2.9.2" + debug: "npm:^4.4.3" + ejs: "npm:^3.1.10" + get-package-type: "npm:^0.1.0" + indent-string: "npm:^4.0.0" + is-wsl: "npm:^2.2.0" + lilconfig: "npm:^3.1.3" + minimatch: "npm:^9.0.5" + semver: "npm:^7.7.3" + string-width: "npm:^4.2.3" + supports-color: "npm:^8" + tinyglobby: "npm:^0.2.14" + widest-line: "npm:^3.1.0" + wordwrap: "npm:^1.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/83347c86250390b9c53d7eeb4f85adcc57578827b2de65be606b609290f4101f24b4245bd9c1dbeaca10a5e29c3fc68eaa3b5efa5f801d222e4f8e59691e8a86 + languageName: node + linkType: hard + +"@oclif/plugin-help@npm:^6.2.36": + version: 6.2.36 + resolution: "@oclif/plugin-help@npm:6.2.36" + dependencies: + "@oclif/core": "npm:^4" + checksum: 10c0/d4517e99c863b919d535cb3a23e48fd37091026055ed6d51b51e9460032e75f801209c1988c86b39011c07476255520920ca366eca8235129781cb01ca4bf440 + languageName: node + linkType: hard + +"@oclif/plugin-not-found@npm:^3.2.73": + version: 3.2.73 + resolution: "@oclif/plugin-not-found@npm:3.2.73" + dependencies: + "@inquirer/prompts": "npm:^7.10.1" + "@oclif/core": "npm:^4.8.0" + ansis: "npm:^3.17.0" + fast-levenshtein: "npm:^3.0.0" + checksum: 10c0/54a6c27f418ce081cd656687e846bde5dbcc7724dc197af153d6ff702a640c6f9cdad9416f4304cfbfed9bdd2c93c93eed9fb0835bf7d4a8c82dc18313b02b38 + languageName: node + linkType: hard + +"@oclif/plugin-warn-if-update-available@npm:^3.1.53": + version: 3.1.53 + resolution: "@oclif/plugin-warn-if-update-available@npm:3.1.53" + dependencies: + "@oclif/core": "npm:^4" + ansis: "npm:^3.17.0" + debug: "npm:^4.4.3" + http-call: "npm:^5.2.2" + lodash: "npm:^4.17.21" + registry-auth-token: "npm:^5.1.0" + checksum: 10c0/f90fceaae7ee9b0a6c2cd85549c7454d867e22fc6f4ee1d359a68a88d881136322b567d79173b8db790e4ad5e0bc2ec2052ef75c75df7610fd59871f12601271 + languageName: node + linkType: hard + +"@oclif/test@npm:4.1.15": + version: 4.1.15 + resolution: "@oclif/test@npm:4.1.15" + dependencies: + ansis: "npm:^3.17.0" + debug: "npm:^4.4.3" + peerDependencies: + "@oclif/core": ">= 3.0.0" + checksum: 10c0/e2405b20ae0e315acf7c1ce836b0bd6269df7233e4e469928b7c4ddd568050a12ab438352f1811924d210a8f6d089fad3dde957f262ae7a045741ea2aab6c608 + languageName: node + linkType: hard + +"@octokit/auth-token@npm:^4.0.0": + version: 4.0.0 + resolution: "@octokit/auth-token@npm:4.0.0" + checksum: 10c0/57acaa6c394c5abab2f74e8e1dcf4e7a16b236f713c77a54b8f08e2d14114de94b37946259e33ec2aab0566b26f724c2b71d2602352b59e541a9854897618f3c + languageName: node + linkType: hard + +"@octokit/core@npm:^5.0.2": + version: 5.2.1 + resolution: "@octokit/core@npm:5.2.1" + dependencies: + "@octokit/auth-token": "npm:^4.0.0" + "@octokit/graphql": "npm:^7.1.0" + "@octokit/request": "npm:^8.4.1" + "@octokit/request-error": "npm:^5.1.1" + "@octokit/types": "npm:^13.0.0" + before-after-hook: "npm:^2.2.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/9759c70a6a6477a636f336d717657761243bab0e9d34c4012a8b2d70aafd89ba3d24289fb7e05352999c6ec526fe572b8aff9ad59e90761842fb72fb7d59ed95 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^9.0.6": + version: 9.0.6 + resolution: "@octokit/endpoint@npm:9.0.6" + dependencies: + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/8e06197b21869aeb498e0315093ca6fbee12bd1bdcfc1667fcd7d79d827d84f2c5a30702ffd28bba7879780e367d14c30df5b20d47fcaed5de5fdc05f5d4e013 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^7.1.0": + version: 7.1.1 + resolution: "@octokit/graphql@npm:7.1.1" + dependencies: + "@octokit/request": "npm:^8.4.1" + "@octokit/types": "npm:^13.0.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/c27216200f3f4ce7ce2a694fb7ea43f8ea4a807fbee3a423c41ed137dd7948dfc0bbf6ea1656f029a7625c84b583acdef740a7032266d0eff55305c91c3a1ed6 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^24.2.0": + version: 24.2.0 + resolution: "@octokit/openapi-types@npm:24.2.0" + checksum: 10c0/8f47918b35e9b7f6109be6f7c8fc3a64ad13a48233112b29e92559e64a564b810eb3ebf81b4cd0af1bb2989d27b9b95cca96e841ec4e23a3f68703cefe62fd9e + languageName: node + linkType: hard + +"@octokit/plugin-enterprise-rest@npm:6.0.1": + version: 6.0.1 + resolution: "@octokit/plugin-enterprise-rest@npm:6.0.1" + checksum: 10c0/26bd0a30582954efcd29b41e16698db79e9d20e3f88c4069b43b183223cee69862621f18b6a7a1c9257b1cd07c24477e403b75c74688660ecf31d467b9d8fd9e + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2": + version: 11.4.4-cjs.2 + resolution: "@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2" + dependencies: + "@octokit/types": "npm:^13.7.0" + peerDependencies: + "@octokit/core": 5 + checksum: 10c0/1d61a63c98a18c171bccdc6cf63ffe279fe852e8bdc9db6647ffcb27f4ea485fdab78fb71b552ed0f2186785cf5264f8ed3f9a8f33061e4697b5f73b097accb1 + languageName: node + linkType: hard + +"@octokit/plugin-request-log@npm:^4.0.0": + version: 4.0.1 + resolution: "@octokit/plugin-request-log@npm:4.0.1" + peerDependencies: + "@octokit/core": 5 + checksum: 10c0/6f556f86258c5fbff9b1821075dc91137b7499f2ad0fd12391f0876064a6daa88abe1748336b2d483516505771d358aa15cb4bcdabc348a79e3d951fe9726798 + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1": + version: 13.3.2-cjs.1 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1" + dependencies: + "@octokit/types": "npm:^13.8.0" + peerDependencies: + "@octokit/core": ^5 + checksum: 10c0/810fe5cb1861386746bf0218ea969d87c56e553ff339490526483b4b66f53c4b4c6092034bec30c5d453172eb6f33e75b5748ade1b401b76774b5a994e2c10b0 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^5.1.1": + version: 5.1.1 + resolution: "@octokit/request-error@npm:5.1.1" + dependencies: + "@octokit/types": "npm:^13.1.0" + deprecation: "npm:^2.0.0" + once: "npm:^1.4.0" + checksum: 10c0/dc9fc76ea5e4199273e4665ce9ddf345fe8f25578d9999c9a16f276298e61ee6fe0e6f5a6147b91ba3b34fdf5b9e6b7af6ae13d6333175e95b30c574088f7a2d + languageName: node + linkType: hard + +"@octokit/request@npm:^8.4.1": + version: 8.4.1 + resolution: "@octokit/request@npm:8.4.1" + dependencies: + "@octokit/endpoint": "npm:^9.0.6" + "@octokit/request-error": "npm:^5.1.1" + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/1a69dcb7336de708a296db9e9a58040e5b284a87495a63112f80eb0007da3fc96a9fadecb9e875fc63cf179c23a0f81031fbef2a6f610a219e45805ead03fcf3 + languageName: node + linkType: hard + +"@octokit/rest@npm:20.1.2": + version: 20.1.2 + resolution: "@octokit/rest@npm:20.1.2" + dependencies: + "@octokit/core": "npm:^5.0.2" + "@octokit/plugin-paginate-rest": "npm:11.4.4-cjs.2" + "@octokit/plugin-request-log": "npm:^4.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:13.3.2-cjs.1" + checksum: 10c0/712e08c43c7af37c5c219f95ae289b3ac2646270be4e8a7141fa2aa9340ed8f7134f117c9467e89206c5a9797c49c8d2c039b884d4865bb3bde91bc5adb3c38c + languageName: node + linkType: hard + +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.7.0, @octokit/types@npm:^13.8.0": + version: 13.10.0 + resolution: "@octokit/types@npm:13.10.0" + dependencies: + "@octokit/openapi-types": "npm:^24.2.0" + checksum: 10c0/f66a401b89d653ec28e5c1529abdb7965752db4d9d40fa54c80e900af4c6bf944af6bd0a83f5b4f1eecb72e3d646899dfb27ffcf272ac243552de7e3b97a038d + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@pnpm/config.env-replace@npm:^1.1.0": + version: 1.1.0 + resolution: "@pnpm/config.env-replace@npm:1.1.0" + checksum: 10c0/4cfc4a5c49ab3d0c6a1f196cfd4146374768b0243d441c7de8fa7bd28eaab6290f514b98490472cc65dbd080d34369447b3e9302585e1d5c099befd7c8b5e55f + languageName: node + linkType: hard + +"@pnpm/network.ca-file@npm:^1.0.1": + version: 1.0.2 + resolution: "@pnpm/network.ca-file@npm:1.0.2" + dependencies: + graceful-fs: "npm:4.2.10" + checksum: 10c0/95f6e0e38d047aca3283550719155ce7304ac00d98911e4ab026daedaf640a63bd83e3d13e17c623fa41ac72f3801382ba21260bcce431c14fbbc06430ecb776 + languageName: node + linkType: hard + +"@pnpm/npm-conf@npm:^2.1.0": + version: 2.3.1 + resolution: "@pnpm/npm-conf@npm:2.3.1" + dependencies: + "@pnpm/config.env-replace": "npm:^1.1.0" + "@pnpm/network.ca-file": "npm:^1.0.1" + config-chain: "npm:^1.1.11" + checksum: 10c0/778a3a34ff7d6000a2594d2a9821f873f737bc56367865718b2cf0ba5d366e49689efe7975148316d7afd8e6f1dcef7d736fbb6ea7ef55caadd1dc93a36bb302 + languageName: node + linkType: hard + +"@rtsao/scc@npm:^1.1.0": + version: 1.1.0 + resolution: "@rtsao/scc@npm:1.1.0" + checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b + languageName: node + linkType: hard + +"@rushstack/eslint-patch@npm:1.15.0": + version: 1.15.0 + resolution: "@rushstack/eslint-patch@npm:1.15.0" + checksum: 10c0/b2aeae0c6228981c40eff7a3cf9fc1c4342f8fc7a0102d8b2b3d3f66137461b1cd2e3c22d9aa6bcde43f227c5e4e698be33ac145d356797774f212da496c0e9c + languageName: node + linkType: hard + +"@sequelize/cli@workspace:packages/cli": + version: 0.0.0-use.local + resolution: "@sequelize/cli@workspace:packages/cli" + dependencies: + "@inquirer/checkbox": "npm:^4.3.2" + "@inquirer/confirm": "npm:^5.1.21" + "@inquirer/input": "npm:^4.3.1" + "@inquirer/select": "npm:^4.4.2" + "@oclif/core": "npm:^4.8.0" + "@oclif/plugin-help": "npm:^6.2.36" + "@oclif/test": "npm:4.1.15" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/mocha": "npm:10.0.10" + ansis: "npm:^3.17.0" + chai: "npm:4.5.0" + concurrently: "npm:9.2.1" + cosmiconfig: "npm:^9.0.0" + mocha: "npm:11.7.5" + oclif: "npm:4.22.59" + rimraf: "npm:5.0.10" + zod: "npm:^4.2.1" + bin: + sequelize: ./bin/run.js + languageName: unknown + linkType: soft + +"@sequelize/core@workspace:*, @sequelize/core@workspace:packages/core": + version: 0.0.0-use.local + resolution: "@sequelize/core@workspace:packages/core" + dependencies: + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/chai-as-promised": "npm:7.1.8" + "@types/chai-datetime": "npm:1.0.0" + "@types/debug": "npm:^4.1.12" + "@types/lodash": "npm:4.17.21" + "@types/mocha": "npm:10.0.10" + "@types/semver": "npm:7.7.1" + "@types/sinon": "npm:17.0.4" + "@types/sinon-chai": "npm:3.2.12" + "@types/validator": "npm:^13.15.10" + ansis: "npm:^3.17.0" + bnf-parser: "npm:^3.1.6" + chai: "npm:4.5.0" + chai-as-promised: "npm:7.1.2" + chai-datetime: "npm:1.8.1" + dayjs: "npm:^1.11.19" + debug: "npm:^4.4.3" + delay: "npm:5.0.0" + dottie: "npm:^2.0.6" + expect-type: "npm:0.13.0" + fast-glob: "npm:^3.3.3" + fs-jetpack: "npm:5.1.0" + inflection: "npm:^3.0.2" + lcov-result-merger: "npm:5.0.1" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + moment: "npm:2.30.1" + nyc: "npm:17.1.0" + p-map: "npm:4.0.0" + p-props: "npm:4.0.0" + p-settle: "npm:4.1.1" + p-timeout: "npm:4.1.0" + retry-as-promised: "npm:^7.1.1" + rimraf: "npm:5.0.10" + semver: "npm:^7.7.3" + sequelize-pool: "npm:^8.0.1" + sinon: "npm:18.0.1" + sinon-chai: "npm:3.7.0" + toposort-class: "npm:^1.0.1" + type-fest: "npm:^4.41.0" + uuid: "npm:^11.1.0" + validator: "npm:^13.15.26" + languageName: unknown + linkType: soft + +"@sequelize/db2-ibmi@workspace:packages/ibmi": + version: 0.0.0-use.local + resolution: "@sequelize/db2-ibmi@workspace:packages/ibmi" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + dayjs: "npm:^1.11.19" + lodash: "npm:^4.17.21" + odbc: "npm:^2.4.9" + languageName: unknown + linkType: soft + +"@sequelize/db2@workspace:packages/db2": + version: 0.0.0-use.local + resolution: "@sequelize/db2@workspace:packages/db2" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + dayjs: "npm:^1.11.19" + ibm_db: "npm:^3.3.4" + lodash: "npm:^4.17.21" + languageName: unknown + linkType: soft + +"@sequelize/mariadb@workspace:packages/mariadb": + version: 0.0.0-use.local + resolution: "@sequelize/mariadb@workspace:packages/mariadb" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/mocha": "npm:10.0.10" + chai: "npm:4.5.0" + dayjs: "npm:^1.11.19" + lodash: "npm:^4.17.21" + mariadb: "npm:^3.4.5" + mocha: "npm:11.7.5" + semver: "npm:^7.7.3" + wkx: "npm:^0.5.0" + languageName: unknown + linkType: soft + +"@sequelize/monorepo@workspace:.": + version: 0.0.0-use.local + resolution: "@sequelize/monorepo@workspace:." + dependencies: + "@ephys/eslint-config-typescript": "npm:20.1.4" + "@rushstack/eslint-patch": "npm:1.15.0" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/lodash": "npm:4.17.21" + "@types/mocha": "npm:10.0.10" + "@types/node": "npm:22.19.3" + chai: "npm:4.5.0" + concurrently: "npm:9.2.1" + cross-env: "npm:10.1.0" + esbuild: "npm:0.27.2" + eslint: "npm:8.57.1" + eslint-plugin-jsdoc: "npm:61.4.2" + eslint-plugin-mocha: "npm:10.5.0" + fast-glob: "npm:3.3.3" + husky: "npm:9.1.7" + lerna: "npm:8.2.4" + lint-staged: "npm:16.2.7" + lodash: "npm:4.17.21" + markdownlint-cli: "npm:0.47.0" + mocha: "npm:11.7.5" + node-gyp: "npm:11.5.0" + node-hook: "npm:1.0.0" + nx: "npm:21.6.10" + prettier: "npm:3.5.3" + prettier-plugin-organize-imports: "npm:4.3.0" + source-map-support: "npm:0.5.21" + ts-node: "npm:10.9.2" + typedoc: "npm:0.27.9" + typedoc-plugin-mdn-links: "npm:5.0.10" + typedoc-plugin-missing-exports: "npm:3.1.0" + typescript: "npm:5.8.3" + languageName: unknown + linkType: soft + +"@sequelize/mssql@workspace:packages/mssql": + version: 0.0.0-use.local + resolution: "@sequelize/mssql@workspace:packages/mssql" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/mocha": "npm:10.0.10" + "@types/sinon": "npm:17.0.4" + chai: "npm:4.5.0" + dayjs: "npm:^1.11.19" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + sinon: "npm:18.0.1" + tedious: "npm:^19.1.2" + languageName: unknown + linkType: soft + +"@sequelize/mysql@workspace:packages/mysql": + version: 0.0.0-use.local + resolution: "@sequelize/mysql@workspace:packages/mysql" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/mocha": "npm:10.0.10" + chai: "npm:4.5.0" + dayjs: "npm:^1.11.19" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + mysql2: "npm:^3.16.0" + wkx: "npm:^0.5.0" + languageName: unknown + linkType: soft + +"@sequelize/postgres@workspace:packages/postgres": + version: 0.0.0-use.local + resolution: "@sequelize/postgres@workspace:packages/postgres" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/mocha": "npm:10.0.10" + "@types/pg": "npm:^8.11.14" + chai: "npm:4.5.0" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + pg: "npm:^8.15.6" + pg-hstore: "npm:^2.3.4" + pg-types: "npm:^4.1.0" + postgres-array: "npm:^3.0.4" + semver: "npm:^7.7.3" + wkx: "npm:^0.5.0" + languageName: unknown + linkType: soft + +"@sequelize/snowflake@workspace:packages/snowflake": + version: 0.0.0-use.local + resolution: "@sequelize/snowflake@workspace:packages/snowflake" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + lodash: "npm:^4.17.21" + snowflake-sdk: "npm:^2.1.0" + languageName: unknown + linkType: soft + +"@sequelize/sqlite3@workspace:*, @sequelize/sqlite3@workspace:packages/sqlite3": + version: 0.0.0-use.local + resolution: "@sequelize/sqlite3@workspace:packages/sqlite3" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/utils": "workspace:*" + lodash: "npm:^4.17.21" + sqlite3: "npm:^5.1.7" + languageName: unknown + linkType: soft + +"@sequelize/utils@workspace:*, @sequelize/utils@workspace:packages/utils": + version: 0.0.0-use.local + resolution: "@sequelize/utils@workspace:packages/utils" + dependencies: + "@types/chai": "npm:4.3.20" + "@types/lodash": "npm:^4.17.21" + "@types/mocha": "npm:10.0.10" + chai: "npm:4.5.0" + concurrently: "npm:9.2.1" + expect-type: "npm:0.19.0" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + languageName: unknown + linkType: soft + +"@sequelize/validator.js@workspace:packages/validator-js": + version: 0.0.0-use.local + resolution: "@sequelize/validator.js@workspace:packages/validator-js" + dependencies: + "@sequelize/core": "workspace:*" + "@sequelize/sqlite3": "workspace:*" + "@types/chai": "npm:4.3.20" + "@types/chai-as-promised": "npm:7.1.8" + chai: "npm:4.5.0" + chai-as-promised: "npm:7.1.2" + lodash: "npm:^4.17.21" + mocha: "npm:11.7.5" + languageName: unknown + linkType: soft + +"@shikijs/engine-oniguruma@npm:^1.27.2": + version: 1.29.2 + resolution: "@shikijs/engine-oniguruma@npm:1.29.2" + dependencies: + "@shikijs/types": "npm:1.29.2" + "@shikijs/vscode-textmate": "npm:^10.0.1" + checksum: 10c0/87d77e05af7fe862df40899a7034cbbd48d3635e27706873025e5035be578584d012f850208e97ca484d5e876bf802d4e23d0394d25026adb678eeb1d1f340ff + languageName: node + linkType: hard + +"@shikijs/types@npm:1.29.2, @shikijs/types@npm:^1.27.2": + version: 1.29.2 + resolution: "@shikijs/types@npm:1.29.2" + dependencies: + "@shikijs/vscode-textmate": "npm:^10.0.1" + "@types/hast": "npm:^3.0.4" + checksum: 10c0/37b4ac315effc03e7185aca1da0c2631ac55bdf613897476bd1d879105c41f86ccce6ebd0b78779513d88cc2ee371039f7efd95d604f77f21f180791978822b3 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.1": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10c0/36b682d691088ec244de292dc8f91b808f95c89466af421cf84cbab92230f03c8348649c14b3251991b10ce632b0c715e416e992dd5f28ff3221dc2693fd9462 + languageName: node + linkType: hard + +"@sigstore/bundle@npm:^2.3.2": + version: 2.3.2 + resolution: "@sigstore/bundle@npm:2.3.2" + dependencies: + "@sigstore/protobuf-specs": "npm:^0.3.2" + checksum: 10c0/872a95928236bd9950a2ecc66af1c60a82f6b482a62a20d0f817392d568a60739a2432cad70449ac01e44e9eaf85822d6d9ebc6ade6cb3e79a7d62226622eb5d + languageName: node + linkType: hard + +"@sigstore/core@npm:^1.0.0, @sigstore/core@npm:^1.1.0": + version: 1.1.0 + resolution: "@sigstore/core@npm:1.1.0" + checksum: 10c0/3b3420c1bd17de0371e1ac7c8f07a2cbcd24d6b49ace5bbf2b63f559ee08c4a80622a4d1c0ae42f2c9872166e9cb111f33f78bff763d47e5ef1efc62b8e457ea + languageName: node + linkType: hard + +"@sigstore/protobuf-specs@npm:^0.3.2": + version: 0.3.3 + resolution: "@sigstore/protobuf-specs@npm:0.3.3" + checksum: 10c0/e0a68795fa19e437fca2c3993e5a57e989642d65434beda54b29017c1629176cc8abeb81bb1e0923259cdfb19fe1fee6f1b8680a8f8240dc14c7a7de2bbae7af + languageName: node + linkType: hard + +"@sigstore/sign@npm:^2.3.2": + version: 2.3.2 + resolution: "@sigstore/sign@npm:2.3.2" + dependencies: + "@sigstore/bundle": "npm:^2.3.2" + "@sigstore/core": "npm:^1.0.0" + "@sigstore/protobuf-specs": "npm:^0.3.2" + make-fetch-happen: "npm:^13.0.1" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + checksum: 10c0/a1e7908f3e4898f04db4d713fa10ddb3ae4f851592c9b554f1269073211e1417528b5088ecee60f27039fde5a5426ae573481d77cfd7e4395d2a0ddfcf5f365f + languageName: node + linkType: hard + +"@sigstore/tuf@npm:^2.3.4": + version: 2.3.4 + resolution: "@sigstore/tuf@npm:2.3.4" + dependencies: + "@sigstore/protobuf-specs": "npm:^0.3.2" + tuf-js: "npm:^2.2.1" + checksum: 10c0/97839882d787196517933df5505fae4634975807cc7adcd1783c7840c2a9729efb83ada47556ec326d544b9cb0d1851af990dc46eebb5fe7ea17bf7ce1fc0b8c + languageName: node + linkType: hard + +"@sigstore/verify@npm:^1.2.1": + version: 1.2.1 + resolution: "@sigstore/verify@npm:1.2.1" + dependencies: + "@sigstore/bundle": "npm:^2.3.2" + "@sigstore/core": "npm:^1.1.0" + "@sigstore/protobuf-specs": "npm:^0.3.2" + checksum: 10c0/af06580a8d5357c31259da1ac7323137054e0ac41e933278d95a4bc409a4463620125cb4c00b502f6bc32fdd68c2293019391b0d31ed921ee3852a9e84358628 + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.34.0": + version: 0.34.38 + resolution: "@sinclair/typebox@npm:0.34.38" + checksum: 10c0/c1b9a1547c64de01ff5c89351baf289d2d5f19cfef3ae30fe4748a103eb58d0842618318543cd3de964cb0370d5a859e24aba231ade9b43ee2ef4d0bb4db7084 + languageName: node + linkType: hard + +"@sindresorhus/base62@npm:^1.0.0": + version: 1.0.0 + resolution: "@sindresorhus/base62@npm:1.0.0" + checksum: 10c0/9a14df0f058fdf4731c30f0f05728a4822144ee42236030039d7fa5a1a1072c2879feba8091fd4a17c8922d1056bc07bada77c31fddc3e15836fc05a266fd918 + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^5.2.0": + version: 5.6.0 + resolution: "@sindresorhus/is@npm:5.6.0" + checksum: 10c0/66727344d0c92edde5760b5fd1f8092b717f2298a162a5f7f29e4953e001479927402d9d387e245fb9dc7d3b37c72e335e93ed5875edfc5203c53be8ecba1b52 + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:11.2.2": + version: 11.2.2 + resolution: "@sinonjs/fake-timers@npm:11.2.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/a4218efa6fdafda622d02d4c0a6ab7df3641cb038bb0b14f0a3ee56f50c95aab4f1ab2d7798ce928b40c6fc1839465a558c9393a77e4dca879e1b2f8d60d8136 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10c0/a707476efd523d2138ef6bba916c83c4a377a8372ef04fad87499458af9f01afc58f4f245c5fd062793d6d70587309330c6f96947b5bd5697961c18004dc3e26 + languageName: node + linkType: hard + +"@sinonjs/samsam@npm:^8.0.0": + version: 8.0.2 + resolution: "@sinonjs/samsam@npm:8.0.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + lodash.get: "npm:^4.4.2" + type-detect: "npm:^4.1.0" + checksum: 10c0/31d74c415040161f2963a202d7f866bedbb5a9b522a74b08a17086c15a75c3ef2893eecebb0c65a7b1603ef4ebdf83fa73cbe384b4cd679944918ed833200443 + languageName: node + linkType: hard + +"@sinonjs/text-encoding@npm:^0.7.3": + version: 0.7.3 + resolution: "@sinonjs/text-encoding@npm:0.7.3" + checksum: 10c0/b112d1e97af7f99fbdc63c7dbcd35d6a60764dfec85cfcfff532e55cce8ecd8453f9fa2139e70aea47142c940fd90cd201d19f370b9a0141700d8a6de3116815 + languageName: node + linkType: hard + +"@smithy/abort-controller@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/abort-controller@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4f992bdff9f035a62c1403da1999e0170f8703a4ad0c7fbc93bc992d4ffcb20d12cebf40ad6dc006c7f0a7e80253646a147ee64ca29266dd7e52800f0ebf93fe + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader-native@npm:^4.2.1": + version: 4.2.1 + resolution: "@smithy/chunked-blob-reader-native@npm:4.2.1" + dependencies: + "@smithy/util-base64": "npm:^4.3.0" + tslib: "npm:^2.6.2" + checksum: 10c0/63831fe47a5b3a1ea6821846a5fb009298da57159e4818238e8110b77245805c1a07cb854df7955a39de1f5f2dfb7c8803ac942117e622665e089d715cb2041c + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader@npm:^5.2.0": + version: 5.2.0 + resolution: "@smithy/chunked-blob-reader@npm:5.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/9fe95b788e022ce2b59c8cab607c8f71d73cce367329871d2a7eafdc0d77cec8d1939fe8141f446bbe4051dcfffce864a562762ac2691c368df3b6c2f6ed62b3 + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^4.4.5": + version: 4.4.5 + resolution: "@smithy/config-resolver@npm:4.4.5" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/0a7c365bc50e82c9e22897b26cafe1d2a176b425a1303ff55fd5bd5f851e85534e7147d2a1408328dc6ca29f535143eab3289a39d03969e924302226711c0d55 + languageName: node + linkType: hard + +"@smithy/core@npm:^3.20.0": + version: 3.20.0 + resolution: "@smithy/core@npm:3.20.0" + dependencies: + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/uuid": "npm:^1.1.0" + tslib: "npm:^2.6.2" + checksum: 10c0/70ef9659b831573a27f68f689658090540da281459c29f61174e3162f2eae10c1dd2c5d959149b8ca00419d199e1de2aebc2b2810a0d009336ebf3a88f7df5b3 + languageName: node + linkType: hard + +"@smithy/credential-provider-imds@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/credential-provider-imds@npm:4.2.7" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/5c190b46879a9ce12c73099db4fd302089de79b5efd4177be256faa096778817cb9bc8e682f01abe397482ed90b00a726888630aecaaed47c2e3214169a23351 + languageName: node + linkType: hard + +"@smithy/eventstream-codec@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-codec@npm:4.2.7" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4388969ead1da4d657e3b05d7a031ad80b54c7e5635d1f6b1c1e3f56ffaeebf928a157b7c7aa886249f223fb0ee8c1b020bf9ab2a08cfdb0840066f40348272a + languageName: node + linkType: hard + +"@smithy/eventstream-serde-browser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-browser@npm:4.2.7" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/76f9f5e9aa3dd0e1162fe66e516d1a6b740f6c0119c8e2a247e24636d58dad7887f233c57dafcdb0842b3b5edee7195aab9bd6dbd653bfeab779c358be0e1645 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-config-resolver@npm:^4.3.7": + version: 4.3.7 + resolution: "@smithy/eventstream-serde-config-resolver@npm:4.3.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/3aac405fae327db2fd2d588e5970e656ee385abaae3612a6c56418a14a291054916d36f409e69f2d61b96ebe615d3130310c33f28fa4c35b653e839927b6626d + languageName: node + linkType: hard + +"@smithy/eventstream-serde-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-node@npm:4.2.7" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/bdbb3372508c45215f4503195d7a60a09ad0433742d90a4b3fcfc415d206e9cca6687ca362131c3d8454629c80154f54bcebef66121a6874a25a41b81b3d8878 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-universal@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-universal@npm:4.2.7" + dependencies: + "@smithy/eventstream-codec": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/05d0f1d1b03c0d4d5b688508e2c04e4197b53c1ed7da4d68c1161b9b5ec54bc83ffff90d02e400753e6f490c4fe90f7d1ae74c6be0fd4dc1d64499365f221fe8 + languageName: node + linkType: hard + +"@smithy/fetch-http-handler@npm:^5.3.8": + version: 5.3.8 + resolution: "@smithy/fetch-http-handler@npm:5.3.8" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/querystring-builder": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + tslib: "npm:^2.6.2" + checksum: 10c0/94ca27084fe0aa1626f5dec3755811d61bb7ec81c0a3d9428c324b238495e695f568800e30fdb127129fba95625355d8c51cbcae52796a008c7cfd4ff5074cb5 + languageName: node + linkType: hard + +"@smithy/hash-blob-browser@npm:^4.2.8": + version: 4.2.8 + resolution: "@smithy/hash-blob-browser@npm:4.2.8" + dependencies: + "@smithy/chunked-blob-reader": "npm:^5.2.0" + "@smithy/chunked-blob-reader-native": "npm:^4.2.1" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/69fc710a64151c1b1bc585cd92bcb2c4a8434ecb9e65ccae13503a3deef1c2e061213eb151e7b5eb079eb7cdda6d2c5fcc6eb8822fe12253ff974eb52aea8c8d + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/hash-node@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/fa3b2194c22dd240b8dcfc191ca68ed563513fc7e852537eb69223933e70a50b365fb53d1a150a37a091cf6d449b4b7aecaa51892b9f49fd3763174e27e1ec5c + languageName: node + linkType: hard + +"@smithy/hash-stream-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/hash-stream-node@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/7d29f2feb91636242f6a1c0f6365584861de47803e778217c3e3d37091d6a2bb9ef9d4fdf1a8d55d9d9332360c21bc7ddbd8b17ece0affe8b9637545a9a9d9aa + languageName: node + linkType: hard + +"@smithy/invalid-dependency@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/invalid-dependency@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/eadbdd4e7dd94f7caa8c17c003e4c48ef03ff2af0401fab3884468535b016cf318c95e57cdad2b170cb852119303e5500f3bb138635705e8a4d6a2fc58a111ed + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/is-array-buffer@npm:2.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/2f2523cd8cc4538131e408eb31664983fecb0c8724956788b015aaf3ab85a0c976b50f4f09b176f1ed7bbe79f3edf80743be7a80a11f22cd9ce1285d77161aaf + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/is-array-buffer@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/8e3e21cff5929d627bbf4a9beded28bd54555cfd37772226290964af6950cc10d700776a2ce7553f34ddf88a2e7e3d4681de58c94e9805592d901fc0f32cb597 + languageName: node + linkType: hard + +"@smithy/md5-js@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/md5-js@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/08910bf3131bfc82445b20a254f5adc978087e76c309aee9a63dd24084d4c7ea5d96f99bc99a0542553091951127f47731af5f2f6af60cf4bfc423833b4dc8b4 + languageName: node + linkType: hard + +"@smithy/middleware-content-length@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/middleware-content-length@npm:4.2.7" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/23237a15d0a39b95157c3d370edd48aeb0be23daff78b858c3a2e8af081c1a91ef6b5800d2746d9c8094e7af7d4aeb44bb2b400b887527bcdab3be4dc0c3b46c + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^4.4.1": + version: 4.4.1 + resolution: "@smithy/middleware-endpoint@npm:4.4.1" + dependencies: + "@smithy/core": "npm:^3.20.0" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10c0/75fb74725ce5c4c2f689fcf7bf3d3e367d1db95440acc236fa0dce021794c40979170be4e254fa54c717d10feffaaca18eb0d40b5e0d9736ca184af87153bc36 + languageName: node + linkType: hard + +"@smithy/middleware-retry@npm:^4.4.17": + version: 4.4.17 + resolution: "@smithy/middleware-retry@npm:4.4.17" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/service-error-classification": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/uuid": "npm:^1.1.0" + tslib: "npm:^2.6.2" + checksum: 10c0/8959721163dc1d132889c24744880d33cdf5323c76792d09c026dde338fe4df841e98fa6cf0a27fcbc94982b30431c7dd3020f69595e101433669ed5a610f928 + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^4.2.8": + version: 4.2.8 + resolution: "@smithy/middleware-serde@npm:4.2.8" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/5ed53af095d605940b540253c38a723d2cc37400c116071455a23b17fdf60af59b74b67b15d84f7bfb3738c9c37e3664b57f24c670d5c96ee46737225c147344 + languageName: node + linkType: hard + +"@smithy/middleware-stack@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/middleware-stack@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/199aa2575a8e4e3fa1a1a7989958e2f3aeb8dae115b41547d8bef18b5573e369d7eacc206ec6648194cdce491fe8c54abcccedd4a5c0bca370a11c480bd11ca7 + languageName: node + linkType: hard + +"@smithy/node-config-provider@npm:^4.3.7": + version: 4.3.7 + resolution: "@smithy/node-config-provider@npm:4.3.7" + dependencies: + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/2fbe9f22e315253d8d4f5a8d16d41d36ff4467c9f7e515d456c3172b59af8fbd67004d0d44bdb7638886eb6057d04ce269f84de65a382d2ccd0c08114dea840c + languageName: node + linkType: hard + +"@smithy/node-http-handler@npm:^4.0.1, @smithy/node-http-handler@npm:^4.4.7": + version: 4.4.7 + resolution: "@smithy/node-http-handler@npm:4.4.7" + dependencies: + "@smithy/abort-controller": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/querystring-builder": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/8f1114b2bc2232b50c0777b58ab5195c91a5aa1a76c48de7aa403f0c3245be287b070498924845036ab558b28827df916c9730f975a1edfc2e7345d1022350c1 + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/property-provider@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/7caaeec11262a169c6509c5cd687900342ab02900f3074e54aeafbd2ce8a112c83ce3190225b2dab9f2a7f737f7176960329f882935ae7bd9d624984387d0fc1 + languageName: node + linkType: hard + +"@smithy/protocol-http@npm:^5.3.7": + version: 5.3.7 + resolution: "@smithy/protocol-http@npm:5.3.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/c5bde38fbb71a63e2e25e33792d8b186523afbe1d520ffc821943c40eb41ca804a99afca7917798337b1f2bdea4ae64d3ae745f1036f7e65291d7c7ff301a953 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/querystring-builder@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-uri-escape": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/24e3b2a35d2828fb19b4213b823b5adc0ce7edcf8e096a618e2dfcd9df3c2a750ee518af4b754759ab49b2a656c5cb66989d6fbbcfc085f8511dc9e02a0e2dce + languageName: node + linkType: hard + +"@smithy/querystring-parser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/querystring-parser@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4efddf97b35e7b2a04018acf5afd0f658506242adab77098b32e4bb625c5a607fdcfd9df2a7504dcacc7ac5e8624757abb881b2013862a098319a08b5c75a0d1 + languageName: node + linkType: hard + +"@smithy/service-error-classification@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/service-error-classification@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + checksum: 10c0/ddbbae91b4eb83ee66262059a3ce0fa2cee7874bcc0704481f5681966ef25af175afe8bfef7cd0868d86901d08cfb61fe34964f5a4c8f7a6347228a34e40845b + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^4.4.2": + version: 4.4.2 + resolution: "@smithy/shared-ini-file-loader@npm:4.4.2" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/3d401b87b21113aa9bb7490d80ec02d7655c1abc1b23eb384fea13b7e1348f1c599011ed109a3fe2e3675b3bc51f91f43b66d7e46f565f78c3f0d45d3b997058 + languageName: node + linkType: hard + +"@smithy/signature-v4@npm:^5.3.7": + version: 5.3.7 + resolution: "@smithy/signature-v4@npm:5.3.7" + dependencies: + "@smithy/is-array-buffer": "npm:^4.2.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-uri-escape": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/01cae99baa7298adadbce6b293548adf1520fa8072086b48a0ef64cb13c3a156cb4d575753fc72af8fe0b50c65fa364ccce8931bd0d8ffe398d210da96efb54a + languageName: node + linkType: hard + +"@smithy/smithy-client@npm:^4.10.2": + version: 4.10.2 + resolution: "@smithy/smithy-client@npm:4.10.2" + dependencies: + "@smithy/core": "npm:^3.20.0" + "@smithy/middleware-endpoint": "npm:^4.4.1" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-stream": "npm:^4.5.8" + tslib: "npm:^2.6.2" + checksum: 10c0/d272f7eab4f7569b2146227bb869c8e8cd7f25ca71da6416b2c6deb3c10717c5699e132224ae8d1c46dfaf7dab4368cb1515bf58dddb288c3150fb86a2faa7b8 + languageName: node + linkType: hard + +"@smithy/types@npm:^4.11.0": + version: 4.11.0 + resolution: "@smithy/types@npm:4.11.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/8be4af86df4a78fe43afe7dc3f875bf8ec6ce7c04f7bb167152bf3c7ab2eef26db38ed7ae365c2f283e8796e40372b01b4c857b8db43da393002c5638ef3f249 + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/url-parser@npm:4.2.7" + dependencies: + "@smithy/querystring-parser": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ca78587b15a843cc62f3439ae062a24f217e90fa0ec3e50a1ada09cf75c681afa1ccb92ca7a90f63c8f53627d51c6e0c83140422ce98713e1f4866c725923ec0 + languageName: node + linkType: hard + +"@smithy/util-base64@npm:^4.3.0": + version: 4.3.0 + resolution: "@smithy/util-base64@npm:4.3.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/02dd536b9257914cc9a595a865faac64fc96db10468d52d0cba475df78764fc25ba255707ccd061ee197fca189d7859d70af8cf89b0b0c3e27c1c693676eb6e4 + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-body-length-browser@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/15553c249088d59406c6917c19ed19810c7dbcc0967c44e5f3fbb2cc870c004b35f388c082b77f370a2c440a69ec7e8336c7a066af904812a66944dd5cb4c8cc + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^4.2.1": + version: 4.2.1 + resolution: "@smithy/util-body-length-node@npm:4.2.1" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/3c32306735af5b62f75375e976a531ab45f171dfb0dc23ee035478d2132eaf21f244c31b0f3e861c514ff97d8112055e74c98ed44595ad24bd31434d5fdaf4bf + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/util-buffer-from@npm:2.2.0" + dependencies: + "@smithy/is-array-buffer": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/223d6a508b52ff236eea01cddc062b7652d859dd01d457a4e50365af3de1e24a05f756e19433f6ccf1538544076b4215469e21a4ea83dc1d58d829725b0dbc5a + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-buffer-from@npm:4.2.0" + dependencies: + "@smithy/is-array-buffer": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4842d5607240c11400db30762ef6cb4def8d13e3474c5a901a4e2a1783198f5b163ab6011cf24a7f0acbba9a4d7cc79db1d811dc8aa9da446448e52773223997 + languageName: node + linkType: hard + +"@smithy/util-config-provider@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-config-provider@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/0699b9980ef94eac8f491c2ac557dc47e01c6ae71dabcb4464cc064f8dbf0855797461dbec8ba1925d45f076e968b0df02f0691c636cd1043e560f67541a1d27 + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-browser@npm:^4.3.16": + version: 4.3.16 + resolution: "@smithy/util-defaults-mode-browser@npm:4.3.16" + dependencies: + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/f2bfab553b77ec10d0b4fb659ad63ba48b2a46a52e2df796486c74d7c5b9a5bc5704e44a795a0c39dac997e4243b9ff399f0d33206741c21c9f74f0a15903fad + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-node@npm:^4.2.19": + version: 4.2.19 + resolution: "@smithy/util-defaults-mode-node@npm:4.2.19" + dependencies: + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0f68a66ed2cf27f2f8ad8ff796cd4c2f8a6edddd788d5d1ffbbdc2da462f9b7f9d1b49b5c698a204ddee86d4ee238ce10280ddfd5ce748a87b03ebcf2e92bf92 + languageName: node + linkType: hard + +"@smithy/util-endpoints@npm:^3.2.7": + version: 3.2.7 + resolution: "@smithy/util-endpoints@npm:3.2.7" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ca4b134e0ed8b62dfedb82b9ea91fa028567732e271e934b9b878a9aa43f1c5c9d8860ad49f60992290c7705b7b6d2e734769304b9ea38eec40eaf524bb27ad8 + languageName: node + linkType: hard + +"@smithy/util-hex-encoding@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-hex-encoding@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/aaa94a69f03d14d3f28125cc915ca421065735e2d05d7305f0958a50021b2fce4fc68a248328e6b5b612dbaa49e471d481ff513bf89554f659f0a49573e97312 + languageName: node + linkType: hard + +"@smithy/util-middleware@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-middleware@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/76c598cfe8062b6daf0bf88bc855544ce071f1d2df5d9d2c2d1c08402a577cb9ade8f33102a869dfb8aae9f679b86b5faacc9011b032bf453ced255fd8d0a0d3 + languageName: node + linkType: hard + +"@smithy/util-retry@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-retry@npm:4.2.7" + dependencies: + "@smithy/service-error-classification": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/51445769ce5382a85f5c78758d6d7d631b3a3f8277fa49ae2c2730536b1a53babfe27efb30e4b96ebc68faead2aafa9ab877e6ed728eb8018d080e26d9a42f58 + languageName: node + linkType: hard + +"@smithy/util-stream@npm:^4.5.8": + version: 4.5.8 + resolution: "@smithy/util-stream@npm:4.5.8" + dependencies: + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/71f43fdf93ccde982edf4ae3b481006dd42146d17f6594abcca21e2f41e5b40ad69d6038052e016f7135011586294d6ed8c778465ea076deaa50b7808f66bc32 + languageName: node + linkType: hard + +"@smithy/util-uri-escape@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-uri-escape@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/1933e8d939dc52e1ee5e7d2397f4c208a9eac0283397a19ee72078d04db997ebe3ad39709b56aac586ffce10d1cf5ab17dfc068ea6ab030098fc06fe3532e085 + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^2.0.0": + version: 2.3.0 + resolution: "@smithy/util-utf8@npm:2.3.0" + dependencies: + "@smithy/util-buffer-from": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e18840c58cc507ca57fdd624302aefd13337ee982754c9aa688463ffcae598c08461e8620e9852a424d662ffa948fc64919e852508028d09e89ced459bd506ab + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-utf8@npm:4.2.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/689a1f2295d52bec0dde7215a075d79ef32ad8b146cb610a529b2cab747d96978401fd31469c225e31f3042830c54403e64d39b28033df013c8de27a84b405a2 + languageName: node + linkType: hard + +"@smithy/util-waiter@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-waiter@npm:4.2.7" + dependencies: + "@smithy/abort-controller": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0de99074db038eb09c4ebe2ed7f0ff3a13aa0ce5baf0b62a4b684f282e772e281f9eab8936d7aa577d8f419b676df60aa752e3c2b5edf07b44d8e999d983253f + languageName: node + linkType: hard + +"@smithy/uuid@npm:^1.1.0": + version: 1.1.0 + resolution: "@smithy/uuid@npm:1.1.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/f8a8bfcc0e241457636884e778e261d45d8a3aaad533775111170cac36ac666275b59ec6d86d3d5b8d470ff4b864202d2a1a188b3c0e0ed0c86a0b693acf1ecf + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^5.0.1": + version: 5.0.1 + resolution: "@szmarczak/http-timer@npm:5.0.1" + dependencies: + defer-to-connect: "npm:^2.0.1" + checksum: 10c0/4629d2fbb2ea67c2e9dc03af235c0991c79ebdddcbc19aed5d5732fb29ce01c13331e9b1a491584b9069bd6ecde6581dcbf871f11b7eefdebbab34de6cf2197e + languageName: node + linkType: hard + +"@techteamer/ocsp@npm:1.0.1": + version: 1.0.1 + resolution: "@techteamer/ocsp@npm:1.0.1" + dependencies: + asn1.js: "npm:^5.4.1" + asn1.js-rfc2560: "npm:^5.0.1" + asn1.js-rfc5280: "npm:^3.0.0" + async: "npm:^3.2.4" + simple-lru-cache: "npm:^0.0.2" + checksum: 10c0/cbc744c4ce8247f0cf06b79e77bebf2f5bf2d9901b1b1c29540edc2525868e154f51e982f8b7338d010c6186257cb041cefd50f7095d10c86d45e56ac7e33578 + languageName: node + linkType: hard + +"@tootallnate/once@npm:1": + version: 1.1.2 + resolution: "@tootallnate/once@npm:1.1.2" + checksum: 10c0/8fe4d006e90422883a4fa9339dd05a83ff626806262e1710cee5758d493e8cbddf2db81c0e4690636dc840b02c9fda62877866ea774ebd07c1777ed5fafbdec6 + languageName: node + linkType: hard + +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: 10c0/073bfa548026b1ebaf1659eb8961e526be22fa77139b10d60e712f46d2f0f05f4e6c8bec62a087d41088ee9e29faa7f54838568e475ab2f776171003c3920858 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node10@npm:1.0.11" + checksum: 10c0/28a0710e5d039e0de484bdf85fee883bfd3f6a8980601f4d44066b0a6bcd821d31c4e231d1117731c4e24268bd4cf2a788a6787c12fc7f8d11014c07d582783c + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb + languageName: node + linkType: hard + +"@tufjs/canonical-json@npm:2.0.0": + version: 2.0.0 + resolution: "@tufjs/canonical-json@npm:2.0.0" + checksum: 10c0/52c5ffaef1483ed5c3feedfeba26ca9142fa386eea54464e70ff515bd01c5e04eab05d01eff8c2593291dcaf2397ca7d9c512720e11f52072b04c47a5c279415 + languageName: node + linkType: hard + +"@tufjs/models@npm:2.0.1": + version: 2.0.1 + resolution: "@tufjs/models@npm:2.0.1" + dependencies: + "@tufjs/canonical-json": "npm:2.0.0" + minimatch: "npm:^9.0.4" + checksum: 10c0/ad9e82fd921954501fd90ed34ae062254637595577ad13fdc1e076405c0ea5ee7d8aebad09e63032972fd92b07f1786c15b24a195a171fc8ac470ca8e2ffbcc4 + languageName: node + linkType: hard + +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d + languageName: node + linkType: hard + +"@types/caseless@npm:*": + version: 0.12.5 + resolution: "@types/caseless@npm:0.12.5" + checksum: 10c0/b1f8b8a38ce747b643115d37a40ea824c658bd7050e4b69427a10e9d12d1606ed17a0f6018241c08291cd59f70aeb3c1f3754ad61e45f8dbba708ec72dde7ec8 + languageName: node + linkType: hard + +"@types/chai-as-promised@npm:7.1.8": + version: 7.1.8 + resolution: "@types/chai-as-promised@npm:7.1.8" + dependencies: + "@types/chai": "npm:*" + checksum: 10c0/c0a19cffe8d3f406b2cb9ba17f5f0efe318b14f27896d807b3199cc2231c16a4b5b6c464fdf2a939214de481de58cffd46c240539d3d4ece18659277d71ccc23 + languageName: node + linkType: hard + +"@types/chai-datetime@npm:1.0.0": + version: 1.0.0 + resolution: "@types/chai-datetime@npm:1.0.0" + dependencies: + "@types/chai": "npm:*" + checksum: 10c0/a2f741e407fcbdb5ddbf7dfd9880ea7393679b628ad01838b1721149f1396d3700ca606a23ea3ce2b5fa8fde51e7a3195e32f7ec2c2a684333098ef425a266ba + languageName: node + linkType: hard + +"@types/chai@npm:*": + version: 5.2.1 + resolution: "@types/chai@npm:5.2.1" + dependencies: + "@types/deep-eql": "npm:*" + checksum: 10c0/f8a03c9f8450b7ab8df11f658c4194be17a6316b870490d5ffaf5289a3c0c0591ed6291b2d6551e181887c3eed89d0490744b3e569d9b23cf611b05f93e775b6 + languageName: node + linkType: hard + +"@types/chai@npm:4.3.20": + version: 4.3.20 + resolution: "@types/chai@npm:4.3.20" + checksum: 10c0/4601189d611752e65018f1ecadac82e94eed29f348e1d5430e5681a60b01e1ecf855d9bcc74ae43b07394751f184f6970fac2b5561fc57a1f36e93a0f5ffb6e8 + languageName: node + linkType: hard + +"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.12": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/geojson@npm:^7946.0.16": + version: 7946.0.16 + resolution: "@types/geojson@npm:7946.0.16" + checksum: 10c0/1ff24a288bd5860b766b073ead337d31d73bdc715e5b50a2cee5cb0af57a1ed02cc04ef295f5fa68dc40fe3e4f104dd31282b2b818a5ba3231bc1001ba084e3c + languageName: node + linkType: hard + +"@types/hast@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7 + languageName: node + linkType: hard + +"@types/http-cache-semantics@npm:^4.0.2": + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6 + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 10c0/6bf5337bc447b706bb5b4431d37686aa2ea6d07cfd6f79cc31de80170d6ff9b1c7384a9c0ccbc45b3f512bae9e9f75c2e12109806a15331dc94e8a8db6dbb4ac + languageName: node + linkType: hard + +"@types/katex@npm:^0.16.0": + version: 0.16.7 + resolution: "@types/katex@npm:0.16.7" + checksum: 10c0/68dcb9f68a90513ec78ca0196a142e15c2a2c270b1520d752bafd47a99207115085a64087b50140359017d7e9c870b3c68e7e4d36668c9e348a9ef0c48919b5a + languageName: node + linkType: hard + +"@types/lodash@npm:4.17.21, @types/lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10c0/73cb006e047d8871e9d63f3a165543bf16c44a5b6fe3f9f6299e37cb8d67a7b1d55ac730959a81f9def510fd07232ff7e30e05413e5d5a12793baad84ebe36c3 + languageName: node + linkType: hard + +"@types/minimatch@npm:^3.0.3": + version: 3.0.5 + resolution: "@types/minimatch@npm:3.0.5" + checksum: 10c0/a1a19ba342d6f39b569510f621ae4bbe972dc9378d15e9a5e47904c440ee60744f5b09225bc73be1c6490e3a9c938eee69eb53debf55ce1f15761201aa965f97 + languageName: node + linkType: hard + +"@types/minimist@npm:^1.2.0": + version: 1.2.5 + resolution: "@types/minimist@npm:1.2.5" + checksum: 10c0/3f791258d8e99a1d7d0ca2bda1ca6ea5a94e5e7b8fc6cde84dd79b0552da6fb68ade750f0e17718f6587783c24254bbca0357648dd59dc3812c150305cabdc46 + languageName: node + linkType: hard + +"@types/mocha@npm:10.0.10": + version: 10.0.10 + resolution: "@types/mocha@npm:10.0.10" + checksum: 10c0/d2b8c48138cde6923493e42b38e839695eb42edd04629abe480a8f34c0e3f50dd82a55832c2e8d2b6e6f9e4deb492d7d733e600fbbdd5a0ceccbcfc6844ff9d5 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/944730fd7b398c5078de3c3d4d0afeec8584283bc694da1803fdfca14149ea385e18b1b774326f1601baf53898ce6d121a952c51eb62d188ef6fcc41f725c0dc + languageName: node + linkType: hard + +"@types/node@npm:*, @types/node@npm:>=18, @types/node@npm:^24.0.13": + version: 24.1.0 + resolution: "@types/node@npm:24.1.0" + dependencies: + undici-types: "npm:~7.8.0" + checksum: 10c0/6c4686bc144f6ce7bffd4cadc3e1196e2217c1da4c639c637213719c8a3ee58b6c596b994befcbffeacd9d9eb0c3bff6529d2bc27da5d1cb9d58b1da0056f9f4 + languageName: node + linkType: hard + +"@types/node@npm:22.19.3, @types/node@npm:^22.5.5": + version: 22.19.3 + resolution: "@types/node@npm:22.19.3" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/a30a75d503da795ddbd5f8851014f3e91925c2a63fa3f14128d54c998f25d682dfba96dc9589c73cf478b87a16d88beb790b11697bb8cd5bee913079237a58f2 + languageName: node + linkType: hard + +"@types/normalize-package-data@npm:^2.4.0": + version: 2.4.4 + resolution: "@types/normalize-package-data@npm:2.4.4" + checksum: 10c0/aef7bb9b015883d6f4119c423dd28c4bdc17b0e8a0ccf112c78b4fe0e91fbc4af7c6204b04bba0e199a57d2f3fbbd5b4a14bf8739bf9d2a39b2a0aad545e0f86 + languageName: node + linkType: hard + +"@types/pg@npm:^8.11.14": + version: 8.11.14 + resolution: "@types/pg@npm:8.11.14" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^4.0.1" + checksum: 10c0/ad9be5f0215a337409d843b844c21af9a0073485125f32e91b1c19a3be233c7c8bfe641c761e91228a4b10e803f1ba4d3c0ed55dcd0ca1dd4f3a07ebd798347c + languageName: node + linkType: hard + +"@types/readable-stream@npm:^4.0.0": + version: 4.0.18 + resolution: "@types/readable-stream@npm:4.0.18" + dependencies: + "@types/node": "npm:*" + safe-buffer: "npm:~5.1.1" + checksum: 10c0/641e0e91b9ecfeed72f7509089f25923e06e19e79ed36f962785c41c07b0c9ecb2ecfdf6d290a101b9b5a669a3b89ee3aff0cad57b66a05e2a30cd199052e3e1 + languageName: node + linkType: hard + +"@types/request@npm:^2.48.8": + version: 2.48.12 + resolution: "@types/request@npm:2.48.12" + dependencies: + "@types/caseless": "npm:*" + "@types/node": "npm:*" + "@types/tough-cookie": "npm:*" + form-data: "npm:^2.5.0" + checksum: 10c0/dd3d03d68af95b1e1961dc51efc63023543a91a74afd481dafb441521a31baa58c42f80d3bdd0d5d4633aa777e31b17f7ff7bed5606ad3f5eb175a65148adbce + languageName: node + linkType: hard + +"@types/semver@npm:7.7.1, @types/semver@npm:^7.3.12": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 + languageName: node + linkType: hard + +"@types/sinon-chai@npm:3.2.12": + version: 3.2.12 + resolution: "@types/sinon-chai@npm:3.2.12" + dependencies: + "@types/chai": "npm:*" + "@types/sinon": "npm:*" + checksum: 10c0/2d4b865f117226c84d4fae861e702fa02598e2f347823dccb5dcdcf4bcc8a5f60fbf2c028feb34001d569b69ccbb401a42830cf85f4f5f2d0bd6283112b848f1 + languageName: node + linkType: hard + +"@types/sinon@npm:*, @types/sinon@npm:17.0.4": + version: 17.0.4 + resolution: "@types/sinon@npm:17.0.4" + dependencies: + "@types/sinonjs__fake-timers": "npm:*" + checksum: 10c0/7c67ae1050d98a86d8dd771f0a764e97adb9d54812bf3b001195f8cfaa1e2bdfc725d5b970b91e7b0bb6b7c1ca209f47993f2c6f84f1f868313c37441313ca5b + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:*": + version: 8.1.5 + resolution: "@types/sinonjs__fake-timers@npm:8.1.5" + checksum: 10c0/2b8bdc246365518fc1b08f5720445093cce586183acca19a560be6ef81f824bd9a96c090e462f622af4d206406dadf2033c5daf99a51c1096da6494e5c8dc32e + languageName: node + linkType: hard + +"@types/tough-cookie@npm:*": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 + languageName: node + linkType: hard + +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a + languageName: node + linkType: hard + +"@types/unist@npm:*": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60 + languageName: node + linkType: hard + +"@types/unist@npm:^2.0.0": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 10c0/24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d + languageName: node + linkType: hard + +"@types/validator@npm:^13.15.10": + version: 13.15.10 + resolution: "@types/validator@npm:13.15.10" + checksum: 10c0/3e2e65fcd37dd6961ca3fd0535293d0c42f5911dc3ca44b96f458835e6db2392b678ccbb0c9815d8c0a14e653439e6c62c7b8758a6cd1d6e390551c9e56618ac + languageName: node + linkType: hard + +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10c0/8d8f53363f360f38135301a06b596c295433ad01debd082078c33c6ed98b05a5c8fe8853a88265432126096084f4a135ec1564e3daad631b83296905509f90b3 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^7.2.0": + version: 7.18.0 + resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.10.0" + "@typescript-eslint/scope-manager": "npm:7.18.0" + "@typescript-eslint/type-utils": "npm:7.18.0" + "@typescript-eslint/utils": "npm:7.18.0" + "@typescript-eslint/visitor-keys": "npm:7.18.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.3.1" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^1.3.0" + peerDependencies: + "@typescript-eslint/parser": ^7.0.0 + eslint: ^8.56.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/2b37948fa1b0dab77138909dabef242a4d49ab93e4019d4ef930626f0a7d96b03e696cd027fa0087881c20e73be7be77c942606b4a76fa599e6b37f6985304c3 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^7.2.0": + version: 7.18.0 + resolution: "@typescript-eslint/parser@npm:7.18.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:7.18.0" + "@typescript-eslint/types": "npm:7.18.0" + "@typescript-eslint/typescript-estree": "npm:7.18.0" + "@typescript-eslint/visitor-keys": "npm:7.18.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^8.56.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/370e73fca4278091bc1b657f85e7d74cd52b24257ea20c927a8e17546107ce04fbf313fec99aed0cc2a145ddbae1d3b12e9cc2c1320117636dc1281bcfd08059 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/scope-manager@npm:7.18.0" + dependencies: + "@typescript-eslint/types": "npm:7.18.0" + "@typescript-eslint/visitor-keys": "npm:7.18.0" + checksum: 10c0/038cd58c2271de146b3a594afe2c99290034033326d57ff1f902976022c8b0138ffd3cb893ae439ae41003b5e4bcc00cabf6b244ce40e8668f9412cc96d97b8e + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/type-utils@npm:7.18.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:7.18.0" + "@typescript-eslint/utils": "npm:7.18.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^1.3.0" + peerDependencies: + eslint: ^8.56.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/ad92a38007be620f3f7036f10e234abdc2fdc518787b5a7227e55fd12896dacf56e8b34578723fbf9bea8128df2510ba8eb6739439a3879eda9519476d5783fd + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/types@npm:7.18.0" + checksum: 10c0/eb7371ac55ca77db8e59ba0310b41a74523f17e06f485a0ef819491bc3dd8909bb930120ff7d30aaf54e888167e0005aa1337011f3663dc90fb19203ce478054 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:^8.46.0": + version: 8.46.1 + resolution: "@typescript-eslint/types@npm:8.46.1" + checksum: 10c0/90887acaa5b33b45af20cf7f87ec4ae098c0daa88484245473e73903fa6e542f613247c22148132167891ca06af6549a60b9d2fd14a65b22871e016901ce3756 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/typescript-estree@npm:7.18.0" + dependencies: + "@typescript-eslint/types": "npm:7.18.0" + "@typescript-eslint/visitor-keys": "npm:7.18.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/0c7f109a2e460ec8a1524339479cf78ff17814d23c83aa5112c77fb345e87b3642616291908dcddea1e671da63686403dfb712e4a4435104f92abdfddf9aba81 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/utils@npm:7.18.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:7.18.0" + "@typescript-eslint/types": "npm:7.18.0" + "@typescript-eslint/typescript-estree": "npm:7.18.0" + peerDependencies: + eslint: ^8.56.0 + checksum: 10c0/a25a6d50eb45c514469a01ff01f215115a4725fb18401055a847ddf20d1b681409c4027f349033a95c4ff7138d28c3b0a70253dfe8262eb732df4b87c547bd1e + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:^5.10.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/visitor-keys@npm:7.18.0" + dependencies: + "@typescript-eslint/types": "npm:7.18.0" + eslint-visitor-keys: "npm:^3.4.3" + checksum: 10c0/538b645f8ff1d9debf264865c69a317074eaff0255e63d7407046176b0f6a6beba34a6c51d511f12444bae12a98c69891eb6f403c9f54c6c2e2849d1c1cb73c0 + languageName: node + linkType: hard + +"@typespec/ts-http-runtime@npm:^0.2.2": + version: 0.2.2 + resolution: "@typespec/ts-http-runtime@npm:0.2.2" + dependencies: + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0f7d633f9885bd7fb065b205887eb6a85639c37ef2d4b50a1b55cee3ef7ad270dcf4757db0882a39157624e8888c6f1f29aaf4c163403c91e75a4b646d362c49 + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.7.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-x64@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.7.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-freebsd-x64@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.7.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-musl@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.7.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-gnu@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.7.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-musl@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.7.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.7.2" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.9" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-x64-msvc@npm:1.7.2": + version: 1.7.2 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.7.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@yarnpkg/lockfile@npm:^1.1.0": + version: 1.1.0 + resolution: "@yarnpkg/lockfile@npm:1.1.0" + checksum: 10c0/0bfa50a3d756623d1f3409bc23f225a1d069424dbc77c6fd2f14fb377390cd57ec703dc70286e081c564be9051ead9ba85d81d66a3e68eeb6eb506d4e0c0fbda + languageName: node + linkType: hard + +"@yarnpkg/parsers@npm:3.0.2": + version: 3.0.2 + resolution: "@yarnpkg/parsers@npm:3.0.2" + dependencies: + js-yaml: "npm:^3.10.0" + tslib: "npm:^2.4.0" + checksum: 10c0/a0c340e13129643162423d7e666061c0b39b143bfad3fc5a74c7d92a30fd740f6665d41cd4e61832c20375889d793eea1d1d103cacb39ed68f7acd168add8c53 + languageName: node + linkType: hard + +"@zkochan/js-yaml@npm:0.0.7": + version: 0.0.7 + resolution: "@zkochan/js-yaml@npm:0.0.7" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/c8b3525717912811f9422ed50e94c5751ed6f771eb1b7e5cde097f14835654931e2bdaecb1e5fc37b51cf8d822410a307f16dd1581d46149398c30215f3f9bac + languageName: node + linkType: hard + +"JSONStream@npm:^1.3.5": + version: 1.3.5 + resolution: "JSONStream@npm:1.3.5" + dependencies: + jsonparse: "npm:^1.2.0" + through: "npm:>=2.2.7 <3" + bin: + JSONStream: ./bin.js + checksum: 10c0/0f54694da32224d57b715385d4a6b668d2117379d1f3223dc758459246cca58fdc4c628b83e8a8883334e454a0a30aa198ede77c788b55537c1844f686a751f2 + languageName: node + linkType: hard + +"abbrev@npm:1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf + languageName: node + linkType: hard + +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.2.0, acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.1.1": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 + languageName: node + linkType: hard + +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 10c0/bd0b2c2b0f334bbee48828ff897c12bd2eb5898d03bf556dcc8942022cec795ac5bb5b6b585e2de687db6231faf07e096b59a361231dd8c9344d5df5f7f0e526 + languageName: node + linkType: hard + +"acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"add-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "add-stream@npm:1.0.0" + checksum: 10c0/985014a14e76ca4cb24e0fc58bb1556794cf38c5c8937de335a10584f50a371dc48e1c34a59391c7eb9c1fc908b4b86764df5d2756f701df6ba95d1ca2f63ddc + languageName: node + linkType: hard + +"adm-zip@npm:^0.5.16": + version: 0.5.16 + resolution: "adm-zip@npm:0.5.16" + checksum: 10c0/6f10119d4570c7ba76dcf428abb8d3f69e63f92e51f700a542b43d4c0130373dd2ddfc8f85059f12d4a843703a90c3970cfd17876844b4f3f48bf042bfa6b49f + languageName: node + linkType: hard + +"agent-base@npm:5": + version: 5.1.1 + resolution: "agent-base@npm:5.1.1" + checksum: 10c0/3baa3f01072c16e3955ce7802166e576cde9831af82b262aae1c780af49c0c84e82e64ba9ef9e7d1704fe29e9f0096a78a4f998ec137360fee3cb95186f97161 + languageName: node + linkType: hard + +"agent-base@npm:6, agent-base@npm:^6.0.2": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 + languageName: node + linkType: hard + +"agentkeepalive@npm:^4.1.3": + version: 4.6.0 + resolution: "agentkeepalive@npm:4.6.0" + dependencies: + humanize-ms: "npm:^1.2.1" + checksum: 10c0/235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.1": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-escapes@npm:^7.0.0": + version: 7.0.0 + resolution: "ansi-escapes@npm:7.0.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/86e51e36fabef18c9c004af0a280573e828900641cea35134a124d2715e0c5a473494ab4ce396614505da77638ae290ff72dd8002d9747d2ee53f5d6bbe336be + languageName: node + linkType: hard + +"ansi-regex@npm:^4.1.0": + version: 4.1.1 + resolution: "ansi-regex@npm:4.1.1" + checksum: 10c0/d36d34234d077e8770169d980fed7b2f3724bfa2a01da150ccd75ef9707c80e883d27cdf7a0eac2f145ac1d10a785a8a855cffd05b85f778629a0db62e7033da + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"ansis@npm:^3.16.0, ansis@npm:^3.17.0": + version: 3.17.0 + resolution: "ansis@npm:3.17.0" + checksum: 10c0/d8fa94ca7bb91e7e5f8a7d323756aa075facce07c5d02ca883673e128b2873d16f93e0dec782f98f1eeb1f2b3b4b7b60dcf0ad98fb442e75054fe857988cc5cb + languageName: node + linkType: hard + +"append-transform@npm:^2.0.0": + version: 2.0.0 + resolution: "append-transform@npm:2.0.0" + dependencies: + default-require-extensions: "npm:^3.0.0" + checksum: 10c0/f1505e4f4597f4eb7b3df8da898e431fc25d6cdc6c78d01c700a4fab38d835e7cbac693eade8df7b0a0944dc52a35f92b1771e440af59f1b1f8a1dadaba7d17b + languageName: node + linkType: hard + +"aproba@npm:2.0.0, aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 10c0/d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5 + languageName: node + linkType: hard + +"archy@npm:^1.0.0": + version: 1.0.0 + resolution: "archy@npm:1.0.0" + checksum: 10c0/200c849dd1c304ea9914827b0555e7e1e90982302d574153e28637db1a663c53de62bad96df42d50e8ce7fc18d05e3437d9aa8c4b383803763755f0956c7d308 + languageName: node + linkType: hard + +"are-docs-informative@npm:^0.0.2": + version: 0.0.2 + resolution: "are-docs-informative@npm:0.0.2" + checksum: 10c0/f0326981bd699c372d268b526b170a28f2e1aec2cf99d7de0686083528427ecdf6ae41fef5d9988e224a5616298af747ad8a76e7306b0a7c97cc085a99636d60 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c + languageName: node + linkType: hard + +"are-we-there-yet@npm:^3.0.0": + version: 3.0.1 + resolution: "are-we-there-yet@npm:3.0.1" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/8373f289ba42e4b5ec713bb585acdac14b5702c75f2a458dc985b9e4fa5762bc5b46b40a21b72418a3ed0cfb5e35bdc317ef1ae132f3035f633d581dd03168c3 + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"aria-query@npm:^5.3.2": + version: 5.3.2 + resolution: "aria-query@npm:5.3.2" + checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"array-differ@npm:^3.0.0": + version: 3.0.0 + resolution: "array-differ@npm:3.0.0" + checksum: 10c0/c0d924cc2b7e3f5a0e6ae932e8941c5fddc0412bcecf8d5152641910e60f5e1c1e87da2b32083dec2f92f9a8f78e916ea68c22a0579794ba49886951ae783123 + languageName: node + linkType: hard + +"array-ify@npm:^1.0.0": + version: 1.0.0 + resolution: "array-ify@npm:1.0.0" + checksum: 10c0/75c9c072faac47bd61779c0c595e912fe660d338504ac70d10e39e1b8a4a0c9c87658703d619b9d1b70d324177ae29dc8d07dda0d0a15d005597bc4c5a59c70c + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": + version: 3.1.8 + resolution: "array-includes@npm:3.1.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + is-string: "npm:^1.0.7" + checksum: 10c0/5b1004d203e85873b96ddc493f090c9672fd6c80d7a60b798da8a14bff8a670ff95db5aafc9abc14a211943f05220dacf8ea17638ae0af1a6a47b8c0b48ce370 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"array.prototype.findlast@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 + languageName: node + linkType: hard + +"array.prototype.findlastindex@npm:^1.2.5": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.2, array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"arrify@npm:^1.0.1": + version: 1.0.1 + resolution: "arrify@npm:1.0.1" + checksum: 10c0/c35c8d1a81bcd5474c0c57fe3f4bad1a4d46a5fa353cedcff7a54da315df60db71829e69104b859dff96c5d68af46bd2be259fe5e50dc6aa9df3b36bea0383ab + languageName: node + linkType: hard + +"arrify@npm:^2.0.0, arrify@npm:^2.0.1": + version: 2.0.1 + resolution: "arrify@npm:2.0.1" + checksum: 10c0/3fb30b5e7c37abea1907a60b28a554d2f0fc088757ca9bf5b684786e583fdf14360721eb12575c1ce6f995282eab936712d3c4389122682eafab0e0b57f78dbb + languageName: node + linkType: hard + +"asn1.js-rfc2560@npm:^5.0.0, asn1.js-rfc2560@npm:^5.0.1": + version: 5.0.1 + resolution: "asn1.js-rfc2560@npm:5.0.1" + dependencies: + asn1.js-rfc5280: "npm:^3.0.0" + peerDependencies: + asn1.js: ^5.0.0 + checksum: 10c0/01599f2d9ea711a61edeb9dc35b0f6551b5a76638d6a103a6f80c722a3bc3b18aacdac49a6b13cb34e3a9bf136fc5bfb772529041766be778bc4c2ab62233734 + languageName: node + linkType: hard + +"asn1.js-rfc5280@npm:^3.0.0": + version: 3.0.0 + resolution: "asn1.js-rfc5280@npm:3.0.0" + dependencies: + asn1.js: "npm:^5.0.0" + checksum: 10c0/5697696dc0bb72dad9dcd88b584fe1fd6d2e5b8cbd9d27f94a5ba3be443bdb285334aaf805d887f354a4e07cceeb2ffa33d79526e56ab0a26505847d6111baa6 + languageName: node + linkType: hard + +"asn1.js@npm:^5.0.0, asn1.js@npm:^5.4.1": + version: 5.4.1 + resolution: "asn1.js@npm:5.4.1" + dependencies: + bn.js: "npm:^4.0.0" + inherits: "npm:^2.0.1" + minimalistic-assert: "npm:^1.0.0" + safer-buffer: "npm:^2.1.0" + checksum: 10c0/b577232fa6069cc52bb128e564002c62b2b1fe47f7137bdcd709c0b8495aa79cee0f8cc458a831b2d8675900eea0d05781b006be5e1aa4f0ae3577a73ec20324 + languageName: node + linkType: hard + +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: 10c0/25456b2aa333250f01143968e02e4884a34588a8538fbbf65c91a637f1dbfb8069249133cd2f4e530f10f624d206a664e7df30207830b659e9f5298b00a4099b + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"ast-types-flow@npm:^0.0.8": + version: 0.0.8 + resolution: "ast-types-flow@npm:0.0.8" + checksum: 10c0/f2a0ba8055353b743c41431974521e5e852a9824870cd6fce2db0e538ac7bf4da406bbd018d109af29ff3f8f0993f6a730c9eddbd0abd031fbcb29ca75c1014e + languageName: node + linkType: hard + +"astral-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "astral-regex@npm:1.0.0" + checksum: 10c0/ca460207a19d84c65671e1a85940101522d42f31a450cdb8f93b3464e6daeaf4b58a362826a6c11c57e6cd1976403d197abb0447cfc2087993a29b35c6d63b63 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-retry@npm:^1.3.3": + version: 1.3.3 + resolution: "async-retry@npm:1.3.3" + dependencies: + retry: "npm:0.13.1" + checksum: 10c0/cabced4fb46f8737b95cc88dc9c0ff42656c62dc83ce0650864e891b6c155a063af08d62c446269b51256f6fbcb69a6563b80e76d0ea4a5117b0c0377b6b19d8 + languageName: node + linkType: hard + +"async@npm:^3.0.1, async@npm:^3.2.3, async@npm:^3.2.4": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"aws-ssl-profiles@npm:^1.1.1": + version: 1.1.2 + resolution: "aws-ssl-profiles@npm:1.1.2" + checksum: 10c0/e5f59a4146fe3b88ad2a84f814886c788557b80b744c8cbcb1cbf8cf5ba19cc006a7a12e88819adc614ecda9233993f8f1d1f3b612cbc2f297196df9e8f4f66e + languageName: node + linkType: hard + +"axe-core@npm:^4.10.0": + version: 4.10.3 + resolution: "axe-core@npm:4.10.3" + checksum: 10c0/1b1c24f435b2ffe89d76eca0001cbfff42dbf012ad9bd37398b70b11f0d614281a38a28bc3069e8972e3c90ec929a8937994bd24b0ebcbaab87b8d1e241ab0c7 + languageName: node + linkType: hard + +"axios@npm:^1.12.0, axios@npm:^1.8.3": + version: 1.12.2 + resolution: "axios@npm:1.12.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/80b063e318cf05cd33a4d991cea0162f3573481946f9129efb7766f38fde4c061c34f41a93a9f9521f02b7c9565ccbc197c099b0186543ac84a24580017adfed + languageName: node + linkType: hard + +"axobject-query@npm:^4.1.0": + version: 4.1.0 + resolution: "axobject-query@npm:4.1.0" + checksum: 10c0/c470e4f95008f232eadd755b018cb55f16c03ccf39c027b941cd8820ac6b68707ce5d7368a46756db4256fbc91bb4ead368f84f7fb034b2b7932f082f6dc0775 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: 10c0/0488c4ae12df758ca9d49b3bb27b47fd559677965c52cae7b335784724fb8bf96c42b6e5ba7d7afcbc31facb0e294c3ef717cc41c5bc2f7bd9e76f8b90acd31c + languageName: node + linkType: hard + +"big-integer@npm:^1.6.43, big-integer@npm:^1.6.51": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 10c0/9604224b4c2ab3c43c075d92da15863077a9f59e5d4205f4e7e76acd0cd47e8d469ec5e5dba8d9b32aa233951893b29329ca56ac80c20ce094b4a647a66abae0 + languageName: node + linkType: hard + +"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.1.2": + version: 9.3.0 + resolution: "bignumber.js@npm:9.3.0" + checksum: 10c0/f54a79cd6fc98552ac0510c1cd9381650870ae443bdb20ba9b98e3548188d941506ac3c22a9f9c69b2cc60da9be5700e87d3f54d2825310a8b2ae999dfd6d99d + languageName: node + linkType: hard + +"bin-links@npm:^4.0.4": + version: 4.0.4 + resolution: "bin-links@npm:4.0.4" + dependencies: + cmd-shim: "npm:^6.0.0" + npm-normalize-package-bin: "npm:^3.0.0" + read-cmd-shim: "npm:^4.0.0" + write-file-atomic: "npm:^5.0.0" + checksum: 10c0/feb664e786429289d189c19c193b28d855c2898bc53b8391306cbad2273b59ccecb91fd31a433020019552c3bad3a1e0eeecca1c12e739a12ce2ca94f7553a17 + languageName: node + linkType: hard + +"binascii@npm:0.0.2": + version: 0.0.2 + resolution: "binascii@npm:0.0.2" + checksum: 10c0/91c100b94ddb9420796bb53a5271c3fa1047783b870bd1aecde8245d11189d0d3907835d8224a7b0d5010d03c80647cc96152b3f5a960ee013cad3666469001f + languageName: node + linkType: hard + +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: "npm:1.0.0" + checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba + languageName: node + linkType: hard + +"bl@npm:^1.0.0": + version: 1.2.3 + resolution: "bl@npm:1.2.3" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10c0/ee6478864d3b1295614f269f3fbabeb2362a2f2fc7f8dc2f6c1f944a278d84e0572ecefd6d0b0736d7418763f98dc3b2738253191ea9e98e4b08de211cfac0a6 + languageName: node + linkType: hard + +"bl@npm:^4.0.3, bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f + languageName: node + linkType: hard + +"bl@npm:^6.1.4": + version: 6.1.4 + resolution: "bl@npm:6.1.4" + dependencies: + "@types/readable-stream": "npm:^4.0.0" + buffer: "npm:^6.0.3" + inherits: "npm:^2.0.4" + readable-stream: "npm:^4.2.0" + checksum: 10c0/8744ecf54269b82c126b29066bd9460e65feeb57e2bb8a46b5a6d60db61f857270524b9a73853f22823806492bb66348c117515511d6840586f519bc195cd140 + languageName: node + linkType: hard + +"bn.js@npm:^4.0.0": + version: 4.12.2 + resolution: "bn.js@npm:4.12.2" + checksum: 10c0/09a249faa416a9a1ce68b5f5ec8bbca87fe54e5dd4ef8b1cc8a4969147b80035592bddcb1e9cc814c3ba79e573503d5c5178664b722b509fb36d93620dba9b57 + languageName: node + linkType: hard + +"bn.js@npm:^5.2.1": + version: 5.2.2 + resolution: "bn.js@npm:5.2.2" + checksum: 10c0/cb97827d476aab1a0194df33cd84624952480d92da46e6b4a19c32964aa01553a4a613502396712704da2ec8f831cf98d02e74ca03398404bd78a037ba93f2ab + languageName: node + linkType: hard + +"bnf-parser@npm:^3.1.6": + version: 3.1.6 + resolution: "bnf-parser@npm:3.1.6" + checksum: 10c0/fb0ee9dcfc0840ad72e846a6a1bd3f11eb541526ad8b1867dbbac9b3ad141421c215a5cfc0fd3c9802da94a6aa43c1acb5cffa6c9a2bde5a0984347ee5934a76 + languageName: node + linkType: hard + +"bowser@npm:^2.11.0": + version: 2.11.0 + resolution: "bowser@npm:2.11.0" + checksum: 10c0/04efeecc7927a9ec33c667fa0965dea19f4ac60b3fea60793c2e6cf06c1dcd2f7ae1dbc656f450c5f50783b1c75cf9dc173ba6f3b7db2feee01f8c4b793e1bd3 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browser-request@npm:^0.3.3": + version: 0.3.3 + resolution: "browser-request@npm:0.3.3" + checksum: 10c0/0aece68471469efb3877a449811d4293f1bde615cf024fe05576b3f9a3adb6a47ca80cbc3c5e8c454496419e452d53c2ac1bba0df5e4e5fb65b012f7085b0b71 + languageName: node + linkType: hard + +"browser-stdout@npm:^1.3.1": + version: 1.3.1 + resolution: "browser-stdout@npm:1.3.1" + checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.24.5 + resolution: "browserslist@npm:4.24.5" + dependencies: + caniuse-lite: "npm:^1.0.30001716" + electron-to-chromium: "npm:^1.5.149" + node-releases: "npm:^2.0.19" + update-browserslist-db: "npm:^1.1.3" + bin: + browserslist: cli.js + checksum: 10c0/f4c1ce1a7d8fdfab5e5b88bb6e93d09e8a883c393f86801537a252da0362dbdcde4dbd97b318246c5d84c6607b2f6b47af732c1b000d6a8a881ee024bad29204 + languageName: node + linkType: hard + +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: 10c0/06b9298c9369621a830227c3797ceb3ff5535e323946d7b39a7398fed8b3243798259b3c85e287608c5aad35ccc551cec1a0a5190cc8f39652e8eee25697fc9c + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.2.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: "npm:^1.1.0" + buffer-fill: "npm:^1.0.0" + checksum: 10c0/09d87dd53996342ccfbeb2871257d8cdb25ce9ee2259adc95c6490200cd6e528c5fbae8f30bcc323fe8d8efb0fe541e4ac3bbe9ee3f81c6b7c4b27434cc02ab4 + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: 10c0/55b5654fbbf2d7ceb4991bb537f5e5b5b5b9debca583fee416a74fcec47c16d9e7a90c15acd27577da7bd750b7fa6396e77e7c221e7af138b6d26242381c6e4d + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + +"builtin-modules@npm:^3.3.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: 10c0/2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"byte-size@npm:8.1.1": + version: 8.1.1 + resolution: "byte-size@npm:8.1.1" + checksum: 10c0/83170a16820fde48ebaef93bf6b2e86c5f72041f76e44eba1f3c738cceb699aeadf11088198944d5d7c6f970b465ab1e3dddc2e60bfb49a74374f3447a8db5b9 + languageName: node + linkType: hard + +"cacache@npm:^15.2.0": + version: 15.3.0 + resolution: "cacache@npm:15.3.0" + dependencies: + "@npmcli/fs": "npm:^1.0.0" + "@npmcli/move-file": "npm:^1.0.1" + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + glob: "npm:^7.1.4" + infer-owner: "npm:^1.0.4" + lru-cache: "npm:^6.0.0" + minipass: "npm:^3.1.1" + minipass-collect: "npm:^1.0.2" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.2" + mkdirp: "npm:^1.0.3" + p-map: "npm:^4.0.0" + promise-inflight: "npm:^1.0.1" + rimraf: "npm:^3.0.2" + ssri: "npm:^8.0.1" + tar: "npm:^6.0.2" + unique-filename: "npm:^1.1.1" + checksum: 10c0/886fcc0acc4f6fd5cd142d373d8276267bc6d655d7c4ce60726fbbec10854de3395ee19bbf9e7e73308cdca9fdad0ad55060ff3bd16c6d4165c5b8d21515e1d8 + languageName: node + linkType: hard + +"cacache@npm:^18.0.0, cacache@npm:^18.0.3": + version: 18.0.4 + resolution: "cacache@npm:18.0.4" + dependencies: + "@npmcli/fs": "npm:^3.1.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f + languageName: node + linkType: hard + +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + languageName: node + linkType: hard + +"cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "cacheable-lookup@npm:7.0.0" + checksum: 10c0/63a9c144c5b45cb5549251e3ea774c04d63063b29e469f7584171d059d3a88f650f47869a974e2d07de62116463d742c287a81a625e791539d987115cb081635 + languageName: node + linkType: hard + +"cacheable-request@npm:^10.2.8": + version: 10.2.14 + resolution: "cacheable-request@npm:10.2.14" + dependencies: + "@types/http-cache-semantics": "npm:^4.0.2" + get-stream: "npm:^6.0.1" + http-cache-semantics: "npm:^4.1.1" + keyv: "npm:^4.5.3" + mimic-response: "npm:^4.0.0" + normalize-url: "npm:^8.0.0" + responselike: "npm:^3.0.0" + checksum: 10c0/41b6658db369f20c03128227ecd219ca7ac52a9d24fc0f499cc9aa5d40c097b48b73553504cebd137024d957c0ddb5b67cf3ac1439b136667f3586257763f88d + languageName: node + linkType: hard + +"caching-transform@npm:^4.0.0": + version: 4.0.0 + resolution: "caching-transform@npm:4.0.0" + dependencies: + hasha: "npm:^5.0.0" + make-dir: "npm:^3.0.0" + package-hash: "npm:^4.0.0" + write-file-atomic: "npm:^3.0.0" + checksum: 10c0/7b33669dadfad292636578087a1aa7bcf9fbd60d6cbc67e8f288e3667397193c00bdac35bb84d34bd44fa9209405791fd3ab101c2126109e6eaaef1b899da759 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camel-case@npm:^4.1.2": + version: 4.1.2 + resolution: "camel-case@npm:4.1.2" + dependencies: + pascal-case: "npm:^3.1.2" + tslib: "npm:^2.0.3" + checksum: 10c0/bf9eefaee1f20edbed2e9a442a226793bc72336e2b99e5e48c6b7252b6f70b080fc46d8246ab91939e2af91c36cdd422e0af35161e58dd089590f302f8f64c8a + languageName: node + linkType: hard + +"camelcase-keys@npm:^6.2.2": + version: 6.2.2 + resolution: "camelcase-keys@npm:6.2.2" + dependencies: + camelcase: "npm:^5.3.1" + map-obj: "npm:^4.0.0" + quick-lru: "npm:^4.0.1" + checksum: 10c0/bf1a28348c0f285c6c6f68fb98a9d088d3c0269fed0cdff3ea680d5a42df8a067b4de374e7a33e619eb9d5266a448fe66c2dd1f8e0c9209ebc348632882a3526 + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.0.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001716": + version: 1.0.30001717 + resolution: "caniuse-lite@npm:1.0.30001717" + checksum: 10c0/6c0bb1e5182fd578ebe97ee2203250849754a4e17d985839fab527ad27e125a4c4ffce3ece5505217fedf30ea0bbc17ac9f93e9ac525c0389ccba61c6e8345dc + languageName: node + linkType: hard + +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10c0/6a034af73401f6e55d91ea35c190bbf8bda21714d4ea8bb8f1799311d123410a80f0875db4e3236dc3f97d74231ff4bf1c8783f2be13d7733c7d990c57387281 + languageName: node + linkType: hard + +"chai-as-promised@npm:7.1.2": + version: 7.1.2 + resolution: "chai-as-promised@npm:7.1.2" + dependencies: + check-error: "npm:^1.0.2" + peerDependencies: + chai: ">= 2.1.2 < 6" + checksum: 10c0/ee20ed75296d8cbf828b2f3c9ad64627cee67b1a38b8e906ca59fe788fb6965ddb10f702ae66645ed88f15a905ade4f2d9f8540029e92e2d59b229c9f912273f + languageName: node + linkType: hard + +"chai-datetime@npm:1.8.1": + version: 1.8.1 + resolution: "chai-datetime@npm:1.8.1" + dependencies: + chai: "npm:>1.9.0" + checksum: 10c0/3bb2e342a517027fbef4d8ab7907dfb478a6260b77263ea2359cecbac4bf4c429e8ccbf9371bfc6b08dc00c61fe49ee0ce28e0cd8573f8e29859c429be26b95e + languageName: node + linkType: hard + +"chai@npm:4.5.0": + version: 4.5.0 + resolution: "chai@npm:4.5.0" + dependencies: + assertion-error: "npm:^1.1.0" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" + pathval: "npm:^1.1.1" + type-detect: "npm:^4.1.0" + checksum: 10c0/b8cb596bd1aece1aec659e41a6e479290c7d9bee5b3ad63d2898ad230064e5b47889a3bc367b20100a0853b62e026e2dc514acf25a3c9385f936aa3614d4ab4d + languageName: node + linkType: hard + +"chai@npm:>1.9.0": + version: 5.2.0 + resolution: "chai@npm:5.2.0" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/dfd1cb719c7cebb051b727672d382a35338af1470065cb12adb01f4ee451bbf528e0e0f9ab2016af5fc1eea4df6e7f4504dc8443f8f00bd8fb87ad32dc516f7d + languageName: node + linkType: hard + +"chalk@npm:4.1.0": + version: 4.1.0 + resolution: "chalk@npm:4.1.0" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/3787bd65ecd98ab3a1acc3b4f71d006268a675875e49ee6ea75fb54ba73d268b97544368358c18c42445e408e076ae8ad5cec8fbad36942a2c7ac654883dc61e + languageName: node + linkType: hard + +"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chalk@npm:^2.1.0": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"change-case@npm:^4": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: "npm:^4.1.2" + capital-case: "npm:^1.0.4" + constant-case: "npm:^3.0.4" + dot-case: "npm:^3.0.4" + header-case: "npm:^2.0.4" + no-case: "npm:^3.0.4" + param-case: "npm:^3.0.4" + pascal-case: "npm:^3.1.2" + path-case: "npm:^3.0.4" + sentence-case: "npm:^3.0.4" + snake-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/95a6e48563cd393241ce18470c7310a8a050304a64b63addac487560ab039ce42b099673d1d293cc10652324d92060de11b5d918179fe3b5af2ee521fb03ca58 + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 10c0/ec4b430af873661aa754a896a2b55af089b4e938d3d010fad5219299a6b6d32ab175142699ee250640678cd64bdecd6db3c9af0b8759ab7b155d970d84c4c7d1 + languageName: node + linkType: hard + +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: 10c0/b0c645a45bcc90ff24f0e0140f4875a8436b8ef13b6bcd31ec02cfb2ca502b680362aa95386f7815bdc04b6464d48cf191210b3840d7c04241a149ede591a308 + languageName: node + linkType: hard + +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 10c0/2ae0dec770cd8659d7e8b0ce24392d83b4c2f0eb4a3395c955dce5528edd4cc030a794cfa06600fcdd700b3f2de2f9b8e40e309c0011c4180e3be64a0b42e6a1 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 10c0/96e4731b9ec8050cbb56ab684e8c48d6c33f7826b755802d14e3ebfdc51c57afeece3ea39bc6b09acc359e4363525388b915e16640c1378053820f5e70d0f27d + languageName: node + linkType: hard + +"chardet@npm:^2.1.1": + version: 2.1.1 + resolution: "chardet@npm:2.1.1" + checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 + languageName: node + linkType: hard + +"check-error@npm:^1.0.2, check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: 10c0/94aa37a7315c0e8a83d0112b5bfb5a8624f7f0f81057c73e4707729cdd8077166c6aefb3d8e2b92c63ee130d4a2ff94bad46d547e12f3238cc1d78342a973841 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^1.0.1, chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0, ci-info@npm:^3.6.1": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"ci-info@npm:^4.0.0": + version: 4.2.0 + resolution: "ci-info@npm:4.2.0" + checksum: 10c0/37a2f4b6a213a5cf835890eb0241f0d5b022f6cfefde58a69e9af8e3a0e71e06d6ad7754b0d4efb9cd2613e58a7a33996d71b56b0d04242722e86666f3f3d058 + languageName: node + linkType: hard + +"clean-regexp@npm:^1.0.0": + version: 1.0.0 + resolution: "clean-regexp@npm:1.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10c0/fd9c7446551b8fc536f95e8a286d431017cd4ba1ec2e53997ec9159385e9c317672f6dfc4d49fdb97449fdb53b0bacd0a8bab9343b8fdd2e46c7ddf6173d0db7 + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"clean-stack@npm:^3.0.1": + version: 3.0.1 + resolution: "clean-stack@npm:3.0.1" + dependencies: + escape-string-regexp: "npm:4.0.0" + checksum: 10c0/4ea5c03bdf78e8afb2592f34c1b5832d0c7858d37d8b0d40fba9d61a103508fa3bb527d39a99469019083e58e05d1ad54447e04217d5d36987e97182adab0e03 + languageName: node + linkType: hard + +"cli-cursor@npm:3.1.0, cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: "npm:^3.1.0" + checksum: 10c0/92a2f98ff9037d09be3dfe1f0d749664797fb674bf388375a2207a1203b69d41847abf16434203e0089212479e47a358b13a0222ab9fccfe8e2644a7ccebd111 + languageName: node + linkType: hard + +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + +"cli-spinners@npm:2.6.1": + version: 2.6.1 + resolution: "cli-spinners@npm:2.6.1" + checksum: 10c0/6abcdfef59aa68e6b51376d87d257f9120a0a7120a39dd21633702d24797decb6dc747dff2217c88732710db892b5053c5c672d221b6c4d13bbcb5372e203596 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 + languageName: node + linkType: hard + +"cli-truncate@npm:^5.0.0": + version: 5.1.0 + resolution: "cli-truncate@npm:5.1.0" + dependencies: + slice-ansi: "npm:^7.1.0" + string-width: "npm:^8.0.0" + checksum: 10c0/388a4c9813372fb82ef3958af9bcf233419e80f4f435386cc83666ba85c9ccfdaa4dd6e47a9fde8f70b1e2b485cfc5da97bc899ce4f3b24ed04933a2f878f7d6 + languageName: node + linkType: hard + +"cli-width@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-width@npm:3.0.0" + checksum: 10c0/125a62810e59a2564268c80fdff56c23159a7690c003e34aeb2e68497dccff26911998ff49c33916fcfdf71e824322cc3953e3f7b48b27267c7a062c81348a9a + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10c0/35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 + languageName: node + linkType: hard + +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clone-deep@npm:4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: "npm:^2.0.4" + kind-of: "npm:^6.0.2" + shallow-clone: "npm:^3.0.0" + checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 10c0/2176952b3649293473999a95d7bebfc9dc96410f6cbd3d2595cf12fd401f63a4bf41a7adbfd3ab2ff09ed60cb9870c58c6acdd18b87767366fabfc163700f13b + languageName: node + linkType: hard + +"cmd-shim@npm:6.0.3, cmd-shim@npm:^6.0.0": + version: 6.0.3 + resolution: "cmd-shim@npm:6.0.3" + checksum: 10c0/dc09fe0bf39e86250529456d9a87dd6d5208d053e449101a600e96dc956c100e0bc312cdb413a91266201f3bd8057d4abf63875cafb99039553a1937d8f3da36 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 + languageName: node + linkType: hard + +"color-support@npm:1.1.3, color-support@npm:^1.1.2, color-support@npm:^1.1.3": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 + languageName: node + linkType: hard + +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c + languageName: node + linkType: hard + +"colorette@npm:^2.0.20": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 + languageName: node + linkType: hard + +"columnify@npm:1.6.0": + version: 1.6.0 + resolution: "columnify@npm:1.6.0" + dependencies: + strip-ansi: "npm:^6.0.1" + wcwidth: "npm:^1.0.0" + checksum: 10c0/25b90b59129331bbb8b0c838f8df69924349b83e8eab9549f431062a20a39094b8d744bb83265be38fd5d03140ce4bfbd85837c293f618925e83157ae9535f1d + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^14.0.2, commander@npm:~14.0.2": + version: 14.0.2 + resolution: "commander@npm:14.0.2" + checksum: 10c0/245abd1349dbad5414cb6517b7b5c584895c02c4f7836ff5395f301192b8566f9796c82d7bd6c92d07eba8775fe4df86602fca5d86d8d10bcc2aded1e21c2aeb + languageName: node + linkType: hard + +"commander@npm:^6.2.1": + version: 6.2.1 + resolution: "commander@npm:6.2.1" + checksum: 10c0/85748abd9d18c8bc88febed58b98f66b7c591d9b5017cad459565761d7b29ca13b7783ea2ee5ce84bf235897333706c4ce29adf1ce15c8252780e7000e2ce9ea + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + +"comment-parser@npm:1.4.1": + version: 1.4.1 + resolution: "comment-parser@npm:1.4.1" + checksum: 10c0/d6c4be3f5be058f98b24f2d557f745d8fe1cc9eb75bebbdccabd404a0e1ed41563171b16285f593011f8b6a5ec81f564fb1f2121418ac5cbf0f49255bf0840dd + languageName: node + linkType: hard + +"common-ancestor-path@npm:^1.0.1": + version: 1.0.1 + resolution: "common-ancestor-path@npm:1.0.1" + checksum: 10c0/390c08d2a67a7a106d39499c002d827d2874966d938012453fd7ca34cd306881e2b9d604f657fa7a8e6e4896d67f39ebc09bf1bfd8da8ff318e0fb7a8752c534 + languageName: node + linkType: hard + +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 10c0/33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 + languageName: node + linkType: hard + +"compare-func@npm:^2.0.0": + version: 2.0.0 + resolution: "compare-func@npm:2.0.0" + dependencies: + array-ify: "npm:^1.0.0" + dot-prop: "npm:^5.1.0" + checksum: 10c0/78bd4dd4ed311a79bd264c9e13c36ed564cde657f1390e699e0f04b8eee1fc06ffb8698ce2dfb5fbe7342d509579c82d4e248f08915b708f77f7b72234086cc3 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concat-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "concat-stream@npm:2.0.0" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.0.2" + typedarray: "npm:^0.0.6" + checksum: 10c0/29565dd9198fe1d8cf57f6cc71527dbc6ad67e12e4ac9401feb389c53042b2dceedf47034cbe702dfc4fd8df3ae7e6bfeeebe732cc4fa2674e484c13f04c219a + languageName: node + linkType: hard + +"concurrently@npm:9.2.1": + version: 9.2.1 + resolution: "concurrently@npm:9.2.1" + dependencies: + chalk: "npm:4.1.2" + rxjs: "npm:7.8.2" + shell-quote: "npm:1.8.3" + supports-color: "npm:8.1.1" + tree-kill: "npm:1.2.2" + yargs: "npm:17.7.2" + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: 10c0/da37f239f82eb7ac24f5ddb56259861e5f1d6da2ade7602b6ea7ad3101b13b5ccec02a77b7001402d1028ff2fdc38eed55644b32853ad5abf30e057002a963aa + languageName: node + linkType: hard + +"config-chain@npm:^1.1.11": + version: 1.1.13 + resolution: "config-chain@npm:1.1.13" + dependencies: + ini: "npm:^1.3.4" + proto-list: "npm:~1.2.1" + checksum: 10c0/39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e + languageName: node + linkType: hard + +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 + languageName: node + linkType: hard + +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10c0/91d54f18341fcc491ae66d1086642b0cc564be3e08984d7b7042f8b0a721c8115922f7f11d6a09f13ed96ff326eabae11f9d1eb0335fa9d8b6e39e4df096010e + languageName: node + linkType: hard + +"content-type@npm:^1.0.4": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"conventional-changelog-angular@npm:7.0.0": + version: 7.0.0 + resolution: "conventional-changelog-angular@npm:7.0.0" + dependencies: + compare-func: "npm:^2.0.0" + checksum: 10c0/90e73e25e224059b02951b6703b5f8742dc2a82c1fea62163978e6735fd3ab04350897a8fc6f443ec6b672d6b66e28a0820e833e544a0101f38879e5e6289b7e + languageName: node + linkType: hard + +"conventional-changelog-core@npm:5.0.1": + version: 5.0.1 + resolution: "conventional-changelog-core@npm:5.0.1" + dependencies: + add-stream: "npm:^1.0.0" + conventional-changelog-writer: "npm:^6.0.0" + conventional-commits-parser: "npm:^4.0.0" + dateformat: "npm:^3.0.3" + get-pkg-repo: "npm:^4.2.1" + git-raw-commits: "npm:^3.0.0" + git-remote-origin-url: "npm:^2.0.0" + git-semver-tags: "npm:^5.0.0" + normalize-package-data: "npm:^3.0.3" + read-pkg: "npm:^3.0.0" + read-pkg-up: "npm:^3.0.0" + checksum: 10c0/c026da415ea58346c167e58f8dd717592e92afc897aa604189a6d69f48b6943e7a656b2c83433810feea32dda117b0914a7f5860ed338a21f6ee9b0f56788b37 + languageName: node + linkType: hard + +"conventional-changelog-preset-loader@npm:^3.0.0": + version: 3.0.0 + resolution: "conventional-changelog-preset-loader@npm:3.0.0" + checksum: 10c0/5de23c4aa8b8526c3542fd5abe9758d56eed79821f32cc16d1fdf480cecc44855edbe4680113f229509dcaf4b97cc41e786ac8e3b0822b44fd9d0b98542ed0e0 + languageName: node + linkType: hard + +"conventional-changelog-writer@npm:^6.0.0": + version: 6.0.1 + resolution: "conventional-changelog-writer@npm:6.0.1" + dependencies: + conventional-commits-filter: "npm:^3.0.0" + dateformat: "npm:^3.0.3" + handlebars: "npm:^4.7.7" + json-stringify-safe: "npm:^5.0.1" + meow: "npm:^8.1.2" + semver: "npm:^7.0.0" + split: "npm:^1.0.1" + bin: + conventional-changelog-writer: cli.js + checksum: 10c0/50790b0d92e06c5ab1c02cc4eb2ecd74575244d31cfacea1885d7c8afeae1bc7bbc169140fe062f2438b9952400762240b796e59521c0246278859296b323338 + languageName: node + linkType: hard + +"conventional-commits-filter@npm:^3.0.0": + version: 3.0.0 + resolution: "conventional-commits-filter@npm:3.0.0" + dependencies: + lodash.ismatch: "npm:^4.4.0" + modify-values: "npm:^1.0.1" + checksum: 10c0/9d43cf9029bf39b70b394c551846a57b6f0473028ba5628c38bd447672655cc27bb80ba502d9a7e41335f63ad62b754cb26579f3d4bae7398dfc092acbb32578 + languageName: node + linkType: hard + +"conventional-commits-parser@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-commits-parser@npm:4.0.0" + dependencies: + JSONStream: "npm:^1.3.5" + is-text-path: "npm:^1.0.1" + meow: "npm:^8.1.2" + split2: "npm:^3.2.2" + bin: + conventional-commits-parser: cli.js + checksum: 10c0/12e390cc80ad8a825c5775a329b95e11cf47a6df7b8a3875d375e28b8cb27c4f32955842ea73e4e357cff9757a6be99fdffe4fda87a23e9d8e73f983425537a0 + languageName: node + linkType: hard + +"conventional-recommended-bump@npm:7.0.1": + version: 7.0.1 + resolution: "conventional-recommended-bump@npm:7.0.1" + dependencies: + concat-stream: "npm:^2.0.0" + conventional-changelog-preset-loader: "npm:^3.0.0" + conventional-commits-filter: "npm:^3.0.0" + conventional-commits-parser: "npm:^4.0.0" + git-raw-commits: "npm:^3.0.0" + git-semver-tags: "npm:^5.0.0" + meow: "npm:^8.1.2" + bin: + conventional-recommended-bump: cli.js + checksum: 10c0/ff751a256ddfbec62efd5a32de059b01659e945073793c6766143a8242864fd8099804a90bbf1e6a61928ade3d12292d6f66f721a113630de392d54eb7f0b0c3 + languageName: node + linkType: hard + +"convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cosmiconfig@npm:9.0.0, cosmiconfig@npm:^9.0.0": + version: 9.0.0 + resolution: "cosmiconfig@npm:9.0.0" + dependencies: + env-paths: "npm:^2.2.1" + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/1c1703be4f02a250b1d6ca3267e408ce16abfe8364193891afc94c2d5c060b69611fdc8d97af74b7e6d5d1aac0ab2fb94d6b079573146bc2d756c2484ce5f0ee + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + +"cross-env@npm:10.1.0": + version: 10.1.0 + resolution: "cross-env@npm:10.1.0" + dependencies: + "@epic-web/invariant": "npm:^1.0.0" + cross-spawn: "npm:^7.0.6" + bin: + cross-env: dist/bin/cross-env.js + cross-env-shell: dist/bin/cross-env-shell.js + checksum: 10c0/834a862db456ba1fedf6c6da43436b123ae38f514fa286d6f0937c14fa83f13469f77f70f2812db041ae2d84f82bac627040b8686030aca27fbdf113dfa38b63 + languageName: node + linkType: hard + +"cross-fetch@npm:^3.1.5": + version: 3.2.0 + resolution: "cross-fetch@npm:3.2.0" + dependencies: + node-fetch: "npm:^2.7.0" + checksum: 10c0/d8596adf0269130098a676f6739a0922f3cc7b71cc89729925411ebe851a87026171c82ea89154c4811c9867c01c44793205a52e618ce2684650218c7fbeeb9f + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.5": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"cssesc@npm:^3.0.0": + version: 3.0.0 + resolution: "cssesc@npm:3.0.0" + bin: + cssesc: bin/cssesc + checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 + languageName: node + linkType: hard + +"damerau-levenshtein@npm:^1.0.8": + version: 1.0.8 + resolution: "damerau-levenshtein@npm:1.0.8" + checksum: 10c0/4c2647e0f42acaee7d068756c1d396e296c3556f9c8314bac1ac63ffb236217ef0e7e58602b18bb2173deec7ec8e0cac8e27cccf8f5526666b4ff11a13ad54a3 + languageName: node + linkType: hard + +"dargs@npm:^7.0.0": + version: 7.0.0 + resolution: "dargs@npm:7.0.0" + checksum: 10c0/ec7f6a8315a8fa2f8b12d39207615bdf62b4d01f631b96fbe536c8ad5469ab9ed710d55811e564d0d5c1d548fc8cb6cc70bf0939f2415790159f5a75e0f96c92 + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"dateformat@npm:^3.0.3": + version: 3.0.3 + resolution: "dateformat@npm:3.0.3" + checksum: 10c0/2effb8bef52ff912f87a05e4adbeacff46353e91313ad1ea9ed31412db26849f5a0fcc7e3ce36dbfb84fc6c881a986d5694f84838ad0da7000d5150693e78678 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.19": + version: 1.11.19 + resolution: "dayjs@npm:1.11.19" + checksum: 10c0/7d8a6074a343f821f81ea284d700bd34ea6c7abbe8d93bce7aba818948957c1b7f56131702e5e890a5622cdfc05dcebe8aed0b8313bdc6838a594d7846b0b000 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"debug@npm:^3.2.7": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + +"decamelize-keys@npm:^1.1.0": + version: 1.1.1 + resolution: "decamelize-keys@npm:1.1.1" + dependencies: + decamelize: "npm:^1.1.0" + map-obj: "npm:^1.0.0" + checksum: 10c0/4ca385933127437658338c65fb9aead5f21b28d3dd3ccd7956eb29aab0953b5d3c047fbc207111672220c71ecf7a4d34f36c92851b7bbde6fca1a02c541bdd7d + languageName: node + linkType: hard + +"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"decamelize@npm:^4.0.0": + version: 4.0.0 + resolution: "decamelize@npm:4.0.0" + checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d + languageName: node + linkType: hard + +"decode-named-character-reference@npm:^1.0.0": + version: 1.1.0 + resolution: "decode-named-character-reference@npm:1.1.0" + dependencies: + character-entities: "npm:^2.0.0" + checksum: 10c0/359c76305b47e67660ec096c5cd3f65972ed75b8a53a40435a7a967cfab3e9516e64b443cbe0c7edcf5ab77f65a6924f12fb1872b1e09e2f044f28f4fd10996a + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"dedent@npm:1.5.3": + version: 1.5.3 + resolution: "dedent@npm:1.5.3" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 + languageName: node + linkType: hard + +"deep-eql@npm:^4.1.3": + version: 4.1.4 + resolution: "deep-eql@npm:4.1.4" + dependencies: + type-detect: "npm:^4.0.0" + checksum: 10c0/264e0613493b43552fc908f4ff87b8b445c0e6e075656649600e1b8a17a57ee03e960156fce7177646e4d2ddaf8e5ee616d76bd79929ff593e5c79e4e5e6c517 + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0, deep-extend@npm:~0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10c0/957fb886502594c8e645e812dfe93dba30ed82e8460d20ce39c53c5b0f3e2afb6ceaec2249083b90bdfbb4cb0f34e1f73fde3d68cac00becdbcfd894156b5ead + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/73f17dc3c58026c55bb5538749597db31f9561c0193cd98604144b704a981c95a466f8ecc3c2db63d8bfd04fb0d426904834cfc91ae510c6aeb97e13c5167c4d + languageName: node + linkType: hard + +"default-require-extensions@npm:^3.0.0": + version: 3.0.1 + resolution: "default-require-extensions@npm:3.0.1" + dependencies: + strip-bom: "npm:^4.0.0" + checksum: 10c0/5ca376cb527d9474336ad76dd302d06367a7163379dda26558258de26f85861e696d0b7bb19ee3c6b8456bb7c95cdc0e4e4d45c2aa487e61b2d3b60d80c10648 + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: "npm:^1.0.2" + checksum: 10c0/9cfbe498f5c8ed733775db62dfd585780387d93c17477949e1670bfcfb9346e0281ce8c4bf9f4ac1fc0f9b851113bd6dc9e41182ea1644ccd97de639fa13c35a + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.1": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"delay@npm:5.0.0": + version: 5.0.0 + resolution: "delay@npm:5.0.0" + checksum: 10c0/01cdc4cd0cd35fb622518a3df848e67e09763a38e7cdada2232b6fda9ddda72eddcf74f0e24211200fbe718434f2335f2a2633875a6c96037fefa6de42896ad7 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 + languageName: node + linkType: hard + +"denque@npm:^2.1.0": + version: 2.1.0 + resolution: "denque@npm:2.1.0" + checksum: 10c0/f9ef81aa0af9c6c614a727cb3bd13c5d7db2af1abf9e6352045b86e85873e629690f6222f4edd49d10e4ccf8f078bbeec0794fafaf61b659c0589d0c511ec363 + languageName: node + linkType: hard + +"deprecation@npm:^2.0.0": + version: 2.3.1 + resolution: "deprecation@npm:2.3.1" + checksum: 10c0/23d688ba66b74d09b908c40a76179418acbeeb0bfdf218c8075c58ad8d0c315130cb91aa3dffb623aa3a411a3569ce56c6460de6c8d69071c17fe6dd2442f032 + languageName: node + linkType: hard + +"dequal@npm:^2.0.0": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + +"detect-indent@npm:^5.0.0": + version: 5.0.0 + resolution: "detect-indent@npm:5.0.0" + checksum: 10c0/58d985dd5b4d5e5aad6fe7d8ecc74538fa92c807c894794b8505569e45651bf01a38755b65d9d3d17e512239a26d3131837cbef43cf4226968d5abf175bbcc9d + languageName: node + linkType: hard + +"detect-indent@npm:^7.0.1": + version: 7.0.1 + resolution: "detect-indent@npm:7.0.1" + checksum: 10c0/47b6e3e3dda603c386e73b129f3e84844ae59bc2615f5072becf3cc02eab400bed5a4e6379c49d0b18cf630e80c2b07e87e0038b777addbc6ef793ad77dd05bc + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.0": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c + languageName: node + linkType: hard + +"detect-newline@npm:^4.0.0": + version: 4.0.1 + resolution: "detect-newline@npm:4.0.1" + checksum: 10c0/1cc1082e88ad477f30703ae9f23bd3e33816ea2db6a35333057e087d72d466f5a777809b71f560118ecff935d2c712f5b59e1008a8b56a900909d8fd4621c603 + languageName: node + linkType: hard + +"devlop@npm:^1.0.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: "npm:^2.0.0" + checksum: 10c0/e0928ab8f94c59417a2b8389c45c55ce0a02d9ac7fd74ef62d01ba48060129e1d594501b77de01f3eeafc7cb00773819b0df74d96251cf20b31c5b3071f45c0e + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + languageName: node + linkType: hard + +"diff@npm:^5.2.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 10c0/aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4 + languageName: node + linkType: hard + +"diff@npm:^7.0.0": + version: 7.0.0 + resolution: "diff@npm:7.0.0" + checksum: 10c0/251fd15f85ffdf814cfc35a728d526b8d2ad3de338dcbd011ac6e57c461417090766b28995f8ff733135b5fbc3699c392db1d5e27711ac4e00244768cd1d577b + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/5b859ea65097a7ea870e2c91b5768b72ddf7fa947223fd29e167bcdff58fe731d941c48e47a38ec8aa8e43044c8fbd15cd8fa21689a526bc34b6548197cd5b05 + languageName: node + linkType: hard + +"dot-prop@npm:^5.1.0": + version: 5.3.0 + resolution: "dot-prop@npm:5.3.0" + dependencies: + is-obj: "npm:^2.0.0" + checksum: 10c0/93f0d343ef87fe8869320e62f2459f7e70f49c6098d948cc47e060f4a3f827d0ad61e83cb82f2bd90cd5b9571b8d334289978a43c0f98fea4f0e99ee8faa0599 + languageName: node + linkType: hard + +"dotenv-expand@npm:~11.0.6": + version: 11.0.7 + resolution: "dotenv-expand@npm:11.0.7" + dependencies: + dotenv: "npm:^16.4.5" + checksum: 10c0/d80b8a7be085edf351270b96ac0e794bc3ddd7f36157912939577cb4d33ba6492ebee349d59798b71b90e36f498d24a2a564fb4aa00073b2ef4c2a3a49c467b1 + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5": + version: 16.5.0 + resolution: "dotenv@npm:16.5.0" + checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9 + languageName: node + linkType: hard + +"dotenv@npm:~16.4.5": + version: 16.4.7 + resolution: "dotenv@npm:16.4.7" + checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462 + languageName: node + linkType: hard + +"dottie@npm:^2.0.6": + version: 2.0.6 + resolution: "dottie@npm:2.0.6" + checksum: 10c0/1d99f8b61ae7541b3b70e9cde57308e0044e7bd13e09945b61cf78b303f5c51e3747e5f99c0d519551c5347427c1c1f89aedafe4bf9d9db554c7113772d99b5d + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"duplexify@npm:^4.1.3": + version: 4.1.3 + resolution: "duplexify@npm:4.1.3" + dependencies: + end-of-stream: "npm:^1.4.1" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + stream-shift: "npm:^1.0.2" + checksum: 10c0/8a7621ae95c89f3937f982fe36d72ea997836a708471a75bb2a0eecde3330311b1e128a6dad510e0fd64ace0c56bff3484ed2e82af0e465600c82117eadfbda5 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + +"ejs@npm:^3.1.10, ejs@npm:^3.1.7": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.149": + version: 1.5.149 + resolution: "electron-to-chromium@npm:1.5.149" + checksum: 10c0/dc9fe63d1ffb034a5c91133291b09d877b4b200b01b1235090e94aed7f75f4413637c7b4e3a783b03f03c06bee8f45bbd5f44ddb397bc35528ac8a838eb6a66d + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: 10c0/a3fcedfc58bfcce21a05a5f36a529d81e88d602100145fcca3dc6f795e3c8acc4fc18fe773fbf9b6d6e9371205edb3afa2668ec3473fa2aa7fd47d2a9d46482d + languageName: node + linkType: hard + +"emoji-regex@npm:^7.0.1": + version: 7.0.3 + resolution: "emoji-regex@npm:7.0.3" + checksum: 10c0/a8917d695c3a3384e4b7230a6a06fd2de6b3db3709116792e8b7b36ddbb3db4deb28ad3e983e70d4f2a1f9063b5dab9025e4e26e9ca08278da4fbb73e213743f + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + +"encoding@npm:^0.1.12, encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"enquirer@npm:~2.3.6": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: "npm:^4.1.1" + checksum: 10c0/8e070e052c2c64326a2803db9084d21c8aaa8c688327f133bf65c4a712586beb126fd98c8a01cfb0433e82a4bd3b6262705c55a63e0f7fb91d06b9cedbde9a11 + languageName: node + linkType: hard + +"entities@npm:^4.4.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"envinfo@npm:7.13.0": + version: 7.13.0 + resolution: "envinfo@npm:7.13.0" + bin: + envinfo: dist/cli.js + checksum: 10c0/9c279213cbbb353b3171e8e333fd2ed564054abade08ab3d735fe136e10a0e14e0588e1ce77e6f01285f2462eaca945d64f0778be5ae3d9e82804943e36a4411 + languageName: node + linkType: hard + +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": + version: 1.23.9 + resolution: "es-abstract@npm:1.23.9" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.0" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-regex: "npm:^1.2.1" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.0" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.3" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.3" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.18" + checksum: 10c0/1de229c9e08fe13c17fe5abaec8221545dfcd57e51f64909599a6ae896df84b8fd2f7d16c60cb00d7bf495b9298ca3581aded19939d4b7276854a4b066f8422b + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.2.1": + version: 1.2.1 + resolution: "es-iterator-helpers@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.0.3" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.6" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + iterator.prototype: "npm:^1.1.4" + safe-array-concat: "npm:^1.1.3" + checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" + dependencies: + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b + languageName: node + linkType: hard + +"es6-error@npm:^4.0.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"esbuild@npm:0.27.2": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" + dependencies: + debug: "npm:^3.2.7" + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 + languageName: node + linkType: hard + +"eslint-import-resolver-typescript@npm:^3.6.1": + version: 3.10.1 + resolution: "eslint-import-resolver-typescript@npm:3.10.1" + dependencies: + "@nolyfill/is-core-module": "npm:1.0.39" + debug: "npm:^4.4.0" + get-tsconfig: "npm:^4.10.0" + is-bun-module: "npm:^2.0.0" + stable-hash: "npm:^0.0.5" + tinyglobby: "npm:^0.2.13" + unrs-resolver: "npm:^1.6.2" + peerDependencies: + eslint: "*" + eslint-plugin-import: "*" + eslint-plugin-import-x: "*" + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + checksum: 10c0/02ba72cf757753ab9250806c066d09082e00807b7b6525d7687e1c0710bc3f6947e39120227fe1f93dabea3510776d86fb3fd769466ba3c46ce67e9f874cb702 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.12.0": + version: 2.12.0 + resolution: "eslint-module-utils@npm:2.12.0" + dependencies: + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/4d8b46dcd525d71276f9be9ffac1d2be61c9d54cc53c992e6333cf957840dee09381842b1acbbb15fc6b255ebab99cd481c5007ab438e5455a14abe1a0468558 + languageName: node + linkType: hard + +"eslint-plugin-eslint-comments@npm:^3.2.0": + version: 3.2.0 + resolution: "eslint-plugin-eslint-comments@npm:3.2.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + ignore: "npm:^5.0.5" + peerDependencies: + eslint: ">=4.19.1" + checksum: 10c0/c71db824592dc8ea498021572a0bd33d763ef26126bdb3b84a027ca75a1adbe0894ec95024f7de39ef12308560e62cbf8af0d06ffe472be5ba8bd9169c928e96 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.27.5": + version: 2.31.0 + resolution: "eslint-plugin-import@npm:2.31.0" + dependencies: + "@rtsao/scc": "npm:^1.1.0" + array-includes: "npm:^3.1.8" + array.prototype.findlastindex: "npm:^1.2.5" + array.prototype.flat: "npm:^1.3.2" + array.prototype.flatmap: "npm:^1.3.2" + debug: "npm:^3.2.7" + doctrine: "npm:^2.1.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.12.0" + hasown: "npm:^2.0.2" + is-core-module: "npm:^2.15.1" + is-glob: "npm:^4.0.3" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.8" + object.groupby: "npm:^1.0.3" + object.values: "npm:^1.2.0" + semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.8" + tsconfig-paths: "npm:^3.15.0" + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10c0/e21d116ddd1900e091ad120b3eb68c5dd5437fe2c930f1211781cd38b246f090a6b74d5f3800b8255a0ed29782591521ad44eb21c5534960a8f1fb4040fd913a + languageName: node + linkType: hard + +"eslint-plugin-jest@npm:^27.2.1": + version: 27.9.0 + resolution: "eslint-plugin-jest@npm:27.9.0" + dependencies: + "@typescript-eslint/utils": "npm:^5.10.0" + peerDependencies: + "@typescript-eslint/eslint-plugin": ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: "*" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + jest: + optional: true + checksum: 10c0/b8b09f7d8ba3d84a8779a6e95702a6e4dce45ab034e4edf5ddb631e77cd38dcdf791dfd9228e0a0d1d80d1eb2d278deb62ad2ec39f10fb8fd43cec07304e0c38 + languageName: node + linkType: hard + +"eslint-plugin-jsdoc@npm:61.4.2": + version: 61.4.2 + resolution: "eslint-plugin-jsdoc@npm:61.4.2" + dependencies: + "@es-joy/jsdoccomment": "npm:~0.76.0" + "@es-joy/resolve.exports": "npm:1.2.0" + are-docs-informative: "npm:^0.0.2" + comment-parser: "npm:1.4.1" + debug: "npm:^4.4.3" + escape-string-regexp: "npm:^4.0.0" + espree: "npm:^10.4.0" + esquery: "npm:^1.6.0" + html-entities: "npm:^2.6.0" + object-deep-merge: "npm:^2.0.0" + parse-imports-exports: "npm:^0.2.4" + semver: "npm:^7.7.3" + spdx-expression-parse: "npm:^4.0.0" + to-valid-identifier: "npm:^1.0.0" + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + checksum: 10c0/e7385cd9daf43cf34d41cb490602c1c5310c2435422b5023bbb44772101dabf8892a1822556d5678219db2cc930ab33cebf6a4900c4743306d840d9886126e5b + languageName: node + linkType: hard + +"eslint-plugin-json@npm:^3.1.0": + version: 3.1.0 + resolution: "eslint-plugin-json@npm:3.1.0" + dependencies: + lodash: "npm:^4.17.21" + vscode-json-languageservice: "npm:^4.1.6" + checksum: 10c0/7b253b398417afa7c2fe3771398c1f118443b12f4af19b679336cd77692efbe333dd0eaaa2a900c76dad471533873aca261fbfa38c8af6e9eb890c0b87f3015c + languageName: node + linkType: hard + +"eslint-plugin-jsx-a11y@npm:^6.7.1": + version: 6.10.2 + resolution: "eslint-plugin-jsx-a11y@npm:6.10.2" + dependencies: + aria-query: "npm:^5.3.2" + array-includes: "npm:^3.1.8" + array.prototype.flatmap: "npm:^1.3.2" + ast-types-flow: "npm:^0.0.8" + axe-core: "npm:^4.10.0" + axobject-query: "npm:^4.1.0" + damerau-levenshtein: "npm:^1.0.8" + emoji-regex: "npm:^9.2.2" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^3.3.5" + language-tags: "npm:^1.0.9" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.8" + safe-regex-test: "npm:^1.0.3" + string.prototype.includes: "npm:^2.0.1" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + checksum: 10c0/d93354e03b0cf66f018d5c50964e074dffe4ddf1f9b535fa020d19c4ae45f89c1a16e9391ca61ac3b19f7042c751ac0d361a056a65cbd1de24718a53ff8daa6e + languageName: node + linkType: hard + +"eslint-plugin-lodash@npm:^7.4.0": + version: 7.4.0 + resolution: "eslint-plugin-lodash@npm:7.4.0" + dependencies: + lodash: "npm:^4.17.21" + peerDependencies: + eslint: ">=2" + checksum: 10c0/6bd681e7374d4c7f9d150556206eb76e9240b0423ffc65a74178816102ba7063227cc296ed8e10c40108e040935fd29871db4ba0a6b6c6eeef49ee341b203ff4 + languageName: node + linkType: hard + +"eslint-plugin-mocha@npm:10.5.0": + version: 10.5.0 + resolution: "eslint-plugin-mocha@npm:10.5.0" + dependencies: + eslint-utils: "npm:^3.0.0" + globals: "npm:^13.24.0" + rambda: "npm:^7.4.0" + peerDependencies: + eslint: ">=7.0.0" + checksum: 10c0/49b5d3a9df038048bd4483f4d4c3b9581eec74309e197abf202376fe3d3a07812dd753a917c83fa89028f89d74be321303dc4917387e9a67450649f0e3a1ffe9 + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^4.6.0": + version: 4.6.2 + resolution: "eslint-plugin-react-hooks@npm:4.6.2" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + checksum: 10c0/4844e58c929bc05157fb70ba1e462e34f1f4abcbc8dd5bbe5b04513d33e2699effb8bca668297976ceea8e7ebee4e8fc29b9af9d131bcef52886feaa2308b2cc + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.32.2": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" + dependencies: + array-includes: "npm:^3.1.8" + array.prototype.findlast: "npm:^1.2.5" + array.prototype.flatmap: "npm:^1.3.3" + array.prototype.tosorted: "npm:^1.1.4" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.2.1" + estraverse: "npm:^5.3.0" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.9" + object.fromentries: "npm:^2.0.8" + object.values: "npm:^1.2.1" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.12" + string.prototype.repeat: "npm:^1.0.0" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 + languageName: node + linkType: hard + +"eslint-plugin-small-import@npm:^1.0.0": + version: 1.0.0 + resolution: "eslint-plugin-small-import@npm:1.0.0" + dependencies: + eslint: "npm:^6.7.2" + peerDependencies: + eslint: ^6.1.0 + checksum: 10c0/b97403313b8279f8049abb907d24a492c340f5c6d8adf0b668e70781aa560347c82537fee5165b7b241b182a7c13c08ae95ce3f1fc2eb74c93c49aca4598244c + languageName: node + linkType: hard + +"eslint-plugin-sort-destructure-keys@npm:^1.5.0": + version: 1.6.0 + resolution: "eslint-plugin-sort-destructure-keys@npm:1.6.0" + dependencies: + natural-compare-lite: "npm:^1.4.0" + peerDependencies: + eslint: 3 - 9 + checksum: 10c0/274702971da1d179c2066f9beaa59d883e08cb7fbc257ea5826e8ffbca79c22b0a23c198d35857ac6364426e654eee91d6bd494f6124898ce0687da28f43a122 + languageName: node + linkType: hard + +"eslint-plugin-unicorn@npm:^46.0.0": + version: 46.0.1 + resolution: "eslint-plugin-unicorn@npm:46.0.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.19.1" + "@eslint-community/eslint-utils": "npm:^4.1.2" + ci-info: "npm:^3.6.1" + clean-regexp: "npm:^1.0.0" + esquery: "npm:^1.4.0" + indent-string: "npm:^4.0.0" + is-builtin-module: "npm:^3.2.0" + jsesc: "npm:^3.0.2" + lodash: "npm:^4.17.21" + pluralize: "npm:^8.0.0" + read-pkg-up: "npm:^7.0.1" + regexp-tree: "npm:^0.1.24" + regjsparser: "npm:^0.9.1" + safe-regex: "npm:^2.1.1" + semver: "npm:^7.3.8" + strip-indent: "npm:^3.0.0" + peerDependencies: + eslint: ">=8.28.0" + checksum: 10c0/e15bf2ef97672aa2d1b2d7de864886b4d9ce3ed22cc0b15953a5454bdfe0aedb994679fa9bf13fcd4d62d64de0222cfb5d8621f2c3a53ca5517ea576f80a2df6 + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.0.0, eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + languageName: node + linkType: hard + +"eslint-utils@npm:^1.4.3": + version: 1.4.3 + resolution: "eslint-utils@npm:1.4.3" + dependencies: + eslint-visitor-keys: "npm:^1.1.0" + checksum: 10c0/ba19a817177d5fc54ae89cd80ecc8bc24eefd640bd8b0db204f29dc79cf9621bb42d68bf31eae6c89ca1f52d748b6583214f57288f9a78d2bd368a2340abe41c + languageName: node + linkType: hard + +"eslint-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "eslint-utils@npm:3.0.0" + dependencies: + eslint-visitor-keys: "npm:^2.0.0" + peerDependencies: + eslint: ">=5" + checksum: 10c0/45aa2b63667a8d9b474c98c28af908d0a592bed1a4568f3145cd49fb5d9510f545327ec95561625290313fe126e6d7bdfe3fdbdb6f432689fab6b9497d3bfb52 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^1.1.0": + version: 1.3.0 + resolution: "eslint-visitor-keys@npm:1.3.0" + checksum: 10c0/10c91fdbbe36810dd4308e57f9a8bc7177188b2a70247e54e3af1fa05ebc66414ae6fd4ce3c6c6821591f43a556e9037bc6b071122e099b5f8b7d2f76df553e3 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.0.0, eslint-visitor-keys@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-visitor-keys@npm:2.1.0" + checksum: 10c0/9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 + languageName: node + linkType: hard + +"eslint@npm:8.57.1, eslint@npm:^8.38.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.1" + "@humanwhocodes/config-array": "npm:^0.13.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + languageName: node + linkType: hard + +"eslint@npm:^6.7.2": + version: 6.8.0 + resolution: "eslint@npm:6.8.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + ajv: "npm:^6.10.0" + chalk: "npm:^2.1.0" + cross-spawn: "npm:^6.0.5" + debug: "npm:^4.0.1" + doctrine: "npm:^3.0.0" + eslint-scope: "npm:^5.0.0" + eslint-utils: "npm:^1.4.3" + eslint-visitor-keys: "npm:^1.1.0" + espree: "npm:^6.1.2" + esquery: "npm:^1.0.1" + esutils: "npm:^2.0.2" + file-entry-cache: "npm:^5.0.1" + functional-red-black-tree: "npm:^1.0.1" + glob-parent: "npm:^5.0.0" + globals: "npm:^12.1.0" + ignore: "npm:^4.0.6" + import-fresh: "npm:^3.0.0" + imurmurhash: "npm:^0.1.4" + inquirer: "npm:^7.0.0" + is-glob: "npm:^4.0.0" + js-yaml: "npm:^3.13.1" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.3.0" + lodash: "npm:^4.17.14" + minimatch: "npm:^3.0.4" + mkdirp: "npm:^0.5.1" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.8.3" + progress: "npm:^2.0.0" + regexpp: "npm:^2.0.1" + semver: "npm:^6.1.2" + strip-ansi: "npm:^5.2.0" + strip-json-comments: "npm:^3.0.1" + table: "npm:^5.2.3" + text-table: "npm:^0.2.0" + v8-compile-cache: "npm:^2.0.3" + bin: + eslint: ./bin/eslint.js + checksum: 10c0/95cd68b3bee8fcabf4468d3fcdfe74baefa878556312ad9ffd25715fe0dc96d6a7d381133bd9307dc24604a14c74fd86ce4fa3851b10783bb69a456b2d7a4acb + languageName: node + linkType: hard + +"espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b + languageName: node + linkType: hard + +"espree@npm:^6.1.2": + version: 6.2.1 + resolution: "espree@npm:6.2.1" + dependencies: + acorn: "npm:^7.1.1" + acorn-jsx: "npm:^5.2.0" + eslint-visitor-keys: "npm:^1.1.0" + checksum: 10c0/499b47bc599ac3515598072ca787016bdaf0d463467ee1c7113061949359a26d74b8fb344afdad63e38b0e81c7b068013125f7a123d0776e0d75fffe2fc9cfac + languageName: node + linkType: hard + +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.0.1, esquery@npm:^1.4.0, esquery@npm:^1.4.2, esquery@npm:^1.6.0": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.4": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + +"events@npm:^3.0.0, events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"execa@npm:5.0.0": + version: 5.0.0 + resolution: "execa@npm:5.0.0" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/e110add7ca0de63aea415385ebad7236c8de281d5d9a916dbd69f59009dac3d5d631e6252c2ea5d0258220b0d22acf25649b2caf05fa162eaa1401339fc69ba4 + languageName: node + linkType: hard + +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 + languageName: node + linkType: hard + +"expand-tilde@npm:^2.0.2": + version: 2.0.2 + resolution: "expand-tilde@npm:2.0.2" + dependencies: + homedir-polyfill: "npm:^1.0.1" + checksum: 10c0/205a60497422746d1c3acbc1d65bd609b945066f239a2b785e69a7a651ac4cbeb4e08555b1ea0023abbe855e6fcb5bbf27d0b371367fdccd303d4fb2b4d66845 + languageName: node + linkType: hard + +"expect-type@npm:0.13.0": + version: 0.13.0 + resolution: "expect-type@npm:0.13.0" + checksum: 10c0/dd1d1f7738e465db7b700292e3884440eea185f28d6ccd9a3dc41fb13c4cd82c6943d886ca9292d29aa97cad936a20220d606154ab448d3d5d2ef127515c6188 + languageName: node + linkType: hard + +"expect-type@npm:0.19.0": + version: 0.19.0 + resolution: "expect-type@npm:0.19.0" + checksum: 10c0/0a7305021c3e37bf024ce01f0a141de109435ac4225457c35fbd649a0d8cdc19b9078185fd9aaa9ebb136a88c5065d27a60883ab0f961ddc281c11851ed18097 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + languageName: node + linkType: hard + +"extend@npm:^3.0.2": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + +"external-editor@npm:^3.0.3": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: "npm:^0.7.0" + iconv-lite: "npm:^0.4.24" + tmp: "npm:^0.0.33" + checksum: 10c0/c98f1ba3efdfa3c561db4447ff366a6adb5c1e2581462522c56a18bf90dfe4da382f9cd1feee3e330108c3595a854b218272539f311ba1b3298f841eb0fbf339 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:3.3.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.3": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-levenshtein@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-levenshtein@npm:3.0.0" + dependencies: + fastest-levenshtein: "npm:^1.0.7" + checksum: 10c0/9e147c682bd0ca54474f1cbf906f6c45262fd2e7c051d2caf2cc92729dcf66949dc809f2392de6adbe1c8716fdf012f91ce38c9422aef63b5732fc688eee4046 + languageName: node + linkType: hard + +"fast-xml-parser@npm:5.2.5, fast-xml-parser@npm:^5.0.7": + version: 5.2.5 + resolution: "fast-xml-parser@npm:5.2.5" + dependencies: + strnum: "npm:^2.1.0" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/d1057d2e790c327ccfc42b872b91786a4912a152d44f9507bf053f800102dfb07ece3da0a86b33ff6a0caa5a5cad86da3326744f6ae5efb0c6c571d754fe48cd + languageName: node + linkType: hard + +"fast-xml-parser@npm:^4.2.5, fast-xml-parser@npm:^4.4.1": + version: 4.5.3 + resolution: "fast-xml-parser@npm:4.5.3" + dependencies: + strnum: "npm:^1.1.1" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/bf9ccadacfadc95f6e3f0e7882a380a7f219cf0a6f96575149f02cb62bf44c3b7f0daee75b8ff3847bcfd7fbcb201e402c71045936c265cf6d94b141ec4e9327 + languageName: node + linkType: hard + +"fastest-levenshtein@npm:^1.0.16, fastest-levenshtein@npm:^1.0.7": + version: 1.0.16 + resolution: "fastest-levenshtein@npm:1.0.16" + checksum: 10c0/7e3d8ae812a7f4fdf8cad18e9cde436a39addf266a5986f653ea0d81e0de0900f50c0f27c6d5aff3f686bcb48acbd45be115ae2216f36a6a13a7dbbf5cad878b + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.19.1 + resolution: "fastq@npm:1.19.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + languageName: node + linkType: hard + +"fdir@npm:^6.4.3, fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + +"figures@npm:3.2.0, figures@npm:^3.0.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10c0/9c421646ede432829a50bc4e55c7a4eb4bcb7cc07b5bab2f471ef1ab9a344595bbebb6c5c21470093fbb730cd81bbca119624c40473a125293f656f49cb47629 + languageName: node + linkType: hard + +"file-entry-cache@npm:^5.0.1": + version: 5.0.1 + resolution: "file-entry-cache@npm:5.0.1" + dependencies: + flat-cache: "npm:^2.0.1" + checksum: 10c0/2480fb523a0342b8ede8b17717517f69ce6b775083d06e50e2e10ca45f16c94f1d4d95976ae627735716174033374a2d6717ba4b58569e1fea8564a1b1f2e4c1 + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 + languageName: node + linkType: hard + +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-cache-dir@npm:^3.2.0": + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^3.0.2" + pkg-dir: "npm:^4.1.0" + checksum: 10c0/92747cda42bff47a0266b06014610981cfbb71f55d60f2c8216bc3108c83d9745507fb0b14ecf6ab71112bed29cd6fb1a137ee7436179ea36e11287e3159e587 + languageName: node + linkType: hard + +"find-up@npm:^2.0.0": + version: 2.1.0 + resolution: "find-up@npm:2.1.0" + dependencies: + locate-path: "npm:^2.0.0" + checksum: 10c0/c080875c9fe28eb1962f35cbe83c683796a0321899f1eed31a37577800055539815de13d53495049697d3ba313013344f843bb9401dd337a1b832be5edfc6840 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"find-yarn-workspace-root@npm:^2.0.0": + version: 2.0.0 + resolution: "find-yarn-workspace-root@npm:2.0.0" + dependencies: + micromatch: "npm:^4.0.2" + checksum: 10c0/b0d3843013fbdaf4e57140e0165889d09fa61745c9e85da2af86e54974f4cc9f1967e40f0d8fc36a79d53091f0829c651d06607d552582e53976f3cd8f4e5689 + languageName: node + linkType: hard + +"flat-cache@npm:^2.0.1": + version: 2.0.1 + resolution: "flat-cache@npm:2.0.1" + dependencies: + flatted: "npm:^2.0.0" + rimraf: "npm:2.6.3" + write: "npm:1.0.3" + checksum: 10c0/09e4d2300d05734eb2ac39ea0bb9cc6d64c0c856f6b77d2bdc7734490b4a8f860d721f59ffd5508f6938c577f8394fe7b5f6acf99a5ec4af6478d7c5c8390bcb + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + languageName: node + linkType: hard + +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe + languageName: node + linkType: hard + +"flatted@npm:^2.0.0": + version: 2.0.2 + resolution: "flatted@npm:2.0.2" + checksum: 10c0/1895ac3971c3a71b0384c6d8b8ef1f217dfe7e72bc7dabc51ef532772b761e626dd7cae53a729b9e65389409d7f03fd84f1900bde4f9e719ed04e57e8510e0d4 + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + languageName: node + linkType: hard + +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.6": + version: 1.15.9 + resolution: "follow-redirects@npm:1.15.9" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/5829165bd112c3c0e82be6c15b1a58fa9dcfaede3b3c54697a82fe4a62dd5ae5e8222956b448d2f98e331525f05d00404aba7d696de9e761ef6e42fdc780244f + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"foreground-child@npm:^2.0.0": + version: 2.0.0 + resolution: "foreground-child@npm:2.0.0" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/6719982783a448162f9a01500757fb2053bc5dcd4d67c7cd30739b38ccc01b39f84e408c30989d1d8774519c021c0498e2450ab127690fb09d7f2568fd94ffcc + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"form-data-encoder@npm:^2.1.2": + version: 2.1.4 + resolution: "form-data-encoder@npm:2.1.4" + checksum: 10c0/4c06ae2b79ad693a59938dc49ebd020ecb58e4584860a90a230f80a68b026483b022ba5e4143cff06ae5ac8fd446a0b500fabc87bbac3d1f62f2757f8dabcaf7 + languageName: node + linkType: hard + +"form-data@npm:^2.5.0": + version: 2.5.3 + resolution: "form-data@npm:2.5.3" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + mime-types: "npm:^2.1.35" + safe-buffer: "npm:^5.2.1" + checksum: 10c0/48b910745d4fcd403f3d6876e33082a334e712199b8c86c4eb82f6da330a59b859943999d793856758c5ff18ca5261ced4d1062235a14543022d986bd21faa7d + languageName: node + linkType: hard + +"form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695 + languageName: node + linkType: hard + +"fromentries@npm:^1.2.0": + version: 1.3.2 + resolution: "fromentries@npm:1.3.2" + checksum: 10c0/63938819a86e39f490b0caa1f6b38b8ad04f41ccd2a1c144eb48a21f76e4dbc074bc62e97abb053c7c1f541ecc70cf0b8aaa98eed3fe02206db9b6f9bb9a6a47 + languageName: node + linkType: hard + +"front-matter@npm:^4.0.2": + version: 4.0.2 + resolution: "front-matter@npm:4.0.2" + dependencies: + js-yaml: "npm:^3.13.1" + checksum: 10c0/7a0df5ca37428dd563c057bc17a8940481fe53876609bcdc443a02ce463c70f1842c7cb4628b80916de46a253732794b36fb6a31105db0f185698a93acee4011 + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 + languageName: node + linkType: hard + +"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a + languageName: node + linkType: hard + +"fs-extra@npm:^8.1": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + +"fs-jetpack@npm:5.1.0": + version: 5.1.0 + resolution: "fs-jetpack@npm:5.1.0" + dependencies: + minimatch: "npm:^5.1.0" + checksum: 10c0/e4961131bebc9c39b23f1c9d4e19c2d6228ed918a6b12749f239829b35748fcd5f7a6f1f201f061cd9720058f4b41138159e9650d56f675e5741426b75b260e0 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 + languageName: node + linkType: hard + +"functional-red-black-tree@npm:^1.0.1": + version: 1.0.1 + resolution: "functional-red-black-tree@npm:1.0.1" + checksum: 10c0/5959eed0375803d9924f47688479bb017e0c6816a0e5ac151e22ba6bfe1d12c41de2f339188885e0aa8eeea2072dad509d8e4448467e816bde0a2ca86a0670d3 + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 + languageName: node + linkType: hard + +"gauge@npm:^4.0.3": + version: 4.0.4 + resolution: "gauge@npm:4.0.4" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.3" + console-control-strings: "npm:^1.1.0" + has-unicode: "npm:^2.0.1" + signal-exit: "npm:^3.0.7" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.5" + checksum: 10c0/ef10d7981113d69225135f994c9f8c4369d945e64a8fc721d655a3a38421b738c9fe899951721d1b47b73c41fdb5404ac87cc8903b2ecbed95d2800363e7e58c + languageName: node + linkType: hard + +"gaxios@npm:^6.0.0, gaxios@npm:^6.0.2, gaxios@npm:^6.1.1": + version: 6.7.1 + resolution: "gaxios@npm:6.7.1" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + is-stream: "npm:^2.0.0" + node-fetch: "npm:^2.6.9" + uuid: "npm:^9.0.1" + checksum: 10c0/53e92088470661c5bc493a1de29d05aff58b1f0009ec5e7903f730f892c3642a93e264e61904383741ccbab1ce6e519f12a985bba91e13527678b32ee6d7d3fd + languageName: node + linkType: hard + +"gcp-metadata@npm:^6.1.0": + version: 6.1.1 + resolution: "gcp-metadata@npm:6.1.1" + dependencies: + gaxios: "npm:^6.1.1" + google-logging-utils: "npm:^0.0.2" + json-bigint: "npm:^1.0.0" + checksum: 10c0/71f6ad4800aa622c246ceec3955014c0c78cdcfe025971f9558b9379f4019f5e65772763428ee8c3244fa81b8631977316eaa71a823493f82e5c44d7259ffac8 + languageName: node + linkType: hard + +"generate-function@npm:^2.3.1": + version: 2.3.1 + resolution: "generate-function@npm:2.3.1" + dependencies: + is-property: "npm:^1.0.2" + checksum: 10c0/4645cf1da90375e46a6f1dc51abc9933e5eafa4cd1a44c2f7e3909a30a4e9a1a08c14cd7d5b32da039da2dba2a085e1ed4597b580c196c3245b2d35d8bc0de5d + languageName: node + linkType: hard + +"generic-pool@npm:^3.8.2": + version: 3.9.0 + resolution: "generic-pool@npm:3.9.0" + checksum: 10c0/6b314d0d71170d5cbaf7162c423f53f8d6556b2135626a65bcdc03c089840b0a2f59eeb2d907939b8200e945eaf71ceb6630426f22d2128a1d242aec4b232aa7 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.0": + version: 1.4.0 + resolution: "get-east-asian-width@npm:1.4.0" + checksum: 10c0/4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83 + languageName: node + linkType: hard + +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-pkg-repo@npm:^4.2.1": + version: 4.2.1 + resolution: "get-pkg-repo@npm:4.2.1" + dependencies: + "@hutson/parse-repository-url": "npm:^3.0.0" + hosted-git-info: "npm:^4.0.0" + through2: "npm:^2.0.0" + yargs: "npm:^16.2.0" + bin: + get-pkg-repo: src/cli.js + checksum: 10c0/1338d2e048a594da4a34e7dd69d909376d72784f5ba50963a242b4b35db77533786f618b3f6a9effdee2af20af4917a3b7cf12533b4575d7f9c163886be1fb62 + languageName: node + linkType: hard + +"get-port@npm:5.1.1": + version: 5.1.1 + resolution: "get-port@npm:5.1.1" + checksum: 10c0/2873877a469b24e6d5e0be490724a17edb39fafc795d1d662e7bea951ca649713b4a50117a473f9d162312cb0e946597bd0e049ed2f866e79e576e8e213d3d1c + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stdin@npm:^9.0.0": + version: 9.0.0 + resolution: "get-stdin@npm:9.0.0" + checksum: 10c0/7ef2edc0c81a0644ca9f051aad8a96ae9373d901485abafaabe59fd347a1c378689d8a3d8825fb3067415d1d09dfcaa43cb9b9516ecac6b74b3138b65a8ccc6b + languageName: node + linkType: hard + +"get-stream@npm:6.0.0": + version: 6.0.0 + resolution: "get-stream@npm:6.0.0" + checksum: 10c0/7cd835cb9180041e7be2cc3de236e5db9f2144515921aeb60ae78d3a46f9944439d654c2aae5b0191e41eb6e2500f0237494a2e6c0790367183f788d1c9f6dd6 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.10.0": + version: 4.10.0 + resolution: "get-tsconfig@npm:4.10.0" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/c9b5572c5118923c491c04285c73bd55b19e214992af957c502a3be0fc0043bb421386ffd45ca3433c0a7fba81221ca300479e8393960acf15d0ed4563f38a86 + languageName: node + linkType: hard + +"git-hooks-list@npm:^3.0.0": + version: 3.2.0 + resolution: "git-hooks-list@npm:3.2.0" + checksum: 10c0/6fdbc727da8e5a6fd9be47b40dd896db3a5c38196a3a52d2f0ed66fe28a6e0df50128b6e674d52b04fa5932a395b693441da9c0cfa7df16f1eff83aee042b127 + languageName: node + linkType: hard + +"git-raw-commits@npm:^3.0.0": + version: 3.0.0 + resolution: "git-raw-commits@npm:3.0.0" + dependencies: + dargs: "npm:^7.0.0" + meow: "npm:^8.1.2" + split2: "npm:^3.2.2" + bin: + git-raw-commits: cli.js + checksum: 10c0/2a5db2e4b5b1ef7b6ecbdc175e559920a5400cbdb8d36f130aaef3588bfd74d8650b354a51ff89e0929eadbb265a00078a6291ff26248a525f0b2f079b001bf6 + languageName: node + linkType: hard + +"git-remote-origin-url@npm:^2.0.0": + version: 2.0.0 + resolution: "git-remote-origin-url@npm:2.0.0" + dependencies: + gitconfiglocal: "npm:^1.0.0" + pify: "npm:^2.3.0" + checksum: 10c0/3a846ce98ed36b2d0b801e8ec1ab299a236cfc6fa264bfdf9f42301abfdfd8715c946507fd83a10b9db449eb609ac6f8a2a341daf52e3af0000367487f486355 + languageName: node + linkType: hard + +"git-semver-tags@npm:^5.0.0": + version: 5.0.1 + resolution: "git-semver-tags@npm:5.0.1" + dependencies: + meow: "npm:^8.1.2" + semver: "npm:^7.0.0" + bin: + git-semver-tags: cli.js + checksum: 10c0/7cacba2f4ac19c0ccb8e6bb7301409376e5a2cc178692667afff453e6fe81f79b5f3f5040343e2be127a2f34977528d354de2aa32430917e90b64884debd3102 + languageName: node + linkType: hard + +"git-up@npm:^7.0.0": + version: 7.0.0 + resolution: "git-up@npm:7.0.0" + dependencies: + is-ssh: "npm:^1.4.0" + parse-url: "npm:^8.1.0" + checksum: 10c0/a3fa02e1a63c7c824b5ebbf23f4a9a6b34dd80031114c5dd8adb7ef53493642e39d3d80dfef4025a452128400c35c2c138d20a0f6ae5d7d7ef70d9ba13083d34 + languageName: node + linkType: hard + +"git-url-parse@npm:14.0.0": + version: 14.0.0 + resolution: "git-url-parse@npm:14.0.0" + dependencies: + git-up: "npm:^7.0.0" + checksum: 10c0/d360cf23c6278e302b74603f3dc490c3fe22e533d58b7f35e0295fad9af209ce5046a55950ccbf2f0d18de7931faefb4353e3f3fd3dda87fce77b409d48e0ba9 + languageName: node + linkType: hard + +"gitconfiglocal@npm:^1.0.0": + version: 1.0.0 + resolution: "gitconfiglocal@npm:1.0.0" + dependencies: + ini: "npm:^1.3.2" + checksum: 10c0/cfcb16344834113199f209f2758ced778dc30e075ddb49b5dde659b4dd2deadee824db0a1b77e1303cb594d9e8b2240da18c67705f657aa76affb444aa349005 + languageName: node + linkType: hard + +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 + languageName: node + linkType: hard + +"github-slugger@npm:^2": + version: 2.0.0 + resolution: "github-slugger@npm:2.0.0" + checksum: 10c0/21b912b6b1e48f1e5a50b2292b48df0ff6abeeb0691b161b3d93d84f4ae6b1acd6ae23702e914af7ea5d441c096453cf0f621b72d57893946618d21dd1a1c486 + languageName: node + linkType: hard + +"glob-parent@npm:6.0.2, glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob-parent@npm:^5.0.0, glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.5": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"glob@npm:^9.2.0": + version: 9.3.5 + resolution: "glob@npm:9.3.5" + dependencies: + fs.realpath: "npm:^1.0.0" + minimatch: "npm:^8.0.2" + minipass: "npm:^4.2.4" + path-scurry: "npm:^1.6.1" + checksum: 10c0/2f6c2b9ee019ee21dc258ae97a88719614591e4c979cb4580b1b9df6f0f778a3cb38b4bdaf18dfa584637ea10f89a3c5f2533a5e449cf8741514ad18b0951f2e + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^12.1.0": + version: 12.4.0 + resolution: "globals@npm:12.4.0" + dependencies: + type-fest: "npm:^0.8.1" + checksum: 10c0/f6731c915f5fde6ac3737016be748682fdf52a436e4f2702d14cec4f15f3aefcc306483bdae4c9195b04848416443f57e9de6771c74fd77becfbba2d2068bc73 + languageName: node + linkType: hard + +"globals@npm:^13.19.0, globals@npm:^13.24.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd + languageName: node + linkType: hard + +"globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"google-auth-library@npm:^9.6.3": + version: 9.15.1 + resolution: "google-auth-library@npm:9.15.1" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^6.1.1" + gcp-metadata: "npm:^6.1.0" + gtoken: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10c0/6eef36d9a9cb7decd11e920ee892579261c6390104b3b24d3e0f3889096673189fe2ed0ee43fd563710e2560de98e63ad5aa4967b91e7f4e69074a422d5f7b65 + languageName: node + linkType: hard + +"google-logging-utils@npm:^0.0.2": + version: 0.0.2 + resolution: "google-logging-utils@npm:0.0.2" + checksum: 10c0/9a4bbd470dd101c77405e450fffca8592d1d7114f245a121288d04a957aca08c9dea2dd1a871effe71e41540d1bb0494731a0b0f6fea4358e77f06645e4268c1 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"got@npm:^13": + version: 13.0.0 + resolution: "got@npm:13.0.0" + dependencies: + "@sindresorhus/is": "npm:^5.2.0" + "@szmarczak/http-timer": "npm:^5.0.1" + cacheable-lookup: "npm:^7.0.0" + cacheable-request: "npm:^10.2.8" + decompress-response: "npm:^6.0.0" + form-data-encoder: "npm:^2.1.2" + get-stream: "npm:^6.0.1" + http2-wrapper: "npm:^2.1.10" + lowercase-keys: "npm:^3.0.0" + p-cancelable: "npm:^3.0.0" + responselike: "npm:^3.0.0" + checksum: 10c0/d6a4648dc46f1f9df2637b8730d4e664349a93cb6df62c66dfbb48f7887ba79742a1cc90739a4eb1c15f790ca838ff641c5cdecdc877993627274aeb0f02b92d + languageName: node + linkType: hard + +"graceful-fs@npm:4.2.10": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 + languageName: node + linkType: hard + +"graceful-fs@npm:4.2.11, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"gtoken@npm:^7.0.0": + version: 7.1.0 + resolution: "gtoken@npm:7.1.0" + dependencies: + gaxios: "npm:^6.0.0" + jws: "npm:^4.0.0" + checksum: 10c0/0a3dcacb1a3c4578abe1ee01c7d0bf20bffe8ded3ee73fc58885d53c00f6eb43b4e1372ff179f0da3ed5cfebd5b7c6ab8ae2776f1787e90d943691b4fe57c716 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.7": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10c0/7aff423ea38a14bb379316f3857fe0df3c5d66119270944247f155ba1f08e07a92b340c58edaa00cfe985c21508870ee5183e0634dcb53dd405f35c93ef7f10d + languageName: node + linkType: hard + +"hard-rejection@npm:^2.1.0": + version: 2.1.0 + resolution: "hard-rejection@npm:2.1.0" + checksum: 10c0/febc3343a1ad575aedcc112580835b44a89a89e01f400b4eda6e8110869edfdab0b00cd1bd4c3bfec9475a57e79e0b355aecd5be46454b6a62b9a359af60e564 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"has-unicode@npm:2.0.1, has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c + languageName: node + linkType: hard + +"hasha@npm:^5.0.0": + version: 5.2.2 + resolution: "hasha@npm:5.2.2" + dependencies: + is-stream: "npm:^2.0.0" + type-fest: "npm:^0.8.0" + checksum: 10c0/9d10d4e665a37beea6e18ba3a0c0399a05b26e505c5ff2fe9115b64fedb3ca95f68c89cf15b08ee4d09fd3064b5e1bfc8e8247353c7aa6b7388471d0f86dca74 + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: "npm:^1.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/c9f295d9d8e38fa50679281fd70d80726962256e888a76c8e72e526453da7a1832dcb427caa716c1ad5d79841d4537301b90156fa30298fefd3d68f4ea2181bb + languageName: node + linkType: hard + +"homedir-polyfill@npm:^1.0.1": + version: 1.0.3 + resolution: "homedir-polyfill@npm:1.0.3" + dependencies: + parse-passwd: "npm:^1.0.0" + checksum: 10c0/3c099844f94b8b438f124bd5698bdcfef32b2d455115fb8050d7148e7f7b95fc89ba9922586c491f0e1cdebf437b1053c84ecddb8d596e109e9ac69c5b4a9e27 + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4": + version: 2.8.9 + resolution: "hosted-git-info@npm:2.8.9" + checksum: 10c0/317cbc6b1bbbe23c2a40ae23f3dafe9fa349ce42a89a36f930e3f9c0530c179a3882d2ef1e4141a4c3674d6faaea862138ec55b43ad6f75e387fda2483a13c70 + languageName: node + linkType: hard + +"hosted-git-info@npm:^4.0.0, hosted-git-info@npm:^4.0.1": + version: 4.1.0 + resolution: "hosted-git-info@npm:4.1.0" + dependencies: + lru-cache: "npm:^6.0.0" + checksum: 10c0/150fbcb001600336d17fdbae803264abed013548eea7946c2264c49ebe2ebd8c4441ba71dd23dd8e18c65de79d637f98b22d4760ba5fb2e0b15d62543d0fff07 + languageName: node + linkType: hard + +"hosted-git-info@npm:^7.0.0, hosted-git-info@npm:^7.0.2": + version: 7.0.2 + resolution: "hosted-git-info@npm:7.0.2" + dependencies: + lru-cache: "npm:^10.0.1" + checksum: 10c0/b19dbd92d3c0b4b0f1513cf79b0fc189f54d6af2129eeb201de2e9baaa711f1936929c848b866d9c8667a0f956f34bf4f07418c12be1ee9ca74fd9246335ca1f + languageName: node + linkType: hard + +"html-entities@npm:^2.5.2, html-entities@npm:^2.6.0": + version: 2.6.0 + resolution: "html-entities@npm:2.6.0" + checksum: 10c0/7c8b15d9ea0cd00dc9279f61bab002ba6ca8a7a0f3c36ed2db3530a67a9621c017830d1d2c1c65beb9b8e3436ea663e9cf8b230472e0e413359399413b27c8b7 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-call@npm:^5.2.2": + version: 5.3.0 + resolution: "http-call@npm:5.3.0" + dependencies: + content-type: "npm:^1.0.4" + debug: "npm:^4.1.1" + is-retry-allowed: "npm:^1.1.0" + is-stream: "npm:^2.0.0" + parse-json: "npm:^4.0.0" + tunnel-agent: "npm:^0.6.0" + checksum: 10c0/049da2a367592b76df9099cd9faa3cb55900748ceb119f4cadea01fc43703917934c678aab90ba590d2a2b610360326f09044a933432167ace37f36b9738fbba + languageName: node + linkType: hard + +"http-proxy-agent@npm:^4.0.1": + version: 4.0.1 + resolution: "http-proxy-agent@npm:4.0.1" + dependencies: + "@tootallnate/once": "npm:1" + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/4fa4774d65b5331814b74ac05cefea56854fc0d5989c80b13432c1b0d42a14c9f4342ca3ad9f0359a52e78da12b1744c9f8a28e50042136ea9171675d972a5fd + languageName: node + linkType: hard + +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": "npm:2" + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/32a05e413430b2c1e542e5c74b38a9f14865301dd69dff2e53ddb684989440e3d2ce0c4b64d25eb63cf6283e6265ff979a61cf93e3ca3d23047ddfdc8df34a32 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"http2-wrapper@npm:^2.1.10": + version: 2.2.1 + resolution: "http2-wrapper@npm:2.2.1" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.2.0" + checksum: 10c0/7207201d3c6e53e72e510c9b8912e4f3e468d3ecc0cf3bf52682f2aac9cd99358b896d1da4467380adc151cf97c412bedc59dc13dae90c523f42053a7449eedb + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"https-proxy-agent@npm:~4.0.0": + version: 4.0.0 + resolution: "https-proxy-agent@npm:4.0.0" + dependencies: + agent-base: "npm:5" + debug: "npm:4" + checksum: 10c0/fbba3e037ec04e1850e867064a763b86dd884baae9c5f4ad380504e321068c9e9b5de79cf2f3a28ede7c36036dce905b58d9f51703c5b3884d887114f4887f77 + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: "npm:^2.0.0" + checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + +"husky@npm:9.1.7": + version: 9.1.7 + resolution: "husky@npm:9.1.7" + bin: + husky: bin.js + checksum: 10c0/35bb110a71086c48906aa7cd3ed4913fb913823715359d65e32e0b964cb1e255593b0ae8014a5005c66a68e6fa66c38dcfa8056dbbdfb8b0187c0ffe7ee3a58f + languageName: node + linkType: hard + +"ibm_db@npm:^3.3.4": + version: 3.3.4 + resolution: "ibm_db@npm:3.3.4" + dependencies: + adm-zip: "npm:^0.5.16" + axios: "npm:^1.12.0" + big-integer: "npm:^1.6.51" + bindings: "npm:^1.5.0" + fs-extra: "npm:^11.1.1" + lodash: "npm:^4.17.21" + nan: "npm:^2.23.0" + q: "npm:^1.5.1" + targz: "npm:^1.0.1" + checksum: 10c0/b3317ae0914c3b4af6a710bd6a77d399fa0bb1f8b3fb1642434666233dd9f96ae63ce8e0d66130a731ff63f2743d517cf71cc1121a29d1b0b7117331541921d6 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.0": + version: 0.7.0 + resolution: "iconv-lite@npm:0.7.0" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore-walk@npm:^6.0.4": + version: 6.0.5 + resolution: "ignore-walk@npm:6.0.5" + dependencies: + minimatch: "npm:^9.0.0" + checksum: 10c0/8bd6d37c82400016c7b6538b03422dde8c9d7d3e99051c8357dd205d499d42828522fb4fbce219c9c21b4b069079445bacdc42bbd3e2e073b52856c2646d8a39 + languageName: node + linkType: hard + +"ignore@npm:^4.0.6": + version: 4.0.6 + resolution: "ignore@npm:4.0.6" + checksum: 10c0/836ee7dc7fd9436096e2dba429359dbb9fa0e33d309e2b2d81692f375f6ca82024fc00567f798613d50c6b989e9cd2ad2b065acf116325cde177f02c86b7d4e0 + languageName: node + linkType: hard + +"ignore@npm:^5.0.4, ignore@npm:^5.0.5, ignore@npm:^5.2.0, ignore@npm:^5.3.1": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:~7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d + languageName: node + linkType: hard + +"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"import-local@npm:3.1.0": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/c67ecea72f775fe8684ca3d057e54bdb2ae28c14bf261d2607c269c18ea0da7b730924c06262eca9aed4b8ab31e31d65bc60b50e7296c85908a56e2f7d41ecd2 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"infer-owner@npm:^1.0.4": + version: 1.0.4 + resolution: "infer-owner@npm:1.0.4" + checksum: 10c0/a7b241e3149c26e37474e3435779487f42f36883711f198c45794703c7556bc38af224088bd4d1a221a45b8208ae2c2bcf86200383621434d0c099304481c5b9 + languageName: node + linkType: hard + +"inflection@npm:^3.0.2": + version: 3.0.2 + resolution: "inflection@npm:3.0.2" + checksum: 10c0/ac6b635f029b27834313ce30188d74607fe9751c729bf91698675b2fd82489e0195e884d8a9455676064a74b2db77b407d35b56ada0978d0e8194e72202bf7af + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:^1.3.2, ini@npm:^1.3.4, ini@npm:^1.3.8, ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"ini@npm:^4.1.3, ini@npm:~4.1.0": + version: 4.1.3 + resolution: "ini@npm:4.1.3" + checksum: 10c0/0d27eff094d5f3899dd7c00d0c04ea733ca03a8eb6f9406ce15daac1a81de022cb417d6eaff7e4342451ffa663389c565ffc68d6825eaf686bf003280b945764 + languageName: node + linkType: hard + +"init-package-json@npm:6.0.3": + version: 6.0.3 + resolution: "init-package-json@npm:6.0.3" + dependencies: + "@npmcli/package-json": "npm:^5.0.0" + npm-package-arg: "npm:^11.0.0" + promzard: "npm:^1.0.0" + read: "npm:^3.0.1" + semver: "npm:^7.3.5" + validate-npm-package-license: "npm:^3.0.4" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10c0/a80f024ee041a2cf4d3062ba936abf015cbc32bda625cabe994d1fa4bd942bb9af37a481afd6880d340d3e94d90bf97bed1a0a877cc8c7c9b48e723c2524ae74 + languageName: node + linkType: hard + +"inquirer@npm:^7.0.0": + version: 7.3.3 + resolution: "inquirer@npm:7.3.3" + dependencies: + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-width: "npm:^3.0.0" + external-editor: "npm:^3.0.3" + figures: "npm:^3.0.0" + lodash: "npm:^4.17.19" + mute-stream: "npm:0.0.8" + run-async: "npm:^2.4.0" + rxjs: "npm:^6.6.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + through: "npm:^2.3.6" + checksum: 10c0/96e75974cfd863fe6653c075e41fa5f1a290896df141189816db945debabcd92d3277145f11aef8d2cfca5409ab003ccdd18a099744814057b52a2f27aeb8c94 + languageName: node + linkType: hard + +"inquirer@npm:^8.2.4": + version: 8.2.6 + resolution: "inquirer@npm:8.2.6" + dependencies: + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.1.1" + cli-cursor: "npm:^3.1.0" + cli-width: "npm:^3.0.0" + external-editor: "npm:^3.0.3" + figures: "npm:^3.0.0" + lodash: "npm:^4.17.21" + mute-stream: "npm:0.0.8" + ora: "npm:^5.4.1" + run-async: "npm:^2.4.0" + rxjs: "npm:^7.5.5" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + through: "npm:^2.3.6" + wrap-ansi: "npm:^6.0.1" + checksum: 10c0/eb5724de1778265323f3a68c80acfa899378cb43c24cdcb58661386500e5696b6b0b6c700e046b7aa767fe7b4823c6f04e6ddc268173e3f84116112529016296 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 10c0/932367456f17237533fd1fc9fe179df77957271020b83ea31da50e5cc472d35ef6b5fb8147453274ffd251134472ce24eb6f8d8398d96dee98237cdb81a6c9a7 + languageName: node + linkType: hard + +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + checksum: 10c0/4b35c42b18e40d41378293f82a3ecd9de77049b476f748db5697c297f686e1e05b072a6aaae2d16f54d2a57f85b00cbbe755c75f6d583d1c77d6657bd0feb5a2 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-builtin-module@npm:^3.2.0": + version: 3.2.1 + resolution: "is-builtin-module@npm:3.2.1" + dependencies: + builtin-modules: "npm:^3.3.0" + checksum: 10c0/5a66937a03f3b18803381518f0ef679752ac18cdb7dd53b5e23ee8df8d440558737bd8dcc04d2aae555909d2ecb4a81b5c0d334d119402584b61e6a003e31af1 + languageName: node + linkType: hard + +"is-bun-module@npm:^2.0.0": + version: 2.0.0 + resolution: "is-bun-module@npm:2.0.0" + dependencies: + semver: "npm:^7.7.1" + checksum: 10c0/7d27a0679cfa5be1f5052650391f9b11040cd70c48d45112e312c56bc6b6ca9c9aea70dcce6cc40b1e8947bfff8567a5c5715d3b066fb478522dab46ea379240 + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-ci@npm:3.0.1": + version: 3.0.1 + resolution: "is-ci@npm:3.0.1" + dependencies: + ci-info: "npm:^3.2.0" + bin: + is-ci: bin.js + checksum: 10c0/0e81caa62f4520d4088a5bef6d6337d773828a88610346c4b1119fb50c842587ed8bef1e5d9a656835a599e7209405b5761ddf2339668f2d0f4e889a92fe6051 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0, is-core-module@npm:^2.5.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 10c0/8085dd66f7d82f9de818fba48b9e9c0429cb4291824e6c5f2622e96b9680b54a07a624cfc663b24148b8e853c62a1c987cfe8b0b5a13f5156991afaf6736e334 + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10c0/e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^2.0.0": + version: 2.0.0 + resolution: "is-fullwidth-code-point@npm:2.0.0" + checksum: 10c0/e58f3e4a601fc0500d8b2677e26e9fe0cd450980e66adb29d85b6addf7969731e38f8e43ed2ec868a09c101a55ac3d8b78902209269f38c5286bc98f5bc1b4d9 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^5.0.0": + version: 5.0.0 + resolution: "is-fullwidth-code-point@npm:5.0.0" + dependencies: + get-east-asian-width: "npm:^1.0.0" + checksum: 10c0/cd591b27d43d76b05fa65ed03eddce57a16e1eca0b7797ff7255de97019bcaf0219acfc0c4f7af13319e13541f2a53c0ace476f442b13267b9a6a7568f2b65c8 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.0 + resolution: "is-generator-function@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.0" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/fdfa96c8087bf36fc4cd514b474ba2ff404219a4dd4cfa6cf5426404a1eed259bdcdb98f082a71029a48d01f27733e3436ecc6690129a7ec09cb0434bee03a2a + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 10c0/3eb60fe2f1e2bbc760b927dcad4d51eaa0c60138cf7fc671803f66353ad90c301605b502c7ea4c6bb0548e1c7e79dfd37b73b632652e3b76030bba603a7e9626 + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 10c0/dd47904dbf286cd20aa58c5192161be1a67138485b9836d5a70433b21a45442e9611b8498b8ab1f839fc962c7620667a50535fdfb4a6bc7989b8858645c06b4d + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-obj@npm:^2.0.0": + version: 2.0.0 + resolution: "is-obj@npm:2.0.0" + checksum: 10c0/85044ed7ba8bd169e2c2af3a178cacb92a97aa75de9569d02efef7f443a824b5e153eba72b9ae3aca6f8ce81955271aa2dc7da67a8b720575d3e38104208cb4e + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "is-plain-obj@npm:1.1.0" + checksum: 10c0/daaee1805add26f781b413fdf192fc91d52409583be30ace35c82607d440da63cc4cac0ac55136716688d6c0a2c6ef3edb2254fecbd1fe06056d6bd15975ee8c + languageName: node + linkType: hard + +"is-plain-obj@npm:^2.1.0": + version: 2.1.0 + resolution: "is-plain-obj@npm:2.1.0" + checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 + languageName: node + linkType: hard + +"is-plain-obj@npm:^4.1.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-property@npm:^1.0.2": + version: 1.0.2 + resolution: "is-property@npm:1.0.2" + checksum: 10c0/33ab65a136e4ba3f74d4f7d9d2a013f1bd207082e11cedb160698e8d5394644e873c39668d112a402175ccbc58a087cef87198ed46829dbddb479115a0257283 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-retry-allowed@npm:^1.1.0": + version: 1.2.0 + resolution: "is-retry-allowed@npm:1.2.0" + checksum: 10c0/a80f14e1e11c27a58f268f2927b883b635703e23a853cb7b8436e3456bf2ea3efd5082a4e920093eec7bd372c1ce6ea7cea78a9376929c211039d0cc4a393a44 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-ssh@npm:^1.4.0": + version: 1.4.1 + resolution: "is-ssh@npm:1.4.1" + dependencies: + protocols: "npm:^2.0.1" + checksum: 10c0/021a7355cb032625d58db3cc8266ad9aa698cbabf460b71376a0307405577fd7d3aa0826c0bf1951d7809f134c0ee80403306f6d7633db94a5a3600a0106b398 + languageName: node + linkType: hard + +"is-stream@npm:2.0.0": + version: 2.0.0 + resolution: "is-stream@npm:2.0.0" + checksum: 10c0/687f6bbd2b995573d33e6b40b2cbc8b9186a751aa3151c23e6fd2c4ca352e323a6dc010b09103f89c9ca0bf5c8c38f3fa8b74d5d9acd1c44f1499874d7e844f9 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-string@npm:^1.0.7, is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-text-path@npm:^1.0.1": + version: 1.0.1 + resolution: "is-text-path@npm:1.0.1" + dependencies: + text-extensions: "npm:^1.0.0" + checksum: 10c0/61c8650c29548febb6bf69e9541fc11abbbb087a0568df7bc471ba264e95fb254def4e610631cbab4ddb0a1a07949d06416f4ebeaf37875023fb184cdb87ee84 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-typedarray@npm:^1.0.0": + version: 1.0.0 + resolution: "is-typedarray@npm:1.0.0" + checksum: 10c0/4c096275ba041a17a13cca33ac21c16bc4fd2d7d7eb94525e7cd2c2f2c1a3ab956e37622290642501ff4310601e413b675cf399ad6db49855527d2163b3eeeec + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + languageName: node + linkType: hard + +"is-windows@npm:^1.0.2": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10c0/b32f418ab3385604a66f1b7a3ce39d25e8881dee0bd30816dc8344ef6ff9df473a732bcc1ec4e84fe99b2f229ae474f7133e8e93f9241686cfcf7eebe53ba7a5 + languageName: node + linkType: hard + +"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10c0/a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-hook@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-hook@npm:3.0.0" + dependencies: + append-transform: "npm:^2.0.0" + checksum: 10c0/0029bdbc4ae82c2a5a0b48a2f4ba074de72601a5d27505493c9be83d4c7952039ad787d2f6d1321710b75a05059c4335a0eb7c8857ca82e7e6d19f8d88d03b46 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.2": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-processinfo@npm:^2.0.2": + version: 2.0.3 + resolution: "istanbul-lib-processinfo@npm:2.0.3" + dependencies: + archy: "npm:^1.0.0" + cross-spawn: "npm:^7.0.3" + istanbul-lib-coverage: "npm:^3.2.0" + p-map: "npm:^3.0.0" + rimraf: "npm:^3.0.0" + uuid: "npm:^8.3.2" + checksum: 10c0/ffd0f9b1c8e266e980580f83e65397caeace3958e4b4326b4479dcb0e41a450698387b96b4d4823e63b7c4a403f72e6e30d9e788ddcf153edb422a9d6f64a998 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.0.2": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"iterator.prototype@npm:^1.1.4": + version: 1.1.5 + resolution: "iterator.prototype@npm:1.1.5" + dependencies: + define-data-property: "npm:^1.1.4" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + get-proto: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.9.2 + resolution: "jake@npm:10.9.2" + dependencies: + async: "npm:^3.2.3" + chalk: "npm:^4.0.2" + filelist: "npm:^1.0.4" + minimatch: "npm:^3.1.2" + bin: + jake: bin/cli.js + checksum: 10c0/c4597b5ed9b6a908252feab296485a4f87cba9e26d6c20e0ca144fb69e0c40203d34a2efddb33b3d297b8bd59605e6c1f44f6221ca1e10e69175ecbf3ff5fe31 + languageName: node + linkType: hard + +"jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-diff@npm:^30.0.2": + version: 30.0.5 + resolution: "jest-diff@npm:30.0.5" + dependencies: + "@jest/diff-sequences": "npm:30.0.1" + "@jest/get-type": "npm:30.0.1" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.0.5" + checksum: 10c0/b218ced37b7676f578ea866762f04caa74901bdcf3f593872aa9a4991a586302651a1d16bb0386772adacc7580a452ec621359af75d733c0b50ea947fe1881d3 + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"js-md4@npm:^0.3.2": + version: 0.3.2 + resolution: "js-md4@npm:0.3.2" + checksum: 10c0/8313e00c45f710a53bdadc199c095b48ebaf54ea7b8cdb67a3f1863c270a5e9d0f89f204436b73866002af8c7ac4cacc872fdf271fc70e26614e424c7685b577 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0, js-yaml@npm:~4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsdoc-type-pratt-parser@npm:~6.10.0": + version: 6.10.0 + resolution: "jsdoc-type-pratt-parser@npm:6.10.0" + checksum: 10c0/8ea395df0cae0e41d4bdba5f8d81b8d3e467fe53d1e4182a5d4e653235a5f17d60ed137343d68dbc74fa10e767f1c58fb85b1f6d5489c2cf16fc7216cc6d3e1a + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"jsesc@npm:~0.5.0": + version: 0.5.0 + resolution: "jsesc@npm:0.5.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/f93792440ae1d80f091b65f8ceddf8e55c4bb7f1a09dee5dcbdb0db5612c55c0f6045625aa6b7e8edb2e0a4feabd80ee48616dbe2d37055573a84db3d24f96d9 + languageName: node + linkType: hard + +"json-bigint@npm:^1.0.0": + version: 1.0.0 + resolution: "json-bigint@npm:1.0.0" + dependencies: + bignumber.js: "npm:^9.0.0" + checksum: 10c0/e3f34e43be3284b573ea150a3890c92f06d54d8ded72894556357946aeed9877fd795f62f37fe16509af189fd314ab1104d0fd0f163746ad231b9f378f5b33f4 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-better-errors@npm:^1.0.1": + version: 1.0.2 + resolution: "json-parse-better-errors@npm:1.0.2" + checksum: 10c0/2f1287a7c833e397c9ddd361a78638e828fc523038bb3441fd4fc144cfd2c6cd4963ffb9e207e648cf7b692600f1e1e524e965c32df5152120910e4903a47dcb + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^3.0.0, json-parse-even-better-errors@npm:^3.0.2": + version: 3.0.2 + resolution: "json-parse-even-better-errors@npm:3.0.2" + checksum: 10c0/147f12b005768abe9fab78d2521ce2b7e1381a118413d634a40e6d907d7d10f5e9a05e47141e96d6853af7cc36d2c834d0a014251be48791e037ff2f13d2b94b + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json-stringify-nice@npm:^1.1.4": + version: 1.1.4 + resolution: "json-stringify-nice@npm:1.1.4" + checksum: 10c0/13673b67ba9e7fde75a103cade0b0d2dd0d21cd3b918de8d8f6cd59d48ad8c78b0e85f6f4a5842073ddfc91ebdde5ef7c81c7f51945b96a33eaddc5d41324b87 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: "npm:^1.2.0" + bin: + json5: lib/cli.js + checksum: 10c0/9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f + languageName: node + linkType: hard + +"json5@npm:^2.2.0, json5@npm:^2.2.2, json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonc-parser@npm:3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 10c0/5a12d4d04dad381852476872a29dcee03a57439574e4181d91dca71904fcdcc5e8e4706c0a68a2c61ad9810e1e1c5806b5100d52d3e727b78f5cdc595401045b + languageName: node + linkType: hard + +"jsonc-parser@npm:^3.0.0, jsonc-parser@npm:~3.3.1": + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 + languageName: node + linkType: hard + +"jsonparse@npm:^1.2.0, jsonparse@npm:^1.3.1": + version: 1.3.1 + resolution: "jsonparse@npm:1.3.1" + checksum: 10c0/89bc68080cd0a0e276d4b5ab1b79cacd68f562467008d176dc23e16e97d4efec9e21741d92ba5087a8433526a45a7e6a9d5ef25408696c402ca1cfbc01a90bf0 + languageName: node + linkType: hard + +"jsonpointer@npm:~5.0.1": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 10c0/89929e58b400fcb96928c0504fcf4fc3f919d81e9543ceb055df125538470ee25290bb4984251e172e6ef8fcc55761eb998c118da763a82051ad89d4cb073fe7 + languageName: node + linkType: hard + +"jsonwebtoken@npm:^9.0.0": + version: 9.0.2 + resolution: "jsonwebtoken@npm:9.0.2" + dependencies: + jws: "npm:^3.2.2" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10c0/d287a29814895e866db2e5a0209ce730cbc158441a0e5a70d5e940eb0d28ab7498c6bf45029cc8b479639bca94056e9a7f254e2cdb92a2f5750c7f358657a131 + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": + version: 3.3.5 + resolution: "jsx-ast-utils@npm:3.3.5" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flat: "npm:^1.3.1" + object.assign: "npm:^4.1.4" + object.values: "npm:^1.1.6" + checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 + languageName: node + linkType: hard + +"just-diff-apply@npm:^5.2.0": + version: 5.5.0 + resolution: "just-diff-apply@npm:5.5.0" + checksum: 10c0/d7b85371f2a5a17a108467fda35dddd95264ab438ccec7837b67af5913c57ded7246039d1df2b5bc1ade034ccf815b56d69786c5f1e07383168a066007c796c0 + languageName: node + linkType: hard + +"just-diff@npm:^6.0.0": + version: 6.0.2 + resolution: "just-diff@npm:6.0.2" + checksum: 10c0/1931ca1f0cea4cc480172165c189a84889033ad7a60bee302268ba8ca9f222b43773fd5f272a23ee618d43d85d3048411f06b635571a198159e9a85bb2495f5c + languageName: node + linkType: hard + +"just-extend@npm:^6.2.0": + version: 6.2.0 + resolution: "just-extend@npm:6.2.0" + checksum: 10c0/d41cbdb6d85b986d4deaf2144d81d4f7266cd408fc95189d046d63f610c2dc486b141aeb6ef319c2d76fe904d45a6bb31f19b098ff0427c35688e0c383fc0511 + languageName: node + linkType: hard + +"jwa@npm:^1.4.1": + version: 1.4.1 + resolution: "jwa@npm:1.4.1" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/5c533540bf38702e73cf14765805a94027c66a0aa8b16bc3e89d8d905e61a4ce2791e87e21be97d1293a5ee9d4f3e5e47737e671768265ca4f25706db551d5e9 + languageName: node + linkType: hard + +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c + languageName: node + linkType: hard + +"jws@npm:^3.2.2": + version: 3.2.2 + resolution: "jws@npm:3.2.2" + dependencies: + jwa: "npm:^1.4.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/e770704533d92df358adad7d1261fdecad4d7b66fa153ba80d047e03ca0f1f73007ce5ed3fbc04d2eba09ba6e7e6e645f351e08e5ab51614df1b0aa4f384dfff + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661 + languageName: node + linkType: hard + +"katex@npm:^0.16.0": + version: 0.16.22 + resolution: "katex@npm:0.16.22" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 10c0/07b8b1f07ae53171b5f1ea0cf6f18841d2055825c8b11cd81cfe039afcd3af2cfc84ad033531ee3875088329105195b039c267e0dd4b0c237807e3c3b2009913 + languageName: node + linkType: hard + +"keyv@npm:^4.5.3": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kind-of@npm:^6.0.2, kind-of@npm:^6.0.3": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + +"language-subtag-registry@npm:^0.3.20": + version: 0.3.23 + resolution: "language-subtag-registry@npm:0.3.23" + checksum: 10c0/e9b05190421d2cd36dd6c95c28673019c927947cb6d94f40ba7e77a838629ee9675c94accf897fbebb07923187deb843b8fbb8935762df6edafe6c28dcb0b86c + languageName: node + linkType: hard + +"language-tags@npm:^1.0.9": + version: 1.0.9 + resolution: "language-tags@npm:1.0.9" + dependencies: + language-subtag-registry: "npm:^0.3.20" + checksum: 10c0/9ab911213c4bd8bd583c850201c17794e52cb0660d1ab6e32558aadc8324abebf6844e46f92b80a5d600d0fbba7eface2c207bfaf270a1c7fd539e4c3a880bff + languageName: node + linkType: hard + +"lcov-result-merger@npm:5.0.1": + version: 5.0.1 + resolution: "lcov-result-merger@npm:5.0.1" + dependencies: + fast-glob: "npm:^3.2.11" + yargs: "npm:^16.2.0" + bin: + lcov-result-merger: bin/lcov-result-merger.js + checksum: 10c0/eb8057f0d47f2f5a9633f65147037a4550f6d22623aaf88b3cba1b7ef0b24b4a091613e61a6abbc13cb8ed131d0e0d501cf549d4d16a410fec3efec3da262d98 + languageName: node + linkType: hard + +"lerna@npm:8.2.4": + version: 8.2.4 + resolution: "lerna@npm:8.2.4" + dependencies: + "@lerna/create": "npm:8.2.4" + "@npmcli/arborist": "npm:7.5.4" + "@npmcli/package-json": "npm:5.2.0" + "@npmcli/run-script": "npm:8.1.0" + "@nx/devkit": "npm:>=17.1.2 < 21" + "@octokit/plugin-enterprise-rest": "npm:6.0.1" + "@octokit/rest": "npm:20.1.2" + aproba: "npm:2.0.0" + byte-size: "npm:8.1.1" + chalk: "npm:4.1.0" + clone-deep: "npm:4.0.1" + cmd-shim: "npm:6.0.3" + color-support: "npm:1.1.3" + columnify: "npm:1.6.0" + console-control-strings: "npm:^1.1.0" + conventional-changelog-angular: "npm:7.0.0" + conventional-changelog-core: "npm:5.0.1" + conventional-recommended-bump: "npm:7.0.1" + cosmiconfig: "npm:9.0.0" + dedent: "npm:1.5.3" + envinfo: "npm:7.13.0" + execa: "npm:5.0.0" + fs-extra: "npm:^11.2.0" + get-port: "npm:5.1.1" + get-stream: "npm:6.0.0" + git-url-parse: "npm:14.0.0" + glob-parent: "npm:6.0.2" + graceful-fs: "npm:4.2.11" + has-unicode: "npm:2.0.1" + import-local: "npm:3.1.0" + ini: "npm:^1.3.8" + init-package-json: "npm:6.0.3" + inquirer: "npm:^8.2.4" + is-ci: "npm:3.0.1" + is-stream: "npm:2.0.0" + jest-diff: "npm:>=29.4.3 < 30" + js-yaml: "npm:4.1.0" + libnpmaccess: "npm:8.0.6" + libnpmpublish: "npm:9.0.9" + load-json-file: "npm:6.2.0" + make-dir: "npm:4.0.0" + minimatch: "npm:3.0.5" + multimatch: "npm:5.0.0" + node-fetch: "npm:2.6.7" + npm-package-arg: "npm:11.0.2" + npm-packlist: "npm:8.0.2" + npm-registry-fetch: "npm:^17.1.0" + nx: "npm:>=17.1.2 < 21" + p-map: "npm:4.0.0" + p-map-series: "npm:2.1.0" + p-pipe: "npm:3.1.0" + p-queue: "npm:6.6.2" + p-reduce: "npm:2.1.0" + p-waterfall: "npm:2.1.1" + pacote: "npm:^18.0.6" + pify: "npm:5.0.0" + read-cmd-shim: "npm:4.0.0" + resolve-from: "npm:5.0.0" + rimraf: "npm:^4.4.1" + semver: "npm:^7.3.8" + set-blocking: "npm:^2.0.0" + signal-exit: "npm:3.0.7" + slash: "npm:3.0.0" + ssri: "npm:^10.0.6" + string-width: "npm:^4.2.3" + tar: "npm:6.2.1" + temp-dir: "npm:1.0.0" + through: "npm:2.3.8" + tinyglobby: "npm:0.2.12" + typescript: "npm:>=3 < 6" + upath: "npm:2.0.1" + uuid: "npm:^10.0.0" + validate-npm-package-license: "npm:3.0.4" + validate-npm-package-name: "npm:5.0.1" + wide-align: "npm:1.1.5" + write-file-atomic: "npm:5.0.1" + write-pkg: "npm:4.0.0" + yargs: "npm:17.7.2" + yargs-parser: "npm:21.1.1" + bin: + lerna: dist/cli.js + checksum: 10c0/6fba1b6a437d76978cf0aa67efb88b14643502839c7121b8ab8c6368fa1c1a4806600bf0f7a57ca1cfc52007ff1b45a03269205f9df93fc6fc4fb3179760b8c7 + languageName: node + linkType: hard + +"levn@npm:^0.3.0, levn@npm:~0.3.0": + version: 0.3.0 + resolution: "levn@npm:0.3.0" + dependencies: + prelude-ls: "npm:~1.1.2" + type-check: "npm:~0.3.2" + checksum: 10c0/e440df9de4233da0b389cd55bd61f0f6aaff766400bebbccd1231b81801f6dbc1d816c676ebe8d70566394b749fa624b1ed1c68070e9c94999f0bdecc64cb676 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"libnpmaccess@npm:8.0.6": + version: 8.0.6 + resolution: "libnpmaccess@npm:8.0.6" + dependencies: + npm-package-arg: "npm:^11.0.2" + npm-registry-fetch: "npm:^17.0.1" + checksum: 10c0/0b63c7cb44e024b0225dae8ebfe5166a0be8a9420c1b5fb6a4f1c795e9eabbed0fff5984ab57167c5634145de018008cbeeb27fe6f808f611ba5ba1b849ec3d6 + languageName: node + linkType: hard + +"libnpmpublish@npm:9.0.9": + version: 9.0.9 + resolution: "libnpmpublish@npm:9.0.9" + dependencies: + ci-info: "npm:^4.0.0" + normalize-package-data: "npm:^6.0.1" + npm-package-arg: "npm:^11.0.2" + npm-registry-fetch: "npm:^17.0.1" + proc-log: "npm:^4.2.0" + semver: "npm:^7.3.7" + sigstore: "npm:^2.2.0" + ssri: "npm:^10.0.6" + checksum: 10c0/5e4bae455d33fb7402b8b8fcc505d89a1d60ff4b7dc47dd9ba318426c00400e1892fd0435d8db6baab808f64d7f226cbf8d53792244ffad1df7fc2f94f3237fc + languageName: node + linkType: hard + +"lilconfig@npm:^3.1.3": + version: 3.1.3 + resolution: "lilconfig@npm:3.1.3" + checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc + languageName: node + linkType: hard + +"lines-and-columns@npm:2.0.3": + version: 2.0.3 + resolution: "lines-and-columns@npm:2.0.3" + checksum: 10c0/09525c10010a925b7efe858f1dd3184eeac34f0a9bc34993075ec490efad71e948147746b18e9540279cc87cd44085b038f986903db3de65ffe96d38a7b91c4c + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"linkify-it@npm:^5.0.0": + version: 5.0.0 + resolution: "linkify-it@npm:5.0.0" + dependencies: + uc.micro: "npm:^2.0.0" + checksum: 10c0/ff4abbcdfa2003472fc3eb4b8e60905ec97718e11e33cca52059919a4c80cc0e0c2a14d23e23d8c00e5402bc5a885cdba8ca053a11483ab3cc8b3c7a52f88e2d + languageName: node + linkType: hard + +"lint-staged@npm:16.2.7": + version: 16.2.7 + resolution: "lint-staged@npm:16.2.7" + dependencies: + commander: "npm:^14.0.2" + listr2: "npm:^9.0.5" + micromatch: "npm:^4.0.8" + nano-spawn: "npm:^2.0.0" + pidtree: "npm:^0.6.0" + string-argv: "npm:^0.3.2" + yaml: "npm:^2.8.1" + bin: + lint-staged: bin/lint-staged.js + checksum: 10c0/9a677c21a8112d823ae5bc565ba2c9e7b803786f2a021c46827a55fe44ed59def96edb24fc99c06a2545cdbbf366022ad82addcb3bf60c712f3b98ef92069717 + languageName: node + linkType: hard + +"listr2@npm:^9.0.5": + version: 9.0.5 + resolution: "listr2@npm:9.0.5" + dependencies: + cli-truncate: "npm:^5.0.0" + colorette: "npm:^2.0.20" + eventemitter3: "npm:^5.0.1" + log-update: "npm:^6.1.0" + rfdc: "npm:^1.4.1" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/46448d1ba0addc9d71aeafd05bb8e86ded9641ccad930ac302c2bd2ad71580375604743e18586fcb8f11906edf98e8e17fca75ba0759947bf275d381f68e311d + languageName: node + linkType: hard + +"load-json-file@npm:6.2.0": + version: 6.2.0 + resolution: "load-json-file@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.15" + parse-json: "npm:^5.0.0" + strip-bom: "npm:^4.0.0" + type-fest: "npm:^0.6.0" + checksum: 10c0/fcb46ef75bab917f37170ba76781a1690bf67144bb53931cb0ed8e4aa20ca439e9c354fcf3594aed531f47dbeb4a49800acab7fdffd553c402ac40c987706d7b + languageName: node + linkType: hard + +"load-json-file@npm:^4.0.0": + version: 4.0.0 + resolution: "load-json-file@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.2" + parse-json: "npm:^4.0.0" + pify: "npm:^3.0.0" + strip-bom: "npm:^3.0.0" + checksum: 10c0/6b48f6a0256bdfcc8970be2c57f68f10acb2ee7e63709b386b2febb6ad3c86198f840889cdbe71d28f741cbaa2f23a7771206b138cd1bdd159564511ca37c1d5 + languageName: node + linkType: hard + +"locate-path@npm:^2.0.0": + version: 2.0.0 + resolution: "locate-path@npm:2.0.0" + dependencies: + p-locate: "npm:^2.0.0" + path-exists: "npm:^3.0.0" + checksum: 10c0/24efa0e589be6aa3c469b502f795126b26ab97afa378846cb508174211515633b770aa0ba610cab113caedab8d2a4902b061a08aaed5297c12ab6f5be4df0133 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.flattendeep@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.flattendeep@npm:4.4.0" + checksum: 10c0/83cb80754b921fb4ed2c222b91a82b2524f3bdc60c3ae91e00688bd4bf1bcc28b8a2cc250e11fdc1b6da3a2de09e57008e13f15a209cafdd4f9163d047f97544 + languageName: node + linkType: hard + +"lodash.get@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.get@npm:4.4.2" + checksum: 10c0/48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e + languageName: node + linkType: hard + +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 + languageName: node + linkType: hard + +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 + languageName: node + linkType: hard + +"lodash.ismatch@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.ismatch@npm:4.4.0" + checksum: 10c0/8f96a5dc4b8d3fc5a033dcb259d0c3148a1044fa4d02b4a0e8dce0fa1f2ef3ec4ac131e20b5cb2c985a4e9bcb1c37c0aa5af2cef70094959389617347b8fc645 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + +"lodash@npm:4.17.21, lodash@npm:^4.17.14, lodash@npm:^4.17.19, lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + cli-cursor: "npm:^5.0.0" + slice-ansi: "npm:^7.1.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/4b350c0a83d7753fea34dcac6cd797d1dc9603291565de009baa4aa91c0447eab0d3815a05c8ec9ac04fdfffb43c82adcdb03ec1fceafd8518e1a8c1cff4ff89 + languageName: node + linkType: hard + +"logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10c0/4789b4b37413c731d1835734cb799240d31b865afde6b7b3e06051d6a4127bfda9e88c99cfbf296d084a315ccbed2647796e6a56b66e725bcb268c586f57558f + languageName: node + linkType: hard + +"loglevel@npm:^1.8.0": + version: 1.9.2 + resolution: "loglevel@npm:1.9.2" + checksum: 10c0/1e317fa4648fe0b4a4cffef6de037340592cee8547b07d4ce97a487abe9153e704b98451100c799b032c72bb89c9366d71c9fb8192ada8703269263ae77acdc7 + languageName: node + linkType: hard + +"long@npm:^4.0.0": + version: 4.0.0 + resolution: "long@npm:4.0.0" + checksum: 10c0/50a6417d15b06104dbe4e3d4a667c39b137f130a9108ea8752b352a4cfae047531a3ac351c181792f3f8768fe17cca6b0f406674a541a86fb638aaac560d83ed + languageName: node + linkType: hard + +"long@npm:^5.2.1": + version: 5.3.2 + resolution: "long@npm:5.3.2" + checksum: 10c0/7130fe1cbce2dca06734b35b70d380ca3f70271c7f8852c922a7c62c86c4e35f0c39290565eca7133c625908d40e126ac57c02b1b1a4636b9457d77e1e60b981 + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"loupe@npm:^2.3.6": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10c0/71a781c8fc21527b99ed1062043f1f2bb30bdaf54fa4cf92463427e1718bc6567af2988300bc243c1f276e4f0876f29e3cbf7b58106fdc186915687456ce5bf4 + languageName: node + linkType: hard + +"loupe@npm:^3.1.0": + version: 3.1.3 + resolution: "loupe@npm:3.1.3" + checksum: 10c0/f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 + languageName: node + linkType: hard + +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10c0/3d925e090315cf7dc1caa358e0477e186ffa23947740e4314a7429b6e62d72742e0bbe7536a5ae56d19d7618ce998aba05caca53c2902bd5742fdca5fc57fd7b + languageName: node + linkType: hard + +"lowercase-keys@npm:^3.0.0": + version: 3.0.0 + resolution: "lowercase-keys@npm:3.0.0" + checksum: 10c0/ef62b9fa5690ab0a6e4ef40c94efce68e3ed124f583cc3be38b26ff871da0178a28b9a84ce0c209653bb25ca135520ab87fea7cd411a54ac4899cb2f30501430 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.2.2, lru-cache@npm:^10.4.3": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"lru-cache@npm:^7.14.1": + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed + languageName: node + linkType: hard + +"lru.min@npm:^1.0.0": + version: 1.1.2 + resolution: "lru.min@npm:1.1.2" + checksum: 10c0/64f0cbb155899b62e57b5f0f1e69d5427252cf87cd1dd2ba87d6768da7636ba1e459bd6b97a7632cf50ee9ede927809dab5c50ab76651d56c3cbf970d1b08f5c + languageName: node + linkType: hard + +"lunr@npm:^2.3.9": + version: 2.3.9 + resolution: "lunr@npm:2.3.9" + checksum: 10c0/77d7dbb4fbd602aac161e2b50887d8eda28c0fa3b799159cee380fbb311f1e614219126ecbbd2c3a9c685f1720a8109b3c1ca85cc893c39b6c9cc6a62a1d8a8b + languageName: node + linkType: hard + +"make-dir@npm:4.0.0, make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 + languageName: node + linkType: hard + +"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: "npm:^6.0.0" + checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0, make-fetch-happen@npm:^13.0.1": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" + http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e + languageName: node + linkType: hard + +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^9.1.0": + version: 9.1.0 + resolution: "make-fetch-happen@npm:9.1.0" + dependencies: + agentkeepalive: "npm:^4.1.3" + cacache: "npm:^15.2.0" + http-cache-semantics: "npm:^4.1.0" + http-proxy-agent: "npm:^4.0.1" + https-proxy-agent: "npm:^5.0.0" + is-lambda: "npm:^1.0.1" + lru-cache: "npm:^6.0.0" + minipass: "npm:^3.1.3" + minipass-collect: "npm:^1.0.2" + minipass-fetch: "npm:^1.3.2" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.2" + promise-retry: "npm:^2.0.1" + socks-proxy-agent: "npm:^6.0.0" + ssri: "npm:^8.0.0" + checksum: 10c0/2c737faf6a7f67077679da548b5bfeeef890595bf8c4323a1f76eae355d27ebb33dcf9cf1a673f944cf2f2a7cbf4e2b09f0a0a62931737728f210d902c6be966 + languageName: node + linkType: hard + +"map-obj@npm:^1.0.0": + version: 1.0.1 + resolution: "map-obj@npm:1.0.1" + checksum: 10c0/ccca88395e7d38671ed9f5652ecf471ecd546924be2fb900836b9da35e068a96687d96a5f93dcdfa94d9a27d649d2f10a84595590f89a347fb4dda47629dcc52 + languageName: node + linkType: hard + +"map-obj@npm:^4.0.0": + version: 4.3.0 + resolution: "map-obj@npm:4.3.0" + checksum: 10c0/1c19e1c88513c8abdab25c316367154c6a0a6a0f77e3e8c391bb7c0e093aefed293f539d026dc013d86219e5e4c25f23b0003ea588be2101ccd757bacc12d43b + languageName: node + linkType: hard + +"mariadb@npm:^3.4.5": + version: 3.4.5 + resolution: "mariadb@npm:3.4.5" + dependencies: + "@types/geojson": "npm:^7946.0.16" + "@types/node": "npm:^24.0.13" + denque: "npm:^2.1.0" + iconv-lite: "npm:^0.6.3" + lru-cache: "npm:^10.4.3" + checksum: 10c0/2b007083f992c1543e73ec9c4113528deeb418502dfee791bb2902c7c2e228d9e46c3ed7415c27f7d03850e34189ee4a8b5c54ef7d262abcfd5eb1848e8ca76e + languageName: node + linkType: hard + +"markdown-it@npm:^14.1.0, markdown-it@npm:~14.1.0": + version: 14.1.0 + resolution: "markdown-it@npm:14.1.0" + dependencies: + argparse: "npm:^2.0.1" + entities: "npm:^4.4.0" + linkify-it: "npm:^5.0.0" + mdurl: "npm:^2.0.0" + punycode.js: "npm:^2.3.1" + uc.micro: "npm:^2.1.0" + bin: + markdown-it: bin/markdown-it.mjs + checksum: 10c0/9a6bb444181d2db7016a4173ae56a95a62c84d4cbfb6916a399b11d3e6581bf1cc2e4e1d07a2f022ae72c25f56db90fbe1e529fca16fbf9541659dc53480d4b4 + languageName: node + linkType: hard + +"markdownlint-cli@npm:0.47.0": + version: 0.47.0 + resolution: "markdownlint-cli@npm:0.47.0" + dependencies: + commander: "npm:~14.0.2" + deep-extend: "npm:~0.6.0" + ignore: "npm:~7.0.5" + js-yaml: "npm:~4.1.1" + jsonc-parser: "npm:~3.3.1" + jsonpointer: "npm:~5.0.1" + markdown-it: "npm:~14.1.0" + markdownlint: "npm:~0.40.0" + minimatch: "npm:~10.1.1" + run-con: "npm:~1.3.2" + smol-toml: "npm:~1.5.2" + tinyglobby: "npm:~0.2.15" + bin: + markdownlint: markdownlint.js + checksum: 10c0/466e2e0f288844a129bfcbdbfb1f08fef81e42f6c0d9760fc3d9a8e668cfc34fdfd055f08d780b9bc73abad7d5827eb94ec100405b38cde1eadadca66bfe0188 + languageName: node + linkType: hard + +"markdownlint@npm:~0.40.0": + version: 0.40.0 + resolution: "markdownlint@npm:0.40.0" + dependencies: + micromark: "npm:4.0.2" + micromark-core-commonmark: "npm:2.0.3" + micromark-extension-directive: "npm:4.0.0" + micromark-extension-gfm-autolink-literal: "npm:2.1.0" + micromark-extension-gfm-footnote: "npm:2.1.0" + micromark-extension-gfm-table: "npm:2.1.1" + micromark-extension-math: "npm:3.1.0" + micromark-util-types: "npm:2.0.2" + string-width: "npm:8.1.0" + checksum: 10c0/1543fcf4a433bc54e0e565cb1c8111e5e3d0df3742df0cc840d470bced21a1e3b5593e4e380ad0d8d5e490d9b399699d48aeabed33719f3fbdc6d00128138f20 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mdurl@npm:^2.0.0": + version: 2.0.0 + resolution: "mdurl@npm:2.0.0" + checksum: 10c0/633db522272f75ce4788440669137c77540d74a83e9015666a9557a152c02e245b192edc20bc90ae953bbab727503994a53b236b4d9c99bdaee594d0e7dd2ce0 + languageName: node + linkType: hard + +"meow@npm:^8.1.2": + version: 8.1.2 + resolution: "meow@npm:8.1.2" + dependencies: + "@types/minimist": "npm:^1.2.0" + camelcase-keys: "npm:^6.2.2" + decamelize-keys: "npm:^1.1.0" + hard-rejection: "npm:^2.1.0" + minimist-options: "npm:4.1.0" + normalize-package-data: "npm:^3.0.0" + read-pkg-up: "npm:^7.0.1" + redent: "npm:^3.0.0" + trim-newlines: "npm:^3.0.0" + type-fest: "npm:^0.18.0" + yargs-parser: "npm:^20.2.3" + checksum: 10c0/9a8d90e616f783650728a90f4ea1e5f763c1c5260369e6596b52430f877f4af8ecbaa8c9d952c93bbefd6d5bda4caed6a96a20ba7d27b511d2971909b01922a2 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromark-core-commonmark@npm:2.0.3, micromark-core-commonmark@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-destination: "npm:^2.0.0" + micromark-factory-label: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-title: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-html-tag-name: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bd4a794fdc9e88dbdf59eaf1c507ddf26e5f7ddf4e52566c72239c0f1b66adbcd219ba2cd42350debbe24471434d5f5e50099d2b3f4e5762ca222ba8e5b549ee + languageName: node + linkType: hard + +"micromark-extension-directive@npm:4.0.0": + version: 4.0.0 + resolution: "micromark-extension-directive@npm:4.0.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + checksum: 10c0/b4aef0f44339543466ae186130a4514985837b6b12d0c155bd1162e740f631e58f0883a39d0c723206fa0ff53a9b579965c79116f902236f6f123c3340b5fefb + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:2.1.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:2.1.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:2.1.1": + version: 2.1.1 + resolution: "micromark-extension-gfm-table@npm:2.1.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912 + languageName: node + linkType: hard + +"micromark-extension-math@npm:3.1.0": + version: 3.1.0 + resolution: "micromark-extension-math@npm:3.1.0" + dependencies: + "@types/katex": "npm:^0.16.0" + devlop: "npm:^1.0.0" + katex: "npm:^0.16.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/56e6f2185a4613f9d47e7e98cf8605851c990957d9229c942b005e286c8087b61dc9149448d38b2f8be6d42cc6a64aad7e1f2778ddd86fbbb1a2f48a3ca1872f + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bbafcf869cee5bf511161354cb87d61c142592fbecea051000ff116068dc85216e6d48519d147890b9ea5d7e2864a6341c0c09d9948c203bff624a80a476023c + languageName: node + linkType: hard + +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/0137716b4ecb428114165505e94a2f18855c8bbea21b07a8b5ce514b32a595ed789d2b967125718fc44c4197ceaa48f6609d58807a68e778138d2e6b91b824e8 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f9ed43f1c0652d8d898de0ac2be3f77f776fffe7dd96bdbba1e02d7ce33d3853c6ff5daa52568fc4fa32cdf3a62d86b85ead9b9189f7211e1d69ff2163c450fb + languageName: node + linkType: hard + +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/e72fad8d6e88823514916890099a5af20b6a9178ccf78e7e5e05f4de99bb8797acb756257d7a3a57a53854cb0086bf8aab15b1a9e9db8982500dd2c9ff5948b6 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/20a1ec58698f24b766510a309b23a10175034fcf1551eaa9da3adcbed3e00cd53d1ebe5f030cf873f76a1cec3c34eb8c50cc227be3344caa9ed25d56cf611224 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d3fe7a5e2c4060fc2a076f9ce699c82a2e87190a3946e1e5eea77f563869b504961f5668d9c9c014724db28ac32fa909070ea8b30c3a39bd0483cc6c04cc76a1 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/b68c0c16fe8106949537bdcfe1be9cf36c0ccd3bc54c4007003cb0984c3750b6cdd0fd77d03f269a3382b85b0de58bde4f6eedbe7ecdf7244759112289b1ab56 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/8a02e59304005c475c332f581697e92e8c585bcd45d5d225a66c1c1b14ab5a8062705188c2ccec33cc998d33502514121478b2091feddbc751887fc9c290ed08 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f15e282af24c8372cbb10b9b0b3e2c0aa681fea0ca323a44d6bc537dc1d9382c819c3689f14eaa000118f5a163245358ce6276b2cda9a84439cdb221f5d86ae7 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/9c8a9f2c790e5593ffe513901c3a110e9ec8882a08f466da014112a25e5059b51551ca0aeb7ff494657d86eceb2f02ee556c6558b8d66aadc61eae4a240da0df + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: 10c0/b2b29f901093845da8a1bf997ea8b7f5e061ffdba85070dfe14b0197c48fda64ffcf82bfe53c90cf9dc185e69eef8c5d41cae3ba918b96bc279326921b59008a + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: 10c0/ae80444db786fde908e9295f19a27a4aa304171852c77414516418650097b8afb401961c9edb09d677b06e97e8370cfa65638dde8438ebd41d60c0a8678b85b9 + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/5299265fa360769fc499a89f40142f10a9d4a5c3dd8e6eac8a8ef3c2e4a6570e4c009cf75ea46dce5ee31c01f25587bde2f4a5cc0a935584ae86dd857f2babbd + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bb6ca28764696bb479dc44a2d5b5fe003e7177aeae1d6b0d43f24cc223bab90234092d9c3ce4a4d2b8df095ccfd820537b10eb96bb7044d635f385d65a4c984a + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/60e92166e1870fd4f1961468c2651013ff760617342918e0e0c3c4e872433aa2e60c1e5a672bfe5d89dc98f742d6b33897585cf86ae002cda23e905a3c02527c + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bee69eece4393308e657c293ba80d92ebcb637e5f55e21dcf9c3fa732b91a8eda8ac248d76ff375e675175bfadeae4712e5158ef97eef1111789da1ce7ab5067 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: 10c0/f2d1b207771e573232436618e78c5e46cd4b5c560dd4a6d63863d58018abbf49cb96ec69f7007471e51434c60de3c9268ef2bf46852f26ff4aacd10f9da16fe9 + languageName: node + linkType: hard + +"micromark-util-types@npm:2.0.2, micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 10c0/c8c15b96c858db781c4393f55feec10004bf7df95487636c9a9f7209e51002a5cca6a047c5d2a5dc669ff92da20e57aaa881e81a268d9ccadb647f9dce305298 + languageName: node + linkType: hard + +"micromark@npm:4.0.2": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/07462287254219d6eda6eac8a3cebaff2994e0575499e7088027b825105e096e4f51e466b14b2a81b71933a3b6c48ee069049d87bc2c2127eee50d9cc69e8af6 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.29, mime-types@npm:^2.1.35": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:^3.0.0": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: 10c0/402e792a8df1b2cc41cb77f0dcc46472b7944b7ec29cb5bbcd398624b6b97096728f1239766d3fdeb20551dd8d94738344c195a6ea10c4f906eb0356323b0531 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"mimic-response@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-response@npm:4.0.0" + checksum: 10c0/761d788d2668ae9292c489605ffd4fad220f442fbae6832adce5ebad086d691e906a6d5240c290293c7a11e99fbdbbef04abbbed498bf8699a4ee0f31315e3fb + languageName: node + linkType: hard + +"min-indent@npm:^1.0.0": + version: 1.0.1 + resolution: "min-indent@npm:1.0.1" + checksum: 10c0/7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c + languageName: node + linkType: hard + +"minimalistic-assert@npm:^1.0.0": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + +"minimatch@npm:3.0.5": + version: 3.0.5 + resolution: "minimatch@npm:3.0.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/f398652d0d260137c289c270a4ac98ebe0a27cd316fa0fac72b096e96cbdc89f71d80d47ac7065c716ba3b0b730783b19180bd85a35f9247535d2adfe96bba76 + languageName: node + linkType: hard + +"minimatch@npm:9.0.3": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/85f407dcd38ac3e180f425e86553911d101455ca3ad5544d6a7cec16286657e4f8a9aa6695803025c55e31e35a91a2252b5dc8e7d527211278b8b65b4dbd5eac + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:^8.0.2": + version: 8.0.4 + resolution: "minimatch@npm:8.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/a0a394c356dd5b4cb7f821720841a82fa6f07c9c562c5b716909d1b6ec5e56a7e4c4b5029da26dd256b7d2b3a3f38cbf9ddd8680e887b9b5282b09c05501c1ca + languageName: node + linkType: hard + +"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minimatch@npm:~10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + +"minimist-options@npm:4.1.0": + version: 4.1.0 + resolution: "minimist-options@npm:4.1.0" + dependencies: + arrify: "npm:^1.0.1" + is-plain-obj: "npm:^1.1.0" + kind-of: "npm:^6.0.3" + checksum: 10c0/7871f9cdd15d1e7374e5b013e2ceda3d327a06a8c7b38ae16d9ef941e07d985e952c589e57213f7aa90a8744c60aed9524c0d85e501f5478382d9181f2763f54 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^1.0.2": + version: 1.0.2 + resolution: "minipass-collect@npm:1.0.2" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/8f82bd1f3095b24f53a991b04b67f4c710c894e518b813f0864a31de5570441a509be1ca17e0bb92b047591a8fdbeb886f502764fefb00d2f144f4011791e898 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^1.3.2": + version: 1.4.1 + resolution: "minipass-fetch@npm:1.4.1" + dependencies: + encoding: "npm:^0.1.12" + minipass: "npm:^3.1.0" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.0.0" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/a43da7401cd7c4f24b993887d41bd37d097356083b0bb836fd655916467463a1e6e9e553b2da4fcbe8745bf23d40c8b884eab20745562199663b3e9060cd8e7a + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.1.2" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0, minipass@npm:^3.1.0, minipass@npm:^3.1.1, minipass@npm:^3.1.3": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^4.2.4": + version: 4.2.8 + resolution: "minipass@npm:4.2.8" + checksum: 10c0/4ea76b030d97079f4429d6e8a8affd90baf1b6a1898977c8ccce4701c5a2ba2792e033abc6709373f25c2c4d4d95440d9d5e9464b46b7b76ca44d2ce26d939ce + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.0.0, minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1": + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.1": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"mocha@npm:11.7.5": + version: 11.7.5 + resolution: "mocha@npm:11.7.5" + dependencies: + browser-stdout: "npm:^1.3.1" + chokidar: "npm:^4.0.1" + debug: "npm:^4.3.5" + diff: "npm:^7.0.0" + escape-string-regexp: "npm:^4.0.0" + find-up: "npm:^5.0.0" + glob: "npm:^10.4.5" + he: "npm:^1.2.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + log-symbols: "npm:^4.1.0" + minimatch: "npm:^9.0.5" + ms: "npm:^2.1.3" + picocolors: "npm:^1.1.1" + serialize-javascript: "npm:^6.0.2" + strip-json-comments: "npm:^3.1.1" + supports-color: "npm:^8.1.1" + workerpool: "npm:^9.2.0" + yargs: "npm:^17.7.2" + yargs-parser: "npm:^21.1.1" + yargs-unparser: "npm:^2.0.0" + bin: + _mocha: bin/_mocha + mocha: bin/mocha.js + checksum: 10c0/e6150cba85345aaabbc5b2e7341b1e6706b878f5a9782960cad57fd4cc458730a76d08c5f1a3e05d3ebb49cad93b455764bb00727bd148dcaf0c6dd4fa665b3d + languageName: node + linkType: hard + +"modify-values@npm:^1.0.1": + version: 1.0.1 + resolution: "modify-values@npm:1.0.1" + checksum: 10c0/6acb1b82aaf7a02f9f7b554b20cbfc159f223a79c66b0a257511c5933d50b85e12ea1220b0a90a2af6f80bc29ff784f929a52a51881867a93ae6a12ce87a729a + languageName: node + linkType: hard + +"moment-timezone@npm:^0.5.15": + version: 0.5.48 + resolution: "moment-timezone@npm:0.5.48" + dependencies: + moment: "npm:^2.29.4" + checksum: 10c0/ab14ec9d94bc33f29ac18e5417b7f8aca0b17130b952c5cc9697b8fea839e5ece9313af5fd3c9703a05db472b1560ddbfc7ad2aa24aac9afd047d6da6c3c6033 + languageName: node + linkType: hard + +"moment@npm:2.30.1, moment@npm:^2.29.4": + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10c0/865e4279418c6de666fca7786607705fd0189d8a7b7624e2e56be99290ac846f90878a6f602e34b4e0455c549b85385b1baf9966845962b313699e7cb847543a + languageName: node + linkType: hard + +"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"multimatch@npm:5.0.0": + version: 5.0.0 + resolution: "multimatch@npm:5.0.0" + dependencies: + "@types/minimatch": "npm:^3.0.3" + array-differ: "npm:^3.0.0" + array-union: "npm:^2.1.0" + arrify: "npm:^2.0.1" + minimatch: "npm:^3.0.4" + checksum: 10c0/252ffae6d19491c169c22fc30cf8a99f6031f94a3495f187d3430b06200e9f05a7efae90ab9d834f090834e0d9c979ab55e7ad21f61a37995d807b4b0ccdcbd1 + languageName: node + linkType: hard + +"mute-stream@npm:0.0.8": + version: 0.0.8 + resolution: "mute-stream@npm:0.0.8" + checksum: 10c0/18d06d92e5d6d45e2b63c0e1b8f25376af71748ac36f53c059baa8b76ffac31c5ab225480494e7d35d30215ecdb18fed26ec23cafcd2f7733f2f14406bcd19e2 + languageName: node + linkType: hard + +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c + languageName: node + linkType: hard + +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10c0/2cf48a2087175c60c8dcdbc619908b49c07f7adcfc37d29236b0c5c612d6204f789104c98cc44d38acab7b3c96f4a3ec2cfdc4934d0738d876dbefa2a12c69f4 + languageName: node + linkType: hard + +"mysql2@npm:^3.16.0": + version: 3.16.0 + resolution: "mysql2@npm:3.16.0" + dependencies: + aws-ssl-profiles: "npm:^1.1.1" + denque: "npm:^2.1.0" + generate-function: "npm:^2.3.1" + iconv-lite: "npm:^0.7.0" + long: "npm:^5.2.1" + lru.min: "npm:^1.0.0" + named-placeholders: "npm:^1.1.3" + seq-queue: "npm:^0.0.5" + sqlstring: "npm:^2.3.2" + checksum: 10c0/79e4f7067d53a9183589558a1f6cdbbec3c635af3e7f42c81921df3cf619e3637a58d4377a9e01bd8192e28ff60c4e441fff855248a17a3e51bfc02b535ca358 + languageName: node + linkType: hard + +"named-placeholders@npm:^1.1.3": + version: 1.1.3 + resolution: "named-placeholders@npm:1.1.3" + dependencies: + lru-cache: "npm:^7.14.1" + checksum: 10c0/cd83b4bbdf358b2285e3c51260fac2039c9d0546632b8a856b3eeabd3bfb3d5b597507ab319b97c281a4a70d748f38bc66fa218a61cb44f55ad997ad5d9c9935 + languageName: node + linkType: hard + +"nan@npm:^2.23.0": + version: 2.23.0 + resolution: "nan@npm:2.23.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/b986dd257dca53ab43a3b6ca6d0eafde697b69e1d63b242fa4aece50ce97eb169f9c4a5d8eb0eb5f58d118a9595fee11f3198fa210f023440053bb6f54109e73 + languageName: node + linkType: hard + +"nano-spawn@npm:^2.0.0": + version: 2.0.0 + resolution: "nano-spawn@npm:2.0.0" + checksum: 10c0/d00f9b5739f86e28cb732ffd774793e110810cded246b8393c75c4f22674af47f98ee37b19f022ada2d8c9425f800e841caa0662fbff4c0930a10e39339fb366 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.1": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"napi-build-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "napi-build-utils@npm:2.0.0" + checksum: 10c0/5833aaeb5cc5c173da47a102efa4680a95842c13e0d9cc70428bd3ee8d96bb2172f8860d2811799b5daa5cbeda779933601492a2028a6a5351c6d0fcf6de83db + languageName: node + linkType: hard + +"napi-postinstall@npm:^0.2.2": + version: 0.2.3 + resolution: "napi-postinstall@npm:0.2.3" + bin: + napi-postinstall: lib/cli.js + checksum: 10c0/125cb677d59f284e61cd9b4cd840cf735edd4c325ffc54af4fad16c8726642ffeddaa63c5ca3533b5e7023be4d8e9ff223484c5eea2a8efe2e2498fd063cabbd + languageName: node + linkType: hard + +"native-duplexpair@npm:^1.0.0": + version: 1.0.0 + resolution: "native-duplexpair@npm:1.0.0" + checksum: 10c0/b4285c69526575b4fa10fb054ad80177a556eede485d0b83bd0366d2276ca24dd50580c3bbb5f262bae5ef8b0e7a1e02d9a6ccb02036e5fdf993dd48500adac7 + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:^0.6.2, negotiator@npm:^0.6.3": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10c0/95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + +"nise@npm:^6.0.0": + version: 6.1.1 + resolution: "nise@npm:6.1.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^13.0.1" + "@sinonjs/text-encoding": "npm:^0.7.3" + just-extend: "npm:^6.2.0" + path-to-regexp: "npm:^8.1.0" + checksum: 10c0/09471adb738dc3be2981cc7815c90879ed6a5a3e162202ca66e12f9a5a0956bea718d0ec2f0c07acc26e3f958481b8fb30c30da76c13620e922f3b9dcd249c50 + languageName: node + linkType: hard + +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: "npm:^2.0.2" + tslib: "npm:^2.0.3" + checksum: 10c0/8ef545f0b3f8677c848f86ecbd42ca0ff3cd9dd71c158527b344c69ba14710d816d8489c746b6ca225e7b615108938a0bda0a54706f8c255933703ac1cf8e703 + languageName: node + linkType: hard + +"node-abi@npm:^3.3.0": + version: 3.75.0 + resolution: "node-abi@npm:3.75.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c43a2409407df3737848fd96202b0a49e15039994aecce963969e9ef7342a8fc544aba94e0bfd8155fb9de5f5fe9a4b6ccad8bf509e7c46caf096fc4491d63f2 + languageName: node + linkType: hard + +"node-addon-api@npm:^3.0.2": + version: 3.2.1 + resolution: "node-addon-api@npm:3.2.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/41f21c9d12318875a2c429befd06070ce367065a3ef02952cfd4ea17ef69fa14012732f510b82b226e99c254da8d671847ea018cad785f839a5366e02dd56302 + languageName: node + linkType: hard + +"node-addon-api@npm:^7.0.0": + version: 7.1.1 + resolution: "node-addon-api@npm:7.1.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/fb32a206276d608037fa1bcd7e9921e177fe992fc610d098aa3128baca3c0050fc1e014fa007e9b3874cf865ddb4f5bd9f43ccb7cbbbe4efaff6a83e920b17e9 + languageName: node + linkType: hard + +"node-fetch@npm:2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/fcae80f5ac52fbf5012f5e19df2bd3915e67d3b3ad51cb5942943df2238d32ba15890fecabd0e166876a9f98a581ab50f3f10eb942b09405c49ef8da36b826c7 + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-gyp@npm:11.5.0": + version: 11.5.0 + resolution: "node-gyp@npm:11.5.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/31ff49586991b38287bb15c3d529dd689cfc32f992eed9e6997b9d712d5d21fe818a8b1bbfe3b76a7e33765c20210c5713212f4aa329306a615b87d8a786da3a + languageName: node + linkType: hard + +"node-gyp@npm:8.x": + version: 8.4.1 + resolution: "node-gyp@npm:8.4.1" + dependencies: + env-paths: "npm:^2.2.0" + glob: "npm:^7.1.4" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^9.1.0" + nopt: "npm:^5.0.0" + npmlog: "npm:^6.0.0" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.2" + which: "npm:^2.0.2" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/80ef333b3a882eb6a2695a8e08f31d618f4533eff192864e4a3a16b67ff0abc9d8c1d5fac0395550ec699326b9248c5e2b3be178492f7f4d1ccf97d2cf948021 + languageName: node + linkType: hard + +"node-gyp@npm:^10.0.0": + version: 10.3.1 + resolution: "node-gyp@npm:10.3.1" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^4.1.0" + semver: "npm:^7.3.5" + tar: "npm:^6.2.1" + which: "npm:^4.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/87c3b50e1f6f5256b5d2879a8c064eefa53ed444bad2a20870be43bc189db7cbffe22c30af056046c6d904181d73881b1726fd391d2f6f79f89b991019f195ea + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.2.0 + resolution: "node-gyp@npm:11.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9 + languageName: node + linkType: hard + +"node-hook@npm:1.0.0": + version: 1.0.0 + resolution: "node-hook@npm:1.0.0" + checksum: 10c0/04bc1fd8155a94a61ea0c9681188fb16998779fe6b4d53b8dbdd33f0b90feab24a64612c2b43f3066a703bc8d9bd41e04ce3656265a112457d445099ffc27484 + languageName: node + linkType: hard + +"node-machine-id@npm:1.1.12": + version: 1.1.12 + resolution: "node-machine-id@npm:1.1.12" + checksum: 10c0/ab2fea5f75a6f1ce3c76c5e0ae3903b631230e0a99b003d176568fff8ddbdf7b2943be96cd8d220c497ca0f6149411831f8a450601929f326781cb1b59bab7f8 + languageName: node + linkType: hard + +"node-preload@npm:^0.2.1": + version: 0.2.1 + resolution: "node-preload@npm:0.2.1" + dependencies: + process-on-spawn: "npm:^1.0.0" + checksum: 10c0/7ae3def896626701e2a27b0c8119e0234089db4317b8c16bb8c44bee9abb82c0e38d57e6317d480970f5a2510e44185af81d3ea85be1a78311701f66f912e9e4 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa + languageName: node + linkType: hard + +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0, nopt@npm:^7.2.1": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + languageName: node + linkType: hard + +"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.5.0": + version: 2.5.0 + resolution: "normalize-package-data@npm:2.5.0" + dependencies: + hosted-git-info: "npm:^2.1.4" + resolve: "npm:^1.10.0" + semver: "npm:2 || 3 || 4 || 5" + validate-npm-package-license: "npm:^3.0.1" + checksum: 10c0/357cb1646deb42f8eb4c7d42c4edf0eec312f3628c2ef98501963cc4bbe7277021b2b1d977f982b2edce78f5a1014613ce9cf38085c3df2d76730481357ca504 + languageName: node + linkType: hard + +"normalize-package-data@npm:^3.0.0, normalize-package-data@npm:^3.0.3": + version: 3.0.3 + resolution: "normalize-package-data@npm:3.0.3" + dependencies: + hosted-git-info: "npm:^4.0.1" + is-core-module: "npm:^2.5.0" + semver: "npm:^7.3.4" + validate-npm-package-license: "npm:^3.0.1" + checksum: 10c0/e5d0f739ba2c465d41f77c9d950e291ea4af78f8816ddb91c5da62257c40b76d8c83278b0d08ffbcd0f187636ebddad20e181e924873916d03e6e5ea2ef026be + languageName: node + linkType: hard + +"normalize-package-data@npm:^6, normalize-package-data@npm:^6.0.0, normalize-package-data@npm:^6.0.1": + version: 6.0.2 + resolution: "normalize-package-data@npm:6.0.2" + dependencies: + hosted-git-info: "npm:^7.0.0" + semver: "npm:^7.3.5" + validate-npm-package-license: "npm:^3.0.4" + checksum: 10c0/7e32174e7f5575ede6d3d449593247183880122b4967d4ae6edb28cea5769ca025defda54fc91ec0e3c972fdb5ab11f9284606ba278826171b264cb16a9311ef + languageName: node + linkType: hard + +"normalize-url@npm:^8.0.0": + version: 8.0.1 + resolution: "normalize-url@npm:8.0.1" + checksum: 10c0/eb439231c4b84430f187530e6fdac605c5048ef4ec556447a10c00a91fc69b52d8d8298d9d608e68d3e0f7dc2d812d3455edf425e0f215993667c3183bcab1ef + languageName: node + linkType: hard + +"npm-bundled@npm:^3.0.0": + version: 3.0.1 + resolution: "npm-bundled@npm:3.0.1" + dependencies: + npm-normalize-package-bin: "npm:^3.0.0" + checksum: 10c0/7975590a50b7ce80dd9f3eddc87f7e990c758f2f2c4d9313dd67a9aca38f1a5ac0abe20d514b850902c441e89d2346adfc3c6f1e9cbab3ea28ebb653c4442440 + languageName: node + linkType: hard + +"npm-install-checks@npm:^6.0.0, npm-install-checks@npm:^6.2.0": + version: 6.3.0 + resolution: "npm-install-checks@npm:6.3.0" + dependencies: + semver: "npm:^7.1.1" + checksum: 10c0/b046ef1de9b40f5d3a9831ce198e1770140a1c3f253dae22eb7b06045191ef79f18f1dcc15a945c919b3c161426861a28050abd321bf439190185794783b6452 + languageName: node + linkType: hard + +"npm-normalize-package-bin@npm:^3.0.0": + version: 3.0.1 + resolution: "npm-normalize-package-bin@npm:3.0.1" + checksum: 10c0/f1831a7f12622840e1375c785c3dab7b1d82dd521211c17ee5e9610cd1a34d8b232d3fdeebf50c170eddcb321d2c644bf73dbe35545da7d588c6b3fa488db0a5 + languageName: node + linkType: hard + +"npm-package-arg@npm:11.0.2": + version: 11.0.2 + resolution: "npm-package-arg@npm:11.0.2" + dependencies: + hosted-git-info: "npm:^7.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.3.5" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10c0/d730572e128980db45c97c184a454cb565283bf849484bf92e3b4e8ec2d08a21bd4b2cba9467466853add3e8c7d81e5de476904ac241f3ae63e6905dfc8196d4 + languageName: node + linkType: hard + +"npm-package-arg@npm:^11.0.0, npm-package-arg@npm:^11.0.2": + version: 11.0.3 + resolution: "npm-package-arg@npm:11.0.3" + dependencies: + hosted-git-info: "npm:^7.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.3.5" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10c0/e18333485e05c3a8774f4b5701ef74f4799533e650b70a68ca8dd697666c9a8d46932cb765fc593edce299521033bd4025a40323d5240cea8a393c784c0c285a + languageName: node + linkType: hard + +"npm-packlist@npm:8.0.2, npm-packlist@npm:^8.0.0": + version: 8.0.2 + resolution: "npm-packlist@npm:8.0.2" + dependencies: + ignore-walk: "npm:^6.0.4" + checksum: 10c0/ac3140980b1475c2e9acd3d0ca1acd0f8660c357aed357f1a4ebff2270975e0280a3b1c4938e2f16bd68217853ceb5725cf8779ec3752dfcc546582751ceedff + languageName: node + linkType: hard + +"npm-pick-manifest@npm:^9.0.0, npm-pick-manifest@npm:^9.0.1": + version: 9.1.0 + resolution: "npm-pick-manifest@npm:9.1.0" + dependencies: + npm-install-checks: "npm:^6.0.0" + npm-normalize-package-bin: "npm:^3.0.0" + npm-package-arg: "npm:^11.0.0" + semver: "npm:^7.3.5" + checksum: 10c0/8765f4199755b381323da2bff2202b4b15b59f59dba0d1be3f2f793b591321cd19e1b5a686ef48d9753a6bd4868550da632541a45dfb61809d55664222d73e44 + languageName: node + linkType: hard + +"npm-registry-fetch@npm:^17.0.0, npm-registry-fetch@npm:^17.0.1, npm-registry-fetch@npm:^17.1.0": + version: 17.1.0 + resolution: "npm-registry-fetch@npm:17.1.0" + dependencies: + "@npmcli/redact": "npm:^2.0.0" + jsonparse: "npm:^1.3.1" + make-fetch-happen: "npm:^13.0.0" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minizlib: "npm:^2.1.2" + npm-package-arg: "npm:^11.0.0" + proc-log: "npm:^4.0.0" + checksum: 10c0/3f66214e106609fd2e92704e62ac929cba1424d4013fec50f783afbb81168b0dc14457d35c1716a77e30fc482c3576bdc4e4bc5c84a714cac59cf98f96a17f47 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa + languageName: node + linkType: hard + +"npmlog@npm:^6.0.0": + version: 6.0.2 + resolution: "npmlog@npm:6.0.2" + dependencies: + are-we-there-yet: "npm:^3.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^4.0.3" + set-blocking: "npm:^2.0.0" + checksum: 10c0/0cacedfbc2f6139c746d9cd4a85f62718435ad0ca4a2d6459cd331dd33ae58206e91a0742c1558634efcde3f33f8e8e7fd3adf1bfe7978310cf00bd55cccf890 + languageName: node + linkType: hard + +"nx@npm:21.6.10": + version: 21.6.10 + resolution: "nx@npm:21.6.10" + dependencies: + "@napi-rs/wasm-runtime": "npm:0.2.4" + "@nx/nx-darwin-arm64": "npm:21.6.10" + "@nx/nx-darwin-x64": "npm:21.6.10" + "@nx/nx-freebsd-x64": "npm:21.6.10" + "@nx/nx-linux-arm-gnueabihf": "npm:21.6.10" + "@nx/nx-linux-arm64-gnu": "npm:21.6.10" + "@nx/nx-linux-arm64-musl": "npm:21.6.10" + "@nx/nx-linux-x64-gnu": "npm:21.6.10" + "@nx/nx-linux-x64-musl": "npm:21.6.10" + "@nx/nx-win32-arm64-msvc": "npm:21.6.10" + "@nx/nx-win32-x64-msvc": "npm:21.6.10" + "@yarnpkg/lockfile": "npm:^1.1.0" + "@yarnpkg/parsers": "npm:3.0.2" + "@zkochan/js-yaml": "npm:0.0.7" + axios: "npm:^1.12.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:3.1.0" + cli-spinners: "npm:2.6.1" + cliui: "npm:^8.0.1" + dotenv: "npm:~16.4.5" + dotenv-expand: "npm:~11.0.6" + enquirer: "npm:~2.3.6" + figures: "npm:3.2.0" + flat: "npm:^5.0.2" + front-matter: "npm:^4.0.2" + ignore: "npm:^5.0.4" + jest-diff: "npm:^30.0.2" + jsonc-parser: "npm:3.2.0" + lines-and-columns: "npm:2.0.3" + minimatch: "npm:9.0.3" + node-machine-id: "npm:1.1.12" + npm-run-path: "npm:^4.0.1" + open: "npm:^8.4.0" + ora: "npm:5.3.0" + resolve.exports: "npm:2.0.3" + semver: "npm:^7.5.3" + string-width: "npm:^4.2.3" + tar-stream: "npm:~2.2.0" + tmp: "npm:~0.2.1" + tree-kill: "npm:^1.2.2" + tsconfig-paths: "npm:^4.1.2" + tslib: "npm:^2.3.0" + yaml: "npm:^2.6.0" + yargs: "npm:^17.6.2" + yargs-parser: "npm:21.1.1" + peerDependencies: + "@swc-node/register": ^1.8.0 + "@swc/core": ^1.3.85 + dependenciesMeta: + "@nx/nx-darwin-arm64": + optional: true + "@nx/nx-darwin-x64": + optional: true + "@nx/nx-freebsd-x64": + optional: true + "@nx/nx-linux-arm-gnueabihf": + optional: true + "@nx/nx-linux-arm64-gnu": + optional: true + "@nx/nx-linux-arm64-musl": + optional: true + "@nx/nx-linux-x64-gnu": + optional: true + "@nx/nx-linux-x64-musl": + optional: true + "@nx/nx-win32-arm64-msvc": + optional: true + "@nx/nx-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc-node/register": + optional: true + "@swc/core": + optional: true + bin: + nx: bin/nx.js + nx-cloud: bin/nx-cloud.js + checksum: 10c0/eb10292578b69693d551eadea48d74226ce3de06e6173f53497154c0bb4594fa6c4092ddcab08441e0e4584530bbd36f23d0dc2d8f11c57d7f908699128fbd5d + languageName: node + linkType: hard + +"nx@npm:>=17.1.2 < 21": + version: 20.8.2 + resolution: "nx@npm:20.8.2" + dependencies: + "@napi-rs/wasm-runtime": "npm:0.2.4" + "@nx/nx-darwin-arm64": "npm:20.8.2" + "@nx/nx-darwin-x64": "npm:20.8.2" + "@nx/nx-freebsd-x64": "npm:20.8.2" + "@nx/nx-linux-arm-gnueabihf": "npm:20.8.2" + "@nx/nx-linux-arm64-gnu": "npm:20.8.2" + "@nx/nx-linux-arm64-musl": "npm:20.8.2" + "@nx/nx-linux-x64-gnu": "npm:20.8.2" + "@nx/nx-linux-x64-musl": "npm:20.8.2" + "@nx/nx-win32-arm64-msvc": "npm:20.8.2" + "@nx/nx-win32-x64-msvc": "npm:20.8.2" + "@yarnpkg/lockfile": "npm:^1.1.0" + "@yarnpkg/parsers": "npm:3.0.2" + "@zkochan/js-yaml": "npm:0.0.7" + axios: "npm:^1.8.3" + chalk: "npm:^4.1.0" + cli-cursor: "npm:3.1.0" + cli-spinners: "npm:2.6.1" + cliui: "npm:^8.0.1" + dotenv: "npm:~16.4.5" + dotenv-expand: "npm:~11.0.6" + enquirer: "npm:~2.3.6" + figures: "npm:3.2.0" + flat: "npm:^5.0.2" + front-matter: "npm:^4.0.2" + ignore: "npm:^5.0.4" + jest-diff: "npm:^29.4.1" + jsonc-parser: "npm:3.2.0" + lines-and-columns: "npm:2.0.3" + minimatch: "npm:9.0.3" + node-machine-id: "npm:1.1.12" + npm-run-path: "npm:^4.0.1" + open: "npm:^8.4.0" + ora: "npm:5.3.0" + resolve.exports: "npm:2.0.3" + semver: "npm:^7.5.3" + string-width: "npm:^4.2.3" + tar-stream: "npm:~2.2.0" + tmp: "npm:~0.2.1" + tsconfig-paths: "npm:^4.1.2" + tslib: "npm:^2.3.0" + yaml: "npm:^2.6.0" + yargs: "npm:^17.6.2" + yargs-parser: "npm:21.1.1" + peerDependencies: + "@swc-node/register": ^1.8.0 + "@swc/core": ^1.3.85 + dependenciesMeta: + "@nx/nx-darwin-arm64": + optional: true + "@nx/nx-darwin-x64": + optional: true + "@nx/nx-freebsd-x64": + optional: true + "@nx/nx-linux-arm-gnueabihf": + optional: true + "@nx/nx-linux-arm64-gnu": + optional: true + "@nx/nx-linux-arm64-musl": + optional: true + "@nx/nx-linux-x64-gnu": + optional: true + "@nx/nx-linux-x64-musl": + optional: true + "@nx/nx-win32-arm64-msvc": + optional: true + "@nx/nx-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc-node/register": + optional: true + "@swc/core": + optional: true + bin: + nx: bin/nx.js + nx-cloud: bin/nx-cloud.js + checksum: 10c0/3b6b202f5447f7c10d4755c9485596f607dff4cb39ef6e241c68a3d7f9cd3c5220166ed0760d3a8e2deaaebfe40db54b95ffe9e5796f7ce38c33d87bc3d10c97 + languageName: node + linkType: hard + +"nyc@npm:17.1.0": + version: 17.1.0 + resolution: "nyc@npm:17.1.0" + dependencies: + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + caching-transform: "npm:^4.0.0" + convert-source-map: "npm:^1.7.0" + decamelize: "npm:^1.2.0" + find-cache-dir: "npm:^3.2.0" + find-up: "npm:^4.1.0" + foreground-child: "npm:^3.3.0" + get-package-type: "npm:^0.1.0" + glob: "npm:^7.1.6" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-hook: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.2" + istanbul-lib-processinfo: "npm:^2.0.2" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.0.2" + make-dir: "npm:^3.0.0" + node-preload: "npm:^0.2.1" + p-map: "npm:^3.0.0" + process-on-spawn: "npm:^1.0.0" + resolve-from: "npm:^5.0.0" + rimraf: "npm:^3.0.0" + signal-exit: "npm:^3.0.2" + spawn-wrap: "npm:^2.0.0" + test-exclude: "npm:^6.0.0" + yargs: "npm:^15.0.2" + bin: + nyc: bin/nyc.js + checksum: 10c0/653497bf11c53c70d821c18a2bfb7dba310b297b8bc83e5392e560c3d60d4dc9836b6c44f060065dfa99f7dacfd49147f8f60b160dfbe3f722517d4e7e236db2 + languageName: node + linkType: hard + +"oauth4webapi@npm:^3.0.1": + version: 3.5.1 + resolution: "oauth4webapi@npm:3.5.1" + checksum: 10c0/5d57ba4299d61173b28ff0612fdfcc550b02c2ce4afcd1641103960c02af18268b55a70f26d47bbfc956680967c307546284b4a0b1f13845589e247f798ff395 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-deep-merge@npm:^2.0.0": + version: 2.0.0 + resolution: "object-deep-merge@npm:2.0.0" + checksum: 10c0/69e8741131ad49fa8720fb96007a3c82dca1119b5d874151d2ecbcc3b44ccd46e8553c7a30b0abcba752c099ba361bbba97f33a68c9ae54c57eed7be116ffc97 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + languageName: node + linkType: hard + +"object.groupby@npm:^1.0.3": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + checksum: 10c0/60d0455c85c736fbfeda0217d1a77525956f76f7b2495edeca9e9bbf8168a45783199e77b894d30638837c654d0cc410e0e02cbfcf445bc8de71c3da1ede6a9c + languageName: node + linkType: hard + +"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + languageName: node + linkType: hard + +"obuf@npm:~1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"oclif@npm:4.22.59": + version: 4.22.59 + resolution: "oclif@npm:4.22.59" + dependencies: + "@aws-sdk/client-cloudfront": "npm:^3.956.0" + "@aws-sdk/client-s3": "npm:^3.956.0" + "@inquirer/confirm": "npm:^3.1.22" + "@inquirer/input": "npm:^2.2.4" + "@inquirer/select": "npm:^2.5.0" + "@oclif/core": "npm:^4.8.0" + "@oclif/plugin-help": "npm:^6.2.36" + "@oclif/plugin-not-found": "npm:^3.2.73" + "@oclif/plugin-warn-if-update-available": "npm:^3.1.53" + ansis: "npm:^3.16.0" + async-retry: "npm:^1.3.3" + change-case: "npm:^4" + debug: "npm:^4.4.0" + ejs: "npm:^3.1.10" + find-yarn-workspace-root: "npm:^2.0.0" + fs-extra: "npm:^8.1" + github-slugger: "npm:^2" + got: "npm:^13" + lodash: "npm:^4.17.21" + normalize-package-data: "npm:^6" + semver: "npm:^7.7.3" + sort-package-json: "npm:^2.15.1" + tiny-jsonc: "npm:^1.0.2" + validate-npm-package-name: "npm:^5.0.1" + bin: + oclif: bin/run.js + checksum: 10c0/47a49ae916555f27fd40bc071db025137c5715107d37a4aea60afeef993a6d9348acb10c3bba917f538fc7b9a65c196199d7ffb5122bc8ee3d4c995c102fcf33 + languageName: node + linkType: hard + +"odbc@npm:^2.4.9": + version: 2.4.9 + resolution: "odbc@npm:2.4.9" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.5" + async: "npm:^3.0.1" + node-addon-api: "npm:^3.0.2" + checksum: 10c0/394e2543f1c24f440c33f5ac05d5178b9a51a757a8628a7d57f9ed811effafc297739448b4bb6fd74212b70f985d6cb3641672edfdef9a992c3ea94543bd4218 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + +"open@npm:^10.1.0": + version: 10.1.2 + resolution: "open@npm:10.1.2" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: 10c0/1bee796f06e549ce764f693272100323fbc04da8fa3c5b0402d6c2d11b3d76fa0aac0be7535e710015ff035326638e3b9a563f3b0e7ac3266473ed5663caae6d + languageName: node + linkType: hard + +"open@npm:^7.3.1": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: "npm:^2.0.0" + is-wsl: "npm:^2.1.1" + checksum: 10c0/77573a6a68f7364f3a19a4c80492712720746b63680ee304555112605ead196afe91052bd3c3d165efdf4e9d04d255e87de0d0a77acec11ef47fd5261251813f + languageName: node + linkType: hard + +"open@npm:^8.4.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + languageName: node + linkType: hard + +"optionator@npm:^0.8.3": + version: 0.8.3 + resolution: "optionator@npm:0.8.3" + dependencies: + deep-is: "npm:~0.1.3" + fast-levenshtein: "npm:~2.0.6" + levn: "npm:~0.3.0" + prelude-ls: "npm:~1.1.2" + type-check: "npm:~0.3.2" + word-wrap: "npm:~1.2.3" + checksum: 10c0/ad7000ea661792b3ec5f8f86aac28895850988926f483b5f308f59f4607dfbe24c05df2d049532ee227c040081f39401a268cf7bbf3301512f74c4d760dc6dd8 + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"ora@npm:5.3.0": + version: 5.3.0 + resolution: "ora@npm:5.3.0" + dependencies: + bl: "npm:^4.0.3" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + log-symbols: "npm:^4.0.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/30d5f3218eb75b0a2028c5fb9aa88e83e38a2f1745ab56839abb06c3ba31bae35f768f4e72c4f9e04e2a66be6a898e9312e8cf85c9333e1e3613eabb8c7cdf57 + languageName: node + linkType: hard + +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: "npm:^4.1.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + is-unicode-supported: "npm:^0.1.0" + log-symbols: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/10ff14aace236d0e2f044193362b22edce4784add08b779eccc8f8ef97195cae1248db8ec1ec5f5ff076f91acbe573f5f42a98c19b78dba8c54eefff983cae85 + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a + languageName: node + linkType: hard + +"p-cancelable@npm:^3.0.0": + version: 3.0.0 + resolution: "p-cancelable@npm:3.0.0" + checksum: 10c0/948fd4f8e87b956d9afc2c6c7392de9113dac817cb1cecf4143f7a3d4c57ab5673614a80be3aba91ceec5e4b69fd8c869852d7e8048bc3d9273c4c36ce14b9aa + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-limit@npm:^1.1.0": + version: 1.3.0 + resolution: "p-limit@npm:1.3.0" + dependencies: + p-try: "npm:^1.0.0" + checksum: 10c0/5c1b1d53d180b2c7501efb04b7c817448e10efe1ba46f4783f8951994d5027e4cd88f36ad79af50546682594c4ebd11702ac4b9364c47f8074890e2acad0edee + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0, p-limit@npm:^2.2.2": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.1, p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^2.0.0": + version: 2.0.0 + resolution: "p-locate@npm:2.0.0" + dependencies: + p-limit: "npm:^1.1.0" + checksum: 10c0/82da4be88fb02fd29175e66021610c881938d3cc97c813c71c1a605fac05617d57fd5d3b337494a6106c0edb2a37c860241430851411f1b265108cead34aee67 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map-series@npm:2.1.0": + version: 2.1.0 + resolution: "p-map-series@npm:2.1.0" + checksum: 10c0/302ca686a61c498b227fc45d4e2b2e5bfd20a03f4156a976d94c4ff7decf9cd5a815fa6846b43b37d587ffa8d4671ff2bd596fa83fe8b9113b5102da94940e2a + languageName: node + linkType: hard + +"p-map@npm:4.0.0, p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-map@npm:^3.0.0": + version: 3.0.0 + resolution: "p-map@npm:3.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/297930737e52412ad9f5787c52774ad6496fad9a8be5f047e75fd0a3dc61930d8f7a9b2bbe1c4d1404e54324228a4f69721da2538208dadaa4ef4c81773c9f20 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + +"p-pipe@npm:3.1.0": + version: 3.1.0 + resolution: "p-pipe@npm:3.1.0" + checksum: 10c0/9b3076828ea7e9469c0f92c78fa44096726208d547efdb2d6148cbe135d1a70bd449de5be13e234dd669d9515343bd68527b316bf9d5639cee639e2fdde20aaf + languageName: node + linkType: hard + +"p-props@npm:4.0.0": + version: 4.0.0 + resolution: "p-props@npm:4.0.0" + dependencies: + p-map: "npm:^4.0.0" + checksum: 10c0/bfac50cf3feea4f2c1c0847412a6a05159de61e990fc1ee65d8f195bf23ed85d59cf1b9847a14a8d006fb64db3f213897816ece6514178558aa3f4fa70bef73b + languageName: node + linkType: hard + +"p-queue@npm:6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e + languageName: node + linkType: hard + +"p-reduce@npm:2.1.0, p-reduce@npm:^2.0.0, p-reduce@npm:^2.1.0": + version: 2.1.0 + resolution: "p-reduce@npm:2.1.0" + checksum: 10c0/27b8ff0fb044995507a06cd6357dffba0f2b98862864745972562a21885d7906ce5c794036d2aaa63ef6303158e41e19aed9f19651dfdafb38548ecec7d0de15 + languageName: node + linkType: hard + +"p-reflect@npm:^2.1.0": + version: 2.1.0 + resolution: "p-reflect@npm:2.1.0" + checksum: 10c0/f8abec0a46f22662929e14df6c8cf7cdbf4dade21c42de0cd4c5f5b6e1be4d668f6406a431da7f97b1e321726b82fb4bb29cf70632d9b698c73e4455170f8f3f + languageName: node + linkType: hard + +"p-settle@npm:4.1.1": + version: 4.1.1 + resolution: "p-settle@npm:4.1.1" + dependencies: + p-limit: "npm:^2.2.2" + p-reflect: "npm:^2.1.0" + checksum: 10c0/d58739ce3e691ef70c857f182c7367765456327c12360dd316210c9003d533b09e361a5ee05f5d2a9171c676a2018db0477175f5fbdb2a10a639d4181e23cea8 + languageName: node + linkType: hard + +"p-timeout@npm:4.1.0": + version: 4.1.0 + resolution: "p-timeout@npm:4.1.0" + checksum: 10c0/25aaf13ae9ebfff4ab45591f6647f3bee1ecede073c367175fb0157c27efb170cfb51259a4d2e9118d2ca595453bc885f086ad0ca7476d1c26cee3d13a7c9f6d + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 + languageName: node + linkType: hard + +"p-try@npm:^1.0.0": + version: 1.0.0 + resolution: "p-try@npm:1.0.0" + checksum: 10c0/757ba31de5819502b80c447826fac8be5f16d3cb4fbf9bc8bc4971dba0682e84ac33e4b24176ca7058c69e29f64f34d8d9e9b08e873b7b7bb0aa89d620fa224a + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"p-waterfall@npm:2.1.1": + version: 2.1.1 + resolution: "p-waterfall@npm:2.1.1" + dependencies: + p-reduce: "npm:^2.0.0" + checksum: 10c0/ccae582b75a3597018a375f8eac32b93e8bfb9fc22a8e5037787ef4ebf5958d7465c2d3cbe26443971fbbfda2bcb7b645f694b91f928fc9a71fa5031e6e33f85 + languageName: node + linkType: hard + +"package-hash@npm:^4.0.0": + version: 4.0.0 + resolution: "package-hash@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.15" + hasha: "npm:^5.0.0" + lodash.flattendeep: "npm:^4.4.0" + release-zalgo: "npm:^1.0.0" + checksum: 10c0/2108b685fd5b2a32323aeed5caf2afef8c5fcf680527b09c7e2eaa05cf04b09a7c586860319097fc589ad028a3d94b2da68e8ab1935249aa95e8162ffd622729 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"pacote@npm:^18.0.0, pacote@npm:^18.0.6": + version: 18.0.6 + resolution: "pacote@npm:18.0.6" + dependencies: + "@npmcli/git": "npm:^5.0.0" + "@npmcli/installed-package-contents": "npm:^2.0.1" + "@npmcli/package-json": "npm:^5.1.0" + "@npmcli/promise-spawn": "npm:^7.0.0" + "@npmcli/run-script": "npm:^8.0.0" + cacache: "npm:^18.0.0" + fs-minipass: "npm:^3.0.0" + minipass: "npm:^7.0.2" + npm-package-arg: "npm:^11.0.0" + npm-packlist: "npm:^8.0.0" + npm-pick-manifest: "npm:^9.0.0" + npm-registry-fetch: "npm:^17.0.0" + proc-log: "npm:^4.0.0" + promise-retry: "npm:^2.0.1" + sigstore: "npm:^2.2.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + bin: + pacote: bin/index.js + checksum: 10c0/d80907375dd52a521255e0debca1ba9089ad8fd7acdf16c5a5db2ea2a5bb23045e2bcf08d1648b1ebc40fcc889657db86ff6187ff5f8d2fc312cd6ad1ec4c6ac + languageName: node + linkType: hard + +"param-case@npm:^3.0.4": + version: 3.0.4 + resolution: "param-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/ccc053f3019f878eca10e70ec546d92f51a592f762917dafab11c8b532715dcff58356118a6f350976e4ab109e321756f05739643ed0ca94298e82291e6f9e76 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-conflict-json@npm:^3.0.0": + version: 3.0.1 + resolution: "parse-conflict-json@npm:3.0.1" + dependencies: + json-parse-even-better-errors: "npm:^3.0.0" + just-diff: "npm:^6.0.0" + just-diff-apply: "npm:^5.2.0" + checksum: 10c0/610b37181229ce3e945125c3a9548ec24d1de2d697a7ea3ef0f2660cccc6613715c2ba4bdbaf37c565133d6b61758703618a2c63d1ee29f97fd33c70a8aae323 + languageName: node + linkType: hard + +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10c0/a13906b1151750b78ed83d386294066daf5fb559e08c5af9591b2d98cc209123103016a01df776f65f8219ad26652d6d6b210d0974d452049cddfc53a8916c34 + languageName: node + linkType: hard + +"parse-imports-exports@npm:^0.2.4": + version: 0.2.4 + resolution: "parse-imports-exports@npm:0.2.4" + dependencies: + parse-statements: "npm:1.0.11" + checksum: 10c0/51b729037208abdf65c4a1f8e9ed06f4e7ccd907c17c668a64db54b37d95bb9e92081f8b16e4133e14102af3cb4e89870975b6ad661b4d654e9ec8f4fb5c77d6 + languageName: node + linkType: hard + +"parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-json@npm:4.0.0" + dependencies: + error-ex: "npm:^1.3.1" + json-parse-better-errors: "npm:^1.0.1" + checksum: 10c0/8d80790b772ccb1bcea4e09e2697555e519d83d04a77c2b4237389b813f82898943a93ffff7d0d2406203bdd0c30dcf95b1661e3a53f83d0e417f053957bef32 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-passwd@npm:^1.0.0": + version: 1.0.0 + resolution: "parse-passwd@npm:1.0.0" + checksum: 10c0/1c05c05f95f184ab9ca604841d78e4fe3294d46b8e3641d305dcc28e930da0e14e602dbda9f3811cd48df5b0e2e27dbef7357bf0d7c40e41b18c11c3a8b8d17b + languageName: node + linkType: hard + +"parse-path@npm:^7.0.0": + version: 7.1.0 + resolution: "parse-path@npm:7.1.0" + dependencies: + protocols: "npm:^2.0.0" + checksum: 10c0/8c8c8b3019323d686e7b1cd6fd9653bc233404403ad68827836fbfe59dfe26aaef64ed4e0396d0e20c4a7e1469312ec969a679618960e79d5e7c652dc0da5a0f + languageName: node + linkType: hard + +"parse-statements@npm:1.0.11": + version: 1.0.11 + resolution: "parse-statements@npm:1.0.11" + checksum: 10c0/48960e085019068a5f5242e875fd9d21ec87df2e291acf5ad4e4887b40eab6929a8c8d59542acb85a6497e870c5c6a24f5ab7f980ef5f907c14cc5f7984a93f3 + languageName: node + linkType: hard + +"parse-url@npm:^8.1.0": + version: 8.1.0 + resolution: "parse-url@npm:8.1.0" + dependencies: + parse-path: "npm:^7.0.0" + checksum: 10c0/68b95afdf4bbf72e57c7ab66f8757c935fff888f7e2b0f1e06098b4faa19e06b6b743bddaed5bc8df4f0c2de6fc475355d787373b2fdd40092be9e4e4b996648 + languageName: node + linkType: hard + +"pascal-case@npm:^3.1.2": + version: 3.1.2 + resolution: "pascal-case@npm:3.1.2" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/05ff7c344809fd272fc5030ae0ee3da8e4e63f36d47a1e0a4855ca59736254192c5a27b5822ed4bae96e54048eec5f6907713cfcfff7cdf7a464eaf7490786d8 + languageName: node + linkType: hard + +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/b6b14637228a558793f603aaeb2fcd981e738b8b9319421b713532fba96d75aa94024b9f6b9ae5aa33d86755144a5b36697d28db62ae45527dbd672fcc2cf0b7 + languageName: node + linkType: hard + +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 10c0/17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10c0/dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-to-regexp@npm:^8.1.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a + languageName: node + linkType: hard + +"path-type@npm:^3.0.0": + version: 3.0.0 + resolution: "path-type@npm:3.0.0" + dependencies: + pify: "npm:^3.0.0" + checksum: 10c0/1332c632f1cac15790ebab8dd729b67ba04fc96f81647496feb1c2975d862d046f41e4b975dbd893048999b2cc90721f72924ad820acc58c78507ba7141a8e56 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 10c0/f63e1bc1b33593cdf094ed6ff5c49c1c0dc5dc20a646ca9725cc7fe7cd9995002d51d5685b9b2ec6814342935748b711bafa840f84c0bb04e38ff40a335c94dc + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + +"pg-cloudflare@npm:^1.2.5": + version: 1.2.5 + resolution: "pg-cloudflare@npm:1.2.5" + checksum: 10c0/48b9105ef027c7b3f57ef88ceaec3634cd82120059bd68273cce06989a1ec547e0b0fbb5d1afdd0711824f409c8b410f9bdec2f6c8034728992d3658c0b36f86 + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.8.5": + version: 2.8.5 + resolution: "pg-connection-string@npm:2.8.5" + checksum: 10c0/5f65afc9dfc99ecf1583a1699c97511f3d505659c9c6a91db8cd0ffe862caa29060722712a034abd6da493356567261febf18b3a6ef223d0a219f0d50d959b97 + languageName: node + linkType: hard + +"pg-hstore@npm:^2.3.4": + version: 2.3.4 + resolution: "pg-hstore@npm:2.3.4" + dependencies: + underscore: "npm:^1.13.1" + checksum: 10c0/2f4e352ba82b200cda55b796e7e8ef7e50377914f1ab4d91fa6dc9701b3d4ae4ddf168376a08a21d1f5a38d87f434be1c55d2287ec6b5135db3745ca296c99e2 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10c0/be6a02d851fc2a4ae3e9de81710d861de3ba35ac927268973eb3cb618873a05b9424656df464dd43bd7dc3fc5295c3f5b3c8349494f87c7af50ec59ef14e0b98 + languageName: node + linkType: hard + +"pg-numeric@npm:1.0.2": + version: 1.0.2 + resolution: "pg-numeric@npm:1.0.2" + checksum: 10c0/43dd9884e7b52c79ddc28d2d282d7475fce8bba13452d33c04ceb2e0a65f561edf6699694e8e1c832ff9093770496363183c950dd29608e1bdd98f344b25bca9 + languageName: node + linkType: hard + +"pg-pool@npm:^3.9.6": + version: 3.9.6 + resolution: "pg-pool@npm:3.9.6" + peerDependencies: + pg: ">=8.0" + checksum: 10c0/458d50a4e7260977f076472d40d0796fa8b513af7e3ce1bf65557e10724e9c13653661c883f6650dff92d0a1a5ff4e7a001a8262b786c1ad4cfbd35c3354353e + languageName: node + linkType: hard + +"pg-protocol@npm:*, pg-protocol@npm:^1.9.5": + version: 1.9.5 + resolution: "pg-protocol@npm:1.9.5" + checksum: 10c0/5cb3444cf973adadd22ee9ea26bb1674f0d980ef8f9c66d426bbe67fc9cb5f0ca4a204bf7e432b3ab2ab59ac8227f4b18ab3b2e64eaed537e037e916991c7319 + languageName: node + linkType: hard + +"pg-types@npm:^2.1.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10c0/ab3f8069a323f601cd2d2279ca8c425447dab3f9b61d933b0601d7ffc00d6200df25e26a4290b2b0783b59278198f7dd2ed03e94c4875797919605116a577c65 + languageName: node + linkType: hard + +"pg-types@npm:^4.0.1, pg-types@npm:^4.1.0": + version: 4.1.0 + resolution: "pg-types@npm:4.1.0" + dependencies: + pg-int8: "npm:1.0.1" + pg-numeric: "npm:1.0.2" + postgres-array: "npm:~3.0.1" + postgres-bytea: "npm:~3.0.0" + postgres-date: "npm:~2.1.0" + postgres-interval: "npm:^3.0.0" + postgres-range: "npm:^1.1.1" + checksum: 10c0/462a56b5c34e0dff38fc5f6ac939c66e80a92333a79bfb1d963ffe0383ca33427a16899e0b9d23235ce1f5df42473373fb9c9f24e1f614f8698b58cc45435ca6 + languageName: node + linkType: hard + +"pg@npm:^8.15.6": + version: 8.15.6 + resolution: "pg@npm:8.15.6" + dependencies: + pg-cloudflare: "npm:^1.2.5" + pg-connection-string: "npm:^2.8.5" + pg-pool: "npm:^3.9.6" + pg-protocol: "npm:^1.9.5" + pg-types: "npm:^2.1.0" + pgpass: "npm:1.x" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10c0/d4dc81020ebd137b6cf6228e43c643067acb8240079a07f7b9a7e97be0f33ad4d8c6f2a3f5b512ad87180e3d48e651fbd72885aa807ab58a715da8f3efea0fab + languageName: node + linkType: hard + +"pgpass@npm:1.x": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: "npm:^4.1.0" + checksum: 10c0/5ea6c9b2de04c33abb08d33a2dded303c4a3c7162a9264519cbe85c0a9857d712463140ba42fad0c7cd4b21f644dd870b45bb2e02fcbe505b4de0744fd802c1d + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"pidtree@npm:^0.6.0": + version: 0.6.0 + resolution: "pidtree@npm:0.6.0" + bin: + pidtree: bin/pidtree.js + checksum: 10c0/0829ec4e9209e230f74ebf4265f5ccc9ebfb488334b525cb13f86ff801dca44b362c41252cd43ae4d7653a10a5c6ab3be39d2c79064d6895e0d78dc50a5ed6e9 + languageName: node + linkType: hard + +"pify@npm:5.0.0": + version: 5.0.0 + resolution: "pify@npm:5.0.0" + checksum: 10c0/9f6f3cd1f159652692f514383efe401a06473af35a699962230ad1c4c9796df5999961461fc1a3b81eed8e3e74adb8bd032474fb3f93eb6bdbd9f33328da1ed2 + languageName: node + linkType: hard + +"pify@npm:^2.3.0": + version: 2.3.0 + resolution: "pify@npm:2.3.0" + checksum: 10c0/551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc + languageName: node + linkType: hard + +"pify@npm:^3.0.0": + version: 3.0.0 + resolution: "pify@npm:3.0.0" + checksum: 10c0/fead19ed9d801f1b1fcd0638a1ac53eabbb0945bf615f2f8806a8b646565a04a1b0e7ef115c951d225f042cca388fdc1cd3add46d10d1ed6951c20bd2998af10 + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"pluralize@npm:^8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.10": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10c0/523196a6bd8cf660bdf537ad95abd79e546d54180f9afb165a4ab3e651ac705d0f8b8ce6b3164fb9e3279ce482c5f751a69eb2d3a1e8eb0fd5e82294fb3ef13e + languageName: node + linkType: hard + +"postgres-array@npm:^3.0.4, postgres-array@npm:~3.0.1": + version: 3.0.4 + resolution: "postgres-array@npm:3.0.4" + checksum: 10c0/47f3e648da512bacdd6a5ed55cf770605ec271330789faeece0fd13805a49f376d6e5c9e0e353377be11a9545e727dceaa2473566c505432bf06366ccd04c6b2 + languageName: node + linkType: hard + +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10c0/cbd56207e4141d7fbf08c86f2aebf21fa7064943d3f808ec85f442ff94b48d891e7a144cc02665fb2de5dbcb9b8e3183a2ac749959e794b4a4cfd379d7a21d08 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: 10c0/febf2364b8a8953695cac159eeb94542ead5886792a9627b97e33f6b5bb6e263bc0706ab47ec221516e79fbd6b2452d668841830fb3b49ec6c0fc29be61892ce + languageName: node + linkType: hard + +"postgres-bytea@npm:~3.0.0": + version: 3.0.0 + resolution: "postgres-bytea@npm:3.0.0" + dependencies: + obuf: "npm:~1.1.2" + checksum: 10c0/41c79cc48aa730c5ba3eda6ab989a940034f07a1f57b8f2777dce56f1b8cca16c5870582932b5b10cc605048aef9b6157e06253c871b4717cafc6d00f55376aa + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10c0/0ff91fccc64003e10b767fcfeefb5eaffbc522c93aa65d5051c49b3c4ce6cb93ab091a7d22877a90ad60b8874202c6f1d0f935f38a7235ed3b258efd54b97ca9 + languageName: node + linkType: hard + +"postgres-date@npm:~2.1.0": + version: 2.1.0 + resolution: "postgres-date@npm:2.1.0" + checksum: 10c0/00a7472c10788f6b0d08d24108bf1eb80858de1bd6317740198a564918ea4a69b80c98148167b92ae688abd606483020d0de0dd3a36f3ea9a3e26bbeef3464f4 + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10c0/c1734c3cb79e7f22579af0b268a463b1fa1d084e742a02a7a290c4f041e349456f3bee3b4ee0bb3f226828597f7b76deb615c1b857db9a742c45520100456272 + languageName: node + linkType: hard + +"postgres-interval@npm:^3.0.0": + version: 3.0.0 + resolution: "postgres-interval@npm:3.0.0" + checksum: 10c0/8b570b30ea37c685e26d136d34460f246f98935a1533defc4b53bb05ee23ae3dc7475b718ec7ea607a57894d8c6b4f1adf67ca9cc83a75bdacffd427d5c68de8 + languageName: node + linkType: hard + +"postgres-range@npm:^1.1.1": + version: 1.1.4 + resolution: "postgres-range@npm:1.1.4" + checksum: 10c0/254494ef81df208e0adeae6b66ce394aba37914ea14c7ece55a45fb6691b7db04bee74c825380a47c887a9f87158fd3d86f758f9cc60b76d3a38ce5aca7912e8 + languageName: node + linkType: hard + +"prebuild-install@npm:^7.1.1": + version: 7.1.3 + resolution: "prebuild-install@npm:7.1.3" + dependencies: + detect-libc: "npm:^2.0.0" + expand-template: "npm:^2.0.3" + github-from-package: "npm:0.0.0" + minimist: "npm:^1.2.3" + mkdirp-classic: "npm:^0.5.3" + napi-build-utils: "npm:^2.0.0" + node-abi: "npm:^3.3.0" + pump: "npm:^3.0.0" + rc: "npm:^1.2.7" + simple-get: "npm:^4.0.0" + tar-fs: "npm:^2.0.0" + tunnel-agent: "npm:^0.6.0" + bin: + prebuild-install: bin.js + checksum: 10c0/25919a42b52734606a4036ab492d37cfe8b601273d8dfb1fa3c84e141a0a475e7bad3ab848c741d2f810cef892fcf6059b8c7fe5b29f98d30e0c29ad009bedff + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prelude-ls@npm:~1.1.2": + version: 1.1.2 + resolution: "prelude-ls@npm:1.1.2" + checksum: 10c0/7284270064f74e0bb7f04eb9bff7be677e4146417e599ccc9c1200f0f640f8b11e592d94eb1b18f7aa9518031913bb42bea9c86af07ba69902864e61005d6f18 + languageName: node + linkType: hard + +"prettier-plugin-organize-imports@npm:4.3.0": + version: 4.3.0 + resolution: "prettier-plugin-organize-imports@npm:4.3.0" + peerDependencies: + prettier: ">=2.0" + typescript: ">=2.9" + vue-tsc: ^2.1.0 || 3 + peerDependenciesMeta: + vue-tsc: + optional: true + checksum: 10c0/57e5a3e611f1da6614fe1228d306b9a361d9249140abfdfcf21cffc9de13bda204c01f44bed0396786269541b63e12b05e1eab03e43d2e201320e4211af88bc6 + languageName: node + linkType: hard + +"prettier@npm:3.5.3": + version: 3.5.3 + resolution: "prettier@npm:3.5.3" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/3880cb90b9dc0635819ab52ff571518c35bd7f15a6e80a2054c05dbc8a3aa6e74f135519e91197de63705bcb38388ded7e7230e2178432a1468005406238b877 + languageName: node + linkType: hard + +"pretty-format@npm:30.0.5": + version: 30.0.5 + resolution: "pretty-format@npm:30.0.5" + dependencies: + "@jest/schemas": "npm:30.0.5" + ansi-styles: "npm:^5.2.0" + react-is: "npm:^18.3.1" + checksum: 10c0/9f6cf1af5c3169093866c80adbfdad32f69c692b62f24ba3ca8cdec8519336123323f896396f9fa40346a41b197c5f6be15aec4d8620819f12496afaaca93f81 + languageName: node + linkType: hard + +"pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"proc-log@npm:^4.0.0, proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"process-on-spawn@npm:^1.0.0": + version: 1.1.0 + resolution: "process-on-spawn@npm:1.1.0" + dependencies: + fromentries: "npm:^1.2.0" + checksum: 10c0/d7379a78e2ecc482d1f79be480505b68449b46c8736bcd94ae839c979f39517425b23d44d4170a8dc0ed5fe5f795e00fdff701c305d06d92dd899e132e3ee8b0 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + +"proggy@npm:^2.0.0": + version: 2.0.0 + resolution: "proggy@npm:2.0.0" + checksum: 10c0/1bfc14fa95769e6dd7e91f9d3cae8feb61e6d833ed7210d87ee5413bfa068f4ee7468483da96b2f138c40a7e91a2307f5d5d2eb6de9761c21e266a34602e6a5f + languageName: node + linkType: hard + +"progress@npm:^2.0.0": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c + languageName: node + linkType: hard + +"promise-all-reject-late@npm:^1.0.0": + version: 1.0.1 + resolution: "promise-all-reject-late@npm:1.0.1" + checksum: 10c0/f1af0c7b0067e84d64751148ee5bb6c3e84f4a4d1316d6fe56261e1d2637cf71b49894bcbd2c6daf7d45afb1bc99efc3749be277c3e0518b70d0c5a29d037011 + languageName: node + linkType: hard + +"promise-call-limit@npm:^3.0.1": + version: 3.0.2 + resolution: "promise-call-limit@npm:3.0.2" + checksum: 10c0/1f984c16025925594d738833f5da7525b755f825a198d5a0cac1c0280b4f38ecc3c32c1f4e5ef614ddcfd6718c1a8c3f98a3290ae6f421342281c9a88c488bf7 + languageName: node + linkType: hard + +"promise-inflight@npm:^1.0.1": + version: 1.0.1 + resolution: "promise-inflight@npm:1.0.1" + checksum: 10c0/d179d148d98fbff3d815752fa9a08a87d3190551d1420f17c4467f628214db12235ae068d98cd001f024453676d8985af8f28f002345646c4ece4600a79620bc + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"promzard@npm:^1.0.0": + version: 1.0.2 + resolution: "promzard@npm:1.0.2" + dependencies: + read: "npm:^3.0.1" + checksum: 10c0/d53c4ecb8b606b7e4bdeab14ac22c5f81a57463d29de1b8fe43bbc661106d9e4a79d07044bd3f69bde82c7ebacba7307db90a9699bc20482ce637bdea5fb8e4b + languageName: node + linkType: hard + +"prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"proto-list@npm:~1.2.1": + version: 1.2.4 + resolution: "proto-list@npm:1.2.4" + checksum: 10c0/b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12 + languageName: node + linkType: hard + +"protocols@npm:^2.0.0, protocols@npm:^2.0.1": + version: 2.0.2 + resolution: "protocols@npm:2.0.2" + checksum: 10c0/b87d78c1fcf038d33691da28447ce94011d5c7f0c7fd25bcb5fb4d975991c99117873200c84f4b6a9d7f8b9092713a064356236960d1473a7d6fcd4228897b60 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"pump@npm:^1.0.0": + version: 1.0.3 + resolution: "pump@npm:1.0.3" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/fa8c1ef9c82d596872446ba0aae5a1b01cb51fcf876b8a93edd7b8abd03e0dc5ffe2683a939c2db142aa587f86dec69028c49a043ef37145e8639859bb57401e + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.2 + resolution: "pump@npm:3.0.2" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/5ad655cb2a7738b4bcf6406b24ad0970d680649d996b55ad20d1be8e0c02394034e4c45ff7cd105d87f1e9b96a0e3d06fd28e11fae8875da26e7f7a8e2c9726f + languageName: node + linkType: hard + +"punycode.js@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode.js@npm:2.3.1" + checksum: 10c0/1d12c1c0e06127fa5db56bd7fdf698daf9a78104456a6b67326877afc21feaa821257b171539caedd2f0524027fa38e67b13dd094159c8d70b6d26d2bea4dfdb + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"python-struct@npm:^1.1.3": + version: 1.1.3 + resolution: "python-struct@npm:1.1.3" + dependencies: + long: "npm:^4.0.0" + checksum: 10c0/f4b8e115bddb6c2454ecc39d32a3c2859300b2ffbc43d2b8ca2cf0932c48eb739d97ed689d3dee4d07756dea482d257594d6410a64be8f65c2cbe951fed8f14f + languageName: node + linkType: hard + +"q@npm:^1.5.1": + version: 1.5.1 + resolution: "q@npm:1.5.1" + checksum: 10c0/7855fbdba126cb7e92ef3a16b47ba998c0786ec7fface236e3eb0135b65df36429d91a86b1fff3ab0927b4ac4ee88a2c44527c7c3b8e2a37efbec9fe34803df4 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-lru@npm:^4.0.1": + version: 4.0.1 + resolution: "quick-lru@npm:4.0.1" + checksum: 10c0/f9b1596fa7595a35c2f9d913ac312fede13d37dc8a747a51557ab36e11ce113bbe88ef4c0154968845559a7709cb6a7e7cbe75f7972182451cd45e7f057a334d + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: 10c0/a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + +"rambda@npm:^7.4.0": + version: 7.5.0 + resolution: "rambda@npm:7.5.0" + checksum: 10c0/7285b60cfc0737394dda6d467ef65a97221f9e208041d212378d78264d17acd372e09070f570af821314a9243b4edf465cbb5e15297ad44e484eac10535b8920 + languageName: node + linkType: hard + +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: "npm:^5.1.0" + checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + languageName: node + linkType: hard + +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0, react-is@npm:^18.3.1": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"read-cmd-shim@npm:4.0.0, read-cmd-shim@npm:^4.0.0": + version: 4.0.0 + resolution: "read-cmd-shim@npm:4.0.0" + checksum: 10c0/e62db17ec9708f1e7c6a31f0a46d43df2069d85cf0df3b9d1d99e5ed36e29b1e8b2f8a427fd8bbb9bc40829788df1471794f9b01057e4b95ed062806e4df5ba9 + languageName: node + linkType: hard + +"read-package-json-fast@npm:^3.0.0, read-package-json-fast@npm:^3.0.2": + version: 3.0.2 + resolution: "read-package-json-fast@npm:3.0.2" + dependencies: + json-parse-even-better-errors: "npm:^3.0.0" + npm-normalize-package-bin: "npm:^3.0.0" + checksum: 10c0/37787e075f0260a92be0428687d9020eecad7ece3bda37461c2219e50d1ec183ab6ba1d9ada193691435dfe119a42c8a5b5b5463f08c8ddbc3d330800b265318 + languageName: node + linkType: hard + +"read-pkg-up@npm:^3.0.0": + version: 3.0.0 + resolution: "read-pkg-up@npm:3.0.0" + dependencies: + find-up: "npm:^2.0.0" + read-pkg: "npm:^3.0.0" + checksum: 10c0/2cd0a180260b0d235990e6e9c8c2330a03882d36bc2eba8930e437ef23ee52a68a894e7e1ccb1c33f03bcceb270a861ee5f7eac686f238857755e2cddfb48ffd + languageName: node + linkType: hard + +"read-pkg-up@npm:^7.0.1": + version: 7.0.1 + resolution: "read-pkg-up@npm:7.0.1" + dependencies: + find-up: "npm:^4.1.0" + read-pkg: "npm:^5.2.0" + type-fest: "npm:^0.8.1" + checksum: 10c0/82b3ac9fd7c6ca1bdc1d7253eb1091a98ff3d195ee0a45386582ce3e69f90266163c34121e6a0a02f1630073a6c0585f7880b3865efcae9c452fa667f02ca385 + languageName: node + linkType: hard + +"read-pkg@npm:^3.0.0": + version: 3.0.0 + resolution: "read-pkg@npm:3.0.0" + dependencies: + load-json-file: "npm:^4.0.0" + normalize-package-data: "npm:^2.3.2" + path-type: "npm:^3.0.0" + checksum: 10c0/65acf2df89fbcd506b48b7ced56a255ba00adf7ecaa2db759c86cc58212f6fd80f1f0b7a85c848551a5d0685232e9b64f45c1fd5b48d85df2761a160767eeb93 + languageName: node + linkType: hard + +"read-pkg@npm:^5.2.0": + version: 5.2.0 + resolution: "read-pkg@npm:5.2.0" + dependencies: + "@types/normalize-package-data": "npm:^2.4.0" + normalize-package-data: "npm:^2.5.0" + parse-json: "npm:^5.0.0" + type-fest: "npm:^0.6.0" + checksum: 10c0/b51a17d4b51418e777029e3a7694c9bd6c578a5ab99db544764a0b0f2c7c0f58f8a6bc101f86a6fceb8ba6d237d67c89acf6170f6b98695d0420ddc86cf109fb + languageName: node + linkType: hard + +"read@npm:^3.0.1": + version: 3.0.1 + resolution: "read@npm:3.0.1" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/af524994ff7cf94aa3ebd268feac509da44e58be7ed2a02775b5ee6a7d157b93b919e8c5ead91333f86a21fbb487dc442760bc86354c18b84d334b8cec33723a + languageName: node + linkType: hard + +"readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"readable-stream@npm:^4.2.0": + version: 4.7.0 + resolution: "readable-stream@npm:4.7.0" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10c0/fd86d068da21cfdb10f7a4479f2e47d9c0a9b0c862fc0c840a7e5360201580a55ac399c764b12a4f6fa291f8cee74d9c4b7562e0d53b3c4b2769f2c98155d957 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"redent@npm:^3.0.0": + version: 3.0.0 + resolution: "redent@npm:3.0.0" + dependencies: + indent-string: "npm:^4.0.0" + strip-indent: "npm:^3.0.0" + checksum: 10c0/d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regexp-tree@npm:^0.1.24, regexp-tree@npm:~0.1.1": + version: 0.1.27 + resolution: "regexp-tree@npm:0.1.27" + bin: + regexp-tree: bin/regexp-tree + checksum: 10c0/f636f44b4a0d93d7d6926585ecd81f63e4ce2ac895bc417b2ead0874cd36b337dcc3d0fedc63f69bf5aaeaa4340f36ca7e750c9687cceaf8087374e5284e843c + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"regexpp@npm:^2.0.1": + version: 2.0.1 + resolution: "regexpp@npm:2.0.1" + checksum: 10c0/4ac2cf4c68941728bffbba5a8f597a23385c2c05afc5bf5de74744ee048765ab84ea3ce3c6a5bdce102a3a54514b75b7add48b4c1abd925ca3afd40e537f2b5f + languageName: node + linkType: hard + +"registry-auth-token@npm:^5.1.0": + version: 5.1.0 + resolution: "registry-auth-token@npm:5.1.0" + dependencies: + "@pnpm/npm-conf": "npm:^2.1.0" + checksum: 10c0/316229bd8a4acc29a362a7a3862ff809e608256f0fd9e0b133412b43d6a9ea18743756a0ec5ee1467a5384e1023602b85461b3d88d1336b11879e42f7cf02c12 + languageName: node + linkType: hard + +"regjsparser@npm:^0.9.1": + version: 0.9.1 + resolution: "regjsparser@npm:0.9.1" + dependencies: + jsesc: "npm:~0.5.0" + bin: + regjsparser: bin/parser + checksum: 10c0/fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225 + languageName: node + linkType: hard + +"release-zalgo@npm:^1.0.0": + version: 1.0.0 + resolution: "release-zalgo@npm:1.0.0" + dependencies: + es6-error: "npm:^4.0.1" + checksum: 10c0/9e161feb073f9e3aa714bb077d67592c34ee578f5b9cff8e2d492423fe2002d5b1e6d11ffcd5c564b9a0ee9435f25569567b658a82b9af931e7ac1313925628a + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10c0/db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + +"reserved-identifiers@npm:^1.0.0": + version: 1.0.0 + resolution: "reserved-identifiers@npm:1.0.0" + checksum: 10c0/e6aa8e3b6c0c2d29e40c4597c0d202911c91b67f875da396340c2213ee7a8924476da54a5a0855c5acb409a9bc9aa84dacfdaa145528883746cbad74666189e9 + languageName: node + linkType: hard + +"resolve-alpn@npm:^1.2.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"resolve.exports@npm:2.0.3": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d + languageName: node + linkType: hard + +"resolve@npm:^1.10.0, resolve@npm:^1.22.4": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.5": + version: 2.0.0-next.5 + resolution: "resolve@npm:2.0.0-next.5" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": + version: 2.0.0-next.5 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 + languageName: node + linkType: hard + +"responselike@npm:^3.0.0": + version: 3.0.0 + resolution: "responselike@npm:3.0.0" + dependencies: + lowercase-keys: "npm:^3.0.0" + checksum: 10c0/8af27153f7e47aa2c07a5f2d538cb1e5872995f0e9ff77def858ecce5c3fe677d42b824a62cde502e56d275ab832b0a8bd350d5cd6b467ac0425214ac12ae658 + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/8051a371d6aa67ff21625fa94e2357bd81ffdc96267f3fb0fc4aaf4534028343836548ef34c240ffa8c25b280ca35eb36be00b3cb2133fa4f51896d7e73c6b4f + languageName: node + linkType: hard + +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + +"retry-as-promised@npm:^7.1.1": + version: 7.1.1 + resolution: "retry-as-promised@npm:7.1.1" + checksum: 10c0/d0f8477d0e5e3c979947824ea415ea622c4a9f423d6f2d2c421182243085c8aaf4686efcaccb4573a7f6bf4b094d9ee39d239f20b9e31ed601dd489ea3713198 + languageName: node + linkType: hard + +"retry-request@npm:^7.0.0": + version: 7.0.2 + resolution: "retry-request@npm:7.0.2" + dependencies: + "@types/request": "npm:^2.48.8" + extend: "npm:^3.0.2" + teeny-request: "npm:^9.0.0" + checksum: 10c0/c79936695a43db1bc82a7bad348a1e0be1c363799be2e1fa87b8c3aeb5dabf0ccb023b811aa5000c000ee73e196b88febff7d3e22cbb63a77175228514256155 + languageName: node + linkType: hard + +"retry@npm:0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa + languageName: node + linkType: hard + +"rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + +"rimraf@npm:2.6.3": + version: 2.6.3 + resolution: "rimraf@npm:2.6.3" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: ./bin.js + checksum: 10c0/f1e646f8c567795f2916aef7aadf685b543da6b9a53e482bb04b07472c7eef2b476045ba1e29f401c301c66b630b22b815ab31fdd60c5e1ae6566ff523debf45 + languageName: node + linkType: hard + +"rimraf@npm:5.0.10": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc + languageName: node + linkType: hard + +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"rimraf@npm:^4.4.1": + version: 4.4.1 + resolution: "rimraf@npm:4.4.1" + dependencies: + glob: "npm:^9.2.0" + bin: + rimraf: dist/cjs/src/bin.js + checksum: 10c0/8c5e142d26d8b222be9dc9a1a41ba48e95d8f374e813e66a8533e87c6180174fcb3f573b9b592eca12740ebf8b78526d136acd971d4a790763d6f2232c34fa24 + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: 10c0/bd821bbf154b8e6c8ecffeaf0c33cebbb78eb2987476c3f6b420d67ab4c5301faa905dec99ded76ebb3a7042b4e440189ae6d85bbbd3fc6e8d493347ecda8bfe + languageName: node + linkType: hard + +"run-async@npm:^2.4.0": + version: 2.4.1 + resolution: "run-async@npm:2.4.1" + checksum: 10c0/35a68c8f1d9664f6c7c2e153877ca1d6e4f886e5ca067c25cdd895a6891ff3a1466ee07c63d6a9be306e9619ff7d509494e6d9c129516a36b9fd82263d579ee1 + languageName: node + linkType: hard + +"run-con@npm:~1.3.2": + version: 1.3.2 + resolution: "run-con@npm:1.3.2" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~4.1.0" + minimist: "npm:^1.2.8" + strip-json-comments: "npm:~3.1.1" + bin: + run-con: cli.js + checksum: 10c0/b0bdd3083cf9f188e72df8905a1a40a1478e2a7437b0312ab1b824e058129388b811705ee7874e9a707e5de0e8fb8eb790da3aa0a23375323feecd1da97d5cf6 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rxjs@npm:7.8.2, rxjs@npm:^7.5.5": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 + languageName: node + linkType: hard + +"rxjs@npm:^6.6.0": + version: 6.6.7 + resolution: "rxjs@npm:6.6.7" + dependencies: + tslib: "npm:^1.9.0" + checksum: 10c0/e556a13a9aa89395e5c9d825eabcfa325568d9c9990af720f3f29f04a888a3b854f25845c2b55875d875381abcae2d8100af9cacdc57576e7ed6be030a01d2fe + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d + languageName: node + linkType: hard + +"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.0.3, safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"safe-regex@npm:^2.1.1": + version: 2.1.1 + resolution: "safe-regex@npm:2.1.1" + dependencies: + regexp-tree: "npm:~0.1.1" + checksum: 10c0/53eb5d3ecf4b3c0954dff465eb179af4d2f5f77f74ba7b57489adbc4fa44454c3d391f37379cd28722d9ac6fa5b70be3f4645d4bd25df395fd99b934f6ec9265 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10c0/baea14971858cadd65df23894a40588ed791769db21bafb7fd7608397dbdce9c5aac60748abae9995e0fc37e15f2061980501e012cd48859740796bea2987f49 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.1.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0, semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.1.2, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.1, semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10c0/9a90527a51300cf5faea7fae0c037728f9ddcff23ac083883774c74d180c0a03c31aab43d5c3347512e8c1b31a0d4712512ec82beb71aa79b85149f9abeb5467 + languageName: node + linkType: hard + +"seq-queue@npm:^0.0.5": + version: 0.0.5 + resolution: "seq-queue@npm:0.0.5" + checksum: 10c0/ec870fc392f0e6e99ec0e551c3041c1a66144d1580efabae7358e572de127b0ad2f844c95a4861d2e6203f836adea4c8196345b37bed55331ead8f22d99ac84c + languageName: node + linkType: hard + +"sequelize-pool@npm:^8.0.1": + version: 8.0.1 + resolution: "sequelize-pool@npm:8.0.1" + checksum: 10c0/1c0d9324931b4982ddbde0e5c6a50fc8a8e7b6d00ff69e493298c1600560d81408ea8646e2e96498dbfcb41c674cfae2f022339428c61f9293a682c6f6c464b2 + languageName: node + linkType: hard + +"serialize-javascript@npm:^6.0.2": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" + dependencies: + randombytes: "npm:^2.1.0" + checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e + languageName: node + linkType: hard + +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10c0/7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10c0/9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shell-quote@npm:1.8.3": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"signal-exit@npm:3.0.7, signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"sigstore@npm:^2.2.0": + version: 2.3.1 + resolution: "sigstore@npm:2.3.1" + dependencies: + "@sigstore/bundle": "npm:^2.3.2" + "@sigstore/core": "npm:^1.0.0" + "@sigstore/protobuf-specs": "npm:^0.3.2" + "@sigstore/sign": "npm:^2.3.2" + "@sigstore/tuf": "npm:^2.3.4" + "@sigstore/verify": "npm:^1.2.1" + checksum: 10c0/8906b1074130d430d707e46f15c66eb6996891dc0d068705f1884fb1251a4a367f437267d44102cdebcee34f1768b3f30131a2ec8fb7aac74ba250903a459aa7 + languageName: node + linkType: hard + +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: "npm:^6.0.0" + once: "npm:^1.3.1" + simple-concat: "npm:^1.0.0" + checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 + languageName: node + linkType: hard + +"simple-lru-cache@npm:^0.0.2": + version: 0.0.2 + resolution: "simple-lru-cache@npm:0.0.2" + checksum: 10c0/1fa03f9b61cdf2d385f38a8bb2dd9eb41748dea2a6fc4c4d157bdb3633c556317ea7281feebce8942c8835f26364db443f3b9029d7bc09784ca3005e093cb79d + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + +"sinon-chai@npm:3.7.0": + version: 3.7.0 + resolution: "sinon-chai@npm:3.7.0" + peerDependencies: + chai: ^4.0.0 + sinon: ">=4.0.0" + checksum: 10c0/9bbb0494be89c313f5287c2a0c9890104e3070ad6bf06c359b0c3cd776ea6273f7846ee1ab01fa2e0328e741f8717348669ee232e83b94b193676a56f48d2c63 + languageName: node + linkType: hard + +"sinon@npm:18.0.1": + version: 18.0.1 + resolution: "sinon@npm:18.0.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:11.2.2" + "@sinonjs/samsam": "npm:^8.0.0" + diff: "npm:^5.2.0" + nise: "npm:^6.0.0" + supports-color: "npm:^7" + checksum: 10c0/c4554b8d9654d42fc4baefecd3b5ac42bcce73ad926d58521233d9c355dc2c1a0d73c55e5b2c929b6814e528cd9b54bc61096b9288579f9b284edd6e3d2da3df + languageName: node + linkType: hard + +"slash@npm:3.0.0, slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slice-ansi@npm:^2.1.0": + version: 2.1.0 + resolution: "slice-ansi@npm:2.1.0" + dependencies: + ansi-styles: "npm:^3.2.0" + astral-regex: "npm:^1.0.0" + is-fullwidth-code-point: "npm:^2.0.0" + checksum: 10c0/c317b21ec9e3d3968f3d5b548cbfc2eae331f58a03f1352621020799cbe695b3611ee972726f8f32d4ca530065a5ec9c74c97fde711c1f41b4a1585876b2c191 + languageName: node + linkType: hard + +"slice-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "slice-ansi@npm:7.1.0" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/631c971d4abf56cf880f034d43fcc44ff883624867bf11ecbd538c47343911d734a4656d7bc02362b40b89d765652a7f935595441e519b59e2ad3f4d5d6fe7ca + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"smol-toml@npm:~1.5.2": + version: 1.5.2 + resolution: "smol-toml@npm:1.5.2" + checksum: 10c0/ccfe5dda80c1d0c45869140b1e695a13a81ba7c57c1ca083146fe2f475d6f57031c12410f95d53a5acb3a1504e8e8e12cab36871909e8c8ce0c7011ccd22a2ac + languageName: node + linkType: hard + +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/ab19a913969f58f4474fe9f6e8a026c8a2142a01f40b52b79368068343177f818cdfef0b0c6b9558f298782441d5ca8ed5932eb57822439fad791d866e62cecd + languageName: node + linkType: hard + +"snowflake-sdk@npm:^2.1.0": + version: 2.1.0 + resolution: "snowflake-sdk@npm:2.1.0" + dependencies: + "@aws-sdk/client-s3": "npm:^3.726.0" + "@azure/storage-blob": "npm:12.26.x" + "@google-cloud/storage": "npm:^7.7.0" + "@smithy/node-http-handler": "npm:^4.0.1" + "@techteamer/ocsp": "npm:1.0.1" + asn1.js-rfc2560: "npm:^5.0.0" + asn1.js-rfc5280: "npm:^3.0.0" + axios: "npm:^1.8.3" + big-integer: "npm:^1.6.43" + bignumber.js: "npm:^9.1.2" + binascii: "npm:0.0.2" + bn.js: "npm:^5.2.1" + browser-request: "npm:^0.3.3" + expand-tilde: "npm:^2.0.2" + fast-xml-parser: "npm:^4.2.5" + fastest-levenshtein: "npm:^1.0.16" + generic-pool: "npm:^3.8.2" + glob: "npm:^10.0.0" + https-proxy-agent: "npm:^7.0.2" + jsonwebtoken: "npm:^9.0.0" + mime-types: "npm:^2.1.29" + mkdirp: "npm:^1.0.3" + moment: "npm:^2.29.4" + moment-timezone: "npm:^0.5.15" + oauth4webapi: "npm:^3.0.1" + open: "npm:^7.3.1" + python-struct: "npm:^1.1.3" + simple-lru-cache: "npm:^0.0.2" + toml: "npm:^3.0.0" + uuid: "npm:^8.3.2" + winston: "npm:^3.1.0" + wiremock-rest-client: "npm:^1.11.0" + peerDependencies: + asn1.js: ^5.4.1 + checksum: 10c0/605da000d4f3eccf3b5c5b60609132d7a843694c27c8b49dd8ded65663fab67ad760511c6c8df8dd7c7b5873ed74d0d811734fb854457fb6661062067858dcc3 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^6.0.0": + version: 6.2.1 + resolution: "socks-proxy-agent@npm:6.2.1" + dependencies: + agent-base: "npm:^6.0.2" + debug: "npm:^4.3.3" + socks: "npm:^2.6.2" + checksum: 10c0/d75c1cf1fdd7f8309a43a77f84409b793fc0f540742ef915154e70ac09a08b0490576fe85d4f8d68bbf80e604a62957a17ab5ef50d312fe1442b0ab6f8f6e6f6 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.6.2, socks@npm:^2.8.3": + version: 2.8.4 + resolution: "socks@npm:2.8.4" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5 + languageName: node + linkType: hard + +"sort-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "sort-keys@npm:2.0.0" + dependencies: + is-plain-obj: "npm:^1.0.0" + checksum: 10c0/c11a6313995cb67ccf35fed4b1f6734176cc1d1e350ee311c061a2340ada4f7e23b046db064d518b63adba98c0f763739920c59fb4659a0b8482ec7a1f255081 + languageName: node + linkType: hard + +"sort-object-keys@npm:^1.1.3": + version: 1.1.3 + resolution: "sort-object-keys@npm:1.1.3" + checksum: 10c0/3bf62398658d3ff4bbca0db4ed8f42f98abc41433859f63d02fb0ab953fbe5526be240ec7e5d85aa50fcab6c937f3fa7015abf1ecdeb3045a2281c53953886bf + languageName: node + linkType: hard + +"sort-package-json@npm:^2.15.1": + version: 2.15.1 + resolution: "sort-package-json@npm:2.15.1" + dependencies: + detect-indent: "npm:^7.0.1" + detect-newline: "npm:^4.0.0" + get-stdin: "npm:^9.0.0" + git-hooks-list: "npm:^3.0.0" + is-plain-obj: "npm:^4.1.0" + semver: "npm:^7.6.0" + sort-object-keys: "npm:^1.1.3" + tinyglobby: "npm:^0.2.9" + bin: + sort-package-json: cli.js + checksum: 10c0/a5dcbafde6f4629c34be9e750687b7c5566c5225dad0e887c558e6a794f7fe1d360003eec0bb4875fa74633e648260819623871e79ff64c1749acead3f2bb3c1 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.21": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"spawn-wrap@npm:^2.0.0": + version: 2.0.0 + resolution: "spawn-wrap@npm:2.0.0" + dependencies: + foreground-child: "npm:^2.0.0" + is-windows: "npm:^1.0.2" + make-dir: "npm:^3.0.0" + rimraf: "npm:^3.0.0" + signal-exit: "npm:^3.0.2" + which: "npm:^2.0.1" + checksum: 10c0/0d30001391eedbd588722be74506d3e60582557e754fe3deb3f84f2c84ddca0d72d8132f16502cf312bacb8952cc7abe833d6f45b4e80c8baea3fa56c5554d3d + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.2.0 + resolution: "spdx-correct@npm:3.2.0" + dependencies: + spdx-expression-parse: "npm:^3.0.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/49208f008618b9119208b0dadc9208a3a55053f4fd6a0ae8116861bd22696fc50f4142a35ebfdb389e05ccf2de8ad142573fefc9e26f670522d899f7b2fe7386 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.5.0 + resolution: "spdx-exceptions@npm:2.5.0" + checksum: 10c0/37217b7762ee0ea0d8b7d0c29fd48b7e4dfb94096b109d6255b589c561f57da93bf4e328c0290046115961b9209a8051ad9f525e48d433082fc79f496a4ea940 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: "npm:^2.1.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^4.0.0": + version: 4.0.0 + resolution: "spdx-expression-parse@npm:4.0.0" + dependencies: + spdx-exceptions: "npm:^2.1.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/965c487e77f4fb173f1c471f3eef4eb44b9f0321adc7f93d95e7620da31faa67d29356eb02523cd7df8a7fc1ec8238773cdbf9e45bd050329d2b26492771b736 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.21 + resolution: "spdx-license-ids@npm:3.0.21" + checksum: 10c0/ecb24c698d8496aa9efe23e0b1f751f8a7a89faedcdfcbfabae772b546c2db46ccde8f3bc447a238eb86bbcd4f73fea88720ef3b8394f7896381bec3d7736411 + languageName: node + linkType: hard + +"split2@npm:^3.2.2": + version: 3.2.2 + resolution: "split2@npm:3.2.2" + dependencies: + readable-stream: "npm:^3.0.0" + checksum: 10c0/2dad5603c52b353939befa3e2f108f6e3aff42b204ad0f5f16dd12fd7c2beab48d117184ce6f7c8854f9ee5ffec6faae70d243711dd7d143a9f635b4a285de4e + languageName: node + linkType: hard + +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 + languageName: node + linkType: hard + +"split@npm:^1.0.1": + version: 1.0.1 + resolution: "split@npm:1.0.1" + dependencies: + through: "npm:2" + checksum: 10c0/7f489e7ed5ff8a2e43295f30a5197ffcb2d6202c9cf99357f9690d645b19c812bccf0be3ff336fea5054cda17ac96b91d67147d95dbfc31fbb5804c61962af85 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"sqlite3@npm:^5.1.7": + version: 5.1.7 + resolution: "sqlite3@npm:5.1.7" + dependencies: + bindings: "npm:^1.5.0" + node-addon-api: "npm:^7.0.0" + node-gyp: "npm:8.x" + prebuild-install: "npm:^7.1.1" + tar: "npm:^6.1.11" + peerDependencies: + node-gyp: 8.x + dependenciesMeta: + node-gyp: + optional: true + peerDependenciesMeta: + node-gyp: + optional: true + checksum: 10c0/10daab5d7854bd0ec3c7690c00359cd3444eabc869b68c68dcb61374a8fa5e2f4be06cf0aba78f7a16336d49e83e4631e8af98f8bd33c772fe8d60b45fa60bc1 + languageName: node + linkType: hard + +"sqlstring@npm:^2.3.2": + version: 2.3.3 + resolution: "sqlstring@npm:2.3.3" + checksum: 10c0/3b5dd7badb3d6312f494cfa6c9a381ee630fbe3dbd571c4c9eb8ecdb99a7bf5a1f7a5043191d768797f6b3c04eed5958ac6a5f948b998f0a138294c6d3125fbd + languageName: node + linkType: hard + +"ssri@npm:^10.0.0, ssri@npm:^10.0.6": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + languageName: node + linkType: hard + +"ssri@npm:^8.0.0, ssri@npm:^8.0.1": + version: 8.0.1 + resolution: "ssri@npm:8.0.1" + dependencies: + minipass: "npm:^3.1.1" + checksum: 10c0/5cfae216ae02dcd154d1bbed2d0a60038a4b3a2fcaac3c7e47401ff4e058e551ee74cfdba618871bf168cd583db7b8324f94af6747d4303b73cd4c3f6dc5c9c2 + languageName: node + linkType: hard + +"stable-hash@npm:^0.0.5": + version: 0.0.5 + resolution: "stable-hash@npm:0.0.5" + checksum: 10c0/ca670cb6d172f1c834950e4ec661e2055885df32fee3ebf3647c5df94993b7c2666a5dbc1c9a62ee11fc5c24928579ec5e81bb5ad31971d355d5a341aab493b3 + languageName: node + linkType: hard + +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + +"stream-events@npm:^1.0.5": + version: 1.0.5 + resolution: "stream-events@npm:1.0.5" + dependencies: + stubs: "npm:^3.0.0" + checksum: 10c0/5d235a5799a483e94ea8829526fe9d95d76460032d5e78555fe4f801949ac6a27ea2212e4e0827c55f78726b3242701768adf2d33789465f51b31ed8ebd6b086 + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.2": + version: 1.0.3 + resolution: "stream-shift@npm:1.0.3" + checksum: 10c0/939cd1051ca750d240a0625b106a2b988c45fb5a3be0cebe9a9858cb01bc1955e8c7b9fac17a9462976bea4a7b704e317c5c2200c70f0ca715a3363b9aa4fd3b + languageName: node + linkType: hard + +"string-argv@npm:^0.3.2": + version: 0.3.2 + resolution: "string-argv@npm:0.3.2" + checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:8.1.0, string-width@npm:^8.0.0": + version: 8.1.0 + resolution: "string-width@npm:8.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/749b5d0dab2532b4b6b801064230f4da850f57b3891287023117ab63a464ad79dd208f42f793458f48f3ad121fe2e1f01dd525ff27ead957ed9f205e27406593 + languageName: node + linkType: hard + +"string-width@npm:^3.0.0": + version: 3.1.0 + resolution: "string-width@npm:3.1.0" + dependencies: + emoji-regex: "npm:^7.0.1" + is-fullwidth-code-point: "npm:^2.0.0" + strip-ansi: "npm:^5.1.0" + checksum: 10c0/85fa0d4f106e7999bb68c1c640c76fa69fb8c069dab75b009e29c123914e2d3b532e6cfa4b9d1bd913176fc83dedd7a2d7bf40d21a81a8a1978432cedfb65b91 + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string.prototype.includes@npm:^2.0.1": + version: 2.0.1 + resolution: "string.prototype.includes@npm:2.0.1" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + checksum: 10c0/25ce9c9b49128352a2618fbe8758b46f945817a58a4420f4799419e40a8d28f116e176c7590d767d5327a61e75c8f32c86171063f48e389b9fdd325f1bd04ee5 + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.12": + version: 4.0.12 + resolution: "string.prototype.matchall@npm:4.0.12" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + regexp.prototype.flags: "npm:^1.5.3" + set-function-name: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + languageName: node + linkType: hard + +"string.prototype.repeat@npm:^1.0.0": + version: 1.0.0 + resolution: "string.prototype.repeat@npm:1.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^5.1.0, strip-ansi@npm:^5.2.0": + version: 5.2.0 + resolution: "strip-ansi@npm:5.2.0" + dependencies: + ansi-regex: "npm:^4.1.0" + checksum: 10c0/de4658c8a097ce3b15955bc6008f67c0790f85748bdc025b7bc8c52c7aee94bc4f9e50624516150ed173c3db72d851826cd57e7a85fe4e4bb6dbbebd5d297fdf + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-indent@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-indent@npm:3.0.0" + dependencies: + min-indent: "npm:^1.0.0" + checksum: 10c0/ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679 + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"strnum@npm:^1.1.1": + version: 1.1.2 + resolution: "strnum@npm:1.1.2" + checksum: 10c0/a0fce2498fa3c64ce64a40dada41beb91cabe3caefa910e467dc0518ef2ebd7e4d10f8c2202a6104f1410254cae245066c0e94e2521fb4061a5cb41831952392 + languageName: node + linkType: hard + +"strnum@npm:^2.1.0": + version: 2.1.0 + resolution: "strnum@npm:2.1.0" + checksum: 10c0/5e47f0effca692caf46f12436136964189d28aff5146e4be4e219b81a3ca82436d81b51c9d27735423d416c1be216190ca04b50720000a1ff50479c8420b68a7 + languageName: node + linkType: hard + +"stubs@npm:^3.0.0": + version: 3.0.0 + resolution: "stubs@npm:3.0.0" + checksum: 10c0/841a4ab8c76795d34aefe129185763b55fbf2e4693208215627caea4dd62e1299423dcd96f708d3128e3dfa0e669bae2cb912e6e906d7d81eaf6493196570923 + languageName: node + linkType: hard + +"supports-color@npm:8.1.1, supports-color@npm:^8, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"table@npm:^5.2.3": + version: 5.4.6 + resolution: "table@npm:5.4.6" + dependencies: + ajv: "npm:^6.10.2" + lodash: "npm:^4.17.14" + slice-ansi: "npm:^2.1.0" + string-width: "npm:^3.0.0" + checksum: 10c0/87ad7b7cc926aa06e0e2a91a0fb4fcb8b365da87969bc5c74b54cae5d518a089245f44bf80f945ec1aa74c405782db15eeb1dd1926315d842cdc9dbb9371672e + languageName: node + linkType: hard + +"tar-fs@npm:^1.8.1": + version: 1.16.4 + resolution: "tar-fs@npm:1.16.4" + dependencies: + chownr: "npm:^1.0.1" + mkdirp: "npm:^0.5.1" + pump: "npm:^1.0.0" + tar-stream: "npm:^1.1.2" + checksum: 10c0/5e60a3ed38359cdf2432924350448f42c891547a5ac4f3fa7e24572946491c2be50ca6829200e05db1e1e4833751eaa020edc0c51e3b7809bf37a0a781d95aca + languageName: node + linkType: hard + +"tar-fs@npm:^2.0.0": + version: 2.1.2 + resolution: "tar-fs@npm:2.1.2" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10c0/9c704bd4a53be7565caf34ed001d1428532457fe3546d8fc1233f0f0882c3d2403f8602e8046e0b0adeb31fe95336572a69fb28851a391523126b697537670fc + languageName: node + linkType: hard + +"tar-stream@npm:^1.1.2": + version: 1.6.2 + resolution: "tar-stream@npm:1.6.2" + dependencies: + bl: "npm:^1.0.0" + buffer-alloc: "npm:^1.2.0" + end-of-stream: "npm:^1.0.0" + fs-constants: "npm:^1.0.0" + readable-stream: "npm:^2.3.0" + to-buffer: "npm:^1.1.1" + xtend: "npm:^4.0.0" + checksum: 10c0/ab8528d2cc9ccd0906d1ce6d8089030b2c92a578c57645ff4971452c8c5388b34c7152c04ed64b8510d22a66ffaf0fee58bada7d6ab41ad1e816e31993d59cf3 + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: "npm:^4.0.3" + end-of-stream: "npm:^1.4.1" + fs-constants: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 + languageName: node + linkType: hard + +"tar@npm:6.2.1, tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.1": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + languageName: node + linkType: hard + +"targz@npm:^1.0.1": + version: 1.0.1 + resolution: "targz@npm:1.0.1" + dependencies: + tar-fs: "npm:^1.8.1" + checksum: 10c0/0ebb2deac071fd80c21011ab51ee403aae029b224c1f115933e98d6e7ed3c57dba25e19d870fe8c4ef1394a9afad39778106f280abec03506a1dc333563a3522 + languageName: node + linkType: hard + +"tedious@npm:^19.1.2": + version: 19.1.2 + resolution: "tedious@npm:19.1.2" + dependencies: + "@azure/core-auth": "npm:^1.7.2" + "@azure/identity": "npm:^4.2.1" + "@azure/keyvault-keys": "npm:^4.4.0" + "@js-joda/core": "npm:^5.6.5" + "@types/node": "npm:>=18" + bl: "npm:^6.1.4" + iconv-lite: "npm:^0.7.0" + js-md4: "npm:^0.3.2" + native-duplexpair: "npm:^1.0.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/7d2f043386083c1550bc66dadbd54c0aaf9118c47d5692721b7beaa9ada04f8be0034fac76e4e7c2dc674839b748e31830afb2638c6854b7dca52d69ca0a07fc + languageName: node + linkType: hard + +"teeny-request@npm:^9.0.0": + version: 9.0.0 + resolution: "teeny-request@npm:9.0.0" + dependencies: + http-proxy-agent: "npm:^5.0.0" + https-proxy-agent: "npm:^5.0.0" + node-fetch: "npm:^2.6.9" + stream-events: "npm:^1.0.5" + uuid: "npm:^9.0.0" + checksum: 10c0/1c51a284075b57b7b7f970fc8d855d611912f0e485aa1d1dfda3c0be3f2df392e4ce83b1b39877134041abb7c255f3777f175b27323ef5bf008839e42a1958bc + languageName: node + linkType: hard + +"temp-dir@npm:1.0.0": + version: 1.0.0 + resolution: "temp-dir@npm:1.0.0" + checksum: 10c0/648669d5e154d1961217784c786acadccf0156519c19e0aceda7edc76f5bdfa32a40dd7f88ebea9238ed6e3dedf08b846161916c8947058c384761351be90a8e + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-extensions@npm:^1.0.0": + version: 1.9.0 + resolution: "text-extensions@npm:1.9.0" + checksum: 10c0/9ad5a9f723a871e2d884e132d7e93f281c60b5759c95f3f6b04704856548715d93a36c10dbaf5f12b91bf405f0cf3893bf169d4d143c0f5509563b992d385443 + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"through2@npm:^2.0.0": + version: 2.0.5 + resolution: "through2@npm:2.0.5" + dependencies: + readable-stream: "npm:~2.3.6" + xtend: "npm:~4.0.1" + checksum: 10c0/cbfe5b57943fa12b4f8c043658c2a00476216d79c014895cef1ac7a1d9a8b31f6b438d0e53eecbb81054b93128324a82ecd59ec1a4f91f01f7ac113dcb14eade + languageName: node + linkType: hard + +"through@npm:2, through@npm:2.3.8, through@npm:>=2.2.7 <3, through@npm:^2.3.6": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 10c0/4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc + languageName: node + linkType: hard + +"tiny-jsonc@npm:^1.0.2": + version: 1.0.2 + resolution: "tiny-jsonc@npm:1.0.2" + checksum: 10c0/9d99f461611682ac5e88f8cc7eb8beef7e871f8c81216612aa1e338b7c09674bf1f682604c5d894673b3a1194b85737548bb7074c1f9f1333718243b3c219587 + languageName: node + linkType: hard + +"tinyglobby@npm:0.2.12": + version: 0.2.12 + resolution: "tinyglobby@npm:0.2.12" + dependencies: + fdir: "npm:^6.4.3" + picomatch: "npm:^4.0.2" + checksum: 10c0/7c9be4fd3625630e262dcb19015302aad3b4ba7fc620f269313e688f2161ea8724d6cb4444baab5ef2826eb6bed72647b169a33ec8eea37501832a2526ff540f + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.9, tinyglobby@npm:~0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 10c0/69863947b8c29cabad43fe0ce65cec5bb4b481d15d4b4b21e036b060b3edbf3bc7a5541de1bacb437bb3f7c4538f669752627fdf9b4aaf034cebd172ba373408 + languageName: node + linkType: hard + +"tmp@npm:~0.2.1": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125 + languageName: node + linkType: hard + +"to-buffer@npm:^1.1.1": + version: 1.1.1 + resolution: "to-buffer@npm:1.1.1" + checksum: 10c0/fb9fc6a0103f2b06e2e01c3d291586d0148759d5584f35d0973376434d1b58bd6ee5df9273f0bb1190eb2a5747c894bf49fed571325a7ac10208a48f31736439 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"to-valid-identifier@npm:^1.0.0": + version: 1.0.0 + resolution: "to-valid-identifier@npm:1.0.0" + dependencies: + "@sindresorhus/base62": "npm:^1.0.0" + reserved-identifiers: "npm:^1.0.0" + checksum: 10c0/569b49f43b5aaaa20677e67f0f1cdcff344855149934cfb80c793c7ac7c30e191b224bc81cab40fb57641af9ca73795c78053c164a2addc617671e2d22c13a4a + languageName: node + linkType: hard + +"toml@npm:^3.0.0": + version: 3.0.0 + resolution: "toml@npm:3.0.0" + checksum: 10c0/8d7ed3e700ca602e5419fca343e1c595eb7aa177745141f0761a5b20874b58ee5c878cd045c408da9d130cb2b611c639912210ba96ce2f78e443569aa8060c18 + languageName: node + linkType: hard + +"toposort-class@npm:^1.0.1": + version: 1.0.1 + resolution: "toposort-class@npm:1.0.1" + checksum: 10c0/75eacd421eca239aa480ead62dfd8966cbfc2483fd39e18893a59fe982cd904aa82ecbd46a0cdcea542f4f0a68799e5fc24bcb987029075f02a75679559fa4d7 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 + languageName: node + linkType: hard + +"treeverse@npm:^3.0.0": + version: 3.0.0 + resolution: "treeverse@npm:3.0.0" + checksum: 10c0/286479b9c05a8fb0538ee7d67a5502cea7704f258057c784c9c1118a2f598788b2c0f7a8d89e74648af88af0225b31766acecd78e6060736f09b21dd3fa255db + languageName: node + linkType: hard + +"trim-newlines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-newlines@npm:3.0.1" + checksum: 10c0/03cfefde6c59ff57138412b8c6be922ecc5aec30694d784f2a65ef8dcbd47faef580b7de0c949345abdc56ec4b4abf64dd1e5aea619b200316e471a3dd5bf1f6 + languageName: node + linkType: hard + +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea + languageName: node + linkType: hard + +"ts-api-utils@npm:^1.3.0": + version: 1.4.3 + resolution: "ts-api-utils@npm:1.4.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 10c0/e65dc6e7e8141140c23e1dc94984bf995d4f6801919c71d6dc27cf0cd51b100a91ffcfe5217626193e5bea9d46831e8586febdc7e172df3f1091a7384299e23a + languageName: node + linkType: hard + +"ts-node@npm:10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" + dependencies: + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10c0/5b4f301a2b7a3766a986baf8fc0e177eb80bdba6e396792ff92dc23b5bca8bb279fc96517dcaaef63a3b49bebc6c4c833653ec58155780bc906bdbcf7dda0ef5 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" + dependencies: + json5: "npm:^2.2.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10c0/09a5877402d082bb1134930c10249edeebc0211f36150c35e1c542e5b91f1047b1ccf7da1e59babca1ef1f014c525510f4f870de7c9bda470c73bb4e2721b3ea + languageName: node + linkType: hard + +"tslib@npm:^1.8.1, tslib@npm:^1.9.0": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + +"tuf-js@npm:^2.2.1": + version: 2.2.1 + resolution: "tuf-js@npm:2.2.1" + dependencies: + "@tufjs/models": "npm:2.0.1" + debug: "npm:^4.3.4" + make-fetch-happen: "npm:^13.0.1" + checksum: 10c0/7c17b097571f001730d7be0aeaec6bec46ed2f25bf73990b1133c383d511a1ce65f831e5d6d78770940a85b67664576ff0e4c98e5421bab6d33ff36e4be500c8 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-check@npm:~0.3.2": + version: 0.3.2 + resolution: "type-check@npm:0.3.2" + dependencies: + prelude-ls: "npm:~1.1.2" + checksum: 10c0/776217116b2b4e50e368c7ee0c22c0a85e982881c16965b90d52f216bc296d6a52ef74f9202d22158caacc092a7645b0b8d5fe529a96e3fe35d0fb393966c875 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-detect@npm:^4.0.0, type-detect@npm:^4.1.0": + version: 4.1.0 + resolution: "type-detect@npm:4.1.0" + checksum: 10c0/df8157ca3f5d311edc22885abc134e18ff8ffbc93d6a9848af5b682730ca6a5a44499259750197250479c5331a8a75b5537529df5ec410622041650a7f293e2a + languageName: node + linkType: hard + +"type-fest@npm:^0.18.0": + version: 0.18.1 + resolution: "type-fest@npm:0.18.1" + checksum: 10c0/303f5ecf40d03e1d5b635ce7660de3b33c18ed8ebc65d64920c02974d9e684c72483c23f9084587e9dd6466a2ece1da42ddc95b412a461794dd30baca95e2bac + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^0.4.1": + version: 0.4.1 + resolution: "type-fest@npm:0.4.1" + checksum: 10c0/2e65f43209492638244842f70d86e7325361c92dd1cc8e3bf5728c96b980305087fa5ba60652e9053d56c302ef4f1beb9652a91b72a50da0ea66c6b851f3b9cb + languageName: node + linkType: hard + +"type-fest@npm:^0.6.0": + version: 0.6.0 + resolution: "type-fest@npm:0.6.0" + checksum: 10c0/0c585c26416fce9ecb5691873a1301b5aff54673c7999b6f925691ed01f5b9232db408cdbb0bd003d19f5ae284322523f44092d1f81ca0a48f11f7cf0be8cd38 + languageName: node + linkType: hard + +"type-fest@npm:^0.8.0, type-fest@npm:^0.8.1": + version: 0.8.1 + resolution: "type-fest@npm:0.8.1" + checksum: 10c0/dffbb99329da2aa840f506d376c863bd55f5636f4741ad6e65e82f5ce47e6914108f44f340a0b74009b0cb5d09d6752ae83203e53e98b1192cf80ecee5651636 + languageName: node + linkType: hard + +"type-fest@npm:^4.41.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 + languageName: node + linkType: hard + +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: "npm:^1.0.0" + checksum: 10c0/4ac5b7a93d604edabf3ac58d3a2f7e07487e9f6e98195a080e81dbffdc4127817f470f219d794a843b87052cedef102b53ac9b539855380b8c2172054b7d5027 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 10c0/6005cb31df50eef8b1f3c780eb71a17925f3038a100d82f9406ac2ad1de5eb59f8e6decbdc145b3a1f8e5836e17b0c0002fb698b9fe2516b8f9f9ff602d36412 + languageName: node + linkType: hard + +"typedoc-plugin-mdn-links@npm:5.0.10": + version: 5.0.10 + resolution: "typedoc-plugin-mdn-links@npm:5.0.10" + peerDependencies: + typedoc: 0.27.x || 0.28.x + checksum: 10c0/fea1e9cc23d96a39e9aa3c0b0a2154e024a1482387eafd822b77a6584171e4718b2ff0c64e295343aca34882876ee0ebbf27995ed77e29a86920de9cd6b442ff + languageName: node + linkType: hard + +"typedoc-plugin-missing-exports@npm:3.1.0": + version: 3.1.0 + resolution: "typedoc-plugin-missing-exports@npm:3.1.0" + peerDependencies: + typedoc: 0.26.x || 0.27.x + checksum: 10c0/516964657833f3235f47e2faf7ebccc741ff32923d32af5c3adfdb9bc1fae94dd85885cb735db77d2605e501aee40d5fbe94e8e18ed965d0c1c0198234a7db44 + languageName: node + linkType: hard + +"typedoc@npm:0.27.9": + version: 0.27.9 + resolution: "typedoc@npm:0.27.9" + dependencies: + "@gerrit0/mini-shiki": "npm:^1.24.0" + lunr: "npm:^2.3.9" + markdown-it: "npm:^14.1.0" + minimatch: "npm:^9.0.5" + yaml: "npm:^2.6.1" + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x + bin: + typedoc: bin/typedoc + checksum: 10c0/999668d9d23e1824b762e2c411e2c0860d0ce4a2e61f23a2c31d36a1d6337a763553bc75205aee25ce34659e9315315c720694e9eccd7e7e4755873fdfec1192 + languageName: node + linkType: hard + +"typescript@npm:5.8.3, typescript@npm:>=3 < 6": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin, typescript@patch:typescript@npm%3A>=3 < 6#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb + languageName: node + linkType: hard + +"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": + version: 2.1.0 + resolution: "uc.micro@npm:2.1.0" + checksum: 10c0/8862eddb412dda76f15db8ad1c640ccc2f47cdf8252a4a30be908d535602c8d33f9855dfcccb8b8837855c1ce1eaa563f7fa7ebe3c98fd0794351aab9b9c55fa + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"underscore@npm:^1.13.1": + version: 1.13.7 + resolution: "underscore@npm:1.13.7" + checksum: 10c0/fad2b4aac48847674aaf3c30558f383399d4fdafad6dd02dd60e4e1b8103b52c5a9e5937e0cc05dacfd26d6a0132ed0410ab4258241240757e4a4424507471cd + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici-types@npm:~7.8.0": + version: 7.8.0 + resolution: "undici-types@npm:7.8.0" + checksum: 10c0/9d9d246d1dc32f318d46116efe3cfca5a72d4f16828febc1918d94e58f6ffcf39c158aa28bf5b4fc52f410446bc7858f35151367bd7a49f21746cab6497b709b + languageName: node + linkType: hard + +"unique-filename@npm:^1.1.1": + version: 1.1.1 + resolution: "unique-filename@npm:1.1.1" + dependencies: + unique-slug: "npm:^2.0.0" + checksum: 10c0/d005bdfaae6894da8407c4de2b52f38b3c58ec86e79fc2ee19939da3085374413b073478ec54e721dc8e32b102cf9e50d0481b8331abdc62202e774b789ea874 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + languageName: node + linkType: hard + +"unique-slug@npm:^2.0.0": + version: 2.0.2 + resolution: "unique-slug@npm:2.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/9eabc51680cf0b8b197811a48857e41f1364b25362300c1ff636c0eca5ec543a92a38786f59cf0697e62c6f814b11ecbe64e8093db71246468a1f03b80c83970 + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + languageName: node + linkType: hard + +"universal-user-agent@npm:^6.0.0": + version: 6.0.1 + resolution: "universal-user-agent@npm:6.0.1" + checksum: 10c0/5c9c46ffe19a975e11e6443640ed4c9e0ce48fcc7203325757a8414ac49940ebb0f4667f2b1fa561489d1eb22cb2d05a0f7c82ec20c5cba42e58e188fb19b187 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"unrs-resolver@npm:^1.6.2": + version: 1.7.2 + resolution: "unrs-resolver@npm:1.7.2" + dependencies: + "@unrs/resolver-binding-darwin-arm64": "npm:1.7.2" + "@unrs/resolver-binding-darwin-x64": "npm:1.7.2" + "@unrs/resolver-binding-freebsd-x64": "npm:1.7.2" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.7.2" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.7.2" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.7.2" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.7.2" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.7.2" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.7.2" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.7.2" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.7.2" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.7.2" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.7.2" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.7.2" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.7.2" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.7.2" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.7.2" + napi-postinstall: "npm:^0.2.2" + dependenciesMeta: + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10c0/c293db95c59b08e33f3bfb00042120fb90fd5448bd1790cd2dc779a13eb6062dddf04a91b72c73d3635b0c539552435675ce816fa52e66bb0cd7b7e5a2f6399c + languageName: node + linkType: hard + +"upath@npm:2.0.1": + version: 2.0.1 + resolution: "upath@npm:2.0.1" + checksum: 10c0/79e8e1296b00e24a093b077cfd7a238712d09290c850ce59a7a01458ec78c8d26dcc2ab50b1b9d6a84dabf6511fb4969afeb8a5c9a001aa7272b9cc74c34670f + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.3": + version: 1.1.3 + resolution: "update-browserslist-db@npm:1.1.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 + languageName: node + linkType: hard + +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10c0/ccad6a0b143310ebfba2b5841f30bef71246297385f1329c022c902b2b5fc5aee009faf1ac9da5ab3ba7f615b88f5dc1cd80461b18a8f38cb1d4c3eb92538ea9 + languageName: node + linkType: hard + +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10c0/5ac176c9d3757abb71400df167f9abb46d63152d5797c630d1a9f083fbabd89711fb4b3dc6de06ff0138fe8946fa5b8518b4fcdae9ca8a3e341417075beae069 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe + languageName: node + linkType: hard + +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + languageName: node + linkType: hard + +"uuid@npm:^8.0.0, uuid@npm:^8.3.0, uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + +"v8-compile-cache@npm:^2.0.3": + version: 2.4.0 + resolution: "v8-compile-cache@npm:2.4.0" + checksum: 10c0/387851192545e7f4d691ba674de90890bba76c0f08ee4909ab862377f556221e75b3a361466490e201203401d64d7795f889882bdabc98b6f3c0bf1038a535be + languageName: node + linkType: hard + +"validate-npm-package-license@npm:3.0.4, validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: "npm:^3.0.0" + spdx-expression-parse: "npm:^3.0.0" + checksum: 10c0/7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f + languageName: node + linkType: hard + +"validate-npm-package-name@npm:5.0.1, validate-npm-package-name@npm:^5.0.0, validate-npm-package-name@npm:^5.0.1": + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 10c0/903e738f7387404bb72f7ac34e45d7010c877abd2803dc2d614612527927a40a6d024420033132e667b1bade94544b8a1f65c9431a4eb30d0ce0d80093cd1f74 + languageName: node + linkType: hard + +"validator@npm:^13.15.26": + version: 13.15.26 + resolution: "validator@npm:13.15.26" + checksum: 10c0/d66041685c531423f6b514d0481228503b96682fe30ed7925ad77ff3cd08c3983dc94f45e18457e44f62f89027b94a3342009d65421800ce65f6e0d2c6eaf7fc + languageName: node + linkType: hard + +"vscode-json-languageservice@npm:^4.1.6": + version: 4.2.1 + resolution: "vscode-json-languageservice@npm:4.2.1" + dependencies: + jsonc-parser: "npm:^3.0.0" + vscode-languageserver-textdocument: "npm:^1.0.3" + vscode-languageserver-types: "npm:^3.16.0" + vscode-nls: "npm:^5.0.0" + vscode-uri: "npm:^3.0.3" + checksum: 10c0/8d91731f75e2cba0d0971be54b1e648590c03287b75115171e964b7b935e20c293d169a20499d36377a7853b2fc6b858fb01645f2fb6a0d4ce2c4b6b1f802b73 + languageName: node + linkType: hard + +"vscode-languageserver-textdocument@npm:^1.0.3": + version: 1.0.12 + resolution: "vscode-languageserver-textdocument@npm:1.0.12" + checksum: 10c0/534349894b059602c4d97615a1147b6c4c031141c2093e59657f54e38570f5989c21b376836f13b9375419869242e9efb4066643208b21ab1e1dee111a0f00fb + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:^3.16.0": + version: 3.17.5 + resolution: "vscode-languageserver-types@npm:3.17.5" + checksum: 10c0/1e1260de79a2cc8de3e46f2e0182cdc94a7eddab487db5a3bd4ee716f67728e685852707d72c059721ce500447be9a46764a04f0611e94e4321ffa088eef36f8 + languageName: node + linkType: hard + +"vscode-nls@npm:^5.0.0": + version: 5.2.0 + resolution: "vscode-nls@npm:5.2.0" + checksum: 10c0/dc9e48f58ebbc807f435d351008813a2ea0c9432d51e778bcac9163c0642f929ddb518411ad654e775ce31e24d6acfa8fb7db8893c05b42c2019894e08b050f9 + languageName: node + linkType: hard + +"vscode-uri@npm:^3.0.3": + version: 3.1.0 + resolution: "vscode-uri@npm:3.1.0" + checksum: 10c0/5f6c9c10fd9b1664d71fab4e9fbbae6be93c7f75bb3a1d9d74399a88ab8649e99691223fd7cef4644376cac6e94fa2c086d802521b9a8e31c5af3e60f0f35624 + languageName: node + linkType: hard + +"walk-up-path@npm:^3.0.1": + version: 3.0.1 + resolution: "walk-up-path@npm:3.0.1" + checksum: 10c0/3184738e0cf33698dd58b0ee4418285b9c811e58698f52c1f025435a85c25cbc5a63fee599f1a79cb29ca7ef09a44ec9417b16bfd906b1a37c305f7aa20ee5bc + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: "npm:^1.0.3" + checksum: 10c0/5b61ca583a95e2dd85d7078400190efd452e05751a64accb8c06ce4db65d7e0b0cde9917d705e826a2e05cc2548f61efde115ffa374c3e436d04be45c889e5b4 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10c0/087038e7992649eaffa6c7a4f3158d5b53b14cf5b6c1f0e043dccfacb1ba179d12f17545d5b85ebd94a42ce280a6fe65d0cbcab70f4fc6daad1dfae85e0e6a3e + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + languageName: node + linkType: hard + +"which@npm:^1.2.9": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + +"which@npm:^2.0.1, which@npm:^2.0.2": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + +"wide-align@npm:1.1.5, wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: "npm:^1.0.2 || 2 || 3 || 4" + checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 + languageName: node + linkType: hard + +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: "npm:^4.0.0" + checksum: 10c0/b1e623adcfb9df35350dd7fc61295d6d4a1eaa65a406ba39c4b8360045b614af95ad10e05abf704936ed022569be438c4bfa02d6d031863c4166a238c301119f + languageName: node + linkType: hard + +"winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" + dependencies: + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10c0/e2990a172e754dbf27e7823772214a22dc8312f7ec9cfba831e5ef30a5d5528792e5ea8f083c7387ccfc5b2af20e3691f64738546c8869086110a26f98671095 + languageName: node + linkType: hard + +"winston@npm:^3.1.0": + version: 3.17.0 + resolution: "winston@npm:3.17.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.7.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.9.0" + checksum: 10c0/ec8eaeac9a72b2598aedbff50b7dac82ce374a400ed92e7e705d7274426b48edcb25507d78cff318187c4fb27d642a0e2a39c57b6badc9af8e09d4a40636a5f7 + languageName: node + linkType: hard + +"wiremock-rest-client@npm:^1.11.0": + version: 1.11.0 + resolution: "wiremock-rest-client@npm:1.11.0" + dependencies: + commander: "npm:^6.2.1" + cross-fetch: "npm:^3.1.5" + https-proxy-agent: "npm:~4.0.0" + json5: "npm:^2.2.0" + loglevel: "npm:^1.8.0" + nanoid: "npm:^3.3.1" + bin: + wrc: bin/index.js + checksum: 10c0/ba86735c2860abd42f6b0026a5393946e6e28f431ce7565d719fed81cf67c0a90fc941d9e5dafea1c08393b45cf43fa4e278c73f473ac6979f82168d302d43ca + languageName: node + linkType: hard + +"wkx@npm:^0.5.0": + version: 0.5.0 + resolution: "wkx@npm:0.5.0" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/9f787ffd2bc83708000f10165a72f0ca121b2e79b279eb44f2f5274eaa6ef819d9e9a00058a3b59dd211fe140d4b47cb6d49683b3a57a2a42ab3a7ccd52247dd + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5, word-wrap@npm:~1.2.3": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + +"workerpool@npm:^9.2.0": + version: 9.3.2 + resolution: "workerpool@npm:9.3.2" + checksum: 10c0/1570bb9a6eb649d477a1a3890e39e6e7dfbec54297151878f4af8a2d54d2bc389a2d796f59619d3326840d6914ceb53b5f4971b475685eb0f189358234bf70ae + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrap-ansi@npm:^9.0.0": + version: 9.0.0 + resolution: "wrap-ansi@npm:9.0.0" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/a139b818da9573677548dd463bd626a5a5286271211eb6e4e82f34a4f643191d74e6d4a9bb0a3c26ec90e6f904f679e0569674ac099ea12378a8b98e20706066 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:5.0.1, write-file-atomic@npm:^5.0.0": + version: 5.0.1 + resolution: "write-file-atomic@npm:5.0.1" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^4.0.1" + checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d + languageName: node + linkType: hard + +"write-file-atomic@npm:^2.4.2": + version: 2.4.3 + resolution: "write-file-atomic@npm:2.4.3" + dependencies: + graceful-fs: "npm:^4.1.11" + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.2" + checksum: 10c0/8cb4bba0c1ab814a9b127844da0db4fb8c5e06ddbe6317b8b319377c73b283673036c8b9360120062898508b9428d81611cf7fa97584504a00bc179b2a580b92 + languageName: node + linkType: hard + +"write-file-atomic@npm:^3.0.0": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: "npm:^0.1.4" + is-typedarray: "npm:^1.0.0" + signal-exit: "npm:^3.0.2" + typedarray-to-buffer: "npm:^3.1.5" + checksum: 10c0/7fb67affd811c7a1221bed0c905c26e28f0041e138fb19ccf02db57a0ef93ea69220959af3906b920f9b0411d1914474cdd90b93a96e5cd9e8368d9777caac0e + languageName: node + linkType: hard + +"write-json-file@npm:^3.2.0": + version: 3.2.0 + resolution: "write-json-file@npm:3.2.0" + dependencies: + detect-indent: "npm:^5.0.0" + graceful-fs: "npm:^4.1.15" + make-dir: "npm:^2.1.0" + pify: "npm:^4.0.1" + sort-keys: "npm:^2.0.0" + write-file-atomic: "npm:^2.4.2" + checksum: 10c0/3eadcb6e832ac34dbba37d4eea8871d9fef0e0d77c486b13ed5f81d84a8fcecd9e1a04277e2691eb803c2bed39c2a315e98b96f492c271acee2836acc6276043 + languageName: node + linkType: hard + +"write-pkg@npm:4.0.0": + version: 4.0.0 + resolution: "write-pkg@npm:4.0.0" + dependencies: + sort-keys: "npm:^2.0.0" + type-fest: "npm:^0.4.1" + write-json-file: "npm:^3.2.0" + checksum: 10c0/8e20db5fa444dad04e3703c18d8e0f89679caa60accbee5da9ea3aa076430b3f32d99f50d8860d29044245775795455c62d12d16a7856d407e30df7b79f39505 + languageName: node + linkType: hard + +"write@npm:1.0.3": + version: 1.0.3 + resolution: "write@npm:1.0.3" + dependencies: + mkdirp: "npm:^0.5.1" + checksum: 10c0/2ab5472e32ce2d25279a9d22365c5dd5b95fe40497ca43fa329aa61687fca56e36837615a1b6adfc4ca540389383185680a23497d75a1698b1dcbb52741d29a4 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0, xtend@npm:~4.0.1": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10c0/308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml@npm:^2.6.0, yaml@npm:^2.6.1, yaml@npm:^2.8.1": + version: 2.8.1 + resolution: "yaml@npm:2.8.1" + bin: + yaml: bin.mjs + checksum: 10c0/7c587be00d9303d2ae1566e03bc5bc7fe978ba0d9bf39cc418c3139d37929dfcb93a230d9749f2cb578b6aa5d9ebebc322415e4b653cb83acd8bc0bc321707f3 + languageName: node + linkType: hard + +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10c0/25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 + languageName: node + linkType: hard + +"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 + languageName: node + linkType: hard + +"yargs-unparser@npm:^2.0.0": + version: 2.0.0 + resolution: "yargs-unparser@npm:2.0.0" + dependencies: + camelcase: "npm:^6.0.0" + decamelize: "npm:^4.0.0" + flat: "npm:^5.0.2" + is-plain-obj: "npm:^2.1.0" + checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 + languageName: node + linkType: hard + +"yargs@npm:17.7.2, yargs@npm:^17.6.2, yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yargs@npm:^15.0.2": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10c0/f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d + languageName: node + linkType: hard + +"yargs@npm:^16.2.0": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: "npm:^7.0.2" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^20.2.2" + checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"yoctocolors-cjs@npm:^2.1.2, yoctocolors-cjs@npm:^2.1.3": + version: 2.1.3 + resolution: "yoctocolors-cjs@npm:2.1.3" + checksum: 10c0/584168ef98eb5d913473a4858dce128803c4a6cd87c0f09e954fa01126a59a33ab9e513b633ad9ab953786ed16efdd8c8700097a51635aafaeed3fef7712fa79 + languageName: node + linkType: hard + +"zod@npm:^4.2.1": + version: 4.2.1 + resolution: "zod@npm:4.2.1" + checksum: 10c0/ecb5219bddf76a42d092a843fb98ad4cb78f1e1077082772b03ef032ee5cbc80790a4051836b962d26fb4af854323bc784d628bd1b8d9898149eba7af21c5560 + languageName: node + linkType: hard